/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is	*
 * provided to you without charge, and with no warranty.  You may give	*
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.						*
 ************************************************************************/

/* Contains commands that deal with creating, selecting, killing and
   listing buffers, and buffer modes, and find-file, etc. */

#include "jove.h"

RCS("$Id: buf.c,v 14.31.0.12 1994/06/23 02:54:50 tom Exp tom $")

#include "ctype.h"
#include "io.h"
#include "process.h"	/* for ErrFree and IPROCS stuff */

const char	Mainbuf[] = "Main";
const char	NoName[] = "Sans un nom!";

Buffer	*world ZERO,		/* First in the list */
	*curbuf ZERO,
	*lastbuf ZERO;	/* Last buffer we were in so we have a default
			   buffer during a select buffer. */
#ifdef MENUS
int	Bufchange ZERO,
	Modechange ZERO;
#endif

/* These arrays are indexed by Major/Minor modes so order is important!
   (the trailing space in `Desc' field is for the convenience of ModeLine.)
 */
const char * const MajorName[] = {
DEF_MAJ( "fundamental-mode",	FUNDAMENTAL,	"Fundamental "	),
DEF_MAJ( "text-mode",		TEXT,  		"Text "		),
DEF_MAJ( "c-mode",		CMODE,		"C "		),
#ifdef LISP
DEF_MAJ( "lisp-mode",		LISPMODE,	"Lisp "		), _IF(def LISP)
#endif
#ifdef PERL
DEF_MAJ( "perl-mode",		PERLMODE,	"Perl "		), _IF(def PERL)
#endif
#ifdef ABBREV
	"Global ",	/* not a real Major Mode but read_abbrev() needs it. */
	NULL		/* sentinel for match() */
#endif
};

const char * const MinorName[] = {
DEF_MIN( "auto-indent-mode",	Indent,		"AI "		),
DEF_MIN( "show-match-mode",	ShowMatch,	"Match "	),
DEF_MIN( "auto-fill-mode",	Fill,		"Fill "		),
DEF_MIN( "over-write-mode",	OverWrite,	"OvrWt "	),
DEF_MIN( "word-abbrev-mode",	Abbrev,		"Abbrev "	), _IF(def ABBREV)
DEF_MIN( "view-mode",		View,		"View "		)
};

/* The next are used in "list-buffers". */

private const char MajorChr[NMAJORS] = {
	'F',	/* Fundamental */
	'T',	/* Text */
	'C',	/* C mode */
#ifdef LISP
    	'L',	/* Lisp */
#endif
#ifdef PERL
	'P',	/* Perl */
#endif
};

private const char MinorChr[] = {
	'i',	/* auto Indent */
	'm',	/* show Match */
	'f',	/* Fill */
	'o',	/* Overwrite */
	'a',	/* Abbrev */
	'v'	/* View */
};

private const char * const TypeNames[] = {
	0,
	"Scratch",
	"File",
	"Process"
#ifdef IPROCS
    ,	"I-Process"
#endif
};

private void	setbname __(( Buffer *_(b), const char *_(bname) ));

/* Toggle BIT in the current buffer's minor mode flags.  If argument is
   supplied, a positive one always turns on the mode and zero (or negative)
   argument always turns it off. */

void
TogMinor(bit)
register int	bit;
{
	register Buffer	*b = curbuf;

	if (exp_p) {
		if (exp <= 0)
			b->b_minor &= ~bit;
		else
			b->b_minor |= bit;
	} else
		b->b_minor ^= bit;
	updmodline();
#ifdef MENUS
	Modechange++;
#endif
}

/* Creates a new buffer, links it at the end of the buffer chain,
   initializes it and returns it. [TRH] merged buf_alloc and mak_buf. */

