buildsystem, epj2make, ide: support Emscripten compiler.
[sdk] / ide / src / project / Workspace.ec
1 import "ide"
2
3 /*static void ParseListValue(List<String> list, char * equal)
4 {
5    char * start, * comma;
6    char * string;
7    string = CopyString(equal);
8    start = string;
9    while(start)
10    {
11       comma = strstr(start, ",");
12       if(comma)
13          comma[0] = '\0';
14       list.Add(CopyString(start));
15       if(comma)
16          comma++;
17       if(comma)
18          comma++;
19       start = comma;
20    }
21    delete string;
22 }*/
23
24 enum OpenedFileState { unknown, opened, closed };
25 enum ValgrindLeakCheck
26 {
27    no, summary, yes, full;
28
29    property const char *
30    {
31       get { return OnGetString(null, null, null); }
32    }
33
34    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
35    {
36       if(this >= no && this <= full)
37       {
38          if(tempString)
39             strcpy(tempString, valgrindLeakCheckNames[this]);
40          return valgrindLeakCheckNames[this];
41       }
42       if(tempString && tempString[0])
43          tempString[0] = '\0';
44       return null;
45    }
46 };
47 static const char * valgrindLeakCheckNames[ValgrindLeakCheck] = { "no", "summary", "yes", "full" };
48
49 class OpenedFileInfo
50 {
51    class_no_expansion;
52 //public:
53 //   class_fixed
54    char * path;
55    OpenedFileState state;
56    int lineNumber, position;
57    Point scroll;
58    bool holdTracking;
59
60    property bool trackingAllowed
61    {
62       get { return !holdTracking && ide && ide.workspace && !ide.workspace.holdTracking; }
63    }
64
65    void CaretMove(int line, int charPos)
66    {
67       if(trackingAllowed && (line != lineNumber || position != charPos))
68       {
69          lineNumber = line;
70          position = charPos;
71          ide.workspace.modified = true;
72       }
73    }
74
75    void ScrollChange(Point scroll)
76    {
77       if(trackingAllowed)
78       {
79          this.scroll.x = scroll.x;
80          this.scroll.y = scroll.y;
81          ide.workspace.modified = true;
82       }
83    }
84
85    void Activate()
86    {
87       if(trackingAllowed)
88       {
89          List<OpenedFileInfo> files = ide.workspace.openedFiles;
90          Iterator<OpenedFileInfo> it { files };
91          if(it.Find(this) && it.pointer != files.GetLast())
92          {
93             files.Move(it.pointer, files.GetPrev(files.GetLast()));
94             ide.workspace.modified = true;
95          }
96       }
97    }
98    ~OpenedFileInfo()
99    {
100       delete path;
101    }
102 };
103
104 class Workspace
105 {
106 public:
107    char * name;
108    char * workspaceFile;
109    char * workspaceDir;
110    char * commandLineArgs;
111    property const char * commandLineArgs { set { delete commandLineArgs; if(value) commandLineArgs = CopyString(value); } }
112    char * debugDir;
113    property const char * debugDir { set { delete debugDir; if(value) debugDir = CopyString(value); } }
114
115    int bpCount;
116
117    property const char * compiler
118    {
119       set { delete compiler; if(value && value[0]) compiler = CopyString(value); }
120       get { return compiler && compiler[0] ? compiler : null; }
121    }
122
123    List<String> sourceDirs { };
124    Array<NamedString> environmentVars { };
125    List<Breakpoint> breakpoints { };
126    List<Watch> watches { };
127    List<OpenedFileInfo> openedFiles { };
128    List<Project> projects { };
129
130    //Project project;
131
132    bool modified;
133    bool holdTracking;
134
135    Timer timer
136    {
137       userData = this, delay = 1.0;
138       bool DelayExpired()
139       {
140          static bool skip = true;
141          if(skip)
142             skip = false;
143          else if(modified)
144             Save();
145
146          if(ide.debugStart)
147          {
148             ide.MenuDebugStart(ide.debugStartResumeItem, 0);
149             ide.debugStart = false;
150          }
151          return true;
152       }
153    };
154
155    property const char * workspaceFile
156    {
157       set
158       {
159          char dir[MAX_DIRECTORY];
160          if(workspaceFile) delete workspaceFile;
161          if(workspaceDir) delete workspaceDir;
162          workspaceFile = CopyString(value);
163          StripLastDirectory(workspaceFile, dir);
164          workspaceDir = CopyUnixPath(dir);
165       }
166       get { return workspaceFile; }
167    }
168
169    property const char * projectDir
170    {
171       get
172       {
173          if(projects.first)
174          {
175             Project prj = projects.firstIterator.data;
176             return prj.topNode ? prj.topNode.path : null;
177          }
178          else
179             return workspaceDir;
180       }
181    }
182
183    /*property Project project
184    {
185       set
186       {
187          if(project)
188          {
189          }
190          project = value;
191          if(project)
192          {
193             projectDir = CopyString(project.topNode.path);
194
195             if(!project.config && activeConfig && activeConfig[0])
196             {
197                ProjectConfig cfg;
198                for(cfg = project.configurations.first; cfg; cfg = cfg.next)
199                   if(!strcmp(cfg.name, activeConfig))
200                      break;
201                project.config = cfg;
202             }
203             if(!project.config)
204                project.config = project.configurations.first;
205          }
206       }
207       get { return project; }
208    }*/
209
210 private:
211    String compiler;
212    int bitDepth;
213    // TODO: save these new settings when json format is ready
214    bool useValgrind;
215    ValgrindLeakCheck vgLeakCheck;
216    bool vgTrackOrigins;
217    int vgRedzoneSize;
218
219    vgRedzoneSize = -1;
220    vgLeakCheck = summary;
221
222 public:
223    void Save()
224    {
225       bool bkpts = false;
226       File file;
227
228       file = FileOpen(workspaceFile, write);
229       if(file)
230       {
231          /*
232          for(bp : breakpoints)
233          {
234             if(bp.type == user)
235             {
236                if(bp.enabled)
237                   file.Printf("Breakpoint=1,%d,%s,%s\n", bp.line, bp.absoluteFilePath, bp.relativeFilePath);
238                else
239                   file.Printf("Breakpoint=0,%d,%s,%s\n", bp.line, bp.absoluteFilePath, bp.relativeFilePath);
240             }
241          }
242
243          for(wh : watches)
244             file.Printf("Watch=%s\n", wh.expression);
245
246          for(dir : sourceDirs)
247             file.Printf("SourceDir=%s\n", dir);
248
249          if(debugDir && debugDir[0])
250             file.Printf("DebugDir=%s\n", debugDir);
251
252          if(commandLineArgs && commandLineArgs[0])
253             file.Printf("CommandLineArgs=%s\n", commandLineArgs);
254          */
255
256          /*
257          char indentation[128*3];
258          char path[MAX_LOCATION];
259          */
260          file.Printf("\nECERE Workspace File\n");
261          file.Printf("\nVersion 0.02\n");
262          file.Printf("\nWorkspace\n");
263          file.Printf("\n   Active Compiler = %s\n", compiler ? compiler : defaultCompilerName);
264          file.Printf("\n   Active Bit Depth = %d\n", bitDepth);
265
266          if(projects.first)
267          {
268             file.Printf("\n   Projects\n\n");
269             for(prj : projects)
270             {
271                char location[MAX_LOCATION];
272                MakePathRelative(prj.topNode.path, workspaceDir, location);
273                MakeSlashPath(location);
274                PathCatSlash(location, prj.topNode.name);
275                //strcat(location, ".epj");
276
277                file.Printf("    %s %s\n", "-", location);
278
279                if(prj.config)
280                   file.Printf("         Active Configuration = %s\n", prj.config.name);
281                for(cfg : prj.configurations)
282                {
283                   if(cfg.compilingModified)
284                      file.Printf("         Modified Compiler Config = %s\n", cfg.name);
285                   else if(cfg.linkingModified)
286                      file.Printf("         Modified Linker Config = %s\n", cfg.name);
287                }
288             }
289          }
290
291          file.Printf("\n   Execution Data\n");
292          if(commandLineArgs && commandLineArgs[0])
293          {
294             file.Printf("\n      Command Line Arguments = ");
295             file.Puts(commandLineArgs);
296             file.Printf("\n");
297          }
298
299          if(environmentVars.count)
300          {
301             file.Printf("\n      Environment Variables\n\n");
302             for(v : environmentVars)
303             {
304                file.Printf("       ~ ");
305                file.Puts(v.name);
306                file.Printf(" = ");
307                file.Puts(v.string);
308                file.Printf("\n");
309             }
310          }
311
312          file.Printf("\n   Debugger Data\n");
313          // This really belonged in Execution Data...
314          if(debugDir && debugDir[0])
315             file.Printf("\n      Debug Working Directory = %s\n", debugDir);
316          if(sourceDirs.count)
317          {
318             file.Printf("\n      Source Directories\n");
319             for(dir : sourceDirs)
320                file.Printf("       = %s\n", dir);
321          }
322
323          for(bp : breakpoints)
324          {
325             if(bp.type == user)
326             {
327                if(!bkpts)
328                {
329                   bkpts = true;
330                   file.Printf("\n   Breakpoints\n\n");
331                }
332                bp.Save(file);
333             }
334          }
335
336          if(watches.count)
337          {
338             file.Printf("\n   Watches\n\n");
339             for(wh : watches)
340                wh.Save(file);
341          }
342
343          if(openedFiles.count)
344          {
345             file.Printf("\n   Opened Files\n\n");
346             for(ofi : openedFiles)
347             {
348                char * location;
349                char chr[2];
350                char relativePath[MAX_LOCATION];
351                if(IsPathInsideOf(ofi.path, workspaceDir))
352                {
353                   MakePathRelative(ofi.path, workspaceDir, relativePath);
354                   MakeSlashPath(relativePath);
355                   location = relativePath;
356                }
357                else
358                   location = ofi.path;
359                strcpy(chr, "=");
360
361                file.Printf("    %s %s:%d:%d:%d:%d:%s\n", chr, ofi.state == closed ? "C" : "O", ofi.lineNumber, ofi.position, ofi.scroll.x, ofi.scroll.y, location);
362             }
363          }
364
365          modified = false;
366          delete file;
367       }
368    }
369
370    char * CopyAbsolutePathFromRelative(const char * relative)
371    {
372       char name[MAX_LOCATION];
373       char absolute[MAX_LOCATION];
374       Project prj = null;
375       ProjectNode node = null;
376
377       GetLastDirectory(relative, name);
378       for(p : projects)
379       {
380          if((node = p.topNode.Find(name, false)))
381          {
382             prj = p;
383             break;
384          }
385       }
386       if(prj)
387       {
388          node.GetFullFilePath(absolute);
389          return CopyString(absolute);
390       }
391
392       prj = null;
393       for(p : projects)
394       {
395          strcpy(absolute, p.topNode.path);
396          PathCatSlash(absolute, relative);
397          if(FileExists(absolute))
398          {
399             prj = p;
400             break;
401          }
402       }
403       if(prj)
404          return CopyString(absolute);
405
406       strcpy(absolute, workspaceDir); //projectDir // CHECK?
407       PathCatSlash(absolute, relative);
408       if(FileExists(absolute))
409          return CopyString(absolute);
410
411       {
412          for(dir : sourceDirs)
413          {
414             strcpy(absolute, dir);
415             PathCatSlash(absolute, relative);
416             if(FileExists(absolute))
417                return CopyString(absolute);
418          }
419       }
420
421       return null;
422    }
423
424    char * CopyUnixPathWorkspaceRelativeOrAbsolute(const char * path)
425    {
426       if(IsPathInsideOf(path, workspaceDir))
427       {
428          char relativePath[MAX_LOCATION];
429          MakePathRelative(path, workspaceDir, relativePath);
430          return CopyUnixPath(relativePath);
431       }
432       else
433          return CopyUnixPath(path);
434    }
435
436    char * MakeRelativePath(char * buffer, const char * path)
437    {
438       char * result = null;
439       if(buffer && path)
440       {
441          MakePathRelative(path, workspaceDir, buffer);
442          MakeSlashPath(buffer);
443          result = buffer;
444       }
445       return result;
446    }
447
448    char * GetRelativePathForProject(char * buffer, Project project)
449    {
450       char * result = null;
451       if(buffer && project && project.topNode.path)
452       {
453          MakePathRelative(project.topNode.path, workspaceDir, buffer);
454          MakeSlashPath(buffer);
455          PathCatSlash(buffer, project.topNode.name);
456          result = buffer;
457       }
458       return result;
459    }
460
461    Array<ProjectNode> GetAllProjectNodes(const char *fullPath, bool skipExcluded)
462    {
463       Array<ProjectNode> nodes = null;
464       for(project : projects)
465       {
466          ProjectNode node;
467          if((node = project.topNode.FindByFullPath(fullPath, false)))
468          {
469             if(!skipExcluded || !node.GetIsExcluded(project.config))
470             {
471                if(!nodes) nodes = { };
472                nodes.Add(node);
473             }
474          }
475       }
476       return nodes;
477    }
478
479    Project GetFileOwner(const char * absolutePath, const char * objectFileExt)
480    {
481       Project owner = null;
482       for(prj : projects)
483       {
484          if(prj.topNode.FindByFullPath(absolutePath, false))
485          {
486             owner = prj;
487             break;
488          }
489       }
490       if(!owner)
491          GetObjectFileNode(absolutePath, &owner, null, objectFileExt);
492       return owner;
493    }
494
495    void GetRelativePath(const char * absolutePath, char * relativePath, Project * owner, const char * objectFileExt)
496    {
497       Project prj = GetFileOwner(absolutePath, objectFileExt);
498       if(owner)
499          *owner = prj;
500       if(!prj)
501          prj = projects.firstIterator.data;
502       if(prj)
503       {
504          MakePathRelative(absolutePath, prj.topNode.path, relativePath);
505          MakeSlashPath(relativePath);
506       }
507       else
508          relativePath[0] = '\0';
509    }
510
511    ProjectNode GetObjectFileNode(const char * filePath, Project * project, char * fullPath, const char * objectFileExt)
512    {
513       ProjectNode node = null;
514       char ext[MAX_EXTENSION];
515       GetExtension(filePath, ext);
516       if(ext[0])
517       {
518          IntermediateFileType type = IntermediateFileType::FromExtension(ext);
519          if(type)
520          {
521             char fileName[MAX_FILENAME];
522             GetLastDirectory(filePath, fileName);
523             if(fileName[0])
524             {
525                DotMain dotMain = DotMain::FromFileName(fileName);
526                for(prj : ide.workspace.projects)
527                {
528                   if((node = prj.FindNodeByObjectFileName(fileName, type, dotMain, null, objectFileExt)))
529                   {
530                      if(project)
531                         *project = prj;
532                      if(fullPath)
533                      {
534                         const char * cfgName = prj.config ? prj.config.name : "";
535                         char name[MAX_FILENAME];
536                         CompilerConfig compiler = ideSettings.GetCompilerConfig(prj.lastBuildCompilerName);
537                         DirExpression objDir = prj.GetObjDir(compiler, prj.config, bitDepth);
538                         strcpy(fullPath, prj.topNode.path);
539                         PathCatSlash(fullPath, objDir.dir);
540                         node.GetObjectFileName(name, prj.configsNameCollisions[cfgName], type, dotMain, objectFileExt);
541                         PathCatSlash(fullPath, name);
542                         delete objDir;
543                         delete compiler;
544                      }
545                      break;
546                   }
547                }
548             }
549          }
550       }
551       return node;
552    }
553
554    OpenedFileInfo UpdateOpenedFileInfo(const char * fileName, OpenedFileState state)
555    {
556       char filePath[MAX_LOCATION];
557       OpenedFileInfo ofi = null;
558       GetSlashPathBuffer(filePath, fileName);
559       for(item : openedFiles)
560       {
561          if(!fstrcmp(item.path, filePath))
562          {
563             ofi = item;
564             break;
565          }
566       }
567       if(state)
568       {
569          if(!ofi)
570          {
571             ofi = OpenedFileInfo { path = CopyString(filePath) };
572             openedFiles.Add(ofi);
573          }
574          ofi.state = state;
575          if(!holdTracking)
576             modified = true;
577       }
578       else if(ofi)
579       {
580          Iterator<OpenedFileInfo> it { openedFiles };
581          if(it.Find(ofi))
582             openedFiles.Delete(it.pointer);
583          if(!holdTracking)
584             modified = true;
585       }
586       return ofi;
587    }
588
589    void UpdateSourceDirsArray(Array<String> dirs)
590    {
591       sourceDirs.Free();
592
593       for(s : dirs)
594          sourceDirs.Add(CopyString(s));
595
596       DropInvalidBreakpoints(null);
597
598       delete dirs;
599    }
600
601    void RemoveProject(Project project)
602    {
603       Iterator<Project> it { projects };
604       if(it.Find(project))
605          it.Remove();
606
607       for(bp : breakpoints)
608       DropInvalidBreakpoints(project);
609       modified = true;
610       ide.findInFilesDialog.RemoveProjectItem(project);
611       ide.UpdateToolBarActiveConfigs(false);
612       Save();
613
614       delete project;
615    }
616
617    void SelectActiveConfig(const char * configName)
618    {
619       bool change = false;
620       for(prj : ide.workspace.projects)
621       {
622          for(cfg : prj.configurations)
623          {
624             if(cfg.name && !strcmp(cfg.name, configName))
625             {
626                prj.config = cfg;
627                change = true;
628                break;
629             }
630          }
631       }
632       if(change)
633       {
634          modified = true;
635          ide.UpdateToolBarActiveConfigs(true);
636          ide.projectView.Update(null);
637          Save();
638       }
639       ide.AdjustDebugMenus();
640    }
641
642    bool FindPath(ProjectNode node, const char * path)
643    {
644       if(node.type == file)
645       {
646          // TODO: Should this code be moved into a ProjectNode::absolutePath property? Taken from NodeProperties.ec
647          char filePath[MAX_LOCATION];
648          GetSlashPathBuffer(filePath, node.project.topNode.path);
649          PathCatSlash(filePath, node.path);
650          PathCatSlash(filePath, node.name);
651
652          if(!fstrcmp(filePath, path))
653             return true;
654       }
655       if(node.files)
656       {
657          for(n : node.files)
658          {
659             if(FindPath(n, path))
660                return true;
661          }
662       }
663       return false;
664    }
665
666    void ChangeBreakpoint(DataRow row, const char * location)
667    {
668       Breakpoint bp = (Breakpoint)(intptr)row.tag;
669       if(bp)
670       {
671          char * currentLoc = bp.CopyUserLocationString();
672          if(strcmp(location, currentLoc))
673          {
674             char * newLoc;
675             bp.location = location;
676             bp.ParseLocation();
677             newLoc = bp.CopyUserLocationString();
678             if(strcmp(newLoc, currentLoc))
679             {
680                ide.breakpointsView.UpdateBreakpoint(row);
681                Save();
682             }
683          }
684          delete currentLoc;
685       }
686       else if(location)
687       {
688          // adding a breakpoint by typing it in the breakpoints view
689          // todo, parse location
690          //  if good, make add breakpoint, make sure possibly previously entered ignore and level are reflected in breakpoint
691          //  else
692          //     ...
693          //bp = Breakpoint { };
694          //row.tag = (int64)bp;
695          //breakpoints.Add(bp);
696          //bp.row = row;
697          Save();
698       }
699    }
700
701    void ChangeBreakpointIgnore(DataRow row, int ignore)
702    {
703       Breakpoint bp = (Breakpoint)(intptr)row.tag;
704       if(bp)
705       {
706          bp.ignore = ignore;
707          Save();
708       }
709    }
710
711    void ChangeBreakpointLevel(DataRow row, int level)
712    {
713       Breakpoint bp = (Breakpoint)(intptr)row.tag;
714       if(bp)
715       {
716          bp.level = level;
717          Save();
718       }
719    }
720
721    void ChangeBreakpointCondition(DataRow row, const char * condition)
722    {
723       Breakpoint bp = (Breakpoint)(intptr)row.tag;
724       if(bp && !(!bp.condition && !(condition && condition[0])))
725       {
726          if(!bp.condition)
727          {
728             bp.condition = Watch { };
729             bp.condition.expression = CopyString(condition);
730             Save();
731          }
732          else if(!(condition && condition[0]))
733          {
734             bp.condition.Free();
735             bp.condition = null;
736             Save();
737          }
738          else if(strcmp(condition, bp.condition.expression))
739          {
740             bp.condition.Free();
741             bp.condition = Watch { };
742             bp.condition.expression = CopyString(condition);
743             Save();
744          }
745       }
746    }
747
748    void RemoveBreakpoint(Breakpoint bp)
749    {
750       bpCount--;
751       ide.breakpointsView.RemoveBreakpoint(bp);
752       ide.debugger.UpdateRemovedBreakpoint(bp);
753       {
754          Iterator<Breakpoint> it { breakpoints };
755          if(it.Find(bp))
756             breakpoints.Remove(it.pointer);
757       }
758       {
759          Window document;
760          for(document = ide.firstChild; document; document = document.next)
761          {
762             const char * fileName = document.fileName;
763             if(document.isDocument && fileName && document.created)
764             {
765                char winFilePath[MAX_LOCATION];
766                const char * slashPath = GetSlashPathBuffer(winFilePath, fileName);
767
768                if(!fstrcmp(slashPath, bp.absoluteFilePath))
769                {
770                   CodeEditor codeEditor = (CodeEditor)document;
771                   int boxH = codeEditor.editBox.clientSize.h;
772                   Box box { 0, 0, 19, boxH - 1 };
773                   document.Update(box);
774                   break;
775                }
776             }
777          }
778       }
779       Save();
780       delete bp;
781    }
782
783    void ParseLoadedBreakpoints()
784    {
785       for(bp : breakpoints; bp.location)
786       {
787          bp.ParseLocation();
788          ide.breakpointsView.UpdateBreakpoint(bp.row);
789       }
790    }
791
792    void DropInvalidBreakpoints(Project removedProject)
793    {
794       Link bpLink, next;
795       for(bpLink = breakpoints.first; bpLink; bpLink = next)
796       {
797          Breakpoint bp = (Breakpoint)(intptr)bpLink.data;
798          next = bpLink.next;
799
800          if(bp.type == user)
801          {
802             if(removedProject)
803             {
804                if(bp.project == removedProject)
805                {
806                   ide.breakpointsView.RemoveBreakpoint(bp);
807                   RemoveBreakpoint(bp);
808                }
809             }
810             else
811             {
812                Project project = bp.project;
813                if(!project)
814                {
815                   for(p : projects)
816                   {
817                      if(FindPath(p.topNode, bp.absoluteFilePath))
818                      {
819                         project = p;
820                         break;
821                      }
822                      // Handle symbol loader modules:
823                      {
824                         char moduleName[MAX_FILENAME];
825                         char * sl;
826                         GetLastDirectory(bp.absoluteFilePath, moduleName);
827                         // Tweak for automatically resolving symbol loader modules
828                         sl = strstr(moduleName, ".main.ec");
829                         if(sl && (*sl = 0, !strcmpi(moduleName, p.name)))
830                         {
831                            project = p;
832                            break;
833                         }
834                      }
835                   }
836                }
837                if(!project)
838                {
839                   bool found = false;
840                   for(dir : sourceDirs)
841                   {
842                      if(IsPathInsideOf(bp.absoluteFilePath, dir))
843                      {
844                         found = true;
845                         break;
846                      }
847                   }
848                   if(!found)
849                   {
850                      ide.breakpointsView.RemoveBreakpoint(bp);
851                      RemoveBreakpoint(bp);
852                   }
853                }
854             }
855          }
856       }
857       ide.breakpointsView.Update(null);
858    }
859
860    void Free()
861    {
862       delete workspaceFile;
863       delete workspaceDir;
864       delete commandLineArgs;
865       delete debugDir;
866
867       //project = null;
868
869       projects.Free();
870       breakpoints.Free();
871       watches.Free();
872    }
873
874    Workspace()
875    {
876       ide.outputView.buildBox.Clear();
877       ide.outputView.debugBox.Clear();
878       ide.callStackView.Clear();
879       ide.watchesView.Clear();
880       ide.threadsView.Clear();
881       ide.breakpointsView.Clear();
882
883       property::debugDir = "";
884
885       SetSourceDirs(sourceDirs);
886    }
887
888    ~Workspace()
889    {
890       Save();
891       timer.Stop();
892
893       sourceDirs.Free();
894       environmentVars.Free();
895       SetSourceDirs(null);
896       Free();
897       openedFiles.Free();
898       delete compiler;
899    }
900
901 }
902
903 Workspace LoadWorkspace(const char * filePath, const char * fromProjectFile)
904 {
905    File file;
906    Workspace workspace = null;
907
908    file = FileOpen(filePath, read);
909    if(file)
910    {
911       OldList openedFilesNotFound { };
912       double version = 0;
913       char section[128];
914       char subSection[128];
915
916       workspace = Workspace { compiler = ideSettings.defaultCompiler, workspaceFile = filePath };
917
918       file.Seek(0, start);
919       while(!file.Eof())
920       {
921          char buffer[65536];
922          char * equal;
923
924          Watch wh;
925          Breakpoint bp = null;
926
927          file.GetLine(buffer, 65536 - 1);
928          TrimLSpaces(buffer, buffer);
929          TrimRSpaces(buffer, buffer);
930          if(strlen(buffer))
931          {
932             if(buffer[0] == '~')
933             {
934                equal = &buffer[0];
935                equal[0] = ' ';
936                TrimLSpaces(equal, equal);
937                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Watches"))
938                {
939                   wh = Watch { };
940                   workspace.watches.Add(wh);
941                   wh.expression = CopyString(equal);
942                }
943                else if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Breakpoints"))
944                {
945                   if(bp)
946                   {
947                      wh = Watch { };
948                      wh.expression = CopyString(equal);
949                      bp.condition = wh;
950                   }
951                }
952                else if(!strcmpi(section, "Execution Data") && !strcmpi(subSection, "Environment Variables"))
953                {
954                   String value = strchr(equal, '=');
955                   if(value)
956                   {
957                      *value = 0;
958                      value++;
959                      TrimRSpaces(equal, equal);
960                      TrimLSpaces(value, value);
961                      workspace.environmentVars.Add({ equal, value });
962                   }
963                }
964             }
965             else if(buffer[0] == '*')
966             {
967                equal = &buffer[0];
968                equal[0] = ' ';
969                TrimLSpaces(equal, equal);
970                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Breakpoints"))
971                {
972                   char * strEnabled = null;
973                   char * strIgnore = null;
974                   char * strLevel = null;
975                   char * strLine = null;
976                   char * strFile = null;
977
978                   strEnabled = equal;
979                   if(strEnabled && strEnabled[0])
980                   {
981                      strIgnore = strstr(strEnabled, ",");
982                      strIgnore[0] = '\0';
983                      strIgnore++;
984                   }
985                   if(strIgnore && strIgnore[0])
986                   {
987                      strLevel = strstr(strIgnore, ",");
988                      strLevel[0] = '\0';
989                      strLevel++;
990                   }
991                   if(strLevel && strLevel[0])
992                   {
993                      strLine = strstr(strLevel, ",");
994                      strLine[0] = '\0';
995                      strLine++;
996                   }
997                   if(strLine && strLine[0])
998                   {
999                      strFile = strstr(strLine, ",");
1000                      strFile[0] = '\0';
1001                      strFile++;
1002                   }
1003                   if(strEnabled && strEnabled[0] && strIgnore && strIgnore[0] &&
1004                         strLevel && strLevel[0] && strLine && strLine[0] && strFile && strFile[0])
1005                   {
1006                      bool enabled;
1007                      int ignore;
1008                      int level;
1009                      int line;
1010
1011                      TrimLSpaces(strEnabled, strEnabled);
1012                      TrimRSpaces(strEnabled, strEnabled);
1013                      TrimLSpaces(strIgnore, strIgnore);
1014                      TrimRSpaces(strIgnore, strIgnore);
1015                      TrimLSpaces(strLevel, strLevel);
1016                      TrimRSpaces(strLevel, strLevel);
1017                      TrimLSpaces(strLevel, strLevel);
1018                      TrimRSpaces(strLevel, strLevel);
1019                      TrimLSpaces(strFile, strFile);
1020                      TrimRSpaces(strFile, strFile);
1021
1022                      enabled = (strEnabled[0] == '1');
1023                      ignore = atoi(strIgnore);
1024                      level = atoi(strLevel);
1025                      line = atoi(strLine);
1026
1027                      bp = { type = user, enabled = enabled, ignore = ignore, level = level, line = line };
1028                      workspace.breakpoints.Add(bp);
1029                      bp.location = strFile;
1030                   }
1031                }
1032             }
1033             else if(buffer[0] == '=' || buffer[0] == '-')
1034             {
1035                equal = &buffer[0];
1036                equal[0] = ' ';
1037                TrimLSpaces(equal, equal);
1038                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Source Directories"))
1039                   workspace.sourceDirs.Add(CopyString(equal));
1040                else if(!strcmpi(section, "Opened Files"))
1041                {
1042                   OpenedFileState state = opened;
1043                   int lineNumber = 0;
1044                   int position = 0;
1045                   Point scroll { };
1046                   char absolutePath[MAX_LOCATION];
1047                   strcpy(absolutePath, workspace.workspaceDir);
1048                   if(version == 0.01)
1049                   {
1050                      char * comma = strchr(equal, ',');
1051                      if(comma)
1052                      {
1053                         comma[0] = '\0';
1054                         lineNumber = atoi(equal);
1055                         equal = comma + 1;
1056                      }
1057                   }
1058                   else if(version >= 0.02)
1059                   {
1060                      char * column = strchr(equal, ':');
1061                      if(column)
1062                      {
1063                         column[0] = '\0';
1064                         if(strcmpi(equal, "O"))
1065                            state = closed;
1066                         column++;
1067                         equal = column;
1068                         column = strchr(equal, ':');
1069                         if(column)
1070                         {
1071                            column[0] = '\0';
1072                            lineNumber = atoi(equal);
1073                            column++;
1074                            equal = column;
1075                            column = strchr(equal, ':');
1076                            if(column)
1077                            {
1078                               column[0] = '\0';
1079                               position = atoi(equal);
1080                               column++;
1081                               equal = column;
1082                               column = strchr(equal, ':');
1083                               if(column)
1084                               {
1085                                  column[0] = '\0';
1086                                  scroll.x = atoi(equal);
1087                                  column++;
1088                                  equal = column;
1089                                  column = strchr(equal, ':');
1090                                  if(column)
1091                                  {
1092                                     column[0] = '\0';
1093                                     scroll.y = atoi(equal);
1094                                     column++;
1095                                     equal = column;
1096                                  }
1097                               }
1098                            }
1099                         }
1100                      }
1101                   }
1102                   PathCatSlash(absolutePath, equal);
1103
1104                   if(state == closed || FileExists(absolutePath))
1105                      workspace.openedFiles.Add(OpenedFileInfo { path = CopyString(absolutePath), state = state, lineNumber = lineNumber, position = position, scroll = scroll });
1106                   else
1107                      openedFilesNotFound.Add(NamedItem { name = CopyString(equal) });
1108                }
1109                else if(!strcmpi(section, "Projects"))
1110                {
1111                   char projectFilePath[MAX_LOCATION];
1112                   Project newProject;
1113                   strcpy(projectFilePath, workspace.workspaceDir);
1114                   PathCatSlash(projectFilePath, equal);
1115                   newProject = LoadProject(projectFilePath, null);
1116                   if(newProject)
1117                   {
1118                      workspace.projects.Add(newProject);
1119                      newProject.StartMonitoring();
1120                   }
1121                   else if(workspace.projects.count == 0)
1122                   {
1123                      delete workspace;
1124                      break;
1125                   }
1126                   else
1127                   {
1128                      // TODO: show message or something when added project fails to load
1129                      // http://ecere.com/mantis/view.php?id=524
1130                   }
1131                }
1132             }
1133             else if(!strcmpi(buffer, "ECERE Workspace File"));
1134             else if(!strcmpi(buffer, "Version 0a"))
1135                version = 0;
1136             else if(!strncmp(buffer, "Version ", 8))
1137                version = atof(&buffer[8]);
1138             else if(!strcmpi(buffer, "Workspace"))
1139                strcpy(section, buffer);
1140             else if(!strcmpi(buffer, "Projects"))
1141                strcpy(section, buffer);
1142             else if(!strcmpi(buffer, "Execution Data"))
1143                strcpy(section, buffer);
1144             else if(!strcmpi(buffer, "Debugger Data"))
1145                strcpy(section, buffer);
1146             else if(!strcmpi(buffer, "Source Directories"))
1147                strcpy(subSection, buffer);
1148             else if(!strcmpi(buffer, "Breakpoints"))
1149                strcpy(subSection, buffer);
1150             else if(!strcmpi(buffer, "Watches"))
1151                strcpy(subSection, buffer);
1152             else if(!strcmpi(buffer, "Environment Variables"))
1153                strcpy(subSection, buffer);
1154             else if(!strcmpi(buffer, "Opened Files"))
1155                strcpy(section, buffer);
1156             else if(!strcmpi(buffer, ""))      // | These two lines were commented out
1157                strcpy(subSection, buffer);     // | Do they serve a purpose? They were there for copy paste when adding a new subsection
1158             else
1159             {
1160                equal = strstr(buffer, "=");
1161                if(equal)
1162                {
1163                   if(!strcmpi(section, "Workspace"))
1164                   {
1165                      equal[0] = '\0';
1166                      TrimRSpaces(buffer, buffer);
1167                      equal++;
1168                      TrimLSpaces(equal, equal);
1169                      if(!strcmpi(buffer, "Active Compiler"))
1170                      {
1171                         CompilerConfig compiler = ideSettings.GetCompilerConfig(equal);
1172                         if(!compiler)
1173                            workspace.compiler = defaultCompilerName;
1174                         else
1175                            workspace.compiler = equal;
1176                         delete compiler;
1177                      }
1178                      if(!strcmpi(buffer, "Active Bit Depth"))
1179                      {
1180                         int bitDepth = atoi(equal);
1181                         if(!(bitDepth == 32 || bitDepth == 64))
1182                            bitDepth = 0;
1183                         workspace.bitDepth = bitDepth;
1184                         ide.toolBar.activeBitDepth.SelectRow(ide.toolBar.activeBitDepth.FindRow(bitDepth));
1185                      }
1186                   }
1187                   else if(!strcmpi(section, "Execution Data"))
1188                   {
1189                      equal[0] = '\0';
1190                      TrimRSpaces(buffer, buffer);
1191                      equal++;
1192                      TrimLSpaces(equal, equal);
1193                      if(!strcmpi(buffer, "Command Line Arguments"))
1194                         workspace.commandLineArgs = equal;
1195
1196                      if(!strcmpi(buffer, "Environment Variables"))
1197                      {
1198                         workspace.environmentVars.Free();
1199                         delete workspace.environmentVars;
1200                         workspace.environmentVars = { };
1201                      }
1202
1203                   }
1204                   else if(!strcmpi(section, "Debugger Data"))
1205                   {
1206                      equal[0] = '\0';
1207                      TrimRSpaces(buffer, buffer);
1208                      equal++;
1209                      TrimLSpaces(equal, equal);
1210                      if(!strcmpi(buffer, "Debug Working Directory"))
1211                         workspace.debugDir = equal;
1212                   }
1213                   else
1214                   {
1215                      equal[0] = '\0';
1216                      TrimRSpaces(buffer, buffer);
1217                      equal++;
1218                      TrimLSpaces(equal, equal);
1219                      if(!strcmpi(buffer, "Active Configuration"))
1220                      {
1221                         Project prj;
1222                         if(workspace.projects.last)
1223                         {
1224                            prj = workspace.projects.lastIterator.data;
1225                            for(cfg : prj.configurations)
1226                            {
1227                               if(!strcmp(cfg.name, equal))
1228                               {
1229                                  prj.config = cfg;
1230                                  break;
1231                               }
1232                            }
1233                         }
1234                      }
1235                      else if(!strcmpi(buffer, "Modified Compiler Config") || !strcmpi(buffer, "Modified Linker Config"))
1236                      {
1237                         Project prj;
1238                         if(workspace.projects.last)
1239                         {
1240                            prj = workspace.projects.lastIterator.data;
1241                            for(cfg : prj.configurations)
1242                            {
1243                               if(!strcmp(cfg.name, equal))
1244                               {
1245                                  if(strstr(buffer, "Compiler"))
1246                                     cfg.compilingModified = true;
1247                                  else
1248                                     cfg.linkingModified = true;
1249                                  break;
1250                               }
1251                            }
1252                         }
1253                      }
1254                      else if(!strcmpi(buffer, "CommandLineArgs"))
1255                         workspace.commandLineArgs = equal;
1256                      else if(!strcmpi(buffer, "Breakpoint"))
1257                      {
1258                         bool enabled;
1259                         char * lineNum = strstr(equal, ",");
1260                         if(lineNum)
1261                         {
1262                            lineNum[0] = '\0';
1263                            lineNum++;
1264                            if(equal[0] == '0')
1265                               enabled = false;
1266                            else
1267                               enabled = true;
1268                            if(lineNum)
1269                            {
1270                               char * absPath = strstr(lineNum, ",");
1271                               if(absPath)
1272                               {
1273                                  absPath[0] = '\0';
1274                                  absPath++;
1275                                  if(absPath)
1276                                  {
1277                                     char * relPath = strstr(absPath, ",");
1278                                     if(relPath)
1279                                     {
1280                                        relPath[0] = '\0';
1281                                        relPath++;
1282                                        if(relPath)
1283                                        {
1284                                           bp = { type = user, enabled = enabled, level = -1 };
1285                                           workspace.breakpoints.Add(bp);
1286                                           bp.line = atoi(lineNum);
1287                                           bp.location = relPath;
1288                                        }
1289                                     }
1290                                  }
1291                               }
1292                            }
1293                         }
1294                      }
1295                      else if(!strcmpi(buffer, "Watch"))
1296                      {
1297                         wh = Watch { };
1298                         workspace.watches.Add(wh);
1299                         wh.expression = CopyString(equal);
1300                      }
1301                      else if(!strcmpi(buffer, "SourceDir"))
1302                      {
1303                         workspace.sourceDirs.Add(CopyString(equal));
1304                      }
1305                      else if(!strcmpi(buffer, "DebugDir"))
1306                      {
1307                         workspace.debugDir = equal;
1308                      }
1309                   }
1310                }
1311             }
1312          }
1313       }
1314
1315       delete file;
1316
1317       if(workspace)
1318       {
1319          if(!workspace.projects.first)
1320          {
1321             Project project;
1322             if(fromProjectFile)
1323                project = LoadProject(fromProjectFile /*projectFilePath*/, null);
1324             else
1325             {
1326                char projectFilePath[MAX_LOCATION];
1327                strcpy(projectFilePath, workspace.workspaceFile);
1328                ChangeExtension(projectFilePath, ProjectExtension, projectFilePath);
1329                project = LoadProject(projectFilePath, null);
1330             }
1331             if(project)
1332             {
1333                project.StartMonitoring();
1334                workspace.projects.Add(project);
1335                workspace.name = CopyString(project.name);
1336             }
1337             else
1338             {
1339                MessageBox { type = ok, master = ide, contents = $"Workspace load file failed", text = $"Workspace Load File Error" }.Modal();
1340                delete workspace;
1341                return null;
1342             }
1343          }
1344
1345          if(openedFilesNotFound.first)
1346          {
1347             int c = 0;
1348             char s[2] = "";
1349             String files = new char[MAX_LOCATION * 16];
1350             char title[512];
1351             String msg = new char[MAX_LOCATION * 16 + 2048];
1352             NamedItem item;
1353             strcpy(files,"\n");
1354
1355             item = openedFilesNotFound.first;
1356             if(item.next)
1357                strcpy(s, "s");
1358
1359             for(item = openedFilesNotFound.first; item; item = item.next)
1360             {
1361                c++;
1362                if(c == 16)
1363                {
1364                   strcat(files, "\n...");
1365                   break;
1366                }
1367                strcat(files, "\n");
1368                strcat(files, item.name);
1369             }
1370
1371             sprintf(title, $"File%s not found", s);
1372             sprintf(msg, $"The following file%s could not be re-opened.%s", s, files);
1373
1374             MessageBox { type = ok, master = ide, contents = msg, text = title }.Modal();
1375
1376             delete files;
1377             delete msg;
1378          }
1379          openedFilesNotFound.Free(OldLink::Free);
1380       }
1381       else
1382          openedFilesNotFound.Free(OldLink::Free);
1383    }
1384    else if(fromProjectFile)
1385    {
1386       //MessageBox { type = Ok, master = ide, contents = "Worspace load file failed", text = "Worspace Load File Error" }.Modal();
1387       //char projectFile[MAX_LOCATION];
1388       Project newProject;
1389
1390       //strcpy(projectFile, filePath);
1391       //ChangeExtension(projectFile, ProjectExtension, projectFile);
1392       newProject = LoadProject(fromProjectFile /*projectFile*/, null);
1393
1394       if(newProject)
1395       {
1396          newProject.StartMonitoring();
1397          workspace = Workspace { workspaceFile = filePath };
1398
1399          workspace.projects.Add(newProject);
1400          workspace.Save();
1401       }
1402    }
1403
1404    if(workspace)
1405    {
1406       ide.ChangeFileDialogsDirectory(workspace.workspaceDir, false);
1407
1408       if(!workspace.compiler || !workspace.compiler[0])
1409          workspace.compiler = defaultCompilerName;
1410    }
1411    return workspace;
1412 }