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