#help_index "AutoComplete/Dictionary"
U0 ACDDictWordsAdd(U8 *st)
{
    I64 i;
    U8 *ptr;

    if (st && *st && (ptr = ACDWordPtAt(st)))
    {
        for (i = 0; i < ACD_FILLINS_NUM; i++)
        {
            if (*ptr++ != ACD_WORD_CHAR)
                break;
            if (i)
                '\n';
            acd.fillins[i] = ptr - 1;
            "$GREEN$'%d'$FG$ $BLACK$%-23ts$FG$", i, ptr;
            ptr += StrLen(ptr) + 3;
        }
        acd.num_fillins = i;
    }
}

#help_index "AutoComplete"
U0 ACDocReset(I64 left, I64 top)
{
    CDoc *doc = DocPut;

    DocReset(doc, TRUE);
    doc->flags |= DOCF_SIZE_MIN;
    Fs->border_src  = BDS_CONST;
    Fs->border_attr = LTGRAY << 4 + DriveTextAttrGet(':') & 15;
    Fs->text_attr   = LTGRAY << 4 + BLUE;
    LBtr(&Fs->display_flags, DISPLAYf_SHOW);
    WinHorz(left, Fs->win_right);
    WinVert(top,  Fs->win_bottom);
    DocCursor;
}

I64 ACSkipCrap(U8 *src, I64 len)
{
    I64 j;

    j = len - 1;
    while (j >= 0)
    {
        if (Bt(char_bmp_alpha_numeric, src[j]))
            break;
        else
            j--;
    }
    return j + 1;
}

I64 ACPriorWordInStr(U8 *src, U8 *dst, I64 len, I64 buf_size)
{
    I64 i, j = 0, k;

    i = len - 1;
    while (i >= 0)
        if (!Bt(char_bmp_alpha_numeric, src[i]))
            break;
        else
            i--;
    if (i >= -1 && len > 0)
        for (k = i + 1; k < len && j < buf_size - 1; k++)
            dst[j++] = src[k];
    dst[j] = 0;
    return i + 1;
}

U0 ACFillInAdd(CHashAC *tmpw)
{
    I64 k;

    if (ac.num_fillins < AC_FILLINS_NUM || tmpw->hits > ac.fillin_hits[ac.num_fillins - 1])
    {
        for (k = ac.num_fillins - 1; k >= 0; k--)
        {
            if (tmpw->hits <= ac.fillin_hits[k])
                break;
            else
            {
                ac.fillin_matches[k + 1] = ac.fillin_matches[k];
                ac.fillin_hits[k + 1]    = ac.fillin_hits[k];
            }
        }
        ac.fillin_matches[k + 1] = tmpw;
        ac.fillin_hits[k + 1]    = tmpw->hits;
        if (ac.num_fillins < AC_FILLINS_NUM)
            ac.num_fillins++;
    }
}

