#help_index "DolDoc/Editor"

public Bool DocGoToLine(CDoc *doc, I64 line_num) //one based
{//Nearest to specified line num.  Move cur_entry & center.
    Bool res = FALSE, unlock;

    if (doc)
    {
        unlock = DocLock(doc);
        doc->x = 0;
        doc->y = line_num - 1;
        DocRecalc(doc, RECALCt_FIND_CURSOR);
        DocCenter(doc);
        if (doc->cur_entry->y == line_num - 1)
            res = TRUE;
        if (unlock)
            DocUnlock(doc);
    }

    return res;
}

public Bool DocFind(CDoc *haystack_doc, I64 start_line_num=I64_MIN, U8 *needle, I64 match=1)
{//Find str by searching tags. Move cur_entry & center.
    Bool         res = FALSE, unlock;
    CDocEntry   *doc_e;
    U8          *ptr;
    I64          i;

    if (haystack_doc)
    {
        unlock = DocLock(haystack_doc);
        if (start_line_num == I64_MIN)
        {
            res = TRUE;
            doc_e = haystack_doc->head.next;
        }
        else
        {
            res = DocGoToLine(haystack_doc, start_line_num);
            doc_e = haystack_doc->cur_entry;
        }
        if (res)
        {
            if (needle)
            {
                res = FALSE;
                while (doc_e != haystack_doc)
                {
                    if (doc_e->de_flags & DOCEF_TAG && doc_e->tag && //TODO: handle multi-DocEntry strs
                        (ptr = StrIMatch(needle, doc_e->tag)))
                    {
                        i = ptr-doc_e->tag;
                        if (!--match)
                        {
                            haystack_doc->cur_entry = doc_e;
                            if (i < doc_e->min_col)
                                i = doc_e->min_col;
                            if (i > doc_e->max_col)
                                i = doc_e->max_col;
                            haystack_doc->cur_col = i;
                            res = TRUE;
                            break;
                        }
                    }
                    doc_e = doc_e->next;
                }
            }
            else
                res = FALSE;
        }
        if (!res)
            DocBottom(haystack_doc);
        DocCenter(haystack_doc);
        if (unlock)
            DocUnlock(haystack_doc);
    }

    return res;
}

public Bool DocAnchorFind(CDoc *haystack_doc, U8 *needle_str)
{//Find named anchor. Move cur_entry & center.
    Bool         res = FALSE, unlock;
    CDocEntry   *doc_e;

    if (haystack_doc)
    {
        unlock = DocLock(haystack_doc);
        doc_e = haystack_doc->head.next;
        if (needle_str)
            while (doc_e != haystack_doc)
            {
                if (doc_e->type_u8 == DOCT_ANCHOR && doc_e->de_flags & DOCEF_AUX_STR)
                {
                    if (!StrCompare(needle_str, doc_e->aux_str))
                    {
                        haystack_doc->cur_entry = doc_e;
                        haystack_doc->cur_col   = doc_e->min_col;
                        res = TRUE;
                        break;
                    }
                }
                doc_e = doc_e->next;
            }
        if (!res)
            DocBottom(haystack_doc);
        DocCenter(haystack_doc);
        if (unlock)
            DocUnlock(haystack_doc);
    }

    return res;
}