private Buffer *mak_buf __(( void ));
private Buffer *
mak_buf()
{
	register Buffer *b,
			**hp;

	/* append to end of buffer chain */
	for (hp = &world; (*hp); hp = &(*hp)->b_next) ;

	(*hp) = b = (Buffer *) emalloc(sizeof(Buffer));

	bzero(b, sizeof(Buffer));	/* in effect, calloc */

	b->b_name = NoName;
	b->b_major = TEXT;
	SETBUFTYPE(b, B_FILE);	/* File until proven otherwise */
#ifdef MENUS
	b->Type = BUFFER;	/* kludge, but simplifies menu handlers */
	Bufchange++;
#endif
	initlist(b);

	return b;
}

#ifndef TINY
DEF_CMD( "scratch-buffer",	NonExisting, ARG(B_SCRATCH) ); _IF(def TINY)
DEF_CMD( "file-buffer",		NonExisting, ARG(B_FILE) ); _IF(def TINY)
DEF_CMD( "scratch-buffer",	ChBufType, ARG(B_SCRATCH) ); _IF(ndef TINY)
DEF_CMD( "file-buffer",		ChBufType, ARG(B_FILE) ) _IF(ndef TINY)
{
	register Buffer	*b = curbuf;

	SETBUFTYPE(b, ObjArg(LastCmd));
	updmodline();
}
#endif

DEF_CMD( "rename-buffer", ReNamBuf, NO )
{
	register const char	*new,
				*prompt = ProcFmt;

	while (buf_exists(new = ask((char *) 0, prompt, new)))
		prompt = "%s already exists; new name? ";

	setbname(curbuf, new);
}

const char *
ask_buf(def)
const Buffer	*def;
{
	const char		*bnames[400];	/* should be enough in most cases */
    {
	register const Buffer	*b;
	register const char	**bnamp = bnames,
				**endp = &bnamp[sizeof(bnames)/sizeof(bnames[0])-1];

	if (b = world) do {
		*bnamp++ = b->b_name;
		if (bnamp == endp)
			break;
	} while (b = b->b_next);
	*bnamp = NULL;
    }
    {
	register const char	*bname = NULL;
	register int		com;
	register const char	*prompt = ProcFmt;

	if (def) {
		bname = def->b_name;
		prompt = ProcDefFmt;
	}
	if ((com = complete(bnames, RET_STATE, prompt, bname)) < 0) {
		Minibuf[BNAMESIZE-5] = '\0';	/* silently truncate... */
		if (com == ORIGINAL || com == AMBIGUOUS)
			return Minibuf;
		if (com == NULLSTRING && bname)
			return bname;
		complain((char *)0);
	}
	return bnames[com];
    }
}

/* choose a convenient default file name, for curbuf. */

const char *
def_bfile()
{
	register Buffer		*cb = curbuf;
	register const char	*fname;

	if ((fname = cb->b_fname) == NULL &&
	    (cb->b_type == B_FILE && strcmp(cb->b_name, Mainbuf) != 0))
		fname = cb->b_name;

	return fname;
}

/* choose a convenient alternate buffer, for a given buffer `b'. */

const Buffer *
def_buf(b)
register const Buffer	*b;
{
	register const Buffer	*defb;

	if ((defb = lastbuf) == NULL || defb == b) {
		register const Buffer	*mainb = buf_exists(Mainbuf);

/*-		lastbuf = NULL;			/* invalidate. */
		defb = b;			/* in case it was NULL. */
		do {
			if (((defb = defb->b_next) == NULL &&
			    /* to make sure loop terminates when `b' is
			       unlinked from the buffer list, `mainb' is
			       made NULL when we see it the first time.
			       (note: `world == NULL' implies `mainb == NULL',
				so everything is under control.) */
			     (defb = world, mainb == NULL)) ||
			    (defb == b)) {		/* all around. */
				defb = mainb;
				break;
			}
		} while ((defb->b_type != B_FILE) ||
			 (defb == mainb && (mainb = NULL, TRUE)));
	}
	return defb;
}

