ecere/gui/EditBox: Fixed hanging caused by 308c3f8a022f58c179f5d52f87be2609fba4ddd8
[sdk] / ecere / src / gui / controls / EditBox.ec
index 05bf37d..ff0116a 100644 (file)
@@ -1,9 +1,32 @@
 namespace gui::controls;
 
+/*
+selectionForeground = white;
+disabled: defaultTextColor = Color { 85, 85, 85 };
+*/
+
 import "Window"
 import "ReplaceDialog"
 import "FindDialog"
 import "GoToDialog"
+import "Array"
+
+public class SyntaxColorScheme
+{
+public:
+   Color commentColor;
+   Color charLiteralColor;
+   Color stringLiteralColor;
+   Color preprocessorColor;
+   Color numberColor;
+   private Array<Color> keywordColors { };
+
+   public property Container<Color> keywordColors
+   {
+      set { keywordColors.Copy((void *)value); }
+      get { return keywordColors; }
+   }
+};
 
 #include <stdarg.h>
 
@@ -24,28 +47,48 @@ import "GoToDialog"
       byte b = (string)[0]; \
       int i; \
       byte mask = 0x7F; \
-      numBytes = 1; \
+      numBytes = b ? 1 : 0; \
       ch = 0; \
-      if(b & 0x80 && b & 0x40) \
+      if(b & 0x80) \
       { \
-         mask >>= 2; \
-         numBytes++; \
-         if(b & 0x20) \
+         if(b & 0x40) \
          { \
+            mask >>= 2; \
             numBytes++; \
-            mask >>= 1; \
-            if(b & 0x10) \
+            if(b & 0x20) \
             { \
                numBytes++; \
                mask >>= 1; \
+               if(b & 0x10) \
+               { \
+                  if(b & 0x08) { numBytes = 0; } \
+                  numBytes++; \
+                  mask >>= 1; \
+               } \
             } \
          } \
+         else \
+            numBytes = 0; \
       } \
       for(i = 0; i<numBytes; i++) \
       { \
          ch <<= 6; \
-         ch |= (string)[i] & mask; \
+         ch |= (b = (string)[i]) & mask; \
          mask = 0x3F; \
+         if(i > 1 && (!(b & 0x80) || (b & 0x40))) \
+         { \
+            numBytes = 0; \
+            ch = 0; \
+         } \
+      } \
+      if(i < numBytes || \
+         ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF) || \
+        (ch < 0x80 && numBytes > 1) || \
+        (ch < 0x800 && numBytes > 2) || \
+        (ch < 0x10000 && numBytes > 3))\
+      { \
+         ch = 0; \
+         numBytes = 0; \
       } \
       ch; \
    })
@@ -56,12 +99,13 @@ class EditBoxBits
    bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
 
    // Syntax States
-   bool inMultiLineComment:1, inPrep:1, escaped:1;
+   bool inMultiLineComment:1, inPrep:1, escaped:1, continuedSingleLineComment:1;
 
    bool recomputeSyntax:1;
    bool cursorFollowsView:1;
    
    // bool lineNumbers:1;
+   bool autoSize:1;
 };
 
 /* TODO:
@@ -98,7 +142,7 @@ public class OldArray
       if(type.type == normalClass || type.type == noHeadClass)
       {
          for(c = 0; c<size; c++)
-            type._vTbl[__ecereVMethodID_class_OnFree](type, array[c]);
+            ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
       }
       // TODO: Call OnFree for structClass
       delete ((ArrayImpl)this).array;
@@ -147,16 +191,9 @@ public:
    }
 };
 
-
-class ArrayUndoActions : OldArray
-{
-   type = class(UndoAction);
-   UndoAction * _;
-};
-
 public class UndoBuffer
 {
-   ArrayUndoActions actions { size = 8 };
+   Array<UndoAction> actions { size = 8 };
 public:
    int count;
    int curAction;
@@ -165,13 +202,18 @@ public:
    bool insideRedo;
 
    dontRecord = 0;
+
+   ~UndoBuffer()
+   {
+      actions.Free();
+   }
    
    void Undo()
    {
       dontRecord++;
       if(curAction > 0)
       {
-         UndoAction action = actions._[--curAction];
+         UndoAction action = actions[--curAction];
 #ifdef _DEBUG
          /*Print("Undoing: ");
          action.Print(data);*/
