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