#help_index "DolDoc/File"

public U0 DocLoad(CDoc *doc, U8 *src2, I64 size)
{//Fetch doc from raw mem buf.
        I64              i;
        U8              *src;
        Bool     unlock = DocLock(doc);
        CDocBin *tmpb;

        doc->find_replace->filter_lines = 0;
        if (src2)
        {
                DocPutS(doc, src2); //Too big DocPrint() is wasteful.
                src = src2 + StrLen(src2) + 1;
                i = size - (offset(CDocBin.end) - offset(CDocBin.start));
                while (src <= src2 + i)
                {
                        tmpb = CAlloc(sizeof(CDocBin), doc->mem_task);
                        MemCopy(&tmpb->start, src, offset(CDocBin.end) - offset(CDocBin.start));
                        src+=offset(CDocBin.end)-offset(CDocBin.start);
                        tmpb->data = MAlloc(tmpb->size, doc->mem_task);
                        if (tmpb->size)
                        {
                                MemCopy(tmpb->data, src, tmpb->size);
                                src += tmpb->size;
                        }
                        QueueInsert(tmpb, doc->bin_head.last);
                        if (tmpb->num >= doc->cur_bin_num)
                                doc->cur_bin_num = tmpb->num + 1;
                }
        }
        if (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)))
                DocBinsValidate(doc);
        DocTop(doc); //Calls DocRecalc().  DOCT_CURSOR will be set.
        if (unlock)
                DocUnlock(doc);
}

public CDoc *DocRead(U8 *name=NULL, I64 flags=0)
{//Fetch doc from disk. See flags.
        CDoc            *doc = DocNew;
        U8                      *src, *name2;
        I64                      size = 0;
        CDirContext     *dirc;

        if (!name)
                name = blkdev.tmp_filename;
        doc->flags |= flags;
        name2 = FileNameAbs(name);
        StrCopy(doc->filename.name, name2);
        if (src = FileRead(name2, &size, &doc->file_attr))
        {
                if (dirc = DirContextNew(name2))
                {
                        DocLoad(doc, src, size);
                        DirContextDel(dirc);
                }
                Free(src);
        }
        Free(name2);

        return doc;
}

public U8 *DocSave(CDoc *doc, I64 *_size=NULL)
{//Store doc to raw mem buf.
        CDocEntry       *doc_e, *doc_e1;
        CDocBin         *b;
        Bool             unlock = DocLock(doc);
        I64                      ch, count = 1;//terminator
        U8                      *st, *res, *dst, *src;

        if (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)))
                DocBinsValidate(doc);
        if (doc->flags & DOCF_NO_CURSOR)
                DocRecalc(doc);
        else
        {
                DocRecalc(doc, RECALCF_ADD_CURSOR);
                if (doc->head.next->type_u8 == DOCT_CURSOR)
                        DocEntryDel(doc, doc->head.next); //If no cursor, DocLoad() puts at top.
        }
        for (doc_e = doc->head.next; doc_e != doc; doc_e = doc_e->next)
        {
                if (!Bt(doldoc.type_flags_data, doc_e->type_u8))
                {
                        switch (doc_e->type_u8)
                        {
                                case DOCT_TAB:
                                case DOCT_PAGE_BREAK:
                                case DOCT_CURSOR:
                                        count++;
                                        break;

                                case DOCT_NEW_LINE:
                                        if (doc->flags & DOCF_CARRIAGE_RETURN)
                                                count += 2;
                                        else
                                                count++;
                                        break;

                                case DOCT_SOFT_NEW_LINE:
                                        break;

                                case DOCT_TEXT:
                                        if (!(doc_e->de_flags &
                                                ~(DOCEF_TAG | DOCG_BL_IV_UL | DOCEF_WORD_WRAP | DOCEF_HIGHLIGHT|DOCEF_SKIP | DOCEF_FILTER_SKIP)) &&
                                                !(doc_e->type & DOCG_BL_IV_UL))
                                        {
                                                count += StrLen(doc_e->tag);
                                                if (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)) || doc->flags & DOCF_DBL_DOLLARS)
                                                        count += StrOcc(doc_e->tag, '$');
                                                break;
                                        }

                                default:
                                        st = Doc2PlainText(doc, doc_e);
                                        count += StrLen(st) + 2;
                                        Free(st);
                        }
                }
        }
        for (b = doc->bin_head.next; b != &doc->bin_head; b = b->next)
                if (b->use_count > b->tmp_use_count)
                        count += offset(CDocBin.end) - offset(CDocBin.start) + b->size;
        res = MAlloc(count);
        dst = res;
        doc_e = doc->head.next;
        while (doc_e != doc)
        {
                doc_e1 = doc_e->next;
                if (!Bt(doldoc.type_flags_data, doc_e->type_u8))
                        switch (doc_e->type_u8)
                        {
                                case DOCT_CURSOR:
                                        DocEntryDel(doc, doc_e);
                                        *dst++ = CH_CURSOR;
                                        break;

                                case DOCT_TAB:
                                        *dst++ = '\t';
                                        break;

                                case DOCT_NEW_LINE:
                                        if (doc->flags & DOCF_CARRIAGE_RETURN)
                                                *dst++ = '\r';
                                        *dst++ = '\n';
                                        break;

                                case DOCT_SOFT_NEW_LINE:
                                        break;

                                case DOCT_TEXT:
                                        if (!(doc_e->de_flags &
                                                ~(DOCEF_TAG | DOCG_BL_IV_UL | DOCEF_WORD_WRAP | DOCEF_HIGHLIGHT | DOCEF_SKIP | DOCEF_FILTER_SKIP)) &&
                                                !(doc_e->type & DOCG_BL_IV_UL))
                                        {
                                                src = doc_e->tag;
                                                while (ch = *src++)
                                                {
                                                        *dst++ = ch;
                                                        if (ch == '$' && (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)) ||
                                                                                                doc->flags & DOCF_DBL_DOLLARS))
                                                                *dst++ = ch;
                                                }
                                                break;
                                        }

                                default:
                                        *dst++ = '$';
                                        st = Doc2PlainText(doc, doc_e);
                                        StrCopy(dst, st);
                                        dst += StrLen(st);
                                        *dst++ = '$';
                                        Free(st);
                        }
                doc_e = doc_e1;
        }
        *dst++ = 0;
        b = doc->bin_head.next;
        if (b != &doc->bin_head)
        {
                do
                {
                        if (b->use_count > b->tmp_use_count)
                        {
                                MemCopy(dst, &b->start, offset(CDocBin.end) - offset(CDocBin.start));
                                dst += offset(CDocBin.end) - offset(CDocBin.start);
                                MemCopy(dst, b->data, b->size);
                                dst += b->size;
                        }
                        b = b->next;
                }
                while (b != &doc->bin_head);

        }
        else
                count--; //No terminator
        if (_size)
                *_size = count;
        if (unlock)
                DocUnlock(doc);

        return res;
}

