#help_index "DolDoc"

public Bool DocLock(CDoc *doc)
{//Make this task have exclusive access to this doc.
    if (!Bt(&doc->locked_flags, DOClf_LOCKED) || doc->owning_task != Fs)
    {
        while (LBts(&doc->locked_flags, DOClf_LOCKED))
            Yield;
        if (doc->owning_task != Fs)
            LBEqual(&doc->flags, DOCf_BREAK_UNLOCKED, BreakLock(Fs));
        doc->owning_task = Fs;
        return TRUE;
    }
    else
        return FALSE;
}

public Bool DocUnlock(CDoc *doc)
{//Release exclusive lock on access to doc.
    Bool unlock_break;

    if (Bt(&doc->locked_flags, DOClf_LOCKED) && doc->owning_task == Fs)
    {
        doc->owning_task = 0;
        unlock_break = Bt(&doc->flags, DOCf_BREAK_UNLOCKED);
        LBtr(&doc->locked_flags, DOClf_LOCKED);
        if (unlock_break)
            BreakUnlock(Fs);
        return TRUE;
    }
    else
        return FALSE;
}

Bool IsEditableText(CDocEntry *doc_e)
{
    if (doc_e->type_u8 == DOCT_TEXT && !(doc_e->de_flags & DOCEG_DONT_EDIT))
        return TRUE;
    else
        return FALSE;
}

CDocEntry *DocEntryNewBase(CDoc *doc, I64 type, I64 de_flags=0, I64 x=0, I64 y=0, I64 page_line_num=0)
{//See also MAllocIdent and CDocEntry.
    CDocEntry *res = CAlloc(sizeof(CDocEntryBase), doc->mem_task);

    res->type           = type;
    res->de_flags       = de_flags | doldoc.default_de_flags[type.u8[0]];
    res->x              = x;
    res->y              = y;
    res->page_line_num  = page_line_num;

    return res;
}

CDocEntry *DocEntryNewTag(CDoc *doc, CDocEntry *doc_ce, U8 *tag)
{
    I64          l = StrLen(tag);
    CDocEntry   *res = DocEntryNewBase(doc, doc_ce->type, doc_ce->de_flags, doc_ce->x, doc_ce->y, doc_ce->page_line_num);

    res->de_flags   = doc_ce->de_flags; //Override
    res->max_col    = l;
    res->tag        = MAlloc(l + 1, doc->mem_task);
    MemCopy(res->tag, tag, l + 1);
    MemCopy(&res->settings, &doc_ce->settings, sizeof(CDocSettings));

    return res;
}

public U0 DocEntryDel(CDoc *doc, CDocEntry *doc_e)
{//Free entry and all parts of entry.
    if (!doc || doc == doc_e)
        RawPrint(3000, "DocEntryDel");
    else
    {
        if (doc->cur_entry == doc_e)
            doc->cur_entry = doc_e->next;
        QueueRemove(doc_e);
        if (doc_e->de_flags & DOCEF_TAG)
            Free(doc_e->tag);
        if (doc_e->de_flags & DOCEF_AUX_STR)
            Free(doc_e->aux_str);
        if (doc_e->de_flags & DOCEF_DEFINE)
            Free(doc_e->define_str);
        if (doc_e->de_flags & DOCEF_HTML_LINK)
            Free(doc_e->html_link);
        if (doc_e->de_flags & DOCEF_LEFT_MACRO)
            Free(doc_e->left_macro);
        if (doc_e->de_flags & DOCEF_RIGHT_MACRO)
            Free(doc_e->right_macro);
        if (doc_e->de_flags & DOCEF_BIN_PTR_LINK)
            Free(doc_e->bin_ptr_link);
        if (doc_e->de_flags & DOCEF_HAS_BIN)
            DocBinDel(doc, doc_e->bin_data);
        if (doc_e->de_flags & DOCEF_REMALLOC_DATA)
            Free(doc_e->data);
        Free(doc_e);
    }
}