public U0 EdFindNext(CDoc *doc)
{//Editor F3 find next,  possibly doing replaces.
    Bool         unlock = DocLock(doc);
    U8          *ptr, *ptr2, *ptr3;
    CDocEntry   *doc_ce = doc->cur_entry, *doc_e = doc_ce;
    I64          sf_flags;

    if (doc->find_replace->match_case)
        sf_flags = 0;
    else
        sf_flags = SFF_IGNORE_CASE;
    if (doc->find_replace->whole_labels)
        sf_flags |= SFG_WHOLE_LABELS;
    do
    {
        if (doc_e != doc)
        {
            if (doc_e->de_flags & DOCEF_TAG && doc_e->tag && !(doc_e->de_flags & (DOCEG_DONT_EDIT | DOCEF_FILTER_SKIP)))
            {
                if (doc_e->type & DOCET_SEL || !doc->find_replace->scan_sel_text)
                {
                    if (doc->find_replace->scan_fwd)
                    {
                        if (doc_e == doc_ce)
                        {
                            ptr = doc_ce->tag + doc->cur_col + 1;
                            if (ptr - doc_ce->tag >= doc_ce->max_col)
                                goto fn_skip;
                            if (ptr - doc_ce->tag < doc_ce->min_col)
                                ptr = doc_ce->tag + doc_ce->min_col;
                        }
                        else
                            ptr = doc_e->tag;
                        if (ptr = StrFind(doc->find_replace->find_text, ptr, sf_flags))
                        {
                            doc->cur_entry  = doc_e;
                            doc->cur_col    = ptr-doc_e->tag;
                            if (doc->cur_col >= doc_e->max_col)
                                doc->cur_col = doc_e->max_col - 1;
                            if (doc->cur_col < doc_e->min_col)
                                doc->cur_col = doc_e->min_col;
                            DocCenter(doc);
                            if (unlock)
                                DocUnlock(doc);
                            return;
                        }
                    }
                    else
                    {
                        ptr2 = NULL;
                        ptr = doc_e->tag + doc_e->min_col;
                        if (doc_e == doc_ce)
                            ptr3 = doc_ce->tag + doc->cur_col;
                        else
                            ptr3 = doc_e->tag + doc_e->max_col;
                        while (ptr = StrFind(doc->find_replace->find_text, ptr, sf_flags))
                        {
                            if (ptr >= ptr3)
                                break;
                            ptr2 = ptr++;
                        }
                        if (ptr2 && ptr2 < ptr3)
                        {
                            doc->cur_entry  = doc_e;
                            doc->cur_col    = ptr2 - doc_e->tag;
                            if (doc->cur_col >= doc_e->max_col)
                                doc->cur_col = doc_e->max_col - 1;
                            if (doc->cur_col < doc_e->min_col)
                                doc->cur_col = doc_e->min_col;
                            DocCenter(doc);
                            if (unlock)
                                DocUnlock(doc);

                            return;
                        }
                    }
                }
            }
        }
fn_skip:
        if (doc->find_replace->scan_fwd)
            doc_e = doc_e->next;
        else
            doc_e = doc_e->last;
    }
    while (doc_e != doc_ce);

    if (unlock)
        DocUnlock(doc);
}

public U0 EdSelAll(CDoc *doc, Bool sel)
{//Set state of DOCET_SEL on all entries.
    Bool         unlock = DocLock(doc);
    CDocEntry   *doc_e = doc->head.next;

    while (doc_e != doc)
    {
        BEqual(&doc_e->type, DOCEt_SEL, sel);
        doc_e = doc_e->next;
    }
    if (unlock)
        DocUnlock(doc);
}

public Bool EdFindPaired(CDoc *doc, U8 plus, U8 minus, 
Bool forward, Bool abort_on_dbl_colon=FALSE)
{//Find { } or ( ) pair. Move cur_entry & center.
    Bool         unlock = DocLock(doc), res = FALSE;
    U8          *ptr;
    I64          ch, levels = 0, colons = 0, original_col = doc->cur_col;
    CDocEntry   *doc_ce = doc->cur_entry, *doc_e = doc_ce, *original_ce = doc_ce;

    if (abort_on_dbl_colon && EdCurU8(doc) == ':')
        colons = 1;
    else
        colons = 0;
    do
    {
        if (doc_e != doc)
        {
            if (doc_e->de_flags & DOCEF_TAG && doc_e->tag && !(doc_e->de_flags & DOCEF_FILTER_SKIP))
            {
                if (forward)
                {
                    if (doc_e == doc_ce)
                        ptr = doc_e->tag + doc->cur_col + 1;
                    else
                        ptr = doc_e->tag;
                    if (ptr - doc_e->tag < doc_e->min_col)
                        ptr = doc_e->tag + doc_e->min_col;
                    if (ptr - doc_e->tag >= doc_e->max_col)
                        goto pa_skip;
                    while (ch = *ptr++)
                        if (abort_on_dbl_colon && ch == ':')
                        {
                            if (++colons == 2)
                            {
                                doc->cur_entry  = doc_e;
                                doc->cur_col    = ptr - doc_e->tag - 1;
                                EdCursorLeft(doc);
                                res = FALSE;
                                goto pa_done;
                            }
                        }
                        else
                        {
                            colons = 0;
                            if (ch == plus)
                                levels++;
                            else if (ch == minus)
                            {
                                if (!levels--)
                                {
                                    doc->cur_entry  = doc_e;
                                    doc->cur_col    = ptr - doc_e->tag - 1;
                                    res = doc->cur_entry != original_ce || doc->cur_col != original_col;
                                    goto pa_done;
                                }
                            }
                        }
                }
                else
                {
                    if (doc_e == doc_ce)
                    {
                        ptr = doc_e->tag + doc->cur_col - 1;
                        if (ptr - doc_e->tag >= doc_e->max_col)
                            ptr = doc_e->tag + doc_e->max_col - 1;
                    }
                    else
                        ptr = doc_e->tag + doc_e->max_col - 1;
                    if (ptr - doc_e->tag < doc_e->min_col)
                        goto pa_skip;
                    while (ptr >= doc_e->tag + doc_e->min_col)
                    {
                        ch = *ptr--;
                        if (abort_on_dbl_colon && ch == ':')
                        {
                            if (++colons == 2)
                            {
                                doc->cur_entry  = doc_e;
                                doc->cur_col    = ptr - doc_e->tag + 1;
                                res = FALSE;
                                goto pa_done;
                            }
                        }
                        else
                        {
                            colons = 0;
                            if (ch == plus)
                                levels++;
                            else if (ch == minus)
                            {
                                if (!levels--)
                                {
                                    doc->cur_entry  = doc_e;
                                    doc->cur_col    = ptr-doc_e->tag+1;
                                    res = doc->cur_entry != original_ce || doc->cur_col != original_col;
                                    goto pa_done;
                                }
                            }
                        }
                    }
                }
            }
        }
pa_skip:
        if (forward)
            doc_e = doc_e->next;
        else
            doc_e = doc_e->last;
    }
    while (doc_e != doc_ce);

pa_done:
    DocRecalc(doc);
    DocCenter(doc);
    if (unlock)
        DocUnlock(doc);

    return res;
}

