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