documentor: Alt-Left/Right shortcuts when edit pane is active
[sdk] / documentor / src / Documentor.ec
index 1a995c9..c3e6212 100644 (file)
@@ -1,4 +1,5 @@
 import "ecere"
+#if !defined(EAR_TO_ECON_ECDOC)
 import "ec"
 import "HTMLView"
 import "IDESettings"
@@ -8,7 +9,10 @@ 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;
 
 #define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
 
@@ -17,17 +21,17 @@ 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;
 
 enum CodeObjectType { typeClass, typeData, typeMethod, typeEvent, typeProperty, typeNameSpace, typeDataType, typeEnumValue, typeDataPrivate, typeMethodPrivate, typePropertyPrivate };
 
-static char * iconNames[CodeObjectType] = 
+static const char * iconNames[CodeObjectType] =
 {
    "<:ecere>constructs/class.png",
    "<:ecere>constructs/data.png",
@@ -42,20 +46,66 @@ static 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
+void GetTemplateString(Class c, char * templateString)
 {
-   driver = "JSON";
-   data = settings;
-   dataOwner = &settings;
-};
+   Module m = c.module.application;
+   const char * n = c.name;
+   char * lt = strchr(n, '<');
+   char * s;
+   char ch;
+   char curName[256];
+   int len = 0;
+
+   memcpy(templateString, n, lt-n);
+   templateString[lt-n] = 0;
+   strcat(templateString, "</a>");
+
+   for(s = lt; (ch = *s); s++)
+   {
+      if(ch == '<' || ch == '>' || ch == ',')
+      {
+         if(len)
+         {
+            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, "%s<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", !strncmp(curName, "const ", 6) ? "const " : "", pc, pc.name);
+            else
+               strcat(d, curName);
+         }
+         if(ch == '<')
+            strcat(templateString, "&lt;");
+         else if(ch == '>')
+            strcat(templateString, "&gt;");
+         else
+            strcat(templateString, ", ");
+         len = 0;
+      }
+      else if(ch == '=')
+      {
+         curName[len++] = ' ';
+         curName[len++] = ch;
+         curName[len++] = ' ';
+         curName[0] = 0;
+         strcat(templateString, curName);
+         len = 0;
+      }
+      else
+         curName[len++] = ch;
+   }
+}
 
 // WARNING : This function expects a null terminated string since it recursively concatenate...
 static void _PrintType(Type type, char * string, bool printName, bool printFunction, bool fullName)
 {
    if(type)
    {
+      if(type.constant && (type.kind != pointerType && type.kind != arrayType))
+         strcat(string, "const ");
       switch(type.kind)
       {
          case classType:
@@ -67,16 +117,24 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                {
                   if(type._class.registered)
                   {
-                     char hex[10];
-                     sprintf(hex, "%08x", type._class.registered);
+                     char hex[20];
+                     const char * s = type._class.registered.name;
+                     sprintf(hex, "%p", type._class.registered.templateClass ? type._class.registered.templateClass : type._class.registered);
                      strcat(string, "<a href=\"api://");
                      strcat(string, hex);
                      strcat(string, "\" style=\"text-decoration: none;\">");
-                     strcat(string, type._class.registered.name);
+                     if(strchr(s, '<'))
+                     {
+                        char n[1024];
+                        GetTemplateString(type._class.registered, n);
+                        strcat(string, n);
+                     }
+                     else
+                        strcat(string, type._class.registered.name);
                      strcat(string, "</a>");
                   }
                   else
-                     strcat(string, type._class.string);                     
+                     strcat(string, type._class.string);
                }
             }
             break;
@@ -106,7 +164,7 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                   DocPrintType(param, string, false, fullName);
                   if(param.next) strcat(string, ", ");
                }
-               strcat(string, ")");               
+               strcat(string, ")");
             }
             else*/
             {
@@ -189,7 +247,7 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                DocPrintType(type.returnType, string, false, fullName);
                strcat(string, " ");
             }
-            
+
             // DANGER: Testing This
             if(printName)
             {
@@ -206,10 +264,6 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                      strcat(string, "</b>");
                   }
                }
-               else
-               {
-                  printf("");
-               }
             }
 
             if(printFunction)
@@ -251,7 +305,7 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                   DocPrintType(param, string, false, fullName);
                   if(param.next) strcat(string, ", ");
                }
-               strcat(string, ")");               
+               strcat(string, ")");
             }
             else*/
             {
@@ -267,7 +321,7 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                      strcat(size, arrayType.enumClass.string);
                   else if(arrayType.arraySizeExp)
                      PrintExpression(arrayType.arraySizeExp, size);
-                  //sprintf(string, "%s[%s]", baseType, size); 
+                  //sprintf(string, "%s[%s]", baseType, size);
                   strcat(size, "]");
 
                   arrayType = arrayType.arrayType;
@@ -283,10 +337,10 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
                   strcpy(size, type.enumClass.string);
                else if(type.arraySizeExp)
                   PrintExpression(type.arraySizeExp, size);
-               //sprintf(string, "%s[%s]", baseType, size); 
+               //sprintf(string, "%s[%s]", baseType, size);
                strcat(string, baseType);
                strcat(string, "[");
-               strcat(string, size); 
+               strcat(string, size);
                strcat(string, "]");
                */
 
@@ -302,10 +356,10 @@ static void _PrintType(Type type, char * string, bool printName, bool printFunct
          case subClassType:
             strcat(string, "subclass(");
             strcat(string, type._class ? type._class.string : "int");
-            strcat(string, ")");                  
+            strcat(string, ")");
             break;
          default:
-            printf("");
+            break;
       }
       if(type.name && printName && type.kind != functionType && (type.kind != pointerType || type.type.kind != functionType))
       {
@@ -321,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);
@@ -348,16 +401,18 @@ 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 = (int)null;
+      row.tag = (int64)null;
       AddNameSpace(row, null, module.application.systemNameSpace, null, "", !isDll);
    }
 
@@ -368,11 +423,12 @@ 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 = (int)module;
+      row.tag = (int64)module;
       AddNameSpace(row, module, module.publicNameSpace, null /*module.application.systemNameSpace*/, "", !isDll);
       if(!isDll)
          AddNameSpace(row, module, module.privateNameSpace, null /*module.application.systemNameSpace*/, "", !isDll);
@@ -382,12 +438,12 @@ void AddComponents(Module module, bool isDll)
 class APIPage
 {
 public:
-   char * name;
+   const char * name;
    APIPage page;
-   char * label;
+   const char * label;
    bool showPrivate;
 
-   char * OnGetString(char * tempString, void * fieldData, bool * needClass)
+   const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
    {
       return name;
    }
@@ -396,10 +452,21 @@ public:
    {
       page.Generate(f);
    }
+
+   virtual Module GetModule()
+   {
+      return page ? page.GetModule() : null;
+   }
+
+   virtual NameSpace * GetNameSpace()
+   {
+      return page ? page.GetNameSpace() : null;
+   }
 };
 
 enum DocumentationType
 {
+   unset,
    nameSpaceDoc,
    classDoc,
    functionDoc,
@@ -408,6 +475,7 @@ enum DocumentationType
 
 enum DocumentationItem
 {
+   unset,
    description,
    usage,
    remarks,
@@ -424,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;
@@ -441,133 +555,242 @@ 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);
-
-   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)
-   {
-      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:
-      {
-         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;
 
-   FigureFileName(fileName, module, type, object, item, data);
-   file = FileOpen(fileName, read);
-   if(file)
+   ItemDoc doc = getDoc(filePath, module, type, object, item, data, false);
+
+   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)
-      contents = CopyString("[Add Text]");
+   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
 {
    NameSpace * nameSpace;
    Module module;
-   
+
+   Module GetModule()
+   {
+      return module;
+   }
+
+   NameSpace * GetNameSpace()
+   {
+      return nameSpace;
+   }
+
    void Generate(File f)
    {
-      char string[1024];
       char nsName[1024], temp[1024];
       NameSpace * ns;
       BTNamedLink link;
-      uint tag;
 
       nsName[0] = 0;
       ns = nameSpace;
@@ -580,18 +803,14 @@ class APIPageNameSpace : APIPage
          ns = ns->parent;
       }
       // Generate Class Page
-      f.Printf("<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
+      f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
       if(nsName[0])
       {
          f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", nsName );
-         tag = (uint)nameSpace;
-         f.Printf("Module: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
+         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 = (uint)((!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);
-      }
+         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;
@@ -603,25 +822,26 @@ class APIPageNameSpace : APIPage
          strcpy(nsName, temp);
          ns = ns->parent;
       }
-      if(nsName[0]) 
-         f.Printf("Parent namespace: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", nameSpace->parent, nsName);
+      if(nsName[0])
+         f.Printf($"Parent namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", nameSpace->parent, nsName);
 
       f.Printf("<br>");
       {
          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;
          }
       }
@@ -634,12 +854,12 @@ class APIPageNameSpace : APIPage
             char * desc = ReadDoc(module, nameSpaceDoc, ns, description, null);
             if(first)
             {
-               f.Printf("<H3>Sub Namespaces</H3><br><br>\n");
-               f.Printf("<TABLE >\n");
+               f.Printf($"<H3>Sub Namespaces</H3><BR>\n");
+               f.Printf("<TABLE>\n");
                first = false;
             }
             f.Printf("<TR>");
-            f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeNameSpace], ns, ns->name);
+            f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeNameSpace], ns, ns->name);
             if(desc)
             {
                if(editing)
@@ -657,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)
@@ -666,20 +886,21 @@ 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("<TABLE >\n");
+                  f.Printf($"<a name=Classes></a><H3>Classes</H3><BR>\n");
+                  f.Printf("<TABLE>\n");
                   first = false;
                }
 
                f.Printf("<TR>");
 
-               f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a></TD>", (cl.type == enumClass || cl.type == unitClass || cl.type == systemClass) ? iconNames[typeDataType] : iconNames[typeClass], cl, cl.name);
+               f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", (cl.type == enumClass || cl.type == unitClass || cl.type == systemClass) ? iconNames[typeDataType] : iconNames[typeClass], cl, cl.name);
                if(desc)
                {
                   if(editing)
@@ -693,12 +914,12 @@ class APIPageNameSpace : APIPage
                   else
                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
                   delete desc;
-               }            
+               }
                f.Printf("</TR>\n");
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       if(nameSpace->functions.first)
@@ -707,17 +928,18 @@ 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);
-            char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
+            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("<TABLE >\n");
+               f.Printf($"<a name=Functions></a><H3>Functions</H3><BR>\n");
+               f.Printf("<TABLE>\n");
                first = false;
             }
             f.Printf("<TR>");
-            f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeMethod], function, name);
+            f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeMethod], function, name);
             if(desc)
             {
                if(editing)
@@ -735,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)
@@ -747,12 +969,12 @@ 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("<TABLE >\n");
+               f.Printf($"<a name=Definitions></a><H3>Definitions</H3><BR>\n");
+               f.Printf("<TABLE>\n");
                first = false;
             }
             f.Printf("<TR>");
-            f.Printf("<TD valign=top height=22 nowrap=1><a name=%08x></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", def, iconNames[typeData], def.name);
+            f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", def, iconNames[typeData], def.name);
             f.Printf("<TD valign=top height=22>%s</TD>", def.value);
             if(desc)
             {
@@ -771,23 +993,32 @@ 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");
-   }   
+   }
 }
 
 class APIPageClass : APIPage
 {
    Class cl;
 
+   Module GetModule()
+   {
+      return cl.module;
+   }
+
+   NameSpace * GetNameSpace()
+   {
+      return cl.nameSpace;
+   }
+
    void Generate(File f)
    {
       char string[1024];
       Method method;
       Property prop;
-      DataMember member;
       char nsName[1024], temp[1024];
       NameSpace * ns = cl.nameSpace;
       Module module = cl.module;
@@ -802,53 +1033,53 @@ class APIPageClass : APIPage
          ns = ns->parent;
       }
       // Generate Class Page
-      f.Printf("<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
+      f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
       f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", name);
 
-      f.Printf("Module: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
-      if(nsName[0]) 
-         f.Printf("Namespace: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", cl.nameSpace, nsName);
+      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);
+      if(nsName[0])
+         f.Printf($"Namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", cl.nameSpace, nsName);
 
       {
-         char * classType = null;
+         const char * classType = null;
          switch(cl.type)
          {
             case bitClass:
-               classType = "Bit Collection";
+               classType = $"Bit Collection";
                break;
             case enumClass:
-               classType = "Enumeration";
+               classType = $"Enumeration";
                break;
             case structClass:
-               classType = "Structure";
+               classType = $"Structure";
                break;
             case normalClass:
-               classType = "Class";
+               classType = $"Class";
                break;
             case noHeadClass:
-               classType = "Class (No header)";
+               classType = $"Class (No header)";
                break;
             case unitClass:
-               classType = "Unit";
+               classType = $"Unit";
                break;
             case systemClass:
-               classType = "Basic Data Type";
+               classType = $"Basic Data Type";
                break;
          }
-         f.Printf("Type: %s<br>\n", classType);
+         f.Printf($"Type: %s<br>\n", classType);
       }
-      
+
       if(cl.type != systemClass && cl.base)
       {
-         f.Printf("Base Class: ");
+         f.Printf($"Base Class: ");
          if(!strcmp(cl.base.name, "struct") || !strcmp(cl.base.name, "class"))
          {
-            f.Printf(cl.type == bitClass ? cl.dataTypeString : "None");
+            f.Printf(cl.type == bitClass ? cl.dataTypeString : $"None");
          }
          else if(cl.type == enumClass && !strcmp(cl.base.name, "enum"))
             f.Printf("%s", cl.dataTypeString);
          else
-            f.Printf("<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a>", cl.base, cl.base.name);
+            f.Printf("<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", cl.base.templateClass ? cl.base.templateClass : cl.base, cl.base.name);
          f.Printf("<br>\n");
       }
 
@@ -856,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;
          }
       }