@@ -187,7 +229,7 @@ public:
       insideRedo = true;
       if(curAction < count)
       {
-         UndoAction action = actions._[curAction];
+         UndoAction action = actions[curAction];
          curAction++;
 #ifdef _DEBUG
          /*Print("Redoing: ");
@@ -207,7 +249,7 @@ public:
          {
             int c;
             for(c = curAction; c < count; c++)
-               delete actions._[c];
+               delete actions[c];
          }
 
          count = curAction;
@@ -219,7 +261,7 @@ public:
          /*Print("Recording: ");
          action.Print(data);*/
 #endif
-         actions._[count++] = action;
+         actions[count++] = action;
          curAction = count;
 
          if(actions.size > count + count / 2 && count + count / 2 >= 8)
@@ -395,10 +437,11 @@ static class ReplaceTextAction : UndoAction
 
       editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
 
+      editBox.PutS(oldString);
       if(addedSpaces || addedTabs)
          editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
 
-      editBox.PutS(oldString);
+      //editBox.PutS(oldString);
       if(!placeAfter)
       {
          editBox.GoToPosition(null, y1, x1);
@@ -593,16 +636,37 @@ public enum EditBoxFindResult { notFound, found, wrapped };
 
 static char * keyWords1[] =
 {
-   "return","break","continue","default","switch","case","if","else","for","while", "do","property","import","long","short",
-   "void", "char","int","float","double","unsigned","static", "extern", "struct", "union", "typedef","class","enum","virtual", 
-   "const", "private", "public", "protected", "sizeof", "delete", "new", "new0", "renew", "renew0", "define",
+   // C
+   "return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
+   "void", "char","int","float","double","unsigned","static", "extern", "struct", "union", "typedef","enum",
+   "const",   "sizeof",
    "#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
-   "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "get", "set", "true", "false", "null", "__attribute__", "__stdcall", "_stdcall", "remote", "this",
+   "__attribute__", "__stdcall", "_stdcall",
+   "__declspec", "goto",
+    "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
+   "asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
+
+   // eC
+   "class", "private", "public",
+   "property","import",
+   "delete", "new", "new0", "renew", "renew0", "define",
+   "get", "set",
+   "remote",
+   "dllexport", "dllimport", "stdcall",
+   "subclass", "__on_register_module", "namespace", "using",
    "typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
    "class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
-   "subclass", "__on_register_module", "namespace", "using", "__declspec", "dllexport", "dllimport", "stdcall", "goto", 
-   "dbtable", "dbindex", "database_open", "dbfield", "unichar", "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
-   "asm", "__asm", "_asm", "volatile", "#cpu", "thisclass", "__stdcall__", "value",
+   "virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
+
+   // Types
+   "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
+
+   // Values
+   "this", "true", "false", "null", "value",
+
+
+   // C++
+   "protected",
    /* "defined" */
    null
 };
@@ -614,31 +678,15 @@ static char * keyWords2[] =
 
 static char ** keyWords[] = { keyWords1, keyWords2 };
 #define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
-static Color colorGroups[] =
-{
-   Color { 0,0,255 },
-   Color { 0,0,255 }
-};
 //static int * keyLen[NUM_KEYWORD_GROUPS];
 static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
 
-/*
-static FileFilter filters[] =
-{
-   { "All files", null },
-   {
-      "Text Files (*.txt)",
-      "txt"
-   }
-};
-static FileListConfig fileListConfig = { "", "", filters, sizeof(filters), null, 0 };
-*/
 static char searchString[1025], replaceString[1025];
 static bool matchCase = false, wholeWord = false, searchUp = false;
 
 static GoToDialog goToDialog
 {
-   autoCreate = false, isModal = true, text = "Go To"
+   autoCreate = false, isModal = true, text = $"Go To"
 };
 
 public class EditBox : CommonControl
@@ -683,11 +731,11 @@ public:
    };
 
    // Properties
-   property bool textHorzScroll { property_category "Behavior" set { style.hScroll = value; } get { return style.hScroll; } };      // Should cut the text on set to false
-   property bool textVertScroll { property_category "Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
+   property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } };      // Should cut the text on set to false
+   property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
    property bool readOnly
    {
-      property_category "Behavior" 
+      property_category $"Behavior" 
       set
       {
          style.readOnly = value;
@@ -697,20 +745,21 @@ public:
       }
       get { return style.readOnly; }
    };
