/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved
 *
 * Code Contributions By:	Bram Moolenaar			mool@oce.nl
 *							Tim Thompson			twitch!tjt
 *							Tony Andrews			onecom!wldrdg!tony 
 *							G. R. (Fred) Walter		watmath!watcgl!grwalter 
 */

/*
 * script.c: functions for handling script files
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#ifdef UNIX				/* include  MAXPATHLEN */
# include <sys/param.h>
#endif

static char *scriptname;			/* name of the script in use */
static FILE *autoscriptfd = NULL;
static char *makescriptname __ARGS((void));
static void Supdatescript __ARGS((char *));

extern int global_busy;			/* this is in csearch.c */

/*
 * for Amiga Dos 2.0x we use Open/Close/Flush instead of fopen/fclose
 */
#ifdef AMIGA
# ifndef NO_ARP
extern int dos2;					/* this is in amiga.c */
# endif
# ifdef SASC
#  include <proto/dos.h>
# endif
#endif

/*
 * We use this flag to avoid writing :win to commands to the script file
 * during startup.
 */
static int script_started = FALSE;

/*
 * startscript(): open automatic script file
 */
	void
startscript()
{
	int		n;
	char	buf[25];
#ifdef AMIGA
	int		r;
	FILE	*dummyfd = NULL;
#endif
#ifdef UNIX
# ifdef SCO
	mode_t	oldmask;
# else
	int		oldmask;
# endif
#endif

	script_started = TRUE;

#ifdef AMIGA
/*
 * With Amiga DOS 2.0 the system may lockup with the sequence: write to .vim
 * file, close it, delete it, create a new .vim file and write to it.
 * This is a problem in the filesystem hash chains (solved in version 39.xx).
 * The Delay seems to solve this problem, maybe because DOS gets a chance to
 * finish closing and deleting the old .vim file. Also do this for DOS 1.3,
 * just in case.
 */
	if (stopscript())
		Delay(10L);		/* This should fix the lockup bug */
#else
	stopscript();		/* stop any old script */
#endif

	if (p_uc == 0 || exiting)	/* no auto script wanted/needed */
		return;
	if (Changed)
		emsg("Warning: buffer already changed, auto script file will be incomplete");

#ifdef AMIGA
/*
 * If we start editing a new file, e.g. "test.doc", which resides on an MSDOS
 * compatible filesystem, it is possible that the file "test.doc.vim" which we
 * create will be exactly the same file. To avoid this problem we temporarily
 * create "test.doc".
 */
	if (!(p_sn || thisfile_sn) && xFilename && getperm(xFilename) < 0)
		dummyfd = fopen(xFilename, "w");
#endif

/*
 * we try different names until we find one that does not exist yet
 */
	scriptname = makescriptname();
	for (;;)
	{
		if (scriptname == NULL)		/* must be out of memory */
			break;
		if ((n = strlen(scriptname)) == 0)	/* safety check */
		{
			free(scriptname);
			break;
		}
		
		/*
		 * check if the scriptfile already exists
		 */
		if (getperm(scriptname) < 0)		/* it does not exist */
		{
				/*
				 * Create the autoscript file.
				 */
#ifdef UNIX
			/*
			 * Disallow others to read our .vim file. This is useful if the
			 * .vim file is put in some public place like /tmp.
			 */
# ifdef SCO
			oldmask = umask((mode_t)066);	/* rw------- */
# else
			oldmask = umask(066);			/* rw------- */
# endif
#endif
#ifdef AMIGA
# ifndef NO_ARP
			if (dos2)
# endif
				autoscriptfd = (FILE *)Open((UBYTE *)scriptname, (long)MODE_NEWFILE);
# ifndef NO_ARP
			else
				autoscriptfd = fopen(scriptname, "w");
# endif
#else	/* !AMIGA */
			autoscriptfd = fopen(scriptname, WRITEBIN);
#endif	/* AMIGA */
#ifdef UNIX
			umask(oldmask);				/* back to default umask */
#endif

#ifdef AMIGA
			/*
			 * on the Amiga getperm() will return -1 when the file exists but
			 * is being used by another program. This happens if you edit
			 * a file twice.
			 */
			if (autoscriptfd != NULL || (IoErr() != ERROR_OBJECT_IN_USE && IoErr() != ERROR_OBJECT_EXISTS))
#endif
				break;
		}
	/*
	 * get here when file already exists
	 */
		if (scriptname[n - 1] == 'm')		/* first try */
		{
#ifdef AMIGA
		/*
		 * on MS-DOS compatible filesystems (e.g. messydos) file.doc.vim
		 * and file.doc are the same file. To guess if this problem is
		 * present try if file.doc.vix exists. If it does, we set thisfile_sn
		 * and try file_doc.vim (dots replaced by underscores for this file),
		 * and try again. If it doesn't we assume that "file.doc.vim" already
		 * exists.
		 */
			if (!(p_sn || thisfile_sn))		/* not tried yet */
			{
				scriptname[n - 1] = 'x';
				r = getperm(scriptname);	/* try "file.vix" */
				scriptname[n - 1] = 'm';
				if (r >= 0)					/* it seems to exist */
				{
					thisfile_sn = TRUE;
					free(scriptname);
					scriptname = makescriptname();	/* '.' replaced by '_' */
					continue;						/* try again */
				}
			}
#endif
			/* if we get here ".vim" file really exists */
			if (!recoverymode)
				emsg(".vim file exists: an edit of this file has not been finished");
		}

		if (scriptname[n - 1] == 'a')	/* tried enough names, give up */
		{
			free(scriptname);
			break;
		}
		--scriptname[n - 1];				/* change last char of the name */
	}
	if (autoscriptfd != NULL)		/* ".vim" file has been created */
	{
		script_winsize();			/* always start with a :win command */
									/* output cursor position if neccessary */
		if (Curpos.lnum > 1 || Curpos.col > 0)
		{
			sprintf(buf, "%ldG0%dl", (long)Curpos.lnum, (int)Curpos.col);
			Supdatescript(buf);
		}
	}

#ifdef AMIGA
	if (dummyfd)		/* file has been created temporarily */
	{
		fclose(dummyfd);
		remove(xFilename);
	}
#endif
}

	int
