/* Screen editor:  buffer module
*                 C/80 version
*
* Source:  ed10.c
* Version: May 15, 1981.
*/

/* define globals */

#include ed1.h

/* globals used by this module -----

int bufcflag;           main buffer changed flag
char *bufp;             start of current line
char *bufpmax;          end of last line
char *buffer;           start of buffer
char *bufend;           last byte of buffer
int bufline;            current line number
int bufmaxln;           number of lines in buffer

----- */

/* This code is built around several invariant
* assumptions:
* First, the last line is always completely empty.
* When bufp points to the last line there is NO
* CR following it.
* Second, bufp points to the last line if and only if
* bufline==bufmaxln+1.
* Third, bufline is always greater than zero.
* Line zero exists only to make scanning for the
* start of line one easier.
*/


/* Clear the main buffer */

bufnew()
{
int k;
       /* do initial allocation of memory */
       if (buffer==0) {
               k=60;
               while ((buffer=alloc(k*1024))==-1)
                       k--;
               bufend=buffer+k*1024;
       }
       /* point past line 0 */
       bufp=bufpmax=buffer+1;
       /* at line one. no lines in buffer */
       bufline=1;
       bufmaxln=0;
       /* line zero is always a null line */
       buffer[0]=CR;
       /* indicate no need to save file yet */
       bufcflag=NO;
}

/* return current line number */

bufln()
{
       return(bufline);
}

/* return YES if the buffer (i.e., file) has been
* changed since the last time the file was saved.
*/

bufchng()
{
       return(bufcflag);
}

/* the file has been saved.  clear bufcflag. */

bufsaved()
{
       bufcflag=NO;
}

/* return number of bytes left in the buffer */

buffree()
{
       return(bufend-bufp);
}

/* Position buffer pointers to start of indicated line */

bufgo(line) int line;
{
       /* put request into range. prevent extension */
       line=min(bufmaxln+1,line);
       line=max(1,line);
       /* already at proper line? return. */
       if (line==bufline) {
               return(OK);
       }
       /* move through buffer one line at a time */
       while (line<bufline) {
               if (bufup()==ERR) {
                       return(ERR);
               }
       }
       while (line>bufline) {
               if (bufdn()==ERR) {
                       return(ERR);
               }
       }
       /* we have reached the line we wanted */
       return(OK);
}

/* move one line closer to front of buffer, i.e.,
* set buffer pointers to start of previous line.
*/

bufup()
{
char *oldbufp;
       oldbufp=bufp;
       /* can't move past line 1 */
       if (bufattop()) {
               return(OK);
       }
       /* move past CR of previous line */
       if (*--bufp!=CR) {
               syserr("bufup: missing CR");
               bufp=oldbufp;
               return(ERR);
       }
       /* move to start of previous line */
       while (*--bufp!=CR) {
               ;
       }
       bufp++;
       /* make sure we haven't gone too far */
       if (bufp<(buffer+1)) {
               syserr("bufup: bufp underflow");
               bufp=oldbufp;
               return(ERR);
       }
       /* success!  we ARE at previous line */
       bufline--;
       return(OK);
}

/* Move one line closer to end of buffer, i.e.,
* set buffer pointers to start of next line.
*/

bufdn()
{
char *oldbufp;
       oldbufp=bufp;
       /* do nothing silly if at end of buffer */
       if (bufatbot()) {
               return(OK);
       }
       /* scan past current line and CR */
       while (*bufp++!=CR) {
               ;
       }
       /* make sure we haven't gone too far */
       if (bufp>bufpmax) {
               syserr("bufdn: bufp overflow");
               bufp=oldbufp;
               return(ERR);
       }
       /* success! we are at next line */
       bufline++;
       return(OK);
}

/* Insert a line before the current line.
* p points to a line of length n to be inserted.
* Note: n does not include trailing CR.
*/

bufins(p,n) char *p; int n;
{
int k;
       /* make room in the buffer for the line */
       if (bufext(n+1)==ERR) {
               return(ERR);
       }
       /* put the line and CR into the buffer */
       k=0;
       while (k<n) {
               *(bufp+k)= *(p+k);
               k++;
       }
       *(bufp+k)=CR;
       /* increase number of lines in buffer */
       bufmaxln++;
       /* special case: inserting a null line at
        * end of file is not a significant change.
        */
       if ((n==0)ff(bufnrbot())) {
               ;
       }
       else {
               bufcflag=YES;
       }
       return(OK);
}

/* delete the current line */

bufdel()
{
       return(bufdeln(1));
}

/* delete n lines, starting with the current line */

