119304Speter/*-
219304Speter * Copyright (c) 1992, 1993, 1994
319304Speter *	The Regents of the University of California.  All rights reserved.
419304Speter * Copyright (c) 1992, 1993, 1994, 1995, 1996
519304Speter *	Keith Bostic.  All rights reserved.
619304Speter *
719304Speter * See the LICENSE file for redistribution information.
819304Speter */
919304Speter
1019304Speter#include "config.h"
1119304Speter
1219304Speter#ifndef lint
13254225Speterstatic const char sccsid[] = "$Id: ex_txt.c,v 10.23 2001/06/25 15:19:21 skimo Exp $";
1419304Speter#endif /* not lint */
1519304Speter
1619304Speter#include <sys/types.h>
1719304Speter#include <sys/queue.h>
18254225Speter#include <sys/time.h>
1919304Speter
2019304Speter#include <bitstring.h>
2119304Speter#include <ctype.h>
2219304Speter#include <limits.h>
2319304Speter#include <stdio.h>
2419304Speter#include <stdlib.h>
2519304Speter#include <string.h>
2619304Speter
2719304Speter#include "../common/common.h"
28254225Speter#include "../vi/vi.h"
2919304Speter
3019304Speter/*
3119304Speter * !!!
3219304Speter * The backslash characters was special when it preceded a newline as part of
3319304Speter * a substitution replacement pattern.  For example, the input ":a\<cr>" would
3419304Speter * failed immediately with an error, as the <cr> wasn't part of a substitution
3519304Speter * replacement pattern.  This implies a frightening integration of the editor
3619304Speter * and the parser and/or the RE engine.  There's no way I'm going to reproduce
3719304Speter * those semantics.
3819304Speter *
3919304Speter * So, if backslashes are special, this code inserts the backslash and the next
4019304Speter * character into the string, without regard for the character or the command
4119304Speter * being entered.  Since "\<cr>" was illegal historically (except for the one
4219304Speter * special case), and the command will fail eventually, no historical scripts
4319304Speter * should break (presuming they didn't depend on the failure mode itself or the
4419304Speter * characters remaining when failure occurred.
4519304Speter */
4619304Speter
47281373Sbaptstatic int	txt_dent(SCR *, TEXT *);
48281373Sbaptstatic void	txt_prompt(SCR *, TEXT *, ARG_CHAR_T, u_int32_t);
4919304Speter
5019304Speter/*
5119304Speter * ex_txt --
5219304Speter *	Get lines from the terminal for ex.
5319304Speter *
54281373Sbapt * PUBLIC: int ex_txt(SCR *, TEXTH *, ARG_CHAR_T, u_int32_t);
5519304Speter */
5619304Speterint
57254225Speterex_txt(SCR *sp, TEXTH *tiqh, ARG_CHAR_T prompt, u_int32_t flags)
5819304Speter{
5919304Speter	EVENT ev;
6019304Speter	GS *gp;
6119304Speter	TEXT ait, *ntp, *tp;
6219304Speter	carat_t carat_st;
6319304Speter	size_t cnt;
6419304Speter	int rval;
65254225Speter	int nochange;
6619304Speter
6719304Speter	rval = 0;
6819304Speter
6919304Speter	/*
7019304Speter	 * Get a TEXT structure with some initial buffer space, reusing the
7119304Speter	 * last one if it's big enough.  (All TEXT bookkeeping fields default
7219304Speter	 * to 0 -- text_init() handles this.)
7319304Speter	 */
74254225Speter	if (!TAILQ_EMPTY(tiqh)) {
75254225Speter		tp = TAILQ_FIRST(tiqh);
76254225Speter		if (TAILQ_NEXT(tp, q) != NULL || tp->lb_len < 32) {
7719304Speter			text_lfree(tiqh);
7819304Speter			goto newtp;
7919304Speter		}
8019304Speter		tp->len = 0;
8119304Speter	} else {
8219304Speternewtp:		if ((tp = text_init(sp, NULL, 0, 32)) == NULL)
8319304Speter			goto err;
84254225Speter		TAILQ_INSERT_HEAD(tiqh, tp, q);
8519304Speter	}
8619304Speter
8719304Speter	/* Set the starting line number. */
8819304Speter	tp->lno = sp->lno + 1;
8919304Speter
9019304Speter	/*
9119304Speter	 * If it's a terminal, set up autoindent, put out the prompt, and
9219304Speter	 * set it up so we know we were suspended.  Otherwise, turn off
9319304Speter	 * the autoindent flag, as that requires less special casing below.
9419304Speter	 *
9519304Speter	 * XXX
9619304Speter	 * Historic practice is that ^Z suspended command mode (but, because
9719304Speter	 * it ran in cooked mode, it was unaffected by the autowrite option.)
9819304Speter	 * On restart, any "current" input was discarded, whether in insert
9919304Speter	 * mode or not, and ex was in command mode.  This code matches historic
10019304Speter	 * practice, but not 'cause it's easier.
10119304Speter	 */
10219304Speter	gp = sp->gp;
10319304Speter	if (F_ISSET(gp, G_SCRIPTED))
10419304Speter		LF_CLR(TXT_AUTOINDENT);
10519304Speter	else {
10619304Speter		if (LF_ISSET(TXT_AUTOINDENT)) {
10719304Speter			LF_SET(TXT_EOFCHAR);
10819304Speter			if (v_txt_auto(sp, sp->lno, NULL, 0, tp))
10919304Speter				goto err;
11019304Speter		}
11119304Speter		txt_prompt(sp, tp, prompt, flags);
11219304Speter	}
11319304Speter
114254225Speter	for (carat_st = C_NOTSET, nochange = 0;;) {
11519304Speter		if (v_event_get(sp, &ev, 0, 0))
11619304Speter			goto err;
11719304Speter
11819304Speter		/* Deal with all non-character events. */
11919304Speter		switch (ev.e_event) {
12019304Speter		case E_CHARACTER:
12119304Speter			break;
12219304Speter		case E_ERR:
12319304Speter			goto err;
12419304Speter		case E_REPAINT:
12519304Speter		case E_WRESIZE:
12619304Speter			continue;
12719304Speter		case E_EOF:
12819304Speter			rval = 1;
12919304Speter			/* FALLTHROUGH */
13019304Speter		case E_INTERRUPT:
13119304Speter			/*
13219304Speter			 * Handle EOF/SIGINT events by discarding partially
13319304Speter			 * entered text and returning.  EOF returns failure,
13419304Speter			 * E_INTERRUPT returns success.
13519304Speter			 */
13619304Speter			goto notlast;
13719304Speter		default:
13819304Speter			v_event_err(sp, &ev);
13919304Speter			goto notlast;
14019304Speter		}
14119304Speter
14219304Speter		/*
14319304Speter		 * Deal with character events.
14419304Speter		 *
14519304Speter		 * Check to see if the character fits into the input buffer.
14619304Speter		 * (Use tp->len, ignore overwrite and non-printable chars.)
14719304Speter		 */
148254225Speter		BINC_GOTOW(sp, tp->lb, tp->lb_len, tp->len + 1);
14919304Speter
15019304Speter		switch (ev.e_value) {
15119304Speter		case K_CR:
15219304Speter			/*
15319304Speter			 * !!!
15419304Speter			 * Historically, <carriage-return>'s in the command
15519304Speter			 * weren't special, so the ex parser would return an
15619304Speter			 * unknown command error message.  However, if they
15719304Speter			 * terminated the command if they were in a map.  I'm
15819304Speter			 * pretty sure this still isn't right, but it handles
15919304Speter			 * what I've seen so far.
16019304Speter			 */
16119304Speter			if (!F_ISSET(&ev.e_ch, CH_MAPPED))
16219304Speter				goto ins_ch;
16319304Speter			/* FALLTHROUGH */
16419304Speter		case K_NL:
16519304Speter			/*
16619304Speter			 * '\' can escape <carriage-return>/<newline>.  We
16719304Speter			 * don't discard the backslash because we need it
16819304Speter			 * to get the <newline> through the ex parser.
16919304Speter			 */
17019304Speter			if (LF_ISSET(TXT_BACKSLASH) &&
17119304Speter			    tp->len != 0 && tp->lb[tp->len - 1] == '\\')
17219304Speter				goto ins_ch;
17319304Speter
17419304Speter			/*
17519304Speter			 * CR returns from the ex command line.
17619304Speter			 *
17719304Speter			 * XXX
17819304Speter			 * Terminate with a nul, needed by filter.
17919304Speter			 */
18019304Speter			if (LF_ISSET(TXT_CR)) {
18119304Speter				tp->lb[tp->len] = '\0';
18219304Speter				goto done;
18319304Speter			}
18419304Speter
18519304Speter			/*
18619304Speter			 * '.' may terminate text input mode; free the current
18719304Speter			 * TEXT.
18819304Speter			 */
18919304Speter			if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 &&
19019304Speter			    tp->lb[tp->len - 1] == '.') {
191254225Speternotlast:			TAILQ_REMOVE(tiqh, tp, q);
19219304Speter				text_free(tp);
19319304Speter				goto done;
19419304Speter			}
19519304Speter
19619304Speter			/* Set up bookkeeping for the new line. */
19719304Speter			if ((ntp = text_init(sp, NULL, 0, 32)) == NULL)
19819304Speter				goto err;
19919304Speter			ntp->lno = tp->lno + 1;
20019304Speter
20119304Speter			/*
20219304Speter			 * Reset the autoindent line value.  0^D keeps the ai
20319304Speter			 * line from changing, ^D changes the level, even if
20419304Speter			 * there were no characters in the old line.  Note, if
20519304Speter			 * using the current tp structure, use the cursor as
20619304Speter			 * the length, the autoindent characters may have been
20719304Speter			 * erased.
20819304Speter			 */
20919304Speter			if (LF_ISSET(TXT_AUTOINDENT)) {
210254225Speter				if (nochange) {
211254225Speter					nochange = 0;
21219304Speter					if (v_txt_auto(sp,
21319304Speter					    OOBLNO, &ait, ait.ai, ntp))
21419304Speter						goto err;
21519304Speter					free(ait.lb);
21619304Speter				} else
21719304Speter					if (v_txt_auto(sp,
21819304Speter					    OOBLNO, tp, tp->len, ntp))
21919304Speter						goto err;
22019304Speter				carat_st = C_NOTSET;
22119304Speter			}
22219304Speter			txt_prompt(sp, ntp, prompt, flags);
22319304Speter
22419304Speter			/*
22519304Speter			 * Swap old and new TEXT's, and insert the new TEXT
22619304Speter			 * into the queue.
22719304Speter			 */
22819304Speter			tp = ntp;
229254225Speter			TAILQ_INSERT_TAIL(tiqh, tp, q);
23019304Speter			break;
23119304Speter		case K_CARAT:			/* Delete autoindent chars. */
23219304Speter			if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT))
23319304Speter				carat_st = C_CARATSET;
23419304Speter			goto ins_ch;
23519304Speter		case K_ZERO:			/* Delete autoindent chars. */
23619304Speter			if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT))
23719304Speter				carat_st = C_ZEROSET;
23819304Speter			goto ins_ch;
23919304Speter		case K_CNTRLD:			/* Delete autoindent char. */
24019304Speter			/*
24119304Speter			 * !!!
24219304Speter			 * Historically, the ^D command took (but then ignored)
24319304Speter			 * a count.  For simplicity, we don't return it unless
24419304Speter			 * it's the first character entered.  The check for len
24519304Speter			 * equal to 0 is okay, TXT_AUTOINDENT won't be set.
24619304Speter			 */
24719304Speter			if (LF_ISSET(TXT_CNTRLD)) {
24819304Speter				for (cnt = 0; cnt < tp->len; ++cnt)
24919304Speter					if (!isblank(tp->lb[cnt]))
25019304Speter						break;
25119304Speter				if (cnt == tp->len) {
25219304Speter					tp->len = 1;
25319304Speter					tp->lb[0] = ev.e_c;
25419304Speter					tp->lb[1] = '\0';
25519304Speter
25619304Speter					/*
25719304Speter					 * Put out a line separator, in case
25819304Speter					 * the command fails.
25919304Speter					 */
26019304Speter					(void)putchar('\n');
26119304Speter					goto done;
26219304Speter				}
26319304Speter			}
26419304Speter
26519304Speter			/*
26619304Speter			 * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that
26719304Speter			 * the EOF characters are discarded if there are other
26819304Speter			 * characters to process in the line, i.e. if the EOF
26919304Speter			 * is not the first character in the line.  For this
27019304Speter			 * reason, historic ex discarded the EOF characters,
27119304Speter			 * even if occurring in the middle of the input line.
27219304Speter			 * We match that historic practice.
27319304Speter			 *
27419304Speter			 * !!!
27519304Speter			 * The test for discarding in the middle of the line is
27619304Speter			 * done in the switch, because the CARAT forms are N+1,
27719304Speter			 * not N.
27819304Speter			 *
27919304Speter			 * !!!
28019304Speter			 * There's considerable magic to make the terminal code
28119304Speter			 * return the EOF character at all.  See that code for
28219304Speter			 * details.
28319304Speter			 */
28419304Speter			if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0)
28519304Speter				continue;
28619304Speter			switch (carat_st) {
28719304Speter			case C_CARATSET:		/* ^^D */
28819304Speter				if (tp->len > tp->ai + 1)
28919304Speter					continue;
29019304Speter
29119304Speter				/* Save the ai string for later. */
29219304Speter				ait.lb = NULL;
29319304Speter				ait.lb_len = 0;
294254225Speter				BINC_GOTOW(sp, ait.lb, ait.lb_len, tp->ai);
295254225Speter				MEMCPY(ait.lb, tp->lb, tp->ai);
29619304Speter				ait.ai = ait.len = tp->ai;
29719304Speter
298254225Speter				carat_st = C_NOTSET;
299254225Speter				nochange = 1;
30019304Speter				goto leftmargin;
30119304Speter			case C_ZEROSET:			/* 0^D */
30219304Speter				if (tp->len > tp->ai + 1)
30319304Speter					continue;
30419304Speter
30519304Speter				carat_st = C_NOTSET;
30619304Speterleftmargin:			(void)gp->scr_ex_adjust(sp, EX_TERM_CE);
30719304Speter				tp->ai = tp->len = 0;
30819304Speter				break;
30919304Speter			case C_NOTSET:			/* ^D */
31019304Speter				if (tp->len > tp->ai)
31119304Speter					continue;
31219304Speter
31319304Speter				if (txt_dent(sp, tp))
31419304Speter					goto err;
31519304Speter				break;
31619304Speter			default:
31719304Speter				abort();
31819304Speter			}
31919304Speter
32019304Speter			/* Clear and redisplay the line. */
32119304Speter			(void)gp->scr_ex_adjust(sp, EX_TERM_CE);
32219304Speter			txt_prompt(sp, tp, prompt, flags);
32319304Speter			break;
32419304Speter		default:
32519304Speter			/*
32619304Speter			 * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c.
32719304Speter			 *
32819304Speter			 * Silently eliminate any iscntrl() character that was
32919304Speter			 * not already handled specially, except for <tab> and
33019304Speter			 * <ff>.
33119304Speter			 */
332254225Speterins_ch:			if (LF_ISSET(TXT_BEAUTIFY) && ISCNTRL(ev.e_c) &&
33319304Speter			    ev.e_value != K_FORMFEED && ev.e_value != K_TAB)
33419304Speter				break;
33519304Speter
33619304Speter			tp->lb[tp->len++] = ev.e_c;
33719304Speter			break;
33819304Speter		}
33919304Speter	}
34019304Speter	/* NOTREACHED */
34119304Speter
34219304Speterdone:	return (rval);
34319304Speter
34419304Spetererr:
34519304Speteralloc_err:
34619304Speter	return (1);
34719304Speter}
34819304Speter
34919304Speter/*
35019304Speter * txt_prompt --
35119304Speter *	Display the ex prompt, line number, ai characters.  Characters had
35219304Speter *	better be printable by the terminal driver, but that's its problem,
35319304Speter *	not ours.
35419304Speter */
35519304Speterstatic void
356254225Spetertxt_prompt(SCR *sp, TEXT *tp, ARG_CHAR_T prompt, u_int32_t flags)
35719304Speter{
35819304Speter	/* Display the prompt. */
35919304Speter	if (LF_ISSET(TXT_PROMPT))
360254225Speter		(void)ex_printf(sp, "%c", prompt);
36119304Speter
36219304Speter	/* Display the line number. */
36319304Speter	if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER))
364254225Speter		(void)ex_printf(sp, "%6lu  ", (u_long)tp->lno);
36519304Speter
36619304Speter	/* Print out autoindent string. */
36719304Speter	if (LF_ISSET(TXT_AUTOINDENT))
368254225Speter		(void)ex_printf(sp, WVS, (int)tp->ai, tp->lb);
369254225Speter	(void)ex_fflush(sp);
37019304Speter}
37119304Speter
37219304Speter/*
37319304Speter * txt_dent --
37419304Speter *	Handle ^D outdents.
37519304Speter *
37619304Speter * Ex version of vi/v_ntext.c:txt_dent().  See that code for the (usual)
37719304Speter * ranting and raving.  This is a fair bit simpler as ^T isn't special.
37819304Speter */
37919304Speterstatic int
380254225Spetertxt_dent(SCR *sp, TEXT *tp)
38119304Speter{
38219304Speter	u_long sw, ts;
38319304Speter	size_t cno, off, scno, spaces, tabs;
38419304Speter
38519304Speter	ts = O_VAL(sp, O_TABSTOP);
38619304Speter	sw = O_VAL(sp, O_SHIFTWIDTH);
38719304Speter
38819304Speter	/* Get the current screen column. */
38919304Speter	for (off = scno = 0; off < tp->len; ++off)
39019304Speter		if (tp->lb[off] == '\t')
39119304Speter			scno += COL_OFF(scno, ts);
39219304Speter		else
39319304Speter			++scno;
39419304Speter
39519304Speter	/* Get the previous shiftwidth column. */
396246874Sdim	cno = scno--;
397246874Sdim	scno -= scno % sw;
39819304Speter
39919304Speter	/*
40019304Speter	 * Since we don't know what comes before the character(s) being
40119304Speter	 * deleted, we have to resolve the autoindent characters .  The
40219304Speter	 * example is a <tab>, which doesn't take up a full shiftwidth
40319304Speter	 * number of columns because it's preceded by <space>s.  This is
40419304Speter	 * easy to get if the user sets shiftwidth to a value less than
40519304Speter	 * tabstop, and then uses ^T to indent, and ^D to outdent.
40619304Speter	 *
40719304Speter	 * Count up spaces/tabs needed to get to the target.
40819304Speter	 */
40919304Speter	for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs)
41019304Speter		cno += COL_OFF(cno, ts);
41119304Speter	spaces = scno - cno;
41219304Speter
41319304Speter	/* Make sure there's enough room. */
414254225Speter	BINC_RETW(sp, tp->lb, tp->lb_len, tabs + spaces + 1);
41519304Speter
41619304Speter	/* Adjust the final ai character count. */
41719304Speter	tp->ai = tabs + spaces;
41819304Speter
41919304Speter	/* Enter the replacement characters. */
42019304Speter	for (tp->len = 0; tabs > 0; --tabs)
42119304Speter		tp->lb[tp->len++] = '\t';
42219304Speter	for (; spaces > 0; --spaces)
42319304Speter		tp->lb[tp->len++] = ' ';
42419304Speter	return (0);
42519304Speter}
426