DEF_CMD( "find-file",	  BufSelect, ARG(1) );
DEF_CMD( "pick-buffer",	  BufSelect, ARG(2) );
DEF_CMD( "select-buffer", BufSelect, ARG(0) )
{
	register Buffer *cb = curbuf;
	register Buffer	*newb;
	register int	lno = 0;

	if (exp_p == YES)
		lno = exp;	/* exp can be destroyed by do_find */

	if (LastCmd->Type & ARG(1)) {	/* "find-file" */
		char	fname[FILESIZE];

		newb = do_find(curwind, ask_file((char *) 0, def_bfile(), fname), NO);
	}
	else {				/* "select-buffer" / "pick-buffer" */
#define		bname ((char *) newb) /* alias! {Cheat, for TINY jove} */
		newb = (Buffer *) ask_buf(def_buf(cb));

		/* "pick" is like "select" but allows existing buffers only.
		   This is especially nice for selection-by-number. */

		if ((LastCmd->Type & ARG(2)) && !buf_exists(bname))
			complain("[No such buffer]");

		newb = do_select(curwind, bname);
#undef		bname /* unalias! */
	}
	SetBuf(newb);
	if (lno)
		to_line(lno);
	if (cb != newb)
		SetABuf(cb);
}

private void defb_wind __(( const Buffer *_(b) ));
private void
defb_wind(b)
register const Buffer *b;
{
	register Window		*w = curwind;
	register const Buffer	*altb = def_buf(b);

	for (;;) {
		if (w->w_bufp == b) {
			if (altb)
				do_select(w, altb->b_name);
			else if (one_windp(w))
				do_select(w, Mainbuf);
			else {
				w = w->w_next;
				del_wind(w->w_prev);
				continue;	/* we already advanced w */
			}
		}
		if ((w = w->w_next) == curwind)
			break;
	}
	if (curbuf == b)
		SetBuf(w->w_bufp);
}

Buffer *
getNMbuf()
{
	register Buffer		*delbuf;

	if ((delbuf = buf_exists(ask_buf(curbuf))) == NULL)
		complain("[No such buffer]");
	if (IsModified(delbuf))
		confirm("%b modified, are you sure? ", delbuf);
	return delbuf;
}

DEF_CMD( "erase-buffer", BufErase, EDIT )
{
	register Buffer *delbuf;

	if (delbuf = getNMbuf()) {
		initlist(delbuf);
		delbuf->b_modified = NO;
		updmodline();			/* Kludge... */
	}
}

void
kill_buf(delbuf)
register Buffer *delbuf;
{
#ifdef IPROCS
	pbuftiedp(delbuf);	/* check for lingering processes */
#endif
    {
	register Buffer	**hp;

	for (hp = &world; (*hp) != delbuf;  hp = &(*hp)->b_next)
		if ((*hp) == NULL)
			complain("?buffer?");
	(*hp) = delbuf->b_next;
    }
	lfreelist(delbuf->b_first);
    {
	register Mark	*m;

	while (m = delbuf->b_marks)
		del_mark(m, delbuf); /* ah, this was missing since day one */
    }
	if (perr_buf == delbuf) {
		ErrFree();
		perr_buf = NULL;
	}
	if (delbuf == lastbuf)
		SetABuf(curbuf);	/* even if NULL! */
	if (curwind) {
		/* so we can use (and delete) buffers BEFORE windows are
		   set up.  This is used for logging errors in FKEYS setup. */
		defb_wind(delbuf);
	}
	if (curbuf == delbuf) {	/* only happens when curwind not yet setup */
		curbuf = NULL;
		SetABuf(NULL);
	}
    {
	register const char	*name;

	if ((name = delbuf->b_name) != NoName)
		free((void_*) name);
	if (name = delbuf->b_fname)
		free((void_*) name);
    }
	free((void_*) delbuf);
#ifdef MENUS
	Bufchange++;
#endif
}

/* offer to kill some buffers */