public Bool DocWrite(CDoc *doc, Bool prompt=FALSE)
{//Store doc to disk.
        I64 size;
        U8 *buf;

        if (prompt && !DocForm(&doc->filename) || doc->filename.name[0] == 'A' && doc->filename.name[2] == ':')
                return FALSE;  //CANCEL || LK_DOC,LK_DOC_ANCHOR,LK_DOC_FIND,LK_DOC_LINE?
        buf = DocSave(doc, &size);
        FileWrite(doc->filename.name, buf, size, 0, doc->file_attr);
        Free(buf);

        return TRUE;
}

#help_index "DolDoc"
public U0 DocInsDoc(CDoc *doc=NULL, CDoc *doc2)
{//Insert copy of doc2 into doc at insert pt, cur_entry.
//TODO: DocReset
        U8                      *dst;
        Bool             unlock_doc, unlock_doc2 = DocLock(doc2);
        CDocEntry       *doc_ne, *doc_e = doc2->head.next, *doc_ce;

        if (!doc)
                doc = DocPut;
        unlock_doc = DocLock(doc), DocRemSoftNewLines(doc, NULL);
        doc_ce = doc->cur_entry;
        if (doc_ce->type_u8 == DOCT_TEXT && doc->cur_col > doc_ce->min_col)
        {
                if (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_entry = doc_ne;
                        doc->cur_col = doc_ne->min_col;
                }
                else
                        if (doc_ce != doc)
                                doc->cur_entry = doc_ce->next;
        }
        while (doc_e != doc2)
        {
                if (doc_e->type_u8 != DOCT_SOFT_NEW_LINE)
                {
                        doc_ne = DocEntryCopy(doc, doc_e);
                        QueueInsert(doc_ne, doc->cur_entry->last);
                }
                doc_e = doc_e->next;
        }
        DocRecalc(doc);
        if (unlock_doc2)
                DocUnlock(doc2);
        if (unlock_doc)
                DocUnlock(doc);
}

#help_index "DolDoc/Compiler;Compiler/Directive"
public U0 StreamDoc(CDoc *doc)
{//Inject doc into compile stream. Use inside #exe{}.
//TODO: DocReset
        Bool             unlock_doc = DocLock(doc);
        CDocEntry       *doc_e = doc->head.next;

        while (doc_e != doc)
        {
                if (doc_e->type_u8 == DOCT_TEXT)
                        StreamPrint("%s", doc_e->tag);
                else if (doc_e->type_u8 == DOCT_NEW_LINE)
                        StreamPrint("\n");
                else if (doc_e->type_u8 == DOCT_TAB)
                        StreamPrint("\t");
                doc_e = doc_e->next;
        }
        if (unlock_doc)
                DocUnlock(doc);
}