@@ -877,9 +1109,9 @@ class APIPageClass : APIPage
          if(enumeration.values.first)
          {
             NamedLink item;
-                        
-            f.Printf("<a name=EnumerationValues></a><H3>Enumeration Values</H3><br><br>\n");
-            f.Printf("<TABLE >\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)
             {
@@ -887,6 +1119,8 @@ class APIPageClass : APIPage
                bool needClass = true;
                Class dataClass;
                Class base = cl;
+               char tempString[1024];
+               String s;
                while(base.type == enumClass) base = base.base;
 
                if(base.type == systemClass ||
@@ -908,21 +1142,21 @@ class APIPageClass : APIPage
                      dataClass = base.dataType._class ? base.dataType._class.registered : null;
                }
                else
-                  dataClass = base;                  
-               
+                  dataClass = base;
+
                f.Printf("<TR>");
-               f.Printf("<TD valign=top height=22 nowrap=1><a name=%08x></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", item, iconNames[typeEnumValue], item.name);
+               f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", item, iconNames[typeEnumValue], item.name);
                if(dataClass.type == systemClass)
                {
                   needClass = false;
-                  dataClass._vTbl[__ecereVMethodID_class_OnGetString](dataClass, &item.data, string, sizeof(string), &needClass);
+                  s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)dataClass._vTbl[__ecereVMethodID_class_OnGetString])(dataClass, &item.data, tempString, null, &needClass);
                }
                else
-                  eSystem_FindClass(componentsApp, "class")._vTbl[__ecereVMethodID_class_OnGetString](dataClass, &item.data, string, sizeof(string), &needClass);
+                  s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)eSystem_FindClass(componentsApp, "class")._vTbl[__ecereVMethodID_class_OnGetString])(dataClass, &item.data, tempString, null, &needClass);
                if(needClass)
-                  f.Printf("<TD valign=top height=22 nowrap=1>%s { %s }</TD>", dataClass.name, string);
+                  f.Printf("<TD valign=top height=22 nowrap=1>%s { %s }</TD>", dataClass.name, s);
                else
-                  f.Printf("<TD valign=top height=22 nowrap=1>%s</TD>", string);
+                  f.Printf("<TD valign=top height=22 nowrap=1>%s</TD>", s);
                if(desc)
                {
                   if(editing)
@@ -945,25 +1179,24 @@ class APIPageClass : APIPage
 
       if(cl.conversions.first)
       {
-         f.Printf("<a name=Conversions></a><H3>Conversions</H3><br><br>\n");
-         f.Printf("<TABLE >\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;
-               char * name;
+               const char * name;
                Type type = ProcessTypeString(prop.name, false);
                name = RSearchString(prop.name, "::", strlen(prop.name), true, false);
                if(name) name += 2; else name = prop.name;
 
                f.Printf("<TR>");
-               
+
                string[0] = 0;
                DocPrintType(type, string, true, false);
-               
-               f.Printf("<TD valign=top height=22 nowrap=1><a name=%08x></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", prop, iconNames[typeDataType], string);
+
+               f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", prop, iconNames[typeDataType], string);
                if(desc)
                {
                   if(editing)
@@ -978,13 +1211,13 @@ class APIPageClass : APIPage
                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
                   delete desc;
                }
-               
+
                f.Printf("</TR>\n");
-               
+
                FreeType(type);
             }
          }
-         f.Printf("</TABLE><br>\n");
+         f.Printf("</TABLE><br><br>\n");
       }
 
       if(cl.membersAndProperties.first)
@@ -996,8 +1229,8 @@ class APIPageClass : APIPage
             {
                if(first)
                {
-                  f.Printf("<a name=Members></a><H3>Properties and Members</H3><br><br>\n");
-                  f.Printf("<TABLE >\n");
+                  f.Printf($"<a name=Members></a><H3>Properties and Members</H3><BR>\n");
+                  f.Printf("<TABLE>\n");
                   first = false;
                }
 
@@ -1011,7 +1244,7 @@ class APIPageClass : APIPage
                   string[0] = 0;
                   DocPrintType(prop.dataType, string, true, false);
 
-                  f.Printf("<TD valign=top height=22 nowrap=1><a name=%08x></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", prop, iconNames[typeProperty], prop.name);
+                  f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", prop, iconNames[typeProperty], prop.name);
                   f.Printf("<TD valign=top height=22 nowrap=1>%s</TD>", string);
                   if(desc)
                   {
@@ -1036,7 +1269,7 @@ class APIPageClass : APIPage
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
       }
 
       if(cl.methods.first)
@@ -1050,15 +1283,15 @@ 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("<TABLE >\n");
+                  f.Printf($"<a name=VirtualMethods></a><H3>Virtual Methods</H3><BR>\n");
+                  f.Printf("<TABLE>\n");
                   first = false;
                }
                if(!method.dataType)
                   ProcessMethodType(method);
 
                f.Printf("<TR>");
-               f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a></TD>", method.dataType.thisClass ? iconNames[typeEvent] : iconNames[typeMethod], method, method.name);
+               f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", method.dataType.thisClass ? iconNames[typeEvent] : iconNames[typeMethod], method, method.name);
                if(desc)
                {
                   if(editing)
@@ -1077,7 +1310,7 @@ class APIPageClass : APIPage
             }
          }
          if(!first)
-            f.Printf("</TABLE><br>\n");
+            f.Printf("</TABLE><br><br>\n");
 
          // Non-Virtual Methods
          first = true;
@@ -1088,8 +1321,8 @@ 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("<TABLE >\n");
+                  f.Printf($"<a name=Methods></a><H3>Non-Virtual Methods</H3><BR>\n");
+                  f.Printf("<TABLE>\n");
                   first = false;
                }
 
@@ -1097,7 +1330,7 @@ class APIPageClass : APIPage
                   ProcessMethodType(method);
 
                f.Printf("<TR>");
-               f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeMethod], method, method.name);
+               f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeMethod], method, method.name);
                if(desc)
                {
                   if(editing)
@@ -1112,18 +1345,18 @@ class APIPageClass : APIPage
                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
                   delete desc;
                }
-               
+
                f.Printf("</TR><br>\n");
             }
          }
          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];
@@ -1134,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;
          }
       }
@@ -1142,9 +1375,9 @@ class APIPageClass : APIPage
          char * exampleDoc = ReadDoc(module, classDoc, cl, example, null);
          if(exampleDoc)
          {
-            f.Printf("<H3>Example</H3><br>\n");
-            f.Printf("<FONT face=\"Courier New\">\n");
-            f.Printf("<br><TABLE >\n");
+            f.Printf($"<H3>Example</H3><BR>\n");
+            f.Printf($"<FONT face=\"Courier New\">\n");
+            f.Printf("<br><TABLE>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1157,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;
          }
       }
@@ -1166,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];
@@ -1177,11 +1410,11 @@ class APIPageClass : APIPage
             }
             else
                f.Printf("<br>%s\n", remarksDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete remarksDoc;
          }
       }
-      
+
       if(cl.type != systemClass)
       {
          bool first = true;
@@ -1194,14 +1427,13 @@ 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
                   f.Printf(", ");
-               f.Printf("<a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a>", deriv, deriv.name);
-             }            
+               f.Printf("<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", deriv, deriv.name);
+             }
          }
          if(!first)
             f.Printf("<br><br>\n");
@@ -1210,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];
@@ -1226,12 +1458,23 @@ class APIPageClass : APIPage
          }
       }
       f.Printf("</FONT></BODY></HTML>\n");
-   }   
+   }
 }
 
 class APIPageMethod : APIPage
 {
    Method method;
+
+   Module GetModule()
+   {
+      return method._class.module;
+   }
+
+   NameSpace * GetNameSpace()
+   {
+      return method._class.nameSpace;
+   }
+
    void Generate(File f)
    {
       Class cl = method._class;
@@ -1251,20 +1494,20 @@ class APIPageMethod : APIPage
          ns = ns->parent;
       }
 
-      f.Printf("<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
+      f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
       f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", name);
 
-      f.Printf("Module: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
+      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);
       if(nsName[0])
-         f.Printf("Namespace: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", cl.nameSpace, nsName);
-      f.Printf("Class: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", cl, cl.name);
+         f.Printf($"Namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", cl.nameSpace, nsName);
+      f.Printf("Class: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", cl, cl.name);
       if(method.dataType.staticMethod)
       {
-         f.Printf("this pointer class: None<br>\n");
+         f.Printf($"this pointer class: None<br>\n");
       }
       else if(method.dataType.thisClass && method.dataType.thisClass.registered && (method.dataType.thisClass.registered != method._class || method.type == virtualMethod))
       {
-         f.Printf("this pointer class: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", method.dataType.thisClass.registered, method.dataType.thisClass.registered.name);
+         f.Printf($"this pointer class: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", method.dataType.thisClass.registered, method.dataType.thisClass.registered.name);
       }
 
       // Generate Method Page
@@ -1278,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];
@@ -1289,6 +1532,7 @@ class APIPageMethod : APIPage
             }
             else
                f.Printf("%s", desc);
+            f.Printf("<BR><BR>");
             delete desc;
          }
       }
@@ -1296,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))
@@ -1324,13 +1568,13 @@ class APIPageMethod : APIPage
                   FigureFileName(fileName, module, methodDoc, method, parameter, param);
                   f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
                   f.Puts(desc);
-                  f.Printf("s</a>&nbsp;</TD>\n");
+                  f.Printf("</a></TD>\n");
                }
                else
                   f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", desc);
                delete desc;
             }
-            
+
             f.Printf("</TR>\n");
          }
       }
@@ -1342,7 +1586,7 @@ class APIPageMethod : APIPage
             f.Printf("<TR><TD>&nbsp;</TD></TR>");
          }
          f.Printf("<TR>");
-         f.Printf("<TD valign=top height=22 nowrap=1><B>Return Value</B></TD>\n");
+         f.Printf($"<TD valign=top height=22 nowrap=1><B>Return Value</B></TD>\n");
          string[0] = 0;
          DocPrintType(method.dataType.returnType, string, false, false);
          f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", string);
@@ -1372,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];
@@ -1383,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;
          }
       }
@@ -1391,9 +1635,9 @@ class APIPageMethod : APIPage
          char * exampleDoc = ReadDoc(module, methodDoc, method, example, null);
          if(exampleDoc)
          {
-            f.Printf("<H3>Example</H3><br>\n");
-            f.Printf("<FONT face=\"Courier New\">\n");
-            f.Printf("<br><TABLE >\n");
+            f.Printf($"<H3>Example</H3><BR>\n");
+            f.Printf($"<FONT face=\"Courier New\">\n");
+            f.Printf("<br><TABLE>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1405,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;
          }
       }
@@ -1413,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];
@@ -1424,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;
          }
       }
@@ -1432,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];
@@ -1443,18 +1687,29 @@ class APIPageMethod : APIPage
             }
             else
                f.Printf("<br>%s\n", method, seeAlsoDoc);
-            
-            f.Printf("<br><br>\n");
+
+            f.Printf("<br><br><br>\n");
             delete seeAlsoDoc;
          }
       }
       f.Printf("</FONT></BODY></HTML>\n");
