#help_index "Info;Hash/System;Cmd Line (Typically)"
class CWho
{
        CHashGeneric *h;
        U8 *idx;
};

I64 HashEntriesCompare(CWho *h1, CWho *h2)
{
        I64 i1, i2;

        if (i1 = StrCompare(h1->h->str, h2->h->str))
                return i1;
        i1 = HashTypeNum(h1->h);
        i2 = HashTypeNum(h2->h);

        return i1 - i2;
}

I64 HashEntriesCompare2(CWho *h1, CWho *h2)
{
        CHashFun        *tmpf1 = h1->h, *tmpf2 = h2->h;
        I64                      i1 = HashVal(tmpf1), i2 = HashVal(tmpf2);

        if (i1 == i2)
        {
                i1 = HashTypeNum(tmpf1);
                i2 = HashTypeNum(tmpf2);
                if (i1 == i2)
                        return StrCompare(tmpf1->str, tmpf2->str);
        }

        return i1 - i2;
}

I64 HelpIndexCount(U8 *ptr, U8 *idx)
{
        I64 count = 0, ch, idx_len = StrLen(idx);

        while (*ptr)
        {
                if (!StrNCompare(ptr, idx, idx_len))
                        count++;
                while (ch = *ptr++)
                        if (ch == ';')
                                break;
                if (!ch)
                        ptr--;
        }

        return count;
}

U8 *HelpIndexStr(U8 **_ptr, U8 *idx)
{
        U8 *ptr = *_ptr, *ptr2, *res;
        I64 ch, idx_len = StrLen(idx);

        while (*ptr)
        {
                ptr2 = ptr;
                while (ch = *ptr++)
                        if (ch == ';')
                                break;
                if (!ch)
                        ptr--;
                *_ptr = ptr;
                if (!StrNCompare(ptr2, idx, idx_len))
                {
                        if (ch == ';')
                                ptr--;
                        *ptr = 0;
                        res = StrNew(ptr2);
                        *ptr = ch;
                        return res;
                }
        }

        return NULL;
}

U8 *HelpComment(CTask *task, CHash *tmph, U8 *_src_link)
{
        CDoc            *doc;
        CDocEntry       *doc_e;
        U8                      *res = NULL, *ptr, *ptr2, *src_link = StrNew(_src_link);

        if (*src_link == 'F' && src_link[2] == ':')
                *src_link = 'P';
        XTalkWait(task, "Ed(0x%X, DOF_DONT_WINMGR_SYNC | DOF_DONT_SHOW);\n", src_link);
        Free(src_link);

        doc = DocPut(task);
        doc_e = doc->cur_entry;
        if (tmph->type & HTT_FUN)
        {
                if (Bt(&tmph(CHashFun *)->flags, Ff__EXTERN) || Bt(&tmph(CHashFun *)->flags, Ff_INTERNAL))
                        while (doc_e != doc && (!(doc_e->de_flags & DOCEF_TAG) || !StrOcc(doc_e->tag, ';')))
                                doc_e = doc_e->next;
                else
                        while (doc_e != doc && (!(doc_e->de_flags & DOCEF_TAG) || !StrOcc(doc_e->tag, '{')))
                                doc_e = doc_e->next;
        }
        if (doc_e != doc)
        {
                if (doc_e->de_flags & DOCEF_TAG)
                {
                        ptr = doc_e->tag;
                        if (ptr2 = StrMatch("//", ptr))
                                ptr = ptr2 + 2;
                        else if (ptr2 = StrMatch("/*", ptr))
                                ptr = ptr2 + 2;
                        else if (!StrNCompare(ptr, "public", 6))
                                ptr += 6;
                        while (*ptr == CH_SPACE)
                                ptr++;
                        res = StrNew(ptr);
                        doc_e = doc_e->next;
                }
                while (doc_e != doc && doc_e->type_u8 != DOCT_NEW_LINE)
                {
                        if (doc_e->type_u8 == DOCT_TAB)
                        {
                                ptr = MStrPrint("%s ", res);
                                Free(res);
                                res = ptr;
                        }
                        else if (doc_e->de_flags & DOCEF_TAG)
                        {
                                ptr = MStrPrint("%s%s", res, doc_e->tag);
                                Free(res);
                                res = ptr;
                        }
                        doc_e = doc_e->next;
                }
        }
        XTalkWait(task, "%c", CH_SHIFT_ESC);
        if (res)
        {
                ptr = MStrUtil(res, SUF_REM_TRAILING | SUF_REM_LEADING | SUF_SINGLE_SPACE);
                Free(res);
                res = ptr;
        }

        return res;
}