bufdeln(n) int n;
{
int oldline;
int k;
char *oldbufp;
       /* remember current buffer parameters */
       oldline=bufline;
       oldbufp=bufp;
       /* scan for first line after deleted lines */
       k=0;
       while ((n--)>0) {
               if (bufatbot()) {
                       break;
               }
               if (bufdn()==ERR) {
                       bufline=oldline;
                       oldbufp=bufp;
                       return(ERR);
               }
               k++;
       }
       /* compress buffer.  update pointers */
       bufmovup(bufp,bufpmax-1,bufp-oldbufp);
       bufpmax=bufpmax-(bufp-oldbufp);
       bufp=oldbufp;
       bufline=oldline;
       bufmaxln=bufmaxln-k;
       bufcflag=YES;
       return(OK);
}

/* replace current line with the line that
* p points to.  The new line is of length n.
*/

bufrepl(p,n) char *p; int n;
{
int oldlen, k;
char *nextp;
       /* do not replace null line.  just insert */
       if (bufatbot()) {
               return(bufins(p,n));
       }
       /* point nextp at start of next line */
       if (bufdn()==ERR) {
               return(ERR);
       }
       nextp=bufp;
       if (bufup()==ERR) {
               return(ERR);
       }
       /* allow for CR at end */
       n=n+1;
       /* see how to move buffer below us;
        * up, down, or not at all.
        */
       oldlen=nextp-bufp;
       if (oldlen<n) {
               /* move buffer down */
               if (bufext(n-oldlen)==ERR) {
                       return(ERR);
               }
               bufpmax=bufpmax+n-oldlen;
       }
       else if (oldlen>n) {
               /* move buffer up */
               bufmovup(nextp,bufpmax-1,oldlen-n);
               bufpmax=bufpmax-(oldlen-n);
       }
       /* put new line in the hole we just made */
       k=0;
       while (k<(n-1)) {
               bufp[k]=p[k];
               k++;
       }
       bufp[k]=CR;
       bufcflag=YES;
       return(OK);
}

/* copy current line into buffer that p points to.
* the maximum size of that buffer is n.
* return k=length of line in the main buffer.
* if k>n then truncate n-k characters and only
* return n characters in the caller's buffer.
*/

bufgetln(p,n) char *p; int n;
{
int k;
       /* last line is always null */
       if (bufatbot()) {
               return(0);
       }
       /* copy line as long as it not too long */
       k=0;
       while (k<n) {
               if (*(bufp+k)==CR) {
                       return(k);
               }
               *(p+k)= *(bufp+k);
               k++;
       }
       /* count length but move no more chars */
       while (*(bufp+k)!=CR) {
               k++;
       }
       return(k);
}

/* move buffer down (towards HIGH addresses) */

bufmovdn(from,to,length) char *from, *to; int length;
{
       /* this code needs to be very fast.
        * use an assembly language routine.
        */

       sysmovdn(to-from+1,from+length,from);
}

/* the call to sysmovdn() is equivalent to the following code:

int k;
       k=to-from+1;
       while ((k--)>0) {
               *(to+length)= *to;
               to--;
       }

*/

/* move buffer up (towards LOW addresses) */

bufmovup(from,to,length) char *from, *to; int length;
{
       /* this code must be very fast.
        * use an assembly language routine.
        */

       sysmovup(to-from+1,from-length,from);
}

/* the call to sysmovup() is equivalent to the following code:

int k;
       k=to-from+1;
       while ((k--)>0) {
               *(from-length)= *from;
               from++;
       }

*/

/* return true if at bottom of buffer.
* NOTE 1: the last line of the buffer is always null.
* NOTE 2: the last line number is always bufmaxln+1.
*/

bufatbot()
{
       return(bufline>bufmaxln);
}

/* return true if at bottom or at the last
* real line before the bottom.
*/

bufnrbot()
{
       return(bufline>=bufmaxln);
}

/* return true if at top of buffer */

bufattop()
{
       return(bufline==1);
}

/* put nlines lines from buffer starting with
* line topline at position topy of the screen.
*/

bufout(topline,topy,nlines) int topline, topy, nlines;
{
int l,p;
       /* remember buffer's state */
       l=bufline;
       p=bufp;
       /* write out one line at a time */
       while ((nlines--)>0) {
               outxy(0,topy++);
               bufoutln(topline++);
       }
       /* restore buffer's state */
       bufline=l;
       bufp=p;
}

/* print line of main buffer on screen */

bufoutln(line) int line;
{
       /* error message does NOT go on prompt line */
       if (bufgo(line)==ERR) {
               fmtsout("disk error: line deleted",0);
               outdeol();
               return;
       }
       /* blank out lines below last line of buffer */
       if (bufatbot()) {
               outdeol();
       }
       /* write one formatted line out */
       else {
               fmtsout(bufp,0);
               outdeol();
       }
}

/* simple memory version of bufext.
* create a hole in buffer at current line.
* length is the size of the hole.
*/

bufext(length) int length;
{
       /* make sure there is room for more */
       if ((bufpmax+length)>=bufend) {
               error("main buffer is full");
               return(ERR);
       }
       /* move lines below current line down */
       bufmovdn(bufp,bufpmax-1,length);
       bufpmax=bufpmax+length;
       return(OK);
}