DEF_CMD( "kill-some-buffers", KillSome, NO )
{
	register Buffer *b,
			*next;

	if (b = world) do {
		next = b->b_next;
		if (yes_or_no_p(NIL, "Kill %b? ", b) == NO)
			continue;
		if (IsModified(b) &&
		    yes_or_no_p('N', "%b modified; should I save it? ", b)) {
			register Buffer	*oldb = curbuf;
			SetBuf(b);
			SaveFile();
			SetBuf(oldb);
		}
		kill_buf(b);
	} while (b = next);
}

DEF_CMD( "delete-buffer", BufKill, NO )
{
	register Buffer	*b;

	if (b = getNMbuf())
		kill_buf(b);
}

private char *modes __(( const Buffer *_(b) ));
private char *
modes(b)
const Buffer	*b;
{
	register char		*d = genbuf;	/* to build temp. string */
	register const char	*s = MinorChr;
	register unsigned	m = b->b_minor;

	*d++ = MajorChr[b->b_major];
	do {
		if (m & 1)
			*d++ = *s;
	} while (s++, m >>= 1);
	*d = '\0';

	return genbuf;
}

DEF_CMD( "list-buffers", BufList, NO )
{
	register const Buffer	*b;
	register int	n,		/* To give each buffer a number */
			buf_width = 11,	/* For the * (variable length field) */
			any_notread = NO;

#ifndef TINY
	if (exp < 0 && !ModBufs(0)) {
		message("No modified buffers.");
		return;
	}
#endif
	if (b = world) do {
		any_notread |= b->b_ntbf;
		if ((n = strlen(b->b_name)) > buf_width)
			buf_width = n;
	} while (b = b->b_next);

	TOstart("*Buffer list*", TRUE);	/* true means auto-newline */

	Typeout("(* means buffer needs saving)");
	if (any_notread)
		Typeout("(+ means file hasn't been read yet)");
	Typeout((char *) 0);
	/* NOTE this should correspond with layout of the format below. */
	Typeout("NO Lines Type      Mode      %-*s  File", buf_width, "Name");
	Typeout("-- ----- ----      ----      %-*s  ----", buf_width, "----");
	n = 0;
	if (b = world) do {
		++n;
#ifndef TINY
		/* show scratch buffers only if full buffer list requested */
		if (exp_p != YES && b->b_type == B_SCRATCH)
			continue;
		/* just show modified buffers if argument is negative */
		if (exp < 0 && !IsModified(b))
			continue;
#endif
		Typeout("%-2d %-5d %-9s %-8s%c %-*b  %-s",
			n,
			lineno(b->b_last),
			TypeNames[b->b_type],
			modes(b),
			IsModified(b) ? '*' : (b->b_ntbf) ? '+' : ' ',
			buf_width,
			b,
			filename(b));

		if (TOabort)
			break;
	} while (b = b->b_next);
	TOstop();
}

private void bufname __(( Buffer *_(b) ));
private void
bufname(b)
Buffer *b;
{
	char		bnamebuf[BNAMESIZE];
	register int	try = 0;
	register const char
			*bname,
			*basnam;

	if ((bname = b->b_fname) == NULL)
		complain("[No file name]");
	basnam = bname = basename(bname);
	while (buf_exists(bname))
		sprintf((char *)(bname = bnamebuf), "%s'%d", basnam, ++try);
	setbname(b, bname);
}

void
initlist(b)
register Buffer *b;
{
	lfreelist(b->b_first);
	b->b_first = b->b_dot = b->b_last = NULL;
	listput(b, NULL);
	SavLine(b->b_dot, NullStr);
	b->b_char = 0;
    {
	register Mark	*m;

	if (m = b->b_marks) do {
		MarkSet(m, b->b_dot, b->b_char);
	} while (m = m->m_next);
    }
	if (b == curbuf)
		/*getDOT(); -- since this is an empty line, boils down to: */
		linebuf[0] = '\0';
	if (perr_buf == b) {
		ErrFree();
		perr_buf = NULL;
	}
}

