ide/settings: Fixed color scheme issues
[sdk] / ide / src / IDESettings.ec
index af82e81..b0feab9 100644 (file)
@@ -4,6 +4,171 @@ public import static "ecere"
 public import "ecere"
 #endif
 
+// *** Color Schemes ***
+class IDEColorScheme
+{
+   String name;
+   SyntaxColorScheme syntaxColors;
+public:
+
+   property const String name
+   {
+      set { delete name; name = CopyString(value); }
+      get { return name; }
+   }
+
+   Color selectionColor;
+   Color selectionText;
+   Color viewsBackground;
+   Color viewsText;
+   Color outputBackground;
+   Color outputText;
+   Color projectViewBackground;
+   Color projectViewText;
+   Color codeEditorBG;
+   Color codeEditorFG;
+   Color marginColor;
+   Color selectedMarginColor;
+   Color lineNumbersColor;
+   Color sheetSelectionColor;
+   Color sheetSelectionText;
+
+   property SyntaxColorScheme syntaxColors
+   {
+      set { delete syntaxColors; syntaxColors = value; if(value) incref value; }
+      get { return syntaxColors; }
+   }
+
+   ~IDEColorScheme()
+   {
+      delete syntaxColors;
+      delete name;
+   }
+}
+
+IDEColorScheme colorScheme;
+
+// Default Color Schemes
+IDEColorScheme darkColorScheme
+{
+   name = "Classic Dark";
+   selectionColor = lightYellow;
+   selectionText = Color { 30, 40, 50 };
+   viewsBackground = Color { 30, 40, 50 };
+   viewsText = lightGray;
+   outputBackground = black;
+   outputText = lime;
+   sheetSelectionColor = Color { 170, 220, 255 };
+   sheetSelectionText = black;
+   projectViewBackground = Color { 30, 40, 50 };
+   projectViewText = lightGray;
+   codeEditorBG = black;
+   codeEditorFG = ivory;
+   marginColor = Color {24, 24, 24};
+   selectedMarginColor = Color {64, 64, 64};
+   lineNumbersColor = Color {160, 160, 160};
+   syntaxColors =
+   {
+      keywordColors = [ skyBlue, skyBlue ];
+      commentColor = Color { 125, 125, 125 };
+      charLiteralColor = Color { 245, 50, 245 };
+      stringLiteralColor = Color { 245, 50, 245 };
+      preprocessorColor = { 120, 220, 140 };
+      numberColor = Color {   0, 192, 192 };
+   };
+};
+
+IDEColorScheme lightColorScheme
+{
+   name = "Classic Light";
+   selectionColor = Color { 10, 36, 106 };
+   selectionText = white;
+   viewsBackground = white;
+   viewsText = black;
+   outputBackground = white;
+   outputText = black;
+   sheetSelectionColor = Color { 170, 220, 255 };
+   sheetSelectionText = black;
+   projectViewBackground = white;
+   projectViewText = black;
+   codeEditorBG = white;
+   codeEditorFG = black;
+   marginColor = Color {230, 230, 230};
+   selectedMarginColor = Color {200, 200, 200};
+   lineNumbersColor = Color {60, 60, 60};
+   syntaxColors =
+   {
+      keywordColors = [ blue, blue ];
+      commentColor = dimGray;
+      charLiteralColor = crimson;
+      stringLiteralColor = crimson;
+      preprocessorColor = green;
+      numberColor = teal;
+   };
+};
+
+IDEColorScheme greenColorScheme
+{
+   name = "Green",
+   selectionColor = 0x00FFFFE0,
+   selectionText = 0x001E2832,
+   viewsBackground = 0x001E2832,
+   viewsText = 0x00D3D3D3,
+   outputBackground = 0x00000000,
+   outputText = 0x0000FF00,
+   projectViewBackground = 0x001E2832,
+   projectViewText = 0x00D3D3D3,
+   codeEditorBG = 0x00000000,
+   codeEditorFG = 0x00FFFFF0,
+   marginColor = 0x001100,
+   selectedMarginColor = 0x002200,
+   lineNumbersColor = 0x00FF00,
+   sheetSelectionColor = 0x00AADCFF,
+   sheetSelectionText = 0x00000000,
+   syntaxColors = {
+      commentColor = 0x008c00,
+      charLiteralColor = 0x89de00,
+      stringLiteralColor = 0x89de00,
+      preprocessorColor = 0x0078DC8C,
+      numberColor = { 8, 237, 141 },
+      keywordColors = [
+         0x00e09d,
+         0x00e09d
+      ]
+   }
+};
+
+IDEColorScheme grayColorScheme
+{
+   name = "Gray & Orange",
+   selectionColor = 0x00FFFFE0,
+   selectionText = 0x001E2832,
+   viewsBackground = 0x001E2832,
+   viewsText = 0x00D3D3D3,
+   outputBackground = 0x00000000,
+   outputText = 0x0000FF00,
+   projectViewBackground = 0x001E2832,
+   projectViewText = 0x00D3D3D3,
+   codeEditorBG = 0x00202020,
+   codeEditorFG = 0x00E2E2E5,
+   marginColor = 0x00303030,
+   selectedMarginColor = 0x00404040,
+   lineNumbersColor = 0x00939395,
+   sheetSelectionColor = 0x00AADCFF,
+   sheetSelectionText = 0x00000000,
+   syntaxColors = {
+      commentColor = 0x005F5F5F,
+      charLiteralColor = 0x0089DE00,
+      stringLiteralColor = 0x00707070,
+      preprocessorColor = 0x00FAF4C6,
+      numberColor = 0x00FF9800,
+      keywordColors = [
+         0x00FF9800,
+         0x00FF9800
+      ]
+   }
+};
+
 define ecpDefaultCommand = "ecp";
 define eccDefaultCommand = "ecc";
 define ecsDefaultCommand = "ecs";