-   }   
+   }
 }
 
 class APIPageFunction : APIPage
 {
    GlobalFunction function;
+
+   Module GetModule()
+   {
+      return function.module;
+   }
+
+   NameSpace * GetNameSpace()
+   {
+      return function.nameSpace;
+   }
+
    void Generate(File f)
    {
       char string[1024];
@@ -1473,20 +1728,20 @@ class APIPageFunction : APIPage
          ns = ns->parent;
       }
 
-      f.Printf("<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
+      f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
       f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", name);
 
-      f.Printf("Module: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
+      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);
 
       if(nsName[0])
-         f.Printf("Namespace: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", function.nameSpace, nsName);
+         f.Printf($"Namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", function.nameSpace, nsName);
 
       if(!function.dataType)
          function.dataType = ProcessTypeString(function.dataTypeString, false);
 
       if(function.dataType.thisClass && function.dataType.thisClass.registered)
       {
-         f.Printf("this pointer class: <a href=\"api://%08x\" style=\"text-decoration: none;\">%s</a><br>\n", function.dataType.thisClass.registered, function.dataType.thisClass.registered.name);
+         f.Printf($"this pointer class: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", function.dataType.thisClass.registered, function.dataType.thisClass.registered.name);
       }
 
       // Generate Method Page
@@ -1500,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];
@@ -1512,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))
@@ -1544,7 +1800,8 @@ class APIPageFunction : APIPage
                   char fileName[MAX_LOCATION];
                   FigureFileName(fileName, module, functionDoc, function, parameter, param);
                   f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
-                  f.Puts(desc);
+                  if(desc)
+                     f.Puts(desc);
                   f.Printf("</a>&nbsp;</TD>\n");
                }
                else
@@ -1562,7 +1819,7 @@ class APIPageFunction : APIPage
             f.Printf("<TR><TD>&nbsp;</TD></TR>");
          }
          f.Printf("<TR>");
-         f.Printf("<TD valign=top height=22 nowrap=1><B>Return Value</B></TD>\n");
+         f.Printf($"<TD valign=top height=22 nowrap=1><B>Return Value</B></TD>\n");
          string[0] = 0;
          DocPrintType(function.dataType.returnType, string, false, false);
          f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", string);
@@ -1592,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];
@@ -1603,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;
          }
       }
@@ -1611,9 +1868,9 @@ class APIPageFunction : APIPage
          char * exampleDoc = ReadDoc(module, functionDoc, function, example, null);
          if(exampleDoc)
          {
-            f.Printf("<H3>Example</H3><br>\n");
-            f.Printf("<FONT face=\"Courier New\">\n");
-            f.Printf("<br><TABLE >\n");
+            f.Printf($"<H3>Example</H3><BR>\n");
+            f.Printf($"<FONT face=\"Courier New\">\n");
+            f.Printf("<br><TABLE>\n");
             if(editing)
             {
                char fileName[MAX_LOCATION];
@@ -1625,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;
          }
       }
@@ -1633,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];
@@ -1644,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;
          }
       }
@@ -1652,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];
@@ -1663,15 +1920,15 @@ class APIPageFunction : APIPage
             }
             else
                f.Printf("<br>%s\n", seeAlsoDoc);
-            f.Printf("<br><br>\n");
+            f.Printf("<br><br><br>\n");
             delete seeAlsoDoc;
          }
       }
       f.Printf("</FONT></BODY></HTML>\n");
-   }   
+   }
 }
 
-static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpace, NameSpace comNameSpace, char * parentName, bool showPrivate)
+static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpace, NameSpace comNameSpace, const char * parentName, bool showPrivate)
 {
    char nsName[1024];
    NameSpace * ns;
@@ -1681,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)
    {
@@ -1695,7 +1950,7 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
    {
       row = parentRow.AddRow();
       row.SetData(null, (page = APIPageNameSpace { nameSpace->name, module = module, nameSpace = nameSpace, showPrivate = showPrivate }));
-      row.tag = (int)nameSpace;
+      row.tag = (int64)nameSpace;
       row.icon = mainForm.icons[typeNameSpace];
    }
    else
@@ -1705,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)
@@ -1735,9 +1987,9 @@ 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; }
+                  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);
                }
             }
@@ -1749,7 +2001,7 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
    {
       for(nameSpace = mainNameSpace ; nameSpace; nameSpace = (nameSpace == mainNameSpace) ? comNameSpace : null)
       {
-         if(nameSpace->functions.first)                            
+         if(nameSpace->functions.first)
          {
             BTNamedLink link;
             GlobalFunction fn;
@@ -1758,16 +2010,16 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
                fn = link.data;
                if(!module || fn.module == module || (!fn.module.name && !strcmp(module.name, "ecere")))
                {
-                  char * name = ( name = RSearchString(fn.name, "::", strlen(fn.name), false, false), name ? name + 2 : fn.name);
+                  const char * name = ( name = RSearchString(fn.name, "::", strlen(fn.name), false, false), name ? name + 2 : fn.name);
                   DataRow fnRow;
-                  if(!functionsRow) { functionsRow = row.AddRow(); functionsRow.SetData(null, APIPage { "Functions", page = page }); functionsRow.collapsed = true; functionsRow.icon = mainForm.icons[typeMethod];  functionsRow.tag = 2; };
-                  fnRow = functionsRow.AddRow(); fnRow.SetData(null, APIPageFunction { name, function = fn }); fnRow.icon = mainForm.icons[typeMethod]; fnRow.tag = (int)fn;
+                  if(!functionsRow) { functionsRow = row.AddRow(); functionsRow.SetData(null, APIPage { $"Functions", page = page }); functionsRow.collapsed = true; functionsRow.icon = mainForm.icons[typeMethod];  functionsRow.tag = 2; };
+                  fnRow = functionsRow.AddRow(); fnRow.SetData(null, APIPageFunction { name, function = fn }); fnRow.icon = mainForm.icons[typeMethod]; fnRow.tag = (int64)fn;
                }
-            }            
+            }
          }
       }
    }
-   
+
    if(mainNameSpace.defines.first || (comNameSpace && comNameSpace.defines.first))
    {
       for(nameSpace = mainNameSpace ; nameSpace; nameSpace = (nameSpace == mainNameSpace) ? comNameSpace : null)
@@ -1783,10 +2035,10 @@ static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpa
                {
                   char * name = ( name = RSearchString(def.name, "::", strlen(def.name), false, false), name ? name + 2 : def.name);
                   DataRow defRow;
-                  if(!definesRow) { definesRow = row.AddRow(); definesRow.SetData(null, APIPage { "Definitions", page = page }); definesRow.collapsed = true; definesRow.icon = mainForm.icons[typeData]; definesRow.tag = 3; };
-                  defRow = definesRow.AddRow(); defRow.SetData(null, APIPage { name, page = page }); defRow.icon = mainForm.icons[typeData]; defRow.tag = (int)def;
+                  if(!definesRow) { definesRow = row.AddRow(); definesRow.SetData(null, APIPage { $"Definitions", page = page }); definesRow.collapsed = true; definesRow.icon = mainForm.icons[typeData]; definesRow.tag = 3; };
+                  defRow = definesRow.AddRow(); defRow.SetData(null, APIPage { name, page = page }); defRow.icon = mainForm.icons[typeData]; defRow.tag = (int64)def;
                }
-            }            
+            }
          }
       }
    }