/* Returns pointer to buffer with name NAME, or if NAME is a string of digits
   returns the buffer whose number equals those digits.  Otherwise, returns
   0. */

Buffer *
buf_exists(name)
const char	*name;
{
	register Buffer		*bp;
	register int		n;
	register const char	*bname;

	if ((bname = name) == NULL)
		return NULL;

	if (bp = world) do {
		if (strcmp(bp->b_name, bname) == 0)
			return bp;
	} while (bp = bp->b_next);

	/* Doesn't match any names.  Try for a buffer number... */

	if ((n = schr_to_int(bname, 10, bufno(curbuf))) > 0) {
		if (bp = world) do {
			if (--n == 0)
				break;
		} while (bp = bp->b_next);
	}
	return bp;
}

/* Returns buffer pointer with a file name NAME, if one exists.  Stat's the
   file and compares inodes, in case NAME is a link, as well as the actual
   characters that make up the file name. */

Buffer *
file_exists(name)
const char	*name;
{
	register Buffer		*b = NULL;
	register const char	*fname;

	if (fname = name) {
		char	fnamebuf[FILESIZE];
		struct stat	filestat, linkstat;
		register int	stat_result;

		fname = PathParse(name, fnamebuf);
		stat_result = zstat(fname, &filestat);

		if (b = world) do {
			if (b->b_fname == NULL)
				continue;
			if (strcmp(b->b_fname, fname) == 0 ||
			    /* Is it a link? */
			    (b_flink_p(b, stat_result, &filestat)
#ifndef TINY
			    /* probably; check that cached inode is still valid.
			       {{this check is to avoid erroneously reporting
				 a file that is already buffered in JOVE,
				 then deleted from disk, and its inode reused
				 for a new file, as a link to that file
				 when that new file is visited.}} */
			     && b_flink_p(b, zstat(b->b_fname, &linkstat),
					  &linkstat)
#endif
			     )) {
				/* If the file stored on disk has been changed
				   from under our ass, re-visit it. */
				if (b_foutdated_p(b, &filestat)) {
					register Buffer *oldb = curbuf;

					SetBuf(b);
					visit_file(fname, 0);
					SetBuf(oldb);
				}
				break;
			}
		} while (b = b->b_next);
	}
	return b;
}

private void
setbname(b, name)
register Buffer *b;
const char	*name;
{
	register const char	*bname;

	updmodline();	/* Kludge ... but speeds things up considerably */

	if ((bname = b->b_name) != NoName) {
		free((void_*) bname);
		b->b_name = NoName;
	}
	if (bname = name)
		b->b_name = copystr(bname);
#ifdef MENUS
	Bufchange++;
#endif
}

void
setfname(b, name)
register Buffer *b;
const char		*name;
{
	char		wholename[FILESIZE];
	register const char
			*oldname,
			*newname;
	Buffer		*save = curbuf;

	SetBuf(b);	/* BEFORE file name is set up! */
	updmodline();	/* Kludge ... but speeds things up considerably */

	oldname = b->b_fname;
	if (newname = name) {
		newname = copystr(PathParse(newname, wholename));
	}
	b->b_fname = newname;
	DoAutoExec(newname, oldname);
	ino_val(b->b_ino, 0);		/* until they're known. */
	b->b_dev = 0;
	b->b_mtime = 0;
	b->b_statsize = 0;
	if (oldname)
		free((void_*) oldname);
	SetBuf(save);
#ifdef MENUS
	Bufchange++;
#endif
}

void
set_ino(b)
register Buffer *b;
{
	struct stat	stbuf;

	zstat(b->b_fname, &stbuf);
	ino_cpy(b->b_ino, stbuf.st_ino);
	b->b_dev = stbuf.st_dev;
	b->b_mtime = stbuf.st_mtime;
	b->b_statsize = stbuf.st_size;
}