stopscript()
{
	if (!autoscriptfd)
		return FALSE;		/* nothing to stop */

#ifdef AMIGA
# ifndef NO_ARP
	if (dos2)
# endif
		Close((BPTR)autoscriptfd);
# ifndef NO_ARP
	else
		fclose(autoscriptfd);
# endif
#else
	fclose(autoscriptfd);
#endif
	remove(scriptname);		/* delete the file */
	autoscriptfd = NULL;
	free(scriptname);
	return TRUE;
}

/*
 * open new script file
 * return 0 on success, 1 on error
 */
	int
openscript(name)
	char *name;
{
	int oldcurscript;

	if (curscript + 1 == NSCRIPT)
	{
		emsg(e_nesting);
		return 1;
	}
	else
	{
		if (scriptin[curscript] != NULL)	/* already reading script */
			++curscript;
		if ((scriptin[curscript] = fopen((char *)name, READBIN)) == NULL)
		{
			emsg(e_notopen);
			if (curscript)
				--curscript;
			return 1;
		}
		/*
		 * With command ":g/pat/so! file" we have to execute the
		 * commands from the file now.
		 */
		if (global_busy)
		{
			State = NORMAL;
			oldcurscript = curscript;
			do
			{
				normal();
				vpeekc();			/* check for end of file */
			}
			while (scriptin[oldcurscript]);
			State = CMDLINE;
		}
	}
	return 0;
}

/*
 * updatescipt() is called when a character has to be written into the script file
 * or when we have waited some time for a character (c == 0)
 */
	void