@@ -1803,7 +2055,7 @@ static void AddDataMemberToPage(File f, DataMember member, int indent, bool show
    string[0] = 0;
    DocPrintType(member.dataType, string, true, false);
 
-   f.Printf("<TD valign=top height=22 nowrap=1><a name=%08x></a>", member);
+   f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a>", member);
    for(c = 0; c<indent; c++)
       f.Printf("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
    f.Printf("<img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", iconNames[typeData], member.name ? member.name : ((member.type == structMember) ? "(struct)" : "(union)"));
@@ -1828,7 +2080,7 @@ static void AddDataMemberToPage(File f, DataMember member, int indent, bool show
    }
    else
       f.Printf("<TD valign=top height=22></TD>");
-   
+
    if(member.type != normalMember)
    {
       DataMember subMember;
@@ -1849,14 +2101,14 @@ static void AddDataMember(DataRow parentRow, APIPage page, DataMember member)
    if(member.type == normalMember)
    {
       row = parentRow.AddRow(); row.SetData(null, APIPage { member.name, page = page }); row.icon = mainForm.icons[typeData];
-      row.tag = (int)member;
+      row.tag = (int64)member;
    }
    else
    {
       DataMember m;
       row = parentRow.AddRow(); row.SetData(null, APIPage { (member.type == unionMember) ? "(union)" : "(struct)", page });
       row.icon = mainForm.icons[typeData];
-      row.tag = (int)member;
+      row.tag = (int64)member;
 
       for(m = member.members.first; m; m = m.next)
       {
@@ -1868,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;
@@ -1881,7 +2129,7 @@ static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName,
 
    row = parentRow.AddRow();
    row.SetData(null, (page = APIPageClass { cl.name, cl = cl, showPrivate = showPrivate }));
-   row.tag = (int)cl;
+   row.tag = (int64)cl;
    row.collapsed = true;
    row.icon = (cl.type == enumClass || cl.type == unitClass || cl.type == systemClass) ? mainForm.icons[typeDataType] : mainForm.icons[typeClass];
 
@@ -1899,22 +2147,22 @@ static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName,
             {
                if(method.dataType.thisClass)
                {
-                  if(!eventsRow) { eventsRow = row.AddRow(); eventsRow.SetData(null, APIPage { "Events", page = page }); eventsRow.collapsed = true; eventsRow.icon = mainForm.icons[typeEvent];  eventsRow.tag = 4; }
+                  if(!eventsRow) { eventsRow = row.AddRow(); eventsRow.SetData(null, APIPage { $"Events", page = page }); eventsRow.collapsed = true; eventsRow.icon = mainForm.icons[typeEvent];  eventsRow.tag = 4; }
                   mRow = eventsRow.AddRow(); mRow.SetData(null, APIPageMethod { method.name, method = method }); mRow.icon = mainForm.icons[typeEvent];
-                  mRow.tag = (int)method;
+                  mRow.tag = (int64)method;
                }
                else
                {
-                  if(!virtualsRow) { virtualsRow = row.AddRow(); virtualsRow.SetData(null, APIPage { "Virtual Methods", page = page }); virtualsRow.collapsed = true; virtualsRow.icon = mainForm.icons[typeMethod]; virtualsRow.tag = 4; }
+                  if(!virtualsRow) { virtualsRow = row.AddRow(); virtualsRow.SetData(null, APIPage { $"Virtual Methods", page = page }); virtualsRow.collapsed = true; virtualsRow.icon = mainForm.icons[typeMethod]; virtualsRow.tag = 4; }
                   mRow = virtualsRow.AddRow(); mRow.SetData(null, APIPageMethod { method.name, method = method }); mRow.icon = mainForm.icons[typeMethod];
-                  mRow.tag = (int)method;
+                  mRow.tag = (int64)method;
                }
             }
             else
             {
-               if(!methodsRow) { methodsRow = row.AddRow(); methodsRow.SetData(null, APIPage { "Methods", page = page }); methodsRow.collapsed = true; methodsRow.icon = mainForm.icons[typeMethod]; methodsRow.tag = 5; }
+               if(!methodsRow) { methodsRow = row.AddRow(); methodsRow.SetData(null, APIPage { $"Methods", page = page }); methodsRow.collapsed = true; methodsRow.icon = mainForm.icons[typeMethod]; methodsRow.tag = 5; }
                mRow = methodsRow.AddRow(); mRow.SetData(null, APIPageMethod { method.name, method = method }); mRow.icon = mainForm.icons[typeMethod];
-               mRow.tag = (int)method;
+               mRow.tag = (int64)method;
             }
          }
       }
@@ -1931,13 +2179,13 @@ static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName,
             if(prop.isProperty)
             {
                DataRow mRow;
-               if(!propertiesRow) { propertiesRow = row.AddRow(); propertiesRow.SetData(null, APIPage { "Properties", page = page }); propertiesRow.collapsed = true; propertiesRow.icon = mainForm.icons[typeProperty]; propertiesRow.tag = 6; }
+               if(!propertiesRow) { propertiesRow = row.AddRow(); propertiesRow.SetData(null, APIPage { $"Properties", page = page }); propertiesRow.collapsed = true; propertiesRow.icon = mainForm.icons[typeProperty]; propertiesRow.tag = 6; }
                mRow = propertiesRow.AddRow(); mRow.SetData(null, APIPage { prop.name, page }); mRow.icon = mainForm.icons[typeProperty];
-               mRow.tag = (int)prop;
+               mRow.tag = (int64)prop;
             }
             else
             {
-               if(!membersRow) { membersRow = row.AddRow(); membersRow.SetData(null, APIPage { "Data Members", page = page }); membersRow.collapsed = true; membersRow.icon = mainForm.icons[typeData]; membersRow.tag = 6; }
+               if(!membersRow) { membersRow = row.AddRow(); membersRow.SetData(null, APIPage { $"Data Members", page = page }); membersRow.collapsed = true; membersRow.icon = mainForm.icons[typeData]; membersRow.tag = 6; }
                AddDataMember(membersRow, page, (DataMember)prop);
             }
          }
@@ -1949,12 +2197,12 @@ static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName,
       for(prop = cl.conversions.first; prop; prop = prop.next)
       {
          DataRow mRow;
-         char * name;
-         if(!conversionsRow) { conversionsRow = row.AddRow(); conversionsRow.SetData(null, APIPage { "Conversions", page = page }); conversionsRow.collapsed = true; conversionsRow.icon = mainForm.icons[typeDataType]; conversionsRow.tag = 7; }
+         const char * name;
+         if(!conversionsRow) { conversionsRow = row.AddRow(); conversionsRow.SetData(null, APIPage { $"Conversions", page = page }); conversionsRow.collapsed = true; conversionsRow.icon = mainForm.icons[typeDataType]; conversionsRow.tag = 7; }
          name = RSearchString(prop.name, "::", strlen(prop.name), true, false);
          if(name) name += 2; else name = prop.name;
          mRow = conversionsRow.AddRow(); mRow.SetData(null, APIPage { name, page = page }); mRow.icon = mainForm.icons[typeDataType];
-         mRow.tag = (int)prop;
+         mRow.tag = (int64)prop;
       }
    }
    if(cl.type == enumClass)
@@ -1963,11 +2211,93 @@ static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName,
       NamedLink item;
       for(item = enumeration.values.first; item; item = item.next)
       {
-         DataRow mRow;                                                                                                                                                                                      
-         if(!enumRow) { enumRow = row.AddRow(); enumRow.SetData(null, APIPage { "Enumeration Values", page = page }); enumRow.collapsed = true; enumRow.icon = mainForm.icons[typeEnumValue]; enumRow.tag = 8; }
-         mRow = enumRow.AddRow(); mRow.SetData(null, APIPage { item.name, page = page }); mRow.icon = mainForm.icons[typeEnumValue];         
-         mRow.tag = (int)item;
+         DataRow mRow;
+         if(!enumRow) { enumRow = row.AddRow(); enumRow.SetData(null, APIPage { $"Enumeration Values", page = page }); enumRow.collapsed = true; enumRow.icon = mainForm.icons[typeEnumValue]; enumRow.tag = 8; }
+         mRow = enumRow.AddRow(); mRow.SetData(null, APIPage { item.name, page = page }); mRow.icon = mainForm.icons[typeEnumValue];
+         mRow.tag = (int64)item;
+      }
+   }
+}
+
+class AddressBar : Window
+{
+   background = activeBorder;
+   tabCycle = true;
+   Button open
+   {
+      this, bevelOver = true, inactive = true, anchor = Anchor { left = 0, top = 0, bottom = 0 }, size = Size { 24 }, bitmap = { ":actions/docOpen.png" };
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         MainForm mainForm = (MainForm)parent;
+         FileDialog fileDialog = mainForm.fileDialog;
+         if(fileDialog.Modal() == ok)
+            mainForm.OpenModule(fileDialog.filePath);
+         return true;
+      }
+   };
+   Button back
+   {
+      this, bevelOver = true, inactive = true, anchor = Anchor { left = 28, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = altLeft, bitmap = { "<:ecere>actions/goPrevious.png" };
+      disabled = true;
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         ((MainForm)parent).Back();
+         return true;
+      }
+   };
+   Button forward
+   {
+      this, bevelOver = true, inactive = true, anchor = Anchor { left = 52, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = altRight, bitmap = { "<:ecere>actions/goNext.png" };
+      disabled = true;
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         ((MainForm)parent).Forward();
+         return true;
+      }
+   };
+   Button home
+   {
+      this, bevelOver = true, inactive = true, anchor = Anchor { left = 80, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = ctrlH, bitmap = { "<:ecere>actions/goHome.png" };
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         ((MainForm)parent).Home();
+         return true;
+      }
+   };
+   /* TODO: Search (#143/#441)
+      When there's something in the search box, list matching sections, the exact match first,instead of the Hierarchy in the ListBox.
+      Update this in the NotifyUpdate. Enter goes to the exact match.
+
+   Label { this, anchor = Anchor { left = (124+12) }, labeledWindow = search };
+
+   EditBox search
+   {
+      this, text = "Search:", anchor = Anchor { left = (16+48+124), right = 60, top = 0, bottom = 0 }, hotKey = altD;
+
+      bool NotifyKeyDown(EditBox editBox, Key key, unichar ch)
+      {
+         if(!disabled && (SmartKey)key == enter)
+            ((MainForm)parent).Go(editBox.contents);
+         return true;
+      }
+
+      void NotifyUpdate(EditBox editBox)
+      {
+         String location = ((MainForm)parent).view.location;
+         disabled = !strcmp(location ? location : "", editBox.contents);
       }
+   };
+   */
+
+   bool OnKeyHit(Key key, unichar ch)
+   {
+      if(key == escape)
+         ((MainForm)parent).view.MakeActive();
+      return true;
    }
 }
 
@@ -1978,7 +2308,8 @@ class MainForm : Window
    borderStyle = sizable;
    hasMaximize = true;
    hasMinimize = true;
-   text = "API Documentation Browser";
+   icon = { ":documentorIcon.png" };
+   text = $"API Documentation Browser";
 
    BitmapResource icons[CodeObjectType];
 
@@ -1994,11 +2325,11 @@ class MainForm : Window
 
    hasMenuBar = true;
    menu = Menu { };
-   Menu fileMenu { menu, "File", f };
+   Menu fileMenu { menu, $"File", f };
    Array<FileFilter> fileFilters
    { [
-      { "eC Shared Library files (*.dll, *.so, *.dylib)", "dll, so, dylib" },
-      { "eC Symbol files (*.sym)", "sym" }
+      { $"eC Shared Library files (*.dll, *.so, *.dylib)", "dll, so, dylib" },
+      { $"eC Symbol files (*.sym)", "sym" }
    ] };
 
    FileDialog fileDialog
@@ -2007,7 +2338,7 @@ class MainForm : Window
    };
    MenuItem fileOpenItem
    {
-      fileMenu, "Open...", o, ctrlO;
+      fileMenu, $"Open...", o, ctrlO;
 
       bool NotifySelect(MenuItem selection, Modifiers mods)
       {
@@ -2020,33 +2351,46 @@ class MainForm : Window
    };
    MenuItem fileSettingsItem
    {
-      fileMenu, "Settings...", s, ctrlS; // set the Settings item to the file menu with shortcut keys:s and ctrl+s
+      fileMenu, $"Settings...", s, ctrlS; // set the Settings item to the file menu with shortcut keys:s and ctrl+s
 
       bool NotifySelect(MenuItem selection, Modifiers mods)
       {
-         SettingsDialog { master = this }.Modal(); // Open the settings dialog to allow the user to change the directory for the eCdoc files
+         if(SettingsDialog { master = this }.Modal() == ok) // Open the settings dialog to allow the user to change the directory for the eCdoc files
+         {
+            // Refresh docs
+            view.edit = false;
+            view.Destroy(0);
+            view.Create();
+         }
+         return true;
       }
    };
    MenuDivider { fileMenu };
-   MenuItem fileExit { fileMenu, "Exit", x, altF4, NotifySelect = MenuFileExit };
+   MenuItem fileExit { fileMenu, $"Exit", x, altF4, NotifySelect = MenuFileExit };
 
-   void OpenModule(char * filePath)
+   void OpenModule(const char * filePath)
    {
+      char moduleName[MAX_LOCATION];
       char extension[MAX_EXTENSION];
       Module module = null;
       static char symbolsDir[MAX_LOCATION];
 
+      history.size = 0;
+      modulesAdded.RemoveAll();
+
       FreeContext(globalContext);
       FreeExcludedSymbols(excludedSymbols);
       ::defines.Free(FreeModuleDefine);
       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);
 
@@ -2059,9 +2403,9 @@ class MainForm : Window
 
       ImportModule(filePath, normalImport, publicAccess, false);
 
-      if(extension[0] && strcmpi(extension, "so") && strcmpi(extension, "dll"))
+      if(extension[0] && strcmpi(extension, "so") && strcmpi(extension, "dll") && strcmpi(extension, "dylib"))
          componentsApp.name = CopyString(filePath);
-      
+
       for(module = componentsApp.allModules.first; module; module = module.next)
       {
          if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
@@ -2071,20 +2415,35 @@ class MainForm : Window
          eModule_LoadStrict(componentsApp, "ecereCOM", publicAccess /*privateAccess*/);
       AddComponents(componentsApp, false);
 
+      GetLastDirectory(filePath, moduleName);
+      // Extension, path and lib prefix get removed in Module::name
+      if(extension[0])
+      {
+         StripExtension(moduleName);
+         if((!strcmpi(extension, "so") || !strcmpi(extension, "dylib")) && strstr(moduleName, "lib") == moduleName)
+         {
+            int len = strlen(moduleName) - 3;
+            memmove(moduleName, moduleName + 3, len);
+            moduleName[len] = 0;
+         }
+      }
+
       for(module = componentsApp.allModules.first; module; module = module.next)
       {
-         if(module.name && (!strcmp(module.name, filePath)))
+         if(module.name && (!strcmp(module.name, moduleName)))
             break;
       }
       if(!module) module = componentsApp;
-      mainForm.browser.SelectRow(mainForm.browser.FindSubRow((int)module));
+      homeModule = module;
+      mainForm.browser.SelectRow(mainForm.browser.FindSubRow((int64)module));
 
       SetSymbolsDir(null);
    }
 
+   AddressBar addressBar { this, borderStyle = bevel, anchor = Anchor { top = 0, left = 0, right = 0 }, size.h = 26, hotKey = altD };
    ListBox browser
    {
-      this, anchor = { left = 0, top = 0, bottom = 0 }, borderStyle = 0, background = aliceBlue;
+      this, anchor = { left = 0, top = 26, bottom = 0 }, borderStyle = 0, background = aliceBlue;
       treeBranches = true; collapseControl = true; fullRowSelect = false; rootCollapseButton = true;
       hotKey = alt0;
 
@@ -2099,14 +2458,30 @@ class MainForm : Window
          {
             Window activeChild = this.activeChild;
 
-            view.Destroy(0);
+            // Back / Forward Support
+            if(row && !dontRecordHistory)
+            {
+               if(history.count > historyPos+1)
+                  history.count = historyPos+1;
+               historyPos = history.count-1;
+               addressBar.back.disabled = (historyPos == 0);
+               addressBar.forward.disabled = (historyPos >= history.count-1);
+
+               history.Add((Instance)(uint64)row.tag);
+               historyPos = history.count-1;
+
+               addressBar.back.disabled = (historyPos == 0);
+               addressBar.forward.disabled = (historyPos >= history.count-1);
+            }
+
+            view.Destroy(0);
             if(page)
                view.Create();
             activeChild.Activate();
          }
          else if(!view.created)
             view.Create();
-         
+
          {
             page = row.GetData(null);
             if(page && page.page)
@@ -2123,8 +2498,8 @@ class MainForm : Window
                   case 8: view.GoToAnchor("EnumerationValues"); break;
                   default:
                   {
-                     char hex[10];
-                     sprintf(hex, "%08x", row.tag);
+                     char hex[20];
+                     sprintf(hex, "%p", (void *)(uintptr)row.tag);
                      view.GoToAnchor(hex);
                   }
                }
@@ -2139,12 +2514,12 @@ class MainForm : Window
    };
    HelpView view
    {
-      this, anchor = { top = 0, bottom = 0, right = 0 };
+      this, anchor = { top = 26, bottom = 0, right = 0 };
       hotKey = escape;
    };
    PaneSplitter slider
    {
-      this, leftPane = browser, rightPane = view, split = 300 /*scaleSplit = 0.3 */
+      this, anchor.top = 26, leftPane = browser, rightPane = view, split = 300 /*scaleSplit = 0.3 */
    };
 
    bool OnClose(bool parentClosing)
@@ -2169,6 +2544,60 @@ class MainForm : Window
       }
       return true;
    }
