ide/settings: Fixed color scheme issues
[sdk] / ide / src / IDESettings.ec
index dac321a..b0feab9 100644 (file)
-#if defined(__WIN32__)
-#define MessageBox _MessageBox
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#undef MessageBox
-#endif
-
 #ifdef ECERE_STATIC
 public import static "ecere"
 #else
 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";
+define earDefaultCommand = "ear";
+define cppDefaultCommand = "gcc"; // As per #624 we decided to default to "gcc"...
+define ccDefaultCommand = "gcc";
+define cxxDefaultCommand = "g++";
+//define ldDefaultCommand = "gcc";
+define arDefaultCommand = "ar";
+define objectDefaultFileExt = "o";
+define outputDefaultFileExt = "";
+
 import "StringsBox"
 
 import "OldIDESettings"
 
+#ifdef __WIN32__
+#define WIN32_LEAN_AND_MEAN
+#define Sleep _Sleep
+#include <windows.h>
+#undef MoveFileEx
+#undef Sleep
+#undef MessageBox
+#undef GetObject
+#endif
+
 define MaxRecent = 9;
 
 enum DirTypes { includes, libraries, executables };
@@ -208,7 +388,7 @@ CompilerConfig MakeDefaultCompiler(const char * name, bool readOnly)
       name,
       readOnly,
       gcc,
-      GetRuntimePlatform(),
+      __runtimePlatform,
       1,
       makeDefaultCommand,
       ecpDefaultCommand,
@@ -217,25 +397,168 @@ CompilerConfig MakeDefaultCompiler(const char * name, bool readOnly)
       earDefaultCommand,
       cppDefaultCommand,
       ccDefaultCommand,
-      cxxDefaultCommand
+      cxxDefaultCommand,
+      arDefaultCommand
+      //ldDefaultCommand
    };
    incref defaultCompiler;
    return defaultCompiler;
 }
 
-class IDESettingsContainer : GlobalSettings
-{
+//#define SETTINGS_TEST
 #ifdef SETTINGS_TEST
-   settingsName = "ecereIDESettingsTest";
+define settingsDir = ".ecereIDE-SettingsTest";
+define ideSettingsName = "ecereIDE-SettingsTest";
 #else
-   settingsName = "ecereIDE";
+define settingsDir = ".ecereIDE";
+define ideSettingsName = "ecereIDE";
 #endif
 
-   virtual void OnLoad(GlobalSettingsData data);
+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)
+         {
+            driver = "ECON";
+            settingsName = "config";
+            settingsExtension = "econ";
+            settingsDirectory = settingsDir;
+         }
+         else
+         {
+            driver = "JSON";
+            settingsName = ideSettingsName;
+            settingsExtension = null;
+            settingsDirectory = null;
+         }
+      }
+   }
 
    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;
 
    IDESettingsContainer()
@@ -247,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];
@@ -256,12 +579,14 @@ private:
 
          strcpy(configFilePath, path);
          PathCat(configFilePath, "Data");
-         PathCat(configFilePath, "ecereIDE.ini");
+         PathCat(configFilePath, ideSettingsName);
+         ChangeExtension(configFilePath, "ini", configFilePath);
 
          strcpy(defaultConfigFilePath, path);
          PathCat(defaultConfigFilePath, "App");
          PathCat(defaultConfigFilePath, "DefaultData");
