/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Read the file "credits.txt" for a list of people who contributed.
 * Read the file "uganda.txt" for copying and usage conditions.
 */

/*
 * cmdcmds.c: functions for command line commands
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#ifdef KANJI
#include "kanji.h"
#endif

#if defined(LATTICE) || defined(NT)
# define mktemp(a)	tmpnam(a)
#endif

extern char		*mktemp __ARGS((char *));

/*
 * align text:
 * type = -1  left aligned
 * type = 0   centered
 * type = 1   right aligned
 */
	void
do_align(start, end, width, type)
	linenr_t	start;
	linenr_t	end;
	int			width;
	int			type;
{
	FPOS	pos;
	int		len;
	int		indent = 0;

	pos = curwin->w_cursor;
	if (type == -1)		/* left align: width is used for new indent */
	{
		if (width >= 0)
			indent = width;
	}
	else
	{
		/*
		 * if 'textwidth' set, use it
		 * else if 'wrapmargin' set, use it
		 * if invalid value, use 80
		 */
		if (width <= 0)
			width = curbuf->b_p_tw;
		if (width == 0 && curbuf->b_p_wm > 0)
			width = Columns - curbuf->b_p_wm;
		if (width <= 0)
			width = 80;
	}

	if (!u_save((linenr_t)(start - 1), (linenr_t)(end + 1)))
		return;
	for (curwin->w_cursor.lnum = start; curwin->w_cursor.lnum <= end; ++curwin->w_cursor.lnum)
	{
		set_indent(indent, TRUE);				/* remove existing indent */
		if (type == -1)							/* left align */
			continue;
		len = strsize(ml_get(curwin->w_cursor.lnum));		/* get line lenght */
		if (len < width)
			switch (type)
			{
			case 0:		set_indent((width - len) / 2, FALSE);	/* center */
						break;
			case 1:		set_indent(width - len, FALSE);			/* right */
						break;
			}
	}
	curwin->w_cursor = pos;
	beginline(TRUE);
	updateScreen(NOT_VALID);
}

/*
 * :move command - move lines line1-line2 to line n
 *
 * return FAIL for failure, OK otherwise
 */
	int
do_move(line1, line2, n)
	linenr_t	line1;
	linenr_t	line2;
	linenr_t	n;
{
	char_u		*q;
	int			has_mark;

	if (n >= line1 && n < line2 && line2 > line1)
	{
		EMSG("Move lines into themselves");
		return FAIL;
	}

	/*
	 * adjust line marks (global marks done below)
	 * if the lines are moved down, the marks in the moved lines
	 * move down and the marks in the lines between the old and
	 * new position move up.
	 * If the lines are moved up it is just the other way round
	 */
	if (n >= line2)			/* move down */
	{
		mark_adjust(line1, line2, n - line2);
		mark_adjust(line2 + 1, n,  -(line2 - line1 + 1));
	}
	else					/* move up */
	{
		mark_adjust(line1, line2, -(line1 - n - 1));
		mark_adjust(n + 1, line1 - 1, line2 - line1 + 1);
	}

	if (n >= line1)
	{
		--n;
		curwin->w_cursor.lnum = n - (line2 - line1) + 1;
	}
	else
		curwin->w_cursor.lnum = n + 1;
	while (line1 <= line2)
	{
			/* this undo is not efficient, but it works */
		u_save(line1 - 1, line1 + 1);
		q = strsave(ml_get(line1));
		if (q != NULL)
		{
			/*
			 * marks from global command go with the line
			 */
			has_mark = ml_has_mark(line1);
			ml_delete(line1);
			u_save(n, n + 1);
			ml_append(n, q, (colnr_t)0, FALSE);
			free(q);
			if (has_mark)
				ml_setmarked(n + 1);
		}
		if (n < line1)
		{
			++n;
			++line1;
		}
		else
			--line2;
	}
	CHANGED;
	return OK;
}

/*
 * :copy command - copy lines line1-line2 to line n
 */
	void
do_copy(line1, line2, n)
	linenr_t	line1;
	linenr_t	line2;
	linenr_t	n;
{
	linenr_t		lnum;
	char_u			*p;

	mark_adjust(n + 1, MAXLNUM, line2 - line1 + 1);

	/*
	 * there are three situations:
	 * 1. destination is above line1
	 * 2. destination is between line1 and line2
	 * 3. destination is below line2
	 *
	 * n = destination (when starting)
	 * curwin->w_cursor.lnum = destination (while copying)
	 * line1 = start of source (while copying)
	 * line2 = end of source (while copying)
	 */
	u_save(n, n + 1);
	curwin->w_cursor.lnum = n;
	lnum = line2 - line1 + 1;
	while (line1 <= line2)
	{
		/* need to use strsave() because the line will be unlocked
			within ml_append */
		p = strsave(ml_get(line1));
		if (p != NULL)
		{
			ml_append(curwin->w_cursor.lnum, p, (colnr_t)0, FALSE);
			free(p);
		}
				/* situation 2: skip already copied lines */
		if (line1 == n)
			line1 = curwin->w_cursor.lnum;
		++line1;
		if (curwin->w_cursor.lnum < line1)
			++line1;
		if (curwin->w_cursor.lnum < line2)
			++line2;
		++curwin->w_cursor.lnum;
	}
	CHANGED;
	msgmore((long)lnum);
}

