#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 (!StrCmp(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) {
    BEqu(&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 fwd,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 (fwd) {
          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 (fwd)
      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 fwd,Bool abort_on_dbl_colon)
{//Move cur_entry to start of cur fun and center.(Shoddy)
  Bool unlock=DocLock(doc),res=FALSE;
  I64 ch,levels,colons;
  if (fwd) {
    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 fun.
  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->pmt)
    cmd=RSAC_REPLACE;
  else
    cmd=RSAC_ALL;
  if (!doc->find_replace->pmt || 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);
  MemCpy(&old_find_replace,doc->find_replace,sizeof(CEdFindText));
  MemSet(doc->find_replace,0,sizeof(CEdFindText));
  i=StrLen(find);
  if (i<sizeof(CEdFindText.find_text)) {
    MemCpy(doc->find_replace->find_text,find,i+1);
    i=StrLen(replace);
    if (i<sizeof(CEdFindText.replace_text)) {
      MemCpy(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->pmt=FALSE;
      res=EdFindReplace(doc);
    }
  }
  MemCpy(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);
}