public Bool EdGoToFun(CDoc *doc, Bool forward, Bool abort_on_dbl_colon)
{//Move cur_entry to start of cur function and center. (Shoddy)
    Bool unlock = DocLock(doc), res = FALSE;
    I64  ch, levels, colons;

    if (forward)
    {
        levels = 0;
        colons = 0;
        while (doc->cur_entry != doc)
        {
            ch = EdCurU8(doc);
            if (abort_on_dbl_colon && ch == ':')
            {
                if (++colons == 2)
                {
                    EdCursorLeft(doc);
                    break;
                }
            }
            else
            {
                colons = 0;
                if (ch == '{')
                    levels++;
                else if (ch == '}' && !levels--)
                    break;
            }
            EdCursorRight(doc);
        }
        DocRecalc(doc);
        if (doc->cur_entry != doc)
            res = TRUE;
    }
    else
    {
        while (EdFindPaired(doc, '}', '{', FALSE, abort_on_dbl_colon));
        if (doc->cur_entry != doc)
        {
            ch = EdCurU8(doc);
            if (abort_on_dbl_colon && ch == ':')
                res = TRUE;
            else
            {
                if (ch == '{')
                    res = TRUE;
            }
        }
    }
    if (unlock)
        DocUnlock(doc);

    return res;
}

public U0 EdSelFun(CDoc *doc, Bool abort_on_dbl_colon=FALSE)
{//Set DOCET_SEL on all entries in cur function.
    Bool         unlock = DocLock(doc);
    U8          *ptr;
    I64          ch, levels = 0, colons = 0;
    CDocEntry   *doc_e;

    EdSelAll(doc, FALSE);
    EdGoToFun(doc, FALSE, abort_on_dbl_colon);
    if (EdCurU8(doc) == '{')
        levels--;
    else if (abort_on_dbl_colon && EdCurU8(doc) == ':')
    {
        EdCursorRight(doc);
        if (EdCurU8(doc) == ':')
            EdCursorRight(doc);
    }
    doc_e = doc->cur_entry;
    while (doc_e != doc)
    {
        doc_e->type |= DOCET_SEL;
        if (doc_e->de_flags & DOCEF_TAG && doc_e->tag)
        {
            ptr = doc_e->tag;
            if (doc_e == doc->cur_entry)
                ptr += doc->cur_col;
            while (ch = *ptr++)
                if (abort_on_dbl_colon && ch == ':')
                {
                    if (++colons == 2)
                        goto sf_done;
                }
                else
                {
                    colons = 0;
                    if (ch == '{')
                        levels++;
                    else if (ch == '}' && !levels--)
                        goto sf_done;
                }
        }
        doc_e = doc_e->next;
    }
sf_done:
    DocRecalc(doc);
    if (unlock)
        DocUnlock(doc);
}