updatescript(c)
	int c;
{
	static int count = 0;

	if (c && scriptout)
		putc(c, scriptout);
	if (autoscriptfd == NULL || (c == 0 && count == 0))		/* nothing to do */
		return;
	if (c)
	{
#ifdef AMIGA
# ifndef NO_ARP
		if (dos2)
# endif
			FPutC((BPTR)autoscriptfd, (unsigned long)c);
# ifndef NO_ARP
		else
			putc(c, autoscriptfd);
# endif
#else
		putc(c, autoscriptfd);
#endif
		++count;
	}
	if ((c == 0 || count >= p_uc) && Updated)
	{
		/*
		 * Before DOS 2.0x we have to close and open the file in order to really
		 * get the characters in the file to disk!
		 * With DOS 2.0x Flush() can be used for that
		 */
#ifdef AMIGA
# ifndef NO_ARP
		if (dos2)
# endif
			Flush((BPTR)autoscriptfd);
# ifndef NO_ARP
		else
		{
			fclose(autoscriptfd);
			autoscriptfd = fopen(scriptname, "a");
		}
# endif
#else 	/* !AMIGA */
		fclose(autoscriptfd);
# ifdef MSDOS
		autoscriptfd = fopen(scriptname, "ab");
# else
		autoscriptfd = fopen(scriptname, "a");
# endif
#endif
		count = 0;
		Updated = 0;
	}
}

	static void
Supdatescript(str)
	char *str;
{
	while (*str)
		updatescript(*str++);
}

/*
 * try to open the ".vim" file for recovery
 * if recoverymode is 1: start recovery, set recoverymode to 2
 * if recoverymode is 2: stop recovery mode
 */
	void
openrecover()
{
	char *fname;
	struct stat efile, rfile;

	if (recoverymode == 2)		/* end of recovery */
	{
		recoverymode = 0;
		if (got_int)
			emsg("Recovery Interrupted");
		else
			msg("Recovery completed; If everything is OK: Save this file and delete the .vim file");
			/* The cursor will be in the wrong place after the msg() */
			/* We need to fix it here because we are called from inchar() */
		setcursor();
		flushbuf();
	}
	else
	{
		fname = makescriptname();
		if (fname)
		{
			recoverymode = 2;
			if (xFilename != NULL &&
					stat(xFilename, &efile) != -1 &&
					stat(fname, &rfile) != -1 &&
					efile.st_mtime > rfile.st_mtime)
				emsg(".vim file is older; file not recovered");
			else
			{
				if (openscript(fname))
					emsg("Cannot open .vim file; file not recovered");
			}
			free(fname);
		}
	}
}

/*
 * make script name out of the filename
 */
	static char *
makescriptname()
{
	char	*r, *s, *fname;

	r = modname(xFilename, ".vim");
	if (*p_dir == 0 || r == NULL)
		return r;

	fname = gettail(r);
	s = alloc((unsigned)(strlen(p_dir) + strlen(fname) + 2));
	if (s != NULL)
	{
		strcpy(s, p_dir);
		if (*s && !ispathsep(*(s + strlen(s) - 1)))	/* don't add '/' after ':' */
			strcat(s, PATHSEPSTR);
		strcat(s, fname);
	}
	free(r);
	return s;
}

/*
 * add full path to auto script name, used before first :cd command.
 */
	void
scriptfullpath()
{
	char *s;

	if (!autoscriptfd)
		return;		/* nothing to do */
	/*
	 * on the Amiga we cannot get the full path name while the file is open
	 * so we close it for a moment
	 */
#ifdef AMIGA
# ifndef NO_ARP
	if (dos2)
# endif
		Close((BPTR)autoscriptfd);
# ifndef NO_ARP
	else
		fclose(autoscriptfd);
# endif
#endif

	if (FullName(scriptname, IObuff, IOSIZE))
	{
		s = strsave(IObuff);
		if (s)
		{
			free(scriptname);
			scriptname = s;
		}
	}

#ifdef AMIGA
# ifndef NO_ARP
	if (dos2)
# endif
	{
		autoscriptfd = (FILE *)Open((UBYTE *)scriptname, (long)MODE_OLDFILE);
		if (autoscriptfd)
			Seek((BPTR)autoscriptfd, 0L, (long)OFFSET_END);
	}
# ifndef NO_ARP
	else
		autoscriptfd = fopen(scriptname, "a");
# endif
#endif
}