+
+   Array<Instance> history { };
+   int historyPos;
+   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)
+      {
+         char location[64];
+         historyPos++;
+         addressBar.back.disabled = (historyPos == 0);
+         addressBar.forward.disabled = (historyPos >= history.count-1);
+         sprintf(location, "api://%p", history[historyPos]);
+         dontRecordHistory = true;
+         view.OnOpen(location);
+         dontRecordHistory = false;
+         return true;
+      }
+      return false;
+   }
+
+   bool Back()
+   {
+      if(historyPos > 0)
+      {
+         char location[64];
+         historyPos--;
+         addressBar.back.disabled = (historyPos == 0);
+         addressBar.forward.disabled = (historyPos >= history.count-1);
+         sprintf(location, "api://%p", history[historyPos]);
+         dontRecordHistory = true;
+         view.OnOpen(location);
+         dontRecordHistory = false;
+         return true;
+      }
+      return false;
+   }
+
+   void Home()
+   {
+      mainForm.browser.SelectRow(mainForm.browser.FindSubRow((int64)homeModule));
+   }
 };
 
 class EditDialog : Window
@@ -2183,11 +2612,11 @@ class EditDialog : Window
    };
    Button saveChanges
    {
-      this, text = "Save Changes", anchor = { horz = 184, vert = 160 }
+      this, text = $"Save Changes", anchor = { horz = 184, vert = 160 }
    };
    Button cancel
    {
-      this, text = "Cancel", anchor = { horz = 254, vert = 160 }
+      this, text = $"Cancel", anchor = { horz = 254, vert = 160 }
    };
 }
 