U0 ACPutChoices(CDoc *focus_l, CDocEntry *doc_e, CTask *focus_task, Bool force_refresh)
{
    I64          i, data_col;
    U8          *buf, *buf1, *src = NULL, *st;
    CHashAC     *tmpw;
    F64          timeout_time = tS + 0.5;
    CHashSrcSym *tmph;

    if (LBtr(&ac.flags, ACf_LAST_WAS_KEYMAP))
    {
        ac.col = ac.old_col;
        ac.row = ac.old_row;
    }
    else
    {
        ac.col = Fs->win_left;
        ac.row = Fs->win_top;
    }

    src = DocScanLine(focus_l, doc_e, &data_col);
    DocUnlock(focus_l);
    i = StrLen(src);
    buf  = MAlloc(MaxI64(i + 1, 256));
    buf1 = MAlloc(MaxI64(i + 1, 256));
    if (data_col == -1)
        data_col = 0;
    data_col = ACPriorWordInStr(src, buf, data_col, 256);
    ac.partial_len = StrLen(buf);
    data_col = ACSkipCrap(src, data_col);
    data_col = ACPriorWordInStr(src, buf1, data_col, 256);

    if (!ac.cur_word || StrCompare(ac.cur_word, buf) || force_refresh)
    {
        st = ac.cur_word;
        ac.cur_word = SysStrNew(buf);
        Free(st);
        ac.num_fillins = 0;
        if (*ac.cur_word)
            for (i = 0; i <= ac.hash_table->mask && tS < timeout_time; i++)
            {
                tmpw = ac.hash_table->body[i];
                while (tmpw)
                {
                    if (!MemCompare(ac.cur_word, tmpw->str, StrLen(ac.cur_word)))
                        ACFillInAdd(tmpw);
                    tmpw = tmpw->next;
                }
            }
        ACDocReset(ac.col, ac.row);
        if (ac.cur_word && *ac.cur_word)
        {
            "$RED$Word:%s$FG$\n", ac.cur_word;
            for (i = 0; i < ac.num_fillins; i++)
            {
                st = ac.fillin_matches[i]->str;
                "$GREEN$F%02d$FG$$HL$ ", i + 1;
                if (TaskValidate(focus_task) && (tmph = HashFind(st, focus_task->hash_table, HTG_SRC_SYM)) && tmph->src_link)
                {
                    if (tmph->type & HTF_PUBLIC)
                        "$IV$";
                    "$TX+UL+L+PU,\"%$Q\",A=\"%s\"$$IV,0$\n", st, tmph->src_link;
                }
                else
                    "%s\n", st;
                "$HL,0$";
            }
            if (acd.has_words)
                ACDDictWordsAdd(ac.cur_word);
        }
        else if (FileFind("::/Doc/StandBy.DD"))
            Type("::/Doc/StandBy.DD", 0);
    }
    Free(src);
    Free(buf);
    Free(buf1);
}

U0 ACTaskNormal(I64 sc, I64 last_sc, CTask *focus_task, CTask *original_focus_task)
{
    CDoc      *doc;
    CDocEntry *doc_e;

    if ((doc=DocPut(focus_task)) && focus_task != Fs && Bt(&focus_task->display_flags, DISPLAYf_SHOW))
    {
        DocLock(doc);
        if (TaskValidate(focus_task) &&
            original_focus_task == sys_focus_task &&
            doc &&
            doc == DocPut(focus_task) &&
            (doc_e = doc->cur_entry))
        {
            if (doc_e == doc)
                doc_e = doc_e->last;
            while (doc_e->last != doc && (doc_e->type_u8 == DOCT_NEW_LINE || doc_e->type_u8 == DOCT_SOFT_NEW_LINE))
                doc_e = doc_e->last;
            while (doc_e->last->type_u8 != DOCT_NEW_LINE && doc_e->last != doc)
                doc_e = doc_e->last;
            ACPutChoices(doc, doc_e, focus_task, ToBool(sc != last_sc));
        }
        else
            DocUnlock(doc);
    }

    if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
        WinZBufUpdate;
}

U0 ACTaskCtrl(I64 sc, I64 last_sc, CTask *focus_task, CTask *original_focus_task)
{
    if (TaskValidate(focus_task) && (focus_task->scroll_x || focus_task->scroll_y))
    {
        if (LBtr(&Fs->display_flags, DISPLAYf_SHOW))
            WinZBufUpdate;
    }
    else
    {
        if (sc != last_sc)
        {
            Bts(&ac.flags, ACf_LAST_WAS_KEYMAP);
            ac.old_col = ac.col;
            ac.old_row = ac.row;
            if (sc & SCF_ALT)
            {
                ACDocReset(TEXT_COLS - 50, 3);

                if (TaskValidate(original_focus_task) && !Bt(&original_focus_task->win_inhibit, WIf_SELF_KEY_DESC))
                    KeyMapFamily(original_focus_task, 0, ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));

                KeyMapCtrlAltFamily(ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));
            }
            else if (TaskValidate(original_focus_task) && !Bt(&original_focus_task->win_inhibit, WIf_SELF_KEY_DESC))
            {
                ACDocReset(TEXT_COLS - 50, 3);
                KeyMapFamily(original_focus_task, SCF_CTRL, ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));
            }
        }

        if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
            WinZBufUpdate;
    }
}