-         PathCat(defaultConfigFilePath, "ecereIDE.ini");
+         PathCat(defaultConfigFilePath, ideSettingsName);
+         ChangeExtension(defaultConfigFilePath, "ini", defaultConfigFilePath);
 
          if(FileExists(defaultConfigFilePath))
          {
@@ -290,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;
@@ -301,20 +627,31 @@ private:
             CloseAndMonitor();
 
             MessageBox { master = w, type = ok, isModal = true,
+                  creationActivation = flash,
                   text = "Global Settings Modified Externally",
                   contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
                   "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();
-         }
+         }*/
       }
    }
 
    SettingsIOResult Load()
    {
-      SettingsIOResult result = GlobalSettings::Load();
-      IDESettings data = (IDESettings)this.data;
-      CompilerConfig defaultCompiler = null;
+      IDESettings data;
+      SettingsIOResult result;
+      useNewConfigurationFiles = true;
+      result = GlobalSettings::Load();
+      data = (IDESettings)this.data;
+      oldConfig = false;
+      if(result == fileNotFound)
+      {
+         oldConfig = true;
+         useNewConfigurationFiles = false;
+         result = GlobalSettings::Load();
+      }
+      data = (IDESettings)this.data;
       if(!data)
       {
          this.data = IDESettings { };
@@ -335,8 +672,8 @@ private:
                for(c : oldSettings.compilerConfigs)
                   data.compilerConfigs.Add(c.Copy());
 
-               for(s : oldSettings.recentFiles) data.recentFiles.Add(CopyString(s));
-               for(s : oldSettings.recentProjects) data.recentProjects.Add(CopyString(s));
+               for(s : oldSettings.recentFiles) data.recentFiles.Add(s);
+               for(s : oldSettings.recentProjects) data.recentProjects.Add(s);
 
                data.docDir = oldSettings.docDir;
                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
@@ -361,62 +698,323 @@ private:
             data.caretFollowsScrolling = false; //true;
          }
       }
-      // Ensure we have a default compiler
-      defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
-      if(!defaultCompiler)
-      {
-         defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
-         data.compilerConfigs.Add(defaultCompiler);
-      }
-
-      // We incref the compilers below, so reset refCount to 0
-      defaultCompiler._refCount = 0;
 
       CloseAndMonitor();
       FileGetSize(settingsFilePath, &settingsFileSize);
-      if(data.compilerConfigs)
+      if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
+         data.ManagePortablePaths(moduleLocation, true);
+      data.ForcePathSeparatorStyle(true);
+
+      if(!data.colorSchemes || !data.colorSchemes.count)
+      {
+         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(c : data.compilerConfigs)
+         for(cs : data.colorSchemes; cs.name && !strcmp(cs.name, data.activeColorScheme))
          {
-            CompilerConfig compiler = c;
-            char * cxxCommand = compiler.cxxCommand;
-            if(!cxxCommand || !cxxCommand[0])
-               compiler.cxxCommand = cxxDefaultCommand;
-            incref compiler;
+            colorScheme = cs;
+            break;
          }
       }
