ecere/gui/EditBox: (#1064) Syntax highlighting fix for strings continued with \
[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","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          NotifyCaretMove(master, this, y + 1, x + 1);
2874
2875          SelectionEnables();
2876       }
2877    }
2878
2879    void SelectionEnables()
2880    {
2881       if((x != selX || y != selY) && !selection)
2882       {
2883          if(!style.readOnly)
2884          {
2885             itemEditCut.disabled = false;
2886             itemEditDelete.disabled = false;
2887          }
2888          itemEditCopy.disabled = false;
2889
2890          this.selection = true;
2891       }
2892       else if((x == selX && y == selY) && selection)
2893       {
2894          itemEditCut.disabled = true;
2895          itemEditCopy.disabled = true;
2896          itemEditDelete.disabled = true;
2897
2898          this.selection = false;
2899       }
2900    }
2901
2902    void SetSelectCursor()
2903    {
2904       if(!inactive || !style.noSelect)
2905       {
2906          if(this.mouseMove)
2907             cursor = guiApp.GetCursor(arrow);
2908          else if(this.mouseSelect)
2909             cursor = guiApp.GetCursor(iBeam);
2910          else
2911          {
2912             if(IsMouseOnSelection())
2913                cursor = guiApp.GetCursor(arrow);
2914             else
2915                cursor = guiApp.GetCursor(iBeam);
2916          }
2917       }
2918    }
2919
2920    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2921    {
2922       int c = sc;
2923       int x = px ? *px : 0;
2924       while(true)
2925       {
2926          int start = c;
2927          int numBytes = 1;
2928          int len = 1;
2929          int w;
2930          if(c < Min(max, line.count))
2931          {
2932             byte ch = 0;
2933             int numBytes;
2934             for(len = 0; c < Min(max, line.count); c += numBytes)
2935             {
2936                ch = line.buffer[c];
2937                numBytes = UTF8_NUM_BYTES(ch);
2938
2939                if(ch == ' ' || ch == '\t')
2940                {
2941                   if(!len) c++;
2942                   break;
2943                }
2944                len += numBytes;
2945             }
2946             if(!len && ch == ' ')
2947             {
2948                w = space.w;
2949                len = 1;
2950             }
2951             else if(!len && ch == '\t')
2952             {
2953                w = (tabSize * space.w) - (x % (tabSize * space.w));
2954                len = 1;
2955             }
2956             else
2957                FontExtent(display, font, line.buffer + start, len, &w, null);
2958          }
2959          else
2960          {
2961             if(style.freeCaret && c < max)
2962                w = space.w;
2963             else
2964             {
2965                if(px) *px = x;
2966                return c;
2967             }
2968             c++;
2969          }
2970          if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
2971          {
2972             int lastW;
2973             while(len > 0)
2974             {
2975                int a = start + len;
2976                lastW = w;
2977                if(a <= line.count)
2978                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
2979                else
2980                   a--;
2981                if(a > start)
2982                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
2983                else
2984                   w = 0;
2985                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
2986                len = a - start;
2987             }
2988             if(px) *px = x + w;
2989             return Min(this.maxLineSize - 1, start + len);
2990          }
2991          x += w;
2992       }
2993    }
2994
2995    void SetCursorToViewX()
2996    {
2997       bool selecting = this.x != selX || y != selY;
2998
2999       // Horizontal Adjustment
3000       int x = 0;
3001       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
3002       if(this.x < c)
3003          this.x = x;
3004       else
3005       {
3006          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
3007          if(this.x > c)
3008             this.x = c;
3009      }
3010
3011       if(!selecting)
3012       {
3013          selX = this.x;
3014          selY = y;
3015          selLine = line;
3016       }
3017
3018       UpdateCaretPosition(false);
3019
3020       UpdateDirty();
3021       SetSelectCursor();
3022    }
3023
3024    void SetCursorToViewY()
3025    {
3026       int c, numLines;
3027       EditLine oldLine = this.line;
3028
3029       bool selecting = this.x != this.selX || this.y != this.selY;
3030
3031       numLines = clientSize.h / this.space.h;
3032
3033       // Vertical Adjustment
3034       if(this.viewY > this.y)
3035       {
3036          this.y = this.viewY;
3037          this.line = this.viewLine;
3038       }
3039
3040       if(this.viewY + numLines <=  this.y)
3041       {
3042          EditLine line;
3043
3044          this.y = this.viewY-1;
3045          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3046          {
3047             this.line = line;
3048             this.y++;
3049          }
3050       }
3051
3052       if(this.line != oldLine)
3053       {
3054          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3055          ComputeColumn();
3056       }
3057
3058       if(!selecting)
3059       {
3060          this.selX = this.x;
3061          this.selY = this.y;
3062          this.selLine = this.line;
3063       }
3064
3065       UpdateCaretPosition(false);
3066
3067       UpdateDirty();
3068       SetSelectCursor();
3069    }
3070
3071    /*
3072    bool SaveFile(char * fileName)
3073    {
3074       File f = eFile_Open(fileName, FO_WRITE);
3075       if(f)
3076       {
3077          Save(f, false);
3078          eWindow_SetModified(false);
3079          eInstance_Delete(f);
3080          return true;
3081       }
3082       return false;
3083    }
3084    */
3085
3086    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3087    {
3088       if(line)
3089       {
3090          if(!style.multiLine)
3091          {
3092             x = (active && this.active && !style.readOnly) ? line.count : 0;
3093             selX = 0;
3094             ComputeColumn();
3095             SetViewToCursor(true);
3096             DirtyLine(0);
3097             UpdateDirty();
3098          }
3099          // Update(null);
3100          if(!active && modified)
3101          {
3102             modified = false;
3103             if(!NotifyModified(master, this))
3104             {
3105                modified = true;
3106                *goOnWithActivation = false;
3107             }
3108          }
3109       }
3110       if(!active)
3111       {
3112          ReleaseCapture();
3113          if(timer) timer.Stop();
3114
3115          mouseSelect = false;
3116          wordSelect = false;
3117       }
3118       return true;
3119    }
3120
3121    void AutoSize()
3122    {
3123       int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3124       int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3125       clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3126    }
3127
3128    bool OnResizing(int *w, int *h)
3129    {
3130       if(!*h)
3131          *h = space.h + 2;
3132       if(!*w)
3133          //*w = 80;
3134          *w = space.h * 80 / 14;
3135       return true;
3136    }
3137
3138    void OnResize(int w, int h)
3139    {
3140    /*
3141       if(!hasHorzScroll && !hasVertScroll && viewLine)
3142          SetViewToCursor(true);
3143    */
3144       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3145       if(viewLine)
3146          SetViewToCursor(true);
3147       //else
3148    //     FixScrollArea();
3149    }
3150
3151    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3152    {
3153       if(style.readOnly) return true;
3154       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3155       // Middle button already serves as a dragger as well.
3156       // Paste();
3157       return true;
3158    }
3159
3160    bool OnRightButtonDown(int x, int y, Modifiers mods)
3161    {
3162       this.rightButtonDown = true;
3163       // Copy();
3164       return true;
3165    }
3166
3167    bool OnRightButtonUp(int x, int y, Modifiers mods)
3168    {
3169       // Context Menu
3170       if(!parent.inactive && rightButtonDown)
3171       {
3172          PopupMenu popup;
3173          Menu contextMenu { };
3174
3175          MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3176          MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3177          MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3178          MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3179          MenuDivider { contextMenu };
3180          MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3181
3182          popup = PopupMenu { master = this, menu = contextMenu,
3183    /*
3184             nonClient = true, interim = false, parent = parent,
3185             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3186    */
3187             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3188          };
3189          popup.Create();
3190       }
3191       rightButtonDown = false;
3192       return true;
3193    }
3194
3195    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3196    {
3197       int x,y;
3198       EditLine line;
3199
3200       if(style.noSelect) return true;
3201
3202       // Should we have a separate 'selectOnActivate' style?
3203       if(!mods.isActivate || (style.readOnly && style.multiLine))
3204       {
3205          Capture();
3206          mouseSelect = true;
3207       }
3208
3209       mouseX = mx - XOFFSET;
3210       mouseY = my;
3211
3212       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3213 #ifdef _DEBUG
3214       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3215 #endif
3216       if(!style.readOnly)
3217       {
3218          if(wordSelect)
3219             mouseMove = false;
3220          else if(IsMouseOnSelection() && !mods.isActivate)
3221          {
3222             DirtyLine(this.y);
3223             mouseMove = true;
3224             dropX = x;
3225             dropY = y;
3226             dropLine = line;
3227          }
3228       }
3229
3230       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3231       {
3232          if(mods.shift && !mods.isActivate)
3233          {
3234             this.x = x;
3235             this.y = y;
3236             this.line = line;
3237             DirtyAll();
3238          }
3239          else
3240          {
3241             SelDirty();
3242             DirtyLine(this.y);
3243             this.x = x;
3244             this.y = y;
3245             this.line = line;
3246             DirtyLine(this.y);
3247             _Deselect();
3248          }
3249          ComputeColumn();
3250       }
3251
3252       UpdateDirty();
3253       UpdateCaretPosition(true);
3254       return true;
3255    }
3256
3257    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3258    {
3259       timer.Stop();
3260
3261       mouseSelect = false;
3262       wordSelect = false;
3263
3264       x -= XOFFSET;
3265
3266       ReleaseCapture();
3267       if(!style.readOnly)
3268       {
3269          if(mouseMove)
3270          {
3271             EditLine line;
3272             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3273 #ifdef _DEBUG
3274             //PrintLn("MouseMove: ", x, ", ", y);
3275 #endif
3276             dropX = x;
3277             dropY = y;
3278             dropLine = line;
3279
3280             mouseMove = IsMouseOnSelection();
3281
3282             if(!mouseMove)
3283             {
3284                int size = SelSize();
3285                if(size)
3286                {
3287                   char * text = new char[size+1];
3288                   if(text)
3289                   {
3290                      int moveX = 0;
3291                      GetSel(text, false);
3292
3293                      if(Max(selY, this.y) == dropY)
3294                      {
3295                         if(this.x > selX)
3296                         {
3297                            if(this.dropX > this.selX)
3298                               moveX = this.x - this.selX;
3299                         }
3300                         else
3301                         {
3302                            if(this.dropX > this.x)
3303                               moveX = this.selX - this.x;
3304                         }
3305                      }
3306                      DelSel(null);
3307                      this.dropX -= moveX;
3308                      this.selX = this.x = this.dropX;
3309                      this.selY = this.y = this.dropY;
3310                      this.selLine = this.line = this.dropLine;
3311                      AddS(text);
3312                      SetViewToCursor(true);
3313                      delete text;
3314                      Modified();
3315 #ifdef _DEBUG
3316                    /*  {
3317                         byte * c = ((EditBox)this).multiLineContents, * c2;
3318                         int l1 = c ? strlen(c) : 0, l2;
3319                         Undo();
3320                         Redo();
3321                         c2 = ((EditBox)this).multiLineContents;
3322                         l2 = c2 ? strlen(c2) : 0;
3323                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3324                         {
3325                            PrintLn("Fail!");
3326                         }
3327                         delete c;
3328                      }
3329                      */
3330 #endif
3331                   }
3332                }
3333             }
3334             else
3335             {
3336                SelDirty();
3337                DirtyLine(this.y);
3338                this.x = x;
3339                this.y = y;
3340                this.line = line;
3341                ComputeColumn();
3342                DirtyLine(this.y);
3343                _Deselect();
3344                UpdateDirty();
3345             }
3346          }
3347          else
3348          {
3349             EditLine line;
3350             mouseX = x;
3351             mouseY = y;
3352
3353             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3354 #ifdef _DEBUG
3355             //PrintLn("Dropped: ", x, ", ", y);
3356 #endif
3357             NotifyDropped(master, this, x, y);
3358          }
3359       }
3360       mouseMove = false;
3361       return true;
3362    }
3363
3364    bool OnMouseMove(int mx, int my, Modifiers mods)
3365    {
3366       int x,y;
3367       EditLine line;
3368       bool needScroll;
3369
3370       if(mods != -1 && mods.isSideEffect)
3371       {
3372          SetSelectCursor();
3373          return true;
3374       }
3375       if(style.noSelect) return true;
3376       if(wordSelect) return true;
3377       mouseX = mx - XOFFSET;
3378       mouseY = my;
3379
3380       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3381
3382       if(this.mouseMove || this.mouseSelect)
3383       {
3384          if(!needScroll)
3385             timer.Stop();
3386          else
3387          {
3388             if(needScroll)
3389                timer.Start();
3390             if(mods != -1 &&
3391                ((style.hScroll) || (style.vScroll)))
3392                return true;
3393          }
3394       }
3395
3396       if(this.mouseMove)
3397       {
3398          DirtyLine(this.dropY);
3399          this.dropX = x;
3400          this.dropY = y;
3401          DirtyLine(this.dropY);
3402          this.dropLine = line;
3403          SetViewToCursor(true);
3404 #ifdef _DEBUG
3405          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3406 #endif
3407       }
3408       else if(this.mouseSelect)
3409       {
3410          DirtyLine(this.selY);
3411          DirtyLine(this.y);
3412          this.x = x;
3413          this.y = y;
3414          ComputeColumn();
3415          DirtyLine(this.y);
3416          this.line = line;
3417          SetViewToCursor(true);
3418          UpdateDirty();
3419 #ifdef _DEBUG
3420          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3421 #endif
3422       }
3423       SetSelectCursor();
3424       return true;
3425    }
3426
3427    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3428    {
3429       int x,y;
3430       EditLine line;
3431 #ifdef _DEBUG
3432       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3433 #endif
3434       mx -= XOFFSET;
3435
3436       if(style.noSelect) return true;
3437       FindMouse(mx, my, &x, &y, &line, false);
3438       if(!NotifyDoubleClick(master, this, line, mods))
3439          return false;
3440       if(x < line.count)
3441       {
3442          int c;
3443          int start = -1;
3444          int numBytes;
3445          for(c = x; c >= 0; c--)
3446          {
3447             unichar ch;
3448             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3449             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3450             if(!IS_ALUNDER(ch))
3451                break;
3452             start = c;
3453          }
3454          if(start != -1)
3455          {
3456             for(c = start; c<line.count; c += numBytes)
3457             {
3458                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3459                if(!ch || !IS_ALUNDER(ch))
3460                   break;
3461             }
3462             SelDirty();
3463             DirtyLine(this.y);
3464             this.y = y;
3465             DirtyLine(this.y);
3466             this.selX = start;
3467             this.x = c;
3468             ComputeColumn();
3469             this.line = this.selLine = line;
3470             this.wordSelect = (c != start);
3471             UpdateDirty();
3472          }
3473       }
3474       return true;
3475    }
3476
3477    bool OnPostCreate()
3478    {
3479       /*if(isDocument)
3480       {
3481          Menu fileMenu { menu, "File", F };
3482          saveDialog = fileDialog;
3483          MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3484          MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3485       }*/
3486       if(style.autoSize) AutoSize();
3487       return true;
3488    }
3489
3490    void ComputeFont()
3491    {
3492       if(FontExtent)
3493       {
3494          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3495          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3496
3497          space.w = Max(space.w, 1);
3498          large.w = Max(large.w, 1);
3499          space.h = Max(space.h, 1);
3500          large.h = Max(large.h, 1);
3501
3502          {
3503             EditLine line;
3504             for(line = lines.first; line; line = line.next)
3505                ComputeLength(line);
3506             FindMaxLine();
3507          }
3508
3509          if(viewLine)
3510             SetViewToCursor(true);
3511
3512          FixScrollArea();
3513
3514          Update(null);
3515       }
3516    }
3517
3518    bool OnLoadGraphics()
3519    {
3520       FontExtent = Display::FontExtent;
3521       font = fontObject;
3522       ComputeFont();
3523       // UpdateCaretPosition(true);
3524       return true;
3525    }
3526
3527    void OnUnloadGraphics()
3528    {
3529       this.font = null;
3530    }
3531
3532    bool OnKeyHit(Key key, unichar ch)
3533    {
3534       bool shift = (key.shift) ? true : false;
3535 #ifdef _DEBUG
3536       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3537 #endif
3538       if(!ch && !key.alt && !key.ctrl)
3539       {
3540          key.code = (SmartKey)key.code;
3541       }
3542       else if(!ch && key.alt)
3543          key.code = 0;
3544
3545       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3546       {
3547          case backSpace:
3548             if(style.readOnly) break;
3549             if(style.stuckCaret) GoToEnd(true);
3550             if(!(style.freeCaret))
3551             {
3552                this.x = Min(this.x, this.line.count);
3553             }
3554             if(key.ctrl)
3555             {
3556                int y;
3557                bool done = false;
3558                EditLine line = this.line;
3559                int c;
3560                for(y = this.y; y>= 0; y--)
3561                {
3562                   c = (y == this.y) ? (this.x-1) : line.count-1;
3563
3564                   // Slow down when going on lines...
3565                   if(y != this.y) break;
3566
3567                   for(; c>=0; c--)
3568                   {
3569                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3570                      if(IS_ALUNDER(line.buffer[c]))
3571                         break;
3572                   }
3573                   for(; c>=0; c--)
3574                   {
3575                      if(!IS_ALUNDER(line.buffer[c]))
3576                      {
3577                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3578                         done = true;
3579                         break;
3580                      }
3581                   }
3582                   if(done)
3583                      break;
3584                   if(line.prev)
3585                      line = line.prev;
3586                   else
3587                      break;
3588                }
3589                //if(this.x != 0)
3590                {
3591                   DelCh(line,y,c+1,this.line,this.y,this.x, true);
3592                   this.x = this.selX = Min(c+1, line.count);
3593                   this.y = this.selY = y;
3594                   this.line = this.selLine = line;
3595                   SetViewToCursor(true);
3596                }
3597                ComputeColumn();
3598             }
3599             else
3600             {
3601                BackSpace();
3602             }
3603             return false;
3604             //break;
3605          case del:
3606             if(style.readOnly) break;
3607             if(style.stuckCaret) break;
3608
3609             if(shift)
3610             {
3611                Cut();
3612             }
3613             else
3614             {
3615                // Delete selection
3616                if(this.line != this.selLine || this.x != this.selX)
3617                {
3618                   DelSel(null);
3619                   SetViewToCursor(true);
3620                   Modified();
3621                }
3622                // Delete word
3623                else if(key.ctrl)
3624                {
3625                   if(this.x < this.line.count)
3626                   {
3627                      int i;
3628                      int length;
3629                      for(i = this.x; i < this.line.count; i++)
3630                      {
3631                         if(!IS_ALUNDER(this.line.buffer[i]))
3632                            break;
3633                      }
3634
3635                      for(; i < this.line.count; i++)
3636                      {
3637                         //Delete trailing whitespace
3638                         if(IS_ALUNDER(this.line.buffer[i]))
3639                            break;
3640                      }
3641                      DelCh(this.line, this.y, this.x, this.line, this.y, i, false);
3642                      SetViewToCursor(true);
3643                      Modified();
3644                   }
3645                   else if(this.line.next)
3646                   {
3647                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3648                      SetViewToCursor(true);
3649                      Modified();
3650                   }
3651                }
3652                else
3653                {
3654                   if(!(style.freeCaret))
3655                   {
3656                      this.selX = this.x = Min(this.x, this.line.count);
3657                      ComputeColumn();
3658                   }
3659                   if(this.x < this.line.count)
3660                   {
3661                      DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3662                   }
3663                   else if(this.line.next)
3664                   {
3665                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3666                   }
3667                   SetViewToCursor(true);
3668                   Modified();
3669                }
3670             }
3671             return false;
3672             //break;
3673          case enter:
3674          case keyPadEnter:
3675          {
3676             if(!key.alt && !key.ctrl)
3677             {
3678                int c;
3679                int position = 0;
3680                bool stuffAfter = false;
3681                char * addString;
3682                int len = 0;
3683
3684                if(style.stuckCaret) GoToEnd(true);
3685                if(style.readOnly) break;
3686                if(!(style.multiLine)) break;
3687
3688                for(c = 0; c<this.line.count && c<this.x; c++)
3689                {
3690                   if(this.line.buffer[c] == '\t')
3691                      position += this.tabSize - (position % this.tabSize);
3692                   else if(this.line.buffer[c] == ' ')
3693                      position++;
3694                   else
3695                      break;
3696                }
3697                if(!line.count)
3698                   position = x;
3699
3700                if(this.x < this.line.count)
3701                   stuffAfter = true;
3702
3703                //If last character is a { indent one tab
3704                if(this.line.buffer[this.x - 1] == '{')
3705                {
3706                   //Except if the next non space character is a }
3707                   bool indent = false;
3708                   int i;
3709                   for(i = this.x; i < this.line.size; i++)
3710                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3711                      {
3712                         if(this.line.buffer[i] != '}')
3713                            indent = true;
3714                         break;
3715                      }
3716                   if(indent)
3717                      position += this.tabSize;
3718                }
3719
3720                addString = new char[position + 2];
3721                addString[len++] = '\n';
3722                addString[len] = '\0';
3723
3724                if(stuffAfter || !style.freeCaret)
3725                {
3726                   for(c = 0; c<position; )
3727                   {
3728                      if(style.useTab && c + this.tabSize <= position)
3729                      {
3730                         addString[len++] = '\t';
3731                         c += this.tabSize;
3732                      }
3733                      else
3734                      {
3735                         addString[len++] = ' ';
3736                         c++;
3737                      }
3738                   }
3739                   addString[len] = '\0';
3740                }
3741                if(AddS(addString))
3742                {
3743                   if(!stuffAfter && style.freeCaret)
3744                   {
3745                      this.x = this.selX = position;
3746                      ComputeColumn();
3747                   }
3748                   FindMaxLine();
3749                   SetViewToCursor(true);
3750                   Modified();
3751                }
3752                delete addString;
3753                return false;
3754             }
3755             break;
3756          }
3757          case left:
3758          {
3759             if(style.stuckCaret) break;
3760             if(!(style.freeCaret))
3761             {
3762                this.x = Min(this.x, this.line.count);
3763                this.selX = Min(this.selX, this.selLine.count);
3764                ComputeColumn();
3765             }
3766             if(!shift) SelDirty();
3767             if(key.ctrl)
3768             {
3769                bool foundAlpha = false;
3770                bool found = false;
3771                int y = this.y;
3772                EditLine line, lastLine;
3773                int lastC, lastY;
3774
3775                for(line = this.line; (line && !found); line = line.prev, y--)
3776                {
3777                   int start;
3778                   int c;
3779
3780                   if(this.x == 0 && line != this.line)
3781                   {
3782                      foundAlpha = true;
3783                      lastC = line.count;
3784                      lastY = y;
3785                      lastLine = line;
3786                      break;
3787                   }
3788
3789                   if(line == this.line) start = this.x -1; else start = line.count-1;
3790                   start = Min(start, line.count-1);
3791
3792                   for(c = start; c >= 0;)
3793                   {
3794                      int numBytes;
3795                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3796                      if(IS_ALUNDER(ch))
3797                      {
3798                         foundAlpha = true;
3799                         lastC = c;
3800                         lastY = y;
3801                         lastLine = line;
3802                      }
3803                      else
3804                      {
3805                         if(foundAlpha)
3806                         {
3807                            found = true;
3808                            break;
3809                         }
3810                      }
3811                      while(--c)
3812                      {
3813                         byte ch = line.buffer[c];
3814                         if(UTF8_IS_FIRST(ch)) break;
3815                      }
3816
3817                   }
3818                   // No next word found,
3819                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3820                   {
3821                      foundAlpha = true;
3822                      lastC = 0;
3823                      lastY = y;
3824                      lastLine = line;
3825                      break;
3826                   }
3827                }
3828                if(foundAlpha)
3829                {
3830                   DirtyLine(this.y);
3831                   this.x = lastC;
3832                   this.y = lastY;
3833                   this.line = lastLine;
3834                   DirtyLine(this.y);
3835                   ComputeColumn();
3836                }
3837             }
3838             else
3839             {
3840                if(x > 0)
3841                {
3842                   if(x <= line.count)
3843                   {
3844                      byte * buffer = line.buffer;
3845                      while(--x)
3846                      {
3847                         byte ch = buffer[x];
3848                         if(UTF8_IS_FIRST(ch)) break;
3849                      }
3850                   }
3851                   else
3852                      x--;
3853                   DirtyLine(y);
3854                }
3855                else if(line.prev)
3856                {
3857                   line = line.prev;
3858                   DirtyLine(y);
3859                   x = line.count;
3860                   y--;
3861                   DirtyLine(y);
3862                }
3863                ComputeColumn();
3864             }
3865             if(!shift) _Deselect();
3866             SetViewToCursor(true);
3867             //break;
3868             return false;
3869          }
3870          case right:
3871          {
3872             if(style.stuckCaret) break;
3873             if(!(style.freeCaret))
3874             {
3875                this.x = Min(this.x, this.line.count);
3876                this.selX = Min(this.selX, this.selLine.count);
3877                ComputeColumn();
3878             }
3879             if(!shift) SelDirty();
3880             if(!shift && (this.x != this.selX || this.y != this.selY));
3881             else if(key.ctrl)
3882             {
3883                bool onAChar = false;
3884                if(this.selX != this.x || this.selY != this.y)
3885                   onAChar = true;
3886                if(this.x<this.line.count)
3887                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3888                      onAChar = true;
3889                if(key.shift && onAChar &&
3890                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3891                {
3892                   bool foundAlpha = false;
3893                   bool found = false;
3894                   EditLine line, lastLine;
3895                   int y = this.y;
3896                   int lastC, lastY, lastNumBytes;
3897
3898                   for(line = this.line; (line && !found); line = line.next, y++)
3899                   {
3900                      int start = (line == this.line) ? this.x : 0;
3901                      int c;
3902                      int numBytes;
3903                      unichar ch;
3904                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3905                      {
3906                         if(IS_ALUNDER(ch))
3907                         {
3908                            foundAlpha = true;
3909                            lastC = c;
3910                            lastNumBytes = numBytes;
3911                            lastY = y;
3912                            lastLine = line;
3913                         }
3914                         else if(foundAlpha)
3915                         {
3916                            found = true;
3917                            break;
3918                         }
3919                      }
3920                      if(!found && (c != this.x || line != this.line))
3921                      {
3922                         found = true;
3923                         lastLine = line;
3924                         lastC = line.count;
3925                         lastNumBytes = 0;
3926                         lastY = y;
3927                         break;
3928                      }
3929                   }
3930                   if(found)
3931                   {
3932                      DirtyLine(this.y);
3933                      this.x = lastC + lastNumBytes;
3934                      this.y = lastY;
3935                      this.line = lastLine;
3936                      DirtyLine(this.y);
3937                      ComputeColumn();
3938                   }
3939                }
3940                else
3941                {
3942                   bool foundAlpha = false;
3943                   bool found = false;
3944                   EditLine line;
3945                   int y = this.y;
3946
3947                   for(line = this.line; (line && !found); line = line.next, y++)
3948                   {
3949                      int start = (line == this.line) ? this.x : 0;
3950                      int c;
3951                      int numBytes;
3952                      unichar ch;
3953                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3954                      {
3955                         if(!IS_ALUNDER(ch))
3956                            foundAlpha = true;
3957                         else if(foundAlpha)
3958                         {
3959                            found = true;
3960                            DirtyLine(this.y);
3961                            this.x = c;
3962                            this.y = y;
3963                            this.line = line;
3964                            DirtyLine(this.y);
3965                            ComputeColumn();
3966                            break;
3967                         }
3968                      }
3969                      // No next word found,
3970                      if(!found && (c != this.x || line != this.line))
3971                      {
3972                         found = true;
3973                         DirtyLine(this.y);
3974                         this.x = line.count;
3975                         this.y = y;
3976                         this.line = line;
3977                         DirtyLine(this.y);
3978                         ComputeColumn();
3979                      }
3980                      foundAlpha = true;
3981                   }
3982                }
3983             }
3984             else
3985             {
3986                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
3987                {
3988                   if(x < line.count)
3989                   {
3990                      byte * buffer = line.buffer;
3991                      while(++x)
3992                      {
3993                         byte ch = buffer[x];
3994                         if(UTF8_IS_FIRST(ch)) break;
3995                      }
3996                   }
3997                   else
3998                      x++;
3999                   ComputeColumn();
4000                   DirtyLine(y);
4001                }
4002                else
4003                {
4004                   if(line.next)
4005                   {
4006                      DirtyLine(y);
4007                      line = line.next;
4008                      y++;
4009                      x = 0;
4010                      col = 0;
4011                      DirtyLine(y);
4012                   }
4013                }
4014             }
4015             if(!shift) _Deselect();
4016             SetViewToCursor(true);
4017             // break;
4018             return false;
4019          }
4020          case up:
4021             if(key.ctrl)
4022             {
4023                if(!style.vScroll || hasVertScroll) break;
4024                LineUp();
4025                return false;
4026             }
4027             else
4028             {
4029                if(style.stuckCaret) break;
4030
4031                if(!shift) SelDirty();
4032                DirtyLine(this.y);
4033
4034                if(style.wrap)
4035                {
4036                }
4037                else if(line.prev)
4038                {
4039                   line = line.prev;
4040                   this.y--;
4041                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4042                }
4043
4044                DirtyLine(this.y);
4045                if(!shift) _Deselect();
4046                ComputeColumn();
4047                SetViewToCursor(false);
4048
4049                /*
4050                if(caretY == this.y * space.h)
4051                {
4052                   if(line.prev)
4053                   {
4054                      line = line.prev;
4055                      this.y--;
4056                      if(!style.freeCaret)
4057                         this.x = Min(this.x, line.count);
4058                      caretY = MAXINT;
4059                   }
4060                   else
4061                      return false;
4062                }
4063
4064                {
4065                   int th = space.h;
4066                   int textPos = 0;
4067                   int sx = 0, sy = this.y * space.h;
4068                   char * text = line.text;
4069                   int maxW = clientSize.w - sx;
4070                   display.FontExtent(font, " ", 1, null, &th);
4071
4072                   do
4073                   {
4074                      int startPos = textPos;
4075                      int width = 0;
4076                      int x = 0;
4077                      bool lineComplete = false;
4078
4079                      if(!style.wrap && caretY == MAXINT)
4080                      {
4081                         caretY = sy + th;
4082                         //textPos = line.count;
4083                         //lineComplete = true;
4084                      }
4085
4086                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4087                      {
4088                         int w = 0;
4089                         int len;
4090                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4091
4092                         if(nextSpace)
4093                            len = (nextSpace - (text + textPos));
4094                         else
4095                            len = line.count - textPos;
4096
4097                         if(textPos < line.count)
4098                         {
4099                            display.FontExtent(font, text + textPos, len, &w, null);
4100                         }
4101                         if(nextSpace) { w += space.w; len++; }
4102
4103                         if(style.wrap && x + width + w > maxW && x > 0)
4104                         {
4105                            lineComplete = true;
4106                            break;
4107                         }
4108                         textPos += len;
4109                         width += w;
4110                         if(nextSpace)
4111                         {
4112                            x += width;
4113                            width = 0;
4114                         }
4115                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4116                         {
4117                            x += width;
4118                            this.x = textPos;
4119                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
4120                            {
4121                               int len;
4122                               if(this.x > line.count)
4123                                  this.x--;
4124                               else
4125                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4126                               len = this.x - startPos;
4127                               display.FontExtent(font, text + startPos, len, &x, null);
4128                            }
4129
4130                            DirtyLine(this.y);
4131                            if(!shift) _Deselect();
4132                            ComputeColumn();
4133                            SetViewToCursor(false);
4134                            return false;
4135                         }
4136                      }
4137                      if(sy == caretY - th || textPos >= line.count)
4138                      {
4139                         if(textPos >= line.count)
4140                         {
4141                            int c = textPos - 1;
4142                            while(c > 0 && text[c] == ' ') c--;
4143                            this.x = c + 1;
4144                         }
4145                         else
4146                            this.x = line.count;
4147
4148                         DirtyLine(this.y);
4149                         if(!shift) _Deselect();
4150                         ComputeColumn();
4151                         SetViewToCursor(false);
4152                         return false;
4153                      }
4154                      sy += th;
4155                      sx = 0;
4156                   } while(textPos < line.count);
4157
4158                   DirtyLine(this.y);
4159                   if(!shift) _Deselect();
4160                   ComputeColumn();
4161                   SetViewToCursor(false);
4162                   return false;
4163                }
4164                */
4165
4166                // PREVIOUS CODE
4167                /*
4168                if(this.line.prev)
4169                {
4170                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4171                   if(!shift) SelDirty();
4172                   this.line = this.line.prev;
4173                   DirtyLine(this.y);
4174                   this.y--;
4175
4176                   DirtyLine(this.y);
4177                   this.x = x;
4178                   if(!shift) _Deselect();
4179
4180                   ComputeColumn();
4181
4182                   SetViewToCursor(false);
4183                }
4184                */
4185
4186             }
4187             // break;
4188             return style.multiLine ? false : true;
4189          case down:
4190             if(key.ctrl)
4191             {
4192                if(!style.vScroll || hasVertScroll)
4193                   break;
4194                LineDown();
4195                return false;
4196             }
4197             else
4198             {
4199                if(style.stuckCaret) break;
4200                {
4201                   int th = space.h;
4202                   int textPos = 0;
4203                   int sx = 0, sy = this.y * this.space.h;
4204                   int maxW = clientSize.w - sx;
4205                   char * text = line.buffer;
4206
4207                   if(!shift) SelDirty();
4208                   DirtyLine(this.y);
4209
4210                   if(style.wrap)
4211                   {
4212                      /*
4213                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4214                      {
4215
4216                      }
4217                      */
4218                   }
4219                   else if(line.next)
4220                   {
4221                      line = line.next;
4222                      this.y++;
4223                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4224                   }
4225
4226                   DirtyLine(this.y);
4227                   if(!shift) _Deselect();
4228                   ComputeColumn();
4229                   SetViewToCursor(false);
4230
4231                   /*
4232                   while(!textPos || (style.freeCaret || textPos<line.count))
4233                   {
4234                      int startPos = textPos;
4235                      int width = 0;
4236                      int x = 0;
4237                      bool lineComplete = false;
4238                      if(!style.wrap && sy <= caretY)
4239                      {
4240                         textPos = line.count;
4241                         lineComplete = true;
4242                      }
4243                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4244                      {
4245                         int w = 0;
4246                         int len;
4247                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4248
4249                         if(nextSpace)
4250                            len = (nextSpace - (text + textPos));
4251                         else
4252                            len = line.count - textPos;
4253
4254                         if(textPos < line.count)
4255                         {
4256                            display.FontExtent(font, text + textPos, len, &w, &th);
4257                         }
4258                         if(nextSpace) { w += space.w; len++; }
4259                         if(style.wrap && x + width + w > maxW && x > 0)
4260                         {
4261                            lineComplete = true;
4262                            break;
4263                         }
4264                         textPos += len;
4265                         width += w;
4266                         if(nextSpace)
4267                         {
4268                            x += width;
4269                            width = 0;
4270                         }
4271                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4272                         {
4273                            this.x = textPos;
4274                            x += width;
4275                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4276                            {
4277                               int len;
4278                               if(this.x > line.count)
4279                                  this.x--;
4280                               else
4281                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4282
4283                               len = this.x - startPos;
4284                               display.FontExtent(font, text + startPos, len, &x, null);
4285                            }
4286
4287                            if(!shift) _Deselect();
4288                            ComputeColumn();
4289
4290                            SetViewToCursor(false);
4291                            return false;
4292                         }
4293                      }
4294                      if(sy > caretY)
4295                      {
4296                         this.x = line.count;
4297
4298                         DirtyLine(this.y);
4299                         if(!shift) _Deselect();
4300                         ComputeColumn();
4301
4302                         SetViewToCursor(false);
4303                         return false;
4304                      }
4305                      else if(textPos >= line.count && line.next)
4306                      {
4307                         startPos = 0;
4308                         textPos = 0;
4309                         line = line.next;
4310                         this.y++;
4311                         sy = this.y * this.space.h;
4312                         sx = 0; //textBlock.startX;
4313                         text = line.buffer;
4314                      }
4315                      else
4316                      {
4317                         sy += th;
4318                         sx = 0; //textBlock.startX;
4319                      }
4320                   }
4321                   */
4322                   /*
4323                   if(line.next)
4324                   {
4325                      line = line.next;
4326                      this.x = Min(this.x, line.count);
4327                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4328                      this.y++;
4329                      if(!shift) _Deselect();
4330                      ComputeColumn();
4331
4332                      if(this.selX != this.x || this.selY != this.y)
4333                         DirtyLine(this.y);
4334
4335                      SetViewToCursor(false);
4336                   }
4337                   */
4338                }
4339                /* PREVIOUS CODE
4340                if(this.line.next)
4341                {
4342                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4343                   if(!shift) SelDirty();
4344                   this.line = this.line.next;
4345                   DirtyLine(this.y);
4346                   this.y++;
4347                   this.x = x;
4348                   if(!shift) _Deselect();
4349                   ComputeColumn();
4350
4351                   if(this.selX != this.x || this.selY != this.y)
4352                      DirtyLine(this.y);
4353
4354                   SetViewToCursor();
4355                }
4356                */
4357             }
4358             // break;
4359             return style.multiLine ? false : true;
4360          case home:
4361          {
4362             if(style.stuckCaret) break;
4363             if(!style.multiLine && key.ctrl) break;
4364             if(!(style.freeCaret))
4365                this.selX = Min(this.selX, this.selLine.count);
4366
4367             if(!shift) SelDirty();
4368             if(key.ctrl)
4369             {
4370                this.line = this.lines.first;
4371                if(this.y != 0 || this.x != 0)
4372                   DirtyAll();
4373                this.y = 0;
4374                this.x = 0;
4375                this.col = 0;
4376             }
4377             else
4378             {
4379                if(style.smartHome)
4380                {
4381                   EditLine line = this.line;
4382                   int c;
4383                   for(c=0; line.buffer[c]; c++)
4384                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4385                         break;
4386                   if(shift && (c != 0 || this.x))
4387                      DirtyLine(this.y);
4388                   if(this.x != c)
4389                      this.x = c;
4390                   else
4391                      this.x = 0;
4392                }
4393                else
4394                {
4395                   if(shift && this.x != 0)
4396                      DirtyLine(this.y);
4397                   this.x = 0;
4398                }
4399                ComputeColumn();
4400             }
4401             if(!shift) _Deselect();
4402             SetViewToCursor(true);
4403             //break;
4404             return false;
4405          }
4406          case end:
4407          {
4408             if(style.stuckCaret) break;
4409             if(!style.multiLine && key.ctrl) break;
4410             if(!style.freeCaret)
4411                this.selX = Min(this.selX, this.selLine.count);
4412
4413             if(!shift) SelDirty();
4414             if(key.ctrl)
4415             {
4416                GoToEnd(false);
4417             }
4418             else if(this.x != this.line.count)
4419             {
4420                this.x = this.line.count;
4421                if(shift)
4422                   DirtyLine(this.y);
4423                ComputeColumn();
4424             }
4425             if(!shift) _Deselect();
4426             SetViewToCursor(true);
4427             //break;
4428             return false;
4429          }
4430          case tab:
4431             if(style.tabKey && !key.ctrl)
4432             {
4433                if(this.selY != this.y && style.tabSel)
4434                {
4435                   EditLine firstLine, lastLine;
4436                   EditLine line;
4437                   int y, x;
4438
4439                   // Do multi line selection tabbing here
4440                   if(this.selY < this.y)
4441                   {
4442                      firstLine = this.selLine;
4443                      lastLine = this.line;
4444                      y = this.selY;
4445                      x = this.x;
4446                   }
4447                   else
4448                   {
4449                      // Selecting going up
4450                      firstLine = this.line;
4451                      lastLine = this.selLine;
4452                      y = this.y;
4453                      x = this.selX;
4454                   }
4455                   ComputeColumn();
4456                   if(shift)
4457                   {
4458                      for(line = firstLine; line; line = line.next, y++)
4459                      {
4460                         if(line != lastLine || x)
4461                         {
4462                            int c;
4463                            int lastC = 0;
4464                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4465
4466                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4467                            {
4468                               if(line.buffer[c] == '\t')
4469                               {
4470                                  lastC++;
4471                                  break;
4472                               }
4473                               else if(line.buffer[c] != ' ')
4474                                  break;
4475                            }
4476                            after.x = lastC;
4477
4478                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4479                            if(lastC)
4480                            {
4481                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4482                               char * string = new char[len];
4483                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4484                               GetText(string, line, y, 0, line, y, lastC, false, false);
4485                               Record(action);
4486                            }
4487                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4488                            if(!line.AdjustBuffer(line.count-lastC))
4489                               break;
4490                            line.count-=lastC;
4491                            DirtyLine(y);
4492                         }
4493
4494                         if(line == lastLine) break;
4495                      }
4496                   }
4497                   else
4498                   {
4499                      for(line = firstLine; line; line = line.next, y++)
4500                      {
4501                         if(line.count)
4502                         {
4503                            if(line != lastLine || x)
4504                            {
4505                               if(style.useTab)
4506                               {
4507                                  if(line.count + 1 <= this.maxLineSize)
4508                                  {
4509                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4510
4511                                     if(!line.AdjustBuffer(line.count+1))
4512                                        break;
4513
4514                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4515                                     {
4516                                        AddCharAction action { ch = '\t', x = 0, y = y };
4517                                        Record(action);
4518                                     }
4519
4520                                     memmove(line.buffer+1,line.buffer,line.size-1);
4521                                     line.count++;
4522                                     line.buffer[0] = '\t';
4523                                  }
4524                               }
4525                               else
4526                               {
4527                                  if(line.count + this.tabSize <= this.maxLineSize)
4528                                  {
4529                                     int c;
4530                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4531                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4532
4533                                     if(!line.AdjustBuffer(line.count+this.tabSize))
4534                                        break;
4535
4536                                     {
4537                                        char * string = new char[this.tabSize + 1];
4538                                        memset(string, ' ', this.tabSize);
4539                                        string[this.tabSize] = '\0';
4540                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4541                                     }
4542
4543                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4544                                     line.count+=this.tabSize;
4545                                     for(c=0; c<this.tabSize; c++)
4546                                        line.buffer[c] = ' ';
4547                                  }
4548                               }
4549                               DirtyLine(y);
4550                            }
4551                         }
4552                         if(line == lastLine) break;
4553                      }
4554                   }
4555                   ComputeLength(maxLine);
4556                }
4557                else
4558                {
4559                   if(style.useTab)
4560                   {
4561                      // Insert character
4562                      AddCh('\t');
4563                   }
4564                   else
4565                   {
4566                      int start, c;
4567                      char * addString = new char[this.tabSize + 1];
4568                      int len = 0;
4569                      if(!(style.freeCaret))
4570                      {
4571                         this.x = Min(this.x, this.line.count);
4572                         ComputeColumn();
4573                      }
4574                      // Insert spaces
4575                      start = Min(this.x, this.selX);
4576                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4577                      {
4578                         addString[len++] = ' ';
4579                      }
4580                      addString[len] = 0;
4581                      AddS(addString);
4582                      delete addString;
4583                   }
4584                }
4585                Modified();
4586                SetViewToCursor(true);
4587                return false;
4588             }
4589             break;
4590          case pageDown:
4591             if(style.multiLine)
4592             {
4593                if(key.ctrl)
4594                {
4595                   if(!(style.hScroll) || hasHorzScroll) break;
4596                   if(this.viewX < this.maxLength)
4597                   {
4598                      //this.viewX+=this.space.w*this.tabSize;
4599                      //DirtyAll();
4600                      SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4601                   }
4602                }
4603                else
4604                {
4605                   PageDown();
4606                   DirtyAll();
4607                   if(!shift) _Deselect();
4608                   SetCursorToViewX();
4609                   SetCursorToViewY();
4610                }
4611                return false;
4612             }
4613             break;
4614          case pageUp:
4615             if(style.multiLine)
4616             {
4617                if(key.ctrl)
4618                {
4619                   if(!(style.hScroll) || hasHorzScroll) break;
4620                   if(this.viewX > 0)
4621                   {
4622                      //this.viewX-=this.space.w*this.tabSize;
4623                      //this.viewX = Max(this.viewX,0);
4624                      //DirtyAll();
4625                      SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4626                      // SetCursorToView();
4627                   }
4628                }
4629                else
4630                {
4631                   PageUp();
4632                   DirtyAll();
4633                   if(!shift) _Deselect();
4634                   SetCursorToViewX();
4635                   SetCursorToViewY();
4636                }
4637                return false;
4638             }
4639             break;
4640          case insert:
4641             if(key.ctrl)
4642             {
4643                Copy();
4644                return false;
4645             }
4646             else if(key.shift)
4647             {
4648                if(!(style.readOnly))
4649                   Paste();
4650                return false;
4651             }
4652             else
4653             {
4654                this.overwrite ^= 1;
4655                UpdateCaretPosition(true);
4656                if(this.overwrite)
4657                   SetCaret(0,0,0);
4658                DirtyLine(this.y);
4659                UpdateDirty();
4660                NotifyOvrToggle(master, this, this.overwrite);
4661             }
4662             break;
4663          case hotKey:
4664             break;
4665          default:
4666
4667             switch(key)
4668             {
4669                case ctrlA:
4670                   // Select All
4671                   if(style.noSelect) break;
4672
4673                   {
4674                      //Save current view position
4675                      int tempX = this.viewX;
4676                      int tempY = this.viewY;
4677
4678                      this.selX = 0;
4679                      this.selY = 0;
4680                      this.selLine = this.lines.first;
4681                      this.y = this.lineCount-1;
4682                      this.line = this.lines.last;
4683                      this.x = this.line.count;
4684                      ComputeColumn();
4685                      DirtyAll();
4686                      SetViewToCursor(true);
4687
4688                      //Restore previous view position
4689                      SetScrollPosition(tempX, tempY * this.space.h);
4690
4691                      UpdateDirty();
4692                      SetSelectCursor();
4693                      SelectionEnables();
4694                   }
4695                   // TOCHECK: Was there any good reason why we weren't returning false here?
4696                   return false; // break;
4697                case ctrlC:
4698                   Copy();
4699                   return false;
4700                case ctrlV:
4701                   if(!(style.readOnly))
4702                   {
4703                      Paste();
4704                      return false;
4705                   }
4706                   break;
4707                case ctrlX:
4708                {
4709                   if(style.readOnly) break;
4710                   Cut();
4711                   return false;
4712                }
4713                case ctrlL:
4714                {
4715                   if(style.readOnly) break;
4716                   ClearLine();
4717                   return false;
4718                }
4719                case ctrlZ:
4720                   if(style.readOnly) break;
4721                   Undo();
4722                   return false;
4723                case ctrlY:
4724                   if(style.readOnly) break;
4725                   Redo();
4726                   return false;
4727                default:
4728                   if(style.readOnly) break;
4729                   if(key.shift && key.code == rightBracket)
4730                   {
4731                      //Only indent back if you are exactly at one tab.
4732                      {
4733                         bool whitespace = true;
4734                         int i;
4735                         char * newline;
4736                         int putsize;
4737
4738                         int indentwidth;
4739                         EditLine line = this.line;
4740
4741                         //Only remove one tab if there is nothing else on the line.
4742                         for(i = 0; i < this.line.count; i++)
4743                         {
4744                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4745                            {
4746                               PutCh(ch);
4747                               return false;
4748                            }
4749                         }
4750
4751                         //Find opening {
4752                         i = 1;
4753                         while(i && line)
4754                         {
4755                            int pos;
4756
4757                            indentwidth = 0;
4758                            for(pos = line.count - 1; pos >= 0; pos--)
4759                            {
4760                               char c = line.buffer[pos];
4761                               if(c == ' ')
4762                                  indentwidth++;
4763                               else if(c == '\t')
4764                                  indentwidth += this.tabSize;
4765                               else
4766                                  //Counting backwards, so when you find a character, indentation is reset
4767                                  indentwidth = 0;
4768                               if(i && c == '}')
4769                                  i++;
4770                               if(i && c == '{')
4771                                  i--;
4772                            }
4773                            line = line.prev;
4774                         }
4775
4776                         //Place the } to get an undo:
4777                         PutCh(ch);
4778
4779                         this.x = this.line.count;
4780                         this.selX = 0;
4781
4782                         if(!style.useTab)
4783                            putsize = indentwidth;
4784                         else
4785                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4786
4787                         newline = new char[putsize+2];
4788                         newline[putsize] = '}';
4789                         newline[putsize+1] = '\0';
4790
4791                         i = 0;
4792                         if(style.useTab)
4793                            for(; i < indentwidth / this.tabSize; i++)
4794                               newline[i] = '\t';
4795                         for(;i < putsize; i++)
4796                            newline[i] = ' ';
4797
4798                         AddS(newline);
4799
4800                         delete newline;
4801                      }
4802                      return false;
4803                   }
4804                   else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4805                   {
4806                      PutCh(ch);
4807                      return false;
4808                   }
4809                   break;
4810             }
4811             break;
4812       }
4813       return true;
4814    }
4815
4816    void OnHScroll(ScrollBarAction action, int position, Key key)
4817    {
4818 #ifdef _DEBUG
4819       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4820 #endif
4821       this.viewX = position;
4822       if(action != setRange)
4823       {
4824          if(!this.mouseMove && style.cursorFollowsView)
4825             SetCursorToViewX();
4826       }
4827       DirtyAll();
4828       UpdateDirty();
4829    }
4830
4831    void OnVScroll(ScrollBarAction action, int position, Key key)
4832    {
4833       int oldViewY = this.viewY;
4834
4835 #ifdef _DEBUG
4836       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4837 #endif
4838       position /= this.space.h;
4839
4840       if(position < this.viewY)
4841       {
4842          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4843          style.recomputeSyntax = true;
4844       }
4845       else if(position > this.viewY)
4846       {
4847          EditLine oldViewLine = viewLine;
4848          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4849          FigureStartSyntaxStates(oldViewLine, false);
4850       }
4851
4852       if(action != setRange)
4853       {
4854          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4855       }
4856
4857       if(this.x != this.selX || this.y != this.selY)
4858          DirtyLine(this.y);
4859
4860       {
4861          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4862
4863          /*
4864          int numLines = clientSize.h / this.space.h;
4865          int y;
4866          if(Abs(this.viewY - oldViewY) < numLines)
4867
4868
4869          if(this.viewY > oldViewY)
4870          {
4871             for(y = oldViewY; y <this.viewY; y++)
4872                DirtyLine(y + numLines);
4873          }
4874          else
4875          {
4876             for(y = this.viewY; y <oldViewY; y++)
4877                DirtyLine(y + numLines);
4878          }*/
4879       }
4880       //DirtyAll();
4881
4882       // Fix dirt of stuff before first line...
4883       if(this.viewY - oldViewY > 0)
4884       {
4885          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4886          Update(box);
4887       }
4888
4889       UpdateDirty();
4890    }
4891
4892    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4893    {
4894       EditLine line;
4895       int length, endX;
4896       bool result;
4897       ReplaceTextAction replaceAction = null;
4898       AddCharAction addCharAction = null;
4899       int addedSpaces = 0, addedTabs = 0;
4900
4901       if(ch == '\r') return true;
4902       if(style.stuckCaret /*|EES_READONLY)*/ )
4903          GoToEnd(true);
4904
4905       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4906
4907       if(!undoBuffer.dontRecord)
4908       {
4909          if(selX != x || selY != y)
4910          {
4911             char buffer[5];
4912             char * newString;
4913             char * oldString;
4914             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4915             oldString = new char[len];
4916             UTF32toUTF8Len(&ch, 1, buffer, 4);
4917             newString = CopyString(buffer);
4918             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4919
4920             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4921             if(selY < y || (selY == y && selX < x))
4922             {
4923                replaceAction.x1 = selX;
4924                replaceAction.y1 = selY;
4925                replaceAction.x2 = x;
4926                replaceAction.y2 = y;
4927             }
4928             else
4929             {
4930                replaceAction.x1 = x;
4931                replaceAction.y1 = y;
4932                replaceAction.x2 = selX;
4933                replaceAction.y2 = selY;
4934             }
4935             Record(replaceAction);
4936             undoBuffer.dontRecord++;
4937          }
4938          else
4939          {
4940             addCharAction = AddCharAction { y = y, x = x, ch = ch };
4941             Record(addCharAction);
4942          }
4943       }
4944
4945       if(ch == '\n')
4946       {
4947          DelSel(&addedSpaces);
4948          if(this.lineCount+1 > this.maxLines)
4949          {
4950             if(style.autoEmpty)
4951                Emptyline(this.lines.first,0);
4952             else
4953                return false;
4954          }
4955          if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
4956          {
4957             // Make sure it fits, but we need a default line is this.font is too big for window
4958             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
4959                return false;
4960          }
4961          if((this.y >= 0) && this.y < this.viewY)
4962          {
4963             this.viewY++;
4964             DirtyAll();
4965          }
4966          line = EditLine { };
4967          if(!line)
4968             return false;
4969          lines.Insert(this.line, line);
4970          line.editBox = this;
4971          line.buffer = null;
4972          line.count = 0;
4973          line.size = 0;
4974          length = 0;
4975
4976          // If we're displacing the lines from a current line ...
4977          if(this.line)
4978          {
4979             if(this.line.buffer)
4980             {
4981                endX = this.x;
4982                if(this.line.count < endX) endX = this.line.count;
4983                length = this.line.count - endX;
4984             }
4985          }
4986          if(!line.AdjustBuffer(length))
4987             return false;
4988          if(this.line)
4989          {
4990             if(this.line.buffer)
4991             {
4992                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
4993 #ifdef _DEBUG
4994       if(endX > 4000 || endX < 0)
4995          printf("Warning");
4996 #endif
4997                this.line.count = endX;
4998                this.line.buffer[this.line.count] = '\0';
4999                this.line.AdjustBuffer(this.line.count);
5000                ComputeLength(this.line);
5001             }
5002          }
5003          {
5004             BufferLocation before = { this.line, this.y, this.x }, after;
5005
5006             this.line = line;
5007             this.x = 0;
5008             this.col = 0;
5009             line.count = length;
5010
5011 #ifdef _DEBUG
5012       if(length > 4000 || length < 0)
5013          printf("Warning");
5014 #endif
5015             ComputeLength(this.line);
5016
5017             DirtyEnd(this.y);
5018             this.y++;
5019             this.lineCount++;
5020             line.buffer[line.count] = '\0';
5021             result = true;
5022
5023             after.line = this.line, after.y = this.y, after.x = this.x;
5024
5025             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
5026          }
5027       }
5028       else
5029       {
5030          char string[5];
5031          int count = UTF32toUTF8Len(&ch, 1, string, 5);
5032          DelSel(&addedSpaces);
5033          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5034          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5035          if(addedTabsPtr) *addedTabsPtr = addedTabs;
5036       }
5037       this.selX = this.x;
5038       this.selY = this.y;
5039       this.selLine = this.line;
5040
5041       if(replaceAction)
5042       {
5043          replaceAction.x3 = x;
5044          replaceAction.y3 = y;
5045          replaceAction.addedSpaces = addedSpaces;
5046          replaceAction.addedTabs = addedTabs;
5047          undoBuffer.dontRecord--;
5048       }
5049       if(addCharAction)
5050       {
5051          addCharAction.x -= addedTabs * (tabSize-1);
5052          addCharAction.addedSpaces = addedSpaces;
5053          addCharAction.addedTabs = addedTabs;
5054       }
5055       return result;
5056    }
5057
5058 public:
5059
5060    /****************************************************************************
5061                                  EDIT BOX METHODS
5062    ****************************************************************************/
5063
5064
5065    bool AddCh(unichar ch)
5066    {
5067       return _AddCh(ch, null, null);
5068    }
5069
5070    void Modified()
5071    {
5072       this.modified = true;
5073       NotifyUpdate(master, this);
5074       modifiedDocument = true;
5075    }
5076
5077    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5078    {
5079       Deselect();
5080       DelCh(line1, y1, x1, line2, y2, x2, false);
5081       SetViewToCursor(true);
5082       UpdateDirty();
5083       Modified();
5084    }
5085
5086    void Undo()
5087    {
5088       undoBuffer.Undo();
5089       itemEditUndo.disabled = undoBuffer.curAction == 0;
5090       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5091       if(savedAction == undoBuffer.curAction)
5092       {
5093          modifiedDocument = false;
5094          SetModified(false);
5095          NotifyUnsetModified(master, this);
5096       }
5097    }
5098
5099    void Redo()
5100    {
5101       undoBuffer.Redo();
5102       itemEditUndo.disabled = undoBuffer.curAction == 0;
5103       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5104       if(savedAction == undoBuffer.curAction)
5105       {
5106          modifiedDocument = false;
5107          SetModified(false);
5108          NotifyUnsetModified(master, this);
5109       }
5110    }
5111
5112    void Record(UndoAction action)
5113    {
5114       if(!undoBuffer.dontRecord)
5115       {
5116          undoBuffer.Record(action);
5117          itemEditUndo.disabled = undoBuffer.curAction == 0;
5118          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5119       }
5120    }
5121
5122    void SelectAll()
5123    {
5124       Select(
5125          this.lines.first, 0,0,
5126          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5127    }
5128
5129    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5130    {
5131       SelDirty();
5132       this.selX = x1;
5133       this.selY = y1;
5134       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5135       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5136       this.y = line2 ? y2 : (this.lineCount-1);
5137       this.line = line2 ? (EditLine)line2 : this.lines.last;
5138       ComputeColumn();
5139       SelDirty();
5140       SetViewToCursor(true);
5141       UpdateDirty();
5142    }
5143
5144    // TODO: Fix this vs modifiedDocument window property
5145    void SetModified(bool flag)
5146    {
5147       if(this)
5148       {
5149          this.modified = false;
5150          if(flag && !NotifyModified(master, this))
5151             this.modified = true;
5152       }
5153    }
5154
5155    // BASIC OUTPUT
5156    bool AddS(char * string)
5157    {
5158       if(this)
5159       {
5160          bool ret = true;
5161          char * line;
5162          int c, count;
5163          int addedSpaces = 0, addedTabs = 0;
5164          AddTextAction action = null;
5165          ReplaceTextAction replaceAction = null;
5166
5167          this.pasteOperation = true;
5168
5169          if(style.stuckCaret /*|EES_READONLY)*/ )
5170             GoToEnd(true);
5171
5172          if(!undoBuffer.dontRecord)
5173          {
5174             char * placeString = CopyString(string);
5175             if(!style.multiLine)
5176             {
5177                int i;
5178                char ch;
5179                for(i = 0; (ch = placeString[i]); i++)
5180                   if(ch == '\n')
5181                   {
5182                      placeString[i] = '\0';
5183                      placeString = renew placeString byte[i+1];
5184                      break;
5185                   }
5186             }
5187
5188             if(selX != x || selY != y)
5189             {
5190                char * newString;
5191                char * oldString;
5192                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5193                oldString = new char[len];
5194                newString = placeString;
5195                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5196
5197                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5198                if(selY < y || (selY == y && selX < x))
5199                {
5200                   replaceAction.x1 = selX;
5201                   replaceAction.y1 = selY;
5202                   replaceAction.x2 = x;
5203                   replaceAction.y2 = y;
5204                }
5205                else
5206                {
5207                   replaceAction.x1 = x;
5208                   replaceAction.y1 = y;
5209                   replaceAction.x2 = selX;
5210                   replaceAction.y2 = selY;
5211                }
5212                Record(replaceAction);
5213             }
5214             else if(string[0])
5215             {
5216                if(string[0] == '\n')
5217                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5218                else
5219                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5220
5221                Record(action);
5222             }
5223             else
5224                delete placeString;
5225          }
5226
5227          undoBuffer.dontRecord++;
5228          DelSel(&addedSpaces);
5229
5230          count = 0;
5231          line = string;
5232          for(c = 0; string[c]; c++)
5233          {
5234             if(string[c] == '\n' || string[c] == '\r')
5235             {
5236                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5237                {
5238                   ret = false;
5239                   break;
5240                }
5241                if(string[c] == '\n')
5242                {
5243                   if(!AddCh('\n'))
5244                   {
5245                      count = 0;
5246                      ret = false;
5247                      break;
5248                   }
5249                }
5250                // Reset for next line
5251                count = 0;
5252                line = string+c+1;
5253                /*
5254                if(string[c] == '\r' && *line == '\n')
5255                {
5256                   line++;
5257                   c++;
5258                }*/
5259             }
5260             else
5261             {
5262                count++;
5263             }
5264          }
5265
5266          // Why was this here?
5267          // FindMaxLine();
5268
5269          // Add the line here
5270          if(ret && count)
5271             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5272             {
5273                ret = false;
5274             }
5275          FindMaxLine();
5276
5277          undoBuffer.dontRecord--;
5278          if(action)
5279          {
5280             action.y2 = y;
5281             action.x2 = x;
5282             action.addedSpaces = addedSpaces;
5283             action.addedTabs = addedTabs;
5284          }
5285          else if(replaceAction)
5286          {
5287             replaceAction.y3 = y;
5288             replaceAction.x3 = x;
5289             replaceAction.addedSpaces = addedSpaces;
5290             replaceAction.addedTabs = addedTabs;
5291          }
5292
5293          UpdateCaretPosition(true);
5294
5295          this.pasteOperation = false;
5296
5297          return ret;
5298       }
5299       return false;
5300    }
5301
5302    // CONSOLE OUTPUT
5303    void Clear()
5304    {
5305       if(this)
5306       {
5307          Deselect();
5308          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5309          SetViewToCursor(true);
5310          UpdateDirty();
5311          Modified();
5312       }
5313    }
5314
5315    void PutCh(unichar ch)
5316    {
5317       bool result;
5318
5319       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5320       //if((ch >= 32) || ch == '\n')
5321       {
5322          int addedSpaces = 0, addedTabs = 0;
5323          ReplaceTextAction replaceAction = null;
5324          AddCharAction addCharAction = null;
5325
5326          if(style.allCaps)
5327             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5328
5329          if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5330          {
5331             char buffer[5];
5332             char * newString;
5333             char * oldString;
5334             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5335             oldString = new char[len];
5336             UTF32toUTF8Len(&ch, 1, buffer, 4);
5337             newString = CopyString(buffer);
5338             GetText(oldString, line, y, x, line, y, x+1, false, false);
5339             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5340             replaceAction.x1 = x;
5341             replaceAction.y1 = y;
5342             replaceAction.x2 = x+1;
5343             replaceAction.y2 = y;
5344             Record(replaceAction);
5345
5346             undoBuffer.dontRecord++;
5347             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5348             undoBuffer.dontRecord--;
5349          }
5350          else if(!undoBuffer.dontRecord)
5351          {
5352             if(selX != x || selY != y)
5353             {
5354                char buffer[5];
5355                char * newString;
5356                char * oldString;
5357                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5358                oldString = new char[len];
5359                UTF32toUTF8Len(&ch, 1, buffer, 4);
5360                newString = CopyString(buffer);
5361                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5362                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5363                if(selY < y || (selY == y && selX < x))
5364                {
5365                   replaceAction.x1 = selX;
5366                   replaceAction.y1 = selY;
5367                   replaceAction.x2 = x;
5368                   replaceAction.y2 = y;
5369                }
5370                else
5371                {
5372                   replaceAction.x1 = x;
5373                   replaceAction.y1 = y;
5374                   replaceAction.x2 = selX;
5375                   replaceAction.y2 = selY;
5376                }
5377                Record(replaceAction);
5378             }
5379             else
5380             {
5381                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5382                Record(addCharAction);
5383             }
5384          }
5385          undoBuffer.dontRecord++;
5386          result = _AddCh(ch, &addedSpaces, &addedTabs);
5387          if(replaceAction)
5388          {
5389             replaceAction.x3 = x;
5390             replaceAction.y3 = y;
5391             replaceAction.addedSpaces = addedSpaces;
5392             replaceAction.addedTabs = addedTabs;
5393          }
5394          if(addCharAction)
5395          {
5396             addCharAction.x -= addedTabs * (tabSize-1);
5397             addCharAction.addedSpaces = addedSpaces;
5398             addCharAction.addedTabs = addedTabs;
5399          }
5400          undoBuffer.dontRecord--;
5401          if(ch == '\n')
5402             FindMaxLine();
5403          Modified();
5404          if(result) SetViewToCursor(true);
5405       }
5406    }
5407
5408    void PutS(char * string)
5409    {
5410       if(this)
5411       {
5412          AddS(string);
5413          SetViewToCursor(true);
5414          Modified();
5415       }
5416    }
5417
5418    void Printf(char * format, ...)
5419    {
5420       if(this)
5421       {
5422          char temp[MAX_F_STRING];
5423          va_list args;
5424          va_start(args, format);
5425          vsnprintf(temp, sizeof(temp), format, args);
5426          temp[sizeof(temp)-1] = 0;
5427          va_end(args);
5428          PutS(temp);
5429       }
5430    }
5431
5432    void SetContents(char * format, ...)
5433    {
5434       if(this)
5435       {
5436          undoBuffer.dontRecord++;
5437          Deselect();
5438          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5439          if(format)
5440          {
5441             char temp[MAX_F_STRING];
5442             va_list args;
5443             va_start(args, format);
5444             vsnprintf(temp, sizeof(temp), format, args);
5445             temp[sizeof(temp)-1] = 0;
5446             va_end(args);
5447
5448             AddS(temp);
5449          }
5450          UpdateDirty();
5451          Home();
5452          undoBuffer.dontRecord--;
5453       }
5454    }
5455
5456    void BackSpace()
5457    {
5458       if(!DelSel(null))
5459       {
5460          if(x > 0)
5461          {
5462             x -= 1 + DelCh(line, y, x-1, line, y, x, true);
5463             Modified();
5464          }
5465          else if(this.line.prev)
5466          {
5467             EditLine line = this.line.prev;
5468             int x = line.count;
5469             int y = this.y;
5470
5471             DelCh(line, this.y-1, x, this.line, this.y, this.x, true);
5472             this.line = line;
5473             this.y = y-1;
5474             this.x = x;
5475             Modified();
5476          }
5477          this.selX = this.x;
5478          this.selY = this.y;
5479          this.selLine = this.line;
5480          ComputeColumn();
5481       }
5482       else
5483          Modified();
5484       SetViewToCursor(true);
5485    }
5486
5487    void ClearLine()
5488    {
5489       Emptyline(this.line,this.y);
5490       this.selX = this.x = 0;
5491       this.col = 0;
5492       this.selY = this.y;
5493       this.selLine = this.line;
5494
5495       SetViewToCursor(true);
5496       Modified();
5497    }
5498
5499    // CARET CONTROL
5500    void End()
5501    {
5502       if(this)
5503       {
5504          GoToEnd(true);
5505          SetViewToCursor(true);
5506       }
5507    }
5508    void Home()
5509    {
5510       if(this)
5511       {
5512          GoToHome(true);
5513          SetViewToCursor(true);
5514       }
5515    }
5516
5517    bool GoToLineNum(int lineNum)
5518    {
5519       if(this.line)
5520       {
5521          int c;
5522          EditLine line = this.lines.first;
5523          for(c = 0; c < lineNum && line; c++, line = line.next);
5524          if(line)
5525          {
5526             if(this.y != c)
5527                DirtyAll();
5528             else
5529                DirtyLine(c);
5530             this.y = c;
5531             this.line = line;
5532             _Deselect();
5533             SetViewToCursor(true);
5534             return true;
5535          }
5536       }
5537       return false;
5538    }
5539
5540    bool GoToPosition(EditLine line, int y, int x)
5541    {
5542       /*
5543       if(!line)
5544       {
5545          line = this.line;
5546          y = this.y;
5547       }
5548       */
5549       if(!line)
5550       {
5551          int c;
5552          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5553       }
5554
5555       if(line)
5556       {
5557          if(this.y != y)
5558             DirtyAll();
5559          else
5560             DirtyLine(y);
5561          this.x = x;
5562          this.y = y;
5563          this.line = line;
5564          ComputeColumn();
5565          _Deselect();
5566          SetViewToCursor(true);
5567          return true;
5568       }
5569       return false;
5570    }
5571
5572    // VIEW POSITIONING
5573    void SetViewToCursor(bool setCaret)
5574    {
5575       if(created)
5576       {
5577          int w;
5578          int c, numLines;
5579          EditLine line;
5580          int x;
5581          int checkX, checkY;
5582          EditLine checkLine;
5583          bool dontScroll = false;
5584          bool selected;
5585          int viewX, viewY;
5586
5587          FixScrollArea();
5588
5589          selected = selX != this.x || selY != y;
5590
5591          viewX = this.viewX;
5592          viewY = this.viewY;
5593
5594          if(mouseMove)
5595          {
5596             checkLine = dropLine;
5597             checkX = dropX;
5598             checkY = dropY;
5599          }
5600          else
5601          {
5602             checkLine = this.line;
5603             checkX = this.x;
5604             checkY = y;
5605          }
5606
5607          numLines = clientSize.h / space.h;
5608
5609          // This is broken. The EditBox now doesn't do anything different when adding to it,
5610          // regardless of the previous scrolling position. It should be read and then set again
5611          // if one wishes to preserve it.
5612          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5613          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5614             dontScroll = true;
5615          */
5616
5617          // Horizontal Adjustment
5618          if(!dontScroll && checkLine)
5619          {
5620             x = 0;
5621             if(mouseMove)
5622                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5623             else
5624             {
5625                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5626                ComputeColumn();
5627             }
5628
5629             if(style.hScroll)
5630             {
5631                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5632                   viewX = x - clientSize.w+space.w;
5633                if(x < this.viewX + clientSize.w/2 - space.w)
5634                   viewX = Max(0, x - clientSize.w/2 + space.w);
5635             }
5636          }
5637
5638          if(!dontScroll)
5639          {
5640             if(style.vScroll)
5641             {
5642                // Vertical Adjustment
5643                if(viewY > checkY) viewY = checkY;
5644                if(viewY + numLines <= checkY)
5645                {
5646                   if(clientSize.h >= space.h)
5647                   {
5648                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5649                         viewY++;
5650                   }
5651                   else
5652                      viewY = checkY;
5653                }
5654             }
5655             else
5656             {
5657                if(mouseMove)
5658                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5659                   {
5660                      dropLine = dropLine.prev;
5661                      dropY--;
5662                   }
5663                else
5664                   for(;this.line && this.line.prev && this.y >= numLines;)
5665                   {
5666                      this.line = this.line.prev;
5667                      y--;
5668                   }
5669             }
5670
5671             SetScrollPosition(viewX, viewY * this.space.h);
5672
5673             UpdateCaretPosition(setCaret);
5674
5675             if(!selected)
5676             {
5677                selX = this.x;
5678                selY = this.y;
5679                selLine = this.line;
5680             }
5681          }
5682
5683          UpdateDirty();
5684          SetSelectCursor();
5685          SelectionEnables();
5686       }
5687    }
5688
5689    void CenterOnCursor()
5690    {
5691       int numLines = clientSize.h / this.space.h;
5692       int y = this.y - numLines / 2;
5693       int viewY;
5694       bool figureSyntax = false;
5695       EditLine oldViewLine = viewLine;
5696       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5697       if(y < 0) y = 0;
5698
5699       viewY = y;
5700
5701       for(;y < this.viewY; y++)
5702       {
5703          this.viewLine = this.viewLine.prev;
5704          style.recomputeSyntax = true;
5705       }
5706       for(;y > this.viewY; y--)
5707       {
5708          this.viewLine = this.viewLine.next;
5709          figureSyntax = true;
5710       }
5711       if(figureSyntax)
5712          FigureStartSyntaxStates(oldViewLine, false);
5713
5714       this.viewY = viewY;
5715
5716       SetScrollPosition(this.viewX, viewY * this.space.h);
5717       UpdateCaretPosition(true);
5718       UpdateDirty();
5719    }
5720
5721    void SetCursorToView()
5722    {
5723       SetCursorToViewX();
5724       SetCursorToViewY();
5725    }
5726
5727    void PageDown()
5728    {
5729       int c, numLines;
5730       EditLine line;
5731
5732       numLines = clientSize.h / this.space.h;
5733
5734       if(style.noCaret)
5735       {
5736          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5737          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5738       }
5739       else
5740       {
5741          EditLine oldLine = this.line;
5742          bool lastOne = false;
5743          EditLine oldViewLine = this.viewLine;
5744          bool figureSyntax = false;
5745
5746          if(this.y >= this.lineCount-1) return;
5747
5748          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5749          {
5750             if(this.viewY + numLines < this.lines.count)
5751             {
5752                this.viewLine = this.viewLine.next;
5753                this.viewY++;
5754                figureSyntax = true;
5755             }
5756             else if(c && !lastOne)
5757                break;
5758             else
5759                lastOne = true;
5760
5761             this.line = line;
5762             this.y++;
5763          }
5764          if(figureSyntax)
5765             FigureStartSyntaxStates(oldViewLine, false);
5766          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5767          ComputeColumn();
5768
5769          SetViewToCursor(false);
5770       }
5771    }
5772
5773    void PageUp()
5774    {
5775       int c, numLines;
5776       EditLine line;
5777
5778       if(this.y == 0) return;
5779
5780       numLines = clientSize.h / this.space.h;
5781
5782       if(style.noCaret)
5783       {
5784          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5785          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5786       }
5787       else
5788       {
5789          EditLine oldLine = this.line;
5790
5791          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5792          {
5793             this.line = line;
5794             this.y--;
5795
5796             if(this.viewLine.prev)
5797             {
5798                this.viewLine = this.viewLine.prev;
5799                this.viewY--;
5800                style.recomputeSyntax = true;
5801             }
5802          }
5803
5804          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5805          ComputeColumn();
5806
5807          SetViewToCursor(false);
5808       }
5809    }
5810
5811    void LineUp()
5812    {
5813
5814       if(this.viewLine.prev)
5815       {
5816          //DirtyAll();
5817          // this.viewLine = this.viewLine.prev;
5818          //this.viewY--;
5819
5820          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5821       }
5822    }
5823
5824
5825    void LineDown()
5826    {
5827       if(this.viewLine.next)
5828       {
5829          //DirtyAll();
5830          // this.viewLine = this.viewLine.next;
5831          // this.viewY++;
5832
5833          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5834       }
5835    }
5836
5837    // Selection
5838    uint SelSize()
5839    {
5840       EditLine l1, l2, line;
5841       int x1, x2, nx1, nx2;
5842       int count;
5843       int start, end;
5844       int size;
5845
5846       if(!this.selLine) return 0;
5847       if(this.selLine == this.line && this.selX == this.x) return 0;
5848       if(this.selY < this.y)
5849       {
5850          l1 = this.selLine;
5851          x1 = this.selX;
5852          l2 = this.line;
5853          x2 = this.x;
5854       }
5855       else if(this.selY > this.y)
5856       {
5857          l1 = this.line;
5858          x1 = this.x;
5859          l2 = this.selLine;
5860          x2 = this.selX;
5861       }
5862       else if(this.selX < this.x)
5863       {
5864          l1 = l2 = this.line;
5865          x1 = this.selX;
5866          x2 = this.x;
5867       }
5868       else
5869       {
5870          l1 = l2 = this.line;
5871          x1 = this.x;
5872          x2 = this.selX;
5873       }
5874       nx1 = Min(x1,l1.count);
5875       nx2 = Min(x2,l2.count);
5876
5877       // Find Number of Bytes Needed
5878       size = 0;
5879       for(line = l1; line; line = line.next)
5880       {
5881          if(line == l1) start = nx1; else start = 0;
5882          if(line == l2) end = nx2; else end = line.count;
5883          size += end-start;
5884          if(style.freeCaret && line == l2)
5885          {
5886             if(l1 == l2)
5887                count = Max(x2-Max(x1,l1.count),0);
5888             else
5889                count = Max(x2-l2.count,0);
5890             size+=count;
5891          }
5892
5893          if(line == l2) break;
5894          // Add Carriage Return / line Feed
5895          size++;
5896          size++;
5897       }
5898       return size;
5899    }
5900
5901    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5902    {
5903       if(this)
5904       {
5905          if(!reorder || this.selY < this.y)
5906          {
5907             if(l1) *l1 = this.selLine;
5908             if(y1) *y1 = this.selY;
5909             if(x1) *x1 = this.selX;
5910             if(l2) *l2 = this.line;
5911             if(y2) *y2 = this.y;
5912             if(x2) *x2 = this.x;
5913          }
5914          else if(this.selY > this.y)
5915          {
5916             if(l1) *l1 = this.line;
5917             if(y1) *y1 = this.y;
5918             if(x1) *x1 = this.x;
5919             if(l2) *l2 = this.selLine;
5920             if(y2) *y2 = this.selY;
5921             if(x2) *x2 = this.selX;
5922          }
5923          else if(this.selX < this.x)
5924          {
5925             if(l1) *l1 = this.line;
5926             if(y1) *y1 = this.selY;
5927             if(x1) *x1 = this.selX;
5928             if(l2) *l2 = this.line;
5929             if(y2) *y2 = this.y;
5930             if(x2) *x2 = this.x;
5931          }
5932          else
5933          {
5934             if(l1) *l1 = this.line;
5935             if(y1) *y1 = this.y;
5936             if(x1) *x1 = this.x;
5937             if(l2) *l2 = this.line;
5938             if(y2) *y2 = this.selY;
5939             if(x2) *x2 = this.selX;
5940          }
5941       }
5942    }
5943
5944    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
5945    {
5946       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
5947       {
5948          this.selLine = (EditLine)l1;
5949          this.selY = y1;
5950          this.selX = x1;
5951          this.line = (EditLine)l2;
5952          this.y = y2;
5953          this.x = x2;
5954          ComputeColumn();
5955          SetViewToCursor(true);
5956       }
5957    }
5958
5959    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
5960    {
5961       EditLine l1, l2, line;
5962       int x1, x2, nx1, nx2;
5963       int count;
5964       int start, end;
5965       int numChars = 0;
5966
5967       if(_y2 < _y1)
5968       {
5969          l1 = _l2;
5970          x1 = _x2;
5971          l2 = _l1;
5972          x2 = _x1;
5973       }
5974       else if(_y2 > _y1)
5975       {
5976          l1 = _l1;
5977          x1 = _x1;
5978          l2 = _l2;
5979          x2 = _x2;
5980       }
5981       else if(_x2 < _x1)
5982       {
5983          l1 = l2 = _l1;
5984          x1 = _x2;
5985          x2 = _x1;
5986       }
5987       else
5988       {
5989          l1 = l2 = _l1;
5990          x1 = _x1;
5991          x2 = _x2;
5992       }
5993       nx1 = Min(x1,l1.count);
5994       nx2 = Min(x2,l2.count);
5995
5996       // Copy text
5997       for(line = l1; line; line = line.next)
5998       {
5999          if(line == l1) start = nx1; else start = 0;
6000          if(line == l2) end = nx2; else end = line.count;
6001          if(text)
6002          {
6003             CopyBytes(text, line.buffer + start, end - start);
6004             text += end-start;
6005          }
6006          numChars += end-start;
6007
6008          if(style.freeCaret && line == l2 && addSpaces)
6009          {
6010             if(l1 == l2)
6011                count = Max(x2-Max(x1,l1.count),0);
6012             else
6013                count = Max(x2-l2.count,0);
6014             if(text)
6015             {
6016                FillBytes(text,' ',count);
6017                text += count;
6018             }
6019             else
6020                numChars += count;
6021          }
6022          if(line == l2) break;
6023          // Add line Feed
6024          if(addCr)
6025          {
6026             if(text)
6027                *(text++) = '\r';
6028             numChars++;
6029          }
6030          if(text)
6031             *(text++) = '\n';
6032          numChars++;
6033       }
6034       // '\0' terminate Terminate
6035       if(text)
6036          *(text) = '\0';
6037       numChars++;
6038       return numChars;
6039    }
6040
6041    void GetSel(char * text, bool addCr)
6042    {
6043       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6044    }
6045
6046    private void _Deselect()   // This assumes marking lines as dirty is handled by the caller
6047    {
6048       selLine = line;
6049       selX = x;
6050       selY = y;
6051    }
6052
6053    void Deselect()
6054    {
6055       SelDirty();
6056       _Deselect();
6057    }
6058
6059    // CLIPBOARD
6060    void Copy()
6061    {
6062       if(this)
6063       {
6064          int size = SelSize();
6065          if(size)
6066          {
6067             // Try to allocate memory
6068             ClipBoard clipBoard { };
6069             if(clipBoard.Allocate(size+1))
6070             {
6071                GetSel(clipBoard.memory, true);
6072                // Save clipboard
6073                clipBoard.Save();
6074             }
6075             delete clipBoard;
6076          }
6077       }
6078    }
6079
6080    void Paste()
6081    {
6082       if(this)
6083       {
6084          ClipBoard clipBoard { };
6085          if(clipBoard.Load())
6086             PutS(clipBoard.memory);
6087          delete clipBoard;
6088       }
6089    }
6090
6091    void Cut()
6092    {
6093       if(this)
6094       {
6095          Copy();
6096          if(DelSel(null))
6097          {
6098             SetViewToCursor(true);
6099             Modified();
6100          }
6101       }
6102    }
6103
6104    void DeleteSelection()
6105    {
6106       if(this)
6107       {
6108          DelSel(null);
6109          SetViewToCursor(true);
6110          Modified();
6111       }
6112    }
6113
6114    // FILE INTERFACE
6115    void Save(File f, bool cr)
6116    {
6117       EditLine line;
6118       savedAction = undoBuffer.curAction;
6119
6120       for(line = this.lines.first; line; line = line.next)
6121       {
6122          if(style.syntax && line.count && isspace(line.buffer[line.count-1]))
6123          {
6124             int c = 0;
6125             for(c=line.count-2; c>=-1; c--)
6126             {
6127                if(c == -1 || !isspace(line.buffer[c]))
6128                {
6129                   c++;
6130                   line.buffer[c] = '\0';
6131                   line.count -= (line.count - c);
6132                   break;
6133                }
6134             }
6135          }
6136          f.Write(line.buffer, line.count,1);
6137          if(line.next)
6138          {
6139             if(cr) f.Putc('\r');
6140             f.Putc('\n');
6141          }
6142       }
6143    }
6144
6145    #define BUFFER_SIZE  16384
6146    void Load(File f)
6147    {
6148       undoBuffer.dontRecord++;
6149       if(f)
6150       {
6151          char buffer[BUFFER_SIZE];
6152
6153          for(;;)
6154          {
6155             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6156             buffer[count] = '\0';
6157             AddS(buffer);
6158             if(!count) break;
6159          }
6160          Home();
6161       }
6162       undoBuffer.dontRecord--;
6163       undoBuffer.count = 0;
6164       undoBuffer.curAction = 0;
6165       itemEditUndo.disabled = undoBuffer.curAction == 0;
6166       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6167    }
6168
6169    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6170    {
6171       EditLine line;
6172       int num;
6173       bool firstPass = true;
6174       EditBoxFindResult result = found;
6175
6176       if(!this.line) return notFound;
6177       num = this.y;
6178
6179       for(line = this.line;;)
6180       {
6181          char * string;
6182
6183          if(!line)
6184          {
6185             if(isSearchDown)
6186             {
6187                line = this.lines.first;
6188                num = 0;
6189                result = wrapped;
6190             }
6191             else
6192             {
6193                line = this.lines.last;
6194                num = this.lineCount - 1;
6195                result = wrapped;
6196             }
6197          }
6198
6199          if(isSearchDown)
6200             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6201          else
6202             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6203
6204          if(string)
6205          {
6206             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6207             return result;
6208          }
6209          if(line == this.line && !firstPass) break;
6210
6211          if(isSearchDown)
6212          {
6213             line = line.next;
6214             num++;
6215          }
6216          else
6217          {
6218             line = line.prev;
6219             num--;
6220          }
6221
6222          firstPass = false;
6223       }
6224       return notFound;
6225    }
6226
6227    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6228    {
6229       EditLine line;
6230       int y;
6231       int searchLen = strlen(text);
6232       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6233       {
6234          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6235          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6236          {
6237             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6238             return found;
6239          }
6240       }
6241       return notFound;
6242    }
6243
6244    bool OnKeyDown(Key key, unichar ch)
6245    {
6246 #ifdef _DEBUG
6247       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6248 #endif
6249       if(!NotifyKeyDown(master, this, key, ch))
6250          return false;
6251       else
6252       {
6253          switch(key)
6254          {
6255             case escape:
6256             {
6257                if(this.mouseMove)
6258                {
6259                   this.mouseMove = false;
6260                   OnLeftButtonUp(0,0,0);
6261                   SetViewToCursor(true);
6262                   return false;
6263                }
6264             }
6265          }
6266       }
6267       return true;
6268    }
6269 };
6270
6271 public class EditBoxStream : File
6272 {
6273    EditBox editBox;
6274    BufferLocation start, sel;
6275    uint pos;
6276    byte utf8Bytes[5];
6277    int numBytes;
6278
6279    ~EditBoxStream()
6280    {
6281       EditBox editBox = this.editBox;
6282
6283       editBox.x = start.x;
6284       editBox.y = start.y;
6285       editBox.line = start.line;
6286
6287       editBox.selX = sel.x;
6288       editBox.selY = sel.y;
6289       editBox.selLine = sel.line;
6290
6291       editBox.SetViewToCursor(true);
6292       //editBox.ComputeColumn();
6293    }
6294
6295 public:
6296    property EditBox editBox
6297    {
6298       set
6299       {
6300          editBox = value;
6301
6302          start.x = value.x;
6303          start.y = value.y;
6304          start.line = value.line;
6305
6306          sel.x = value.selX;
6307          sel.y = value.selY;
6308          sel.line = value.selLine;
6309          numBytes = 0;
6310
6311          value.GoToHome(true);
6312       }
6313
6314       get { return editBox; }
6315    }
6316
6317    uint Read(byte * buffer, uint size, uint count)
6318    {
6319       uint read = 0;
6320       EditBox editBox = this.editBox;
6321       EditLine line = editBox.line;
6322       int x = editBox.x;
6323       int y = editBox.y;
6324
6325       count *= size;
6326
6327       for(;read < count && line; line = (*&line.next))
6328       {
6329          int numBytes = Min(count - read, (*&line.count) - x);
6330          if(numBytes > 0)
6331          {
6332             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6333             read += numBytes;
6334             x += numBytes;
6335          }
6336          /*
6337          for(;read < count && x < (*&line.count);)
6338          {
6339             buffer[read++] = (*&line.buffer)[x++];
6340          }
6341          */
6342          if((*&line.next))
6343          {
6344             if(read < count)
6345             {
6346                buffer[read++] = '\n';
6347             }
6348             else
6349                break;
6350          }
6351          if(x == (*&line.count) && (*&line.next))
6352          {
6353             x = 0;
6354             y++;
6355          }
6356          else
6357             break;
6358       }
6359
6360       editBox.line = editBox.selLine = line;
6361       editBox.x = editBox.selX = x;
6362       editBox.y = editBox.selY = y;
6363       pos += read;
6364       return read / size;
6365    }
6366
6367    bool Seek(int pos, FileSeekMode mode)
6368    {
6369       bool result = true;
6370       EditBox editBox = this.editBox;
6371       EditLine line = editBox.line;
6372       numBytes = 0;
6373       if(mode == FileSeekMode::start)
6374       {
6375          pos = pos - this.pos;
6376          mode = current;
6377       }
6378       if(mode == current)
6379       {
6380          uint read = 0;
6381          int x = editBox.x;
6382          int y = editBox.y;
6383          if(pos > 0)
6384          {
6385             for(;read < pos && line; line = (*&line.next))
6386             {
6387                int numBytes = Min(pos - read, (*&line.count) - x);
6388                if(numBytes > 0)
6389                {
6390                   read += numBytes; x += numBytes;
6391                }
6392
6393                /*for(;read < pos && x < (*&line.count);)
6394                {
6395                   read++; x++;
6396                }
6397                */
6398                if((*&line.next))
6399                {
6400                   if(read < pos)
6401                   {
6402                      read++;
6403                      x = 0;
6404                   }
6405                   else
6406                      break;
6407                }
6408                else
6409                {
6410                   if(read<pos)
6411                      result = false;
6412                   break;
6413                }
6414                y++;
6415             }
6416             this.pos += read;
6417          }
6418          else if(pos < 0)
6419          {
6420             pos = -pos;
6421             for(;read < pos && line; line = (*&line.prev))
6422             {
6423                int numBytes = Min(pos - read, x);
6424                if(numBytes > 0)
6425                {
6426                   read += numBytes;
6427                   x -= numBytes;
6428                }
6429                /*for(;read < pos && x > 0;)
6430                {
6431                   read++; x--;
6432                }
6433                */
6434                if((*&line.prev))
6435                {
6436                   if(read < pos)
6437                   {
6438                      read++;
6439                      x = (*&(*&line.prev).count);
6440                   }
6441                   else
6442                      break;
6443                }
6444                else
6445                {
6446                   if(read<pos)
6447                      result = false;
6448                   break;
6449                }
6450                y--;
6451             }
6452             this.pos -= read;
6453          }
6454
6455          editBox.line = editBox.selLine = line;
6456          editBox.x = editBox.selX = x;
6457          editBox.y = editBox.selY = y;
6458       }
6459       return result;
6460    }
6461
6462    bool Puts(char * string)
6463    {
6464       EditBox editBox = this.editBox;
6465       BufferLocation start { editBox.line, editBox.y, editBox.x };
6466       BufferLocation pos;
6467
6468       numBytes = 0;
6469       editBox.AddS(string);
6470
6471       pos.line = editBox.line;
6472       pos.y = editBox.y;
6473       pos.x = editBox.x;
6474
6475       this.start.AdjustAdd(start, pos);
6476       sel.AdjustAdd(start, pos);
6477
6478       return true;
6479    }
6480
6481    // NOTE: BYTE, NOT UNICODE CHARACTER!
6482    bool Putc(char ch)
6483    {
6484       EditBox editBox = this.editBox;
6485       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6486       BufferLocation pos;
6487
6488       if(numBytes < 4)
6489       {
6490          utf8Bytes[numBytes++] = ch;
6491          utf8Bytes[numBytes] = 0;
6492          if(UTF8Validate(utf8Bytes))
6493          {
6494             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6495             numBytes = 0;
6496             pos.line = editBox.line;
6497             pos.y = editBox.y;
6498             pos.x = editBox.x;
6499             this.start.AdjustAdd(start, pos);
6500             sel.AdjustAdd(start, pos);
6501          }
6502          return true;
6503       }
6504       return false;
6505    }
6506
6507    bool Getc(char * ch)
6508    {
6509       return Read(ch, 1, 1) ? true : false;
6510    }
6511
6512    void DeleteBytes(uint count)
6513    {
6514       EditBox editBox = this.editBox;
6515       if(count)
6516       {
6517          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6518          BufferLocation end = pos;
6519
6520          uint c = 0;
6521          for(;;)
6522          {
6523             for(;c < count && end.line && end.x < end.line.count;)
6524             {
6525                end.x++;
6526                c++;
6527             }
6528             if(c < count && end.line && end.line.next)
6529             {
6530                end.line = end.line.next;
6531                end.y++;
6532                c++;
6533                end.x = 0;
6534             }
6535             else
6536                break;
6537          }
6538
6539          start.AdjustDelete(pos, end);
6540          sel.AdjustDelete(pos, end);
6541
6542          editBox.DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true);
6543       }
6544    }
6545 };