@@ -20,19 +185,16 @@ import "StringsBox"
 
 import "OldIDESettings"
 
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-import "ide"
-import "process"
+#ifdef __WIN32__
+#define WIN32_LEAN_AND_MEAN
+#define Sleep _Sleep
+#include <windows.h>
+#undef MoveFileEx
+#undef Sleep
+#undef MessageBox
+#undef GetObject
 #endif
 
-IDESettings ideSettings;
-
-IDESettingsContainer settingsContainer
-{
-   dataOwner = &ideSettings;
-   dataClass = class(IDESettings);
-};
-
 define MaxRecent = 9;
 
 enum DirTypes { includes, libraries, executables };
@@ -243,6 +405,7 @@ CompilerConfig MakeDefaultCompiler(const char * name, bool readOnly)
    return defaultCompiler;
 }
 
+//#define SETTINGS_TEST
 #ifdef SETTINGS_TEST
 define settingsDir = ".ecereIDE-SettingsTest";
 define ideSettingsName = "ecereIDE-SettingsTest";
@@ -251,22 +414,72 @@ define settingsDir = ".ecereIDE";
 define ideSettingsName = "ecereIDE";
 #endif
 
+class IDEConfigHolder
+{
+   CompilerConfigs compilers { };
+   RecentFiles recentFiles { };
+   RecentWorkspaces recentWorkspaces { };
+
+   property CompilerConfigs compilers
+   {
+      set { compilers.Free(); delete compilers; compilers = value; }
+      get { return compilers; }
+   }
+   property RecentFiles recentFiles
+   {
+      set { recentFiles.Free(); delete recentFiles; recentFiles = value; }
+      get { return recentFiles; }
+   }
+   property RecentWorkspaces recentProjects
+   {
+      set { recentWorkspaces.Free(); delete recentWorkspaces; recentWorkspaces = value; }
+      get { return recentWorkspaces; }
+   }
+
+   ~IDEConfigHolder()
+   {
+      compilers.Free();
+      recentFiles.Free();
+      recentWorkspaces.Free();
+   }
+
+   void forcePathSeparatorStyle(bool unixStyle)
+   {
+      char from, to;
+      if(unixStyle)
+         from = '\\', to = '/';
+      else
+         from = '/', to = '\\';
+      recentFiles.changeChar(from, to);
+      recentWorkspaces.changeChar(from, to);
+   }
+}
+
 class IDESettingsContainer : GlobalSettings
 {
+   virtual void onLoadCompilerConfigs();
+   virtual void onLoadRecentFiles();
+   virtual void onLoadRecentProjects();
+   virtual void onLoad();
+
+   CompilerConfigs compilerConfigs;
+   RecentFiles recentFiles;
+   RecentWorkspaces recentProjects;
+
    property bool useNewConfigurationFiles
    {
       set
       {
          if(value)
          {
-            settingsContainer.driver = "ECON";
+            driver = "ECON";
             settingsName = "config";
             settingsExtension = "econ";
             settingsDirectory = settingsDir;
          }
          else
          {
-            settingsContainer.driver = "JSON";
+            driver = "JSON";
             settingsName = ideSettingsName;
             settingsExtension = null;
             settingsDirectory = null;
@@ -276,6 +489,74 @@ class IDESettingsContainer : GlobalSettings
 
    char moduleLocation[MAX_LOCATION];
 
+   FileMonitor recentFilesMonitor
+   {
+      this, fileChange = { modified = true };
+
+      bool OnFileNotify(FileChange action, const char * param)
+      {
+         File f;
+         recentFilesMonitor.StopMonitoring();
+         f = FileOpen(recentFilesMonitor.fileName, read);
+         if(f)
+         {
+            int c;
+            bool locked;
+            for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
+            recentFiles.read(this);
+            f.Unlock(0,0,true);
+            delete f;
+         }
+         return true;
+      }
+   };
+
+   FileMonitor recentProjectsMonitor
+   {
+      this, fileChange = { modified = true };
+
+      bool OnFileNotify(FileChange action, const char * param)
+      {
+         File f;
+         recentProjectsMonitor.StopMonitoring();
+         f = FileOpen(recentProjectsMonitor.fileName, read);
+         if(f)
+         {
+            int c;
+            bool locked;
+            for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
+            recentProjects.read(this);
+            f.Unlock(0,0,true);
+            delete f;
+         }
+         return true;
+      }
+   };
+
+   static void getConfigFilePath(char * path, Class _class, char * dir, const char * configName)
+   {
+      if(dir) *dir = 0;
+      strcpy(path, settingsFilePath);
+      StripLastDirectory(path, path);
+      if(oldConfig)
+         PathCatSlash(path, settingsDir);
+      if(_class == class(CompilerConfig))
+      {
+         PathCatSlash(path, "compilerConfigs");
+         if(dir)
+            strcpy(dir, path);
+         if(configName)
+         {
+            PathCatSlash(path, configName);
+            strcat(path, ".econ");
+         }
+      }
+      else if(_class == class(RecentFilesData))
+         PathCatSlash(path, "recentFiles.econ");
+      else if(_class == class(RecentWorkspacesData))
+         PathCatSlash(path, "recentWorkspaces.econ");
+   }
+
 private:
    bool oldConfig;
    FileSize settingsFileSize;
@@ -289,7 +570,7 @@ private:
       StripLastDirectory(moduleLocation, moduleLocation);
       ChangeCh(moduleLocation, '\\', '/');
       // PortableApps.com directory structure
-      if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
+      if((start = strstr(path, "\\App\\EcereSDK\\bin\\ecere-ide.exe")))
       {
          char configFilePath[MAX_LOCATION];
          char defaultConfigFilePath[MAX_LOCATION];
@@ -334,9 +615,10 @@ private:
 
       if(OpenAndLock(&newSettingsFileSize))
       {
-         if((double)settingsFileSize - (double)newSettingsFileSize < 2048)
+         //if((double)settingsFileSize - (double)newSettingsFileSize < 2048)
             Load();
-         else
+            onLoad();
+         /*else
          {
             GuiApplication app = ((GuiApplication)__thisModule.application);
             Window w;
@@ -351,7 +633,7 @@ private:
                   "The new settings will not be loaded to prevent loss of your ide settings.\n"
                   "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
                   }.Create();
-         }
+         }*/
       }
    }
 
@@ -419,25 +701,49 @@ private:
 
       CloseAndMonitor();
       FileGetSize(settingsFilePath, &settingsFileSize);
-      CompilerConfigs::fix();
       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
          data.ManagePortablePaths(moduleLocation, true);
       data.ForcePathSeparatorStyle(true);
 
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-      globalSettingsDialog.ideSettings = data;
-      if(oldConfig)
+      if(!data.colorSchemes || !data.colorSchemes.count)
       {
-         ide.updateRecentMenus();
-         ide.UpdateCompilerConfigs(true);
+         if(!data.colorSchemes) data.colorSchemes = { };
+
+         data.colorSchemes.Add(darkColorScheme);   incref darkColorScheme;
+         data.colorSchemes.Add(lightColorScheme);  incref lightColorScheme;
+         data.colorSchemes.Add(greenColorScheme);  incref greenColorScheme;
+         data.colorSchemes.Add(grayColorScheme);   incref grayColorScheme;
+      }
+      colorScheme = null;
+      if(data.activeColorScheme)
+      {
+         for(cs : data.colorSchemes; cs.name && !strcmp(cs.name, data.activeColorScheme))
+         {
+            colorScheme = cs;
+            break;
+         }
+      }
+      if(!colorScheme)
+      {
+         colorScheme = data.colorSchemes[0];
+         data.activeColorScheme = colorScheme.name;
       }
-#endif
 
+      // Import from previous ecereIDE settings
       if(oldConfig)
       {
-         useNewConfigurationFiles = true;
+         // Save first so that settingsFilePath get set up
          Save();
-         CompilerConfigs::write(null);
+
+         data.compilerConfigs.ensureDefaults();
+         data.compilerConfigs.write(this, null);
+         data.compilerConfigs.Free();
+
+         data.recentFiles.write(this);
+         data.recentFiles.Free();
+
+         data.recentProjects.write(this);
+         data.recentProjects.Free();
       }
       return result;
    }
@@ -480,10 +786,7 @@ static Map<String, String> getCompilerConfigFilePathsByName(const char * path)
          char * path = CopyString(fl.path);
          MakeSlashPath(path);
          GetLastDirectory(path, name);
-         {
-            char * s = strstr(name, ".");
-            if(s) *s = 0;
-         }
+         StripExtension(name);
          map[name] = path;
       }
    }
@@ -502,10 +805,7 @@ static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
          char * path = CopyString(fl.path);
          MakeSlashPath(path);
          GetLastDirectory(path, name);
-         {
-            char * s = strstr(name, ".");
-            if(s) *s = 0;
-         }
+         StripExtension(name);
          {
             CompilerConfig ccfg = CompilerConfig::read(path);
             if(ccfg)
@@ -517,38 +817,14 @@ static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
    return map;
 }
 
-static void getConfigFilePath(char * path, Class _class, char * dir, const char * configName)
-{
-   if(dir) *dir = 0;
-   strcpy(path, settingsContainer.settingsFilePath);
-   StripLastDirectory(path, path);
-   if(settingsContainer.oldConfig)
-      PathCatSlash(path, settingsDir);
-   if(_class == class(CompilerConfig))
-   {
-      PathCatSlash(path, "compilerConfigs");
-      if(dir)
-         strcpy(dir, path);
-      if(configName)
-      {
-         PathCatSlash(path, configName);
-         ChangeExtension(path, "econ", path);
-      }
-   }
-   else if(_class == class(RecentFilesData))
-      PathCatSlash(path, "recentFiles.econ");
-   else if(_class == class(RecentWorkspacesData))
-      PathCatSlash(path, "recentWorkspaces.econ");
-}
-
 static SettingsIOResult writeConfigFile(const char * path, Class dataType, void * data)
 {
    SettingsIOResult result = error;
-   SafeFile sf;
-   sf = safeWriteFileOpen(path, write);
-   if(sf.file)
+   SafeFile sf = SafeFile::open(path, write);
+   if(sf)
    {
       WriteECONObject(sf.file, dataType, data, 0);
+      sf.sync();
       delete sf;
       result = success;
    }
@@ -563,7 +839,7 @@ static SettingsIOResult readConfigFile(const char * path, Class dataType, void *
    SafeFile sf;
    if(!FileExists(path))
       result = fileNotFound;
-   else if((sf = safeWriteFileOpen(path, read)))
+   else if((sf = SafeFile::open(path, read)))
    {
       JSONResult jsonResult;
       {
@@ -589,81 +865,121 @@ static SettingsIOResult readConfigFile(const char * path, Class dataType, void *
 class SafeFile
 {
    File file;
-   File lock;
    FileOpenMode mode;
+   char path[MAX_LOCATION];
    char tmp[MAX_LOCATION];
-   char lck[MAX_LOCATION];
 
-   ~SafeFile()
+   SafeFile ::open(const char * path, FileOpenMode mode)
    {
-      delete file;
-      if(mode == write)
-         DeleteFile(tmp);
-      if(lock)
+      SafeFile result = null;
+      if(mode == write || mode == read)
       {
-         delete lock;
-         DeleteFile(lck);
-      }
-   }
-}
+         SafeFile sf { mode = mode };
+         int c;
+         bool locked = false;
+         FileLock lockType = mode == write ? exclusive : shared;
 
-SafeFile safeWriteFileOpen(const char * path, FileOpenMode mode)
-{
-   SafeFile sf { mode = mode };
-   strcpy(sf.lck, path);
-   strcat(sf.lck, ".lck");
-   strcpy(sf.tmp, path);
-   strcat(sf.tmp, ".tmp");
-   if(mode == write)
-   {
-      sf.lock = FileOpen(sf.lck, write);
-      if(sf.lock && sf.lock.Lock(exclusive, 0, 0, false))
-      {
-         if(sf.tmp && FileExists(path).isFile)
-            MoveFile(path, sf.tmp);
-         sf.file = FileOpen(path, write);
+         strcpy(sf.path, path);
+         strcpy(sf.tmp, path);
+         strcat(sf.tmp, ".tmp");
+         if(mode == write && FileExists(sf.tmp).isFile)
+            DeleteFile(sf.tmp);
+
+         if(mode == write)
+         {
+            sf.file = FileOpen(sf.tmp, readWrite);
+            if(!sf.file)
+            {
+               sf.file = FileOpen(sf.tmp, writeRead);
+               if(sf.file)
+               {
+                  delete sf.file;
+                  sf.file = FileOpen(sf.tmp, readWrite);
+               }
+            }
+         }
+         else
+            sf.file = FileOpen(path, mode);
+         if(sf.file)
+         {
+            for(c = 0; c < 10 && !(locked = sf.file.Lock(lockType, 0, 0, false)); c++) Sleep(0.01);
+            if(locked)
+            {
+               sf.file.Truncate(0);
+               sf.file.Seek(0, start);
+               result = sf;
+            }
+            else if(mode == write)
+               PrintLn($"warning: SafeFile::open: unable to obtain exclusive lock on temporary file for writing: ", sf.tmp);
+            else
+               PrintLn($"warning: SafeFile::open: unable to obtain shared lock on file for reading: ", path);
+         }
+         else if(mode == write)
+            PrintLn($"warning: SafeFile::open: unable to open temporary file for writing: ", sf.tmp);
+         else
+            PrintLn($"warning: SafeFile::open: unable to open file for reading: ", path);
+
+         if(!result)
+            delete sf;
       }
       else
-         PrintLn($"warning: safeWriteFileOpen: unable to obtain exclusive lock for writing: ", sf.lck);
+         PrintLn($"warning: SafeFile::open: does not yet support FileOpenMode::", mode);
+      return result;
    }
-   else if(mode == read)
+
+   void sync()
    {
-      int c;
-      bool locked = false;
-      bool failed = false;
-      for(c = 0; c < 10 && !(failed = sf.tmp && FileExists(sf.tmp).isFile); c++) Sleep(0.01);
-      if(failed)
+      if(file && mode == write)
       {
-         sf.lock = FileOpen(sf.lck, write);
-         if(sf.lock && sf.lock.Lock(exclusive, 0, 0, false))
+         int c;
+         File f = FileOpen(path, readWrite);
+         if(!f)
          {
-            if(FileExists(sf.tmp).isFile)
+            f = FileOpen(path, writeRead);
+            if(f)
             {
-               if(FileExists(path).isFile)
-                  DeleteFile(path);
-               MoveFile(sf.tmp, path);
+               delete f;
+               f = FileOpen(path, readWrite);
+            }
+         }
+         if(f)
+         {
+            bool locked = true;
+            for(c = 0; c < 10 && !(locked = f.Lock(exclusive, 0,0, false)); c++) Sleep(0.01);
+
+            if(locked)
+            {
+               f.Unlock(0,0, false);
+               delete f;
+               file.Unlock(0,0, false);
+               delete file;
+
+               for(c = 0; c < 10; c++)
+               {
+                  if(MoveFileEx(tmp, path, { true, true }))
+                     break;
+                  else
+                     Sleep(0.01);
+               }
             }
             else
-               PrintLn($"warning: safeWriteFileOpen: file is gone: ", sf.tmp);
-            delete sf.lock;
-            DeleteFile(sf.lck);
+            {
+               delete f;
+               PrintLn($"warning: SafeFile::sync: failed to lock file for ", mode);
+            }
          }
-         else
-            PrintLn($"warning: safeWriteFileOpen: unable to obtain exclusive lock for failed write repair: ", sf.lck);
       }
-      sf.lock = FileOpen(sf.lck, write);
-      if(sf.lock) delete sf.lock;
-      sf.lock = FileOpen(sf.lck, read);
-      if(sf.lock)
+   }
+
+
+   ~SafeFile()
+   {
+      if(file)
       {
-         for(c = 0; c < 10 && !(locked = sf.lock.Lock(shared, 0, 0, false)); c++) Sleep(0.01);
-         if(locked)
-            sf.file = FileOpen(path, read);
+         file.Unlock(0,0, false);
+         delete file;
       }
    }
-   else
-      PrintLn($"warning: safeWriteFileOpen: does not yet support FileOpenMode::", mode);
-   return sf;
 }
 
 class RecentFilesData
@@ -683,7 +999,7 @@ class IDESettings : GlobalSettingsData
 public:
    property CompilerConfigs compilerConfigs
    {
-      set { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; }
+      set { /*if(settingsContainer.oldConfig)*/ { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
       get { return compilerConfigs; }
       isset { return false; }
    }
@@ -763,6 +1079,43 @@ public:
       isset { return language != null; }
    }
 
+   property const String codeEditorFont
+   {
+      set
+      {
+         delete codeEditorFont;
+         codeEditorFont = CopyString(value);
+      }
+      get { return codeEditorFont; }
+   }
+
+   float codeEditorFontSize;
+   bool showFixedPitchFontsOnly;
+
+   property Array<IDEColorScheme> colorSchemes
+   {
+      set
+      {
+         if(colorSchemes && colorSchemes._refCount < 2)
+            colorSchemes.Free();
+         delete colorSchemes;
+         colorSchemes = value;
+         if(value)
+            incref colorSchemes;
+      }
+      get { return colorSchemes; }
+   }
+
+   property const String activeColorScheme
+   {
+      set
+      {
+         delete activeColorScheme;
+         activeColorScheme = CopyString(value);
+      }
+      get { return activeColorScheme; }
+   }
+
 private:
    CompilerConfigs compilerConfigs { };
    char * docDir;
@@ -776,24 +1129,15 @@ private:
    RecentFiles recentFiles { };
    RecentWorkspaces recentProjects { };
 
-   CompilerConfig GetCompilerConfig(const String compilerName)
-   {
-      const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
-      CompilerConfig compilerConfig = null;
-      for(compiler : compilerConfigs)
-      {
-         if(!strcmp(compiler.name, name))
-         {
-            compilerConfig = compiler;
-            break;
-         }
-      }
-      if(!compilerConfig && compilerConfigs.count)
-         compilerConfig = compilerConfigs.firstIterator.data;
-      if(compilerConfig)
-         incref compilerConfig;
-      return compilerConfig;
-   }
+   Array<IDEColorScheme> colorSchemes;
+
+   String codeEditorFont;
+
+   String activeColorScheme;
+
+   showFixedPitchFontsOnly = true;
+   codeEditorFontSize = 12;
+   codeEditorFont = CopyString("Courier New");
 
    ~IDESettings()
    {
@@ -812,6 +1156,11 @@ private:
       delete ideFileDialogLocation;
       delete ideProjectFileDialogLocation;
       delete displayDriver;
+
+      delete codeEditorFont;
+
+      colorSchemes.Free();
+      delete activeColorScheme;
    }
 
    void ForcePathSeparatorStyle(bool unixStyle)
@@ -959,38 +1308,32 @@ private:
 
 class RecentFiles : RecentPaths
 {
-   void onAdd()
-   {
-      write();
-   }
-
-   void ::read()
+   void read(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
       RecentFilesData d = null;
       Class _class = class(RecentFilesData);
-      getConfigFilePath(path, _class, null, null);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
       readConfigFile(path, _class, &d);
       if(d && d.recentFiles && d.recentFiles.count)
       {
-         IDESettings s = (IDESettings)settingsContainer.data;
-         s.property::recentFiles = d.recentFiles;
-         d.recentFiles = null;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-         ide.updateRecentFilesMenu();
-#endif
+         Free();
+         Copy((void *)d.recentFiles);
+         settingsContainer.recentFiles = this; // Merge IDEConfigHolder / IDESettingsContainer?
       }
       delete d;
+      settingsContainer.recentFilesMonitor.fileName = path;
+      settingsContainer.recentFilesMonitor.StartMonitoring();
+      settingsContainer.onLoadRecentFiles();
    }
 
-   void ::write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
-      IDESettings s = (IDESettings)settingsContainer.data;
       RecentFilesData d { };
       Class _class = class(RecentFilesData);
-      getConfigFilePath(path, _class, null, null);
-      d.recentFiles = s.recentFiles;
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      d.recentFiles = this;
       writeConfigFile(path, _class, d);
       d.recentFiles = null;
       delete d;
@@ -999,38 +1342,32 @@ class RecentFiles : RecentPaths
 
 class RecentWorkspaces : RecentPaths
 {
-   void onAdd()
-   {
-      write();
-   }
-
-   void ::read()
+   void read(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
       RecentWorkspacesData d = null;
       Class _class = class(RecentWorkspacesData);
-      getConfigFilePath(path, _class, null, null);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
       readConfigFile(path, _class, &d);
       if(d && d.recentWorkspaces && d.recentWorkspaces.count)
       {
-         IDESettings s = (IDESettings)settingsContainer.data;
-         s.property::recentProjects = d.recentWorkspaces;
-         d.recentWorkspaces = null;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-         ide.updateRecentProjectsMenu();
-#endif
+         Free();
+         Copy((void *)d.recentWorkspaces);
+         settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
       }
       delete d;
+      settingsContainer.recentProjectsMonitor.fileName = path;
+      settingsContainer.recentProjectsMonitor.StartMonitoring();
+      settingsContainer.onLoadRecentProjects();
    }
 
-   void ::write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
-      IDESettings s = (IDESettings)settingsContainer.data;
       RecentWorkspacesData d { };
       Class _class = class(RecentWorkspacesData);
-      getConfigFilePath(path, _class, null, null);
-      d.recentWorkspaces = s.recentProjects;
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      d.recentWorkspaces = this;
       writeConfigFile(path, _class, d);
       d.recentWorkspaces = null;
       delete d;
@@ -1039,8 +1376,6 @@ class RecentWorkspaces : RecentPaths
 
 class RecentPaths : Array<String>
 {
-   virtual void onAdd();
-
    IteratorPointer Add(T value)
    {
       int c;
@@ -1057,10 +1392,10 @@ class RecentPaths : Array<String>
       return Array::Add((T)filePath);
    }
 
-   IteratorPointer addRecent(T value)
+   IteratorPointer addRecent(const String value)
    {
       int c;
-      char * filePath = (char *)value;
+      char * filePath = CopyString((char *)value);
       IteratorPointer ip;
       ChangeCh(filePath, '\\', '/');
       for(c = 0; c < count; c++)
@@ -1074,7 +1409,6 @@ class RecentPaths : Array<String>
       while(count >= MaxRecent)
          Delete(GetLast());
       ip = Insert(null, filePath);
-      onAdd();
       return ip;
    }
 
@@ -1244,11 +1578,23 @@ public:
       get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
       isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
    }
-   property const char * outputFileExt
+   property const char * staticLibFileExt
+   {
+      set { delete staticLibFileExt; if(value && value[0]) staticLibFileExt = CopyString(value); }
+      get { return staticLibFileExt; }
+      isset { return staticLibFileExt && staticLibFileExt[0]; }
+   }
+   property const char * sharedLibFileExt
+   {
+      set { delete sharedLibFileExt; if(value && value[0]) sharedLibFileExt = CopyString(value); }
+      get { return sharedLibFileExt; }
+      isset { return sharedLibFileExt && sharedLibFileExt[0]; }
+   }
+   property const char * executableFileExt
    {
-      set { delete outputFileExt; if(value && value[0]) outputFileExt = CopyString(value); }
-      get { return outputFileExt; }
-      isset { return outputFileExt && outputFileExt[0]; }
+      set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
+      get { return executableFileExt; }
+      isset { return executableFileExt && executableFileExt[0]; }
    }
    property const char * executableLauncher
    {
@@ -1281,6 +1627,7 @@ public:
       isset { return sysroot && sysroot[0]; }
    }
    bool resourcesDotEar;
+   bool noStripTarget;
    property Array<String> includeDirs
    {
       set
@@ -1434,6 +1781,23 @@ public:
       get { return executableLauncher; }
       isset { return false; }
    }
+   property const char * outputFileExt
+   {
+      set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
+      get { return executableFileExt; }
+      isset { return false; }
+   }
+   // utility
+   property bool hasDocumentOutput
+   {
+      get
+      {
+         bool result = executableFileExt && executableFileExt[0] &&
+               (!strcmpi(executableFileExt, "htm") || !strcmpi(executableFileExt, "html"));
+         return result;
+      }
+      isset { return false; }
+   }
 private:
    Array<String> includeDirs { };
    Array<String> libraryDirs { };
@@ -1459,7 +1823,9 @@ private:
    char * ldCommand;
    char * arCommand;
    char * objectFileExt;
-   char * outputFileExt;
+   char * staticLibFileExt;
+   char * sharedLibFileExt;
+   char * executableFileExt;
    char * executableLauncher;
    char * distccHosts;
    char * gnuToolchainPrefix;
@@ -1483,7 +1849,9 @@ private:
       delete ldCommand;
       delete arCommand;
       delete objectFileExt;
-      delete outputFileExt;
+      delete staticLibFileExt;
+      delete sharedLibFileExt;
+      delete executableFileExt;
       delete makeCommand;
       delete executableLauncher;
       delete distccHosts;
@@ -1504,7 +1872,18 @@ private:
    int OnCompare(CompilerConfig b)
    {
       int result;
-      if(!(result = name.OnCompare(b.name)) &&
+      if(
+         !(result = type.OnCompare(b.type)) &&
+         !(result = targetPlatform.OnCompare(b.targetPlatform)) &&
+         !(result = numJobs.OnCompare(b.numJobs)) &&
+         !(result = ccacheEnabled.OnCompare(b.ccacheEnabled)) &&
+         !(result = distccEnabled.OnCompare(b.distccEnabled)) &&
+         !(result = resourcesDotEar.OnCompare(b.resourcesDotEar)) &&
+         !(result = noStripTarget.OnCompare(b.noStripTarget))
+         );
+
+      if(!result &&
+         !(result = name.OnCompare(b.name)) &&
          !(result = ecpCommand.OnCompare(b.ecpCommand)) &&
          !(result = eccCommand.OnCompare(b.eccCommand)) &&
          !(result = ecsCommand.OnCompare(b.ecsCommand)) &&
@@ -1520,8 +1899,7 @@ private:
          !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
          !(result = distccHosts.OnCompare(b.distccHosts)) &&
          !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
-         !(result = sysroot.OnCompare(b.sysroot)))
-         ;
+         !(result = sysroot.OnCompare(b.sysroot)));
 
       if(!result &&
          !(result = includeDirs.OnCompare(b.includeDirs)) &&
@@ -1533,9 +1911,7 @@ private:
          !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
          !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
          !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
-         !(result = linkerFlags.OnCompare(b.linkerFlags)))
-         ;
-
+         !(result = linkerFlags.OnCompare(b.linkerFlags)));
       return result;
    }
 
@@ -1560,7 +1936,9 @@ public:
          arCommand,
          ldCommand,
          objectFileExt,
-         outputFileExt,
+         staticLibFileExt,
+         sharedLibFileExt,
+         executableFileExt,
          executableLauncher,
          ccacheEnabled,
          distccEnabled,
@@ -1568,7 +1946,8 @@ public:
          distccHosts,
          gnuToolchainPrefix,
          sysroot,
-         resourcesDotEar
+         resourcesDotEar,
+         noStripTarget
       };
       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
@@ -1592,12 +1971,12 @@ public:
       return d;
    }
 
-   void write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char dir[MAX_LOCATION];
       char path[MAX_LOCATION];
       const char * settingsFilePath = settingsContainer.settingsFilePath;
-      getConfigFilePath(path, _class, dir, name);
+      settingsContainer.getConfigFilePath(path, _class, dir, name);
       if(FileExists(settingsFilePath) && !FileExists(dir))
       {
          MakeDir(dir);
@@ -1610,48 +1989,70 @@ public:
 
 class CompilerConfigs : List<CompilerConfig>
 {
-   void ::fix()
+   CompilerConfig GetCompilerConfig(const String compilerName)
+   {
+      const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
+      CompilerConfig compilerConfig = null;
+      for(compiler : this)
+      {
+         if(!strcmp(compiler.name, name))
+         {
+            compilerConfig = compiler;
+            break;
+         }
+      }
+      if(!compilerConfig && count)
+         compilerConfig = this[0];
+      if(compilerConfig)
+      {
+         incref compilerConfig;
+         if(compilerConfig._refCount == 1)
+            incref compilerConfig;
+      }
+      return compilerConfig;
+   }
+
+   void ensureDefaults()
    {
-      IDESettings s = (IDESettings)settingsContainer.data;
       // Ensure we have a default compiler
-      CompilerConfig defaultCompiler = null;
-      defaultCompiler = s.GetCompilerConfig(defaultCompilerName);
+      CompilerConfig defaultCompiler = GetCompilerConfig(defaultCompilerName);
       if(!defaultCompiler)
       {
          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
-         s.compilerConfigs.Insert(null, defaultCompiler);
+         Insert(null, defaultCompiler);
          defaultCompiler = null;
       }
       delete defaultCompiler;
 
-      if(s.compilerConfigs)
+      for(ccfg : this)
       {
-         for(ccfg : s.compilerConfigs)
-         {
-            if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
-               ccfg.ecpCommand = ecpDefaultCommand;
-            if(!ccfg.eccCommand || !ccfg.eccCommand[0])
-               ccfg.eccCommand = eccDefaultCommand;
-            if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
-               ccfg.ecsCommand = ecsDefaultCommand;
-            if(!ccfg.earCommand || !ccfg.earCommand[0])
-               ccfg.earCommand = earDefaultCommand;
-            if(!ccfg.cppCommand || !ccfg.cppCommand[0])
-               ccfg.cppCommand = cppDefaultCommand;
-            if(!ccfg.ccCommand || !ccfg.ccCommand[0])
-               ccfg.ccCommand = ccDefaultCommand;
-            if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
-               ccfg.cxxCommand = cxxDefaultCommand;
-            /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
-               ccfg.ldCommand = ldDefaultCommand;*/
-            if(!ccfg.arCommand || !ccfg.arCommand[0])
-               ccfg.arCommand = arDefaultCommand;
-            if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
-               ccfg.objectFileExt = objectDefaultFileExt;
-            /*if(!ccfg.outputFileExt || !ccfg.outputFileExt[0])
-               ccfg.outputFileExt = outputDefaultFileExt;*/
-            if(!ccfg._refCount) incref ccfg;
-         }
+         if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
+            ccfg.ecpCommand = ecpDefaultCommand;
+         if(!ccfg.eccCommand || !ccfg.eccCommand[0])
+            ccfg.eccCommand = eccDefaultCommand;
+         if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
+            ccfg.ecsCommand = ecsDefaultCommand;
+         if(!ccfg.earCommand || !ccfg.earCommand[0])
+            ccfg.earCommand = earDefaultCommand;
+         if(!ccfg.cppCommand || !ccfg.cppCommand[0])
+            ccfg.cppCommand = cppDefaultCommand;
+         if(!ccfg.ccCommand || !ccfg.ccCommand[0])
+            ccfg.ccCommand = ccDefaultCommand;
+         if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
+            ccfg.cxxCommand = cxxDefaultCommand;
+         /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
+            ccfg.ldCommand = ldDefaultCommand;*/
+         if(!ccfg.arCommand || !ccfg.arCommand[0])
+            ccfg.arCommand = arDefaultCommand;
+         if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
+            ccfg.objectFileExt = objectDefaultFileExt;
+         /*if(!ccfg.staticLibFileExt || !ccfg.staticLibFileExt[0])
+            ccfg.staticLibFileExt = staticLibDefaultFileExt;*/
+         /*if(!ccfg.sharedLibFileExt || !ccfg.sharedLibFileExt[0])
+            ccfg.sharedLibFileExt = sharedLibDefaultFileExt;*/
+         /*if(!ccfg.executableFileExt || !ccfg.executableFileExt[0])
+            ccfg.executableFileExt = outputDefaultFileExt;*/
+         if(!ccfg._refCount) incref ccfg;
       }
    }
 
@@ -1660,80 +2061,74 @@ class CompilerConfigs : List<CompilerConfig>
       AVLTree<String> list { };
       for(ccfg : this)
       {
-         for(occfg : oldConfigs)
+         bool found = false;
+         for(occfg : oldConfigs; !strcmp(ccfg.name, occfg.name))
          {
-            if(!strcmp(ccfg.name, occfg.name))
-            {
-               if(ccfg.OnCompare(occfg))
-                  list.Add(CopyString(ccfg.name));
-               break;
-            }
+            found = true;
+            if(ccfg.OnCompare(occfg))
+               list.Add(CopyString(ccfg.name));
+            break;
          }
+         if(!found)
+            list.Add(CopyString(ccfg.name));
       }
       return list;
    }
 
-   void ::read()
+   bool read(IDESettingsContainer settingsContainer)
    {
       if(settingsContainer.settingsFilePath)
       {
          char dir[MAX_LOCATION];
          char path[MAX_LOCATION];
          Class _class = class(CompilerConfig);
-         getConfigFilePath(path, _class, dir, null);
+         settingsContainer.getConfigFilePath(path, _class, dir, null);
          if(dir[0])
          {
-            CompilerConfigs ccfgs { };
             AVLTree<const String> addedConfigs { };
-            IDESettings s = (IDESettings)settingsContainer.data;
             Map<String, CompilerConfig> compilerConfigsByName = getCompilerConfigsByName(dir);
             MapIterator<const String, CompilerConfig> it { map = compilerConfigsByName };
+            Free();
+            settingsContainer.compilerConfigs = this; // Merge IDEConfigHolder / IDESettingsContainer?
             if(it.Index("Default", false))
             {
                CompilerConfig ccfg = it.data;
-               ccfgs.Add(ccfg.Copy());
+               Add(ccfg.Copy());
                addedConfigs.Add(ccfg.name);
             }
             for(ccfg : compilerConfigsByName)
             {
                if(!addedConfigs.Find(ccfg.name))
                {
-                  ccfgs.Add(ccfg.Copy());
+                  Add(ccfg.Copy());
                   addedConfigs.Add(ccfg.name);
                }
             }
-            for(ccfg : s.compilerConfigs)
-            {
-               if(!addedConfigs.Find(ccfg.name))
-                  ccfgs.Add(ccfg.Copy());
-            }
             delete addedConfigs;
-            s.property::compilerConfigs = ccfgs;
-            fix();
+            ensureDefaults();
             compilerConfigsByName.Free();
             delete compilerConfigsByName;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-            ide.UpdateCompilerConfigs(true);
-#endif
+            settingsContainer.onLoadCompilerConfigs();
+            return true;
          }
       }
+      return false;
    }
 
-   void ::write(AVLTree<String> cfgsToWrite)
+   void write(IDESettingsContainer settingsContainer, AVLTree<String> cfgsToWrite)
    {
       char dir[MAX_LOCATION];
       char path[MAX_LOCATION];
       Map<String, String> paths;
-      IDESettings s = (IDESettings)settingsContainer.data;
-      getConfigFilePath(path, class(CompilerConfig), dir, null);
+      settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
       paths = getCompilerConfigFilePathsByName(dir);
       {
          MapIterator<String, String> it { map = paths };
-         for(c : s.compilerConfigs)
+         for(c : this)
          {
             CompilerConfig ccfg = c;
             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
-               ccfg.write();
+               ccfg.write(settingsContainer);
             if(it.Index(ccfg.name, false))
             {
                delete it.data;
@@ -1817,6 +2212,24 @@ const String GetLanguageString()
    return language;
 }
 
+void setEcereLanguageInWinRegEnvironment(const char * languageCode)
+{
+#ifdef __WIN32__
+   HKEY key = null;
+   uint16 wLanguage[256];
+   DWORD status;
+   wLanguage[0] = 0;
+
+   RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
+   if(key)
+   {
+      UTF8toUTF16Buffer(languageCode, wLanguage, sizeof(wLanguage) / sizeof(uint16));
+      RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
+      RegCloseKey(key);
+   }
+#endif
+}
+
 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
 {
    bool restart = true;