-      if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
-         data.ManagePortablePaths(moduleLocation, true);
-      data.ForcePathSeparatorStyle(true);
-      OnLoad(data);
+      if(!colorScheme)
+      {
+         colorScheme = data.colorSchemes[0];
+         data.activeColorScheme = colorScheme.name;
+      }
+
+      // Import from previous ecereIDE settings
+      if(oldConfig)
+      {
+         // Save first so that settingsFilePath get set up
+         Save();
+
+         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;
    }
 
    SettingsIOResult Save()
    {
       SettingsIOResult result;
-
-      IDESettings data = (IDESettings)this.data;
+      IDESettings data;
+      useNewConfigurationFiles = true;
+      data = (IDESettings)this.data;
       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
          data.ManagePortablePaths(moduleLocation, false);
       data.ForcePathSeparatorStyle(true);
+      if(oldConfig)
+         settingsFilePath = null;
       result = GlobalSettings::Save();
       if(result != success)
          PrintLn("Error saving IDE settings");
+      else
+         oldConfig = false;
       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
          data.ManagePortablePaths(moduleLocation, true);
+
       CloseAndMonitor();
       FileGetSize(settingsFilePath, &settingsFileSize);
+
+      return result;
+   }
+}
+
+static Map<String, String> getCompilerConfigFilePathsByName(const char * path)
+{
+   Map<String, String> map { };
+   FileListing fl { path, extensions = "econ" };
+   while(fl.Find())
+   {
+      if(fl.stats.attribs.isFile)
+      {
+         char name[MAX_FILENAME];
+         char * path = CopyString(fl.path);
+         MakeSlashPath(path);
+         GetLastDirectory(path, name);
+         StripExtension(name);
+         map[name] = path;
+      }
+   }
+   return map;
+}
+
+static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
+{
+   Map<String, CompilerConfig> map { };
+   FileListing fl { path, extensions = "econ" };
+   while(fl.Find())
+   {
+      if(fl.stats.attribs.isFile)
+      {
+         char name[MAX_FILENAME];
+         char * path = CopyString(fl.path);
+         MakeSlashPath(path);
+         GetLastDirectory(path, name);
+         StripExtension(name);
+         {
+            CompilerConfig ccfg = CompilerConfig::read(path);
+            if(ccfg)
+               map[name] = ccfg;
+         }
+         delete path;
+      }
+   }
+   return map;
+}
+
+static SettingsIOResult writeConfigFile(const char * path, Class dataType, void * data)
+{
+   SettingsIOResult result = error;
+   SafeFile sf = SafeFile::open(path, write);
+   if(sf)
+   {
+      WriteECONObject(sf.file, dataType, data, 0);
+      sf.sync();
+      delete sf;
+      result = success;
+   }
+   else
+      PrintLn($"error: could not safely open file for writing configuration: ", path);
+   return result;
+}
+
+static SettingsIOResult readConfigFile(const char * path, Class dataType, void ** data)
+{
+   SettingsIOResult result = error;
+   SafeFile sf;
+   if(!FileExists(path))
+      result = fileNotFound;
+   else if((sf = SafeFile::open(path, read)))
+   {
+      JSONResult jsonResult;
+      {
+         ECONParser parser { f = sf.file };
+         sf.file.Seek(0, start);
+         jsonResult = parser.GetObject(dataType, data);
+         if(jsonResult != success)
+            delete *data;
+         delete parser;
+      }
+      if(jsonResult == success)
+         result = success;
+      else
+      {
+         result = fileNotCompatibleWithDriver;
+         PrintLn($"error: could not parse configuration file: ", path);
+      }
+      delete sf;
+   }
+   return result;
+}
+
+class SafeFile
+{
+   File file;
+   FileOpenMode mode;
+   char path[MAX_LOCATION];
+   char tmp[MAX_LOCATION];
+
+   SafeFile ::open(const char * path, FileOpenMode mode)
+   {
+      SafeFile result = null;
+      if(mode == write || mode == read)
+      {
+         SafeFile sf { mode = mode };
+         int c;
+         bool locked = false;
+         FileLock lockType = mode == write ? exclusive : shared;
+
+         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: SafeFile::open: does not yet support FileOpenMode::", mode);
       return result;
    }
+
+   void sync()
+   {
+      if(file && mode == write)
+      {
+         int c;
+         File f = FileOpen(path, readWrite);
+         if(!f)
+         {
+            f = FileOpen(path, writeRead);
+            if(f)
+            {
+               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
+            {
+               delete f;
+               PrintLn($"warning: SafeFile::sync: failed to lock file for ", mode);
+            }
+         }
+      }
+   }
+
+
+   ~SafeFile()
+   {
+      if(file)
+      {
+         file.Unlock(0,0, false);
+         delete file;
+      }
+   }
+}
+
+class RecentFilesData
+{
+public:
+   RecentFiles recentFiles;
+}
+
+class RecentWorkspacesData
+{
+public:
+   RecentWorkspaces recentWorkspaces;
 }
 
 class IDESettings : GlobalSettingsData
 {
 public:
-   List<CompilerConfig> compilerConfigs { };
-   Array<String> recentFiles { };
-   Array<String> recentProjects { };
+   property CompilerConfigs compilerConfigs
+   {
+      set { /*if(settingsContainer.oldConfig)*/ { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
+      get { return compilerConfigs; }
+      isset { return false; }
+   }
+   property RecentFiles recentFiles
+   {
+      set { if(recentFiles) recentFiles.Free(); delete recentFiles; if(value) recentFiles = value; }
+      get { return recentFiles; }
+      isset { return false; }
+   }
+   property RecentWorkspaces recentProjects
+   {
+      set { if(recentProjects) recentProjects.Free(); delete recentProjects; if(value) recentProjects = value; }
+      get { return recentProjects; }
+      isset { return false; }
+   }
    property const char * docDir
    {
       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
@@ -481,7 +1079,45 @@ 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;
    char * ideFileDialogLocation;
    char * ideProjectFileDialogLocation;
@@ -490,34 +1126,25 @@ private:
    char * compilerConfigsDir;
    char * defaultCompiler;
    String language;
+   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()
    {
       compilerConfigs.Free();
       delete compilerConfigs;
-      recentFiles.Free();
-      delete recentFiles;
-      recentProjects.Free();
-      delete recentProjects;
+      if(recentProjects) { recentFiles.Free(); delete recentFiles; }
+      if(recentProjects) { recentProjects.Free(); delete recentProjects; }
       delete docDir;
 
       delete projectDefaultTargetDir;
@@ -529,6 +1156,11 @@ private:
       delete ideFileDialogLocation;
       delete ideProjectFileDialogLocation;
       delete displayDriver;
+
+      delete codeEditorFont;
+
+      colorSchemes.Free();
+      delete activeColorScheme;
    }
 
    void ForcePathSeparatorStyle(bool unixStyle)
@@ -569,24 +1201,8 @@ private:
             }
          }
       }
-      if(recentFiles && recentFiles.count)
-      {
-         int c;
-         for(c = 0; c < recentFiles.count; c++)
-         {
-            if(recentFiles[c] && recentFiles[c][0])
-               ChangeCh(recentFiles[c], from, to);
-         }
-      }
-      if(recentProjects && recentProjects.count)
-      {
-         int c;
-         for(c = 0; c < recentProjects.count; c++)
-         {
-            if(recentProjects[c] && recentProjects[c][0])
-               ChangeCh(recentProjects[c], from, to);
-         }
-      }
+      recentFiles.changeChar(from, to);
+      recentProjects.changeChar(from, to);
       if(docDir && docDir[0])
          ChangeCh(docDir, from, to);
       if(ideFileDialogLocation && ideFileDialogLocation[0])
@@ -688,43 +1304,125 @@ private:
       }
       return output;
    }
+}
+
+class RecentFiles : RecentPaths
+{
+   void read(IDESettingsContainer settingsContainer)
+   {
+      char path[MAX_LOCATION];
+      RecentFilesData d = null;
+      Class _class = class(RecentFilesData);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      readConfigFile(path, _class, &d);
+      if(d && d.recentFiles && d.recentFiles.count)
+      {
+         Free();
+         Copy((void *)d.recentFiles);
+         settingsContainer.recentFiles = this; // Merge IDEConfigHolder / IDESettingsContainer?
+      }
+      delete d;
+      settingsContainer.recentFilesMonitor.fileName = path;
+      settingsContainer.recentFilesMonitor.StartMonitoring();
+      settingsContainer.onLoadRecentFiles();
+   }
+
+   void write(IDESettingsContainer settingsContainer)
+   {
+      char path[MAX_LOCATION];
+      RecentFilesData d { };
+      Class _class = class(RecentFilesData);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      d.recentFiles = this;
+      writeConfigFile(path, _class, d);
+      d.recentFiles = null;
+      delete d;
+   }
+}
+
+class RecentWorkspaces : RecentPaths
+{
+   void read(IDESettingsContainer settingsContainer)
+   {
+      char path[MAX_LOCATION];
+      RecentWorkspacesData d = null;
+      Class _class = class(RecentWorkspacesData);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      readConfigFile(path, _class, &d);
+      if(d && d.recentWorkspaces && d.recentWorkspaces.count)
+      {
+         Free();
+         Copy((void *)d.recentWorkspaces);
+         settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
+      }
+      delete d;
+      settingsContainer.recentProjectsMonitor.fileName = path;
+      settingsContainer.recentProjectsMonitor.StartMonitoring();
+      settingsContainer.onLoadRecentProjects();
+   }
 
-   void AddRecentFile(const char * fileName)
+   void write(IDESettingsContainer settingsContainer)
+   {
+      char path[MAX_LOCATION];
+      RecentWorkspacesData d { };
+      Class _class = class(RecentWorkspacesData);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      d.recentWorkspaces = this;
+      writeConfigFile(path, _class, d);
+      d.recentWorkspaces = null;
+      delete d;
+   }
+}
+
+class RecentPaths : Array<String>
+{
+   IteratorPointer Add(T value)
    {
       int c;
-      char * filePath = CopyString(fileName);
+      char * filePath = (char *)value;
       ChangeCh(filePath, '\\', '/');
-      for(c = 0; c<recentFiles.count; c++)
+      for(c = 0; c < count; c++)
       {
-         if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
+         if(this[c] && !fstrcmp(this[c], filePath))
          {
-            recentFiles.Delete((void *)&recentFiles[c]);
+            Delete((void *)&this[c]);
             c--;
          }
       }
-      while(recentFiles.count >= MaxRecent)
-         recentFiles.Delete(recentFiles.GetLast());
-      recentFiles.Insert(null, filePath);
-      //UpdateRecentMenus(owner);
+      return Array::Add((T)filePath);
    }
 
-   void AddRecentProject(const char * projectName)
+   IteratorPointer addRecent(const String value)
    {
       int c;
-      char * filePath = CopyString(projectName);
+      char * filePath = CopyString((char *)value);
+      IteratorPointer ip;
       ChangeCh(filePath, '\\', '/');
-      for(c = 0; c<recentProjects.count; c++)
+      for(c = 0; c < count; c++)
       {
-         if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
+         if(this[c] && !fstrcmp(this[c], filePath))
          {
-            recentProjects.Delete((void *)&recentProjects[c]);
+            Delete((void *)&this[c]);
             c--;
          }
       }
-      while(recentProjects.count >= MaxRecent)
-         recentProjects.Delete(recentProjects.GetLast());
-      recentProjects.Insert(null, filePath);
-      //UpdateRecentMenus(owner);
+      while(count >= MaxRecent)
+         Delete(GetLast());
+      ip = Insert(null, filePath);
+      return ip;
+   }
+
+   void changeChar(char from, char to)
+   {
+      if(this && count)
+      {
+         int c;
+         for(c = 0; c < count; c++)
+         {
+            if(this[c] && this[c][0])
+               ChangeCh(this[c], from, to);
+         }
+      }
    }
 }
 