/*
 * add extention to filename - change path/fo.o.h to path/fo.o.h.ext or
 * fo_o_h.ext for MSDOS or when dotfname option reset.
 *
 * Assumed that fname is a valid name found in the filesystem we assure that
 * the return value is a different name and ends in ".ext".
 * "ext" MUST start with a "." and MUST be at most 4 characters long.
 * Space for the returned name is allocated, must be freed later.
 */

	char *
modname(fname, ext)
	char *fname, *ext;
{
	char			*retval;
	register char   *s;
	register char   *ptr;
	register int	fnamelen, extlen;
#ifdef MAXPATHLEN
	char			currentdir[MAXPATHLEN];
#else
	char			currentdir[512];
#endif

	extlen = strlen(ext);

	/*
	 * if there is no filename we must get the name of the current directory
	 * (we need the full path in case :cd is used)
	 */
	if (fname == NULL || *fname == NUL)
	{
#ifdef MAXPATHLEN
		(void)dirname(currentdir, MAXPATHLEN - 1);
#else
		(void)dirname(currentdir, 511);
#endif
		strcat(currentdir, PATHSEPSTR);
		fnamelen = strlen(currentdir);
	}
	else
		fnamelen = strlen(fname);
	retval = alloc((unsigned) (fnamelen + extlen + 1));
	if (retval != NULL)
	{
		if (fname == NULL || *fname == NUL)
			strcpy(retval, currentdir);
		else
			strcpy(retval, fname);
		/*
		 * search backwards until we hit a '/', '\' or ':' replacing all '.' by '_'
		 * for MSDOS or when dotfname option reset.
		 * Then truncate what is after the '/', '\' or ':' to 8 characters for MSDOS
		 * and 26 characters for AMIGA and UNIX.
		 */
		for (ptr = retval + fnamelen; ptr >= retval; ptr--)
		{
#if !defined(MSDOS) || defined(WIN32)
			if (p_sn || thisfile_sn)
#endif
				if (*ptr == '.')	/* replace '.' by '_' */
					*ptr = '_';
			if (ispathsep(*ptr))
				break;
		}
		ptr++;

		/* the filename has at most BASENAMELEN characters. */
		if (strlen(ptr) > (unsigned)BASENAMELEN)
			ptr[BASENAMELEN] = '\0';
#if !defined(MSDOS) || defined(WIN32)
		if ((p_sn || thisfile_sn) && strlen(ptr) > (unsigned)8)
			ptr[8] = '\0';
#endif
		s = ptr + strlen(ptr);

		/*
		 * Append the extention.
		 * ext must start with '.' and cannot exceed 3 more characters.
		 */
		strcpy(s, ext);
		if (fname != NULL && strcmp(fname, retval) == 0)
		{
			/* after modification still the same name? */
			/* we search for a character that can be replaced by '_' */
			while (--s >= ptr)
			{
				if (*s != '_')
				{
					*s = '_';
					break;
				}
			}
			if (s < ptr)
			{
				/* fname was "________.<ext>" how tricky! */
				*ptr = 'v';
			}
		}
	}
	return retval;
}

/*
 * the new window size must be used in scripts;
 * write a ":winsize width height" command to the (auto)script
 * Postpone this action if not in NORMAL State, otherwise we may insert the
 * command halfway another command.
 */
int script_winsize_postponed = FALSE;

	void
script_winsize()
{
	char			buf[25];

	if (!script_started || State != NORMAL)		/* postpone action */
	{
		script_winsize_postponed = TRUE;
		return;
	}

	sprintf(buf, ":win %d %d\r", (int)Columns, (int)Rows);
	Supdatescript(buf);
	script_winsize_postponed = FALSE;
}

/*
 * This function is called after each "State = NORMAL"
 */
	void
script_winsize_pp()
{
	if (script_winsize_postponed)
		script_winsize();
}


syntax highlighted by Code2HTML, v. 0.9.1