#help_index "DolDoc"
Bool DocCaptureUndo(CDoc *doc, Bool force=FALSE)
{
        Bool             res = FALSE, unlock;
        I64                      time_stamp, flags;
        CDocUndo        *u;

        if (doc->flags & DOCF_ALLOW_UNDO)
        {
                unlock = DocLock(doc);
                time_stamp = TSCGet;
                if (doc->flags & DOCF_UNDO_DIRTY &&
                        time_stamp > doc->undo_head.last->time_stamp + counts.time_stamp_freq << 4 ||
                        force)
                {
                        u = CAlloc(sizeof(CDocUndo), doc->mem_task);
                        u->time_stamp = time_stamp;
                        flags = doc->flags;
                        doc->flags &= ~DOCF_NO_CURSOR;
                        u->body = DocSave(doc, &u->size);
                        doc->flags = flags;
                        QueueInsert(u, doc->undo_head.last);
                        doc->flags &= ~DOCF_UNDO_DIRTY;
                        doc->undo_count++;
                        u->doc_flags = doc->flags;
                        res = TRUE;
                        if (doc->flags & DOCF_AUTO_SAVE)
                                DocWrite(doc);
                }
                if (unlock)
                        DocUnlock(doc);
        }
        return res;
}
 
U0 DocUndoRestore(CDoc *doc)
{
        Bool             unlock = DocLock(doc);
        CDocUndo        *u = doc->undo_head.last, *u_next, *u_last;

        if (u != &doc->undo_head)
        {
                QueueRemove(u);
                u_next = doc->undo_head.next;
                u_last = doc->undo_head.last;
                QueueInit(&doc->undo_head);
                DocReset(doc, TRUE);
                doc->flags = u->doc_flags & ~DOCF_NO_CURSOR;
                DocLoad(doc, u->body, u->size);
                doc->flags = u->doc_flags;
                DocUndoDel(doc, u);
                doc->undo_head.next = u_next;
                doc->undo_head.last = u_last;
        }
        DocUndoCountSet(doc);
        doc->flags &= ~DOCF_UNDO_DIRTY;
        if (unlock)
                DocUnlock(doc);
}

#help_index "Graphics/GR Files;DolDoc/Output;StdOut/DolDoc"
public Bool DocType(CDoc *doc=NULL, U8 *filename, I64 trailing_new_lines=1)
{//Output txt or graphic file to document.
        Bool  res = FALSE;
        CDoc *doc2;

        if (!doc && !(doc = DocPut) || doc->doc_signature != DOC_SIGNATURE_VAL)
                return FALSE;
        if (FilesFindMatch(filename, FILEMASK_TXT))
        {
                doc2 = DocRead(filename);
                DocInsDoc(doc, doc2);
                if (IsRaw)
                        DocDump(doc2, 100000);
                DocDel(doc2);
                res = TRUE;
        }
        else if (FilesFindMatch(filename, "*.GR*"))
        {
                DocGR(doc, filename);
                res = TRUE;
        }
        if (res)
                DocPrint(doc, "%h*c", trailing_new_lines, '\n');

        return res;
}

#help_index "Graphics/GR Files;"\
                        "File/Cmd Line (Typically);DolDoc/Cmd Line (Typically);"\
                        "StdOut;Cmd Line (Typically)"
public Bool Type(U8 *filename, I64 trailing_new_lines=1)
{//Output txt or graphic file to command line.
        return DocType(, filename, trailing_new_lines);
}

#help_index "DolDoc/File"

public U8 *DocLineRead(U8 *filename, I64 line, CTask *mem_task=NULL)
{//Extract line from stored doc file. (Slow.)
        U8       *res = NULL;
        CDoc *doc = DocRead(filename, DOCF_PLAIN_TEXT_TABS | DOCF_NO_CURSOR);

        if (DocGoToLine(doc, line) && doc->cur_entry->type_u8 == DOCT_TEXT)
                res = StrNew(doc->cur_entry->tag, mem_task);
        DocDel(doc);

        return res;
}

public U8 *DocLineWrite(U8 *filename, I64 line, U8 *st)
{//Write line to stored doc file. (Slow.)
        U8       *res = NULL;
        CDoc *doc = DocRead(filename, DOCF_PLAIN_TEXT_TABS | DOCF_NO_CURSOR);

        if (DocGoToLine(doc, line))
        {
                if (doc->cur_entry->type_u8 == DOCT_TEXT)
                {
                        Free(doc->cur_entry->tag);
                        doc->cur_entry->tag = StrNew(st);
                }
                else
                        DocPrint(doc, "%s", st);
                DocTop(doc);
                DocWrite(doc);
        }
        DocDel(doc);

        return res;
}