@@ -807,12 +1505,7 @@ class CompilerConfig
 public:
    property const char * name
    {
-      set
-      {
-         delete name;
-         if(value)
-            name = CopyString(value);
-      }
+      set { delete name; if(value) name = CopyString(value); }
       get { return name; }
    }
    bool readOnly;
@@ -867,7 +1560,43 @@ public:
       get { return cxxCommand; }
       isset { return cxxCommand && cxxCommand[0]; }
    }
-   property const char * execPrefixCommand // <-- old name for json ide settings file compatibility
+   property const char * arCommand
+   {
+      set { delete arCommand; if(value && value[0]) arCommand = CopyString(value); }
+      get { return arCommand; }
+      isset { return arCommand && arCommand[0]; }
+   }
+   property const char * ldCommand
+   {
+      set { delete ldCommand; if(value && value[0]) ldCommand = CopyString(value); }
+      get { return ldCommand; }
+      isset { return ldCommand && ldCommand[0]; }
+   }
+   property const char * objectFileExt
+   {
+      set { delete objectFileExt; if(value && value[0]) objectFileExt = CopyString(value); }
+      get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
+      isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
+   }
+   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 executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
+      get { return executableFileExt; }
+      isset { return executableFileExt && executableFileExt[0]; }
+   }
+   property const char * executableLauncher
    {
       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
       get { return executableLauncher; }
@@ -885,7 +1614,7 @@ public:
       get { return distccHosts; }
       isset { return distccHosts && distccHosts[0]; }
    }