public I64 DocEntrySize(CDoc *, CDocEntry *doc_e)
{//Mem size of entry and all parts.
    I64 res;

    if (!doc_e)
        return 0;
    res = MSize2(doc_e);
    if (doc_e->de_flags & DOCEF_TAG)
        res += MSize2(doc_e->tag);
    if (doc_e->de_flags & DOCEF_AUX_STR)
        res += MSize2(doc_e->aux_str);
    if (doc_e->de_flags & DOCEF_DEFINE)
        res += MSize2(doc_e->define_str);
    if (doc_e->de_flags & DOCEF_HTML_LINK)
        res += MSize2(doc_e->html_link);
    if (doc_e->de_flags & DOCEF_LEFT_MACRO)
        res += MSize2(doc_e->left_macro);
    if (doc_e->de_flags & DOCEF_RIGHT_MACRO)
        res += MSize2(doc_e->right_macro);
    if (doc_e->de_flags & DOCEF_BIN_PTR_LINK)
        res += MSize2(doc_e->bin_ptr_link);
    if (doc_e->de_flags & DOCEF_REMALLOC_DATA)
        res += MSize2(doc_e->data);

    return res;
}

U0 DocUndoDel(CDoc *, CDocUndo *u)
{
    Free(u->body);
    Free(u);
}

U0 DocUndoCountSet(CDoc *doc)
{
    Bool         unlock = DocLock(doc);
    CDocUndo    *u = doc->undo_head.next;

    doc->undo_count = 0;
    while (u != &doc->undo_head)
    {
        doc->undo_count++;
        u = u->next;
    }
    if (unlock)
        DocUnlock(doc);
}

public CDocEntry *DocEntryCopy(CDoc *doc, CDocEntry *doc_e)
{//Make copy of entry and all parts of entry.
    CDocEntry   *doc_ne;
    CDocBin     *tmpb;
    CTask       *task = doc->mem_task;

    doc_ne = MAllocIdent(doc_e, task);
    doc_ne->next = doc_ne;
    doc_ne->last = doc_ne;
    if (doc_e->de_flags & DOCEF_TAG)
        doc_ne->tag = MAllocIdent(doc_e->tag, task);
    if (doc_e->de_flags & DOCEF_AUX_STR)
        doc_ne->aux_str = MAllocIdent(doc_e->aux_str, task);
    if (doc_e->de_flags & DOCEF_DEFINE)
        doc_ne->define_str = MAllocIdent(doc_e->define_str, task);
    if (doc_e->de_flags & DOCEF_HTML_LINK)
        doc_ne->html_link = MAllocIdent(doc_e->html_link, task);
    if (doc_e->de_flags & DOCEF_LEFT_MACRO)
        doc_ne->left_macro = MAllocIdent(doc_e->left_macro, task);
    if (doc_e->de_flags & DOCEF_RIGHT_MACRO)
        doc_ne->right_macro = MAllocIdent(doc_e->right_macro, task);
    if (doc_e->de_flags & DOCEF_BIN_PTR_LINK)
        doc_ne->bin_ptr_link = MAllocIdent(doc_e->bin_ptr_link, task);
    if (doc_e->de_flags & DOCEF_HAS_BIN)
    {
        tmpb = MAllocIdent(doc_e->bin_data, task);
        tmpb->data = MAllocIdent(doc_e->bin_data->data, task);
        doc_ne->bin_num = doc->cur_bin_num;
        tmpb->num = doc->cur_bin_num++;
        doc_ne->bin_data = tmpb;
        if (doc_e->de_flags & DOCEF_TAG && doc_e->tag && *doc_e->tag)
            tmpb->tag = StrNew(doc_e->tag, task);
        else
            tmpb->tag = NULL;
        QueueInsert(tmpb, doc->bin_head.last);
    }
    if (doc_e->de_flags & DOCEF_REMALLOC_DATA)
        doc_ne->data = MAllocIdent(doc_e->data, task);

    return doc_ne;
}