@@ -2206,9 +2635,36 @@ class HelpView : HTMLView
    bool OnCreate()
    {
       TempFile f { };
+
       page = mainForm.browser.currentRow.GetData(null);
       if(page)
       {
+         // Writability test
+         {
+            char docDir[MAX_LOCATION];
+            readOnly = true;
+            strcpy(docDir, ideSettings.docDir);
+            if(FileExists(docDir).isDirectory)
+            {
+               PathCatSlash(docDir, "___docWriteTest");
+               if(FileExists(docDir).isDirectory)
+               {
+                  RemoveDir(docDir);
+                  if(!FileExists(docDir))
+                     readOnly = false;
+               }
+               else
+               {
+                  MakeDir(docDir);
+                  if(FileExists(docDir).isDirectory)
+                  {
+                     readOnly = false;
+                     RemoveDir(docDir);
+                  }
+               }
+            }
+         }
+
          page.Generate(f);
          f.Seek(0, start);
          OpenFile(f, null);
@@ -2226,421 +2682,521 @@ class HelpView : HTMLView
 
    void SaveEdit()
    {
-      char archiveFile[MAX_LOCATION];
-      char fileName[MAX_FILENAME];
-      char directory[MAX_LOCATION];
-      char * location;
-      Archive archive;
-      SplitArchivePath(editString, archiveFile, &location);
-      GetLastDirectory(location, fileName);
-      StripLastDirectory(location, directory);
-      archive = ArchiveOpen(archiveFile, { true } );
-      if(archive)
-      {
-         TempFile f { };
-         ArchiveDir dir = archive.OpenDirectory(directory, null, replace);
-         Block block;
-         bool empty = true;
+      Block block;
+      bool empty = true;
+      String contents = null;
+      uint len;
+      TempFile f { };
+      for(block = textBlock.parent.subBlocks.first; block; block = block.next)
+      {
+         if(block.type == TEXT && block.textLen)
+         {
+            empty = false;
+            break;
+         }
+      }
+      if(!empty)
+      {
          for(block = textBlock.parent.subBlocks.first; block; block = block.next)
          {
-            if(block.type == TEXT && block.textLen)
-            {
-               empty = false;
-               break;
-            }
+            if(block.type == BR)
+               f.Puts("<br>");
+            else if(block.type == TEXT)
+               f.Write(block.text, 1, block.textLen);
          }
-         if(!empty)
+      }
+      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?
          {
-            for(block = textBlock.parent.subBlocks.first; block; block = block.next)
+            MapIterator<const String, DocCacheEntry> it { map = docCache };
+            if(it.Index(docPath, false))
             {
-               if(block.type == BR)
-                  f.Puts("<br>");
-               else if(block.type == TEXT)
-                  f.Write(block.text, 1, block.textLen);
+               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;
          }
-         f.Seek(0, start);
-         dir.AddFromFile(fileName, f, null, replace, 0, null, null);
-         delete dir;
-         delete archive;
-         delete f;
-         if(empty)
+
+         if(doc)
          {
-            Block parent = textBlock.parent;
-            while((block = parent.subBlocks.first))
+            if(eClass_IsDerived(doc._class, class(ClassDoc)))
+            {
+               clDoc = (ClassDoc)doc;
+            }
+            else if(eClass_IsDerived(doc._class, class(NamespaceDoc)))
             {
-               parent.subBlocks.Remove(block);
-               delete block;
+               nsDoc = (NamespaceDoc)doc;
             }
-            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(clDoc || nsDoc)
          {
-            ComputeMinSizes();
-            ComputeSizes();
-            PositionCaret(true);
-            Update(null);
+            if(type == functionDoc)
+            {
+               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;
+                  }
+               }
+            }
          }
-      }
-   }
 
-   bool OnLeftButtonDown(int x, int y, Modifiers mods)
-   {
-      if(edit)
-      {
-         // Update overLink
-         HTMLView::OnMouseMove(x, y, mods);
-         if(textBlock && overLink == textBlock.parent)
+         if(type == functionDoc && fnDoc && fnDoc.isEmpty)
          {
-            selPosition = curPosition = TextPosFromPoint(x, y, &textBlock);
-            PositionCaret(true);            
+            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
+         else if(type == methodDoc && mdDoc && mdDoc.isEmpty)
          {
-            SaveEdit();
-            HTMLView::OnLeftButtonDown(x, y, mods);
+            MapIterator<String, MethodDoc> it { map = clDoc.methods };
+            if(it.Index(method.name, false))
+               it.Remove();
+            delete mdDoc;
          }
-         return true;
-      }
-      return HTMLView::OnLeftButtonDown(x, y, mods);
-   }
-
-   bool OnLeftButtonUp(int x, int y, Modifiers mods)
-   {
-      if(!edit || !textBlock || clickedLink != textBlock.parent)
-      {
-         HTMLView::OnLeftButtonUp(x, y, mods);
-         if(edit)
+         if(nsDoc)
          {
-            selPosition = curPosition = TextPosFromPoint(x, y, &textBlock);
-            PositionCaret(true);
+            if(nsDoc.functions && !nsDoc.functions.count) delete nsDoc.functions;
+            if(nsDoc.defines && !nsDoc.defines.count) delete nsDoc.defines;
          }
-      }
-      return true;
-   }
-
-   // Returns true if it needs scrolling
-   /*
-   bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
-   {
-      int w;
-      int c;
-      int x, y;
-      EditLine line;
-      bool needHScroll = false;
-
-      if(py < 0)
-      {
-         if(this.viewY > 0)
+         if(clDoc)
          {
-            y = this.viewY-1;
-            line = this.viewLine ? (void *)this.viewLine.prev : null;
+            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;
          }
-         else
+
+         if(clDoc || nsDoc)
          {
-            y = 0;
-            line = (void *)this.lines.first;
+            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;
       }
-      else
+
+      if(empty)
       {
-         py = Min(py, clientSize.h);
-         py /= this.space.h;
-         py = Min(py, this.lineCount);
-         y = this.viewY;
-         for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
+         Block parent = textBlock.parent;
+         while((block = parent.subBlocks.first))
          {
-            y++;
+            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);
       }
 
-      if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
-         needHScroll = true;
-      px = Max(px,0);
-      px = Min(px,clientSize.w+this.space.w);
-
-      if(tx && line)
+      edit = false;
+      if(created)
       {
-         *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
+         ComputeMinSizes();
+         ComputeSizes();
+         PositionCaret(true);
+         Update(null);
       }
-
-      if(tline) *tline = line;
-      if(ty) *ty = y;
-
-      // Prevent divide by 0 from non valid this.font
-      if(!this.space.h)
-         return (y < this.viewY) || needHScroll;
-      else
-         return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
-      return false;
    }
-*/
-/*
-   bool OnLeftButtonDown(int mx, int my, Modifiers mods)
-   {
-      int x,y;
-      EditLine line;
 
-      if(style.noSelect) return true;
+   bool OnLeftButtonDown(int x, int y, Modifiers mods)
+   {
+      bool result = true;
 
-      if(!mods.isActivate)
+      if(edit && (!textBlock || overLink != textBlock.parent))
       {
-         Capture();
-         mouseSelect = true;
+         if(!readOnly)
+            SaveEdit();
+         HTMLView::OnLeftButtonDown(x, y, mods);
+         selPosition = curPosition = 0;
+         selBlock = textBlock;
+         Update(null);
       }
+      else
+         result = HTMLView::OnLeftButtonDown(x, y, mods);
 
-      mouseX = mx - XOFFSET;
-      mouseY = my;
-
-      FindMouse(mouseX, mouseY, &x, &y, &line, true);
-
-      if(!style.readOnly)
+      if(!edit && clickedLink)
       {
-         if(wordSelect)
-            mouseMove = false;
-         else if(IsMouseOnSelection() && !mods.isActivate)
+         ReleaseCapture();
+         if(clickedLink == overLink && clickedLink.href)
          {
-            DirtyLine(this.y);
-            mouseMove = true;
-            dropX = x;
-            dropY = y;
-            dropLine = line;
+            if(OnOpen(clickedLink.href))
+               Update(null);
          }
       }
 
-      if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
+      if(edit)
       {
-         if(mods.shift && !mods.isActivate)
+         // Update overLink
+         if(textBlock && overLink == textBlock.parent)
          {
-            this.x = x;
-            this.y = y;
-            this.line = line;
-            DirtyAll();
+            selPosition = curPosition = TextPosFromPoint(x, y, &textBlock, true);
+            selBlock = textBlock;
+            PositionCaret(true);
+            selecting = true;
+            Update(null);
          }
-         else
-         {
-            SelDirty();
-            DirtyLine(this.y);
-            this.x = x;
-            this.y = y;
-            this.line = line;
-            DirtyLine(this.y);
-            this.selLine = this.line;
-            this.selX = this.x;
-            this.selY = this.y;
-            //Deselect();
-         }
-         ComputeColumn();
-      }
-      
-      UpdateDirty();
-      UpdateCaretPosition(true);
-      return true;
+      }
+      return result;
    }
-*/
-/*
+
    bool OnLeftButtonUp(int x, int y, Modifiers mods)
    {
-      timer.Stop();
-
-      mouseSelect = false;
-      wordSelect = false;
-      
-      x -= XOFFSET;
-      
-      ReleaseCapture();
-      if(!style.readOnly)
+      if(!edit || !textBlock || clickedLink != textBlock.parent)
       {
-         if(mouseMove)
-         {
-            EditLine line;
-            FindMouse(mouseX, mouseY, &x, &y, &line, true);
-    
-            dropX = x;
-            dropY = y;
-            dropLine = line;
-
-            mouseMove = IsMouseOnSelection();
-
-            if(!mouseMove)
-            {
-               int size = SelSize();
-               if(size)
-               {
-                  char * text = new char[size+1];
-                  if(text)
-                  {
-                     int moveX = 0;
-                     GetSel(text, false);
-
-                     if(Max(selY, this.y) == dropY)
-                     {
-                        if(this.x > selX)
-                        {
-                           if(this.dropX > this.selX)
-                              moveX = this.x - this.selX;
-                        }
-                        else
-                        {
-                           if(this.dropX > this.x)
-                              moveX = this.selX - this.x;
-                        }
-                     }
-                     DelSel(null);
-                     this.dropX -= moveX;
-                     this.selX = this.x = this.dropX;
-                     this.selY = this.y = this.dropY;
-                     this.selLine = this.line = this.dropLine;
-                     AddS(text);
-                     SetViewToCursor(true);
-                     delete text;
-                     Modified();
-                  }
-               }
-            }
-            else
-            {
-               SelDirty();
-               DirtyLine(this.y);
-               this.x = x;
-               this.y = y;
-               this.line = line;
-               ComputeColumn();
-               DirtyLine(this.y);
-               Deselect();
-               UpdateDirty();
-            }
-         }
-         else
+         HTMLView::OnLeftButtonUp(x, y, mods);
+         if(edit)
          {
-            EditLine line;
-            mouseX = x;
-            mouseY = y;
-
-            FindMouse(mouseX, mouseY, &x, &y, &line, true);
-
-            NotifyDropped(master, this, x, y);
+            selPosition = curPosition = TextPosFromPoint(x, y, &textBlock, true);
+            selBlock = textBlock;
+            PositionCaret(true);
+            Update(null);
          }
       }
-      mouseMove = false;
+      else
+         ReleaseCapture();
+      selecting = false;
       return true;
    }
+   bool selecting;
 
-   bool OnMouseMove(int mx, int my, Modifiers mods)
+   bool OnMouseMove(int x, int y, Modifiers mods)
    {
-      int x,y;
-      EditLine line;
-      bool needScroll;
-      
-      if(mods != -1 && mods.isSideEffect) 
-      { 
-         SetSelectCursor(); 
-         return true; 
-      }
-      if(style.noSelect) return true;
-      if(wordSelect) return true;
-      mouseX = mx - XOFFSET;
-      mouseY = my;
-
-      needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
-
-      if(this.mouseMove || this.mouseSelect)
-      {
-         if(!needScroll)
-            timer.Stop();
-         else 
-         {
-            if(needScroll)
-               timer.Start();
-            if(mods != -1 && 
-               ((style.hScroll) || (style.vScroll)))
-               return true;
-         }
-      }
-
-      if(this.mouseMove)
+      if(edit && selecting)
       {
-         DirtyLine(this.dropY);
-         this.dropX = x;
-         this.dropY = y;
-         DirtyLine(this.dropY);
-         this.dropLine = line;
-         SetViewToCursor(true);
-      }
-      else if(this.mouseSelect)
-      {
-         DirtyLine(this.selY);
-         DirtyLine(this.y);
-         this.x = x;
-         this.y = y;
-         ComputeColumn();
-         DirtyLine(this.y);
-         this.line = line;
-         SetViewToCursor(true);
-         UpdateDirty();
+         curPosition = TextPosFromPoint(x, y, &textBlock, true);
+         PositionCaret(true);
+         Update(null);
       }
-      SetSelectCursor();
-      return true;
+      return HTMLView::OnMouseMove(x, y, mods);
    }
 
    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
    {
-      int x,y;
-      EditLine line;
-      
-      mx -= XOFFSET;
-
-      if(style.noSelect) return true;
-      FindMouse(mx, my, &x, &y, &line, false);
-      if(!NotifyDoubleClick(master, this, line, mods))
-         return false;
-      if(x < line.count)
+      if(edit && textBlock)
       {
          int c;
          int start = -1;
          int numBytes;
-         for(c = x; c >= 0; c--)
+
+         selPosition = curPosition = TextPosFromPoint(mx, my, &textBlock, false);
+         selBlock = textBlock;
+         for(c = curPosition; c >= 0; c--)
          {
             unichar ch;
-            while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
-            ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
-            if(!IS_ALUNDER(ch))
+            while(c > 0 && !UTF8_IS_FIRST(textBlock.text[c])) c--;
+            ch = UTF8GetChar(textBlock.text + c, &numBytes);
+            if(!CharMatchCategories(ch, letters|numbers|marks|connector))
                break;
             start = c;
          }
          if(start != -1)
          {
-            for(c = start; c<line.count; c += numBytes)
+            for(c = start; c < textBlock.textLen; c += numBytes)
             {
-               unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
-               if(!IS_ALUNDER(ch))
+               unichar ch = UTF8GetChar(textBlock.text + c, &numBytes);
+               if(!CharMatchCategories(ch, letters|numbers|marks|connector))
                   break;
             }
-            SelDirty();
-            DirtyLine(this.y);
-            this.y = y;
-            DirtyLine(this.y);
-            this.selX = start;
-            this.x = c;
-            ComputeColumn();
-            this.line = this.selLine = line;
-            this.wordSelect = (c != start);
-            UpdateDirty();
+            selPosition = start;
+            curPosition = c;
+
+            PositionCaret(true);
+            Update(null);
+            return false;
          }
       }
       return true;
    }
-*/
+
    bool OnOpen(char * href)
    {
       if(!strncmp(href, "api://", 6))
       {
-         int tag = strtoul(href + 6, null, 16);
+         int64 tag = (int64)strtoull(href + 6, null, 16);
          DataRow row = mainForm.browser.FindSubRow(tag);
          if(row)
          {
@@ -2662,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)
                {
@@ -2672,8 +3228,8 @@ class HelpView : HTMLView
                else
                {
                   block.parent.subBlocks.Insert(block, newBlock);
-                  startY += th;
-               }               
+                  startY += block.prev.height;
+               }
                newBlock.startX = startX;
                newBlock.startY = startY;
                newBlock.text = new0 char[1];
@@ -2681,24 +3237,140 @@ class HelpView : HTMLView
          }
 
          textBlock = (Block)clickedLink.subBlocks.first;
-         if(!strcmp(textBlock.text, "[Add Text]"))
+         if(!strcmp(textBlock.text, $"[Add Text]"))
          {
             textBlock.text[0] = 0;
-            textBlock.textLen = 0;               
+            textBlock.textLen = 0;
          }
 
          strcpy(editString, href + 7);
          selPosition = curPosition = 0;
+         selBlock = textBlock;
          // dialog.Create();
          edit = true;
-         PositionCaret(true);
+         // PositionCaret(true);
       }
       return true;
    }
 
-   Block textBlock;
    char * text;
-   int curPosition, selPosition;
+
+   void DeleteSelection()
+   {
+      if(textBlock != selBlock || curPosition != selPosition)
+      {
+         if(textBlock == selBlock)
+         {
+            // Within same block
+            int start = Min(curPosition, selPosition);
+            int end = Max(curPosition, selPosition);
+            memmove(textBlock.text + start, textBlock.text + end, textBlock.textLen - end);
+            textBlock.textLen -= end-start;
+            textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
+            curPosition = start;
+            selPosition = start;
+         }
+         else
+         {
+            int startSel, endSel;
+            Block startSelBlock = null, endSelBlock = null, b, next;
+
+            NormalizeSelection(&startSelBlock, &startSel, &endSelBlock, &endSel);
+
+            startSelBlock.text = renew startSelBlock.text char[startSel + endSelBlock.textLen - endSel + 1];
+            memcpy(startSelBlock.text + startSel, endSelBlock.text + endSel, endSelBlock.textLen - endSel + 1);
+
+            startSelBlock.textLen = startSel + endSelBlock.textLen - endSel;
+            for(b = startSelBlock.next; b; b = next)
+            {
+               bool isEnd = b == endSelBlock;
+               next = GetNextBlock(b);
+               b.parent.subBlocks.Remove(b);
+               delete b;
+               if(isEnd)
+                  break;
+            }
+            textBlock = startSelBlock;
+            selBlock = startSelBlock;
+            curPosition = startSel;
+            selPosition = startSel;
+         }
+         ComputeMinSizes();
+         ComputeSizes();
+         PositionCaret(true);
+         Update(null);
+      }
+   }
+
+   String GetSelectionString()
+   {
+      String selection = null;
+      if(textBlock == selBlock)
+      {
+         // Within same block
+         int start = Min(curPosition, selPosition);
+         int end = Max(curPosition, selPosition);
+         int len = end - start;
+         selection = new char[len + 1];
+         memcpy(selection, textBlock.text + start, len);
+         selection[len] = 0;
+      }
+      else
+      {
+         int startSel, endSel;
+         Block startSelBlock = null, endSelBlock = null, b;
+         int totalLen = 0;
+
+         NormalizeSelection(&startSelBlock, &startSel, &endSelBlock, &endSel);
+
+         // Compute length
+         for(b = startSelBlock; b; b = GetNextBlock(b))
+         {
+            int start = (b == startSelBlock) ? startSel : 0;
+            int end = (b == endSelBlock) ? endSel : b.textLen;
+            int len = end - start;
+            totalLen += len;
+            if(b == endSelBlock)
+               break;
+            else if(b.type == TEXT)
+               totalLen++;
+         }
+
+         selection = new char[totalLen + 1];
+         totalLen = 0;
+         for(b = startSelBlock; b; b = GetNextBlock(b))
+         {
+            int start = (b == startSelBlock) ? startSel : 0;
+            int end = (b == endSelBlock) ? endSel : b.textLen;
+            int len = end - start;
+            memcpy(selection + totalLen, b.text + start, len);
+            totalLen += len;
+            if(b == endSelBlock)
+               break;
+            else if(b.type == TEXT)
+               selection[totalLen++] = '\n';
+         }
+         selection[totalLen] = 0;
+      }
+      return selection;
+   }
+
+   void CopySelection()
+   {
+      String s = GetSelectionString();
+      if(s)
+      {
+         int len = strlen(s);
+         ClipBoard cb { };
+         if(cb.Allocate(len + 1))
+         {
+            memcpy(cb.text, s, len + 1);
+            cb.Save();
+         }
+         delete cb;
+         delete s;
+      }
+   }
 
    bool OnKeyDown(Key key, unichar ch)
    {
@@ -2709,27 +3381,51 @@ class HelpView : HTMLView
             case escape:
                OnLeftButtonDown(0,0,0);
                return false;
+            case Key { end, shift = true }:
             case end:
-               selPosition = curPosition = textBlock.textLen;
+               curPosition = textBlock.textLen;
+               if(!key.shift)
+               {
+                  selPosition = curPosition;
+                  selBlock = textBlock;
+               }
                PositionCaret(true);
                Update(null);
                break;
+            case Key { home, shift = true }:
             case home:
-               selPosition = curPosition = 0;
+               curPosition = 0;
+               if(!key.shift)
+               {
+                  selPosition = curPosition;
+                  selBlock = textBlock;
+               }
                PositionCaret(true);
                Update(null);
                break;
+            case Key { home, ctrl = true, shift = true }:
             case ctrlHome:
-               selPosition = curPosition = 0;
+               curPosition = 0;
                while(textBlock.prev)
                   textBlock = textBlock.prev.prev;
+               if(!key.shift)
+               {
+                  selPosition = curPosition;
+                  selBlock = textBlock;
+               }
                PositionCaret(true);
                Update(null);
                return false;
+            case Key { end, ctrl = true, shift = true }:
             case ctrlEnd:
                while(textBlock.next && textBlock.next.next)
                   textBlock = textBlock.next.next;
-               selPosition = curPosition = textBlock.textLen;
+               curPosition = textBlock.textLen;
+               if(!key.shift)
+               {
+                  selPosition = curPosition;
+                  selBlock = textBlock;
+               }
                PositionCaret(true);
                Update(null);
                return false;
@@ -2739,12 +3435,14 @@ class HelpView : HTMLView
          return HTMLView::OnKeyDown(key, ch);
       return true;
    }
+
    bool OnKeyHit(Key key, unichar ch)
    {
       if(edit)
       {
          switch(key)
          {
+            case Key { up, shift = true }:
             case up:
             {
                if(caretY == textBlock.startY)
@@ -2752,7 +3450,13 @@ class HelpView : HTMLView
                   if(textBlock.prev)
                   {
                      textBlock = textBlock.prev.prev;
-                     selPosition = curPosition = Min(curPosition, textBlock.textLen);
+                     curPosition = Min(curPosition, textBlock.textLen);
+                     if(!key.shift)
+                     {
+                        selPosition = curPosition;
+                        selBlock = textBlock;
+                     }
+                     Update(null);
                      PositionCaret(false);
                      caretY = MAXINT;
                   }
@@ -2761,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;
@@ -2797,7 +3501,7 @@ class HelpView : HTMLView
                            len = (nextSpace - (text + textPos)) + 1;
                         else
                            len = textBlock.textLen - textPos;
-                        
+
                         display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
 
                         if(x + width + w > maxW && x > 0)
@@ -2823,7 +3527,13 @@ class HelpView : HTMLView
                               len = curPosition - startPos;
                               display.FontExtent(textBlock.font.font, text + startPos, len, &x, null);
                            }
-                           selPosition = curPosition;
+                           if(!key.shift)
+                           {
+                              selPosition = curPosition;
+                              selBlock = textBlock;
+                           }
+                           Update(null);
+
                            PositionCaret(false);
                            return false;
                         }
@@ -2834,10 +3544,24 @@ class HelpView : HTMLView
                         {
                            int c = textPos - 1;
                            while(c > 0 && text[c] == ' ') c--;
-                           selPosition = curPosition = c + 1;
+                           curPosition = c + 1;
+                           if(!key.shift)
+                           {
+                              selPosition = curPosition;
+                              selBlock = textBlock;
+                           }
+                           Update(null);
                         }
                         else
-                           selPosition = curPosition = textBlock.textLen;
+                        {
+                           curPosition = textBlock.textLen;
+                           if(!key.shift)
+                           {
+                              selPosition = curPosition;
+                              selBlock = textBlock;
+                           }
+                           Update(null);
+                        }
                         PositionCaret(false);
                         return false;
                      }
@@ -2848,9 +3572,10 @@ class HelpView : HTMLView
                }
                return false;
             }
+            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;
@@ -2886,7 +3611,7 @@ class HelpView : HTMLView
                         len = (nextSpace - (text + textPos)) + 1;
                      else
                         len = textBlock.textLen - textPos;
-                     
+
                      display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
 
                      if(x + width + w > maxW && x > 0)
@@ -2912,17 +3637,28 @@ class HelpView : HTMLView
                            len = curPosition - startPos;
                            display.FontExtent(textBlock.font.font, text + startPos, len, &x, null);
                         }
