ide: add recent files to workspace for the recent files menu when a workspace is...
[sdk] / ide / src / project / Workspace.ec
index bdff439..0ba89ae 100644 (file)
@@ -1,37 +1,43 @@
 import "ide"
 
-/*static void ParseListValue(List<String> list, char * equal)
+enum OpenedFileState { unknown, opened, closed };
+enum ValgrindLeakCheck
 {
-   char * start, * comma;
-   char * string;
-   string = CopyString(equal);
-   start = string;
-   while(start)
+   no, summary, yes, full;
+
+   property const char *
    {
-      comma = strstr(start, ",");
-      if(comma)
-         comma[0] = '\0';
-      list.Add(CopyString(start));
-      if(comma)
-         comma++;
-      if(comma)
-         comma++;
-      start = comma;
+      get { return OnGetString(null, null, null); }
    }
-   delete string;
-}*/
 
-enum OpenedFileState { unknown, opened, closed };
+   const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
+   {
+      if(this >= no && this <= full)
+      {
+         if(tempString)
+            strcpy(tempString, valgrindLeakCheckNames[this]);
+         return valgrindLeakCheckNames[this];
+      }
+      if(tempString && tempString[0])
+         tempString[0] = '\0';
+      return null;
+   }
+};
+static const char * valgrindLeakCheckNames[ValgrindLeakCheck] = { "no", "summary", "yes", "full" };
 
-class OpenedFileInfo
+class OpenedFileInfo : struct
 {
    class_no_expansion;
-//public:
+public:
 //   class_fixed
    char * path;
    OpenedFileState state;
-   int lineNumber, position;
+   int lineNumber;
+   int position;
    Point scroll;
+   TimeStamp modified;
+
+private:
    bool holdTracking;
 
    property bool trackingAllowed
@@ -58,69 +64,223 @@ class OpenedFileInfo
          ide.workspace.modified = true;
       }
    }
-   
+
    void Activate()
    {
       if(trackingAllowed)
       {
          List<OpenedFileInfo> files = ide.workspace.openedFiles;
          Iterator<OpenedFileInfo> it { files };
-         if(it.Find(this) && it.pointer != files.GetLast())
+         IteratorPointer last;
+         if(it.Find(this) && it.pointer != (last = files.GetLast()))
          {
-            files.Move(it.pointer, files.GetPrev(files.GetLast()));
+            files.Move(it.pointer, last);
             ide.workspace.modified = true;
-         } 
+         }
+         modified = GetLocalTimeStamp();
       }
    }
+
+   void SetCodeEditorState(CodeEditor editor)
+   {
+      int num = Max(lineNumber - 1, 0);
+      Point scrl;
+      holdTracking = true;
+      if(editor.editBox.GoToLineNum(num))
+      {
+         int pos = Max(Min(editor.editBox.line.count, position - 1), 0);
+         editor.editBox.GoToPosition(editor.editBox.line, num, pos);
+      }
+      scrl.x = Max(scroll.x, 0);
+      scrl.y = Max(scroll.y, 0);
+      editor.editBox.scroll = scrl;
+      holdTracking = false;
+   }
+
    ~OpenedFileInfo()
    {
       delete path;
    }
 };
 