I64 HashEntriesCompare3(CWho *h1, CWho *h2)
{
        I64 i, i1 = 0, i2 = 0;

        i = StrCompare(h1->idx, h2->idx);
        if (i)
                return i;
        else
        {
                if (h1->h->type & HTT_HELP_FILE)
                        i1 = 1;
                if (h2->h->type & HTT_HELP_FILE)
                        i2 = 1;
                i = i2 - i1;
                if (i)
                        return i;
                else
                        return StrCompare(h1->h->str, h2->h->str);
        }
}

public U0 Who(U8 *fu_flags=NULL, CHashTable *h=NULL, U8 *idx=NULL, CDoc *doc=NULL)
{       //Dump hash symbol table.
        // "+p" for only public symbols
        // "+m" to order by number (normally alphabetical)
        // "-r" just local hash table
        CHashTable              *table;
        CHashSrcSym             *tmph;
        CHashGeneric    *ptr;
        CWho                    *list;
        I64                              count, i, j, k, f = 0;
        U8                               buf[512], *last_idx = StrNew(""), *cur_idx, *comment;
        Bool                     recurse, publics, map;
        CTask                   *task;

        FlagsScan(&f, Define("ST_FILE_UTIL_FLAGS"), "+r");
        FlagsScan(&f, Define("ST_FILE_UTIL_FLAGS"), fu_flags);
        if (f & ~(FUF_RECURSE | FUF_PUBLIC | FUF_MAP))
                throw('FUF');
        recurse = Bt(&f, FUf_RECURSE);
        publics = Bt(&f, FUf_PUBLIC);
        map     = Bt(&f, FUf_MAP);

        if (!h)
                h = Fs->hash_table;

        if (idx)
        {
                task = User;
                TaskWait(task);
                LBtr(&task->display_flags, DISPLAYf_SHOW);
        }
        else
                task = NULL;

        count = 0;
        table = h;
        while (table)
        {
                for (i = 0; i <= table->mask; i++)
                {
                        tmph = table->body[i];
                        while (tmph)
                        {
                                if (!(tmph->type & (HTF_IMPORT | HTF_PRIVATE)) && (tmph->type & HTF_PUBLIC || !publics))
                                {
                                        if (!idx)
                                                count++;
                                        else if (tmph->type & HTG_SRC_SYM && (cur_idx = tmph->idx))
                                                count += HelpIndexCount(cur_idx, idx);
                                }
                                tmph = tmph->next;
                        }
                }
                if (recurse)
                        table = table->next;
                else
                        break;
        }
        if (!count) goto wh_done;

        list = CAlloc(count * sizeof(CWho));
        j = 0;
        table = h;
        while (table)
        {
                for (i = 0; i <= table->mask; i++)
                {
                        tmph = table->body[i];
                        while (tmph)
                        {
                                if (!(tmph->type & (HTF_IMPORT | HTF_PRIVATE)) && (tmph->type & HTF_PUBLIC || !publics))
                                        if (!idx)
                                                list[j++].h = tmph;
                                        else if (tmph->type & HTG_SRC_SYM && (cur_idx=tmph->idx) && (k = HelpIndexCount(cur_idx, idx)))
                                                while (k--)
                                                {
                                                        list[j].idx = HelpIndexStr(&cur_idx, idx);
                                                        list[j++].h = tmph;
                                                }
                                tmph = tmph->next;
                        }
                }
                if (recurse)
                        table = table->next;
                else
                        break;
        }

        if (map)
                QuickSort(list, count, sizeof(CWho), &HashEntriesCompare2);
        else if (idx)
                QuickSort(list, count, sizeof(CWho), &HashEntriesCompare3);
        else
                QuickSort(list, count, sizeof(CWho), &HashEntriesCompare);

        if (idx)
        {
                progress1_max = count;
                progress1 = 0;
        }
        for (i = 0; i < count; i++)
        {
                comment = NULL;
                ptr = list[i].h;
                if (idx)
                        if (cur_idx = list[i].idx)
                        {
                                if (StrCompare(cur_idx, last_idx))
                                {
                                        Free(last_idx);
                                        last_idx = StrNew(cur_idx);
                                        if (i)
                                                DocPrint(doc, "\n\n");
                                        DocPrint(doc, "$WW,0$$PURPLE$$TX+CX,\"%$Q\"$$FG$\n", cur_idx);
                                }
                        }

                if (idx && ptr->type & HTT_HELP_FILE)
                {
                        DocPrint(doc, "$WW,1$");
                        DocType(doc, ptr->str);
                        DocPrint(doc, "$WW,0$");
                }
                else
                {
                        if (ptr->type & HTG_SRC_SYM && ptr(CHashSrcSym *)->src_link)
                        {
                                DocPrint(doc, "$LK,\"%-20s\",A=\"%s\"$", ptr->str, ptr(CHashSrcSym *)->src_link);
                                if (idx)
                                        comment = HelpComment(task, ptr, ptr(CHashSrcSym *)->src_link);
                        }
                        else
                                DocPrint(doc, "%-20s", ptr->str);

                        if (!idx)
                        {
                                if (ptr->type & HTT_DEFINE_STR)
                                {
                                        j = ptr(CHashDefineStr *)->count;
                                        if (j == -1)
                                                StrPrint(buf, "%-10t$Q   ", ptr(CHashDefineStr *)->data);
                                        else
                                                StrPrint(buf, "%-10t$Q %02X", ptr(CHashDefineStr *)->data, j);
                                }
                                else if (ptr->type & HTT_GLOBAL_VAR)
                                        StrPrint(buf, "%010X   ", ptr(CHashGlobalVar *)->data_addr);
                                else
                                        StrPrint(buf, "%010X   ", HashVal(ptr));
                                j = HashEntrySize(ptr);
                                if (j == -1)
                                        CatPrint(buf, " %04X            ", ptr->use_count);
                                else
                                        CatPrint(buf, " %04X %010X ", ptr->use_count, j);
                        }
                        else
                                *buf = 0;

                        k = ptr->type;
                        if (publics)
                                k &= ~HTF_PUBLIC;
                        if (!(k & HTG_TYPE_MASK))
                                CatPrint(buf, "NULL ");
                        while (k)
                        {
                                j = Bsf(k);
                                if (j < 0)
                                        break;
                                Btr(&k, j);
                                CatPrint(buf, "%Z ", j, "ST_HTT_TYPES");
                        }
                        DocPrint(doc, "%s", buf);
                        if (comment)
                        {
                                DocPrint(doc, "$GREEN$%s$FG$", comment);
                                Free(comment);
                        }
                        DocPrint(doc, "\n");
                }
                Free(list[i].idx);
                if (idx)
                        progress1++;
        }
        Free(list);
        if (idx)
                progress1 = progress1_max = 0;

wh_done:
        if (doc)
        {
                if (doc->head.next == doc)
                        DocPrint(doc, "No Match");
                else
                        DocRecalc(doc);
        }
        Free(last_idx);
        Kill(task);
}