-                        selPosition = curPosition;
+                        if(!key.shift)
+                        {
+                           selPosition = curPosition;
+                           selBlock = textBlock;
+                        }
+                        Update(null);
                         PositionCaret(false);
                         return false;
                      }
                   }
                   if(sy > caretY)
                   {
-                     selPosition = curPosition = textBlock.textLen;
+                     curPosition = textBlock.textLen;
+                     if(!key.shift)
+                     {
+                        selPosition = curPosition;
+                        selBlock = textBlock;
+                     }
+                     Update(null);
                      PositionCaret(false);
                      return false;
-                  } 
+                  }
                   else if(textPos == textBlock.textLen && textBlock.next && textBlock.next.next)
                   {
                      startPos = 0;
@@ -2938,20 +3674,19 @@ class HelpView : HTMLView
                      sx = textBlock.startX;
                   }
                }
-               
+
                /*if(textBlock.next && textBlock.next.next)
                {
                   textBlock = textBlock.next.next;
                   selPosition = curPosition = Min(curPosition, textBlock.textLen);
+                  selBlock = textBlock;
                   PositionCaret(false);
                }*/
                break;
             }
-            #define IS_ALUNDER(ch) ((ch) == '_' || isalnum((ch)))
+            case Key { right, shift = true, ctrl = true }:
             case ctrlRight:
             {
-               // SELECTION CTRL-RIGHT
-               /*
                bool foundAlpha = false;
                bool found = false;
                Block line, lastLine;
@@ -2963,7 +3698,9 @@ class HelpView : HTMLView
                   int c;
                   for(c = start; c < line.textLen; c++)
                   {
-                     if(IS_ALUNDER(line.text[c]))
+                     char ch = line.text[c];
+                     bool isAlUnder = CharMatchCategories(ch, letters|numbers|marks|connector);
+                     if(key.shift ? isAlUnder : !isAlUnder)
                      {
                         foundAlpha = true;
                         lastC = c;
@@ -2972,58 +3709,56 @@ class HelpView : HTMLView
                      else if(foundAlpha)
                      {
                         found = true;
+                        if(!key.shift)
+                        {
+                           curPosition = c;
+                           if(!key.shift)
+                           {
+                              selPosition = curPosition;
+                              selBlock = textBlock;
+                           }
+                           Update(null);
+                           textBlock = line;
+                           PositionCaret(true);
+                        }
                         break;
                      }
                   }
+                  // No next word found,
                   if(!found && (c != curPosition || line != textBlock))
                   {
                      found = true;
                      lastLine = line;
                      lastC = line.textLen-1;
-                     break;
-                  }
-               }  
-               if(found)
-               {
-                  selPosition = curPosition = lastC+1;
-                  textBlock = lastLine;
-                  PositionCaret(true);
-               }
-               */
-               
-               bool foundAlpha = false;
-               bool found = false;
-               Block line;
-
-               for(line = textBlock; (line && !found); line = line.next ? line.next.next : null)
-               {
-                  int start = (line == textBlock) ? curPosition : 0;
-                  int c;
-                  for(c = start; c < line.textLen; c++)
-                  {
-                     if(!IS_ALUNDER(line.text[c]))
-                        foundAlpha = true;
-                     else if(foundAlpha)
+                     if(key.shift)
+                        break;
+                     else
                      {
-                        found = true;
-                        selPosition = curPosition = c;
+                        curPosition = line.textLen;
+                        if(!key.shift)
+                        {
+                           selPosition = curPosition;
+                           selBlock = textBlock;
+                        }
+                        Update(null);
+
                         textBlock = line;
                         PositionCaret(true);
-                        break;
                      }
                   }
-                  // No next word found, 
-                  if(!found && (c != curPosition || line != textBlock))
-                  {
-                     found = true;
-                     selPosition = curPosition = line.textLen;
-                     textBlock = line;
-                     PositionCaret(true);
-                  }
-                  foundAlpha = true;
+                  if(!key.shift)
+                     foundAlpha = true;
+               }
+               if(key.shift && found)
+               {
+                  curPosition = lastC+1;
+                  textBlock = lastLine;
+                  PositionCaret(true);
+                  Update(null);
                }
                break;
             }
+            case Key { left, ctrl = true, shift = true }:
             case ctrlLeft:
             {
                bool foundAlpha = false;
@@ -3044,7 +3779,7 @@ class HelpView : HTMLView
                   if(line == textBlock) start = curPosition-1; else start = line.textLen-1;
                   for(c = start; c>=0; c--)
                   {
-                     if(IS_ALUNDER(line.text[c]))
+                     if(CharMatchCategories(line.text[c], letters|numbers|marks|connector))
                      {
                         foundAlpha = true;
                         lastC = c;
@@ -3059,7 +3794,7 @@ class HelpView : HTMLView
                         }
                      }
                   }
-                  // No next word found, 
+                  // No next word found,
                   if(!found && curPosition > 0)
                   {
                      foundAlpha = true;
@@ -3071,99 +3806,138 @@ class HelpView : HTMLView
                if(foundAlpha)
                {
                   textBlock = lastLine;
-                  selPosition = curPosition = lastC;
+                  curPosition = lastC;
+                  if(!key.shift)
+                  {
+                     selPosition = curPosition;
+                     selBlock = textBlock;
+                  }
                   PositionCaret(true);
+                  Update(null);
                }
                break;
             }
+            case Key { right, shift = true }:
             case right:
                if(curPosition < textBlock.textLen)
                {
                   curPosition += UTF8_NUM_BYTES(textBlock.text[curPosition]);
+                  if(!key.shift)
+                  {
+                     selPosition = curPosition;
+                     selBlock = textBlock;
+                  }
                   PositionCaret(true);
-                  selPosition = curPosition;
+                  Update(null);
                }
                else if(textBlock.next && textBlock.next.next)
                {
                   textBlock = textBlock.next.next;
-                  selPosition = curPosition = 0;
+                  curPosition = 0;
+                  if(!key.shift)
+                  {
+                     selPosition = curPosition;
+                     selBlock = textBlock;
+                  }
                   PositionCaret(true);
+                  Update(null);
                }
                break;
+            case Key { left, shift = true }:
             case left:
                if(curPosition > 0)
                {
                   while(curPosition > 0 && !UTF8_IS_FIRST(textBlock.text[--curPosition]));
+                  if(!key.shift)
+                  {
+                     selPosition = curPosition;
+                     selBlock = textBlock;
+                  }
                   PositionCaret(true);
-                  selPosition = curPosition;
+                  Update(null);
                }
                else if(textBlock.prev)
                {
                   textBlock = textBlock.prev.prev;
-                  selPosition = curPosition = textBlock.textLen;
+                  curPosition = textBlock.textLen;
+                  if(!key.shift)
+                  {
+                     selPosition = curPosition;
+                     selBlock = textBlock;
+                  }
                   PositionCaret(true);
+                  Update(null);
                }
                break;
             case backSpace:
