documentor: Alt-Left/Right shortcuts when edit pane is active
[sdk] / documentor / src / Documentor.ec
index a5e3d7e..c3e6212 100644 (file)
@@ -1,4 +1,5 @@
 import "ecere"
+#if !defined(EAR_TO_ECON_ECDOC)
 import "ec"
 import "HTMLView"
 import "IDESettings"
@@ -8,7 +9,7 @@ static Context globalContext { };
 static OldList defines { };
 static OldList imports { };
 static NameSpace globalData;
-static OldList excludedSymbols { offset = (uint)&((Symbol)0).left };
+static OldList excludedSymbols { offset = (uint)(uintptr)&((Symbol)0).left };
 static bool readOnly;
 
 define app = (GuiApplication)__thisModule.application;
@@ -20,10 +21,10 @@ default:
 private:
 
 
-static void Dummy()
+static __attribute__((unused)) void Dummy()
 {
-int a;
-a.OnGetString(null, null, null);
+   int a;
+   a.OnGetString(null, null, null);
 }
 
 static bool editing = true;
@@ -45,15 +46,6 @@ static const char * iconNames[CodeObjectType] =
    "<:ecere>constructs/propertyPrivate.png"
 };
 
-IDESettings settings { }; // instantiate the IDESettings class from the IDESettings.ec file. Do this at a global level so that all methods can access settings.
-
-IDESettingsContainer settingsContainer
-{
-   driver = "JSON";
-   data = settings;
-   dataOwner = &settings;
-};
-
 void GetTemplateString(Class c, char * templateString)
 {
    Module m = c.module.application;
@@ -77,9 +69,11 @@ void GetTemplateString(Class c, char * templateString)
             Class pc;
             char * d = templateString + strlen(templateString);
             curName[len] = 0;
+            TrimLSpaces(curName, curName);
+            TrimRSpaces(curName, curName);
             pc = eSystem_FindClass(m, curName);
             if(pc)
-               sprintf(d, "<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", pc, pc.name);
+               sprintf(d, "%s<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", !strncmp(curName, "const ", 6) ? "const " : "", pc, pc.name);
             else
                strcat(d, curName);
          }
@@ -100,7 +94,7 @@ void GetTemplateString(Class c, char * templateString)
          strcat(templateString, curName);
          len = 0;
       }
-      else if(ch != ' ')
+      else
          curName[len++] = ch;
    }
 }
@@ -270,10 +264,6 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                      strcat(string, "</b>");
                   }
                }
-               else
-               {
-                  printf("");
-               }
             }
 
             if(printFunction)
@@ -369,7 +359,7 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
             strcat(string, ")");
             break;
          default:
-            printf("");
+            break;
       }
       if(type.name && printName && type.kind != functionType && (type.kind != pointerType || type.type.kind != functionType))
       {
@@ -385,7 +375,6 @@ void DocPrintType(Type type, char * string, bool printName, bool fullName)
    for(funcType = type; funcType && (funcType.kind == pointerType || funcType.kind == arrayType); funcType = funcType.type);
    if(funcType && funcType.kind == functionType && type != funcType)
    {
-      char typeString[1024];
       Type param;
 
       DocPrintType(funcType.returnType, string, false, fullName);
@@ -412,14 +401,16 @@ void DocPrintType(Type type, char * string, bool printName, bool fullName)
       _PrintType(type, string, printName, true, fullName);
 }
 
+Map<String, bool> modulesAdded { };
 void AddComponents(Module module, bool isDll)
 {
    DataRow row = null;
    SubModule m;
 
-   if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
+   if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")) && !modulesAdded["ecereCOM"])
    {
       row = mainForm.browser.AddRow();
+      modulesAdded["ecereCOM"] = true;
       row.SetData(null, APIPageNameSpace { name = "ecereCOM", nameSpace = &module.application.systemNameSpace });
       row.tag = (int64)null;
       AddNameSpace(row, null, module.application.systemNameSpace, null, "", !isDll);
@@ -432,9 +423,10 @@ void AddComponents(Module module, bool isDll)
    }
 
    // PUT MODULE DESCRIPTION HERE
-   if(module.name && strcmp(module.name, "ecereCOM"))
+   if(module.name && !modulesAdded[module.name] && strcmp(module.name, "ecereCOM"))
    {
       row = mainForm.browser.AddRow();
+      modulesAdded[module.name] = true;
       row.SetData(null, APIPageNameSpace { name = module.name, module = module, nameSpace = &module.publicNameSpace });
       row.tag = (int64)module;
       AddNameSpace(row, module, module.publicNameSpace, null /*module.application.systemNameSpace*/, "", !isDll);
@@ -474,6 +466,7 @@ public:
 
 enum DocumentationType
 {
+   unset,
    nameSpaceDoc,
    classDoc,
    functionDoc,
@@ -482,6 +475,7 @@ enum DocumentationType
 
 enum DocumentationItem
 {
+   unset,
    description,
    usage,
    remarks,
@@ -498,14 +492,60 @@ enum DocumentationItem
 
 static void FigureFileName(char * fileName, Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
 {
+   char hex[20];
+   fileName[0] = 0;
+   sprintf(hex, "%p", module);
+   strcat(fileName, hex);
+   strcat(fileName, "/");
+   sprintf(hex, "%p", object);
+   strcat(fileName, hex);
+   strcat(fileName, "/");
+   sprintf(hex, "%p", data);
+   strcat(fileName, hex);
+   strcat(fileName, "/");
+   if(type == nameSpaceDoc)
+      strcat(fileName, "namespace");
+   else if(type == functionDoc)
+      strcat(fileName, "function");
+   else if(type == classDoc)
+      strcat(fileName, "class");
+   else if(type == methodDoc)
+      strcat(fileName, "method");
+   strcat(fileName, "/");
+   if(item == description)
+      strcat(fileName, "description");
+   else if(item == usage)
+      strcat(fileName, "usage");
+   else if(item == remarks)
+      strcat(fileName, "remarks");
+   else if(item == example)
+      strcat(fileName, "example");
+   else if(item == seeAlso)
+      strcat(fileName, "seeAlso");
+   else if(item == enumerationValue)
+      strcat(fileName, "enumerationValue");
+   else if(item == definition)
+      strcat(fileName, "definition");
+   else if(item == conversion)
+      strcat(fileName, "conversion");
+   else if(item == memberDescription)
+      strcat(fileName, "memberDescription");
+   else if(item == propertyDescription)
+      strcat(fileName, "propertyDescription");
+   else if(item == parameter)
+      strcat(fileName, "parameter");
+   else if(item == returnValue)
+      strcat(fileName, "returnValue");
+}
+
+static void FigureFilePath(char * path, Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
+{
+   char docPath[MAX_LOCATION];
    NameSpace * nameSpace, * ns;
    Class cl = null;
    Method method = null;
    GlobalFunction function = null;
    char nsName[1024], temp[1024];
-   char docFile[1024];
-
-
    switch(type)
    {
       case nameSpaceDoc: nameSpace = object; break;
@@ -515,125 +555,221 @@ static void FigureFileName(char * fileName, Module module, DocumentationType typ
    }
 
    nsName[0] = 0;
+   temp[0] = 0;
    ns = nameSpace;
    while(ns && ns->name)
    {
-      strcpy(temp, "namespaces/");
-      strcat(temp, ns->name);
+      strcpy(temp, ns->name);
       strcat(temp, "/");
       strcat(temp, nsName);
       strcpy(nsName, temp);
       ns = ns->parent;
    }
-   sprintf(docFile, "%s.eCdoc", (!module || !module.name || !strcmp(nsName, "namespaces/ecere/namespaces/com")) ? "ecereCOM" : module.name);
-   if(strchr(docFile, DIR_SEP))
-   {
-      GetLastDirectory(docFile, temp);
-      strcpy(docFile, temp);
-   }
-
-   sprintf(fileName, "<%s/%s>", settings.docDir, docFile); // Note that in the ecereIDE.ini file, there can be no quotes around the path, and there needs to be the final backslash. Otherwise this does not work.
-   strcat(fileName, nsName);
 
+   docPath[0] = 0;
+   PathCatSlash(docPath, (!module || !module.name || !strcmp(nsName, "namespaces/ecere/namespaces/com")) ? "ecereCOM" : module.name);
+   //ChangeExtension(docPath, "eCdoc", docPath);
+   PathCatSlash(docPath, nsName);
    if(cl)
    {
-      strcat(fileName, "classes/");
-      strcat(fileName, cl.name);
-      strcat(fileName, "/");
-   }
-
-   if(method)
-   {
-      strcat(fileName, "methods/");
-      strcat(fileName, method.name);
-      strcat(fileName, "/");
-   }
-   else if(function)
-   {
-      const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
-      if(name) name += 2; else name = function.name;
-      strcat(fileName, "functions/");
-      strcat(fileName, name);
-      strcat(fileName, "/");
-   }
-
-   switch(item)
-   {
-      case description: strcat(fileName, "description"); break;
-      case usage: strcat(fileName, "usage"); break;
-      case remarks: strcat(fileName, "remarks"); break;
-      case example: strcat(fileName, "example"); break;
-      case seeAlso: strcat(fileName, "seeAlso"); break;
-      case returnValue: strcat(fileName, "returnValue"); break;
-      case enumerationValue:
-         strcat(fileName, "enumeration values/");
-         strcat(fileName, ((NamedLink)data).name);
-         break;
-      case definition:
-         strcat(fileName, "definitions/");
-         strcat(fileName, ((Definition)data).name);
-         break;
-      case conversion:
-      {
-         const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
-         if(name) name += 2; else name = ((Property)data).name;
-         strcat(fileName, "conversions/");
-         strcat(fileName, name);
-         break;
-      }
-      case memberDescription:
-         strcat(fileName, "data members/");
-         strcat(fileName, ((DataMember)data).name);
-         break;
-      case propertyDescription:
-         strcat(fileName, "properties/");
-         strcat(fileName, ((Property)data).name);
-         break;
-      case parameter:
-      {
-         int count;
-         char name[1024];
-         Type prev;
-         strcat(fileName, "parameters/");
-         for(prev = data, count = 0; prev; prev = prev.prev, count++);
-         sprintf(name, "%s.%d", ((Type)data).name, count);
-         strcat(fileName, name);
-         break;
-      }
+      char * name = getDocFileNameFromTypeName(cl.name);
+      PathCatSlash(docPath, name);
+      delete name;
    }
+   else
+      PathCatSlash(docPath, "_global-defs");
+   ChangeExtension(docPath, "econ", docPath);
+
+   path[0] = 0;
+   strcpy(path, ideSettings.docDir);
+   PathCatSlash(path, docPath);
 }
 
 static char * ReadDoc(Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
 {
-   char fileName[MAX_LOCATION];
    String contents = null;
-   File file;
+   NamespaceDoc nsDoc = null;
+   ClassDoc clDoc = null;
+   FunctionDoc fnDoc = null;
+   MethodDoc mdDoc = null;
+   String s = null;
+   char filePath[MAX_LOCATION];
+   Method method = null;
+   GlobalFunction function = null;
+
+   ItemDoc doc = getDoc(filePath, module, type, object, item, data, false);
 
-   FigureFileName(fileName, module, type, object, item, data);
-   file = FileOpen(fileName, read);
-   if(file)
+   switch(type)
    {
-      uint len;
-      if((len = file.GetSize()))
+      case functionDoc:  function = object; break;
+      case methodDoc:    method = object; break;
+   }
+
+   if(doc)
+   {
+      if(eClass_IsDerived(doc._class, class(ClassDoc)))
       {
-         contents = new char[len+1];
-         file.Read(contents, 1, len);
-         contents[len] = '\0';
+         clDoc = (ClassDoc)doc;
+      }
+      else if(eClass_IsDerived(doc._class, class(NamespaceDoc)))
+      {
+         nsDoc = (NamespaceDoc)doc;
       }
-      delete file;
    }
-   if(contents)
+
+   if(clDoc || nsDoc)
    {
-      int c;
-      for(c = 0; contents[c]; c++)
-         if(!isspace(contents[c])) break;
-      if(!contents[c])
-         delete contents;
+      ItemDoc itDoc = null;
+      if(type == functionDoc)
+      {
+         MapIterator<String, FunctionDoc> it { map = nsDoc.functions };
+         const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
+         if(name) name += 2; else name = function.name;
+         if(it.Index(name, false))
+            fnDoc = it.data;
+      }
+      else if(type == methodDoc)
+      {
+         MapIterator<String, MethodDoc> it { map = clDoc.methods };
+         if(it.Index(method.name, false))
+            mdDoc = it.data;
+      }
+
+      switch(item)
+      {
+         case description: s = type == methodDoc ? mdDoc.description : type == functionDoc ? fnDoc.description : type == classDoc ? clDoc.description : nsDoc.description; break;
+         case usage: s = type == methodDoc ? mdDoc.usage : type == functionDoc ? fnDoc.usage : type == classDoc ? clDoc.usage : null; break;
+         case remarks: s = type == methodDoc ? mdDoc.remarks : type == functionDoc ? fnDoc.remarks : type == classDoc ? clDoc.remarks : null; break;
+         case example: s = type == methodDoc ? mdDoc.example : type == functionDoc ? fnDoc.example : type == classDoc ? clDoc.example : null; break;
+         case seeAlso: s = type == methodDoc ? mdDoc.also : type == functionDoc ? fnDoc.also : type == classDoc ? clDoc.also : null; break;
+         case returnValue: s = type == methodDoc ? mdDoc.returnValue : type == functionDoc ? fnDoc.returnValue : null; break;
+         case enumerationValue:
+            if(clDoc && clDoc.values)
+            {
+               itDoc = clDoc.values[((NamedLink)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case definition:
+            if(nsDoc && nsDoc.defines)
+            {
+               itDoc = nsDoc.defines[((Definition)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case conversion:
+            if(clDoc && clDoc.conversions)
+            {
+               const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
+               if(name) name += 2; else name = ((Property)data).name;
+               itDoc = clDoc.conversions[name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case memberDescription:
+            if(clDoc && clDoc.fields)
+            {
+               itDoc = clDoc.fields[((DataMember)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case propertyDescription:
+            if(clDoc && clDoc.properties)
+            {
+               itDoc = clDoc.properties[((Property)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case parameter:
+            if((type == functionDoc && fnDoc && fnDoc.parameters) || (type == methodDoc && mdDoc && mdDoc.parameters))
+            {
+               char * name = ((Type)data).name;
+               itDoc = ((type == functionDoc) ? fnDoc.parameters : mdDoc.parameters)[name] ;
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+      }
+      if(s)
+         contents = CopyString(s);
    }
    if(editing && !contents && !readOnly)
       contents = CopyString($"[Add Text]");
+   delete doc;
    return contents;
 }
+               // The filePath is returned!
+ItemDoc getDoc(char * filePath, Module module, DocumentationType type, void * object, DocumentationItem item, void * data, bool create)
+{
+   ItemDoc doc = null;
+   Class cl = null;
+   Method method = null;
+   DocCacheEntry entry;
+   Time now;
+
+   switch(type)
+   {
+      case classDoc:     cl = (Class)object; break;
+      case methodDoc:    method = object; cl = method._class; break;
+   }
+
+   FigureFilePath(filePath, module, type, object, item, data);
+
+   entry = docCache[filePath];
+   if(entry)
+      doc = entry.doc;
+
+   if(!doc)
+   {
+      File f = FileOpen(filePath, read);
+      if(f)
+      {
+         ECONParser parser { f = f };
+         JSONResult jsonResult = parser.GetObject(cl ? class(ClassDoc) : class(NamespaceDoc), &doc);
+         delete parser;
+         delete f;
+
+         if(jsonResult != success)
+         {
+            PrintLn("error: problem parsing file: ", filePath);
+            delete doc;
+         }
+      }
+      if(!doc)
+         doc = cl ? (ItemDoc)ClassDoc { } : (ItemDoc)NamespaceDoc { };
+   }
+
+   incref doc;    // Reference to return
+
+   now = GetTime();
+   // Add to the cache
+   if(entry)
+      entry.timeStamp = now;
+   else
+   {
+      docCache[filePath] = { now, doc };
+      incref doc; // Reference for the cache
+   }
+
+   //void pruneDocCache()
+   // NOTE: If we want time stamp to be last retrieved, the pruning should be done before the retrieval
+   {
+      MapIterator<String, DocCacheEntry> it { map = docCache };
+      Array<const String> toRemove { };
+      for(entry : docCache; now - entry.timeStamp > 30)
+         toRemove.Add(&entry);
+      while(toRemove.count)
+      {
+         if(it.Index(toRemove.lastIterator.data, false))
+         {
+            delete it.data;
+            it.Remove();
+         }
+         toRemove.Remove(toRemove.lastIterator.pointer);
+      }
+      delete toRemove;
+   }
+   return doc;
+}
 
 class APIPageNameSpace : APIPage
 {
@@ -652,11 +788,9 @@ class APIPageNameSpace : APIPage
 
    void Generate(File f)
    {
-      char string[1024];
       char nsName[1024], temp[1024];
       NameSpace * ns;
       BTNamedLink link;
-      int64 tag;
 
       nsName[0] = 0;
       ns = nameSpace;
@@ -673,14 +807,10 @@ class APIPageNameSpace : APIPage
       if(nsName[0])
       {
          f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", nsName );
-         tag = (int64)nameSpace;
          f.Printf($"Module: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
       }
       else
-      {
-         tag = (int64)(!module || !module.name || !strcmp(nsName, "ecere::com") ? null : module);
          f.Printf($"<FONT FACE=\"Arial\" SIZE=\"6\">Module %s</FONT><br>\n", (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
-      }
 
       nsName[0] = 0;
       ns = nameSpace->parent;
@@ -700,17 +830,18 @@ class APIPageNameSpace : APIPage
          char * desc = ReadDoc(module, nameSpaceDoc, nameSpace, description, null);
          if(desc)
          {
-            f.Printf($"<H3>Description</H3><br><br>\n");
+            f.Printf($"<H3>Description</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
                FigureFileName(fileName, module, nameSpaceDoc, nameSpace, description, null);
                f.Printf("<a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
                f.Puts(desc);
-               f.Printf("</a><br><br>");
+               f.Printf("</a>");
             }
             else
-               f.Printf("%s<br><br>", desc);
+               f.Printf("%s", desc);
+            f.Printf("<br><br><br>");
             delete desc;
          }
       }
@@ -723,7 +854,7 @@ class APIPageNameSpace : APIPage
             char * desc = ReadDoc(module, nameSpaceDoc, ns, description, null);
             if(first)
             {
-               f.Printf($"<H3>Sub Namespaces</H3><br><br>\n");
+               f.Printf($"<H3>Sub Namespaces</H3><BR>\n");
                f.Printf("<TABLE>\n");
                first = false;
             }
@@ -746,7 +877,7 @@ class APIPageNameSpace : APIPage
             f.Printf("</TR><br>\n");
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       if(nameSpace->classes.first)
@@ -755,13 +886,14 @@ class APIPageNameSpace : APIPage
          for(link = (BTNamedLink)nameSpace->classes.first; link; link = (BTNamedLink)((BTNode)link).next)
          {
             Class cl = link.data;
-            if(!cl.templateClass)
+            Module module = cl.module ? cl.module  : this.module;
+            if(!cl.templateClass) // && !cl.internalDecl)
             {
                char * desc = ReadDoc(module, classDoc, cl, description, null);
 
                if(first)
                {
-                  f.Printf($"<a name=Classes></a><H3>Classes</H3><br><br>\n");
+                  f.Printf($"<a name=Classes></a><H3>Classes</H3><BR>\n");
                   f.Printf("<TABLE>\n");
                   first = false;
                }
@@ -787,7 +919,7 @@ class APIPageNameSpace : APIPage
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       if(nameSpace->functions.first)
@@ -796,12 +928,13 @@ class APIPageNameSpace : APIPage
          for(link = (BTNamedLink)nameSpace->functions.first; link; link = (BTNamedLink)((BTNode)link).next)
          {
             GlobalFunction function = link.data;
+            Module module = function.module ? function.module  : this.module;
             char * desc = ReadDoc(module, functionDoc, function, description, null);
             const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
             if(name) name += 2; else name = function.name;
             if(first)
             {
-               f.Printf($"<a name=Functions></a><H3>Functions</H3><br><br>\n");
+               f.Printf($"<a name=Functions></a><H3>Functions</H3><BR>\n");
                f.Printf("<TABLE>\n");
                first = false;
             }
@@ -824,7 +957,7 @@ class APIPageNameSpace : APIPage
             f.Printf("</TR><br>\n");
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       if(nameSpace->defines.first)
@@ -836,7 +969,7 @@ class APIPageNameSpace : APIPage
             char * desc = ReadDoc(module, nameSpaceDoc, nameSpace, definition, def);
             if(first)
             {
-               f.Printf($"<a name=Definitions></a><H3>Definitions</H3><br><br>\n");
+               f.Printf($"<a name=Definitions></a><H3>Definitions</H3><BR>\n");
                f.Printf("<TABLE>\n");
                first = false;
             }
@@ -860,7 +993,7 @@ class APIPageNameSpace : APIPage
             f.Printf("</TR><br>\n");
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       f.Printf("</FONT></BODY></HTML>\n");
@@ -886,7 +1019,6 @@ class APIPageClass : APIPage
       char string[1024];
       Method method;
       Property prop;
-      DataMember member;
       char nsName[1024], temp[1024];
       NameSpace * ns = cl.nameSpace;
       Module module = cl.module;
@@ -955,17 +1087,18 @@ class APIPageClass : APIPage
          char * desc = ReadDoc(module, classDoc, cl, description, null);
          if(desc)
          {
-            f.Printf($"<br><H3>Description</H3><br><br>\n");
+            f.Printf($"<br><H3>Description</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
                FigureFileName(fileName, module, classDoc, cl, description, null);
                f.Printf("<a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
                f.Puts(desc);
-               f.Printf("</a><br><br>");
+               f.Printf("</a>");
             }
             else
-               f.Printf("%s<br><br>", desc);
+               f.Printf("%s", desc);
+            f.Printf("<br><br><br>");
             delete desc;
          }
       }
@@ -977,7 +1110,7 @@ class APIPageClass : APIPage
          {
             NamedLink item;
 
-            f.Printf($"<a name=EnumerationValues></a><H3>Enumeration Values</H3><br><br>\n");
+            f.Printf($"<a name=EnumerationValues></a><H3>Enumeration Values</H3><BR>\n");
             f.Printf("<TABLE>\n");
 
             for(item = enumeration.values.first; item; item = item.next)
@@ -1046,14 +1179,13 @@ class APIPageClass : APIPage
 
       if(cl.conversions.first)
       {
-         f.Printf($"<a name=Conversions></a><H3>Conversions</H3><br><br>\n");
+         f.Printf($"<a name=Conversions></a><H3>Conversions</H3><BR>\n");
          f.Printf("<TABLE>\n");
          for(prop = cl.conversions.first; prop; prop = prop.next)
          {
             if((prop.memberAccess == publicAccess || (prop.memberAccess == privateAccess && showPrivate)) && prop.name)
             {
                char * desc = ReadDoc(module, classDoc, cl, conversion, prop);
-               DataRow mRow;
                const char * name;
                Type type = ProcessTypeString(prop.name, false);
                name = RSearchString(prop.name, "::", strlen(prop.name), true, false);
@@ -1085,7 +1217,7 @@ class APIPageClass : APIPage
                FreeType(type);
             }
          }
-         f.Printf("</TABLE><br>\n");
+         f.Printf("</TABLE><br><br>\n");
       }
 
       if(cl.membersAndProperties.first)
@@ -1097,7 +1229,7 @@ class APIPageClass : APIPage
             {
                if(first)
                {
-                  f.Printf($"<a name=Members></a><H3>Properties and Members</H3><br><br>\n");
+                  f.Printf($"<a name=Members></a><H3>Properties and Members</H3><BR>\n");
                   f.Printf("<TABLE>\n");
                   first = false;
                }
@@ -1137,7 +1269,7 @@ class APIPageClass : APIPage
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       if(cl.methods.first)
@@ -1151,7 +1283,7 @@ class APIPageClass : APIPage
                char * desc = ReadDoc(module, methodDoc, method, description, null);
                if(first)
                {
-                  f.Printf($"<a name=VirtualMethods></a><H3>Virtual Methods</H3><br><br>\n");
+                  f.Printf($"<a name=VirtualMethods></a><H3>Virtual Methods</H3><BR>\n");
                   f.Printf("<TABLE>\n");
                   first = false;
                }
@@ -1178,7 +1310,7 @@ class APIPageClass : APIPage
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
 
          // Non-Virtual Methods
          first = true;
@@ -1189,7 +1321,7 @@ class APIPageClass : APIPage
                char * desc = ReadDoc(module, methodDoc, method, description, null);
                if(first)
                {
-                  f.Printf($"<a name=Methods></a><H3>Non-Virtual Methods</H3><br><br>\n");
+                  f.Printf($"<a name=Methods></a><H3>Non-Virtual Methods</H3><BR>\n");
                   f.Printf("<TABLE>\n");
                   first = false;
                }
@@ -1218,13 +1350,13 @@ class APIPageClass : APIPage
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
       {
          char * usageDoc = ReadDoc(module, classDoc, cl, usage, null);
          if(usageDoc)
          {
-            f.Printf($"<H3>Usage</H3><br>\n");
+            f.Printf($"<H3>Usage</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1235,7 +1367,7 @@ class APIPageClass : APIPage
             }
             else
                f.Printf("<br>%s\n", usageDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete usageDoc;
          }
       }
@@ -1243,7 +1375,7 @@ class APIPageClass : APIPage
          char * exampleDoc = ReadDoc(module, classDoc, cl, example, null);
          if(exampleDoc)
          {
-            f.Printf($"<H3>Example</H3><br>\n");
+            f.Printf($"<H3>Example</H3><BR>\n");
             f.Printf($"<FONT face=\"Courier New\">\n");
             f.Printf("<br><TABLE>\n");
             if(editing)
@@ -1258,7 +1390,7 @@ class APIPageClass : APIPage
                f.Printf("<TR><TD><CODE>%s</CODE></TD></TR>\n", exampleDoc);   // bgcolor=#CFC9C0
 
             f.Printf("</TABLE></FONT>\n");
-            f.Printf("<br>\n");
+            f.Printf("<br><br>\n");
             delete exampleDoc;
          }
       }
@@ -1267,7 +1399,7 @@ class APIPageClass : APIPage
 
          if(remarksDoc)
          {
-            f.Printf($"<H3>Remarks</H3><br>\n");
+            f.Printf($"<H3>Remarks</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1278,7 +1410,7 @@ class APIPageClass : APIPage
             }
             else
                f.Printf("<br>%s\n", remarksDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete remarksDoc;
          }
       }
@@ -1295,8 +1427,7 @@ class APIPageClass : APIPage
             {
                if(first)
                {
-                  f.Printf($"<H3>Derived Classes</H3><br>\n");
-                  f.Printf("<br>");
+                  f.Printf($"<H3>Derived Classes</H3><BR>\n");
                   first = false;
                }
                else
@@ -1311,7 +1442,7 @@ class APIPageClass : APIPage
          char * seeAlsoDoc = ReadDoc(module, classDoc, cl, seeAlso, null);
          if(seeAlsoDoc)
          {
-            f.Printf($"<H3>See Also</H3><br>\n");
+            f.Printf($"<H3>See Also</H3>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1390,7 +1521,7 @@ class APIPageMethod : APIPage
          char * desc = ReadDoc(module, methodDoc, method, description, null);
          if(desc)
          {
-            f.Printf($"<br><br><H3>Description</H3><br><br>\n");
+            f.Printf($"<br><br><H3>Description</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1401,6 +1532,7 @@ class APIPageMethod : APIPage
             }
             else
                f.Printf("%s", desc);
+            f.Printf("<BR><BR>");
             delete desc;
          }
       }
@@ -1408,7 +1540,7 @@ class APIPageMethod : APIPage
       f.Printf("<br><br>\n");
       if(method.dataType.params.first && ((Type)method.dataType.params.first).kind != voidType)
       {
-         f.Printf($"<H3>Parameters</H3><br><br>\n");
+         f.Printf($"<H3>Parameters</H3><BR>\n");
       }
       if((method.dataType.returnType && method.dataType.returnType.kind != voidType) ||
          (method.dataType.params.first && ((Type)method.dataType.params.first).kind != voidType))
@@ -1484,7 +1616,7 @@ class APIPageMethod : APIPage
          char * usageDoc = ReadDoc(module, methodDoc, method, usage, null);
          if(usageDoc)
          {
-            f.Printf($"<H3>Usage</H3><br>\n");
+            f.Printf($"<H3>Usage</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1495,7 +1627,7 @@ class APIPageMethod : APIPage
             }
             else
                f.Printf("<br>%s\n", usageDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete usageDoc;
          }
       }
@@ -1503,7 +1635,7 @@ class APIPageMethod : APIPage
          char * exampleDoc = ReadDoc(module, methodDoc, method, example, null);
          if(exampleDoc)
          {
-            f.Printf($"<H3>Example</H3><br>\n");
+            f.Printf($"<H3>Example</H3><BR>\n");
             f.Printf($"<FONT face=\"Courier New\">\n");
             f.Printf("<br><TABLE>\n");
             if(editing)
@@ -1517,7 +1649,7 @@ class APIPageMethod : APIPage
             else
                f.Printf("<TR><TD><CODE>%s</CODE></TD></TR>\n", exampleDoc);   // bgcolor=#CFC9C0
             f.Printf("</TABLE></FONT>\n");
-            f.Printf("<br>\n");
+            f.Printf("<br><br>\n");
             delete exampleDoc;
          }
       }
@@ -1525,7 +1657,7 @@ class APIPageMethod : APIPage
          char * remarksDoc = ReadDoc(module, methodDoc, method, remarks, null);
          if(remarksDoc)
          {
-            f.Printf($"<H3>Remarks</H3><br>\n");
+            f.Printf($"<H3>Remarks</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1536,7 +1668,7 @@ class APIPageMethod : APIPage
             }
             else
                f.Printf("<br>%s\n", method, remarksDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete remarksDoc;
          }
       }
@@ -1544,7 +1676,7 @@ class APIPageMethod : APIPage
          char * seeAlsoDoc = ReadDoc(module, methodDoc, method, seeAlso, null);
          if(seeAlsoDoc)
          {
-            f.Printf($"<H3>See Also</H3><br>\n");
+            f.Printf($"<H3>See Also</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1556,7 +1688,7 @@ class APIPageMethod : APIPage
             else
                f.Printf("<br>%s\n", method, seeAlsoDoc);
 
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete seeAlsoDoc;
          }
       }
@@ -1623,7 +1755,7 @@ class APIPageFunction : APIPage
          char * desc = ReadDoc(module, functionDoc, function, description, null);
          if(desc)
          {
-            f.Printf($"<br><br><H3>Description</H3><br><br>\n");
+            f.Printf($"<br><br><H3>Description</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1635,12 +1767,13 @@ class APIPageFunction : APIPage
             else
                f.Printf("%s", desc);
             delete desc;
+            f.Printf("<BR><BR>");
          }
       }
       f.Printf("<br><br>\n");
       if(function.dataType.params.first && ((Type)function.dataType.params.first).kind != voidType)
       {
-         f.Printf($"<H3>Parameters</H3><br><br>\n");
+         f.Printf($"<H3>Parameters</H3><BR>\n");
       }
       if((function.dataType.returnType && function.dataType.returnType.kind != voidType) ||
          (function.dataType.params.first && ((Type)function.dataType.params.first).kind != voidType))
@@ -1716,7 +1849,7 @@ class APIPageFunction : APIPage
          char * usageDoc = ReadDoc(module, functionDoc, function, usage, null);
          if(usageDoc)
          {
-            f.Printf($"<H3>Usage</H3><br>\n");
+            f.Printf($"<H3>Usage</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1727,7 +1860,7 @@ class APIPageFunction : APIPage
             }
             else
                f.Printf("<br>%s\n", usageDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete usageDoc;
          }
       }
@@ -1735,7 +1868,7 @@ class APIPageFunction : APIPage
          char * exampleDoc = ReadDoc(module, functionDoc, function, example, null);
          if(exampleDoc)
          {
-            f.Printf($"<H3>Example</H3><br>\n");
+            f.Printf($"<H3>Example</H3><BR>\n");
             f.Printf($"<FONT face=\"Courier New\">\n");
             f.Printf("<br><TABLE>\n");
             if(editing)
@@ -1749,7 +1882,7 @@ class APIPageFunction : APIPage
             else
                f.Printf("<TR><TD><CODE>%s</CODE></TD></TR>\n", exampleDoc);   // bgcolor=#CFC9C0
             f.Printf("</TABLE></FONT>\n");
-            f.Printf("<br>\n");
+            f.Printf("<br><br>\n");
             delete exampleDoc;
          }
       }
@@ -1757,7 +1890,7 @@ class APIPageFunction : APIPage
          char * remarksDoc = ReadDoc(module, functionDoc, function, remarks, null);
          if(remarksDoc)
          {
-            f.Printf($"<H3>Remarks</H3><br>\n");
+            f.Printf($"<H3>Remarks</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1768,7 +1901,7 @@ class APIPageFunction : APIPage
             }
             else
                f.Printf("<br>%s\n", remarksDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete remarksDoc;
          }
       }
@@ -1776,7 +1909,7 @@ class APIPageFunction : APIPage
          char * seeAlsoDoc = ReadDoc(module, functionDoc, function, seeAlso, null);
          if(seeAlsoDoc)
          {
-            f.Printf($"<H3>See Also</H3><br>\n");
+            f.Printf($"<H3>See Also</H3><BR>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1787,7 +1920,7 @@ class APIPageFunction : APIPage
             }
             else
                f.Printf("<br>%s\n", seeAlsoDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete seeAlsoDoc;
          }
       }
@@ -1805,8 +1938,6 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
    DataRow functionsRow = null, definesRow = null;
    APIPage page;
 
-   char fileName[MAX_LOCATION];
-
    strcpy(nsName, parentName ? parentName : "");
    if(nameSpace->name)
    {
@@ -1829,25 +1960,22 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
       page = parentRow.GetData(null);
    }
 
+   for(ns = (NameSpace *)mainNameSpace.nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
    {
-      bool first = true;
-
-      for(ns = (NameSpace *)mainNameSpace.nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
-      {
-         NameSpace * comNS = (comNameSpace != null) ? (NameSpace *)comNameSpace.nameSpaces.FindString(ns->name) : null;
-         AddNameSpace(row, module, ns, comNS, nsName, showPrivate);
-      }
-      if(comNameSpace != null)
+      NameSpace * comNS = (comNameSpace != null) ? (NameSpace *)comNameSpace.nameSpaces.FindString(ns->name) : null;
+      AddNameSpace(row, module, ns, comNS, nsName, showPrivate);
+   }
+   if(comNameSpace != null)
+   {
+      for(ns = (NameSpace *)comNameSpace.nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
       {
-         for(ns = (NameSpace *)comNameSpace.nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
+         if(!mainNameSpace.nameSpaces.FindString(ns->name))
          {
-            if(!mainNameSpace.nameSpaces.FindString(ns->name))
-            {
-               AddNameSpace(row, module, ns, null, nsName, showPrivate);
-            }
+            AddNameSpace(row, module, ns, null, nsName, showPrivate);
          }
       }
    }
+
    if(mainNameSpace.classes.first || (comNameSpace && comNameSpace.classes.first))
    {
       for(nameSpace = mainNameSpace ; nameSpace; nameSpace = (nameSpace == mainNameSpace) ? comNameSpace : null)
@@ -1859,7 +1987,7 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
             for(link = (BTNamedLink)nameSpace->classes.first; link; link = (BTNamedLink)((BTNode)link).next)
             {
                cl = link.data;
-               if(!cl.templateClass && (!module || cl.module == module || (!cl.module.name && !strcmp(module.name, "ecere"))))
+               if(!cl.templateClass /*&& !cl.internalDecl*/ && (!module || cl.module == module || (!cl.module.name && !strcmp(module.name, "ecere"))))
                {
                   if(!classesRow) { classesRow = row.AddRow(); classesRow.SetData(null, APIPage { $"Classes", page = page }); classesRow.collapsed = true; classesRow.icon = mainForm.icons[typeClass]; classesRow.tag = 1; }
                   AddClass(classesRow, module, cl, nsName, showPrivate);
@@ -1992,12 +2120,8 @@ static void AddDataMember(DataRow parentRow, APIPage page, DataMember member)
 
 static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName, bool showPrivate)
 {
-   char fileName[MAX_LOCATION];
-   char string[1024];
    Method method;
    Property prop;
-   DataMember member;
-   Type param;
    DataRow row;
    DataRow methodsRow = null, virtualsRow = null, eventsRow = null;
    DataRow propertiesRow = null, membersRow = null, conversionsRow = null, enumRow = null;
@@ -2252,6 +2376,7 @@ class MainForm : Window
       static char symbolsDir[MAX_LOCATION];
 
       history.size = 0;
+      modulesAdded.RemoveAll();
 
       FreeContext(globalContext);
       FreeExcludedSymbols(excludedSymbols);
@@ -2259,11 +2384,13 @@ class MainForm : Window
       imports.Free(FreeModuleImport);
 
       FreeGlobalData(globalData);
-      FreeTypeData(componentsApp);
       FreeIncludeFiles();
-      delete componentsApp;
+      if(componentsApp)
+      {
+         FreeTypeData(componentsApp);
+         delete componentsApp;
+      }
 
-      SetGlobalContext(globalContext);
       componentsApp = __ecere_COM_Initialize(false, 1, null);
       SetPrivateModule(componentsApp);
 
@@ -2372,7 +2499,7 @@ class MainForm : Window
                   default:
                   {
                      char hex[20];
-                     sprintf(hex, "%p", row.tag);
+                     sprintf(hex, "%p", (void *)(uintptr)row.tag);
                      view.GoToAnchor(hex);
                   }
                }
@@ -2423,6 +2550,16 @@ class MainForm : Window
    bool dontRecordHistory;
    Module homeModule;
 
+   bool OnKeyHit(Key key, unichar ch)
+   {
+      switch(key)
+      {
+         case altLeft: Back(); return false;
+         case altRight: Forward(); return false;
+      }
+      return true;
+   }
+
    bool Forward()
    {
       if(historyPos < history.count-1)
@@ -2504,31 +2641,27 @@ class HelpView : HTMLView
       {
          // Writability test
          {
-            char docFile[MAX_LOCATION];
-            Archive archive;
-            Module module = page ? page.GetModule() : null;
-            NameSpace * ns = page ? page.GetNameSpace() : null;
-
-            sprintf(docFile, "%s/%s.eCdoc", settings.docDir, (!module || !module.name || (ns && ns->name && !strcmp(ns->name, "namespaces/ecere/namespaces/com"))) ? "ecereCOM" : module.name);
-            if(FileExists(docFile))
+            char docDir[MAX_LOCATION];
+            readOnly = true;
+            strcpy(docDir, ideSettings.docDir);
+            if(FileExists(docDir).isDirectory)
             {
-               archive = ArchiveOpen(docFile, { true } );
-               readOnly = archive == null;
-               delete archive;
-            }
-            else
-            {
-               readOnly = true;
-               archive = ArchiveOpen(docFile, { true } );
-               if(archive)
+               PathCatSlash(docDir, "___docWriteTest");
+               if(FileExists(docDir).isDirectory)
                {
-                  // Must create root directory on archive creation
-                  ArchiveDir dir = archive.OpenDirectory("", null, replace);
-                  if(dir)
+                  RemoveDir(docDir);
+                  if(!FileExists(docDir))
                      readOnly = false;
-                  delete dir;
                }
-               delete archive;
+               else
+               {
+                  MakeDir(docDir);
+                  if(FileExists(docDir).isDirectory)
+                  {
+                     readOnly = false;
+                     RemoveDir(docDir);
+                  }
+               }
             }
          }
 
@@ -2549,68 +2682,403 @@ class HelpView : HTMLView
 
    void SaveEdit()
    {
-      char archiveFile[MAX_LOCATION];
-      char fileName[MAX_FILENAME];
-      char directory[MAX_LOCATION];
-      char * location;
-      Archive archive = null;
-      if(SplitArchivePath(editString, archiveFile, &location))
+      Block block;
+      bool empty = true;
+      String contents = null;
+      uint len;
+      TempFile f { };
+      for(block = textBlock.parent.subBlocks.first; block; block = block.next)
       {
-         GetLastDirectory(location, fileName);
-         StripLastDirectory(location, directory);
-         archive = ArchiveOpen(archiveFile, { true } );
+         if(block.type == TEXT && block.textLen)
+         {
+            empty = false;
+            break;
+         }
       }
+      if(!empty)
       {
-         TempFile f { };
-         ArchiveDir dir = archive ? archive.OpenDirectory(directory, null, replace) : null;
-         Block block;
-         bool empty = true;
          for(block = textBlock.parent.subBlocks.first; block; block = block.next)
          {
-            if(block.type == TEXT && block.textLen)
+            if(block.type == BR)
+               f.Puts("<br>");
+            else if(block.type == TEXT)
+               f.Write(block.text, 1, block.textLen);
+         }
+      }
+      f.Seek(0, start);
+      if((len = f.GetSize()))
+      {
+         contents = new char[len+1];
+         f.Read(contents, 1, len);
+         contents[len] = '\0';
+      }
+
+      {
+         char docPath[MAX_LOCATION];
+         char temp[MAX_LOCATION];
+         char part[MAX_FILENAME];
+         Module module;
+         void * object;
+         void * data;
+         DocumentationType type;
+         DocumentationItem item;
+         ItemDoc doc;
+         NamespaceDoc nsDoc = null;
+         ClassDoc clDoc = null;
+         FunctionDoc fnDoc = null;
+         MethodDoc mdDoc = null;
+         Class cl = null;
+         Method method = null;
+         GlobalFunction function = null;
+
+         strcpy(temp, editString);
+         SplitDirectory(temp, part, temp);
+         module = (Module)strtoull(part, null, 16);
+         SplitDirectory(temp, part, temp);
+         object = (void *)strtoull(part, null, 16);
+         SplitDirectory(temp, part, temp);
+         data = (void *)strtoull(part, null, 16);
+         SplitDirectory(temp, part, temp);
+         if(!strcmp(part, "namespace"))
+            type = nameSpaceDoc;
+         else if(!strcmp(part, "function"))
+            type = functionDoc;
+         else if(!strcmp(part, "class"))
+            type = classDoc;
+         else if(!strcmp(part, "method"))
+            type = methodDoc;
+         SplitDirectory(temp, part, temp);
+         if(!strcmp(part, "description"))
+            item = description;
+         else if(!strcmp(part, "usage"))
+            item = usage;
+         else if(!strcmp(part, "remarks"))
+            item = remarks;
+         else if(!strcmp(part, "example"))
+            item = example;
+         else if(!strcmp(part, "seeAlso"))
+            item = seeAlso;
+         else if(!strcmp(part, "enumerationValue"))
+            item = enumerationValue;
+         else if(!strcmp(part, "definition"))
+            item = definition;
+         else if(!strcmp(part, "conversion"))
+            item = conversion;
+         else if(!strcmp(part, "memberDescription"))
+            item = memberDescription;
+         else if(!strcmp(part, "propertyDescription"))
+            item = propertyDescription;
+         else if(!strcmp(part, "parameter"))
+            item = parameter;
+         else if(!strcmp(part, "returnValue"))
+            item = returnValue;
+
+         doc = getDoc(docPath, module, type, object, item, data, !empty && contents);
+
+         /* Why invalidate this entry here?
+         {
+            MapIterator<const String, DocCacheEntry> it { map = docCache };
+            if(it.Index(docPath, false))
             {
-               empty = false;
-               break;
+               delete it.data;
+               it.Remove();
             }
+         }*/
+
+         switch(type)
+         {
+            case classDoc:     cl = (Class)object; break;
+            case functionDoc:  function = object; break;
+            case methodDoc:    method = object; cl = method._class; break;
          }
-         if(!empty)
+
+         if(doc)
          {
-            for(block = textBlock.parent.subBlocks.first; block; block = block.next)
+            if(eClass_IsDerived(doc._class, class(ClassDoc)))
             {
-               if(block.type == BR)
-                  f.Puts("<br>");
-               else if(block.type == TEXT)
-                  f.Write(block.text, 1, block.textLen);
+               clDoc = (ClassDoc)doc;
+            }
+            else if(eClass_IsDerived(doc._class, class(NamespaceDoc)))
+            {
+               nsDoc = (NamespaceDoc)doc;
             }
          }
-         f.Seek(0, start);
-         if(dir)
-            dir.AddFromFile(fileName, f, null, replace, 0, null, null);
-         delete dir;
-         delete archive;
-         delete f;
-         if(empty)
+
+         if(clDoc || nsDoc)
          {
-            Block parent = textBlock.parent;
-            while((block = parent.subBlocks.first))
+            if(type == functionDoc)
             {
-               parent.subBlocks.Remove(block);
-               delete block;
+               const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
+               if(name) name += 2; else name = function.name;
+               fnDoc = nsDoc.functions ? nsDoc.functions[name] : null;
+               if(!empty && !fnDoc)
+               {
+                  if(!nsDoc.functions) nsDoc.functions = { };
+                  nsDoc.functions[name] = fnDoc = { };
+               }
+            }
+            else if(type == methodDoc)
+            {
+               mdDoc = clDoc.methods ? clDoc.methods[method.name] : null;
+               if(!empty && !mdDoc)
+               {
+                  if(!clDoc.methods && !empty) clDoc.methods = { };
+                  clDoc.methods[method.name] = mdDoc = { };
+               }
+            }
+
+            if(!empty || mdDoc || fnDoc || (type == classDoc && clDoc) || (type == nameSpaceDoc && nsDoc))
+            {
+               switch(item)
+               {
+                  case description:
+                     if(type == methodDoc) { mdDoc.description = contents; contents = null; }
+                     else if(type == functionDoc) { fnDoc.description = contents; contents = null; }
+                     else if(type == classDoc) { clDoc.description = contents; contents = null; }
+                     else { nsDoc.description = contents; contents = null; }
+                     break;
+                  case usage:
+                     if(type == methodDoc) { mdDoc.usage = contents; contents = null; }
+                     else if(type == functionDoc) { fnDoc.usage = contents; contents = null; }
+                     else if(type == classDoc) { clDoc.usage = contents; contents = null; }
+                     break;
+                  case remarks:
+                     if(type == methodDoc) { mdDoc.remarks = contents; contents = null; }
+                     else if(type == functionDoc) { fnDoc.remarks = contents; contents = null; }
+                     else if(type == classDoc) { clDoc.remarks = contents; contents = null; }
+                     break;
+                  case example:
+                     if(type == methodDoc) { mdDoc.example = contents; contents = null; }
+                     else if(type == functionDoc) { fnDoc.example = contents; contents = null; }
+                     else if(type == classDoc) { clDoc.example = contents; contents = null; }
+                     break;
+                  case seeAlso:
+                     if(type == methodDoc) { mdDoc.also = contents; contents = null; }
+                     else if(type == functionDoc) { fnDoc.also = contents; contents = null; }
+                     else if(type == classDoc) { clDoc.also = contents; contents = null; }
+                     break;
+                  case returnValue:
+                     if(type == methodDoc) { mdDoc.returnValue = contents; contents = null; }
+                     else if(type == functionDoc) { fnDoc.returnValue = contents; contents = null; }
+                     break;
+                  case enumerationValue:
+                  {
+                     ValueDoc itDoc = clDoc.values ? clDoc.values[((NamedLink)data).name] : null;
+                     if(!empty || itDoc)
+                     {
+                        if(!empty && !itDoc)
+                        {
+                           if(!clDoc.values) clDoc.values = { };
+                           clDoc.values[((NamedLink)data).name] = itDoc = { };
+                        }
+                        itDoc.description = contents; contents = null;
+                        if(itDoc.isEmpty)
+                        {
+                           MapIterator<String, ValueDoc> it { map = clDoc.values };
+                           if(it.Index(((NamedLink)data).name, false))
+                              it.Remove();
+                           delete itDoc;
+                        }
+                     }
+                     break;
+                  }
+                  case definition:
+                  {
+                     DefineDoc itDoc = nsDoc.defines ? nsDoc.defines[((Definition)data).name] : null;
+                     if(!empty || itDoc)
+                     {
+                        if(!empty && !itDoc)
+                        {
+                           if(!nsDoc.defines) nsDoc.defines = { };
+                           nsDoc.defines[((Definition)data).name] = itDoc = { };
+                        }
+                        itDoc.description = contents; contents = null;
+                        if(itDoc.isEmpty)
+                        {
+                           MapIterator<String, DefineDoc> it { map = nsDoc.defines };
+                           if(it.Index(((Definition)data).name, false))
+                              it.Remove();
+                           delete itDoc;
+                        }
+                     }
+                     break;
+                  }
+                  case conversion:
+                  {
+                     ConversionDoc itDoc;
+                     const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
+                     if(name) name += 2; else name = ((Property)data).name;
+                     itDoc = clDoc.conversions ? clDoc.conversions[name] : null;
+                     if(!empty || itDoc)
+                     {
+                        if(!empty && !itDoc)
+                        {
+                           if(!clDoc.conversions) clDoc.conversions = { };
+                           clDoc.conversions[name] = itDoc = { };
+                        }
+                        itDoc.description = contents; contents = null;
+                        if(itDoc.isEmpty)
+                        {
+                           MapIterator<String, ConversionDoc> it { map = clDoc.conversions };
+                           if(it.Index(name, false))
+                              it.Remove();
+                           delete itDoc;
+                        }
+                     }
+                     break;
+                  }
+                  case memberDescription:
+                  {
+                     FieldDoc itDoc = clDoc.fields ? clDoc.fields[((DataMember)data).name] : null;
+                     if(!empty || itDoc)
+                     {
+                        if(!empty && !itDoc)
+                        {
+                           if(!clDoc.fields) clDoc.fields = { };
+                           clDoc.fields[((DataMember)data).name] = itDoc = { };
+                        }
+                        itDoc.description = contents; contents = null;
+                        if(itDoc.isEmpty)
+                        {
+                           MapIterator<String, FieldDoc> it { map = clDoc.fields };
+                           if(it.Index(((DataMember)data).name, false))
+                              it.Remove();
+                           delete itDoc;
+                        }
+                     }
+                     break;
+                  }
+                  case propertyDescription:
+                  {
+                     PropertyDoc itDoc = clDoc.properties ? clDoc.properties[((Property)data).name] : null;
+                     if(!empty || itDoc)
+                     {
+                        if(!empty && !itDoc)
+                        {
+                           if(!clDoc.properties) clDoc.properties = { };
+                           clDoc.properties[((Property)data).name] = itDoc = { };
+                        }
+                        itDoc.description = contents, contents = null;
+                        if(itDoc.isEmpty)
+                        {
+                           MapIterator<String, PropertyDoc> it { map = clDoc.properties };
+                           if(it.Index(((Property)data).name, false))
+                              it.Remove();
+                           delete itDoc;
+                        }
+                     }
+                     break;
+                  }
+                  case parameter:
+                  {
+                     if(type == functionDoc || type == methodDoc)
+                     {
+                        Map<String, ParameterDoc> * parameters = (type == functionDoc) ? &fnDoc.parameters : &mdDoc.parameters;
+                        char * name = ((Type)data).name;
+                        ParameterDoc itDoc = *parameters ? (*parameters)[name] : null;
+                        int position = 0;
+                        Type prev = data;
+                        while(prev) position++, prev = prev.prev;
+
+                        if(!empty || itDoc)
+                        {
+                           if(!empty && !itDoc)
+                           {
+                              if(!*parameters) *parameters = { };
+                              (*parameters)[name] = itDoc = { };
+                           }
+                           itDoc.description = contents; contents = null;
+                           itDoc.position = position;
+                           if(itDoc.isEmpty)
+                           {
+                              MapIterator<String, ParameterDoc> it { map = *parameters };
+                              if(it.Index(((Type)data).name, false))
+                                 it.Remove();
+                              delete itDoc;
+                           }
+                        }
+                     }
+                     break;
+                  }
+               }
             }
-            textBlock = Block { type = TEXT, parent = parent, font = parent.font };
-            textBlock.text = CopyString($"[Add Text]");
-            textBlock.textLen = strlen(textBlock.text);
-            parent.subBlocks.Add(textBlock);
          }
 
-         edit = false;
-         if(created)
+         if(type == functionDoc && fnDoc && fnDoc.isEmpty)
          {
-            ComputeMinSizes();
-            ComputeSizes();
-            PositionCaret(true);
-            Update(null);
+            MapIterator<String, FunctionDoc> it { map = nsDoc.functions };
+            const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
+            if(name) name += 2; else name = function.name;
+            if(it.Index(name, false))
+               it.Remove();
+            delete fnDoc;
+         }
+         else if(type == methodDoc && mdDoc && mdDoc.isEmpty)
+         {
+            MapIterator<String, MethodDoc> it { map = clDoc.methods };
+            if(it.Index(method.name, false))
+               it.Remove();
+            delete mdDoc;
+         }
+         if(nsDoc)
+         {
+            if(nsDoc.functions && !nsDoc.functions.count) delete nsDoc.functions;
+            if(nsDoc.defines && !nsDoc.defines.count) delete nsDoc.defines;
+         }
+         if(clDoc)
+         {
+            if(clDoc && clDoc.conversions && !clDoc.conversions.count) delete clDoc.conversions;
+            if(clDoc && clDoc.properties && !clDoc.properties.count) delete clDoc.properties;
+            if(clDoc && clDoc.fields && !clDoc.fields.count) delete clDoc.fields;
+            if(clDoc && clDoc.methods && !clDoc.methods.count) delete clDoc.methods;
+            if(clDoc && clDoc.values && !clDoc.values.count) delete clDoc.values;
+         }
+
+         if(clDoc || nsDoc)
+         {
+            char dirPath[MAX_LOCATION];
+            StripLastDirectory(docPath, dirPath);
+            if(FileExists(docPath))
+               DeleteFile(docPath);
+            if(cl ? (clDoc && !clDoc.isEmpty) : (nsDoc && !nsDoc.isEmpty))
+            {
+               File f;
+               if(!FileExists(dirPath))
+                  MakeDir(dirPath);
+               if((f = FileOpen(docPath, write)))
+               {
+                  WriteECONObject(f, cl ? class(ClassDoc) : class(NamespaceDoc), doc, 0);
+                  delete f;
+               }
+               else
+                  PrintLn("error: writeClassDocFile -- problem opening file: ", docPath);
+            }
          }
+         delete doc;
+         delete contents;
+      }
+
+      if(empty)
+      {
+         Block parent = textBlock.parent;
+         while((block = parent.subBlocks.first))
+         {
+            parent.subBlocks.Remove(block);
+            delete block;
+         }
+         textBlock = Block { type = TEXT, parent = parent, font = parent.font };
+         textBlock.text = CopyString($"[Add Text]");
+         textBlock.textLen = strlen(textBlock.text);
+         parent.subBlocks.Add(textBlock);
+      }
+
+      edit = false;
+      if(created)
+      {
+         ComputeMinSizes();
+         ComputeSizes();
+         PositionCaret(true);
+         Update(null);
       }
    }
 
@@ -2750,7 +3218,7 @@ class HelpView : HTMLView
             if(block.type == BR && (!block.prev || !block.next || block.next.type != TEXT))
             {
                Block newBlock { type = TEXT, parent = block.parent, font = block.parent.font };
-               int tw = 0, th = 0;
+               int th = 0;
                display.FontExtent(block.font.font, " ", 1, null, &th);
                if(!block.prev)
                {
@@ -2997,7 +3465,7 @@ class HelpView : HTMLView
                }
 
                {
-                  int tw = 0, th = 0;
+                  int th = 0;
                   int textPos = 0;
                   int sx = textBlock.startX, sy = textBlock.startY;
                   char * text = textBlock.text;
@@ -3107,7 +3575,7 @@ class HelpView : HTMLView
             case Key { down, shift = true }:
             case down:
             {
-               int tw = 0, th = 0;
+               int th = 0;
                int textPos = 0;
                int sx = textBlock.startX, sy = textBlock.startY;
                char * text = textBlock.text;
@@ -3503,7 +3971,7 @@ class HelpView : HTMLView
                break;
             case enter:
             {
-               int tw = 0, th = 0;
+               int th = 0;
                Block block;
                Block newBlock;
                int startY, startX;
@@ -3590,7 +4058,7 @@ class HelpView : HTMLView
                               Block block { type = BR, parent = parent, font = font };
                               Block newBlock { type = TEXT, parent = parent, font = font };
                               int startY = textBlock.startY, startX = textBlock.startX;
-                              int tw = 0, th = 0;
+                              int th = 0;
 
                               display.FontExtent(textBlock.font.font, " ", 1, null, &th);
                               textBlock.parent.subBlocks.Insert(textBlock, block);
@@ -3815,7 +4283,6 @@ class HelpView : HTMLView
 
          while(textPos < textBlock.textLen)
          {
-            int startPos = textPos;
             int width = 0;
             int x = 0;
             bool lineComplete = false;
@@ -3886,18 +4353,17 @@ class Documentor : GuiApplication
    bool Init()
    {
       Platform os = __runtimePlatform;
-      componentsApp = __ecere_COM_Initialize(false, 1, null);
-      SetPrivateModule(componentsApp);
       SetGlobalContext(globalContext);
       SetExcludedSymbols(&excludedSymbols);
       SetDefines(&::defines);
       SetImports(&imports);
+      SetInDocumentor(true);
 
       SetGlobalData(globalData);
 
-      settingsContainer.dataOwner = &settings;
+      settingsContainer.dataOwner = &ideSettings;
       settingsContainer.Load();
-      if(!settings.docDir || !settings.docDir[0] )
+      if(!ideSettings.docDir || !ideSettings.docDir[0] )
       {
          if(os == win32) // if Windows OS then
          {
@@ -3911,23 +4377,23 @@ class Documentor : GuiApplication
             if(GetEnvironment("ProgramFiles", programFilesDir, MAX_LOCATION))
             {
                PathCat(programFilesDir, "ECERE SDK\\doc");
-               settings.docDir = programFilesDir;
+               ideSettings.docDir = programFilesDir;
             }
-            else if(homeDrive && homeDrive[0])
+            else if(homeDrive[0])
             {
                PathCat(homeDrive, "ECERE SDK\\doc");
-               settings.docDir = homeDrive;
+               ideSettings.docDir = homeDrive;
             }
-            else if(winDir && winDir[0])
+            else if(winDir[0])
             {
                PathCat(winDir, "..\\ECERE SDK\\doc");
-               settings.docDir = winDir;
+               ideSettings.docDir = winDir;
             }
             else
-               settings.docDir = "C:\\ECERE SDK\\doc";
+               ideSettings.docDir = "C:\\ECERE SDK\\doc";
          }
          else // if Os is Linux, or Mac OSX or something else
-            settings.docDir = "/usr/share/ecere/doc/";
+            ideSettings.docDir = "/usr/share/ecere/doc/";
          settingsContainer.Save();
       }
 
@@ -4006,3 +4472,197 @@ Thread commandThread
       return 0;
    }
 };
+#endif // !defined(EAR_TO_ECON_ECDOC)
+
+class ItemDoc
+{
+public:
+   property String name { get { return this ? name : null; } set { delete name; name = CopyString(value); } isset { return name && *name; } }
+   property String description { get { return this ? description : null; } set { delete description; description = CopyString(value); } isset { return description && *description; } }
+private:
+   char * name;
+   char * description;
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (name && *name) ||
+            (description && *description));
+      }
+   }
+   ~ItemDoc()
+   {
+      delete name;
+      delete description;
+   }
+}
+
+class MoreDoc : ItemDoc
+{
+public:
+   property String usage { get { return this ? usage : null; } set { delete usage; usage = CopyString(value); } isset { return usage && *usage; } }
+   property String example { get { return this ? example : null; } set { delete example; example = CopyString(value); } isset { return example && *example; } }
+   property String remarks { get { return this ? remarks : null; } set { delete remarks; remarks = CopyString(value); } isset { return remarks && *remarks; } }
+   property String also { get { return this ? also : null; } set { delete also; also = CopyString(value); } isset { return also && *also; } }
+private:
+   char * usage;
+   char * example;
+   char * remarks;
+   char * also;
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (usage && *usage) ||
+            (example && *example) ||
+            (remarks && *remarks) ||
+            (also && *also) ||
+            !ItemDoc::isEmpty);
+      }
+   }
+   ~MoreDoc()
+   {
+      delete usage;
+      delete example;
+      delete remarks;
+      delete also;
+   }
+}
+
+class NamespaceDoc : ItemDoc
+{
+public:
+   Map<String, DefineDoc> defines;
+   Map<String, FunctionDoc> functions;
+private:
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (defines && defines.count) ||
+            (functions && functions.count) ||
+            !ItemDoc::isEmpty);
+      }
+   }
+   ~NamespaceDoc()
+   {
+      delete defines;
+      delete functions;
+   }
+}
+
+class DefineDoc : ItemDoc { }
+
+class FunctionDoc : MoreDoc
+{
+public:
+   Map<String, ParameterDoc> parameters;
+   property String returnValue { get { return this ? returnValue : null; } set { delete returnValue; returnValue = CopyString(value); } isset { return returnValue && *returnValue; } }
+private:
+   char * returnValue;
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (parameters && parameters.count) ||
+            (returnValue && *returnValue) ||
+            !MoreDoc::isEmpty);
+      }
+   }
+   ~FunctionDoc()
+   {
+      delete parameters;
+      delete returnValue;
+   }
+}
+
+class ParameterDoc : ItemDoc
+{
+public:
+   uint position;
+}
+
+class ClassDoc : MoreDoc
+{
+public:
+   Map<String, ValueDoc> values;
+   Map<String, FieldDoc> fields;
+   Map<String, PropertyDoc> properties;
+   Map<String, ConversionDoc> conversions;
+   Map<String, MethodDoc> methods;
+private:
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (values && values.count) ||
+            (fields && fields.count) ||
+            (properties && properties.count) ||
+            (conversions && conversions.count) ||
+            (methods && methods.count) ||
+            !MoreDoc::isEmpty);
+      }
+   }
+   ~ClassDoc()
+   {
+      delete values;
+      delete fields;
+      delete properties;
+      delete conversions;
+      delete methods;
+   }
+}
+
+class ValueDoc : ItemDoc { }
+
+class FieldDoc : ItemDoc { }
+
+class PropertyDoc : ItemDoc { }
+
+class ConversionDoc : ItemDoc { }
+
+class MethodDoc : FunctionDoc { }
+
+char * getDocFileNameFromTypeName(const char * typeName)
+{
+   char * docFileName = new char[MAX_FILENAME];
+   const char * swap = "pointer";
+   const char * s = typeName;
+   char * d = docFileName;
+   const char * end = s + strlen(typeName);
+   int swapLen = strlen(swap);
+   for(; s < end; s++)
+   {
+      if(*s == ' ')
+         *d = '-';
+      else if(*s == '*')
+      {
+         strcpy(d, swap);
+         d += swapLen;
+      }
+      else
+         *d = *s;
+      d++;
+   }
+   *d = '\0';
+   return docFileName;
+}
+
+class DocCacheEntry //: struct      // TOCHECK: Why does this causes an error: 'struct __ecereNameSpace__ecere__com__MapIterator' has no member named 'data'
+{
+public:
+   Time timeStamp;      // Should this be last accessed, or last retrieved?
+   ItemDoc doc;
+
+   ~DocCacheEntry()
+   {
+      delete doc;
+   }
+}
+
+Map<String, DocCacheEntry> docCache { };