/*
 * handle the :! command.
 * We replace the extra bangs by the previously entered command and remember
 * the command.
 */
	void
dobang(addr_count, line1, line2, forceit, arg)
	int			addr_count;
	linenr_t	line1, line2;
	int			forceit;
	char_u		*arg;
{
	static	char_u	*prevcmd = NULL;		/* the previous command */
	char_u			*t;
	char_u			*trailarg;
	int 			len;

	/*
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return;
	}
	len = STRLEN(arg) + 1;

	autowrite_all();
	/*
	 * try to find an embedded bang, like in :!<cmd> ! [args]
	 * (:!! is indicated by the 'forceit' variable)
	 */
	trailarg = arg;
	skiptospace(&trailarg);
	skipspace(&trailarg);
	if (*trailarg == '!')
		*trailarg++ = NUL;
	else
		trailarg = NULL;

	if (forceit || trailarg != NULL)			/* use the previous command */
	{
		if (prevcmd == NULL)
		{
			emsg(e_noprev);
			return;
		}
		len += STRLEN(prevcmd) * (trailarg != NULL && forceit ? 2 : 1);
	}

	if (len > CMDBUFFSIZE)
	{
		emsg(e_toolong);
		return;
	}
	if ((t = alloc(len)) == NULL)
		return;
	*t = NUL;
	if (forceit)
		STRCPY(t, prevcmd);
	STRCAT(t, arg);
	if (trailarg != NULL)
	{
		STRCAT(t, prevcmd);
		STRCAT(t, trailarg);
	}
	free(prevcmd);
	prevcmd = t;

	if (bangredo)			/* put cmd in redo buffer for ! command */
	{
		AppendToRedobuff(prevcmd);
		AppendToRedobuff((char_u *)"\n");
		bangredo = FALSE;
	}
		/* echo the command */
	msg_start();
	msg_outchar(':');
	if (addr_count)						/* :range! */
	{
		msg_outnum((long)line1);
		msg_outchar(',');
		msg_outnum((long)line2);
	}
	msg_outchar('!');
	msg_outtrans(prevcmd, -1);
	msg_ceol();

	if (addr_count == 0)				/* :! */
		doshell(prevcmd); 
	else								/* :range! */
		dofilter(line1, line2, prevcmd, TRUE, TRUE);
}

/*
 * call a shell to execute a command
 */
	void
doshell(cmd)
	char_u	*cmd;
{
	BUF		*buf;

	/*
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		msg_end();
		return;
	}
	stoptermcap();
	msg_outchar('\n');					/* may shift screen one line up */

		/* warning message before calling the shell */
	if (p_warn)
		for (buf = firstbuf; buf; buf = buf->b_next)
			if (buf->b_changed)
			{
				msg_outstr((char_u *)"[No write since last change]\n");
				break;
			}

	windgoto((int)Rows - 1, 0);
	cursor_on();
#ifdef KANJI
	cmd = kanjiconvsto(cmd, toupper(JP_SYS), TRUE);
	(void)call_shell(cmd, 0, TRUE);
	free(cmd);
#else
	(void)call_shell(cmd, 0, TRUE);
#endif

#ifdef AMIGA
	wait_return(term_console ? -1 : TRUE);		/* see below */
#else
	wait_return(TRUE);				/* includes starttermcap() */
#endif

	/*
	 * In an Amiga window redrawing is caused by asking the window size.
	 * If we got an interrupt this will not work. The chance that the window
	 * size is wrong is very small, but we need to redraw the screen.
	 * Don't do this if ':' hit in wait_return().
	 * THIS IS UGLY but it save an extra redraw.
	 */
#ifdef AMIGA
	if (skip_redraw)				/* ':' hit in wait_return() */
		must_redraw = CLEAR;
	else if (term_console)
	{
		OUTSTR("\033[0 q"); 		/* get window size */
		if (got_int)
			must_redraw = CLEAR;	/* if got_int is TRUE we have to redraw */
		else
			must_redraw = 0;		/* no extra redraw needed */
	}
#endif /* AMIGA */
}

/*
 * dofilter: filter lines through a command given by the user
 *
 * We use temp files and the call_shell() routine here. This would normally
 * be done using pipes on a UNIX machine, but this is more portable to
 * the machines we usually run on. The call_shell() routine needs to be able
 * to deal with redirection somehow, and should handle things like looking
 * at the PATH env. variable, and adding reasonable extensions to the
 * command name given by the user. All reasonable versions of call_shell()
 * do this.
 * We use input redirection if do_in is TRUE.
 * We use output redirection if do_out is TRUE.
 */
	void