-               if(curPosition)
+               if(readOnly) break;
+               if(textBlock == selBlock && curPosition == selPosition)
                {
-                  int c = curPosition;
-                  int nb = 1;
-                  while(c > 0 && !UTF8_IS_FIRST(textBlock.text[--c])) nb++;
-                  memmove(textBlock.text + curPosition - nb, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
-                  textBlock.textLen -= nb;
-                  textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
-                  curPosition -= nb;
-                  selPosition = curPosition;
+                  if(curPosition)
                   {
-                     //Clear(html.block);
-                     //CreateForms(html.block);
+                     int c = curPosition;
+                     int nb = 1;
+                     while(c > 0 && !UTF8_IS_FIRST(textBlock.text[--c])) nb++;
+                     memmove(textBlock.text + curPosition - nb, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
+                     textBlock.textLen -= nb;
+                     textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
+                     curPosition -= nb;
+                     selPosition = curPosition;
+                     selBlock = textBlock;
+
                      ComputeMinSizes();
                      ComputeSizes();
-                     //PositionForms();
+                     PositionCaret(true);
+                     Update(null);
                   }
-                  PositionCaret(true);
-                  Update(null);
-               }
-               else if(textBlock.prev)
-               {
-                  Block prev = textBlock.prev, prevBlock = textBlock.prev.prev;
-                  prevBlock.text = renew prevBlock.text char[prevBlock.textLen + textBlock.textLen + 1];
-                  memcpy(prevBlock.text + prevBlock.textLen, textBlock.text, textBlock.textLen + 1);
-
-                  selPosition = curPosition = prevBlock.textLen;
-                  prevBlock.textLen += textBlock.textLen;
-                  textBlock.parent.subBlocks.Remove(prev);
-                  delete prev;
-                  textBlock.parent.subBlocks.Remove(textBlock);                  
-                  delete textBlock;
-                  textBlock = prevBlock;
-                  
+                  else if(textBlock.prev)
                   {
-                     //Clear(html.block);
-                     //CreateForms(html.block);
+                     Block prev = textBlock.prev, prevBlock = textBlock.prev.prev;
+                     prevBlock.text = renew prevBlock.text char[prevBlock.textLen + textBlock.textLen + 1];
+                     memcpy(prevBlock.text + prevBlock.textLen, textBlock.text, textBlock.textLen + 1);
+
+                     selPosition = curPosition = prevBlock.textLen;
+                     selBlock = textBlock;
+                     prevBlock.textLen += textBlock.textLen;
+                     textBlock.parent.subBlocks.Remove(prev);
+                     if(prev == selBlock)
+                     {
+                        selBlock = textBlock;
+                        selPosition = curPosition;
+                     }
+                     delete prev;
+                     textBlock.parent.subBlocks.Remove(textBlock);
+                     if(textBlock == selBlock)
+                     {
+                        selBlock = prevBlock;
+                        selPosition = curPosition;
+                     }
+                     delete textBlock;
+                     textBlock = prevBlock;
+
                      ComputeMinSizes();
                      ComputeSizes();
-                     //PositionForms();
+                     PositionCaret(true);
+                     Update(null);
                   }
-                  PositionCaret(true);
-                  Update(null);
                }
+               else
+                  DeleteSelection();
                break;
             case del:
-               if(textBlock.textLen > curPosition)
+               if(readOnly) break;
+               if(textBlock != selBlock || curPosition != selPosition)
+                  DeleteSelection();
+               else if(textBlock.textLen > curPosition)
                {
                   int nb = UTF8_NUM_BYTES(textBlock.text[curPosition]);
                   memmove(textBlock.text + curPosition, textBlock.text + curPosition + nb, textBlock.textLen - curPosition + 1 - nb + 1);
                   textBlock.textLen -= nb;
                   textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
-                  {
-                     //Clear(html.block);
-                     //CreateForms(html.block);
-                     ComputeMinSizes();
-                     ComputeSizes();
-                     //PositionForms();
-                  }
+
+                  ComputeMinSizes();
+                  ComputeSizes();
+
                   PositionCaret(true);
                   Update(null);
                }
@@ -3175,27 +3949,40 @@ class HelpView : HTMLView
 
                   textBlock.textLen += nextBlock.textLen;
                   textBlock.parent.subBlocks.Remove(next);
+                  if(next == selBlock)
+                  {
+                     selBlock = textBlock;
+                     selPosition = curPosition;
+                  }
                   delete next;
-                  textBlock.parent.subBlocks.Remove(nextBlock);                  
-                  delete nextBlock;
-                  
+                  textBlock.parent.subBlocks.Remove(nextBlock);
+                  if(nextBlock == selBlock)
                   {
-                     //Clear(html.block);
-                     //CreateForms(html.block);
-                     ComputeMinSizes();
-                     ComputeSizes();
-                     //PositionForms();
+                     selBlock = textBlock;
+                     selPosition = curPosition;
                   }
+                  delete nextBlock;
+
+                  ComputeMinSizes();
+                  ComputeSizes();
                   PositionCaret(true);
                   Update(null);
                }
                break;
             case enter:
             {
-               Block block { type = BR, parent = textBlock.parent, font = textBlock.font };
-               Block newBlock { type = TEXT, parent = textBlock.parent, font = textBlock.font };
-               int startY = textBlock.startY, startX = textBlock.startX;
-               int tw = 0, th = 0;
+               int th = 0;
+               Block block;
+               Block newBlock;
+               int startY, startX;
+
+               if(readOnly) break;
+               DeleteSelection();
+
+               block = { type = BR, parent = textBlock.parent, font = textBlock.font };
+               newBlock = { type = TEXT, parent = textBlock.parent, font = textBlock.font };
+               startY = textBlock.startY;
+               startX = textBlock.startX;
 
                display.FontExtent(textBlock.font.font, " ", 1, null, &th);
                textBlock.parent.subBlocks.Insert(textBlock, block);
@@ -3212,90 +3999,111 @@ class HelpView : HTMLView
                newBlock.startY = startY;
                newBlock.startX = startX;
                selPosition = curPosition = 0;
-               {
-                  //Clear(html.block);
-                  //CreateForms(html.block);
-                  ComputeMinSizes();
-                  ComputeSizes();
-                  //PositionForms();
-               }
+
+               ComputeMinSizes();
+               ComputeSizes();
+
                textBlock = newBlock;
+               selBlock = textBlock;
                PositionCaret(true);
                Update(null);
                break;
             }
+            case ctrlX:
+            case Key { del, shift = true }:
+               if(readOnly) break;
+               // Cut
+               CopySelection();
+               DeleteSelection();
+               break;
+            case ctrlC:
+            case ctrlInsert:
+               // Copy
+               CopySelection();
+               break;
             case shiftInsert:
             case ctrlV:
-            {
-               ClipBoard clipBoard { };
-               if(clipBoard.Load())
-               {  
-                  int c;
-                  char * text = clipBoard.memory;
-                  char ch;
-                  int start = 0;
-                  Block parent = textBlock.parent;
-                  FontEntry font = textBlock.font;
-                  for(c = 0; ; c++)
+               if(!readOnly)
+               {
+                  ClipBoard clipBoard { };
+                  if(clipBoard.Load())
                   {
-                     ch = text[c];
-                     if(ch == '\n' || ch == '\r' || !ch)
+                     int c;
+                     char * text = clipBoard.memory;
+                     char ch;
+                     int start = 0;
+                     Block parent;
+                     FontEntry font;
+
+                     DeleteSelection();
+
+                     parent = textBlock.parent;
+                     font = textBlock.font;
+
+                     for(c = 0; ; c++)
                      {
-                        int len = c - start;
-                        textBlock.text = renew textBlock.text char[textBlock.textLen + 1 + len];
-                        memmove(textBlock.text + curPosition + len, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
-                        memcpy(textBlock.text + curPosition, text + start, len);
-                        textBlock.textLen += len;
-                        curPosition += len;
-                        selPosition = curPosition;
-                        if(!ch) break;
+                        ch = text[c];
+                        if(ch == '\n' || ch == '\r' || !ch)
                         {
-                           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;
-
-                           display.FontExtent(textBlock.font.font, " ", 1, null, &th);
-                           textBlock.parent.subBlocks.Insert(textBlock, block);
-                           textBlock.parent.subBlocks.Insert(block, newBlock);
-
-                           startY += th;
-
-                           newBlock.textLen = textBlock.textLen - curPosition;
-                           newBlock.text = new char[newBlock.textLen+1];
-                           memcpy(newBlock.text, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
-                           textBlock.textLen = curPosition;
-                           textBlock.text[curPosition] = 0;
-
-                           newBlock.startY = startY;
-                           newBlock.startX = startX;
-                           selPosition = curPosition = 0;
-                           textBlock = newBlock;
+                           int len = c - start;
+                           textBlock.text = renew textBlock.text char[textBlock.textLen + 1 + len];
+                           memmove(textBlock.text + curPosition + len, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
+                           memcpy(textBlock.text + curPosition, text + start, len);
+                           textBlock.textLen += len;
+                           curPosition += len;
+                           selPosition = curPosition;
+                           selBlock = textBlock;
+                           if(!ch) break;
+                           {
+                              Block block { type = BR, parent = parent, font = font };
+                              Block newBlock { type = TEXT, parent = parent, font = font };
+                              int startY = textBlock.startY, startX = textBlock.startX;
+                              int th = 0;
+
+                              display.FontExtent(textBlock.font.font, " ", 1, null, &th);
+                              textBlock.parent.subBlocks.Insert(textBlock, block);
+                              textBlock.parent.subBlocks.Insert(block, newBlock);
+
+                              startY += th;
+
+                              newBlock.textLen = textBlock.textLen - curPosition;
+                              newBlock.text = new char[newBlock.textLen+1];
+                              memcpy(newBlock.text, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
+                              textBlock.textLen = curPosition;
+                              textBlock.text[curPosition] = 0;
+
+                              newBlock.startY = startY;
+                              newBlock.startX = startX;
+                              selPosition = curPosition = 0;
+                              selBlock = textBlock;
+                              textBlock = newBlock;
+                           }
+                           if(ch == '\r' && text[c+1] == '\n') c++;
+                           start = c + 1;
                         }
-                        if(ch == '\r' && text[c+1] == '\n') c++;
-                        start = c + 1;
                      }
+                     ComputeMinSizes();
+                     ComputeSizes();
+                     PositionCaret(true);
+                     Update(null);
                   }
-                  ComputeMinSizes();
-                  ComputeSizes();
-                  PositionCaret(true);
-                  Update(null);
+                  delete clipBoard;
                }
-               delete clipBoard;
                break;
-            }
             default:
             {
                // eC BUG HERE: (Should be fixed)
-               if(key.ctrl && !key.alt && ch >= 32 && ch != 128 /*&& ch < 128*/)
+               if(!readOnly && !key.ctrl && !key.alt && ch >= 32 && ch != 128 /*&& ch < 128*/)
                {
                   char string[5];
                   int len = UTF32toUTF8Len(&ch, 1, string, 5);
                   int c;
 
+                  DeleteSelection();
+
                   textBlock.text = renew textBlock.text char[textBlock.textLen + len + 1];
                   memmove(textBlock.text + curPosition + len, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
-                  
+
                   for(c = 0; c<len; c++)
                   {
                      textBlock.text[curPosition] = string[c];
@@ -3303,7 +4111,8 @@ class HelpView : HTMLView
                      curPosition++;
                   }
                   selPosition = curPosition;
-                  
+                  selBlock = textBlock;
+
                   {
                      //Clear(html.block);
                      //CreateForms(html.block);
@@ -3349,7 +4158,7 @@ class HelpView : HTMLView
          }
          else
             maxW = clientSize.w - 10 - sx;
-         
+
          display.FontExtent(textBlock.font.font, " ", 1, null, &th);
 
          while(textPos < textBlock.textLen)
@@ -3369,7 +4178,7 @@ class HelpView : HTMLView
                   len = (nextSpace - (text + textPos)) + 1;
                else
                   len = textBlock.textLen - textPos;
-               
+
                display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
 
                if(x + width + w > maxW && x > 0)
@@ -3392,7 +4201,7 @@ class HelpView : HTMLView
                int len = curPosition - startPos;
                display.FontExtent(textBlock.font.font, text + startPos, len, &tw, null);
                sx += tw;
-               break;            
+               break;
             }
             sy += th;
             sx = textBlock.startX;
@@ -3412,7 +4221,7 @@ class HelpView : HTMLView
             else if(sy - scroll.y < 0)
             {
                scrollPos.y = sy;
-               doScroll = true;            
+               doScroll = true;
             }
             if(sx - scroll.x + 10 > clientSize.w)
             {
@@ -3422,7 +4231,7 @@ class HelpView : HTMLView
             else if(sx - scroll.x < 10)
             {
                scrollPos.x = sx - 10;
-               doScroll = true;            
+               doScroll = true;
             }
             if(doScroll)
                scroll = scrollPos;
@@ -3433,7 +4242,7 @@ class HelpView : HTMLView
    }
 
    // Returns a character offset into the TextBlock from a window coordinate
-   int TextPosFromPoint(int px, int py, Block * block)
+   int TextPosFromPoint(int px, int py, Block * block, bool half)
    {
       Block parentBlock = this.textBlock.parent;
       Block textBlock;
@@ -3467,14 +4276,13 @@ class HelpView : HTMLView
          }
          else
             maxW = clientSize.w - 10 - sx;
-         
+
          display.FontExtent(textBlock.font.font, " ", 1, &space, &th);
          //space = space/2+2;
          space = 2;
 
          while(textPos < textBlock.textLen)
          {
-            int startPos = textPos;
             int width = 0;
             int x = 0;
             bool lineComplete = false;
@@ -3489,7 +4297,7 @@ class HelpView : HTMLView
                   len = (nextSpace - (text + textPos)) + 1;
                else
                   len = textBlock.textLen - textPos;
-               
+
                display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
 
                sx = x + textBlock.startX;
@@ -3502,7 +4310,7 @@ class HelpView : HTMLView
                   {
                      numBytes = UTF8_NUM_BYTES(ch);
                      display.FontExtent(textBlock.font.font, text + c, numBytes, &w, &th);
-                     if(/*py >= sy && */py < sy + th && /*px >= sx-w/2-space && */px < sx + w -w/2-space)
+                     if(/*py >= sy && */py < sy + th && /*px >= sx-w/2-space && */px < sx + (half ? w/2 : w) -space)
                         break;
                      sx += w;
                   }
@@ -3544,24 +4352,23 @@ class Documentor : GuiApplication
 {
    bool Init()
    {
-      Platform os = GetRuntimePlatform();
-      componentsApp = __ecere_COM_Initialize(false, 1, null);
-      SetPrivateModule(componentsApp);
+      Platform os = __runtimePlatform;
       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
          {
             char programFilesDir[MAX_LOCATION];
-            char appData[MAX_LOCATION]; 
+            char appData[MAX_LOCATION];
             char homeDrive[MAX_LOCATION];
             char winDir[MAX_LOCATION];
             GetEnvironment("APPDATA", appData, sizeof(appData));
@@ -3570,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();
       }
 
@@ -3596,17 +4403,38 @@ class Documentor : GuiApplication
          Module module = eModule_Load(componentsApp, "ecere" /*argv[1]*/, privateAccess);
          DataRow row;
          AddComponents(module, true);
-         mainForm.browser.currentRow = row = mainForm.browser.FindSubRow((int)module);
-         // mainForm.browser.currentRow = row = mainForm.browser.FindSubRow((int)eSystem_FindClass(componentsApp, "Window"));
+         mainForm.browser.currentRow = row = mainForm.browser.FindSubRow((int64)module);
+         // mainForm.browser.currentRow = row = mainForm.browser.FindSubRow((int64)eSystem_FindClass(componentsApp, "Window"));
          while((row = row.parent))
             row.collapsed = false;
       #endif
       }
+
+      commandThread.Create();
+      return true;
+   }
+
+   bool Cycle(bool idle)
+   {
+      if(quit)
+         mainForm.Destroy(0);
       return true;
    }
 
    void Terminate()
    {
+      PrintLn("Exited");
+      console.Flush();
+      quit = true;
+      if(commandThread.created)
+      {
+         console.CloseInput();
+         console.CloseOutput();
+         app.Unlock();
+         commandThread.Wait();
+         app.Lock();
+      }
+
       FreeContext(globalContext);
       FreeExcludedSymbols(excludedSymbols);
       ::defines.Free(FreeModuleDefine);
@@ -3619,4 +4447,222 @@ class Documentor : GuiApplication
    }
 }
 
+ConsoleFile console { };
 MainForm mainForm { };
+bool quit;
+
+Thread commandThread
+{
+   unsigned int Main()
+   {
+      while(!quit)
+      {
+         char command[1024];
+         console.GetLine(command, sizeof(command));
+         if(!quit && command[0])
+         {
+            app.Lock();
+            if(!strcmpi(command, "Activate"))
+               mainForm.Activate();
+            else if(!strcmpi(command, "Quit"))
+               quit = true;
+            app.Unlock();
+         }
+      }
+      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 { };