/* Find the file `fname' into buf and put in in window `w' */
/* if `force' non-zero, force load the file and move dot to that line. */

Buffer *
do_find(wind, fname, force)
Window		*wind;
const char	*fname;
{
	register Buffer	*b;
	register Window	*w;

	if ((b = file_exists(fname)) == NULL) {
		b = mak_buf();
		setfname(b, fname);
		bufname(b);
 		set_ino(b);
		b->b_ntbf = 1;
	}

	if (force) {
		register Buffer	*oldb = curbuf;

		SetBuf(b);	/* this'll read the file */
		to_line(force);
		SetBuf(oldb);
	}

	if (w = wind)
		tiewind(w, b);
	return b;
}

void
SetBuf(buf)
Buffer *buf;
{
	register Buffer *newbuf;
	register Line *line;
#ifdef FIXED_MAPS
#   ifdef IPROCS
	register Buffer *oldbuf = curbuf;
#   endif
#endif
	if ((newbuf = buf) == NULL || newbuf == curbuf)
		return;

	lsave();	/* with old buffer, in case of OverWrite mode */

	curbuf = newbuf;
	line = newbuf->b_dot;
	newbuf->b_dot = NULL;		/* force DotTo to load linebuf (Urp!) */
	DotTo(line, newbuf->b_char);

	/* Do the read now ... */
	if (newbuf->b_ntbf)
		read_file(newbuf->b_fname, 0);

	SET_TABLE(newbuf->b_major);		/* [TRH] set syntax table */
#ifdef FIXED_MAPS
#   ifdef IPROCS
	if (oldbuf && oldbuf->b_process) {
		if (!newbuf->b_process)		/* exit i-process */
			PopPBs();
	} else {
		if (newbuf->b_process)		/* enter i-process */
			PushPBs();
	}
#   endif
#endif
#ifdef MENUS
	Modechange++;
#endif
}

Buffer *
do_select(wind, name)
Window			*wind;
register const char	*name;
{
	register Buffer *new;
	register Window	*w;

	if ((new = buf_exists(name)) == NULL) {
		new = mak_buf();
/*-		setfname(new, (char *) 0);	-*/
		setbname(new, name);
	}
	if (w = wind)
		tiewind(w, new);
	return new;
}

int
bufno(buf)
register Buffer	*buf;
{
	register Buffer	*b;
	register int	n = 0;

	if (b = world) do {
		++n;
		if (b == buf)
			return n;
	} while (b = b->b_next);

	return 0;
}

/*======================================================================
 * $Log: buf.c,v $
 * Revision 14.31.0.12  1994/06/23  02:54:50  tom
 *  (BufList): fix name of scratch buffer; (bufname): fix uniquized bufname.
 *
 * Revision 14.31.0.8  1993/11/11  16:07:43  tom
 * (SetBuf): call DotTo() to load linebuf and set point; this fixes bug in
 * OverWrite mode, which caused lossage if point was `beyond' end-of-line.
 *
 * Revision 14.31  1993/02/15  02:16:10  tom
 * remove (void) casts; fix visit_file bug in file_exists();
 * lots of random optimizations.
 *
 * Revision 14.30  1993/02/06  00:48:31  tom
 * cleanup whitespace; some random optimizations; fix bug in def_buf();
 * check for outdated file in file_exists() and offer to load the disk-based
 * version of the file; remove call to Active() in SetBuf()--active keymaps are
 * now determined whenever necessary by calls to active_map().
 *
 * Revision 14.28  1992/10/24  01:24:18  tom
 * replace `pointer' with `void_*' convention from "portansi.h".
 *
 * Revision 14.27  1992/09/22  15:23:10  tom
 * file_exists(): improve link recognition.
 *
 * Revision 14.26  1992/08/26  23:56:50  tom
 * make setbname() private; inline AllMarkSet; add RCS directives.
 *
 */