-   property const char * gccPrefix // <-- old name for json ide settings file compatibility
+   property const char * gnuToolchainPrefix
    {
       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
       get { return gnuToolchainPrefix; }
@@ -897,6 +1626,8 @@ public:
       get { return sysroot; }
       isset { return sysroot && sysroot[0]; }
    }
+   bool resourcesDotEar;
+   bool noStripTarget;
    property Array<String> includeDirs
    {
       set
@@ -1009,6 +1740,20 @@ public:
       get { return compilerFlags; }
       isset { return compilerFlags.count != 0; }
    }
+   property Array<String> cxxFlags
+   {
+      set
+      {
+         cxxFlags.Free();
+         if(value)
+         {
+            delete cxxFlags;
+            cxxFlags = value;
+         }
+      }
+      get { return cxxFlags; }
+      isset { return cxxFlags.count != 0; }
+   }
    property Array<String> linkerFlags
    {
       set
@@ -1023,6 +1768,36 @@ public:
       get { return linkerFlags; }
       isset { return linkerFlags.count != 0; }
    }
+   // json backward compatibility
+   property const char * gccPrefix
+   {
+      set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
+      get { return gnuToolchainPrefix; }
+      isset { return false; }
+   }
+   property const char * execPrefixCommand
+   {
+      set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
+      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 { };
@@ -1034,6 +1809,7 @@ private:
    Array<String> excludeLibs { };
    Array<String> eCcompilerFlags { };
    Array<String> compilerFlags { };
+   Array<String> cxxFlags { };
    Array<String> linkerFlags { };
    char * name;
    char * makeCommand;
@@ -1044,6 +1820,12 @@ private:
    char * cppCommand;
    char * ccCommand;
    char * cxxCommand;
+   char * ldCommand;
+   char * arCommand;
+   char * objectFileExt;
+   char * staticLibFileExt;
+   char * sharedLibFileExt;
+   char * executableFileExt;
    char * executableLauncher;
    char * distccHosts;
    char * gnuToolchainPrefix;
@@ -1064,6 +1846,12 @@ private:
       delete cppCommand;
       delete ccCommand;
       delete cxxCommand;
+      delete ldCommand;
+      delete arCommand;
+      delete objectFileExt;
+      delete staticLibFileExt;
+      delete sharedLibFileExt;
+      delete executableFileExt;
       delete makeCommand;
       delete executableLauncher;
       delete distccHosts;
@@ -1076,9 +1864,58 @@ private:
       if(prepDirectives) { prepDirectives.Free(); }
       if(excludeLibs) { excludeLibs.Free(); }
       if(compilerFlags) { compilerFlags.Free(); }
+      if(cxxFlags) { cxxFlags.Free(); }
       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
       if(linkerFlags) { linkerFlags.Free(); }
    }