U0 ACTaskAlt(I64 sc, I64 last_sc, CTask *, CTask *original_focus_task)
{
    if (sc != last_sc && TaskValidate(original_focus_task) && !Bt(&original_focus_task->win_inhibit, WIf_SELF_KEY_DESC))
    {
        Bts(&ac.flags, ACf_LAST_WAS_KEYMAP);
        ac.old_col = ac.col;
        ac.old_row = ac.row;

        ACDocReset(TEXT_COLS - 50, 3);
        KeyMapFamily(original_focus_task, SCF_ALT, ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));
    }
    if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
        WinZBufUpdate;
}

U0 ACTaskEndCB()
{
    ac.task = NULL;
    RegWrite("AutoComplete", "ac.col=%d;ac.row=%d;", ac.col, ac.row);
    Exit;
}

U0 ACTask(I64)
{
    CTask   *focus_task, *original_focus_task;
    I64      ch, scan_code = 0, last_scan_code = 0;
    CDoc    *doc;

    Fs->task_end_cb = &ACTaskEndCB;
    DocTermNew;
    LBts(&Fs->display_flags, DISPLAYf_SHOW);
    ACDocReset(ac.col, ac.row);
    LBts(&Fs->display_flags, DISPLAYf_WIN_ON_TOP);
    Fs->win_inhibit = WIG_NO_FOCUS_TASK_DEFAULT;
    Free(ac.cur_word);
    ac.cur_word = NULL;

    while (TRUE)
    {
        if (scan_code & (SCF_CTRL | SCF_ALT) || TSCGet > KbdMouseEventTime + counts.time_stamp_freq >> 1)
        {
            last_scan_code = scan_code;
            scan_code = kbd.scan_code;
        }
        original_focus_task = focus_task = sys_focus_task;
        while (TaskValidate(focus_task) && Bt(&focus_task->task_flags, TASKf_INPUT_FILTER_TASK))
            focus_task = focus_task->parent_task;
        if (scan_code & SCF_CTRL)
            ACTaskCtrl(scan_code, last_scan_code, focus_task, original_focus_task);
        else if (TaskValidate(focus_task))
        {
            if (scan_code & SCF_ALT)
                ACTaskAlt(scan_code, last_scan_code, focus_task, original_focus_task);
            else
                ACTaskNormal(scan_code, last_scan_code, focus_task, original_focus_task);
        }
        Sleep(333);
        if (MessageScan(&ch,, 1 << MESSAGE_KEY_DOWN) && (ch == CH_ESC || ch == CH_SHIFT_ESC))
            break;
        doc = DocPut;
        DocLock(doc);
        if (doc->cur_entry->de_flags & DOCEF_LINK)
        {
            '' CH_SPACE;
            doc->cur_entry = doc;
        }
        DocUnlock(doc);
    }
}

public Bool AutoComplete(Bool val=OFF)
{//Turn AutoComplete OFF or ON.
    Bool old_val = FALSE;

    while (Bt(&ac.flags, ACf_INIT_IN_PROGRESS))
        Sleep(10);
    if (val)
    {
        if (Bt(&sys_run_level, RLf_AUTOCOMPLETE))
        {
            if (TaskValidate(ac.task))
                old_val = TRUE;
            else
            {
                ac.task = Spawn(&ACTask, NULL, "AutoComplete");
                TaskWait(ac.task);
            }
            WinToTop(ac.task);
        }
    }
    else
    {
        if (TaskValidate(ac.task))
        {
            if (Bt(&sys_run_level, RLf_AUTOCOMPLETE))
                old_val = TRUE;
            Kill(ac.task);
        }
    }
    return old_val;
}