#help_index "Info;Hash;Cmd Line (Typically)"

#define HDR_NUM 16
public I64 HashDepthRep(CHashTable *table=NULL)
{       //Hash table linked-list chain depth report.
        //Histogram of collision count.
        I64              i, j, longest = 0, count = 0, a[HDR_NUM];
        CHash   *tmph;

        if (!table)
                table = Fs->hash_table;
        MemSet(a, 0, sizeof(a));
        for (i = 0; i <= table->mask; i++)
        {
                tmph = table->body[i];
                if (tmph)
                {
                        j = LinkedListCount(tmph);
                        if (j < HDR_NUM)
                                a[j]++;
                        count += j;
                        if (j > longest)
                                longest = j;
                }
        }
        "Histogram\n";
        for (i = 0; i < HDR_NUM; i++)
                if (a[i])
                        "%02d:%d\n", i, a[i];
        "Size:%d Count:%d Longest:%d\n", table->mask + 1, count, longest;

        return longest;
}

#help_index "Help System"
#help_file "::/Doc/HelpSystem"

public U0 DocHelpIdx(CDoc *doc, U8 *idx)
{//Put to doc report for given help idx.
        Who("+p",, idx, doc);
}

public U0 PopUpHelpIndex(U8 *idx, CTask *parent=NULL)
{//PopUp win report for given help idx.
        U8 *buf;

        buf = MStrPrint("DocHelpIdx(DocPut, \"%s\"); View;", idx);
        PopUp(buf, parent);
        Free(buf);
}