+
+   int OnCompare(CompilerConfig b)
+   {
+      int result;
+      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)) &&
+         !(result = earCommand.OnCompare(b.earCommand)) &&
+         !(result = cppCommand.OnCompare(b.cppCommand)) &&
+         !(result = ccCommand.OnCompare(b.ccCommand)) &&
+         !(result = cxxCommand.OnCompare(b.cxxCommand)) &&
+         !(result = ldCommand.OnCompare(b.ldCommand)) &&
+         !(result = arCommand.OnCompare(b.arCommand)) &&
+         !(result = objectFileExt.OnCompare(b.objectFileExt)) &&
+         !(result = outputFileExt.OnCompare(b.outputFileExt)) &&
+         !(result = makeCommand.OnCompare(b.makeCommand)) &&
+         !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
+         !(result = distccHosts.OnCompare(b.distccHosts)) &&
+         !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
+         !(result = sysroot.OnCompare(b.sysroot)));
+
+      if(!result &&
+         !(result = includeDirs.OnCompare(b.includeDirs)) &&
+         !(result = libraryDirs.OnCompare(b.libraryDirs)) &&
+         !(result = executableDirs.OnCompare(b.executableDirs)) &&
+         !(result = environmentVars.OnCompare(b.environmentVars)) &&
+         !(result = prepDirectives.OnCompare(b.prepDirectives)) &&
+         !(result = excludeLibs.OnCompare(b.excludeLibs)) &&
+         !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
+         !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
+         !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
+         !(result = linkerFlags.OnCompare(b.linkerFlags)));
+      return result;
+   }
+
+public:
    CompilerConfig Copy()
    {
       CompilerConfig copy
@@ -1096,13 +1933,21 @@ private:
          cppCommand,
          ccCommand,
          cxxCommand,
+         arCommand,
+         ldCommand,
+         objectFileExt,
+         staticLibFileExt,
+         sharedLibFileExt,
+         executableFileExt,
          executableLauncher,
          ccacheEnabled,
          distccEnabled,
          false,
          distccHosts,
          gnuToolchainPrefix,
-         sysroot
+         sysroot,
+         resourcesDotEar,
+         noStripTarget
       };
       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
@@ -1111,14 +1956,197 @@ private:
       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
+      for(s : cxxFlags) copy.cxxFlags.Add(CopyString(s));
       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
 
       incref copy;
       return copy;
    }
