ide/IDESettings: Fixed issues with fresh install
[sdk] / ide / src / IDESettings.ec
1 #ifdef ECERE_STATIC
2 public import static "ecere"
3 #else
4 public import "ecere"
5 #endif
6
7 // *** Color Schemes ***
8 class IDEColorScheme
9 {
10    String name;
11    SyntaxColorScheme syntaxColors;
12 public:
13
14    property const String name
15    {
16       set { delete name; name = CopyString(value); }
17       get { return name; }
18    }
19
20    Color selectionColor;
21    Color selectionText;
22    Color viewsBackground;
23    Color viewsText;
24    Color outputBackground;
25    Color outputText;
26    Color projectViewBackground;
27    Color projectViewText;
28    Color codeEditorBG;
29    Color codeEditorFG;
30    Color marginColor;
31    Color selectedMarginColor;
32    Color lineNumbersColor;
33    Color sheetSelectionColor;
34    Color sheetSelectionText;
35
36    property SyntaxColorScheme syntaxColors
37    {
38       set { delete syntaxColors; syntaxColors = value; if(value) incref value; }
39       get { return syntaxColors; }
40    }
41
42    ~IDEColorScheme()
43    {
44       delete syntaxColors;
45       delete name;
46    }
47 }
48
49 IDEColorScheme colorScheme;
50
51 // Default Color Schemes
52 IDEColorScheme darkColorScheme
53 {
54    name = "Classic Dark";
55    selectionColor = lightYellow;
56    selectionText = Color { 30, 40, 50 };
57    viewsBackground = Color { 30, 40, 50 };
58    viewsText = lightGray;
59    outputBackground = black;
60    outputText = lime;
61    sheetSelectionColor = Color { 170, 220, 255 };
62    sheetSelectionText = black;
63    projectViewBackground = Color { 30, 40, 50 };
64    projectViewText = lightGray;
65    codeEditorBG = black;
66    codeEditorFG = ivory;
67    marginColor = Color {24, 24, 24};
68    selectedMarginColor = Color {64, 64, 64};
69    lineNumbersColor = Color {160, 160, 160};
70    syntaxColors =
71    {
72       keywordColors = [ skyBlue, skyBlue ];
73       commentColor = Color { 125, 125, 125 };
74       charLiteralColor = Color { 245, 50, 245 };
75       stringLiteralColor = Color { 245, 50, 245 };
76       preprocessorColor = { 120, 220, 140 };
77       numberColor = Color {   0, 192, 192 };
78    };
79 };
80
81 IDEColorScheme lightColorScheme
82 {
83    name = "Classic Light";
84    selectionColor = Color { 10, 36, 106 };
85    selectionText = white;
86    viewsBackground = white;
87    viewsText = black;
88    outputBackground = white;
89    outputText = black;
90    sheetSelectionColor = Color { 170, 220, 255 };
91    sheetSelectionText = black;
92    projectViewBackground = white;
93    projectViewText = black;
94    codeEditorBG = white;
95    codeEditorFG = black;
96    marginColor = Color {230, 230, 230};
97    selectedMarginColor = Color {200, 200, 200};
98    lineNumbersColor = Color {60, 60, 60};
99    syntaxColors =
100    {
101       keywordColors = [ blue, blue ];
102       commentColor = dimGray;
103       charLiteralColor = crimson;
104       stringLiteralColor = crimson;
105       preprocessorColor = green;
106       numberColor = teal;
107    };
108 };
109
110 IDEColorScheme greenColorScheme
111 {
112    name = "Green",
113    selectionColor = 0x00FFFFE0,
114    selectionText = 0x001E2832,
115    viewsBackground = 0x001E2832,
116    viewsText = 0x00D3D3D3,
117    outputBackground = 0x00000000,
118    outputText = 0x0000FF00,
119    projectViewBackground = 0x001E2832,
120    projectViewText = 0x00D3D3D3,
121    codeEditorBG = 0x00000000,
122    codeEditorFG = 0x00FFFFF0,
123    marginColor = 0x001100,
124    selectedMarginColor = 0x002200,
125    lineNumbersColor = 0x00FF00,
126    sheetSelectionColor = 0x00AADCFF,
127    sheetSelectionText = 0x00000000,
128    syntaxColors = {
129       commentColor = 0x008c00,
130       charLiteralColor = 0x89de00,
131       stringLiteralColor = 0x89de00,
132       preprocessorColor = 0x0078DC8C,
133       numberColor = { 8, 237, 141 },
134       keywordColors = [
135          0x00e09d,
136          0x00e09d
137       ]
138    }
139 };
140
141 IDEColorScheme grayColorScheme
142 {
143    name = "Gray & Orange",
144    selectionColor = 0x00FFFFE0,
145    selectionText = 0x001E2832,
146    viewsBackground = 0x001E2832,
147    viewsText = 0x00D3D3D3,
148    outputBackground = 0x00000000,
149    outputText = 0x0000FF00,
150    projectViewBackground = 0x001E2832,
151    projectViewText = 0x00D3D3D3,
152    codeEditorBG = 0x00202020,
153    codeEditorFG = 0x00E2E2E5,
154    marginColor = 0x00303030,
155    selectedMarginColor = 0x00404040,
156    lineNumbersColor = 0x00939395,
157    sheetSelectionColor = 0x00AADCFF,
158    sheetSelectionText = 0x00000000,
159    syntaxColors = {
160       commentColor = 0x005F5F5F,
161       charLiteralColor = 0x0089DE00,
162       stringLiteralColor = 0x00707070,
163       preprocessorColor = 0x00FAF4C6,
164       numberColor = 0x00FF9800,
165       keywordColors = [
166          0x00FF9800,
167          0x00FF9800
168       ]
169    }
170 };
171
172 define ecpDefaultCommand = "ecp";
173 define eccDefaultCommand = "ecc";
174 define ecsDefaultCommand = "ecs";
175 define earDefaultCommand = "ear";
176 define cppDefaultCommand = "gcc"; // As per #624 we decided to default to "gcc"...
177 define ccDefaultCommand = "gcc";
178 define cxxDefaultCommand = "g++";
179 //define ldDefaultCommand = "gcc";
180 define arDefaultCommand = "ar";
181 define objectDefaultFileExt = "o";
182 define outputDefaultFileExt = "";
183
184 import "StringsBox"
185
186 import "OldIDESettings"
187
188 #ifdef __WIN32__
189 #define WIN32_LEAN_AND_MEAN
190 #define Sleep _Sleep
191 #include <windows.h>
192 #undef MoveFileEx
193 #undef Sleep
194 #undef MessageBox
195 #undef GetObject
196 #endif
197
198 define MaxRecent = 9;
199
200 enum DirTypes { includes, libraries, executables };
201
202 define defaultCompilerName = "Default";
203
204 define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)$(COMPILER_SUFFIX)$(DEBUG_SUFFIX)";
205
206 const char * settingsDirectoryNames[DirTypes] =
207 {
208    "Include Files",
209    "Library Files",
210    "Executable Files"
211 };
212
213 // This function cannot accept same pointer for source and output
214 // todo: rename ReplaceSpaces to EscapeSpaceAndSpecialChars or something
215 void ReplaceSpaces(char * output, const char * source)
216 {
217    int c, dc;
218    char ch, pch = 0;
219
220    for(c = 0, dc = 0; (ch = source[c]); c++, dc++)
221    {
222       if(ch == ' ') output[dc++] = '\\';
223       if(ch == '\"') output[dc++] = '\\';
224       if(ch == '&') output[dc++] = '\\';
225       if(pch != '$')
226       {
227          if(ch == '(' || ch == ')') output[dc++] = '\\';
228          pch = ch;
229       }
230       else if(ch == ')')
231          pch = 0;
232       output[dc] = ch;
233    }
234    output[dc] = '\0';
235 }
236
237
238 enum GlobalSettingsChange { none, editorSettings, projectOptions, compilerSettings };
239
240 enum PathRelationship { unrelated, identical, siblings, subPath, parentPath, insuficientInput, pathEmpty, toEmpty, pathNull, toNull, bothEmpty, bothNull };
241 PathRelationship eString_PathRelated(const char * path, const char * to, char * pathDiff)
242 {
243    PathRelationship result;
244    if(pathDiff) *pathDiff = '\0';
245    if(path && *path && to && *to)
246    {
247       char toPart[MAX_FILENAME], toRest[MAX_LOCATION];
248       char pathPart[MAX_FILENAME], pathRest[MAX_LOCATION];
249       strcpy(toRest, to);
250       strcpy(pathRest, path);
251       for(; toRest[0] && pathRest[0];)
252       {
253          SplitDirectory(toRest, toPart, toRest);
254          SplitDirectory(pathRest, pathPart, pathRest);
255          if(!fstrcmp(pathPart, toPart)) result = siblings;
256          else break;
257       }
258       if(result == siblings)
259       {
260          if(!*toRest && !*pathRest) result = identical;
261          else if(!*pathRest) result = parentPath;
262          else result = subPath;
263          if(pathDiff && result != identical) strcpy(pathDiff, *pathRest == '\0' ? toRest : pathRest);
264       }
265       else result = unrelated;
266    }
267    else
268    {
269       if(path && to)
270       {
271          if(!*path && !*to) result = bothEmpty;
272          else if(!*path) result = pathEmpty;
273          else result = toEmpty;
274       }
275       else if(!path && !to) result = bothNull;
276       else if(!path) result = pathNull;
277       else result = toNull;
278    }
279    return result;
280 }
281
282 char * CopyValidateMakefilePath(const char * path)
283 {
284    const int map[]  =    {           0,           1,             2,             3,           4,                    5,                 6,            0,                   1,                    2,        7 };
285    const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(COMPILER_SUFFIX)", "$(DEBUG_SUFFIX)", "$(PROJECT)",  "$(CONFIGURATION)", "$(TARGET_PLATFORM)",(char *)0 };
286
287    char * copy = null;
288    if(path)
289    {
290       int len;
291       len = (int)strlen(path);
292       copy = CopyString(path);
293       if(len)
294       {
295          int c;
296          char * tmp = copy;
297          char * start = tmp;
298          Array<const char *> parts { };
299
300          for(c=0; c<len; c++)
301          {
302             if(tmp[c] == '$')
303             {
304                int v;
305                for(v=0; vars[v]; v++)
306                {
307                   if(SearchString(&tmp[c], 0, vars[v], false, false) == &tmp[c])
308                   {
309                      tmp[c] = '\0';
310                      parts.Add(start);
311                      parts.Add(vars[map[v]]);
312                      c += strlen(vars[v]);
313                      start = &tmp[c];
314                      c--;
315                      break;
316                   }
317                }
318             }
319          }
320          if(start[0])
321             parts.Add(start);
322
323          if(parts.count)
324          {
325             /*int c, */len = 0;
326             for(c=0; c<parts.count; c++) len += strlen(parts[c]);
327             copy = new char[++len];
328             copy[0] = '\0';
329             for(c=0; c<parts.count; c++) strcat(copy, parts[c]);
330          }
331          else
332             copy = null;
333          delete parts;
334          delete tmp;
335       }
336    }
337    return copy;
338 }
339
340 void ValidPathBufCopy(char *output, const char *input)
341 {
342 #ifdef __WIN32__
343    bool volumePath = false;
344 #endif
345    strcpy(output, input);
346    TrimLSpaces(output, output);
347    TrimRSpaces(output, output);
348    MakeSystemPath(output);
349 #ifdef __WIN32__
350    if(output[0] && output[1] == ':')
351    {
352       output[1] = '_';
353       volumePath = true;
354    }
355 #endif
356    {
357       const char * chars = "*|:\",<>?";
358       char ch, * s = output, * o = output;
359       while((ch = *s++)) { if(!strchr(chars, ch)) *o++ = ch; }
360       *o = '\0';
361    }
362 #ifdef __WIN32__
363    if(volumePath && output[0])
364       output[1] = ':';
365 #endif
366 }
367
368 void RemoveTrailingPathSeparator(char *path)
369 {
370    int len;
371    len = (int)strlen(path);
372    if(len>1 && path[len-1] == DIR_SEP)
373       path[--len] = '\0';
374 }
375
376 void BasicValidatePathBoxPath(PathBox pathBox)
377 {
378    char path[MAX_LOCATION];
379    ValidPathBufCopy(path, pathBox.path);
380    RemoveTrailingPathSeparator(path);
381    pathBox.path = path;
382 }
383
384 CompilerConfig MakeDefaultCompiler(const char * name, bool readOnly)
385 {
386    CompilerConfig defaultCompiler
387    {
388       name,
389       readOnly,
390       gcc,
391       __runtimePlatform,
392       1,
393       makeDefaultCommand,
394       ecpDefaultCommand,
395       eccDefaultCommand,
396       ecsDefaultCommand,
397       earDefaultCommand,
398       cppDefaultCommand,
399       ccDefaultCommand,
400       cxxDefaultCommand,
401       arDefaultCommand
402       //ldDefaultCommand
403    };
404    incref defaultCompiler;
405    return defaultCompiler;
406 }
407
408 //#define SETTINGS_TEST
409 #ifdef SETTINGS_TEST
410 define settingsDir = ".ecereIDE-SettingsTest";
411 define ideSettingsName = "ecereIDE-SettingsTest";
412 #else
413 #ifdef __WIN32__
414 define settingsDir = "EcereIDE";
415 define ideSettingsName = "EcereIDE";
416 #else
417 define settingsDir = ".ecereIDE";
418 define ideSettingsName = "ecereIDE";
419 #endif
420 #endif
421
422 class IDEConfigHolder
423 {
424    CompilerConfigs compilers { };
425    RecentFiles recentFiles { };
426    RecentWorkspaces recentWorkspaces { };
427
428    property CompilerConfigs compilers
429    {
430       set { compilers.Free(); delete compilers; compilers = value; }
431       get { return compilers; }
432    }
433    property RecentFiles recentFiles
434    {
435       set { recentFiles.Free(); delete recentFiles; recentFiles = value; }
436       get { return recentFiles; }
437    }
438    property RecentWorkspaces recentProjects
439    {
440       set { recentWorkspaces.Free(); delete recentWorkspaces; recentWorkspaces = value; }
441       get { return recentWorkspaces; }
442    }
443
444    ~IDEConfigHolder()
445    {
446       compilers.Free();
447       recentFiles.Free();
448       recentWorkspaces.Free();
449    }
450
451    void forcePathSeparatorStyle(bool unixStyle)
452    {
453       char from, to;
454       if(unixStyle)
455          from = '\\', to = '/';
456       else
457          from = '/', to = '\\';
458       recentFiles.changeChar(from, to);
459       recentWorkspaces.changeChar(from, to);
460    }
461 }
462
463 class IDESettingsContainer : GlobalSettings
464 {
465    virtual void onLoadCompilerConfigs();
466    virtual void onLoadRecentFiles();
467    virtual void onLoadRecentProjects();
468    virtual void onLoad();
469
470    CompilerConfigs compilerConfigs;
471    RecentFiles recentFiles;
472    RecentWorkspaces recentProjects;
473
474    property bool useNewConfigurationFiles
475    {
476       set
477       {
478          if(value)
479          {
480             driver = "ECON";
481             settingsName = "config";
482             settingsExtension = "econ";
483             settingsDirectory = settingsDir;
484          }
485          else
486          {
487             driver = "JSON";
488             settingsName = ideSettingsName;
489             settingsExtension = null;
490             settingsDirectory = null;
491          }
492       }
493    }
494
495    char moduleLocation[MAX_LOCATION];
496
497    FileMonitor recentFilesMonitor
498    {
499       this, fileChange = { modified = true };
500
501       bool OnFileNotify(FileChange action, const char * param)
502       {
503          File f;
504          recentFilesMonitor.StopMonitoring();
505          f = FileOpen(recentFilesMonitor.fileName, read);
506          if(f)
507          {
508             int c;
509             bool locked;
510             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
511             recentFiles.read(this);
512             f.Unlock(0,0,true);
513             delete f;
514          }
515          return true;
516       }
517    };
518
519    FileMonitor recentProjectsMonitor
520    {
521       this, fileChange = { modified = true };
522
523       bool OnFileNotify(FileChange action, const char * param)
524       {
525          File f;
526          recentProjectsMonitor.StopMonitoring();
527          f = FileOpen(recentProjectsMonitor.fileName, read);
528          if(f)
529          {
530             int c;
531             bool locked;
532             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
533             if(!recentProjects) recentProjects = { };
534             recentProjects.read(this);
535             f.Unlock(0,0,true);
536             delete f;
537          }
538          return true;
539       }
540    };
541
542    static void getConfigFilePath(char * path, Class _class, char * dir, const char * configName)
543    {
544       if(dir) *dir = 0;
545       strcpy(path, settingsFilePath);
546       StripLastDirectory(path, path);
547       if(oldConfig)
548          PathCatSlash(path, settingsDir);
549       if(_class == class(CompilerConfig))
550       {
551          PathCatSlash(path, "compilerConfigs");
552          if(dir)
553             strcpy(dir, path);
554          if(configName)
555          {
556             PathCatSlash(path, configName);
557             strcat(path, ".econ");
558          }
559       }
560       else if(_class == class(RecentFilesData))
561          PathCatSlash(path, "recentFiles.econ");
562       else if(_class == class(RecentWorkspacesData))
563          PathCatSlash(path, "recentWorkspaces.econ");
564    }
565
566 private:
567    bool oldConfig;
568    FileSize settingsFileSize;
569
570    IDESettingsContainer()
571    {
572       char path[MAX_LOCATION];
573       char * start;
574       LocateModule(null, moduleLocation);
575       strcpy(path, moduleLocation);
576       StripLastDirectory(moduleLocation, moduleLocation);
577       ChangeCh(moduleLocation, '\\', '/');
578       // PortableApps.com directory structure
579       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ecere-ide.exe")))
580       {
581          char configFilePath[MAX_LOCATION];
582          char defaultConfigFilePath[MAX_LOCATION];
583
584          start[0] = '\0';
585
586          strcpy(configFilePath, path);
587          PathCat(configFilePath, "Data");
588          PathCat(configFilePath, ideSettingsName);
589          ChangeExtension(configFilePath, "ini", configFilePath);
590
591          strcpy(defaultConfigFilePath, path);
592          PathCat(defaultConfigFilePath, "App");
593          PathCat(defaultConfigFilePath, "DefaultData");
594          PathCat(defaultConfigFilePath, ideSettingsName);
595          ChangeExtension(defaultConfigFilePath, "ini", defaultConfigFilePath);
596
597          if(FileExists(defaultConfigFilePath))
598          {
599             if(!FileExists(configFilePath))
600             {
601                File f = FileOpen(defaultConfigFilePath, read);
602                f.CopyTo(configFilePath);
603                f.Flush();
604                delete f;
605             }
606             PathCat(path, "Data");
607             // the forced settings location will only be
608             // used if the running ide's path matches
609             // the PortableApps.com directory structure
610             // and the default ini file is found in
611             // the DefaultData directory
612             settingsLocation = path;
613             portable = true;
614          }
615       }
616    }
617
618    void OnAskReloadSettings()
619    {
620       FileSize newSettingsFileSize;
621
622       if(OpenAndLock(&newSettingsFileSize))
623       {
624          //if((double)settingsFileSize - (double)newSettingsFileSize < 2048)
625             Load();
626             onLoad();
627          /*else
628          {
629             GuiApplication app = ((GuiApplication)__thisModule.application);
630             Window w;
631             for(w = app.desktop.firstChild; w && (!w.created || !w.visible); w = w.next);
632
633             CloseAndMonitor();
634
635             MessageBox { master = w, type = ok, isModal = true,
636                   creationActivation = flash,
637                   text = "Global Settings Modified Externally",
638                   contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
639                   "The new settings will not be loaded to prevent loss of your ide settings.\n"
640                   "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
641                   }.Create();
642          }*/
643       }
644    }
645
646    SettingsIOResult Load()
647    {
648       IDESettings data;
649       SettingsIOResult result;
650       useNewConfigurationFiles = true;
651       result = GlobalSettings::Load();
652       data = (IDESettings)this.data;
653       oldConfig = false;
654       if(result == fileNotFound || (settingsFilePath && isGlobalPath))
655       {
656          bool retryNewConfig = settingsFilePath && isGlobalPath;
657
658          if(retryNewConfig)
659          {
660             // Need to load the data outside of main config file first
661             data.compilerConfigs.read(this);
662             data.recentFiles.read(this);
663             data.recentProjects.read(this);
664          }
665          oldConfig = true; // WARNING: This is being used with two meanings: Old format and loaded from system-wide settings
666          useNewConfigurationFiles = false;
667          if(retryNewConfig)
668          {
669             settingsFilePath = null;
670             CloseAndMonitor();
671          }
672
673          GlobalSettings::Load();
674
675          if(result == fileNotFound && retryNewConfig)
676          {
677             oldConfig = false;
678             useNewConfigurationFiles = true;
679             result = GlobalSettings::Load();
680          }
681       }
682       data = (IDESettings)this.data;
683       if(!data)
684       {
685          this.data = IDESettings { };
686          if(dataOwner)
687             *dataOwner = this.data;
688
689          if(result == fileNotCompatibleWithDriver)
690          {
691             bool loaded;
692             OldIDESettings oldSettings { };
693             Close();
694             loaded = oldSettings.Load() == success;
695             oldSettings.Close();
696             if(loaded)
697             {
698                data = (IDESettings)this.data;
699
700                for(c : oldSettings.compilerConfigs)
701                   data.compilerConfigs.Add(c.Copy());
702
703                for(s : oldSettings.recentFiles) data.recentFiles.Add(s);
704                for(s : oldSettings.recentProjects) data.recentProjects.Add(s);
705
706                data.docDir = oldSettings.docDir;
707                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
708                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
709                data.useFreeCaret = oldSettings.useFreeCaret;
710                data.showLineNumbers = oldSettings.showLineNumbers;
711                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
712                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
713                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
714                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
715
716                Save();
717                result = success;
718             }
719             delete oldSettings;
720          }
721          if(result == fileNotFound || !data)
722          {
723             data = (IDESettings)this.data;
724             data.useFreeCaret = false; //true;
725             data.showLineNumbers = true;
726             data.caretFollowsScrolling = false; //true;
727          }
728       }
729
730       CloseAndMonitor();
731       FileGetSize(settingsFilePath, &settingsFileSize);
732       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
733          data.ManagePortablePaths(moduleLocation, true);
734       data.ForcePathSeparatorStyle(true);
735
736       if(!data.colorSchemes || !data.colorSchemes.count)
737       {
738          if(!data.colorSchemes) data.colorSchemes = { };
739
740          data.colorSchemes.Add(darkColorScheme);   incref darkColorScheme;
741          data.colorSchemes.Add(lightColorScheme);  incref lightColorScheme;
742          data.colorSchemes.Add(greenColorScheme);  incref greenColorScheme;
743          data.colorSchemes.Add(grayColorScheme);   incref grayColorScheme;
744       }
745       colorScheme = null;
746       if(data.activeColorScheme)
747       {
748          for(cs : data.colorSchemes; cs.name && !strcmp(cs.name, data.activeColorScheme))
749          {
750             colorScheme = cs;
751             break;
752          }
753       }
754       if(!colorScheme)
755       {
756          colorScheme = data.colorSchemes[0];
757          data.activeColorScheme = colorScheme.name;
758       }
759
760       // Import from previous ecereIDE settings
761       if(oldConfig)
762       {
763          // Save first so that settingsFilePath get set up
764          Save();
765
766          data.compilerConfigs.ensureDefaults();
767          data.compilerConfigs.write(this, null);
768          data.compilerConfigs.Free();
769
770          data.recentFiles.write(this);
771          data.recentFiles.Free();
772
773          data.recentProjects.write(this);
774          data.recentProjects.Free();
775       }
776       return result;
777    }
778
779    SettingsIOResult Save()
780    {
781       SettingsIOResult result;
782       IDESettings data;
783       useNewConfigurationFiles = true;
784       data = (IDESettings)this.data;
785       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
786          data.ManagePortablePaths(moduleLocation, false);
787       data.ForcePathSeparatorStyle(true);
788       if(oldConfig)
789          settingsFilePath = null;
790       result = GlobalSettings::Save();
791       if(result != success)
792          PrintLn("Error saving IDE settings");
793       else
794          oldConfig = false;
795       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
796          data.ManagePortablePaths(moduleLocation, true);
797
798       CloseAndMonitor();
799       FileGetSize(settingsFilePath, &settingsFileSize);
800
801       return result;
802    }
803 }
804
805 static Map<String, String> getCompilerConfigFilePathsByName(const char * path)
806 {
807    Map<String, String> map { };
808    FileListing fl { path, extensions = "econ" };
809    while(fl.Find())
810    {
811       if(fl.stats.attribs.isFile)
812       {
813          char name[MAX_FILENAME];
814          char * path = CopyString(fl.path);
815          MakeSlashPath(path);
816          GetLastDirectory(path, name);
817          StripExtension(name);
818          map[name] = path;
819       }
820    }
821    return map;
822 }
823
824 static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
825 {
826    Map<String, CompilerConfig> map { };
827    FileListing fl { path, extensions = "econ" };
828    while(fl.Find())
829    {
830       if(fl.stats.attribs.isFile)
831       {
832          char name[MAX_FILENAME];
833          char * path = CopyString(fl.path);
834          MakeSlashPath(path);
835          GetLastDirectory(path, name);
836          StripExtension(name);
837          {
838             CompilerConfig ccfg = CompilerConfig::read(path);
839             if(ccfg)
840                map[name] = ccfg;
841          }
842          delete path;
843       }
844    }
845    return map;
846 }
847
848 static SettingsIOResult writeConfigFile(const char * path, Class dataType, void * data)
849 {
850    SettingsIOResult result = error;
851    SafeFile sf = SafeFile::open(path, write);
852    if(sf)
853    {
854       WriteECONObject(sf.file, dataType, data, 0);
855       sf.sync();
856       delete sf;
857       result = success;
858    }
859    else
860       PrintLn($"error: could not safely open file for writing configuration: ", path);
861    return result;
862 }
863
864 static SettingsIOResult readConfigFile(const char * path, Class dataType, void ** data)
865 {
866    SettingsIOResult result = error;
867    SafeFile sf;
868    if(!FileExists(path))
869       result = fileNotFound;
870    else if((sf = SafeFile::open(path, read)))
871    {
872       JSONResult jsonResult;
873       {
874          ECONParser parser { f = sf.file };
875          sf.file.Seek(0, start);
876          jsonResult = parser.GetObject(dataType, data);
877          if(jsonResult != success)
878             delete *data;
879          delete parser;
880       }
881       if(jsonResult == success)
882          result = success;
883       else
884       {
885          result = fileNotCompatibleWithDriver;
886          PrintLn($"error: could not parse configuration file: ", path);
887       }
888       delete sf;
889    }
890    return result;
891 }
892
893 class SafeFile
894 {
895    File file;
896    FileOpenMode mode;
897    char path[MAX_LOCATION];
898    char tmp[MAX_LOCATION];
899
900    SafeFile ::open(const char * path, FileOpenMode mode)
901    {
902       SafeFile result = null;
903       if(mode == write || mode == read)
904       {
905          SafeFile sf { mode = mode };
906          int c;
907          bool locked = false;
908          FileLock lockType = mode == write ? exclusive : shared;
909
910          strcpy(sf.path, path);
911          strcpy(sf.tmp, path);
912          strcat(sf.tmp, ".tmp");
913          if(mode == write && FileExists(sf.tmp).isFile)
914             DeleteFile(sf.tmp);
915
916          if(mode == write)
917          {
918             sf.file = FileOpen(sf.tmp, readWrite);
919             if(!sf.file)
920             {
921                sf.file = FileOpen(sf.tmp, writeRead);
922                if(sf.file)
923                {
924                   delete sf.file;
925                   sf.file = FileOpen(sf.tmp, readWrite);
926                }
927             }
928          }
929          else
930             sf.file = FileOpen(path, mode);
931          if(sf.file)
932          {
933             for(c = 0; c < 10 && !(locked = sf.file.Lock(lockType, 0, 0, false)); c++) Sleep(0.01);
934             if(locked)
935             {
936                sf.file.Truncate(0);
937                sf.file.Seek(0, start);
938                result = sf;
939             }
940             else if(mode == write)
941                PrintLn($"warning: SafeFile::open: unable to obtain exclusive lock on temporary file for writing: ", sf.tmp);
942             else
943                PrintLn($"warning: SafeFile::open: unable to obtain shared lock on file for reading: ", path);
944          }
945          else if(mode == write)
946             PrintLn($"warning: SafeFile::open: unable to open temporary file for writing: ", sf.tmp);
947          else
948             PrintLn($"warning: SafeFile::open: unable to open file for reading: ", path);
949
950          if(!result)
951             delete sf;
952       }
953       else
954          PrintLn($"warning: SafeFile::open: does not yet support FileOpenMode::", mode);
955       return result;
956    }
957
958    void sync()
959    {
960       if(file && mode == write)
961       {
962          int c;
963          File f = FileOpen(path, readWrite);
964          if(!f)
965          {
966             f = FileOpen(path, writeRead);
967             if(f)
968             {
969                delete f;
970                f = FileOpen(path, readWrite);
971             }
972          }
973          if(f)
974          {
975             bool locked = true;
976             for(c = 0; c < 10 && !(locked = f.Lock(exclusive, 0,0, false)); c++) Sleep(0.01);
977
978             if(locked)
979             {
980                f.Unlock(0,0, false);
981                delete f;
982                file.Unlock(0,0, false);
983                delete file;
984
985                for(c = 0; c < 10; c++)
986                {
987                   if(MoveFileEx(tmp, path, { true, true }))
988                      break;
989                   else
990                      Sleep(0.01);
991                }
992             }
993             else
994             {
995                delete f;
996                PrintLn($"warning: SafeFile::sync: failed to lock file for ", mode);
997             }
998          }
999       }
1000    }
1001
1002
1003    ~SafeFile()
1004    {
1005       if(file)
1006       {
1007          file.Unlock(0,0, false);
1008          delete file;
1009       }
1010    }
1011 }
1012
1013 class RecentFilesData
1014 {
1015 public:
1016    RecentFiles recentFiles;
1017
1018    ~RecentFilesData()
1019    {
1020       if(recentFiles) recentFiles.Free();
1021       delete recentFiles;
1022    }
1023 }
1024
1025 class RecentWorkspacesData
1026 {
1027 public:
1028    RecentWorkspaces recentWorkspaces;
1029
1030    ~RecentWorkspacesData()
1031    {
1032       if(recentWorkspaces) recentWorkspaces.Free();
1033       delete recentWorkspaces;
1034    }
1035 }
1036
1037 class IDESettings : GlobalSettingsData
1038 {
1039 public:
1040    property CompilerConfigs compilerConfigs
1041    {
1042       set { /*if(settingsContainer.oldConfig)*/ { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
1043       get { return compilerConfigs; }
1044       isset { return false; }
1045    }
1046    property RecentFiles recentFiles
1047    {
1048       set { if(recentFiles) recentFiles.Free(); delete recentFiles; if(value) recentFiles = value; }
1049       get { return recentFiles; }
1050       isset { return false; }
1051    }
1052    property RecentWorkspaces recentProjects
1053    {
1054       set { if(recentProjects) recentProjects.Free(); delete recentProjects; if(value) recentProjects = value; }
1055       get { return recentProjects; }
1056       isset { return false; }
1057    }
1058    property const char * docDir
1059    {
1060       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
1061       get { return docDir ? docDir : ""; }
1062       isset { return docDir && docDir[0]; }
1063    }
1064    property const char * ideFileDialogLocation
1065    {
1066       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
1067       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
1068       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
1069    }
1070    property const char * ideProjectFileDialogLocation
1071    {
1072       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
1073       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
1074       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
1075    }
1076    bool useFreeCaret;
1077    bool showLineNumbers;
1078    bool caretFollowsScrolling;
1079    char * displayDriver;
1080
1081    // TODO: Classify settings
1082    //EditorSettings editor { };
1083
1084    property const char * projectDefaultTargetDir
1085    {
1086       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
1087       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
1088       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
1089    }
1090    property const char * projectDefaultIntermediateObjDir
1091    {
1092       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
1093       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
1094       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
1095    }
1096
1097    property const char * compilerConfigsDir
1098    {
1099       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
1100       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
1101       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
1102    }
1103
1104    property const char * defaultCompiler
1105    {
1106       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
1107       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
1108       isset { return defaultCompiler && defaultCompiler[0]; }
1109    }
1110
1111    property const String language
1112    {
1113       set
1114       {
1115          delete language;
1116          language = CopyString(value);
1117       }
1118       get { return language; }
1119       isset { return language != null; }
1120    }
1121
1122    property const String codeEditorFont
1123    {
1124       set
1125       {
1126          delete codeEditorFont;
1127          codeEditorFont = CopyString(value);
1128       }
1129       get { return codeEditorFont; }
1130    }
1131
1132    float codeEditorFontSize;
1133    bool showFixedPitchFontsOnly;
1134
1135    property Array<IDEColorScheme> colorSchemes
1136    {
1137       set
1138       {
1139          if(colorSchemes && colorSchemes._refCount < 2)
1140             colorSchemes.Free();
1141          delete colorSchemes;
1142          colorSchemes = value;
1143          if(value)
1144             incref colorSchemes;
1145       }
1146       get { return colorSchemes; }
1147    }
1148
1149    property const String activeColorScheme
1150    {
1151       set
1152       {
1153          delete activeColorScheme;
1154          activeColorScheme = CopyString(value);
1155       }
1156       get { return activeColorScheme; }
1157    }
1158
1159 private:
1160    CompilerConfigs compilerConfigs { };
1161    char * docDir;
1162    char * ideFileDialogLocation;
1163    char * ideProjectFileDialogLocation;
1164    char * projectDefaultTargetDir;
1165    char * projectDefaultIntermediateObjDir;
1166    char * compilerConfigsDir;
1167    char * defaultCompiler;
1168    String language;
1169    RecentFiles recentFiles { };
1170    RecentWorkspaces recentProjects { };
1171
1172    Array<IDEColorScheme> colorSchemes;
1173
1174    String codeEditorFont;
1175
1176    String activeColorScheme;
1177
1178    showFixedPitchFontsOnly = true;
1179    codeEditorFontSize = 12;
1180    codeEditorFont = CopyString("Courier New");
1181
1182    ~IDESettings()
1183    {
1184       compilerConfigs.Free();
1185       delete compilerConfigs;
1186       if(recentProjects) { recentFiles.Free(); delete recentFiles; }
1187       if(recentProjects) { recentProjects.Free(); delete recentProjects; }
1188       delete docDir;
1189
1190       delete projectDefaultTargetDir;
1191       delete projectDefaultIntermediateObjDir;
1192       delete compilerConfigsDir;
1193       delete defaultCompiler;
1194       delete language;
1195
1196       delete ideFileDialogLocation;
1197       delete ideProjectFileDialogLocation;
1198       delete displayDriver;
1199
1200       delete codeEditorFont;
1201
1202       colorSchemes.Free();
1203       delete activeColorScheme;
1204    }
1205
1206    void ForcePathSeparatorStyle(bool unixStyle)
1207    {
1208       char from, to;
1209       if(unixStyle)
1210          from = '\\', to = '/';
1211       else
1212          from = '/', to = '\\';
1213       if(compilerConfigs && compilerConfigs.count)
1214       {
1215          int i;
1216          for(config : compilerConfigs)
1217          {
1218             if(config.includeDirs && config.includeDirs.count)
1219             {
1220                for(i = 0; i < config.includeDirs.count; i++)
1221                {
1222                   if(config.includeDirs[i] && config.includeDirs[i][0])
1223                      ChangeCh(config.includeDirs[i], from, to);
1224                }
1225             }
1226             if(config.libraryDirs && config.libraryDirs.count)
1227             {
1228                for(i = 0; i < config.libraryDirs.count; i++)
1229                {
1230                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
1231                      ChangeCh(config.libraryDirs[i], from, to);
1232                }
1233             }
1234             if(config.executableDirs && config.executableDirs.count)
1235             {
1236                for(i = 0; i < config.executableDirs.count; i++)
1237                {
1238                   if(config.executableDirs[i] && config.executableDirs[i][0])
1239                      ChangeCh(config.executableDirs[i], from, to);
1240                }
1241             }
1242          }
1243       }
1244       recentFiles.changeChar(from, to);
1245       recentProjects.changeChar(from, to);
1246       if(docDir && docDir[0])
1247          ChangeCh(docDir, from, to);
1248       if(ideFileDialogLocation && ideFileDialogLocation[0])
1249          ChangeCh(ideFileDialogLocation, from, to);
1250       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
1251          ChangeCh(ideProjectFileDialogLocation, from, to);
1252
1253       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
1254          ChangeCh(projectDefaultTargetDir, from, to);
1255       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
1256          ChangeCh(projectDefaultIntermediateObjDir, from, to);
1257
1258       if(compilerConfigsDir && compilerConfigsDir[0])
1259          ChangeCh(compilerConfigsDir, from, to);
1260    }
1261
1262    void ManagePortablePaths(char * location, bool makeAbsolute)
1263    {
1264       int c;
1265       if(compilerConfigs && compilerConfigs.count)
1266       {
1267          for(config : compilerConfigs)
1268          {
1269             DirTypes t;
1270             for(t = 0; t < DirTypes::enumSize; t++)
1271             {
1272                Array<String> dirs = null;
1273                if(t == executables) dirs = config.executableDirs;
1274                else if(t == includes) dirs = config.includeDirs;
1275                else if(t == libraries) dirs = config.libraryDirs;
1276                if(dirs && dirs.count)
1277                {
1278                   for(c = 0; c < dirs.count; c++)
1279                   {
1280                      if(dirs[c] && dirs[c][0])
1281                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
1282                   }
1283                }
1284             }
1285          }
1286       }
1287       if(recentFiles && recentFiles.count)
1288       {
1289          for(c = 0; c < recentFiles.count; c++)
1290          {
1291             if(recentFiles[c] && recentFiles[c][0])
1292                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
1293          }
1294       }
1295       if(recentProjects && recentProjects.count)
1296       {
1297          for(c = 0; c < recentProjects.count; c++)
1298          {
1299             if(recentProjects[c] && recentProjects[c][0])
1300                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
1301          }
1302       }
1303       if(docDir && docDir[0])
1304          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
1305       if(ideFileDialogLocation && ideFileDialogLocation[0])
1306          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
1307       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
1308          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
1309
1310       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
1311          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
1312       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
1313          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
1314
1315       if(compilerConfigsDir && compilerConfigsDir[0])
1316          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
1317    }
1318
1319    char * UpdatePortablePath(char * path, const char * location, bool makeAbsolute)
1320    {
1321       char * output;
1322       if(makeAbsolute)
1323       {
1324          char p[MAX_LOCATION];
1325          strcpy(p, location);
1326          PathCatSlash(p, path);
1327          delete path;
1328          output = CopyString(p);
1329       }
1330       else
1331       {
1332          PathRelationship rel = eString_PathRelated(path, location, null);
1333          if(rel == subPath || rel == identical)
1334          {
1335             char p[MAX_LOCATION];
1336             MakePathRelative(path, location, p);
1337             if(!*p) strcpy(p, "./");
1338             else ChangeCh(p, '\\', '/');
1339             delete path;
1340             output = CopyString(p);
1341          }
1342          else
1343             output = path;
1344       }
1345       return output;
1346    }
1347 }
1348
1349 class RecentFiles : RecentPaths
1350 {
1351    void read(IDESettingsContainer settingsContainer)
1352    {
1353       char path[MAX_LOCATION];
1354       RecentFilesData d = null;
1355       Class _class = class(RecentFilesData);
1356       settingsContainer.getConfigFilePath(path, _class, null, null);
1357       readConfigFile(path, _class, &d);
1358       if(d && d.recentFiles && d.recentFiles.count)
1359       {
1360          Free();
1361          Copy((void *)d.recentFiles);
1362          d.recentFiles.RemoveAll();
1363          settingsContainer.recentFiles = this; // Merge IDEConfigHolder / IDESettingsContainer?
1364       }
1365       delete d;
1366       settingsContainer.recentFilesMonitor.fileName = path;
1367       settingsContainer.recentFilesMonitor.StartMonitoring();
1368       settingsContainer.onLoadRecentFiles();
1369    }
1370
1371    void write(IDESettingsContainer settingsContainer)
1372    {
1373       char path[MAX_LOCATION];
1374       RecentFilesData d { };
1375       Class _class = class(RecentFilesData);
1376       settingsContainer.getConfigFilePath(path, _class, null, null);
1377       d.recentFiles = this;
1378       writeConfigFile(path, _class, d);
1379       d.recentFiles = null;
1380       delete d;
1381    }
1382 }
1383
1384 class RecentWorkspaces : RecentPaths
1385 {
1386    void read(IDESettingsContainer settingsContainer)
1387    {
1388       char path[MAX_LOCATION];
1389       RecentWorkspacesData d = null;
1390       Class _class = class(RecentWorkspacesData);
1391       settingsContainer.getConfigFilePath(path, _class, null, null);
1392       readConfigFile(path, _class, &d);
1393       if(d && d.recentWorkspaces && d.recentWorkspaces.count)
1394       {
1395          Free();
1396          Copy((void *)d.recentWorkspaces);
1397          d.recentWorkspaces.RemoveAll();
1398          settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
1399       }
1400       delete d;
1401       settingsContainer.recentProjectsMonitor.fileName = path;
1402       settingsContainer.recentProjectsMonitor.StartMonitoring();
1403       settingsContainer.onLoadRecentProjects();
1404    }
1405
1406    void write(IDESettingsContainer settingsContainer)
1407    {
1408       char path[MAX_LOCATION];
1409       RecentWorkspacesData d { };
1410       Class _class = class(RecentWorkspacesData);
1411       settingsContainer.getConfigFilePath(path, _class, null, null);
1412       d.recentWorkspaces = this;
1413       writeConfigFile(path, _class, d);
1414       d.recentWorkspaces = null;
1415       delete d;
1416    }
1417 }
1418
1419 class RecentPaths : Array<String>
1420 {
1421    IteratorPointer Add(T value)
1422    {
1423       int c;
1424       char * filePath = (char *)value;
1425       ChangeCh(filePath, '\\', '/');
1426       for(c = 0; c < count; c++)
1427       {
1428          if(this[c] && !fstrcmp(this[c], filePath))
1429          {
1430             Delete((void *)&this[c]);
1431             c--;
1432          }
1433       }
1434       return Array::Add((T)filePath);
1435    }
1436
1437    IteratorPointer addRecent(const String value)
1438    {
1439       int c;
1440       char * filePath = CopyString((char *)value);
1441       IteratorPointer ip;
1442       ChangeCh(filePath, '\\', '/');
1443       for(c = 0; c < count; c++)
1444       {
1445          if(this[c] && !fstrcmp(this[c], filePath))
1446          {
1447             Delete((void *)&this[c]);
1448             c--;
1449          }
1450       }
1451       while(count >= MaxRecent)
1452          Delete(GetLast());
1453       ip = Insert(null, filePath);
1454       return ip;
1455    }
1456
1457    void changeChar(char from, char to)
1458    {
1459       if(this && count)
1460       {
1461          int c;
1462          for(c = 0; c < count; c++)
1463          {
1464             if(this[c] && this[c][0])
1465                ChangeCh(this[c], from, to);
1466          }
1467       }
1468    }
1469 }
1470
1471 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
1472 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
1473 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
1474 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
1475 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
1476 // TODO: i18n with Array
1477 static Array<const String> compilerTypeLongNames
1478 { [
1479    $"GNU Compiler Collection (GCC) / GNU Make",
1480    $"Tiny C Compiler / GNU Make",
1481    $"Portable C Compiler / GNU Make",
1482    $"Microsoft Visual Studio 2005 (8.0) Compiler",
1483    $"Microsoft Visual Studio 2008 (9.0) Compiler",
1484    $"Microsoft Visual Studio 2010 (10.0) Compiler"
1485 ] };
1486 const CompilerType firstCompilerType = gcc;
1487 const CompilerType lastCompilerType = vs10;
1488 public enum CompilerType
1489 {
1490    gcc, tcc, pcc, vs8, vs9, vs10;
1491
1492    property bool isVC
1493    {
1494       get { return this == vs8 || this == vs9 || this == vs10; }
1495    }
1496
1497    property const char *
1498    {
1499       get { return OnGetString(null, null, null); }
1500       set
1501       {
1502          if(value)
1503          {
1504             CompilerType c;
1505             for(c = firstCompilerType; c <= lastCompilerType; c++)
1506                if(!strcmpi(value, compilerTypeNames[c]))
1507                   return c;
1508          }
1509          return gcc;
1510       }
1511    };
1512
1513    property const char * longName { get { return OnGetString(null, (void*)1, null); } };
1514    property const char * versionString { get { return OnGetString(null, (void*)2, null); } };
1515    property const char * yearString { get { return OnGetString(null, (void*)3, null); } };
1516    property const char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
1517    property const char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
1518
1519    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1520    {
1521       if(this >= firstCompilerType && this <= lastCompilerType)
1522       {
1523          if(tempString)
1524             strcpy(tempString, compilerTypeNames[this]);
1525          if(fieldData == null)
1526             return compilerTypeNames[this];
1527          else if(fieldData == (void*)1)
1528             return compilerTypeLongNames[this];
1529          else if(fieldData == (void*)2)
1530             return compilerTypeVersionString[this];
1531          else if(fieldData == (void*)3)
1532             return compilerTypeYearString[this];
1533          else if(fieldData == (void*)4)
1534             return compilerTypeProjectFileExtension[this];
1535          else if(fieldData == (void*)5)
1536             return compilerTypeSolutionFileVersionString[this];
1537       }
1538       return null;
1539    }
1540 };
1541
1542 class CompilerConfig
1543 {
1544    class_no_expansion;
1545
1546    numJobs = 1;
1547 public:
1548    property const char * name
1549    {
1550       set { delete name; if(value) name = CopyString(value); }
1551       get { return name; }
1552    }
1553    bool readOnly;
1554    CompilerType type;
1555    Platform targetPlatform;
1556    int numJobs;
1557    property const char * makeCommand
1558    {
1559       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
1560       get { return makeCommand; }
1561       isset { return makeCommand && makeCommand[0]; }
1562    }
1563    property const char * ecpCommand
1564    {
1565       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
1566       get { return ecpCommand; }
1567       isset { return ecpCommand && ecpCommand[0]; }
1568    }
1569    property const char * eccCommand
1570    {
1571       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
1572       get { return eccCommand; }
1573       isset { return eccCommand && eccCommand[0]; }
1574    }
1575    property const char * ecsCommand
1576    {
1577       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
1578       get { return ecsCommand; }
1579       isset { return ecsCommand && ecsCommand[0]; }
1580    }
1581    property const char * earCommand
1582    {
1583       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
1584       get { return earCommand; }
1585       isset { return earCommand && earCommand[0]; }
1586    }
1587    property const char * cppCommand
1588    {
1589       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
1590       get { return cppCommand; }
1591       isset { return cppCommand && cppCommand[0]; }
1592    }
1593    property const char * ccCommand
1594    {
1595       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
1596       get { return ccCommand; }
1597       isset { return ccCommand && ccCommand[0]; }
1598    }
1599    property const char * cxxCommand
1600    {
1601       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
1602       get { return cxxCommand; }
1603       isset { return cxxCommand && cxxCommand[0]; }
1604    }
1605    property const char * arCommand
1606    {
1607       set { delete arCommand; if(value && value[0]) arCommand = CopyString(value); }
1608       get { return arCommand; }
1609       isset { return arCommand && arCommand[0]; }
1610    }
1611    property const char * ldCommand
1612    {
1613       set { delete ldCommand; if(value && value[0]) ldCommand = CopyString(value); }
1614       get { return ldCommand; }
1615       isset { return ldCommand && ldCommand[0]; }
1616    }
1617    property const char * objectFileExt
1618    {
1619       set { delete objectFileExt; if(value && value[0]) objectFileExt = CopyString(value); }
1620       get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
1621       isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
1622    }
1623    property const char * staticLibFileExt
1624    {
1625       set { delete staticLibFileExt; if(value && value[0]) staticLibFileExt = CopyString(value); }
1626       get { return staticLibFileExt; }
1627       isset { return staticLibFileExt && staticLibFileExt[0]; }
1628    }
1629    property const char * sharedLibFileExt
1630    {
1631       set { delete sharedLibFileExt; if(value && value[0]) sharedLibFileExt = CopyString(value); }
1632       get { return sharedLibFileExt; }
1633       isset { return sharedLibFileExt && sharedLibFileExt[0]; }
1634    }
1635    property const char * executableFileExt
1636    {
1637       set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
1638       get { return executableFileExt; }
1639       isset { return executableFileExt && executableFileExt[0]; }
1640    }
1641    property const char * executableLauncher
1642    {
1643       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1644       get { return executableLauncher; }
1645       isset { return executableLauncher && executableLauncher[0]; }
1646    }
1647    // TODO: implement CompilerConfig::windresCommand
1648    bool ccacheEnabled;
1649    bool distccEnabled;
1650    // deprecated
1651    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
1652
1653    property const char * distccHosts
1654    {
1655       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
1656       get { return distccHosts; }
1657       isset { return distccHosts && distccHosts[0]; }
1658    }
1659    property const char * gnuToolchainPrefix
1660    {
1661       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1662       get { return gnuToolchainPrefix; }
1663       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
1664    }
1665    property const char * sysroot
1666    {
1667       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
1668       get { return sysroot; }
1669       isset { return sysroot && sysroot[0]; }
1670    }
1671    bool resourcesDotEar;
1672    bool noStripTarget;
1673    property Array<String> includeDirs
1674    {
1675       set
1676       {
1677          includeDirs.Free();
1678          if(value)
1679          {
1680             delete includeDirs;
1681             includeDirs = value;
1682          }
1683       }
1684       get { return includeDirs; }
1685       isset { return includeDirs.count != 0; }
1686    }
1687    property Array<String> libraryDirs
1688    {
1689       set
1690       {
1691          libraryDirs.Free();
1692          if(value)
1693          {
1694             delete libraryDirs;
1695             libraryDirs = value;
1696          }
1697       }
1698       get { return libraryDirs; }
1699       isset { return libraryDirs.count != 0; }
1700    }
1701    property Array<String> executableDirs
1702    {
1703       set
1704       {
1705          executableDirs.Free();
1706          if(value)
1707          {
1708             delete executableDirs;
1709             executableDirs = value;
1710          }
1711       }
1712       get { return executableDirs; }
1713       isset { return executableDirs.count != 0; }
1714    }
1715    property Array<NamedString> environmentVars
1716    {
1717       set
1718       {
1719          environmentVars.Free();
1720          if(value)
1721          {
1722             delete environmentVars;
1723             environmentVars = value;
1724          }
1725       }
1726       get { return environmentVars; }
1727       isset { return environmentVars.count != 0; }
1728    }
1729    property Array<String> prepDirectives
1730    {
1731       set
1732       {
1733          prepDirectives.Free();
1734          if(value)
1735          {
1736             delete prepDirectives;
1737             prepDirectives = value;
1738          }
1739       }
1740       get { return prepDirectives; }
1741       isset { return prepDirectives.count != 0; }
1742    }
1743    property Array<String> excludeLibs
1744    {
1745       set
1746       {
1747          excludeLibs.Free();
1748          if(value)
1749          {
1750             delete excludeLibs;
1751             excludeLibs = value;
1752          }
1753       }
1754       get { return excludeLibs; }
1755       isset { return excludeLibs.count != 0; }
1756    }
1757    property Array<String> eCcompilerFlags
1758    {
1759       set
1760       {
1761          eCcompilerFlags.Free();
1762          if(value)
1763          {
1764             delete eCcompilerFlags;
1765             eCcompilerFlags = value;
1766          }
1767       }
1768       get { return eCcompilerFlags; }
1769       isset { return eCcompilerFlags.count != 0; }
1770    }
1771    property Array<String> compilerFlags
1772    {
1773       set
1774       {
1775          compilerFlags.Free();
1776          if(value)
1777          {
1778             delete compilerFlags;
1779             compilerFlags = value;
1780          }
1781       }
1782       get { return compilerFlags; }
1783       isset { return compilerFlags.count != 0; }
1784    }
1785    property Array<String> cxxFlags
1786    {
1787       set
1788       {
1789          cxxFlags.Free();
1790          if(value)
1791          {
1792             delete cxxFlags;
1793             cxxFlags = value;
1794          }
1795       }
1796       get { return cxxFlags; }
1797       isset { return cxxFlags.count != 0; }
1798    }
1799    property Array<String> linkerFlags
1800    {
1801       set
1802       {
1803          linkerFlags.Free();
1804          if(value)
1805          {
1806             delete linkerFlags;
1807             linkerFlags = value;
1808          }
1809       }
1810       get { return linkerFlags; }
1811       isset { return linkerFlags.count != 0; }
1812    }
1813    // json backward compatibility
1814    property const char * gccPrefix
1815    {
1816       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1817       get { return gnuToolchainPrefix; }
1818       isset { return false; }
1819    }
1820    property const char * execPrefixCommand
1821    {
1822       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1823       get { return executableLauncher; }
1824       isset { return false; }
1825    }
1826    property const char * outputFileExt
1827    {
1828       set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
1829       get { return executableFileExt; }
1830       isset { return false; }
1831    }
1832    // utility
1833    property bool hasDocumentOutput
1834    {
1835       get
1836       {
1837          bool result = executableFileExt && executableFileExt[0] &&
1838                (!strcmpi(executableFileExt, "htm") || !strcmpi(executableFileExt, "html"));
1839          return result;
1840       }
1841       isset { return false; }
1842    }
1843 private:
1844    Array<String> includeDirs { };
1845    Array<String> libraryDirs { };
1846    Array<String> executableDirs { };
1847    // TODO: Can JSON parse and serialize maps?
1848    //EnvironmentVariables { };
1849    Array<NamedString> environmentVars { };
1850    Array<String> prepDirectives { };
1851    Array<String> excludeLibs { };
1852    Array<String> eCcompilerFlags { };
1853    Array<String> compilerFlags { };
1854    Array<String> cxxFlags { };
1855    Array<String> linkerFlags { };
1856    char * name;
1857    char * makeCommand;
1858    char * ecpCommand;
1859    char * eccCommand;
1860    char * ecsCommand;
1861    char * earCommand;
1862    char * cppCommand;
1863    char * ccCommand;
1864    char * cxxCommand;
1865    char * ldCommand;
1866    char * arCommand;
1867    char * objectFileExt;
1868    char * staticLibFileExt;
1869    char * sharedLibFileExt;
1870    char * executableFileExt;
1871    char * executableLauncher;
1872    char * distccHosts;
1873    char * gnuToolchainPrefix;
1874    char * sysroot;
1875    /*union
1876    {
1877       struct { Array<String> includes, libraries, executables; };
1878       Array<String> dirs[DirTypes];
1879    }*/
1880
1881    ~CompilerConfig()
1882    {
1883       delete name;
1884       delete ecpCommand;
1885       delete eccCommand;
1886       delete ecsCommand;
1887       delete earCommand;
1888       delete cppCommand;
1889       delete ccCommand;
1890       delete cxxCommand;
1891       delete ldCommand;
1892       delete arCommand;
1893       delete objectFileExt;
1894       delete staticLibFileExt;
1895       delete sharedLibFileExt;
1896       delete executableFileExt;
1897       delete makeCommand;
1898       delete executableLauncher;
1899       delete distccHosts;
1900       delete gnuToolchainPrefix;
1901       delete sysroot;
1902       if(environmentVars) environmentVars.Free();
1903       if(includeDirs) { includeDirs.Free(); }
1904       if(libraryDirs) { libraryDirs.Free(); }
1905       if(executableDirs) { executableDirs.Free(); }
1906       if(prepDirectives) { prepDirectives.Free(); }
1907       if(excludeLibs) { excludeLibs.Free(); }
1908       if(compilerFlags) { compilerFlags.Free(); }
1909       if(cxxFlags) { cxxFlags.Free(); }
1910       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
1911       if(linkerFlags) { linkerFlags.Free(); }
1912    }
1913
1914    int OnCompare(CompilerConfig b)
1915    {
1916       int result;
1917       if(
1918          !(result = type.OnCompare(b.type)) &&
1919          !(result = targetPlatform.OnCompare(b.targetPlatform)) &&
1920          !(result = numJobs.OnCompare(b.numJobs)) &&
1921          !(result = ccacheEnabled.OnCompare(b.ccacheEnabled)) &&
1922          !(result = distccEnabled.OnCompare(b.distccEnabled)) &&
1923          !(result = resourcesDotEar.OnCompare(b.resourcesDotEar)) &&
1924          !(result = noStripTarget.OnCompare(b.noStripTarget))
1925          );
1926
1927       if(!result &&
1928          !(result = name.OnCompare(b.name)) &&
1929          !(result = ecpCommand.OnCompare(b.ecpCommand)) &&
1930          !(result = eccCommand.OnCompare(b.eccCommand)) &&
1931          !(result = ecsCommand.OnCompare(b.ecsCommand)) &&
1932          !(result = earCommand.OnCompare(b.earCommand)) &&
1933          !(result = cppCommand.OnCompare(b.cppCommand)) &&
1934          !(result = ccCommand.OnCompare(b.ccCommand)) &&
1935          !(result = cxxCommand.OnCompare(b.cxxCommand)) &&
1936          !(result = ldCommand.OnCompare(b.ldCommand)) &&
1937          !(result = arCommand.OnCompare(b.arCommand)) &&
1938          !(result = objectFileExt.OnCompare(b.objectFileExt)) &&
1939          !(result = outputFileExt.OnCompare(b.outputFileExt)) &&
1940          !(result = makeCommand.OnCompare(b.makeCommand)) &&
1941          !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
1942          !(result = distccHosts.OnCompare(b.distccHosts)) &&
1943          !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
1944          !(result = sysroot.OnCompare(b.sysroot)));
1945
1946       if(!result &&
1947          !(result = includeDirs.OnCompare(b.includeDirs)) &&
1948          !(result = libraryDirs.OnCompare(b.libraryDirs)) &&
1949          !(result = executableDirs.OnCompare(b.executableDirs)) &&
1950          !(result = environmentVars.OnCompare(b.environmentVars)) &&
1951          !(result = prepDirectives.OnCompare(b.prepDirectives)) &&
1952          !(result = excludeLibs.OnCompare(b.excludeLibs)) &&
1953          !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
1954          !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
1955          !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
1956          !(result = linkerFlags.OnCompare(b.linkerFlags)));
1957       return result;
1958    }
1959
1960 public:
1961    CompilerConfig Copy()
1962    {
1963       CompilerConfig copy
1964       {
1965          name,
1966          readOnly,
1967          type,
1968          targetPlatform,
1969          numJobs,
1970          makeCommand,
1971          ecpCommand,
1972          eccCommand,
1973          ecsCommand,
1974          earCommand,
1975          cppCommand,
1976          ccCommand,
1977          cxxCommand,
1978          arCommand,
1979          ldCommand,
1980          objectFileExt,
1981          staticLibFileExt,
1982          sharedLibFileExt,
1983          executableFileExt,
1984          executableLauncher,
1985          ccacheEnabled,
1986          distccEnabled,
1987          false,
1988          distccHosts,
1989          gnuToolchainPrefix,
1990          sysroot,
1991          resourcesDotEar,
1992          noStripTarget
1993       };
1994       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1995       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1996       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1997       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1998       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1999       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
2000       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
2001       for(s : cxxFlags) copy.cxxFlags.Add(CopyString(s));
2002       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
2003       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
2004
2005       incref copy;
2006       return copy;
2007    }
2008
2009    CompilerConfig ::read(const char * path)
2010    {
2011       CompilerConfig d = null;
2012       readConfigFile(path, class(CompilerConfig), &d);
2013       return d;
2014    }
2015
2016    void write(IDESettingsContainer settingsContainer)
2017    {
2018       char dir[MAX_LOCATION];
2019       char path[MAX_LOCATION];
2020       const char * settingsFilePath = settingsContainer.settingsFilePath;
2021       settingsContainer.getConfigFilePath(path, _class, dir, name);
2022       if(FileExists(settingsFilePath) && !FileExists(dir))
2023       {
2024          MakeDir(dir);
2025          if(!FileExists(dir))
2026             PrintLn($"Error creating compiler configs directory at ", dir, " location.");
2027       }
2028       writeConfigFile(path, _class, this);
2029    }
2030 }
2031
2032 class CompilerConfigs : List<CompilerConfig>
2033 {
2034    CompilerConfig GetCompilerConfig(const String compilerName)
2035    {
2036       const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
2037       CompilerConfig compilerConfig = null;
2038       for(compiler : this)
2039       {
2040          if(!strcmp(compiler.name, name))
2041          {
2042             compilerConfig = compiler;
2043             break;
2044          }
2045       }
2046       if(!compilerConfig && count)
2047          compilerConfig = this[0];
2048       if(compilerConfig)
2049       {
2050          incref compilerConfig;
2051          if(compilerConfig._refCount == 1)
2052             incref compilerConfig;
2053       }
2054       return compilerConfig;
2055    }
2056
2057    void ensureDefaults()
2058    {
2059       // Ensure we have a default compiler
2060       CompilerConfig defaultCompiler = GetCompilerConfig(defaultCompilerName);
2061       if(!defaultCompiler)
2062       {
2063          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
2064          Insert(null, defaultCompiler);
2065          defaultCompiler = null;
2066       }
2067       delete defaultCompiler;
2068
2069       for(ccfg : this)
2070       {
2071          if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
2072             ccfg.ecpCommand = ecpDefaultCommand;
2073          if(!ccfg.eccCommand || !ccfg.eccCommand[0])
2074             ccfg.eccCommand = eccDefaultCommand;
2075          if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
2076             ccfg.ecsCommand = ecsDefaultCommand;
2077          if(!ccfg.earCommand || !ccfg.earCommand[0])
2078             ccfg.earCommand = earDefaultCommand;
2079          if(!ccfg.cppCommand || !ccfg.cppCommand[0])
2080             ccfg.cppCommand = cppDefaultCommand;
2081          if(!ccfg.ccCommand || !ccfg.ccCommand[0])
2082             ccfg.ccCommand = ccDefaultCommand;
2083          if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
2084             ccfg.cxxCommand = cxxDefaultCommand;
2085          /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
2086             ccfg.ldCommand = ldDefaultCommand;*/
2087          if(!ccfg.arCommand || !ccfg.arCommand[0])
2088             ccfg.arCommand = arDefaultCommand;
2089          if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
2090             ccfg.objectFileExt = objectDefaultFileExt;
2091          /*if(!ccfg.staticLibFileExt || !ccfg.staticLibFileExt[0])
2092             ccfg.staticLibFileExt = staticLibDefaultFileExt;*/
2093          /*if(!ccfg.sharedLibFileExt || !ccfg.sharedLibFileExt[0])
2094             ccfg.sharedLibFileExt = sharedLibDefaultFileExt;*/
2095          /*if(!ccfg.executableFileExt || !ccfg.executableFileExt[0])
2096             ccfg.executableFileExt = outputDefaultFileExt;*/
2097          if(!ccfg._refCount) incref ccfg;
2098       }
2099    }
2100
2101    AVLTree<String> getWriteRequiredList(CompilerConfigs oldConfigs)
2102    {
2103       AVLTree<String> list { };
2104       for(ccfg : this)
2105       {
2106          bool found = false;
2107          for(occfg : oldConfigs; !strcmp(ccfg.name, occfg.name))
2108          {
2109             found = true;
2110             if(ccfg.OnCompare(occfg))
2111                list.Add(CopyString(ccfg.name));
2112             break;
2113          }
2114          if(!found)
2115             list.Add(CopyString(ccfg.name));
2116       }
2117       return list;
2118    }
2119
2120    bool read(IDESettingsContainer settingsContainer)
2121    {
2122       if(settingsContainer.settingsFilePath)
2123       {
2124          char dir[MAX_LOCATION];
2125          char path[MAX_LOCATION];
2126          Class _class = class(CompilerConfig);
2127          settingsContainer.getConfigFilePath(path, _class, dir, null);
2128          if(dir[0])
2129          {
2130             AVLTree<const String> addedConfigs { };
2131             Map<String, CompilerConfig> compilerConfigsByName = getCompilerConfigsByName(dir);
2132             MapIterator<const String, CompilerConfig> it { map = compilerConfigsByName };
2133             Free();
2134             settingsContainer.compilerConfigs = this; // Merge IDEConfigHolder / IDESettingsContainer?
2135             if(it.Index("Default", false))
2136             {
2137                CompilerConfig ccfg = it.data;
2138                Add(ccfg.Copy());
2139                addedConfigs.Add(ccfg.name);
2140             }
2141             for(ccfg : compilerConfigsByName)
2142             {
2143                if(!addedConfigs.Find(ccfg.name))
2144                {
2145                   Add(ccfg.Copy());
2146                   addedConfigs.Add(ccfg.name);
2147                }
2148             }
2149             delete addedConfigs;
2150             ensureDefaults();
2151             compilerConfigsByName.Free();
2152             delete compilerConfigsByName;
2153             settingsContainer.onLoadCompilerConfigs();
2154             return true;
2155          }
2156       }
2157       return false;
2158    }
2159
2160    void write(IDESettingsContainer settingsContainer, AVLTree<String> cfgsToWrite)
2161    {
2162       char dir[MAX_LOCATION];
2163       char path[MAX_LOCATION];
2164       Map<String, String> paths;
2165       settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
2166       paths = getCompilerConfigFilePathsByName(dir);
2167       {
2168          MapIterator<String, String> it { map = paths };
2169          for(c : this)
2170          {
2171             CompilerConfig ccfg = c;
2172             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
2173                ccfg.write(settingsContainer);
2174             if(it.Index(ccfg.name, false))
2175             {
2176                delete it.data;
2177                it.Remove();
2178             }
2179          }
2180       }
2181       for(p : paths)
2182       {
2183          const char * path = p;
2184          DeleteFile(path);
2185       }
2186       paths.Free();
2187       delete paths;
2188    }
2189 }
2190
2191 #if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
2192 struct LanguageOption
2193 {
2194    const String name;
2195    const String bitmap;
2196    const String code;
2197    BitmapResource res;
2198
2199    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
2200    {
2201       return name;
2202    }
2203
2204    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
2205    {
2206       Bitmap icon = res ? res.bitmap : null;
2207       int w = 8 + 16;
2208       if(icon)
2209          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
2210       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
2211    }
2212 };
2213
2214 Array<LanguageOption> languages
2215 { [
2216    { "English",            ":countryCode/gb.png", "" },
2217    { "汉语",                ":countryCode/cn.png", "zh_CN" },
2218    { "Español",            ":countryCode/es.png", "es" },
2219    { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
2220    { "Русский (43%)",      ":countryCode/ru.png", "ru" },
2221    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
2222    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
2223    { "मराठी (10%)",          ":countryCode/in.png", "mr" },
2224    { "Hebrew (8%)",        ":countryCode/il.png", "he" },
2225    { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
2226 ] };
2227
2228 const String GetLanguageString()
2229 {
2230    char * dot, * colon;
2231    static char lang[256];
2232    const String language = getenv("ECERE_LANGUAGE");
2233    if(!language) language = getenv("LANGUAGE");
2234    if(!language) language = getenv("LC_ALL");
2235    if(!language) language = getenv("LC_MESSAGES");
2236    if(!language) language = getenv("LANG");
2237    if(!language) language = "";
2238    if(language && (colon = strchr(language, ':')))
2239    {
2240       if(lang != language)
2241          strncpy(lang, language, sizeof(lang));
2242       lang[sizeof(lang)-1] = 0;
2243       lang[colon - language] = 0;
2244       language = lang;
2245    }
2246    if(language && (dot = strchr(language, '.')))
2247    {
2248       if(lang != language)
2249          strncpy(lang, language, sizeof(lang));
2250       lang[sizeof(lang)-1] = 0;
2251       lang[dot - language] = 0;
2252       language = lang;
2253    }
2254    return language;
2255 }
2256
2257 void setEcereLanguageInWinRegEnvironment(const char * languageCode)
2258 {
2259 #ifdef __WIN32__
2260    HKEY key = null;
2261    uint16 wLanguage[256];
2262    DWORD status;
2263    wLanguage[0] = 0;
2264
2265    RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
2266    if(key)
2267    {
2268       UTF8toUTF16Buffer(languageCode, wLanguage, sizeof(wLanguage) / sizeof(uint16));
2269       RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
2270       RegCloseKey(key);
2271    }
2272 #endif
2273 }
2274
2275 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
2276 {
2277    bool restart = true;
2278    String command = null;
2279    int arg0Len = (int)strlen(app.argv[0]);
2280    int len = arg0Len;
2281    int j;
2282    char ch;
2283
2284    if(ide)
2285    {
2286       Window w;
2287
2288       if(projectView)
2289       {
2290          Window w;
2291          for(w = ide.firstChild; w; w = w.next)
2292          {
2293             if(w.isActiveClient && w.isDocument)
2294             {
2295                if(!w.CloseConfirmation(true))
2296                {
2297                   restart = false;
2298                   break;
2299                }
2300             }
2301          }
2302          if(restart)
2303          {
2304             if(!projectView.CloseConfirmation(true))
2305                restart = false;
2306             if(projectView.fileName)
2307             {
2308                const char * name = projectView.fileName;
2309                if(name)
2310                {
2311                   for(j = 0; (ch = name[j]); j++)
2312                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2313                }
2314             }
2315
2316             command = new char[len + 1];
2317
2318             strcpy(command, app.argv[0]);
2319             len = arg0Len;
2320             if(projectView.fileName)
2321             {
2322                strcat(command, " ");
2323                len++;
2324                ReplaceSpaces(command + len, projectView.fileName);
2325             }
2326          }
2327          if(restart)
2328          {
2329             for(w = ide.firstChild; w; w = w.next)
2330                if(w.isActiveClient && w.isDocument)
2331                   w.modifiedDocument = false;
2332             projectView.modifiedDocument = false;
2333          }
2334       }
2335       else
2336       {
2337          for(w = ide.firstChild; w; w = w.next)
2338          {
2339             if(w.isActiveClient && w.isDocument)
2340             {
2341                if(!w.CloseConfirmation(true))
2342                {
2343                   restart = false;
2344                   break;
2345                }
2346                if(w.fileName)
2347                {
2348                   const char * name = w.fileName;
2349                   len++;
2350                   for(j = 0; (ch = name[j]); j++)
2351                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2352                }
2353             }
2354          }
2355
2356          if(restart)
2357          {
2358             command = new char[len + 1];
2359             strcpy(command, app.argv[0]);
2360             len = arg0Len;
2361
2362             for(w = ide.firstChild; w; w = w.next)
2363             {
2364                if(w.isActiveClient && w.isDocument)
2365                {
2366                   const char * name = w.fileName;
2367                   if(name)
2368                   {
2369                      strcat(command, " ");
2370                      len++;
2371                      ReplaceSpaces(command + len, name);
2372                      len = (int)strlen(command);
2373                   }
2374                }
2375             }
2376          }
2377          if(restart)
2378          {
2379             for(w = ide.firstChild; w; w = w.next)
2380                if(w.isActiveClient && w.isDocument)
2381                   w.modifiedDocument = false;
2382          }
2383       }
2384       if(restart)
2385       {
2386          settings.language = code;
2387          settingsContainer.Save();
2388
2389          setEcereLanguageInWinRegEnvironment(code);
2390
2391          if(eClass_IsDerived(app._class, class(GuiApplication)))
2392          {
2393             GuiApplication guiApp = (GuiApplication)app;
2394             guiApp.desktop.Destroy(0);
2395          }
2396       }
2397    }
2398    else
2399    {
2400       int i;
2401       for(i = 1; i < app.argc; i++)
2402       {
2403          const char * arg = app.argv[i];
2404          len++;
2405          for(j = 0; (ch = arg[j]); j++)
2406             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2407       }
2408
2409       command = new char[len + 1];
2410       strcpy(command, app.argv[0]);
2411       len = arg0Len;
2412       for(i = 1; i < app.argc; i++)
2413       {
2414          strcat(command, " ");
2415          len++;
2416          ReplaceSpaces(command + len, app.argv[i]);
2417          len = (int)strlen(command);
2418       }
2419    }
2420
2421    if(restart)
2422    {
2423       SetEnvironment("ECERE_LANGUAGE", code);
2424       if(wait)
2425          ExecuteWait(command);
2426       else
2427          Execute(command);
2428    }
2429    delete command;
2430    return restart;
2431 }
2432 #endif