6f126a074dbc1cbceea8315b2a6860c528739197
[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 = 2.5;
138       bool DelayExpired()
139       {
140          if(modified)
141             Save();
142          return true;
143       }
144    };
145
146    property const 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 const 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 * CopyAbsolutePathFromRelative(const 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 * CopyUnixPathWorkspaceRelativeOrAbsolute(const 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    char * MakeRelativePath(char * buffer, const char * path)
428    {
429       char * result = null;
430       if(buffer && path)
431       {
432          MakePathRelative(path, workspaceDir, buffer);
433          MakeSlashPath(buffer);
434          result = buffer;
435       }
436       return result;
437    }
438
439    char * GetRelativePathForProject(char * buffer, Project project)
440    {
441       char * result = null;
442       if(buffer && project && project.topNode.path)
443       {
444          MakePathRelative(project.topNode.path, workspaceDir, buffer);
445          MakeSlashPath(buffer);
446          PathCatSlash(buffer, project.topNode.name);
447          result = buffer;
448       }
449       return result;
450    }
451
452    Array<ProjectNode> GetAllProjectNodes(const char *fullPath, bool skipExcluded)
453    {
454       Array<ProjectNode> nodes = null;
455       for(project : projects)
456       {
457          ProjectNode node;
458          if((node = project.topNode.FindByFullPath(fullPath, false)))
459          {
460             if(!skipExcluded || !node.GetIsExcluded(project.config))
461             {
462                if(!nodes) nodes = { };
463                nodes.Add(node);
464             }
465          }
466       }
467       return nodes;
468    }
469
470    Project GetFileOwner(const char * absolutePath, const char * objectFileExt)
471    {
472       Project owner = null;
473       for(prj : projects)
474       {
475          if(prj.topNode.FindByFullPath(absolutePath, false))
476          {
477             owner = prj;
478             break;
479          }
480       }
481       if(!owner)
482          GetObjectFileNode(absolutePath, &owner, null, objectFileExt);
483       return owner;
484    }
485
486    void GetRelativePath(const char * absolutePath, char * relativePath, Project * owner, const char * objectFileExt)
487    {
488       Project prj = GetFileOwner(absolutePath, objectFileExt);
489       if(owner)
490          *owner = prj;
491       if(!prj)
492          prj = projects.firstIterator.data;
493       if(prj)
494       {
495          MakePathRelative(absolutePath, prj.topNode.path, relativePath);
496          MakeSlashPath(relativePath);
497       }
498       else
499          relativePath[0] = '\0';
500    }
501
502    ProjectNode GetObjectFileNode(const char * filePath, Project * project, char * fullPath, const char * objectFileExt)
503    {
504       ProjectNode node = null;
505       char ext[MAX_EXTENSION];
506       GetExtension(filePath, ext);
507       if(ext[0])
508       {
509          IntermediateFileType type = IntermediateFileType::FromExtension(ext);
510          if(type)
511          {
512             char fileName[MAX_FILENAME];
513             GetLastDirectory(filePath, fileName);
514             if(fileName[0])
515             {
516                DotMain dotMain = DotMain::FromFileName(fileName);
517                for(prj : ide.workspace.projects)
518                {
519                   if((node = prj.FindNodeByObjectFileName(fileName, type, dotMain, null, objectFileExt)))
520                   {
521                      if(project)
522                         *project = prj;
523                      if(fullPath)
524                      {
525                         const char * cfgName = prj.config ? prj.config.name : "";
526                         char name[MAX_FILENAME];
527                         CompilerConfig compiler = ideSettings.GetCompilerConfig(prj.lastBuildCompilerName);
528                         DirExpression objDir = prj.GetObjDir(compiler, prj.config, bitDepth);
529                         strcpy(fullPath, prj.topNode.path);
530                         PathCatSlash(fullPath, objDir.dir);
531                         node.GetObjectFileName(name, prj.configsNameCollisions[cfgName], type, dotMain, objectFileExt);
532                         PathCatSlash(fullPath, name);
533                         delete objDir;
534                         delete compiler;
535                      }
536                      break;
537                   }
538                }
539             }
540          }
541       }
542       return node;
543    }
544
545    OpenedFileInfo UpdateOpenedFileInfo(const char * fileName, OpenedFileState state)
546    {
547       char filePath[MAX_LOCATION];
548       OpenedFileInfo ofi = null;
549       GetSlashPathBuffer(filePath, fileName);
550       for(item : openedFiles)
551       {
552          if(!fstrcmp(item.path, filePath))
553          {
554             ofi = item;
555             break;
556          }
557       }
558       if(state)
559       {
560          if(!ofi)
561          {
562             ofi = OpenedFileInfo { path = CopyString(filePath) };
563             openedFiles.Add(ofi);
564          }
565          ofi.state = state;
566          if(!holdTracking)
567             modified = true;
568       }
569       else if(ofi)
570       {
571          Iterator<OpenedFileInfo> it { openedFiles };
572          if(it.Find(ofi))
573             openedFiles.Delete(it.pointer);
574          if(!holdTracking)
575             modified = true;
576       }
577       return ofi;
578    }
579
580    void UpdateSourceDirsArray(Array<String> dirs)
581    {
582       sourceDirs.Free();
583
584       for(s : dirs)
585          sourceDirs.Add(CopyString(s));
586
587       DropInvalidBreakpoints(null);
588
589       delete dirs;
590    }
591
592    void RemoveProject(Project project)
593    {
594       Iterator<Project> it { projects };
595       if(it.Find(project))
596          it.Remove();
597
598       for(bp : breakpoints)
599       DropInvalidBreakpoints(project);
600       modified = true;
601       ide.findInFilesDialog.RemoveProjectItem(project);
602       ide.UpdateToolBarActiveConfigs(false);
603       Save();
604
605       delete project;
606    }
607
608    void SelectActiveConfig(const char * configName)
609    {
610       bool change = false;
611       for(prj : ide.workspace.projects)
612       {
613          for(cfg : prj.configurations)
614          {
615             if(cfg.name && !strcmp(cfg.name, configName))
616             {
617                prj.config = cfg;
618                change = true;
619                break;
620             }
621          }
622       }
623       if(change)
624       {
625          modified = true;
626          ide.UpdateToolBarActiveConfigs(true);
627          ide.projectView.Update(null);
628          Save();
629       }
630       ide.AdjustDebugMenus();
631    }
632
633    bool FindPath(ProjectNode node, const char * path)
634    {
635       if(node.type == file)
636       {
637          // TODO: Should this code be moved into a ProjectNode::absolutePath property? Taken from NodeProperties.ec
638          char filePath[MAX_LOCATION];
639          GetSlashPathBuffer(filePath, node.project.topNode.path);
640          PathCatSlash(filePath, node.path);
641          PathCatSlash(filePath, node.name);
642
643          if(!fstrcmp(filePath, path))
644             return true;
645       }
646       if(node.files)
647       {
648          for(n : node.files)
649          {
650             if(FindPath(n, path))
651                return true;
652          }
653       }
654       return false;
655    }
656
657    void ChangeBreakpoint(DataRow row, const char * location)
658    {
659       Breakpoint bp = (Breakpoint)(intptr)row.tag;
660       if(bp)
661       {
662          char * currentLoc = bp.CopyUserLocationString();
663          if(strcmp(location, currentLoc))
664          {
665             char * newLoc;
666             bp.location = location;
667             bp.ParseLocation();
668             newLoc = bp.CopyUserLocationString();
669             if(strcmp(newLoc, currentLoc))
670             {
671                ide.breakpointsView.UpdateBreakpoint(row);
672                Save();
673             }
674          }
675          delete currentLoc;
676       }
677       else if(location)
678       {
679          // adding a breakpoint by typing it in the breakpoints view
680          // todo, parse location
681          //  if good, make add breakpoint, make sure possibly previously entered ignore and level are reflected in breakpoint
682          //  else
683          //     ...
684          //bp = Breakpoint { };
685          //row.tag = (int64)bp;
686          //breakpoints.Add(bp);
687          //bp.row = row;
688          Save();
689       }
690    }
691
692    void ChangeBreakpointIgnore(DataRow row, int ignore)
693    {
694       Breakpoint bp = (Breakpoint)(intptr)row.tag;
695       if(bp)
696       {
697          bp.ignore = ignore;
698          Save();
699       }
700    }
701
702    void ChangeBreakpointLevel(DataRow row, int level)
703    {
704       Breakpoint bp = (Breakpoint)(intptr)row.tag;
705       if(bp)
706       {
707          bp.level = level;
708          Save();
709       }
710    }
711
712    void ChangeBreakpointCondition(DataRow row, const char * condition)
713    {
714       Breakpoint bp = (Breakpoint)(intptr)row.tag;
715       if(bp && !(!bp.condition && !(condition && condition[0])))
716       {
717          if(!bp.condition)
718          {
719             bp.condition = Watch { };
720             bp.condition.expression = CopyString(condition);
721             Save();
722          }
723          else if(!(condition && condition[0]))
724          {
725             bp.condition.Free();
726             bp.condition = null;
727             Save();
728          }
729          else if(strcmp(condition, bp.condition.expression))
730          {
731             bp.condition.Free();
732             bp.condition = Watch { };
733             bp.condition.expression = CopyString(condition);
734             Save();
735          }
736       }
737    }
738
739    void RemoveBreakpoint(Breakpoint bp)
740    {
741       bpCount--;
742       ide.breakpointsView.RemoveBreakpoint(bp);
743       ide.debugger.UpdateRemovedBreakpoint(bp);
744       {
745          Iterator<Breakpoint> it { breakpoints };
746          if(it.Find(bp))
747             breakpoints.Remove(it.pointer);
748       }
749       {
750          Window document;
751          for(document = ide.firstChild; document; document = document.next)
752          {
753             const char * fileName = document.fileName;
754             if(document.isDocument && fileName && document.created)
755             {
756                char winFilePath[MAX_LOCATION];
757                const char * slashPath = GetSlashPathBuffer(winFilePath, fileName);
758
759                if(!fstrcmp(slashPath, bp.absoluteFilePath))
760                {
761                   CodeEditor codeEditor = (CodeEditor)document;
762                   int boxH = codeEditor.editBox.clientSize.h;
763                   Box box { 0, 0, 19, boxH - 1 };
764                   document.Update(box);
765                   break;
766                }
767             }
768          }
769       }
770       Save();
771       delete bp;
772    }
773
774    void ParseLoadedBreakpoints()
775    {
776       for(bp : breakpoints; bp.location)
777       {
778          bp.ParseLocation();
779          ide.breakpointsView.UpdateBreakpoint(bp.row);
780       }
781    }
782
783    void DropInvalidBreakpoints(Project removedProject)
784    {
785       Link bpLink, next;
786       for(bpLink = breakpoints.first; bpLink; bpLink = next)
787       {
788          Breakpoint bp = (Breakpoint)(intptr)bpLink.data;
789          next = bpLink.next;
790
791          if(bp.type == user)
792          {
793             if(removedProject)
794             {
795                if(bp.project == removedProject)
796                {
797                   ide.breakpointsView.RemoveBreakpoint(bp);
798                   RemoveBreakpoint(bp);
799                }
800             }
801             else
802             {
803                Project project = bp.project;
804                if(!project)
805                {
806                   for(p : projects)
807                   {
808                      if(FindPath(p.topNode, bp.absoluteFilePath))
809                      {
810                         project = p;
811                         break;
812                      }
813                      // Handle symbol loader modules:
814                      {
815                         char moduleName[MAX_FILENAME];
816                         char * sl;
817                         GetLastDirectory(bp.absoluteFilePath, moduleName);
818                         // Tweak for automatically resolving symbol loader modules
819                         sl = strstr(moduleName, ".main.ec");
820                         if(sl && (*sl = 0, !strcmpi(moduleName, p.name)))
821                         {
822                            project = p;
823                            break;
824                         }
825                      }
826                   }
827                }
828                if(!project)
829                {
830                   bool found = false;
831                   for(dir : sourceDirs)
832                   {
833                      if(IsPathInsideOf(bp.absoluteFilePath, dir))
834                      {
835                         found = true;
836                         break;
837                      }
838                   }
839                   if(!found)
840                   {
841                      ide.breakpointsView.RemoveBreakpoint(bp);
842                      RemoveBreakpoint(bp);
843                   }
844                }
845             }
846          }
847       }
848       ide.breakpointsView.Update(null);
849    }
850
851    void Free()
852    {
853       delete workspaceFile;
854       delete workspaceDir;
855       delete commandLineArgs;
856       delete debugDir;
857
858       //project = null;
859
860       projects.Free();
861       breakpoints.Free();
862       watches.Free();
863    }
864
865    Workspace()
866    {
867       ide.outputView.buildBox.Clear();
868       ide.outputView.debugBox.Clear();
869       ide.callStackView.Clear();
870       ide.watchesView.Clear();
871       ide.threadsView.Clear();
872       ide.breakpointsView.Clear();
873
874       property::debugDir = "";
875
876       SetSourceDirs(sourceDirs);
877    }
878
879    ~Workspace()
880    {
881       Save();
882       timer.Stop();
883
884       sourceDirs.Free();
885       environmentVars.Free();
886       SetSourceDirs(null);
887       Free();
888       openedFiles.Free();
889       delete compiler;
890    }
891
892 }
893
894 Workspace LoadWorkspace(const char * filePath, const char * fromProjectFile)
895 {
896    File file;
897    Workspace workspace = null;
898
899    file = FileOpen(filePath, read);
900    if(file)
901    {
902       OldList openedFilesNotFound { };
903       double version = 0;
904       char section[128];
905       char subSection[128];
906
907       workspace = Workspace { compiler = ideSettings.defaultCompiler, workspaceFile = filePath };
908
909       file.Seek(0, start);
910       while(!file.Eof())
911       {
912          char buffer[65536];
913          char * equal;
914
915          Watch wh;
916          Breakpoint bp = null;
917
918          file.GetLine(buffer, 65536 - 1);
919          TrimLSpaces(buffer, buffer);
920          TrimRSpaces(buffer, buffer);
921          if(strlen(buffer))
922          {
923             if(buffer[0] == '~')
924             {
925                equal = &buffer[0];
926                equal[0] = ' ';
927                TrimLSpaces(equal, equal);
928                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Watches"))
929                {
930                   wh = Watch { };
931                   workspace.watches.Add(wh);
932                   wh.expression = CopyString(equal);
933                }
934                else if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Breakpoints"))
935                {
936                   if(bp)
937                   {
938                      wh = Watch { };
939                      wh.expression = CopyString(equal);
940                      bp.condition = wh;
941                   }
942                }
943                else if(!strcmpi(section, "Execution Data") && !strcmpi(subSection, "Environment Variables"))
944                {
945                   String value = strchr(equal, '=');
946                   if(value)
947                   {
948                      *value = 0;
949                      value++;
950                      TrimRSpaces(equal, equal);
951                      TrimLSpaces(value, value);
952                      workspace.environmentVars.Add({ equal, value });
953                   }
954                }
955             }
956             else if(buffer[0] == '*')
957             {
958                equal = &buffer[0];
959                equal[0] = ' ';
960                TrimLSpaces(equal, equal);
961                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Breakpoints"))
962                {
963                   char * strEnabled = null;
964                   char * strIgnore = null;
965                   char * strLevel = null;
966                   char * strLine = null;
967                   char * strFile = null;
968
969                   strEnabled = equal;
970                   if(strEnabled && strEnabled[0])
971                   {
972                      strIgnore = strstr(strEnabled, ",");
973                      strIgnore[0] = '\0';
974                      strIgnore++;
975                   }
976                   if(strIgnore && strIgnore[0])
977                   {
978                      strLevel = strstr(strIgnore, ",");
979                      strLevel[0] = '\0';
980                      strLevel++;
981                   }
982                   if(strLevel && strLevel[0])
983                   {
984                      strLine = strstr(strLevel, ",");
985                      strLine[0] = '\0';
986                      strLine++;
987                   }
988                   if(strLine && strLine[0])
989                   {
990                      strFile = strstr(strLine, ",");
991                      strFile[0] = '\0';
992                      strFile++;
993                   }
994                   if(strEnabled && strEnabled[0] && strIgnore && strIgnore[0] &&
995                         strLevel && strLevel[0] && strLine && strLine[0] && strFile && strFile[0])
996                   {
997                      bool enabled;
998                      int ignore;
999                      int level;
1000                      int line;
1001
1002                      TrimLSpaces(strEnabled, strEnabled);
1003                      TrimRSpaces(strEnabled, strEnabled);
1004                      TrimLSpaces(strIgnore, strIgnore);
1005                      TrimRSpaces(strIgnore, strIgnore);
1006                      TrimLSpaces(strLevel, strLevel);
1007                      TrimRSpaces(strLevel, strLevel);
1008                      TrimLSpaces(strLevel, strLevel);
1009                      TrimRSpaces(strLevel, strLevel);
1010                      TrimLSpaces(strFile, strFile);
1011                      TrimRSpaces(strFile, strFile);
1012
1013                      enabled = (strEnabled[0] == '1');
1014                      ignore = atoi(strIgnore);
1015                      level = atoi(strLevel);
1016                      line = atoi(strLine);
1017
1018                      bp = { type = user, enabled = enabled, ignore = ignore, level = level, line = line };
1019                      workspace.breakpoints.Add(bp);
1020                      bp.location = strFile;
1021                   }
1022                }
1023             }
1024             else if(buffer[0] == '=' || buffer[0] == '-')
1025             {
1026                equal = &buffer[0];
1027                equal[0] = ' ';
1028                TrimLSpaces(equal, equal);
1029                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Source Directories"))
1030                   workspace.sourceDirs.Add(CopyString(equal));
1031                else if(!strcmpi(section, "Opened Files"))
1032                {
1033                   OpenedFileState state = opened;
1034                   int lineNumber = 0;
1035                   int position = 0;
1036                   Point scroll { };
1037                   char absolutePath[MAX_LOCATION];
1038                   strcpy(absolutePath, workspace.workspaceDir);
1039                   if(version == 0.01)
1040                   {
1041                      char * comma = strchr(equal, ',');
1042                      if(comma)
1043                      {
1044                         comma[0] = '\0';
1045                         lineNumber = atoi(equal);
1046                         equal = comma + 1;
1047                      }
1048                   }
1049                   else if(version >= 0.02)
1050                   {
1051                      char * column = strchr(equal, ':');
1052                      if(column)
1053                      {
1054                         column[0] = '\0';
1055                         if(strcmpi(equal, "O"))
1056                            state = closed;
1057                         column++;
1058                         equal = column;
1059                         column = strchr(equal, ':');
1060                         if(column)
1061                         {
1062                            column[0] = '\0';
1063                            lineNumber = atoi(equal);
1064                            column++;
1065                            equal = column;
1066                            column = strchr(equal, ':');
1067                            if(column)
1068                            {
1069                               column[0] = '\0';
1070                               position = atoi(equal);
1071                               column++;
1072                               equal = column;
1073                               column = strchr(equal, ':');
1074                               if(column)
1075                               {
1076                                  column[0] = '\0';
1077                                  scroll.x = atoi(equal);
1078                                  column++;
1079                                  equal = column;
1080                                  column = strchr(equal, ':');
1081                                  if(column)
1082                                  {
1083                                     column[0] = '\0';
1084                                     scroll.y = atoi(equal);
1085                                     column++;
1086                                     equal = column;
1087                                  }
1088                               }
1089                            }
1090                         }
1091                      }
1092                   }
1093                   PathCatSlash(absolutePath, equal);
1094
1095                   if(state == closed || FileExists(absolutePath))
1096                      workspace.openedFiles.Add(OpenedFileInfo { path = CopyString(absolutePath), state = state, lineNumber = lineNumber, position = position, scroll = scroll });
1097                   else
1098                      openedFilesNotFound.Add(NamedItem { name = CopyString(equal) });
1099                }
1100                else if(!strcmpi(section, "Projects"))
1101                {
1102                   char projectFilePath[MAX_LOCATION];
1103                   Project newProject;
1104                   strcpy(projectFilePath, workspace.workspaceDir);
1105                   PathCatSlash(projectFilePath, equal);
1106                   newProject = LoadProject(projectFilePath, null);
1107                   if(newProject)
1108                   {
1109                      workspace.projects.Add(newProject);
1110                      newProject.StartMonitoring();
1111                   }
1112                   else if(workspace.projects.count == 0)
1113                   {
1114                      delete workspace;
1115                      break;
1116                   }
1117                   else
1118                   {
1119                      // TODO: show message or something when added project fails to load
1120                      // http://ecere.com/mantis/view.php?id=524
1121                   }
1122                }
1123             }
1124             else if(!strcmpi(buffer, "ECERE Workspace File"));
1125             else if(!strcmpi(buffer, "Version 0a"))
1126                version = 0;
1127             else if(!strncmp(buffer, "Version ", 8))
1128                version = atof(&buffer[8]);
1129             else if(!strcmpi(buffer, "Workspace"))
1130                strcpy(section, buffer);
1131             else if(!strcmpi(buffer, "Projects"))
1132                strcpy(section, buffer);
1133             else if(!strcmpi(buffer, "Execution Data"))
1134                strcpy(section, buffer);
1135             else if(!strcmpi(buffer, "Debugger Data"))
1136                strcpy(section, buffer);
1137             else if(!strcmpi(buffer, "Source Directories"))
1138                strcpy(subSection, buffer);
1139             else if(!strcmpi(buffer, "Breakpoints"))
1140                strcpy(subSection, buffer);
1141             else if(!strcmpi(buffer, "Watches"))
1142                strcpy(subSection, buffer);
1143             else if(!strcmpi(buffer, "Environment Variables"))
1144                strcpy(subSection, buffer);
1145             else if(!strcmpi(buffer, "Opened Files"))
1146                strcpy(section, buffer);
1147             else if(!strcmpi(buffer, ""))      // | These two lines were commented out
1148                strcpy(subSection, buffer);     // | Do they serve a purpose? They were there for copy paste when adding a new subsection
1149             else
1150             {
1151                equal = strstr(buffer, "=");
1152                if(equal)
1153                {
1154                   if(!strcmpi(section, "Workspace"))
1155                   {
1156                      equal[0] = '\0';
1157                      TrimRSpaces(buffer, buffer);
1158                      equal++;
1159                      TrimLSpaces(equal, equal);
1160                      if(!strcmpi(buffer, "Active Compiler"))
1161                      {
1162                         CompilerConfig compiler = ideSettings.GetCompilerConfig(equal);
1163                         if(!compiler)
1164                            workspace.compiler = defaultCompilerName;
1165                         else
1166                            workspace.compiler = equal;
1167                         delete compiler;
1168                      }
1169                      if(!strcmpi(buffer, "Active Bit Depth"))
1170                      {
1171                         int bitDepth = atoi(equal);
1172                         if(!(bitDepth == 32 || bitDepth == 64))
1173                            bitDepth = 0;
1174                         workspace.bitDepth = bitDepth;
1175                         ide.toolBar.activeBitDepth.SelectRow(ide.toolBar.activeBitDepth.FindRow(bitDepth));
1176                      }
1177                   }
1178                   else if(!strcmpi(section, "Execution Data"))
1179                   {
1180                      equal[0] = '\0';
1181                      TrimRSpaces(buffer, buffer);
1182                      equal++;
1183                      TrimLSpaces(equal, equal);
1184                      if(!strcmpi(buffer, "Command Line Arguments"))
1185                         workspace.commandLineArgs = equal;
1186
1187                      if(!strcmpi(buffer, "Environment Variables"))
1188                      {
1189                         workspace.environmentVars.Free();
1190                         delete workspace.environmentVars;
1191                         workspace.environmentVars = { };
1192                      }
1193
1194                   }
1195                   else if(!strcmpi(section, "Debugger Data"))
1196                   {
1197                      equal[0] = '\0';
1198                      TrimRSpaces(buffer, buffer);
1199                      equal++;
1200                      TrimLSpaces(equal, equal);
1201                      if(!strcmpi(buffer, "Debug Working Directory"))
1202                         workspace.debugDir = equal;
1203                   }
1204                   else
1205                   {
1206                      equal[0] = '\0';
1207                      TrimRSpaces(buffer, buffer);
1208                      equal++;
1209                      TrimLSpaces(equal, equal);
1210                      if(!strcmpi(buffer, "Active Configuration"))
1211                      {
1212                         Project prj;
1213                         if(workspace.projects.last)
1214                         {
1215                            prj = workspace.projects.lastIterator.data;
1216                            for(cfg : prj.configurations)
1217                            {
1218                               if(!strcmp(cfg.name, equal))
1219                               {
1220                                  prj.config = cfg;
1221                                  break;
1222                               }
1223                            }
1224                         }
1225                      }
1226                      else if(!strcmpi(buffer, "Modified Compiler Config") || !strcmpi(buffer, "Modified Linker Config"))
1227                      {
1228                         Project prj;
1229                         if(workspace.projects.last)
1230                         {
1231                            prj = workspace.projects.lastIterator.data;
1232                            for(cfg : prj.configurations)
1233                            {
1234                               if(!strcmp(cfg.name, equal))
1235                               {
1236                                  if(strstr(buffer, "Compiler"))
1237                                     cfg.compilingModified = true;
1238                                  else
1239                                     cfg.linkingModified = true;
1240                                  break;
1241                               }
1242                            }
1243                         }
1244                      }
1245                      else if(!strcmpi(buffer, "CommandLineArgs"))
1246                         workspace.commandLineArgs = equal;
1247                      else if(!strcmpi(buffer, "Breakpoint"))
1248                      {
1249                         bool enabled;
1250                         char * lineNum = strstr(equal, ",");
1251                         if(lineNum)
1252                         {
1253                            lineNum[0] = '\0';
1254                            lineNum++;
1255                            if(equal[0] == '0')
1256                               enabled = false;
1257                            else
1258                               enabled = true;
1259                            if(lineNum)
1260                            {
1261                               char * absPath = strstr(lineNum, ",");
1262                               if(absPath)
1263                               {
1264                                  absPath[0] = '\0';
1265                                  absPath++;
1266                                  if(absPath)
1267                                  {
1268                                     char * relPath = strstr(absPath, ",");
1269                                     if(relPath)
1270                                     {
1271                                        relPath[0] = '\0';
1272                                        relPath++;
1273                                        if(relPath)
1274                                        {
1275                                           bp = { type = user, enabled = enabled, level = -1 };
1276                                           workspace.breakpoints.Add(bp);
1277                                           bp.line = atoi(lineNum);
1278                                           bp.location = relPath;
1279                                        }
1280                                     }
1281                                  }
1282                               }
1283                            }
1284                         }
1285                      }
1286                      else if(!strcmpi(buffer, "Watch"))
1287                      {
1288                         wh = Watch { };
1289                         workspace.watches.Add(wh);
1290                         wh.expression = CopyString(equal);
1291                      }
1292                      else if(!strcmpi(buffer, "SourceDir"))
1293                      {
1294                         workspace.sourceDirs.Add(CopyString(equal));
1295                      }
1296                      else if(!strcmpi(buffer, "DebugDir"))
1297                      {
1298                         workspace.debugDir = equal;
1299                      }
1300                   }
1301                }
1302             }
1303          }
1304       }
1305
1306       delete file;
1307
1308       if(workspace)
1309       {
1310          if(!workspace.projects.first)
1311          {
1312             Project project;
1313             if(fromProjectFile)
1314                project = LoadProject(fromProjectFile /*projectFilePath*/, null);
1315             else
1316             {
1317                char projectFilePath[MAX_LOCATION];
1318                strcpy(projectFilePath, workspace.workspaceFile);
1319                ChangeExtension(projectFilePath, ProjectExtension, projectFilePath);
1320                project = LoadProject(projectFilePath, null);
1321             }
1322             if(project)
1323             {
1324                project.StartMonitoring();
1325                workspace.projects.Add(project);
1326                workspace.name = CopyString(project.name);
1327             }
1328             else
1329             {
1330                MessageBox { type = ok, master = ide, contents = $"Workspace load file failed", text = $"Workspace Load File Error" }.Modal();
1331                delete workspace;
1332                return null;
1333             }
1334          }
1335
1336          if(openedFilesNotFound.first)
1337          {
1338             int c = 0;
1339             char s[2] = "";
1340             String files = new char[MAX_LOCATION * 16];
1341             char title[512];
1342             String msg = new char[MAX_LOCATION * 16 + 2048];
1343             NamedItem item;
1344             strcpy(files,"\n");
1345
1346             item = openedFilesNotFound.first;
1347             if(item.next)
1348                strcpy(s, "s");
1349
1350             for(item = openedFilesNotFound.first; item; item = item.next)
1351             {
1352                c++;
1353                if(c == 16)
1354                {
1355                   strcat(files, "\n...");
1356                   break;
1357                }
1358                strcat(files, "\n");
1359                strcat(files, item.name);
1360             }
1361
1362             sprintf(title, $"File%s not found", s);
1363             sprintf(msg, $"The following file%s could not be re-opened.%s", s, files);
1364
1365             MessageBox { type = ok, master = ide, contents = msg, text = title }.Modal();
1366
1367             delete files;
1368             delete msg;
1369          }
1370          openedFilesNotFound.Free(OldLink::Free);
1371       }
1372       else
1373          openedFilesNotFound.Free(OldLink::Free);
1374    }
1375    else if(fromProjectFile)
1376    {
1377       //MessageBox { type = Ok, master = ide, contents = "Worspace load file failed", text = "Worspace Load File Error" }.Modal();
1378       //char projectFile[MAX_LOCATION];
1379       Project newProject;
1380
1381       //strcpy(projectFile, filePath);
1382       //ChangeExtension(projectFile, ProjectExtension, projectFile);
1383       newProject = LoadProject(fromProjectFile /*projectFile*/, null);
1384
1385       if(newProject)
1386       {
1387          newProject.StartMonitoring();
1388          workspace = Workspace { workspaceFile = filePath };
1389
1390          workspace.projects.Add(newProject);
1391          workspace.Save();
1392       }
1393    }
1394
1395    if(workspace)
1396    {
1397       ide.ChangeFileDialogsDirectory(workspace.workspaceDir, false);
1398
1399       if(!workspace.compiler || !workspace.compiler[0])
1400          workspace.compiler = defaultCompilerName;
1401    }
1402    return workspace;
1403 }