#help_index "Hash/System"
public U0 MapFileLoad(U8 *filename)
{//Load map file so we have src line info.
        U8                      *st, *ptr, *name = ExtDefault(filename, "MAP"), *absname = FileNameAbs(name);
        CDoc            *doc = DocRead(name);
        CDocEntry       *doc_e;
        CHashSrcSym     *tmph;
        I64                      i, j, base=0;
        CDebugInfo      *debug_info;

        FileExtRemove(absname);
        if (absname[1] == ':' && StrLen(absname) > 2 && (tmph = HashSingleTableFind(absname + 2, Fs->hash_table, HTT_MODULE)))
                base = tmph(CHashGeneric *)->user_data0 + sizeof(CBinFile);

        if (!doc) return;
        doc_e = doc->head.next;
        while (doc_e != doc)
        {
                if (doc_e->type_u8 == DOCT_LINK)
                {
                        if (*doc_e->tag)
                                st = MStrUtil(doc_e->tag, SUF_REM_TRAILING);
                        else
                                st = MStrUtil(doc_e->aux_str, SUF_REM_TRAILING);
                        if (tmph = HashSingleTableFind(st, Fs->hash_table, HTG_SRC_SYM))
                        {
                                if (*doc_e->tag)
                                {
                                        Free(tmph->src_link);
                                        tmph->src_link = doc_e->aux_str;
                                        ptr = tmph->src_link;
                                        if (ptr[0] && ptr[1] && ptr[2] == ':')
                                        {
                                                if (ptr[3] == ':')
                                                        ptr[3] = blkdev.boot_drive_let;
                                                else if (ptr[3] == '~')
                                                        ptr[3] = *blkdev.home_dir;
                                        }
                                        doc_e->aux_str = NULL;
                                }
                                if (tmph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM) &&
                                        !(debug_info = tmph->debug_info) && doc_e->bin_data &&
                                        (debug_info = doc_e->bin_data->data))
                                {
                                        if (doc_e->bin_data->size > MSize(debug_info))
                                                "Corrupt Map Entry\n";
                                        else
                                        {
                                                doc_e->bin_data->data = NULL;
                                                tmph->debug_info = debug_info;
                                                for (i = debug_info->min_line; i <= debug_info->max_line + 1; i++)
                                                {
                                                        j = i - debug_info->min_line;
                                                        if (debug_info->body[j])
                                                                debug_info->body[j] = debug_info->body[j] + base;
                                                }
                                        }
                                }
                        }
                        Free(st);
                }
                doc_e = doc_e->next;
        }
        DocDel(doc);
        Free(name);
        Free(absname);
}