+
+   CompilerConfig ::read(const char * path)
+   {
+      CompilerConfig d = null;
+      readConfigFile(path, class(CompilerConfig), &d);
+      return d;
+   }
+
+   void write(IDESettingsContainer settingsContainer)
+   {
+      char dir[MAX_LOCATION];
+      char path[MAX_LOCATION];
+      const char * settingsFilePath = settingsContainer.settingsFilePath;
+      settingsContainer.getConfigFilePath(path, _class, dir, name);
+      if(FileExists(settingsFilePath) && !FileExists(dir))
+      {
+         MakeDir(dir);
+         if(!FileExists(dir))
+            PrintLn($"Error creating compiler configs directory at ", dir, " location.");
+      }
+      writeConfigFile(path, _class, this);
+   }
+}
+
+class CompilerConfigs : List<CompilerConfig>
+{
+   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()
+   {
+      // Ensure we have a default compiler
+      CompilerConfig defaultCompiler = GetCompilerConfig(defaultCompilerName);
+      if(!defaultCompiler)
+      {
+         defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
+         Insert(null, defaultCompiler);
+         defaultCompiler = null;
+      }
+      delete defaultCompiler;
+
+      for(ccfg : this)
+      {
+         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;
+      }
+   }
+
+   AVLTree<String> getWriteRequiredList(CompilerConfigs oldConfigs)
+   {
+      AVLTree<String> list { };
+      for(ccfg : this)
+      {
+         bool found = false;
+         for(occfg : oldConfigs; !strcmp(ccfg.name, occfg.name))
+         {
+            found = true;
+            if(ccfg.OnCompare(occfg))
+               list.Add(CopyString(ccfg.name));
+            break;
+         }
+         if(!found)
+            list.Add(CopyString(ccfg.name));
+      }
+      return list;
+   }
+
+   bool read(IDESettingsContainer settingsContainer)
+   {
+      if(settingsContainer.settingsFilePath)
+      {
+         char dir[MAX_LOCATION];
+         char path[MAX_LOCATION];
+         Class _class = class(CompilerConfig);
+         settingsContainer.getConfigFilePath(path, _class, dir, null);
+         if(dir[0])
+         {
+            AVLTree<const String> addedConfigs { };
+            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;
+               Add(ccfg.Copy());
+               addedConfigs.Add(ccfg.name);
+            }
+            for(ccfg : compilerConfigsByName)
+            {
+               if(!addedConfigs.Find(ccfg.name))
+               {
+                  Add(ccfg.Copy());
+                  addedConfigs.Add(ccfg.name);
+               }
+            }
+            delete addedConfigs;
+            ensureDefaults();
+            compilerConfigsByName.Free();
+            delete compilerConfigsByName;
+            settingsContainer.onLoadCompilerConfigs();
+            return true;
+         }
+      }
+      return false;
+   }
+
+   void write(IDESettingsContainer settingsContainer, AVLTree<String> cfgsToWrite)
+   {
+      char dir[MAX_LOCATION];
+      char path[MAX_LOCATION];
+      Map<String, String> paths;
+      settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
+      paths = getCompilerConfigFilePathsByName(dir);
+      {
+         MapIterator<String, String> it { map = paths };
+         for(c : this)
+         {
+            CompilerConfig ccfg = c;
+            if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
+               ccfg.write(settingsContainer);
+            if(it.Index(ccfg.name, false))
+            {
+               delete it.data;
+               it.Remove();
+            }
+         }
+      }
+      for(p : paths)
+      {
+         const char * path = p;
+         DeleteFile(path);
+      }
+      paths.Free();
+      delete paths;
+   }
 }
 
+#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
 struct LanguageOption
 {
    const String name;
@@ -1184,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;
@@ -1298,23 +2344,7 @@ bool LanguageRestart(const char * code, Application app, IDESettings settings, I
          settings.language = code;
          settingsContainer.Save();
 
-#if defined(__WIN32__)
-         // Set LANGUAGE environment variable
-         {
-            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(code, wLanguage, sizeof(wLanguage) / sizeof(uint16));
-               RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
-               RegCloseKey(key);
-            }
-         }
-#endif
+         setEcereLanguageInWinRegEnvironment(code);
 
          if(eClass_IsDerived(app._class, class(GuiApplication)))
          {
@@ -1357,3 +2387,4 @@ bool LanguageRestart(const char * code, Application app, IDESettings settings, I
    delete command;
    return restart;
 }
+#endif