#define RSAC_REPLACE    0
#define RSAC_SKIP       1
#define RSAC_ALL        2

I64 PopUpReplaceSkipAllCancel(U8 *header=NULL, U8 *footer=NULL)
{
    I64   i;
    CDoc *doc = DocNew;

    if (header)
        DocPrint(doc, "%s", header);
    DocPrint(doc,
                "$CM+LX,1,4$$BT,\"REPLACE\",LE=RSAC_REPLACE$"
                "$CM+LX,17,0$$BT,\"SKIP\",LE=RSAC_SKIP$"
                "$CM+LX,1,3$$BT,\"ALL\",LE=RSAC_ALL$"
                "$CM+LX,17,0$$BT,\"CANCEL\",LE=DOCM_CANCEL$\n");
    if (footer)
        DocPrint(doc, "%s", footer);
    i = PopUpMenu(doc);
    DocDel(doc);

    return i;
}

I64 EdFindReplace(CDoc *doc)
{
    Bool         found, unlock;
    I64          cmd, i, j, plen, rlen, dlen, res = -1, sf_flags;
    U8          *src, *dst, *dst2;
    CDocEntry   *doc_ce, *doc_e, *doc_marker = NULL;

    if (doc->find_replace->prompt)
        cmd = RSAC_REPLACE;
    else
        cmd = RSAC_ALL;
    if (!doc->find_replace->prompt || DocForm(doc->find_replace))
    {
        res = 0;
        unlock = DocLock(doc);
        if (doc->find_replace->match_case || doc->find_replace->local_var)
            sf_flags = 0;
        else
            sf_flags = SFF_IGNORE_CASE;
        if (doc->find_replace->whole_labels || doc->find_replace->local_var)
            sf_flags |= SFG_WHOLE_LABELS;

        if (i = doc->find_replace->filter_lines)
        {
            doc_ce = doc->head.next;
            while (doc_ce != doc)
            {
                if (doc_ce->de_flags & DOCEF_TAG && doc_ce->tag && !(doc_ce->de_flags & DOCEF_FILTER_SKIP) &&
                    StrFind(doc->find_replace->find_text, doc_ce->tag, sf_flags))
                {
                    doc_ce->type |= DOCET_SEL;
                    res++;
                }
                else
                    doc_ce->type &= ~DOCET_SEL;
                doc_ce = doc_ce->next;
            }

            doc_ce = doc->head.next;
            while (doc_ce != doc)
            {
                if (!(doc_ce->de_flags & DOCEF_FILTER_SKIP))
                {
                    found = FALSE;

                    doc_e = doc_ce;
                    while (doc_e != doc && doc_e->y > doc_ce->y - i)
                    {
                        if (doc_e->type & DOCET_SEL)
                        {
                            found = TRUE;
                            break;
                        }
                        else
                            doc_e = doc_e->last;
                    }

                    if (!found)
                    {
                        doc_e = doc_ce;
                        while (doc_e != doc && doc_e->y < doc_ce->y + i)
                        {
                            if (doc_e->type & DOCET_SEL)
                            {
                                found = TRUE;
                                break;
                            }
                            else
                                doc_e = doc_e->next;
                        }
                    }

                    if (!found)
                        doc_ce->de_flags |= DOCEF_FILTER_SKIP;
                }

                doc_ce = doc_ce->next;
            }
            EdSelAll(doc, FALSE);
            goto fr_unlock_done;
        }

        if (doc->find_replace->local_var)
            EdSelFun(doc);

        if (!doc->find_replace->replace && !doc->find_replace->local_var)
        {
            EdFindNext(doc);
            goto fr_unlock_done;
        }
        plen = StrLen(doc->find_replace->find_text);
        if (!plen)
            goto fr_unlock_done;
        rlen = StrLen(doc->find_replace->replace_text);
        if (doc->head.next != doc)
        {
            doc_e = doc_marker = DocSplitTag(doc, doc->cur_entry, doc->cur_col,
                                    doc->cur_entry->x + doc->cur_col, doc->cur_entry->y, DOCT_MARKER);
            do
            {
                if (doc_e == doc)
                {
                    if (doc->find_replace->scan_fwd)
                        doc_e = doc_e->next;
                    else
                        doc_e = doc_e->last;
                    if (doc_e == doc_marker)
                        break;
                }
                if (doc_e->type_u8 == DOCT_TEXT &&
                    !(doc_e->de_flags & (DOCEG_DONT_EDIT | DOCEF_FILTER_SKIP)) &&
                    (doc_e->type & DOCET_SEL || !doc->find_replace->scan_sel_text && !doc->find_replace->local_var))
                {
                    src = doc_e->tag;
                    while (src)
                    {
                        src = StrFind(doc->find_replace->find_text, src, sf_flags);
                        if (src)
                        {
                            doc->cur_col    = src - doc_e->tag;
                            doc->cur_entry  = doc_e;
                            if (cmd != RSAC_ALL)
                                DocCenter(doc);
                            if (cmd != RSAC_ALL)
                            {
                                DocUnlock(doc);
                                cmd = PopUpReplaceSkipAllCancel("");
                                DocLock(doc);
                                if (cmd < 0)
                                    goto fr_unlock_done;
                            }
                            doc_e = doc->cur_entry;
                            src = doc->cur_col + doc_e->tag;
                            if (cmd == RSAC_REPLACE || cmd == RSAC_ALL)
                            {
                                dlen = StrLen(doc_e->tag);
                                doc_e->max_col = dlen + rlen - plen;
                                dst = MAlloc(doc_e->max_col + 1, doc->mem_task);
                                dst2 = dst;
                                j = src - doc_e->tag;
                                for (i = 0; i < j; i++)
                                    *dst++ = doc_e->tag[i];
                                for (i = 0; i < rlen; i++)
                                    *dst++ = doc->find_replace->replace_text[i];
                                src = dst;
                                for (i = j + plen; i <= dlen; i++)
                                    *dst++ = doc_e->tag[i];
                                Free(doc_e->tag);
                                doc_e->tag = dst2;
                                doc->cur_col    = src - doc_e->tag;
                                doc->cur_entry  = doc_e;
                                if (cmd != RSAC_ALL)
                                {
                                    DocRemSoftNewLines(doc, doc->cur_entry);
                                    DocRecalc(doc);
                                }
                                doc_e = doc->cur_entry;
                                src = doc->cur_col + doc_e->tag;
                                res++;
                            }
                            else
                                src++;
                        }
                    }
                }
                if (doc->find_replace->scan_fwd)
                    doc_e = doc_e->next;
                else
                    doc_e = doc_e->last;
            }
            while (doc_e != doc_marker);
        }
fr_unlock_done:
        if (doc_marker)
            DocEntryDel(doc, doc_marker);
        DocRemSoftNewLines(doc, NULL);
        DocRecalc(doc);
        DocCenter(doc);
        if (unlock)
            DocUnlock(doc);
    }

    return res;
}