dofilter(line1, line2, buff, do_in, do_out)
	linenr_t	line1, line2;
	char_u		*buff;
	int			do_in, do_out;
{
#ifdef LATTICE
	char_u		itmp[L_tmpnam];		/* use tmpnam() */
	char_u		otmp[L_tmpnam];
#else
	char_u		itmp[TMPNAMELEN];
	char_u		otmp[TMPNAMELEN];
#endif
	linenr_t 	linecount;
#ifdef KANJI
	char_u		code;
#endif

	/*
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return;
	}
	if (*buff == NUL)		/* no filter command */
		return;
	linecount = line2 - line1 + 1;
	curwin->w_cursor.lnum = line1;
	curwin->w_cursor.col = 0;
	/* cursupdate(); */

	/*
	 * 1. Form temp file names
	 * 2. Write the lines to a temp file
	 * 3. Run the filter command on the temp file
	 * 4. Read the output of the command into the buffer
	 * 5. Delete the original lines to be filtered
	 * 6. Remove the temp files
	 */

#ifndef LATTICE
	/* for lattice we use tmpnam(), which will make its own name */
	STRCPY(itmp, TMPNAME1);
	STRCPY(otmp, TMPNAME2);
#endif

	if ((do_in && *mktemp((char *)itmp) == NUL) || (do_out && *mktemp((char *)otmp) == NUL))
	{
		emsg(e_notmp);
		return;
	}

/*
 * ! command will be overwritten by next mesages
 * This is a trade off between showing the command and not scrolling the
 * text one line up (problem on slow terminals).
 */
#ifdef KANJI
	code = *curbuf->b_p_jc;
	*curbuf->b_p_jc = tolower(JP_SYS);
#endif
	must_redraw = CLEAR;		/* screen has been shifted up one line */
	++no_wait_return;			/* don't call wait_return() while busy */
	if (do_in && buf_write(curbuf, itmp, NULL, line1, line2, FALSE, 0, FALSE) == FAIL)
	{
		msg_outchar('\n');					/* keep message from writeit() */
		--no_wait_return;
		(void)emsg2(e_notcreate, itmp);		/* will call wait_return */
#ifdef KANJI
		*curbuf->b_p_jc = code;
#endif
		return;
	}
	if (!do_out)
		outchar('\n');

#if defined(UNIX) && !defined(ARCHIE)
/*
 * put braces around the command (for concatenated commands)
 */
 	sprintf((char *)IObuff, "(%s)", (char *)buff);
	if (do_in)
	{
		STRCAT(IObuff, " < ");
		STRCAT(IObuff, itmp);
	}
	if (do_out)
	{
		STRCAT(IObuff, " > ");
		STRCAT(IObuff, otmp);
	}
#else
/*
 * for shells that don't understand braces around commands, at least allow
 * the use of commands in a pipe.
 */
	STRCPY(IObuff, buff);
	if (do_in)
	{
		char_u		*p;
	/*
	 * If there is a pipe, we have to put the '<' in front of it
	 */
		p = STRCHR(IObuff, '|');
		if (p)
			*p = NUL;
		STRCAT(IObuff, " < ");
		STRCAT(IObuff, itmp);
		p = STRCHR(buff, '|');
		if (p)
			STRCAT(IObuff, p);
	}
	if (do_out)
	{
		STRCAT(IObuff, " > ");
		STRCAT(IObuff, otmp);
	}
#endif

	windgoto((int)Rows - 1, 0);
	cursor_on();
			/* errors are ignored, so you can see the error
			   messages from the command; use 'u' to fix the text */
#ifdef KANJI
	buff = kanjiconvsto(IObuff, toupper(JP_SYS), TRUE);
	(void)call_shell(buff, 1, FALSE);
	free(buff);
#else
	(void)call_shell(IObuff, 1, FALSE);
#endif

	if (do_out)
	{
		if (!u_save((linenr_t)(line2), (linenr_t)(line2 + 1)))
		{
			linecount = 0;
			goto error;
		}
		if (readfile(otmp, NULL, line2, FALSE, (linenr_t)0, MAXLNUM) == FAIL)
		{
			outchar('\n');
			emsg2(e_notread, otmp);
			linecount = 0;
			goto error;
		}

		if (do_in)
		{
			curwin->w_cursor.lnum = line1;
			dellines(linecount, TRUE, TRUE);
		}
		--no_wait_return;
	}
	else
	{
error:
		--no_wait_return;
		wait_return(FALSE);
	}
#ifdef KANJI
	*curbuf->b_p_jc = code;
#endif
	updateScreen(CLEAR);		/* do this before messages below */

	if (linecount > p_report)
	{
		if (!do_in && do_out)
			msgmore(linecount);
		else
			smsg((char_u *)"%ld lines filtered", (long)linecount);
	}
	remove((char *)itmp);
	remove((char *)otmp);
}
