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