-class Workspace
+class WorkspaceFile : struct
+{
+   class_no_expansion;
+public:
+   property const char * format
+   {
+      set { }
+      get { return "Ecere IDE Workspace File"; }
+      isset { return true; }
+   }
+   property const char * version
+   {
+      set { }
+      get { return "0.1"; }
+      isset { return true; }
+   }
+   Workspace workspace;
+}
+
+class AddedProjectInfo : struct
+{
+   class_no_expansion;
+public:
+   property const char * path
+   {
+      set { delete path; if(value && value[0]) path = CopyString(value); }
+      get { return path && path[0] ? path : null; }
+      isset { return path != null; }
+   }
+   property const char * activeConfig
+   {
+      set { delete activeConfig; if(value && value[0]) activeConfig = CopyString(value); }
+      get
+      {
+         char * config;
+         if(project && project.config)
+            config = project.config.name;
+         else
+            config = activeConfig;
+         return config && config[0] ? config : null;
+      }
+      isset { return property::activeConfig != null; }
+   }
+private:
+   char * path;
+   char * activeConfig;
+   Project project;
+
+   void Free()
+   {
+      delete path;
+      delete activeConfig;
+   }
+
+   void OnFree()
+   {
+      Free();
+      class::OnFree();
+   }
+
+   ~AddedProjectInfo()
+   {
+      Free();
+   }
+}
+
+class Workspace : struct
 {
+   class_no_expansion;
 public:
+   property const char * name
+   {
+      set { delete name; if(value && value[0]) name = CopyString(value); }
+      get { return name && name[0] ? name : null; }
+      isset { return name != null; }
+   }
+   property const char * activeCompiler
+   {
+      set { delete activeCompiler; if(value && value[0]) activeCompiler = CopyString(value); }
+      get { return activeCompiler && activeCompiler[0] ? activeCompiler : null; }
+      isset { return activeCompiler != null; }
+   }
+   int bitDepth;
+   property const char * commandLineArgs
+   {
+      set { delete commandLineArgs; if(value && value[0]) commandLineArgs = CopyString(value); }
+      get { return commandLineArgs && commandLineArgs[0] ? commandLineArgs : null; }
+      isset { return commandLineArgs != null; }
+   }
+   property const char * debugDir
+   {
+      set { delete debugDir; if(value && value[0]) debugDir = CopyString(value); }
+      get { return debugDir && debugDir[0] ? debugDir : null; }
+      isset { return debugDir != null; }
+   }
+
+   property List<AddedProjectInfo> addedProjects { set { addedProjects = value; } get { return addedProjects; } isset { return addedProjects && addedProjects.count; } }
+   property List<String> sourceDirs { set { sourceDirs = value; } get { return sourceDirs; } isset { return sourceDirs && sourceDirs.count; } }
+   property Array<NamedString> environmentVars { set { environmentVars = value; } get { return environmentVars; } isset { return environmentVars && environmentVars.count; } }
+   property List<Breakpoint> breakpoints { set { breakpoints = value; } get { return breakpoints; } isset { return breakpoints && breakpoints.count; } }
+   property List<Watch> watches { set { watches = value; } get { return watches; } isset { return watches && watches.count; } }
+   property List<OpenedFileInfo> openedFiles { set { openedFiles = value; } get { return openedFiles; } isset { return openedFiles && openedFiles.count; } }
+   property RecentPaths recentFiles { set { recentFiles = value; } get { return recentFiles; } isset { return recentFiles && recentFiles.count; } }
+
+   bool useValgrind;
+   ValgrindLeakCheck vgLeakCheck;
+   bool vgTrackOrigins;
+   int vgRedzoneSize;
+
+private:
    char * name;
-   char * workspaceFile;
-   char * workspaceDir;
+   char * activeCompiler;
    char * commandLineArgs;
-   property char * commandLineArgs { set { delete commandLineArgs; if(value) commandLineArgs = CopyString(value); } }
    char * debugDir;
-   property char * debugDir { set { delete debugDir; if(value) debugDir = CopyString(value); } }
 
-   int bpCount;
+   List<AddedProjectInfo> addedProjects;
+   List<String> sourceDirs;
+   Array<NamedString> environmentVars;
+   List<Breakpoint> breakpoints;
+   List<Watch> watches;
+   List<OpenedFileInfo> openedFiles;
+   RecentPaths recentFiles;
 
-   property char * compiler
-   {
-      set { delete compiler; if(value && value[0]) compiler = CopyString(value); }
-      get { return compiler && compiler[0] ? compiler : null; }
-   }
+   char * workspaceFile;
+   char * workspaceDir;
 
-   List<String> sourceDirs { };
-   Array<NamedString> environmentVars { };
-   List<Breakpoint> breakpoints { };
-   List<Watch> watches { };
-   List<OpenedFileInfo> openedFiles { };
-   List<Project> projects { };
+   int bpCount;
 
+   List<Project> projects { };
    //Project project;
 
    bool modified;
    bool holdTracking;
 
+   vgRedzoneSize = -1;
+   vgLeakCheck = summary;
+
    Timer timer
    {
-      userData = this, delay = 2.5;
+      userData = this, delay = 1.0;
       bool DelayExpired()
       {
-         if(modified)
+         static bool skip = true;
+         if(skip)
+            skip = false;
+         else if(modified)
             Save();
+
+         if(ide.debugStart)
+         {
+            ide.MenuDebugStart(ide.debugStartResumeItem, 0);
+            ide.debugStart = false;
+         }
          return true;
       }
    };
 
-   property char * workspaceFile
+   void AddProject(Project project, AddedProjectInfo addedProject)
+   {
+      if(addedProject)
+      {
+         ProjectConfig activeConfig = project.GetConfig(addedProject.activeConfig);
+         if(activeConfig)
+            project.config = activeConfig;
+         addedProject.project = project;
+      }
+      else
+      {
+         char location[MAX_LOCATION];
+         GetRelativePathForProject(location, project);
+         if(!addedProjects)
+            addedProjects = { };
+         addedProjects.Add(AddedProjectInfo { path = location, project = project });
+      }
+      projects.Add(project);
+   }
+
+   property const char * workspaceFile
    {
       set
       {
@@ -134,7 +294,7 @@ public:
       get { return workspaceFile; }
    }
 
-   property char * projectDir
+   property const char * projectDir
    {
       get
       {
@@ -159,7 +319,7 @@ public:
          if(project)
          {
             projectDir = CopyString(project.topNode.path);
-            
+
             if(!project.config && activeConfig && activeConfig[0])
             {
                ProjectConfig cfg;
@@ -175,159 +335,24 @@ public:
       get { return project; }
    }*/
 
-private:
-   String compiler;
-   int bitDepth;
-
 public:
    void Save()
    {
-      bool bkpts = false;
-      File file;
-      
-      file = FileOpen(workspaceFile, write);
-      if(file)
+      if(workspaceFile && workspaceFile[0])
       {
-         /*
-         for(bp : breakpoints)
-         {
-            if(bp.type == user)
-            {
-               if(bp.enabled)
-                  file.Printf("Breakpoint=1,%d,%s,%s\n", bp.line, bp.absoluteFilePath, bp.relativeFilePath);
-               else
-                  file.Printf("Breakpoint=0,%d,%s,%s\n", bp.line, bp.absoluteFilePath, bp.relativeFilePath);
-            }
-         }
-         
-         for(wh : watches)
-            file.Printf("Watch=%s\n", wh.expression);
-         
-         for(dir : sourceDirs)
-            file.Printf("SourceDir=%s\n", dir);
-
-         if(debugDir && debugDir[0])
-            file.Printf("DebugDir=%s\n", debugDir);
-         
-         if(commandLineArgs && commandLineArgs[0])
-            file.Printf("CommandLineArgs=%s\n", commandLineArgs);
-         */
-
-         /*
-         char indentation[128*3];
-         char path[MAX_LOCATION];
-         */
-         file.Printf("\nECERE Workspace File\n");
-         file.Printf("\nVersion 0.02\n");
-         file.Printf("\nWorkspace\n");
-         file.Printf("\n   Active Compiler = %s\n", compiler ? compiler : defaultCompilerName);
-         file.Printf("\n   Active Bit Depth = %d\n", bitDepth);
-         
-         if(projects.first)
-         {
-            file.Printf("\n   Projects\n\n");
-            for(prj : projects)
-            {
-               char location[MAX_LOCATION];
-               MakePathRelative(prj.topNode.path, workspaceDir, location);
-               MakeSlashPath(location);
-               PathCatSlash(location, prj.topNode.name);
-               //strcat(location, ".epj");
-
-               file.Printf("    %s %s\n", "-", location);
-               
-               if(prj.config)
-                  file.Printf("         Active Configuration = %s\n", prj.config.name);
-               for(cfg : prj.configurations)
-               {
-                  if(cfg.compilingModified)
-                     file.Printf("         Modified Compiler Config = %s\n", cfg.name);
-                  else if(cfg.linkingModified)
-                     file.Printf("         Modified Linker Config = %s\n", cfg.name);
-               }
-            }
-         }
-         
-         file.Printf("\n   Execution Data\n");
-         if(commandLineArgs && commandLineArgs[0])
-         {
-            file.Printf("\n      Command Line Arguments = ");
-            file.Puts(commandLineArgs);
-            file.Printf("\n");
-         }
-
-         if(environmentVars.count)
-         {
-            file.Printf("\n      Environment Variables\n\n");
-            for(v : environmentVars)
-            {
-               file.Printf("       ~ ");
-               file.Puts(v.name);
-               file.Printf(" = ");
-               file.Puts(v.string);
-               file.Printf("\n");
-            }
-         }
-
-         file.Printf("\n   Debugger Data\n");
-         // This really belonged in Execution Data...
-         if(debugDir && debugDir[0])
-            file.Printf("\n      Debug Working Directory = %s\n", debugDir);
-         if(sourceDirs.count)
-         {
-            file.Printf("\n      Source Directories\n");
-            for(dir : sourceDirs)
-               file.Printf("       = %s\n", dir);
-         }
-
-         for(bp : breakpoints)
-         {
-            if(bp.type == user)
-            {
-               if(!bkpts)
-               {
-                  bkpts = true;
-                  file.Printf("\n   Breakpoints\n\n");
-               }
-               bp.Save(file);
-            }
-         }
-
-         if(watches.count)
-         {
-            file.Printf("\n   Watches\n\n");
-            for(wh : watches)
-               wh.Save(file);
-         }
-
-         if(openedFiles.count)
+         File file = FileOpen(workspaceFile, write);
+         if(file)
          {
-            file.Printf("\n   Opened Files\n\n");
-            for(ofi : openedFiles)
-            {
-               char * location;
-               char chr[2];
-               char relativePath[MAX_LOCATION];
-               if(IsPathInsideOf(ofi.path, workspaceDir))
-               {
-                  MakePathRelative(ofi.path, workspaceDir, relativePath);
-                  MakeSlashPath(relativePath);
-                  location = relativePath;
-               }
-               else
-                  location = ofi.path;
-               strcpy(chr, "=");
-
-               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);
-            }
+            WorkspaceFile wf { workspace = this };
+            WriteECONObject(file, class(WorkspaceFile), wf, 0);
+            delete wf;
+            delete file;
+            modified = false;
          }
-
-         modified = false;
-         delete file;
       }
    }
 
-   char * GetAbsolutePathFromRelative(char * relative)
+   char * CopyAbsolutePathFromRelative(const char * relative)
    {
       char name[MAX_LOCATION];
       char absolute[MAX_LOCATION];
@@ -337,7 +362,7 @@ public:
       GetLastDirectory(relative, name);
       for(p : projects)
       {
-         if(node = p.topNode.Find(name, false))
+         if((node = p.topNode.Find(name, false)))
          {
             prj = p;
             break;
@@ -345,7 +370,7 @@ public:
       }
       if(prj)
       {
-         node.GetFullFilePath(absolute);
+         node.GetFullFilePath(absolute, true);
          return CopyString(absolute);
       }
 
@@ -377,11 +402,11 @@ public:
                return CopyString(absolute);
          }
       }
-      
+
       return null;
    }
 
-   char * GetPathWorkspaceRelativeOrAbsolute(char * path)
+   char * CopyUnixPathWorkspaceRelativeOrAbsolute(const char * path)
    {
       if(IsPathInsideOf(path, workspaceDir))
       {
@@ -393,27 +418,147 @@ public:
          return CopyUnixPath(path);
    }
 
-   OpenedFileInfo UpdateOpenedFileInfo(char * fileName, OpenedFileState state)
+   char * MakeRelativePath(char * buffer, const char * path)
+   {
+      char * result = null;
+      if(buffer && path)
+      {
+         MakePathRelative(path, workspaceDir, buffer);
+         MakeSlashPath(buffer);
+         result = buffer;
+      }
+      return result;
+   }
+
+   char * GetRelativePathForProject(char * buffer, Project project)
+   {
+      char * result = null;
+      if(buffer && project && project.topNode.path)
+      {
+         MakePathRelative(project.topNode.path, workspaceDir, buffer);
+         MakeSlashPath(buffer);
+         PathCatSlash(buffer, project.topNode.name);
+         result = buffer;
+      }
+      return result;
+   }
+
+   Array<ProjectNode> GetAllProjectNodes(const char *fullPath, bool skipExcluded)
+   {
+      Array<ProjectNode> nodes = null;
+      for(project : projects)
+      {
+         ProjectNode node;
+         if((node = project.topNode.FindByFullPath(fullPath, false)))
+         {
+            if(!skipExcluded || !node.GetIsExcluded(project.config))
+            {
+               if(!nodes) nodes = { };
+               nodes.Add(node);
+            }
+         }
+      }
+      return nodes;
+   }
+
+   Project GetFileOwner(const char * absolutePath, const char * objectFileExt)
    {
-      char filePath[MAX_LOCATION];
-      OpenedFileInfo ofi = null;
-      GetSlashPathBuffer(filePath, fileName);
-      for(item : openedFiles)
+      Project owner = null;
+      for(prj : projects)
       {
-         if(!fstrcmp(item.path, filePath))
+         if(prj.topNode.FindByFullPath(absolutePath, false))
          {
-            ofi = item;
+            owner = prj;
             break;
          }
       }
+      if(!owner)
+         GetObjectFileNode(absolutePath, &owner, null, objectFileExt);
+      return owner;
+   }
+
+   void GetRelativePath(const char * absolutePath, char * relativePath, Project * owner, const char * objectFileExt)
+   {
+      Project prj = GetFileOwner(absolutePath, objectFileExt);
+      if(owner)
+         *owner = prj;
+      if(!prj)
+         prj = projects.firstIterator.data;
+      if(prj)
+      {
+         MakePathRelative(absolutePath, prj.topNode.path, relativePath);
+         MakeSlashPath(relativePath);
+      }
+      else
+         relativePath[0] = '\0';
+   }
+
+   ProjectNode GetObjectFileNode(const char * filePath, Project * project, char * fullPath, const char * objectFileExt)
+   {
+      ProjectNode node = null;
+      char ext[MAX_EXTENSION];
+      GetExtension(filePath, ext);
+      if(ext[0])
+      {
+         IntermediateFileType type = IntermediateFileType::FromExtension(ext);
+         if(type)
+         {
+            char fileName[MAX_FILENAME];
+            GetLastDirectory(filePath, fileName);
+            if(fileName[0])
+            {
+               DotMain dotMain = DotMain::FromFileName(fileName);
+               for(prj : ide.workspace.projects)
+               {
+                  if((node = prj.FindNodeByObjectFileName(fileName, type, dotMain, null, objectFileExt)))
+                  {
+                     if(project)
+                        *project = prj;
+                     if(fullPath)
+                     {
+                        const char * cfgName = prj.config ? prj.config.name : "";
+                        char name[MAX_FILENAME];
+                        CompilerConfig compiler = ideSettings.GetCompilerConfig(prj.lastBuildCompilerName);
+                        DirExpression objDir = prj.GetObjDir(compiler, prj.config, bitDepth);
+                        strcpy(fullPath, prj.topNode.path);
+                        PathCatSlash(fullPath, objDir.dir);
+                        node.GetObjectFileName(name, prj.configsNameCollisions[cfgName], type, dotMain, objectFileExt);
+                        PathCatSlash(fullPath, name);
+                        delete objDir;
+                        delete compiler;
+                     }
+                     break;
+                  }
+               }
+            }
+         }
+      }
+      return node;
+   }
+
+   OpenedFileInfo UpdateOpenedFileInfo(const char * fileName, OpenedFileState state, bool fileOpen)
+   {
+      bool insert = false;
+      char absolutePath[MAX_LOCATION];
+      char relativePath[MAX_LOCATION];
+      OpenedFileInfo ofi;
+      GetSlashPathBuffer(absolutePath, fileName);
+      MakeRelativePath(relativePath, fileName);
+      ofi = FindOpenedFileInfo(relativePath, absolutePath);
+      if(fileOpen && ofi)
+      {
+         openedFiles.Remove(openedFiles.Find(ofi));
+         insert = true;
+      }
       if(state)
       {
          if(!ofi)
          {
-            ofi = OpenedFileInfo { path = CopyString(filePath) };
-            openedFiles.Add(ofi);
+            ofi = OpenedFileInfo { path = CopyString(relativePath) };
+            insert = true;
          }
          ofi.state = state;
+         ofi.modified = GetLocalTimeStamp();
          if(!holdTracking)
             modified = true;
       }
@@ -425,31 +570,117 @@ public:
          if(!holdTracking)
             modified = true;
       }
+      if(insert)
+         openedFiles.Insert(null, ofi);
       return ofi;
    }
 
-   void UpdateSourceDirsArray(Array<String> dirs)
+   void LoadOpenedFileInfo(const char * path, OpenedFileState state, int lineNumber, int position, Point scroll, TimeStamp modified, Array<String> openedFilesNotFound)
+   {
+      char absolutePath[MAX_LOCATION];
+      char relativePath[MAX_LOCATION];
+      TimeStamp stamp = modified;
+      bool exists;
+      strcpy(absolutePath, workspaceDir);
+      PathCatSlash(absolutePath, path);
+      MakeRelativePath(relativePath, absolutePath);
+      if(!(exists = FileExists(absolutePath)))
+         stamp -= 60*60*24*20; // Days { 20 };
+      if(stamp > GetLocalTimeStamp() - 60*60*24*384) // Days { 384 });
+      {
+         if(state == closed || exists)
+         {
+            OpenedFileInfo ofi = FindOpenedFileInfo(relativePath, absolutePath);
+            if(!ofi)
+               openedFiles.Add(OpenedFileInfo { CopyString(relativePath), state, lineNumber, position, scroll, stamp });
+            // else silently drop duplicates if they should ever occur;
+         }
+         else
+            openedFilesNotFound.Add(CopyString(absolutePath));
+      }
+      // else silently discarding old or broken OpenedFileInfo entries;
+   }
+
+   void OpenPreviouslyOpenedFiles(bool noParsing)
    {
-      byte * tokens[256];
-      int c, numTokens;
+      Array<String> filePaths {};
+      holdTracking = true;
+      for(ofi : openedFiles; ofi.state != closed)
+         filePaths.Add(CopyString(ofi.path));
+      for(path : filePaths)
+      {
+         Window file;
+         char absolutePath[MAX_LOCATION];
+         strcpy(absolutePath, workspaceDir);
+         PathCatSlash(absolutePath, path);
+         file = ide.OpenFile(absolutePath, false, true, null, no, normal, noParsing);
+         if(file)
+         {
+            for(prj : projects)
+            {
+               ProjectNode node = prj.topNode.FindByFullPath(absolutePath, true);
+               if(node)
+                  node.EnsureVisible();
+            }
+         }
+      }
+      holdTracking = false;
+      filePaths.Free();
+      delete filePaths;
+   }
+
+   OpenedFileInfo FindOpenedFileInfo(const char * relativePath, const char * absolutePath)
+   {
+      OpenedFileInfo result = null;
+      for(e : openedFiles)
+      {
+         bool switchToRelative;
+         if((switchToRelative = !fstrcmp(absolutePath, e.path)) || !fstrcmp(relativePath, e.path))
+         {
+            result = e;
+            if(switchToRelative)
+            {
+               delete result.path;
+               result.path = CopyString(relativePath);
+            }
+            break;
+         }
+      }
+      return result;
+   }
 
+   void RestorePreviouslyOpenedFileState(CodeEditor editor)
+   {
+      if((editor.openedFileInfo = UpdateOpenedFileInfo(editor.fileName, opened, true)))
+         editor.openedFileInfo.SetCodeEditorState(editor);
+   }
+
+   void UpdateSourceDirsArray(Array<String> dirs)
+   {
       sourceDirs.Free();
-      
+
       for(s : dirs)
          sourceDirs.Add(CopyString(s));
-      
-      DropInvalidBreakpoints();
+
+      DropInvalidBreakpoints(null);
 
       delete dirs;
    }
-   
+
    void RemoveProject(Project project)
    {
-      Iterator<Project> it { projects };
-      if(it.Find(project))
-         it.Remove();
+      Iterator<AddedProjectInfo> it { addedProjects };
+      while(it.Next())
+      {
+         if(it.data.project == project)
+         {
+            addedProjects.Remove(it.pointer);
+            break;
+         }
+      }
+      projects.TakeOut(project);
 
-      DropInvalidBreakpoints();
+      DropInvalidBreakpoints(project);
       modified = true;
       ide.findInFilesDialog.RemoveProjectItem(project);
       ide.UpdateToolBarActiveConfigs(false);
@@ -458,7 +689,32 @@ public:
       delete project;
    }
 
-   bool FindPath(ProjectNode node, char * path)
+   void SelectActiveConfig(const char * configName)
+   {
+      bool change = false;
+      for(prj : ide.workspace.projects)
+      {
+         for(cfg : prj.configurations)
+         {
+            if(cfg.name && !strcmp(cfg.name, configName))
+            {
+               prj.config = cfg;
+               change = true;
+               break;
+            }
+         }
+      }
+      if(change)
+      {
+         modified = true;
+         ide.UpdateToolBarActiveConfigs(true);
+         ide.projectView.Update(null);
+         Save();
+      }
+      ide.AdjustDebugMenus();
+   }
+
+   bool FindPath(ProjectNode node, const char * path)
    {
       if(node.type == file)
       {
@@ -482,18 +738,24 @@ public:
       return false;
    }
 
-   void ChangeBreakpoint(DataRow row, char * location)
+   void ChangeBreakpoint(DataRow row, const char * location)
    {
-      Breakpoint bp = (Breakpoint)row.tag;
+      Breakpoint bp = (Breakpoint)(intptr)row.tag;
       if(bp)
       {
-         char * currentLoc = bp.LocationToString();
+         char * currentLoc = bp.CopyUserLocationString();
          if(strcmp(location, currentLoc))
          {
-            // todo, parse location
-            //  if good, make changes to breakpoint, according to execution state delete and place breakpoint
-            ide.breakpointsView.UpdateBreakpoint(row); // this is the else
-            //Save();
+            char * newLoc;
+            bp.location = CopyString(location);
+            bp.ParseLocation();
+            delete bp.location;
+            newLoc = bp.CopyUserLocationString();
+            if(strcmp(newLoc, currentLoc))
+            {
+               ide.breakpointsView.UpdateBreakpoint(row);
+               Save();
+            }
          }
          delete currentLoc;
       }
@@ -514,7 +776,7 @@ public:
 
    void ChangeBreakpointIgnore(DataRow row, int ignore)
    {
-      Breakpoint bp = (Breakpoint)row.tag;
+      Breakpoint bp = (Breakpoint)(intptr)row.tag;
       if(bp)
       {
          bp.ignore = ignore;
@@ -524,7 +786,7 @@ public:
 
    void ChangeBreakpointLevel(DataRow row, int level)
    {
-      Breakpoint bp = (Breakpoint)row.tag;
+      Breakpoint bp = (Breakpoint)(intptr)row.tag;
       if(bp)
       {
          bp.level = level;
@@ -532,9 +794,9 @@ public:
       }
    }
 
-   void ChangeBreakpointCondition(DataRow row, char * condition)
+   void ChangeBreakpointCondition(DataRow row, const char * condition)
    {
-      Breakpoint bp = (Breakpoint)row.tag;
+      Breakpoint bp = (Breakpoint)(intptr)row.tag;
       if(bp && !(!bp.condition && !(condition && condition[0])))
       {
          if(!bp.condition)
@@ -573,11 +835,11 @@ public:
          Window document;
          for(document = ide.firstChild; document; document = document.next)
          {
-            char * fileName = document.fileName;
+            const char * fileName = document.fileName;
             if(document.isDocument && fileName && document.created)
             {
                char winFilePath[MAX_LOCATION];
-               char * slashPath = GetSlashPathBuffer(winFilePath, fileName);
+               const char * slashPath = GetSlashPathBuffer(winFilePath, fileName);
 
                if(!fstrcmp(slashPath, bp.absoluteFilePath))
                {
@@ -594,72 +856,114 @@ public:
       delete bp;
    }
 
-   void DropInvalidBreakpoints()
+   void ParseLoadedBreakpoints()
    {
-      Link bpLink, next;
-      for(bpLink = breakpoints.first; bpLink; bpLink = next)
+      for(bp : breakpoints; bp.location)
       {
-         Breakpoint bp = (Breakpoint)bpLink.data;
-         next = bpLink.next;
+         bp.ParseLocation();
+         delete bp.location;
+         ide.breakpointsView.UpdateBreakpoint(bp.row);
+      }
+   }
 
-         if(bp.type == user)
+   void DropInvalidBreakpoints(Project removedProject) // think about not dropping BPs that are past end of file but simply disable them
+   {                                                   // why? when using a scm/vcs you might be alternating between two version of a file
+      if(breakpoints)                                  // and anoyingly keep loosing breakpoints in the version of the file you are working on
+      {
+         Link bpLink, next;
+         for(bpLink = breakpoints.first; bpLink; bpLink = next)
          {
-            Project project = null;
-            for(p : projects)
+            Breakpoint bp = (Breakpoint)(intptr)bpLink.data;
+            next = bpLink.next;
+            if(removedProject)
             {
-               if(FindPath(p.topNode, bp.absoluteFilePath))
+               if(bp.project == removedProject)
                {
-                  project = p;
-                  break;
-               }
-               // Handle symbol loader modules:
-               {
-                  char moduleName[MAX_FILENAME];
-                  char * sl;
-                  GetLastDirectory(bp.absoluteFilePath, moduleName);
-                  // Tweak for automatically resolving symbol loader modules
-                  sl = strstr(moduleName, ".main.ec");
-                  if(sl && (*sl = 0, !strcmpi(moduleName, p.name)))
-                  {
-                     project = p;
-                     break;
-                  }
+                  ide.breakpointsView.RemoveBreakpoint(bp);
+                  RemoveBreakpoint(bp);
                }
             }
-            if(!project)
+            else
             {
-               bool found = false;
-               for(dir : sourceDirs)
+               Project project = bp.project;
+               if(!project)
                {
-                  if(IsPathInsideOf(bp.absoluteFilePath, dir))
+                  for(p : projects)
                   {
-                     found = true;
-                     break;
+                     if(FindPath(p.topNode, bp.absoluteFilePath))
+                     {
+                        project = p;
+                        break;
+                     }
+                     // Handle symbol loader modules:
+                     {
+                        char moduleName[MAX_FILENAME];
+                        char * sl;
+                        GetLastDirectory(bp.absoluteFilePath, moduleName);
+                        // Tweak for automatically resolving symbol loader modules
+                        sl = strstr(moduleName, ".main.ec");
+                        if(sl && (*sl = 0, !strcmpi(moduleName, p.name)))
+                        {
+                           project = p;
+                           break;
+                        }
+                     }
                   }
                }
-               if(!found)
+               if(!project)
                {
-                  ide.breakpointsView.RemoveBreakpoint(bp);
-                  RemoveBreakpoint(bp);
+                  bool found = false;
+                  for(dir : sourceDirs)
+                  {
+                     if(IsPathInsideOf(bp.absoluteFilePath, dir))
+                     {
+                        found = true;
+                        break;
+                     }
+                  }
+                  if(!found)
+                  {
+                     ide.breakpointsView.RemoveBreakpoint(bp);
+                     RemoveBreakpoint(bp);
+                  }
                }
             }
          }
+         ide.breakpointsView.Update(null);
       }
-      ide.breakpointsView.Update(null);
+   }
+
+   void Init()
+   {
+      if(!addedProjects) addedProjects = { };
+      if(!sourceDirs) sourceDirs = { };
+      if(!environmentVars) environmentVars = { };
+      if(!breakpoints) breakpoints = { };
+      if(!watches) watches = { };
+      if(!openedFiles) openedFiles = { };
+      if(!recentFiles) recentFiles = { };
    }
 
    void Free()
    {
+      delete name;
       delete workspaceFile;
       delete workspaceDir;
       delete commandLineArgs;
       delete debugDir;
-      
+      delete activeCompiler;
+
       //project = null;
 
+      if(addedProjects) { addedProjects.Free(); delete addedProjects; }
+      if(sourceDirs) { sourceDirs.Free(); delete sourceDirs; }
+      if(environmentVars) { environmentVars.Free(); delete environmentVars; }
+      if(breakpoints) { breakpoints.Free(); delete breakpoints; }
+      if(watches) { watches.Free(); delete watches; }
+      if(openedFiles) { openedFiles.Free(); delete openedFiles; }
+      if(recentFiles) { recentFiles.Free(); delete recentFiles; }
+
       projects.Free();
-      breakpoints.Free();
-      watches.Free();
    }
 
    Workspace()
@@ -672,7 +976,7 @@ public:
       ide.breakpointsView.Clear();
 
       property::debugDir = "";
-      
+
       SetSourceDirs(sourceDirs);
    }
 
@@ -681,17 +985,222 @@ public:
       Save();
       timer.Stop();
 
-      sourceDirs.Free();
-      environmentVars.Free();
       SetSourceDirs(null);
       Free();
-      openedFiles.Free();
-      delete compiler;
    }
+}
+
+Workspace LoadWorkspace(const char * filePath, const char * fromProjectFile)
+{
+   File f;
+   Workspace workspace = null;
 
+   f = FileOpen(filePath, read);
+   if(f)
+   {
+      Array<String> openedFilesNotFound { };
+      JSONResult result;
+      WorkspaceFile wf = null;
+      {
+         ECONParser parser { f = f };
+         f.Seek(0, start);
+         result = parser.GetObject(class(WorkspaceFile), &wf);
+         if(result != success)
+            delete wf;
+         delete parser;
+      }
+      if(!wf)
+         workspace = LoadLegacyWorkspace(filePath, openedFilesNotFound);
+      else if(wf.workspace)
+      {
+         workspace = wf.workspace;
+         //incref workspace;
+         workspace.workspaceFile = filePath;
+         {
+            char absolutePath[MAX_LOCATION];
+            bool done = false;
+            bool ahead = false;
+            Iterator<AddedProjectInfo> it { workspace.addedProjects };
+            while(!done && (ahead || !(done = !it.Next())))
+            {
+               // TODO: implement some type of time based pruning of "dea" added projects instead of just dropping them on the spot
+               //         TimeStamp modified; modified = GetLocalTimeStamp();
+               //               TimeStamp stamp = modified; if(stamp > GetLocalTimeStamp() - 60*60*24*20) // Days { 20 });
+               AddedProjectInfo addedPrj = it.data;
+               strcpy(absolutePath, workspace.workspaceDir);
+               PathCatSlash(absolutePath, addedPrj.path);
+               if(FileExists(absolutePath))
+               {
+                  Project loadedProject = LoadProject(absolutePath, null);
+                  if(loadedProject)
+                  {
+                     workspace.AddProject(loadedProject, addedPrj);
+                     loadedProject.StartMonitoring();
+                  }
+                  else if(workspace.projects.count == 0)
+                  {
+                     delete workspace;
+                     break;
+                  }
+                  else
+                  {
+                     // TODO: (#524) show message or something when added project fails to load;
+                  }
+                  ahead = false;
+               }
+               else
+               {
+                  IteratorPointer notFound = it.pointer;
+                  done = !it.Next();
+                  workspace.addedProjects.Delete(notFound);
+                  ahead = true;
+               }
+            }
+            if(workspace)
+            {
+               for(bp : workspace.breakpoints)
+               {
+                  char * path = workspace.CopyAbsolutePathFromRelative(bp.relativeFilePath);
+                  bp.type = user;
+                  if(path)
+                  {
+                     bp.absoluteFilePath = path;
+                     delete path;
+                  }
+                  else
+                     bp.absoluteFilePath = "";
+               }
+               if(workspace.openedFiles && workspace.openedFiles.count)
+               {
+                  List<OpenedFileInfo> openedFiles = workspace.openedFiles;
+                  workspace.openedFiles = { };
+                  for(of : openedFiles)
+                  {
+                     workspace.LoadOpenedFileInfo(of.path, of.state, of.lineNumber, of.position, of.scroll, of.modified, openedFilesNotFound);
+                  }
+                  openedFiles.Free();
+                  delete openedFiles;
+               }
+            }
+         }
+      }
+
+      if(workspace)
+      {
+         if(!workspace.recentFiles || !workspace.recentFiles.count)
+         {
+            int c;
+            if(!workspace.recentFiles) workspace.recentFiles = { };
+            if(workspace.openedFiles && workspace.openedFiles.count)
+            {
+               for(c = workspace.openedFiles.count - 1; c >= 0; c--)
+               {
+                 char path[MAX_LOCATION];
+                 strcpy(path, workspace.workspaceDir);
+                 PathCatSlash(path, workspace.openedFiles[c].path);
+                 workspace.recentFiles.addRecent(CopyString(path));
+               }
+            }
+         }
+         workspace.Init();
+         if(!workspace.projects.first)
+         {
+            Project project;
+            if(fromProjectFile)
+               project = LoadProject(fromProjectFile /*projectFilePath*/, null);
+            else
+            {
+               char projectFilePath[MAX_LOCATION];
+               strcpy(projectFilePath, workspace.workspaceFile);
+               ChangeExtension(projectFilePath, ProjectExtension, projectFilePath);
+               project = LoadProject(projectFilePath, null);
+            }
+            if(project)
+            {
+               project.StartMonitoring();
+               workspace.AddProject(project, null);
+               workspace.name = CopyString(project.name);
+            }
+            else
+            {
+               MessageBox { type = ok, master = ide, contents = $"Workspace load file failed", text = $"Workspace Load File Error" }.Modal();
+               delete workspace;
+               return null;
+            }
+         }
+
+         if(openedFilesNotFound.count)
+         {
+            int c = 0;
+            char s[2] = "";
+            String files = new char[MAX_LOCATION * 16];
+            char title[512];
+            String msg = new char[MAX_LOCATION * 16 + 2048];
+            strcpy(files,"\n");
+
+            if(openedFilesNotFound.count > 1)
+               strcpy(s, "s");
+
+            for(item : openedFilesNotFound)
+            {
+               c++;
+               if(c == 16)
+               {
+                  strcat(files, "\n...");
+                  break;
+               }
+               strcat(files, "\n");
+               strcat(files, item);
+            }
+
+            sprintf(title, $"File%s not found", s);
+            sprintf(msg, $"The following file%s could not be re-opened.%s", s, files);
+
+            MessageBox { type = ok, master = ide, contents = msg, text = title }.Modal();
+
+            delete files;
+            delete msg;
+         }
+      }
+      openedFilesNotFound.Free();
+      delete openedFilesNotFound;
+      delete wf;
+      delete f;
+   }
+   else if(fromProjectFile)
+   {
+      //MessageBox { type = Ok, master = ide, contents = "Worspace load file failed", text = "Worspace Load File Error" }.Modal();
+
+      //char projectFile[MAX_LOCATION];
+      Project newProject;
+
+      //strcpy(projectFile, filePath);
+      //ChangeExtension(projectFile, ProjectExtension, projectFile);
+      newProject = LoadProject(fromProjectFile /*projectFile*/, null);
+
+      if(newProject)
+      {
+         newProject.StartMonitoring();
+         workspace = Workspace { property::workspaceFile = filePath };
+
+         workspace.Init();
+         workspace.AddProject(newProject, null);
+         workspace.Save();
+      }
+   }
+
+   if(workspace)
+   {
+      ide.ChangeFileDialogsDirectory(workspace.workspaceDir, false);
+
+      if(!workspace.activeCompiler || !workspace.activeCompiler[0])
+         workspace.activeCompiler = defaultCompilerName;
+   }
+
+   return workspace;
 }
 
-Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
+Workspace LoadLegacyWorkspace(const char * filePath, Array<String> openedFilesNotFound)
 {
    File file;
    Workspace workspace = null;
@@ -699,23 +1208,22 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
    file = FileOpen(filePath, read);
    if(file)
    {
-      OldList openedFilesNotFound { };
       double version = 0;
       char section[128];
       char subSection[128];
 
-      workspace = Workspace { compiler = ideSettings.defaultCompiler, workspaceFile = filePath };
+      workspace = Workspace { activeCompiler = ideSettings.defaultCompiler, property::workspaceFile = filePath };
+      workspace.Init();
 
       file.Seek(0, start);
       while(!file.Eof())
       {
          char buffer[65536];
          char * equal;
-         int len;
-         
+
          Watch wh;
-         Breakpoint bp;
-         
+         Breakpoint bp = null;
+
          file.GetLine(buffer, 65536 - 1);
          TrimLSpaces(buffer, buffer);
          TrimRSpaces(buffer, buffer);
@@ -729,6 +1237,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                if(!strcmpi(section, "Debugger Data") && !strcmpi(subSection, "Watches"))
                {
                   wh = Watch { };
+                  if(!workspace.watches) workspace.watches = { };
                   workspace.watches.Add(wh);
                   wh.expression = CopyString(equal);
                }
@@ -766,7 +1275,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                   char * strLevel = null;
                   char * strLine = null;
                   char * strFile = null;
-                  
+
                   strEnabled = equal;
                   if(strEnabled && strEnabled[0])
                   {
@@ -792,7 +1301,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                      strFile[0] = '\0';
                      strFile++;
                   }
-                  if(strEnabled && strEnabled[0] && strIgnore && strIgnore[0] && 
+                  if(strEnabled && strEnabled[0] && strIgnore && strIgnore[0] &&
                         strLevel && strLevel[0] && strLine && strLine[0] && strFile && strFile[0])
                   {
                      bool enabled;
@@ -817,11 +1326,10 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                      line = atoi(strLine);
 
                      bp = { type = user, enabled = enabled, ignore = ignore, level = level, line = line };
+                     if(!workspace.breakpoints)
+                        workspace.breakpoints = { };
                      workspace.breakpoints.Add(bp);
-                     bp.relativeFilePath = CopyString(strFile);
-                     bp.absoluteFilePath = workspace.GetAbsolutePathFromRelative(strFile);
-                     if(!bp.absoluteFilePath)
-                        bp.absoluteFilePath = CopyString("");
+                     bp.location = CopyString(strFile);
                   }
                }
             }
@@ -838,8 +1346,6 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                   int lineNumber = 0;
                   int position = 0;
                   Point scroll { };
-                  char absolutePath[MAX_LOCATION];
-                  strcpy(absolutePath, workspace.workspaceDir);
                   if(version == 0.01)
                   {
                      char * comma = strchr(equal, ',');
@@ -894,12 +1400,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                         }
                      }
                   }
-                  PathCatSlash(absolutePath, equal);
-                     
-                  if(state == closed || FileExists(absolutePath))
-                     workspace.openedFiles.Add(OpenedFileInfo { path = CopyString(absolutePath), state = state, lineNumber = lineNumber, position = position, scroll = scroll });
-                  else
-                     openedFilesNotFound.Add(NamedItem { name = CopyString(equal) });
+                  workspace.LoadOpenedFileInfo(equal, state, lineNumber, position, scroll, GetLocalTimeStamp(), openedFilesNotFound);
                }
                else if(!strcmpi(section, "Projects"))
                {
@@ -910,7 +1411,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                   newProject = LoadProject(projectFilePath, null);
                   if(newProject)
                   {
-                     workspace.projects.Add(newProject);
+                     workspace.AddProject(newProject, null);
                      newProject.StartMonitoring();
                   }
                   else if(workspace.projects.count == 0)
@@ -920,8 +1421,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                   }
                   else
                   {
-                     // TODO: show message or something when added project fails to load
-                     // http://ecere.com/mantis/view.php?id=524
+                     // TODO: (#524) show message or something when added project fails to load;
                   }
                }
             }
@@ -947,7 +1447,10 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
             else if(!strcmpi(buffer, "Environment Variables"))
                strcpy(subSection, buffer);
             else if(!strcmpi(buffer, "Opened Files"))
+            {
                strcpy(section, buffer);
+               if(!workspace.openedFiles) workspace.openedFiles = { };
+            }
             else if(!strcmpi(buffer, ""))      // | These two lines were commented out
                strcpy(subSection, buffer);     // | Do they serve a purpose? They were there for copy paste when adding a new subsection
             else
@@ -965,9 +1468,9 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                      {
                         CompilerConfig compiler = ideSettings.GetCompilerConfig(equal);
                         if(!compiler)
-                           workspace.compiler = defaultCompilerName;
+                           workspace.activeCompiler = defaultCompilerName;
                         else
-                           workspace.compiler = equal;
+                           workspace.activeCompiler = equal;
                         delete compiler;
                      }
                      if(!strcmpi(buffer, "Active Bit Depth"))
@@ -1079,10 +1582,7 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
                                           bp = { type = user, enabled = enabled, level = -1 };
                                           workspace.breakpoints.Add(bp);
                                           bp.line = atoi(lineNum);
-                                          bp.relativeFilePath = CopyString(relPath);
-                                          bp.absoluteFilePath = workspace.GetAbsolutePathFromRelative(relPath);
-                                          if(!bp.absoluteFilePath)
-                                             bp.absoluteFilePath = CopyString("");
+                                          bp.location = relPath;
                                        }
                                     }
                                  }
@@ -1109,103 +1609,16 @@ Workspace LoadWorkspace(char * filePath, char * fromProjectFile)
             }
          }
       }