public I64 EdReplace(CDoc *doc, U8 *find, U8 *replace, Bool sel=TRUE, Bool match_case=TRUE, Bool whole_labels=FALSE)
{//Find & replace using editor's cmd.
    CEdFindText old_find_replace;
    Bool        unlock;
    I64         i, res = -1;

    if (!doc)
        return -1;
    unlock = DocLock(doc);
    MemCopy(&old_find_replace, doc->find_replace, sizeof(CEdFindText));
    MemSet(doc->find_replace, 0, sizeof(CEdFindText));
    i = StrLen(find);
    if (i < sizeof(CEdFindText.find_text))
    {
        MemCopy(doc->find_replace->find_text, find, i + 1);
        i = StrLen(replace);
        if (i < sizeof(CEdFindText.replace_text))
        {
            MemCopy(doc->find_replace->replace_text, replace, i + 1);
            doc->find_replace->replace       = TRUE;
            doc->find_replace->scan_sel_text = sel;
            doc->find_replace->match_case    = match_case;
            doc->find_replace->whole_labels  = whole_labels;
            doc->find_replace->prompt        = FALSE;
            res = EdFindReplace(doc);
        }
    }
    MemCopy(doc->find_replace, &old_find_replace, sizeof(CEdFindText));
    if (unlock)
        DocUnlock(doc);

    return res;
}

class CEdLineGoTo
{
    I64 line format "$DA,A=\"Go to Line:%d\"$";
};

U0 EdGoToLine(CDoc *doc)
{//Prompt with form and go to line num.
    CEdLineGoTo gtl;

    gtl.line = 1;
    if (DocForm(&gtl))
        DocGoToLine(doc, gtl.line);
}