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