-
       delete file;
-
-      if(workspace)
-      {
-         if(!workspace.projects.first)
-         {
-            Project project;
-            if(fromProjectFile)
-               project = LoadProject(fromProjectFile /*projectFilePath*/, null);
-            else
-            {
-               char projectFilePath[MAX_LOCATION];
-               strcpy(projectFilePath, workspace.workspaceFile);
-               ChangeExtension(projectFilePath, ProjectExtension, projectFilePath);
-               project = LoadProject(projectFilePath, null);
-            }
-            if(project)
-            {
-               project.StartMonitoring();
-               workspace.projects.Add(project);
-               workspace.name = CopyString(project.name);
-            }
-            else
-            {
-               MessageBox { type = ok, master = ide, contents = $"Workspace load file failed", text = $"Workspace Load File Error" }.Modal();
-               delete workspace;
-               return null;
-            }
-         }
-
-         if(openedFilesNotFound.first)
-         {
-            int c = 0;
-            char s[2] = "";
-            String files = new char[MAX_LOCATION * 16];
-            char title[512];
-            String msg = new char[MAX_LOCATION * 16 + 2048];
-            NamedItem item;
-            strcpy(files,"\n");
-
-            item = openedFilesNotFound.first;
-            if(item.next)
-               strcpy(s, "s");
-            
-            for(item = openedFilesNotFound.first; item; item = item.next)
-            {
-               c++;
-               if(c == 16)
-               {
-                  strcat(files, "\n...");
-                  break;
-               }
-               strcat(files, "\n");
-               strcat(files, item.name);
-            }
-
-            sprintf(title, $"File%s not found", s);
-            sprintf(msg, $"The following file%s could not be re-opened.%s", s, files);
-            
-            MessageBox { type = ok, master = ide, contents = msg, text = title }.Modal();
-
-            delete files;
-            delete msg;
-         }
-         openedFilesNotFound.Free(OldLink::Free);
-      }
-      else
-         openedFilesNotFound.Free(OldLink::Free);
-   }
-   else if(fromProjectFile)
-   {
-      //MessageBox { type = Ok, master = ide, contents = "Worspace load file failed", text = "Worspace Load File Error" }.Modal();
-      
-      char projectFile[MAX_LOCATION];
-      Project newProject;
-      
-      //strcpy(projectFile, filePath);
-      //ChangeExtension(projectFile, ProjectExtension, projectFile);
-      newProject = LoadProject(fromProjectFile /*projectFile*/, null);
-
-      if(newProject)
-      {
-         newProject.StartMonitoring();
-         workspace = Workspace { workspaceFile = filePath };
-
-         workspace.projects.Add(newProject);
-         workspace.Save();
-      }
-   }
-   
-   if(workspace)
-   {
-      ide.ChangeFileDialogsDirectory(workspace.workspaceDir, false);
-
-      if(!workspace.compiler || !workspace.compiler[0])
-         workspace.compiler = defaultCompilerName;
    }
    return workspace;
 }
+
+TimeStamp GetLocalTimeStamp()
+{
+   TimeStamp now;
+   DateTime time { };
+   time.GetLocalTime();
+   now = time;
+   return now;
+}