U0 DocRemSoftNewLines(CDoc *doc=NULL, CDocEntry *doc_e=NULL)
{
    CDocEntry   *doc_e2, *saved_ll = doc_e;
    Bool         unlock;

    if (!doc && !(doc = DocPut))
        return;
    unlock = DocLock(doc);
    if (!doc_e)
        doc_e = doc->head.next;
    while (doc_e != doc)
    {
        doc_e2 = doc_e->next;
        if (doc_e->type_u8 == DOCT_SOFT_NEW_LINE)
        {
            if (doc->cur_entry == doc_e)
            {
                doc->cur_entry  = doc_e2;
                doc->cur_col    = doc->cur_entry->min_col;
            }
            DocEntryDel(doc, doc_e);
        }
        else if (saved_ll && doc_e->type_u8 == DOCT_NEW_LINE)
            break;
        doc_e = doc_e2;
    }
    if (unlock)
        DocUnlock(doc);
}

public U0 DocInsEntry(CDoc *doc, CDocEntry *doc_e)
{//Insert entry into doc, updating its values.
    U8          *dst;
    Bool         unlock = DocLock(doc);
    CDocEntry   *doc_ce = doc->cur_entry, *doc_ne;

    doc_e->x             = doc_ce->x;
    doc_e->y             = doc_ce->y;
    doc_e->page_line_num = doc_ce->page_line_num;
    MemCopy(&doc_e->settings, &doc_ce->settings, sizeof(CDocSettings));
    if (doc->cur_col > 0 &&
        doc_ce->type_u8 == DOCT_TEXT &&
        !(doc_ce->de_flags & (DOCEF_TAG_CB | DOCEF_DEFINE | DOCEF_AUX_STR | DOCEF_HTML_LINK | DOCEF_BIN_PTR_LINK)) &&
        doc->cur_col < doc_ce->max_col)
    {
        dst = doc_ce->tag + doc->cur_col;
        doc_ne = DocEntryNewTag(doc, doc_ce, dst);
        *dst = 0;
        doc_ne->type    = DOCT_TEXT | doc_ce->type & 0xFFFFFF00;
        doc_ce->max_col = doc->cur_col;
        QueueInsert(doc_ne, doc_ce);
        doc->cur_col = 0;
        doc_ce = doc_ne;
    }
    if (doc_ce->type_u8 == DOCT_TEXT && doc->cur_col >= doc_ce->max_col)
    {
        QueueInsert(doc_e, doc_ce);
        doc->cur_entry = doc_e->next;
    }
    else
    {
        QueueInsert(doc_e, doc_ce->last);
        doc->cur_entry = doc_ce;
    }
    doc->cur_col = doc->cur_entry->min_col;
    DocRemSoftNewLines(doc, doc->cur_entry);
    if (unlock)
        DocUnlock(doc);
}