-   property bool multiLine { property_category "Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
-   property bool freeCaret { property_category "Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
-   property bool tabKey { property_category "Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
-   property int tabSize { property_category "Behavior" set { tabSize = value; } get { return tabSize; } };
-   property bool tabSelection { property_category "Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
-   property bool smartHome { property_category "Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
-   property bool autoEmpty { property_category "Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
-   property bool noCaret { property_category "Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
-   property int maxLineSize { property_category "Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
-   property int maxNumLines { property_category "Behavior" set { maxLines = value; } get { return maxLines; } };
-   property bool useTab { property_category "Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
-   property bool syntaxHighlighting { property_category "Appearance" set { style.syntax = value; } get { return style.syntax; } };
-   property bool noSelect { property_category "Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
-   property bool allCaps { property_category "Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
+   property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
+   property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
+   property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
+   property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
+   property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
+   property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
+   property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
+   property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
+   property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
+   property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
+   property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
+   property bool syntaxHighlighting { property_category $"Appearance" set { style.syntax = value; } get { return style.syntax; } };
+   property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
+   property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
+   property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
    property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
    //property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
    property int numLines { get { return this ? lineCount : 0; } };
@@ -722,7 +771,7 @@ public:
    property EditLine line { get { return this.line; } }; // TODO: Add Set   this.line = this.lines[10]
    property char * contents
    {
-      property_category "Data" 
+      property_category $"Data" 
       set
       {
          if(this)
@@ -818,6 +867,9 @@ public:
       }
    }
    */
+   property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
+   property Color selectionText  { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
+   property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
 
    // selectionStart.line, selectionStart.column (With Set)
    // selection.line1, selection.line2, selection.column1, selection.column2  (Read only)
@@ -891,14 +943,16 @@ private:
    int caretX, caretY;
    UndoBuffer undoBuffer { data = this };
    int savedAction;
+   Color selectionColor, selectionText;
+   SyntaxColorScheme colorScheme { };
 
    menu = Menu { };
 
    // Edit Menu
-   Menu editMenu { menu, "Edit", e };
+   Menu editMenu { menu, $"Edit", e };
    MenuItem itemEditCut
    {
-      editMenu, "Cut\tCtrl+X", t, disabled = true;
+      editMenu, $"Cut\tCtrl+X", t, disabled = true;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -909,7 +963,7 @@ private:
    };
    MenuItem itemEditCopy
    {
-      editMenu, "Copy\tCtrl+C", c, disabled = true;
+      editMenu, $"Copy\tCtrl+C", c, disabled = true;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -919,7 +973,7 @@ private:
    };
    MenuItem itemEditPaste
    {
-      editMenu, "Paste\tCtrl+V", p;
+      editMenu, $"Paste\tCtrl+V", p;
    
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -930,7 +984,7 @@ private:
    };
    MenuItem itemEditDelete
    {
-      editMenu, "Delete\tDel", d, disabled = true;
+      editMenu, $"Delete\tDel", d, disabled = true;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -942,7 +996,7 @@ private:
    MenuDivider { editMenu };
    MenuItem itemEditSelectAll
    {
-      editMenu, "Select All\tCtrl+A", a;
+      editMenu, $"Select All\tCtrl+A", a;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -953,7 +1007,7 @@ private:
    MenuDivider { editMenu };
    MenuItem itemEditUndo
    {
-      editMenu, "Undo\tCtrl+Z", u;
+      editMenu, $"Undo\tCtrl+Z", u;
       disabled = true;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
@@ -964,7 +1018,7 @@ private:
    };
    MenuItem itemEditRedo
    {
-      editMenu, "Redo\tCtrl+Y", o;
+      editMenu, $"Redo\tCtrl+Y", o;
       disabled = true;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
@@ -976,7 +1030,7 @@ private:
    MenuDivider { editMenu };
    MenuItem
    {
-      editMenu, "Find Previous\tShift-F3", e, shiftF3;
+      editMenu, $"Find Previous\tShift-F3", e, shiftF3;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -989,7 +1043,7 @@ private:
    };
    MenuItem
    {
-      editMenu, "Find Next\tF3", n, f3;
+      editMenu, $"Find Next\tF3", n, f3;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -1002,7 +1056,7 @@ private:
    };
    MenuItem itemEditFind
    {
-      editMenu, "Find...\tCtrl+F", f, ctrlF;
+      editMenu, $"Find...\tCtrl+F", f, ctrlF;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -1029,7 +1083,7 @@ private:
    };
    MenuItem
    {
-      editMenu, "Replace...\tCtrl+R", r, ctrlR;
+      editMenu, $"Replace...\tCtrl+R", r, ctrlR;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -1063,7 +1117,7 @@ private:
    MenuDivider { editMenu };
    MenuItem
    {
-      editMenu, "Go To...\tCtrl+G", g, ctrlG;
+      editMenu, $"Go To...\tCtrl+G", g, ctrlG;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -1076,7 +1130,7 @@ private:
    MenuDivider { editMenu };
    MenuItem itemEditInsertTab
    {
-      editMenu, "Insert Tabs", i, checkable = true;
+      editMenu, $"Insert Tabs", i, checkable = true;
 
       bool NotifySelect(MenuItem item, Modifiers mods)
       {
@@ -1105,6 +1159,13 @@ private:
                keyLen[g][c] = strlen(keyWords[g][c]);
             }
          }
+
+         colorScheme.commentColor = dimGray;
+         colorScheme.charLiteralColor = crimson;
+         colorScheme.stringLiteralColor = crimson;
+         colorScheme.preprocessorColor = green;
+         colorScheme.numberColor = teal;
+         colorScheme.keywordColors = [ blue, blue ];
       }
 
       FontExtent = Display::FontExtent;
@@ -1236,7 +1297,7 @@ private:
          bool inPrep = reset ? false : style.inPrep;
          bool inSingleLineComment = false;
          bool escaped = reset ? false : style.escaped;
-         bool continuedSingleLineComment = false;
+         bool continuedSingleLineComment = reset ? false : style.continuedSingleLineComment;
 
          EditLine line = reset ? lines.first : firstLine;
          // int maxBackUp = 1000, c;
@@ -1281,7 +1342,7 @@ private:
                {
                   if(!c || text[c-1] != '/') lastWasStar = true;
                }
-               else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && ch == '\"')
+               else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
                {
                   if(inString && !wasEscaped)
                   {
@@ -1292,7 +1353,7 @@ private:
                      inString = true;
                   }
                }
-               else if(!inSingleLineComment && !inMultiLineComment && !inString && ch == '\'')
+               else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
                {
                   if(inQuotes && !wasEscaped)
                      inQuotes = false;
@@ -1306,24 +1367,20 @@ private:
                   if(!wasEscaped)
                      escaped = true;
                }
-               else
+               else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
                {
-                  if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && ch == '#')
+                  if(firstWord)
                   {
-                     if(firstWord)
-                     {
-                        inPrep = true;
-                     }
+                     inPrep = true;
                   }
                }
-               firstWord = false;
+               else if(ch != ' ' && ch != '\t')
+                  firstWord = false;
             }
+            continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
          }
          
-         continuedSingleLineComment = false;
-         if(line.count && line.text[line.count - 1] == '\\')
-            continuedSingleLineComment = true;
-
+         style.continuedSingleLineComment = continuedSingleLineComment;
          style.inMultiLineComment = inMultiLineComment;
          style.inPrep = inPrep;
          style.escaped = escaped;
@@ -1365,7 +1422,8 @@ private:
       int y = YOFFSET;
       bool selected = false, selection = true;
       int selX, editX;
-      Color selectionBackground = SELECTION_COLOR, selectionForeground = white;
+      Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
+      Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
       Color defaultTextColor = property::foreground;
       Color textColor;
       Box box;
@@ -1386,7 +1444,7 @@ private:
       bool inPrep = style.inPrep;
       bool inSingleLineComment = false;
       bool escaped = style.escaped;
-      bool continuedSingleLineComment = false;
+      bool continuedSingleLineComment = style.continuedSingleLineComment;
       // ****** ************* ******
 
       if(!isEnabled)
@@ -1398,8 +1456,8 @@ private:
          Abs(selectionBackground.g - property::background.g) + 
          Abs(selectionBackground.b - property::background.b) < 92)
       {
-         selectionBackground = activeBorder;
-         selectionForeground = SELECTION_COLOR;
+         selectionBackground = formColor;
+         selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
       }
 
       surface.TextFont(this.font);
@@ -1410,7 +1468,7 @@ private:
 
       if(!isEnabled)
       {
-         surface.SetBackground(activeBorder);
+         surface.SetBackground(formColor);
          surface.Area(0,0,clientSize.w, clientSize.h);
       }
 
@@ -1506,7 +1564,10 @@ private:
             
             textColor = newTextColor;
             if(!selected)
+            {
+               foreground = textColor;
                surface.SetForeground(textColor);
+            }
 
             // Look at words
             for(; c<end && !cantHaveWords;)
@@ -1593,19 +1654,19 @@ private:
                      {
                         if(inSingleLineComment || inMultiLineComment)
                         {
-                           newTextColor = dimGray;
+                           newTextColor = colorScheme.commentColor;
                         }
                         else if(inQuotes)
                         {
-                           newTextColor = crimson;
+                           newTextColor = colorScheme.charLiteralColor;
                         }
                         else if(inString)
                         {
-                           newTextColor = crimson;
+                           newTextColor = colorScheme.stringLiteralColor;
                         }
                         else if(inPrep)
                         {
-                           newTextColor = green;
+                           newTextColor = colorScheme.preprocessorColor;
                         }
                         if(wordLen == 1 && word[0] == '/')
                         {
@@ -1614,12 +1675,12 @@ private:
                               if(word[1] == '/')
                               {
                                  inSingleLineComment = true;
-                                 newTextColor = dimGray;
+                                 newTextColor = colorScheme.commentColor;
                               }
                               else if(word[1] == '*')
                               {
                                  inMultiLineComment = true;
-                                 newTextColor = dimGray;
+                                 newTextColor = colorScheme.commentColor;
                               }
                            }
                            else if(backLastWasStar)
@@ -1627,7 +1688,7 @@ private:
                         }
                         else if(wordLen == 1 && word[0] == '*')
                         {
-                           if(!c || word[-1] != '/')
+                           if(c < 2 || word[-1] != '/')
                               lastWasStar = true;
                         }
                         else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
@@ -1639,7 +1700,7 @@ private:
                            else
                            {
                               inString = true;
-                              newTextColor = crimson;
+                              newTextColor = colorScheme.stringLiteralColor;
                            }
                         }
                         else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
@@ -1649,7 +1710,7 @@ private:
                            else
                            {
                               inQuotes = true;
-                              newTextColor = crimson;
+                              newTextColor = colorScheme.charLiteralColor;
                            }
                         }
                         else if(wordLen == 1 && word[0] == '\\')
@@ -1660,7 +1721,7 @@ private:
                         else if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && 
                            ( ( isdigit(word[0]) /*&& (!c || word[-1] == ' ' || word[-1] == '\t')*/ ) || (word[0] == '.' && isdigit(word[1]))))
                         {
-                           newTextColor = teal;
+                           newTextColor = colorScheme.numberColor;
                         }
                         else
                         {
@@ -1669,7 +1730,7 @@ private:
                               if(firstWord)
                               {
                                  inPrep = true;
-                                 newTextColor = green;
+                                 newTextColor = colorScheme.preprocessorColor; 
                               }
                            }
                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
@@ -1682,7 +1743,7 @@ private:
                                  {
                                     if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
                                     {
-                                       newTextColor = colorGroups[g];
+                                       newTextColor = colorScheme.keywordColors[g];
                                        break;
                                     }
                                  }
@@ -1713,7 +1774,10 @@ private:
                            {
                               textColor = newTextColor;
                               if(!selected)
+                              {
+                                 foreground = textColor;
                                  surface.SetForeground(textColor);
+                              }
                            }
                         }
                      }
@@ -1830,9 +1894,7 @@ private:
          }
          */
          
-         continuedSingleLineComment = false;
-         if(line.count && line.text[line.count - 1] == '\\')
-            continuedSingleLineComment = true;
+         continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
 
          y+=this.space.h;
          if(y > box.bottom) // >=clientSize.h) 
@@ -1962,9 +2024,8 @@ private:
       int c, position = 0;
       unichar ch;
       int nb;
-      for(c = 0; c<this.line.count && c<this.x; c+= nb)
+      for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
       {
-         ch = UTF8_GET_CHAR(this.line.buffer + c, nb);
          // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
          if(ch == '\t')
             position += this.tabSize - (position % this.tabSize);
@@ -2025,6 +2086,7 @@ private:
                start--;
          }
       }
+      oldCount1 = l1.count;
       buffer = l1.buffer;
       while(c1 < oldCount1)
       {
@@ -2033,6 +2095,7 @@ private:
          c1--;         
          extras++;
       }
+      oldCount2 = l2.count;
       buffer = l2.buffer;
       while(c2 < oldCount2)
       {
@@ -2054,8 +2117,8 @@ private:
          Record(action);
       }
 
-      oldCount1 = l1.count;
-      oldCount2 = l2.count;
+      //oldCount1 = l1.count;
+      //oldCount2 = l2.count;
 
       {
          BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
@@ -2207,6 +2270,7 @@ private:
       }
       ComputeLength(l1);
       FindMaxLine();
+      if(style.autoSize) AutoSize();
       if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
       {
          DirtyAll();
@@ -2420,6 +2484,7 @@ private:
                int backDontRecord = undoBuffer.dontRecord;
                undoBuffer.dontRecord = 0;
                NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
+               if(style.autoSize) AutoSize();
                undoBuffer.dontRecord = backDontRecord;
             }
             if(style.syntax && (hadComment || hasComment || line != this.line))
@@ -2932,6 +2997,43 @@ private:
       return true;
    }
 
+   void AutoSize()
+   {
+      //if(created)
+      {
+         if(multiLine)
+         {
+            // todo: resize width based on largest on-screen-line extent...
+            int sh = 0;
+            display.FontExtent(font, " ", 1, null, &sh);
+            if(sh)
+            {
+               int nh = 0;
+               nh = lineCount * sh + 2;
+               size.h = nh < minClientSize.h ? minClientSize.h : nh;
+            }
+         }
+         else
+         {
+            int tw = 0;
+            int sh = 0;
+            int nw = 0;
+            int nh = 0;
+            MinMaxValue dw = 0;
+            MinMaxValue dh = 0;
+            int len = line ? strlen(line.text) : 0;
+            GetDecorationsSize(&dw, &dh);
+            display.FontExtent(font, " ", 1, null, &sh);
+            if(len) display.FontExtent(font, line.text, len, &tw, null);
+            nw = dw+tw+12;
+            if(nw < minClientSize.w) nw = minClientSize.w;
+            nh = dh+sh+4;
+            if(nh < minClientSize.h) nh = minClientSize.h;
+            size = { nw, nh };
+         }
+      }
+   }
+
    bool OnResizing(int *w, int *h)
    {
       if(!*h)
@@ -2958,7 +3060,9 @@ private:
    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
    {
       if(style.readOnly) return true;
-      Paste();
+      // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
+      // Middle button already serves as a dragger as well.
+      // Paste();
       return true;
    }
 
@@ -2977,12 +3081,12 @@ private:
          PopupMenu popup;
          Menu contextMenu { };
 
-         MenuItem { contextMenu, "Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
-         MenuItem { contextMenu, "Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
-         MenuItem { contextMenu, "Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
-         MenuItem { contextMenu, "Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
+         MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
+         MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
+         MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
+         MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
          MenuDivider { contextMenu };
-         MenuItem { contextMenu, "Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
+         MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
 
          popup = PopupMenu { master = this, menu = contextMenu,
    /*
@@ -3004,7 +3108,8 @@ private:
 
       if(style.noSelect) return true;
 
-      if(!mods.isActivate)
+      // Should we have a separate 'selectOnActivate' style?
+      if(!mods.isActivate || (style.readOnly && style.multiLine))
       {
          Capture();
          mouseSelect = true;
@@ -3263,7 +3368,7 @@ private:
             for(c = start; c<line.count; c += numBytes)
             {
                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
-               if(!IS_ALUNDER(ch))
+               if(!ch || !IS_ALUNDER(ch))
                   break;
             }
             SelDirty();
@@ -3281,19 +3386,18 @@ private:
       return true;
    }
 
-   /*
    bool OnPostCreate()
    {
-      if(isDocument)
+      /*if(isDocument)
       {
          Menu fileMenu { menu, "File", F };
          saveDialog = fileDialog;
-         MenuItem { fileMenu, "Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
-         MenuItem { fileMenu, "Save As...", A, NotifySelect = MenuFileSaveAs };
-      }
+         MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
+         MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
+      }*/
+      if(style.autoSize) AutoSize();
       return true;
    }
-   */
 
    void ComputeFont()
    {
@@ -3432,13 +3536,10 @@ private:
                   {
                      int i;
                      int length;
-                     //Can't delete right away as that would change the line.count
                      for(i = this.x; i < this.line.count; i++)
                      {
                         if(!IS_ALUNDER(this.line.buffer[i]))
                            break;
-                        DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
-                        i--;
                      }
                      
                      for(; i < this.line.count; i++)
@@ -3446,9 +3547,8 @@ private:
                         //Delete trailing whitespace
                         if(IS_ALUNDER(this.line.buffer[i]))
                            break;
-                        DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
-                        i--;
                      }
+                     DelCh(this.line, this.y, this.x, this.line, this.y, i, false);
                      SetViewToCursor(true);
                      Modified();
                   }
@@ -3710,9 +3810,9 @@ private:
                      int start = (line == this.line) ? this.x : 0;
                      int c;
                      int numBytes;
-                     for(c = start; c < line.count; c += numBytes)
+                     unichar ch;
+                     for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
                      {
-                        unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
                         if(IS_ALUNDER(ch))
                         {
                            foundAlpha = true;
@@ -3759,9 +3859,9 @@ private:
                      int start = (line == this.line) ? this.x : 0;
                      int c;
                      int numBytes;
-                     for(c = start; c < line.count; c += numBytes)
+                     unichar ch;
+                     for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
                      {
-                        unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
                         if(!IS_ALUNDER(ch))
                            foundAlpha = true;
                         else if(foundAlpha)
@@ -4296,6 +4396,7 @@ private:
                            if(!line.AdjustBuffer(line.count-lastC)) 
                               break;
                            line.count-=lastC;
+                           if(style.autoSize) AutoSize();
                            DirtyLine(y);
                         }
 
@@ -4320,6 +4421,7 @@ private:
                                        break;
 
                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
+                                    if(style.autoSize) AutoSize();
                                     {
                                        AddCharAction action { ch = '\t', x = 0, y = y };
                                        Record(action);
@@ -4337,6 +4439,7 @@ private:
                                     int c;
                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
+                                    if(style.autoSize) AutoSize();
 
                                     if(!line.AdjustBuffer(line.count+this.tabSize)) 
                                        break;
@@ -4379,7 +4482,7 @@ private:
                         ComputeColumn();
                      }
                      // Insert spaces
-                     start = this.x;
+                     start = Min(this.x, this.selX);
                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
                      {
                         addString[len++] = ' ';
@@ -4823,6 +4926,7 @@ private:
             after.line = this.line, after.y = this.y, after.x = this.x;
 
             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
+            if(style.autoSize) AutoSize();
          }
       }
       else
@@ -4848,6 +4952,7 @@ private:
       }
       if(addCharAction)
       {
+         addCharAction.x -= addedTabs * (tabSize-1);
          addCharAction.addedSpaces = addedSpaces;
          addCharAction.addedTabs = addedTabs;
       }
@@ -5041,6 +5146,7 @@ public:
                {
                   if(!AddCh('\n'))
                   {
+                     count = 0;
                      ret = false;
                      break;
                   }
@@ -5065,7 +5171,7 @@ public:
          // FindMaxLine();
 
          // Add the line here
-         if(count)
+         if(ret && count)
             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
             {
                ret = false;
@@ -5191,6 +5297,7 @@ public:
          }  
          if(addCharAction)
          {
+            addCharAction.x -= addedTabs * (tabSize-1);
             addCharAction.addedSpaces = addedSpaces;
             addCharAction.addedTabs = addedTabs;
          }
@@ -5219,7 +5326,8 @@ public:
          char temp[MAX_F_STRING];
          va_list args;
          va_start(args, format);
-         vsprintf(temp, format, args);
+         vsnprintf(temp, sizeof(temp), format, args);
+         temp[sizeof(temp)-1] = 0;
          va_end(args);
          PutS(temp);
       }
@@ -5236,7 +5344,8 @@ public:
             char temp[MAX_F_STRING];
             va_list args;
             va_start(args, format);
-            vsprintf(temp, format, args);
+            vsnprintf(temp, sizeof(temp), format, args);
+            temp[sizeof(temp)-1] = 0;
             va_end(args);
 
             AddS(temp);
@@ -5881,9 +5990,11 @@ public:
       if(this)
       {
          Copy();
-         DelSel(null);
-         SetViewToCursor(true);
-         Modified();
+         if(DelSel(null))
+         {
+            SetViewToCursor(true);
+            Modified();
+         }
       }
    }
 
@@ -5898,7 +6009,7 @@ public:
    }
 
    // FILE INTERFACE
-   void Save(File f, bool lf)
+   void Save(File f, bool cr)
    {
       EditLine line;
       savedAction = undoBuffer.curAction;
@@ -5908,7 +6019,7 @@ public:
          f.Write(line.buffer, line.count,1);
          if(line.next)
          {
-            if(lf) f.Putc('\r');
+            if(cr) f.Putc('\r');
             f.Putc('\n');
          }
       }