public U0 DocReset(CDoc *doc, Bool is_old)
{//Del all entries and set doc to defaults.
    Bool             unlock;
    CDocEntry       *doc_e, *doc_e2;
    CDocUndo        *u, *u8;
    CDocSettings    *s;
    CDocBin         *b, *b1;

    if (!doc && !(doc = DocPut))
        return;
    unlock = DocLock(doc);
    if (is_old)
    {
        doc_e = doc->head.next;
        while (doc_e != doc)
        {
            doc_e2 = doc_e->next;
            DocEntryDel(doc, doc_e);
            doc_e = doc_e2;
        }
        u = doc->undo_head.next;
        while (u != &doc->undo_head)
        {
            u8 = u->next;
            DocUndoDel(doc, u);
            u = u8;
        }
        b = doc->bin_head.next;
        while (b != &doc->bin_head)
        {
            b1 = b->next;
            QueueRemove(b);
            Free(b->data);
            Free(b);
            b = b1;
        }
    }
//Check DocInsDoc
    doc->flags &= DOCF_BREAK_UNLOCKED;
    doc->head.next = doc->head.last = doc;
    QueueInit(&doc->bin_head);
    QueueInit(&doc->undo_head);
    doc->undo_head.time_stamp   = 0;
    doc->undo_count             = 0;
    doc->cur_bin_num            = 1;
    doc->dollar_buf_ptr         = 0;
    doc->cmd_U8                 = CH_SPACE;
    doc->page_line_num          = 0;
    doc->best_d                 = I64_MAX;

    s = &doc->settings_head;
    s->left_margin      = DOC_DEFAULT;
    s->right_margin     = DOC_DEFAULT;
    s->indent           = 0;
    s->page_len         = 66;
    s->header           = DOC_DEFAULT;
    s->footer           = DOC_DEFAULT;
    s->state            = DOCSS_NORMAL;
    s->comment_depth    = 0;
    s->paren_depth      = 0;
    s->brace_depth      = 0;
    s->shifted_x        = 0;
    s->shifted_y        = 0;
    s->cur_text_attr    = s->default_text_attr = DOC_ATTR_DEFAULT_TEXT;

    doc_e = &doc->head;
    doc_e->type             = DOCT_ERROR;
    doc_e->de_flags         = 0;
    doc_e->x                = 0;
    doc_e->y                = 0;
    doc_e->min_col          = 0;
    doc_e->max_col          = 0;
    doc_e->page_line_num    = doc->page_line_num;
    MemCopy(&doc_e->settings, s, sizeof(CDocSettings));

    DocTop(doc);
    if (unlock)
        DocUnlock(doc);
}

public U0 DocDel(CDoc *doc)
{//Free entire doc and entries.
    if (!doc || doc->doc_signature != DOC_SIGNATURE_VAL)
        return;
    DocLock(doc);
    doc->doc_signature = 0;
    DocReset(doc, TRUE);
    Free(doc->find_replace);
    Free(doc->dollar_buf);
    DocUnlock(doc);
    Free(doc);
}

public I64 DocSize(CDoc *doc)
{//Mem size of doc and all its entries.
    Bool         unlock;
    CDocEntry   *doc_e;
    CDocUndo    *u;
    CDocBin     *b;
    I64          res = 0;

    if (!doc || doc->doc_signature != DOC_SIGNATURE_VAL)
        return 0;
    unlock = DocLock(doc);

    doc_e = doc->head.next;
    while (doc_e != doc)
    {
        res += DocEntrySize(doc, doc_e);
        doc_e = doc_e->next;
    }

    u = doc->undo_head.next;
    while (u != &doc->undo_head)
    {
        res += MSize2(u->body);
        res += MSize2(u);
        u = u->next;
    }

    b = doc->bin_head.next;
    while (b != &doc->bin_head)
    {
        res += MSize2(b->data);
        res += MSize2(b);
        b = b->next;
    }

    res += MSize2(doc->find_replace);
    res += MSize2(doc->dollar_buf);
    res += MSize2(doc);
    if (unlock)
        DocUnlock(doc);

    return res;
}

#help_index "DolDoc"
public CDoc *DocNew(U8 *filename=NULL, CTask *task=NULL)
{//MAlloc new DolDoc. (Begin a new doc.)
    CDoc *doc;

    if (!task)
        task = Fs;
    doc = CAlloc(sizeof(CDoc), task);
    if (filename)
        StrCopy(doc->filename.name, filename);
    else
        StrCopy(doc->filename.name, blkdev.tmp_filename);
    doc->find_replace       = CAlloc(sizeof(CEdFindText), task);
    doc->find_replace->scan_fwd     = TRUE;
    doc->find_replace->match_case   = TRUE;
    doc->find_replace->prompt       = TRUE;
    doc->left_click_link    = &EdLeftClickLink;
    doc->dollar_buf_size    = 84;
    doc->dollar_buf         = MAlloc(doc->dollar_buf_size, task);
    doc->max_entries        = I64_MAX;
    doc->win_task           = task;
    doc->mem_task           = task;
    DocReset(doc, FALSE);
    doc->doc_signature      = DOC_SIGNATURE_VAL;

    return doc;
}