1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29
30/* Copyright (c) 1981 Regents of the University of California */
31
32#include "ex.h"
33#include "ex_tty.h"
34#include "ex_vis.h"
35#ifndef PRESUNEUC
36#include <wctype.h>
37/* Undef putchar/getchar if they're defined. */
38#ifdef putchar
39#	undef putchar
40#endif
41#ifdef getchar
42#	undef getchar
43#endif
44#endif /* PRESUNEUC */
45
46/*
47 * This is the main routine for visual.
48 * We here decode the count and possible named buffer specification
49 * preceding a command and interpret a few of the commands.
50 * Commands which involve a target (i.e. an operator) are decoded
51 * in the routine operate in ex_voperate.c.
52 */
53
54#define	forbid(a)	{ if (a) goto fonfon; }
55
56extern int windowchg;
57extern int sigok;
58#ifdef XPG6
59int redisplay;	/* XPG6 assertion 313 [count]r\n :  Also used in ex_vops2.c */
60#endif
61void redraw(), windowinit();
62
63#ifdef XPG4
64extern int P_cursor_offset;
65#endif
66
67void
68vmain(void)
69{
70	int c, cnt, i;
71	wchar_t esave[TUBECOLS];
72	extern wchar_t atube[];
73	unsigned char *oglobp;
74	short d;
75	line *addr;
76	int ind, nlput;
77	int shouldpo = 0;
78	int tag_reset_wrap = 0;
79	int onumber, olist, (*OPline)(), (*OPutchar)();
80
81
82	vch_mac = VC_NOTINMAC;
83	ixlatctl(0);
84
85	/*
86	 * If we started as a vi command (on the command line)
87	 * then go process initial commands (recover, next or tag).
88	 */
89	if (initev) {
90		oglobp = globp;
91		globp = initev;
92		hadcnt = cnt = 0;
93		i = tchng;
94		addr = dot;
95		goto doinit;
96	}
97
98	vshowmode("");		/* As a precaution */
99	/*
100	 * NB:
101	 *
102	 * The current line is always in the line buffer linebuf,
103	 * and the cursor at the position cursor.  You should do
104	 * a vsave() before moving off the line to make sure the disk
105	 * copy is updated if it has changed, and a getDOT() to get
106	 * the line back if you mung linebuf.  The motion
107	 * routines in ex_vwind.c handle most of this.
108	 */
109	for (;;) {
110		/*
111		 * Decode a visual command.
112		 * First sync the temp file if there has been a reasonable
113		 * amount of change.  Clear state for decoding of next
114		 * command.
115		 */
116		TSYNC();
117		vglobp = 0;
118		vreg = 0;
119		hold = 0;
120		seenprompt = 1;
121		wcursor = 0;
122		Xhadcnt = hadcnt = 0;
123		Xcnt = cnt = 1;
124		splitw = 0;
125		if (i = holdupd && !windowchg) {
126			if (state == VISUAL) {
127				sigok = 1;
128				(void)peekkey();
129				sigok = 0;
130			}
131
132			holdupd = 0;
133/*
134			if (LINE(0) < ZERO) {
135				vclear();
136				vcnt = 0;
137				i = 3;
138			}
139*/
140			if (state != VISUAL) {
141				vcnt = 0;
142				vsave();
143				vrepaint(cursor);
144			} else if (i == 3)
145				vredraw(WTOP);
146			else
147				vsync(WTOP);
148			vfixcurs();
149		} else if(windowchg)
150			redraw();
151
152#ifdef XPG6
153		if (redisplay) {
154			/* XPG6 assertion 313 & 254 : after [count]r\n */
155			fixdisplay();
156		}
157		redisplay = 0;
158#endif
159		/*
160		 * Gobble up counts and named buffer specifications.
161		 */
162		for (;;) {
163looptop:
164#ifdef MDEBUG
165			if (trace)
166				fprintf(trace, "pc=%c",peekkey());
167#endif
168			sigok = 1;
169			c = peekkey();
170			sigok = 0;
171			if (isdigit(peekkey()) && peekkey() != '0') {
172				hadcnt = 1;
173				cnt = vgetcnt();
174				forbid (cnt <= 0);
175			}
176			if (peekkey() != '"')
177				break;
178			(void)getkey(), c = getkey();
179			/*
180			 * Buffer names be letters or digits.
181			 * But not '0' as that is the source of
182			 * an 'empty' named buffer spec in the routine
183			 * kshift (see ex_temp.c).
184			 */
185			if(!isascii(c) && MB_CUR_MAX > 1) {
186				/* get rest of character */
187				wchar_t wchar;
188				char multic[MULTI_BYTE_MAX];
189				ungetkey(c);
190				(void)_mbftowc(multic, &wchar, getkey, &Peekkey);
191			}
192			forbid (c == '0' || !isalpha(c) && !isascii(c) && !isdigit(c));
193			vreg = c;
194		}
195reread:
196		/*
197		 * Come to reread from below after some macro expansions.
198		 * The call to map allows use of function key pads
199		 * by performing a terminal dependent mapping of inputs.
200		 */
201#ifdef MDEBUG
202		if (trace)
203			fprintf(trace,"pcb=%c,",peekkey());
204#endif
205		op = getkey();
206		maphopcnt = 0;
207		do {
208			/*
209			 * Keep mapping the char as long as it changes.
210			 * This allows for double mappings, e.g., q to #,
211			 * #1 to something else.
212			 */
213			c = op;
214			op = map(c, arrows, 0);
215#ifdef MDEBUG
216			if (trace)
217				fprintf(trace,"pca=%c,",c);
218#endif
219			/*
220			 * Maybe the mapped to char is a count. If so, we have
221			 * to go back to the "for" to interpret it. Likewise
222			 * for a buffer name.
223			 */
224			if ((isdigit(c) && c!='0') || c == '"') {
225				ungetkey(c);
226				goto looptop;
227			}
228			if (!value(vi_REMAP)) {
229				c = op;
230				break;
231			}
232			if (++maphopcnt > 256)
233				error(gettext("Infinite macro loop"));
234		} while (c != op);
235
236		/*
237		 * Begin to build an image of this command for possible
238		 * later repeat in the buffer workcmd.  It will be copied
239		 * to lastcmd by the routine setLAST
240		 * if/when completely specified.
241		 */
242		lastcp = workcmd;
243		if (!vglobp)
244			*lastcp++ = c;
245
246		/*
247		 * First level command decode.
248		 */
249		switch (c) {
250
251		/*
252		 * ^L		Clear screen e.g. after transmission error.
253		 */
254
255		/*
256		 * ^R		Retype screen, getting rid of @ lines.
257		 *		If in open, equivalent to ^L.
258		 *		On terminals where the right arrow key sends
259		 *		^L we make ^R act like ^L, since there is no
260		 *		way to get ^L.  These terminals (adm31, tvi)
261		 *		are intelligent so ^R is useless.  Soroc
262		 *		will probably foul this up, but nobody has
263		 *		one of them.
264		 */
265		case CTRL('l'):
266		case CTRL('r'):
267			if (c == CTRL('l') || (key_right && *key_right==CTRL('l'))) {
268				vclear();
269				vdirty(0, vcnt);
270			}
271			if (state != VISUAL) {
272				/*
273				 * Get a clean line, throw away the
274				 * memory of what is displayed now,
275				 * and move back onto the current line.
276				 */
277				vclean();
278				vcnt = 0;
279				vmoveto(dot, cursor, 0);
280				continue;
281			}
282			vredraw(WTOP);
283			/*
284			 * Weird glitch -- when we enter visual
285			 * in a very small window we may end up with
286			 * no lines on the screen because the line
287			 * at the top is too long.  This forces the screen
288			 * to be expanded to make room for it (after
289			 * we have printed @'s ick showing we goofed).
290			 */
291			if (vcnt == 0)
292				vrepaint(cursor);
293			vfixcurs();
294			continue;
295
296		/*
297		 * $		Escape just cancels the current command
298		 *		with a little feedback.
299		 */
300		case ESCAPE:
301			(void) beep();
302			continue;
303
304		/*
305		 * @   		Macros. Bring in the macro and put it
306		 *		in vmacbuf, point vglobp there and punt.
307		 */
308		 case '@':
309			c = getesc();
310			if (c == 0)
311				continue;
312			if (c == '@')
313				c = lastmac;
314			if (isupper(c))
315				c = tolower(c);
316			forbid(!islower(c));
317			lastmac = c;
318			vsave();
319			CATCH
320				unsigned char tmpbuf[BUFSIZE];
321
322				regbuf(c, tmpbuf, sizeof (vmacbuf));
323				macpush(tmpbuf, 1);
324			ONERR
325				lastmac = 0;
326				splitw = 0;
327				getDOT();
328				vrepaint(cursor);
329				continue;
330			ENDCATCH
331			vmacp = vmacbuf;
332			goto reread;
333
334		/*
335		 * .		Repeat the last (modifying) open/visual command.
336		 */
337		case '.':
338			/*
339			 * Check that there was a last command, and
340			 * take its count and named buffer unless they
341			 * were given anew.  Special case if last command
342			 * referenced a numeric named buffer -- increment
343			 * the number and go to a named buffer again.
344			 * This allows a sequence like "1pu.u.u...
345			 * to successively look for stuff in the kill chain
346			 * much as one does in EMACS with C-Y and M-Y.
347			 */
348			forbid (lastcmd[0] == 0);
349			if (hadcnt)
350				lastcnt = cnt;
351			if (vreg)
352				lastreg = vreg;
353			else if (isdigit(lastreg) && lastreg < '9')
354				lastreg++;
355			vreg = lastreg;
356			cnt = lastcnt;
357			hadcnt = lasthad;
358			vglobp = lastcmd;
359			goto reread;
360
361		/*
362		 * ^U		Scroll up.  A count sticks around for
363		 *		future scrolls as the scroll amount.
364		 *		Attempt to hold the indentation from the
365		 *		top of the screen (in logical lines).
366		 *
367		 * BUG:		A ^U near the bottom of the screen
368		 *		on a dumb terminal (which can't roll back)
369		 *		causes the screen to be cleared and then
370		 *		redrawn almost as it was.  In this case
371		 *		one should simply move the cursor.
372		 */
373		case CTRL('u'):
374			if (hadcnt)
375				vSCROLL = cnt;
376			cnt = vSCROLL;
377			if (state == VISUAL)
378				ind = vcline, cnt += ind;
379			else
380				ind = 0;
381			vmoving = 0;
382			vup(cnt, ind, 1);
383			vnline((unsigned char *)NOSTR);
384			continue;
385
386		/*
387		 * ^D		Scroll down.  Like scroll up.
388		 */
389		case CTRL('d'):
390#ifdef TRACE
391		if (trace)
392			fprintf(trace, "before vdown in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
393#endif
394			if (hadcnt)
395				vSCROLL = cnt;
396			cnt = vSCROLL;
397			if (state == VISUAL)
398				ind = vcnt - vcline - 1, cnt += ind;
399			else
400				ind = 0;
401			vmoving = 0;
402			vdown(cnt, ind, 1);
403#ifdef TRACE
404		if (trace)
405			fprintf(trace, "before vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
406#endif
407			vnline((unsigned char *)NOSTR);
408#ifdef TRACE
409		if (trace)
410			fprintf(trace, "after vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
411#endif
412			continue;
413
414		/*
415		 * ^E		Glitch the screen down (one) line.
416		 *		Cursor left on same line in file.
417		 */
418		case CTRL('e'):
419			if (state != VISUAL)
420				continue;
421			if (!hadcnt)
422				cnt = 1;
423			/* Bottom line of file already on screen */
424			forbid(lineDOL()-lineDOT() <= vcnt-1-vcline);
425			ind = vcnt - vcline - 1 + cnt;
426			vdown(ind, ind, 1);
427			vnline(cursor);
428			continue;
429
430		/*
431		 * ^Y		Like ^E but up
432		 */
433		case CTRL('y'):
434			if (state != VISUAL)
435				continue;
436			if (!hadcnt)
437				cnt = 1;
438			forbid(lineDOT()-1<=vcline); /* line 1 already there */
439			ind = vcline + cnt;
440			vup(ind, ind, 1);
441			vnline(cursor);
442			continue;
443
444
445		/*
446		 * m		Mark position in mark register given
447		 *		by following letter.  Return is
448		 *		accomplished via ' or `; former
449		 *		to beginning of line where mark
450		 *		was set, latter to column where marked.
451		 */
452		case 'm':
453			/*
454			 * Getesc is generally used when a character
455			 * is read as a latter part of a command
456			 * to allow one to hit rubout/escape to cancel
457			 * what you have typed so far.  These characters
458			 * are mapped to 0 by the subroutine.
459			 */
460			c = getesc();
461			if (c == 0)
462				continue;
463
464			/*
465			 * Markreg checks that argument is a letter
466			 * and also maps ' and ` to the end of the range
467			 * to allow '' or `` to reference the previous
468			 * context mark.
469			 */
470			c = markreg(c);
471			forbid (c == 0);
472			vsave();
473			names[c - 'a'] = (*dot &~ 01);
474			ncols[c - 'a'] = cursor;
475			anymarks = 1;
476			continue;
477
478		/*
479		 * ^F		Window forwards, with 2 lines of continuity.
480		 *		Count repeats.
481		 */
482		case CTRL('f'):
483			vsave();
484			if (vcnt > 2) {
485				addr = dot + (vcnt - vcline) - 2 + (cnt-1)*basWLINES;
486				forbid(addr > dol);
487				dot = addr;
488				vcnt = vcline = 0;
489			}
490			vzop(0, 0, '+');
491			continue;
492
493		/*
494		 * ^B		Window backwards, with 2 lines of continuity.
495		 *		Inverse of ^F.
496		 */
497		case CTRL('b'):
498			vsave();
499			if (one + vcline != dot && vcnt > 2) {
500				addr = dot - vcline + 2 - (cnt-1)*basWLINES;
501				forbid (addr <= zero);
502				dot = addr;
503				vcnt = vcline = 0;
504			}
505			vzop(0, 0, '^');
506			continue;
507
508		/*
509		 * z		Screen adjustment, taking a following character:
510		 *			zcarriage_return		current line to top
511		 *			z<NL>		like zcarriage_return
512		 *			z-		current line to bottom
513		 *		also z+, z^ like ^F and ^B.
514		 *		A preceding count is line to use rather
515		 *		than current line.  A count between z and
516		 *		specifier character changes the screen size
517		 *		for the redraw.
518		 *
519		 */
520		case 'z':
521			if (state == VISUAL) {
522				i = vgetcnt();
523				if (i > 0)
524					vsetsiz(i);
525				c = getesc();
526				if (c == 0)
527					continue;
528			}
529			vsave();
530			vzop(hadcnt, cnt, c);
531			continue;
532
533		/*
534		 * Y		Yank lines, abbreviation for y_ or yy.
535		 *		Yanked lines can be put later if no
536		 *		changes intervene, or can be put in named
537		 *		buffers and put anytime in this session.
538		 */
539		case 'Y':
540			ungetkey('_');
541			c = 'y';
542			break;
543
544		/*
545		 * J		Join lines, 2 by default.  Count is number
546		 *		of lines to join (no join operator sorry.)
547		 */
548		case 'J':
549			forbid (dot == dol);
550			if (cnt == 1)
551				cnt = 2;
552			if (cnt > (i = dol - dot + 1))
553				cnt = i;
554			vsave();
555			vmacchng(1);
556			setLAST();
557			cursor = strend(linebuf);
558			vremote(cnt, join, 0);
559			notenam = (unsigned char *)"join";
560			vmoving = 0;
561			killU();
562			vreplace(vcline, cnt, 1);
563			if (!*cursor && cursor > linebuf)
564				cursor--;
565			if (notecnt == 2)
566				notecnt = 0;
567			vrepaint(cursor);
568			continue;
569
570		/*
571		 * S		Substitute text for whole lines, abbrev for c_.
572		 *		Count is number of lines to change.
573		 */
574		case 'S':
575			ungetkey('_');
576			c = 'c';
577			break;
578
579		/*
580		 * O		Create a new line above current and accept new
581		 *		input text, to an escape, there.
582		 *		A count specifies, for dumb terminals when
583		 *		slowopen is not set, the number of physical
584		 *		line space to open on the screen.
585		 *
586		 * o		Like O, but opens lines below.
587		 */
588		case 'O':
589		case 'o':
590			vmacchng(1);
591			voOpen(c, cnt);
592			continue;
593
594		/*
595		 * C		Change text to end of line, short for c$.
596		 */
597		case 'C':
598			if (*cursor) {
599				ungetkey('$'), c = 'c';
600				break;
601			}
602			goto appnd;
603
604		/*
605		 * ~	Switch case of letter under cursor
606		 */
607		case '~':
608			{
609				unsigned char mbuf[2049];
610				unsigned char *ccursor = cursor;
611#ifdef PRESUNEUC
612				int tmp, length;
613				wchar_t wchar;
614				unsigned char tmp1;
615#else
616				int tmp, len;
617				wchar_t wc;
618#endif /* PRESUNEUC */
619				setLAST();
620				for (tmp = 0; tmp + 3 < 2048; ) {
621				/*
622				 * Use multiple 'r' commands to replace
623				 * alpha with alternate case.
624				 */
625
626					if(cnt-- <= 0)
627						break;
628#ifdef PRESUNEUC
629					length = mbtowc(&wchar, (char *)ccursor, MULTI_BYTE_MAX);
630#else
631					len = mbtowc(&wc, (char *)ccursor, MULTI_BYTE_MAX);
632#endif /* PRESUNEUC */
633#ifdef PRESUNEUC
634					if(length > 1) {
635#else
636					if(len > 1 && !iswalpha(wc)) {
637#endif /* PRESUNEUC */
638						mbuf[tmp+0] = ' ';
639						tmp++;
640#ifdef PRESUNEUC
641						ccursor += length;
642#else
643						ccursor += len;
644#endif /* PRESUNEUC */
645						continue;
646					}
647					mbuf[tmp] = 'r';
648#ifdef PRESUNEUC
649					mbuf[tmp+1] = *ccursor++;
650#else
651					ccursor += ((len > 0) ? len : 1);
652#endif /* PRESUNEUC */
653				/*
654				 * If pointing to an alpha character,
655				 * change the case.
656				 */
657
658#ifdef PRESUNEUC
659					tmp1 = mbuf[tmp+1];
660					if (isupper((unsigned char)tmp1))
661						mbuf[tmp+1] = tolower((unsigned char)tmp1);
662					else
663						mbuf[tmp+1] = toupper((unsigned char)tmp1);
664#else
665					if (iswupper(wc))
666						len = wctomb((char *)(mbuf + tmp + 1),
667							(wc = towlower(wc)));
668					else
669						len = wctomb((char *)(mbuf + tmp + 1),
670							(wc = towupper(wc)));
671					tmp += len - 1;
672#endif /* PRESUNEUC */
673					if(*ccursor)
674				/*
675				 * If at end of line do not advance
676				 * to the next character, else use a
677				 * space to advance 1 column.
678				 */
679						mbuf[tmp+2] = ' ';
680					else {
681						mbuf[tmp+2] = '\0';
682						tmp +=3;
683						break;
684					}
685					tmp += 3;
686				}
687
688				mbuf[tmp] = 0;
689				macpush(mbuf, 1);
690			}
691			continue;
692
693
694		/*
695		 * A		Append at end of line, short for $a.
696		 */
697		case 'A':
698			operate('$', 1);
699appnd:
700			c = 'a';
701			/* FALLTHROUGH */
702
703		/*
704		 * a		Appends text after cursor.  Text can continue
705		 *		through arbitrary number of lines.
706		 */
707		case 'a':
708			if (*cursor) {
709				wchar_t wchar;
710				int length = mbtowc(&wchar, (char *)cursor, MULTI_BYTE_MAX);
711				if (state == HARDOPEN) {
712					if(length < 0) {
713						putoctal = 1;
714						putchar(*cursor);
715						putoctal = 0;
716					} else
717						putchar(wchar);
718				}
719				if(length < 0)
720					cursor++;
721				else
722					cursor += length;
723			}
724			goto insrt;
725
726		/*
727		 * I		Insert at beginning of whitespace of line,
728		 *		short for ^i.
729		 */
730		case 'I':
731			operate('^', 1);
732			c = 'i';
733			/* FALLTHROUGH */
734
735		/*
736		 * R		Replace characters, one for one, by input
737		 *		(logically), like repeated r commands.
738		 *
739		 * BUG:		This is like the typeover mode of many other
740		 *		editors, and is only rarely useful.  Its
741		 *		implementation is a hack in a low level
742		 *		routine and it doesn't work very well, e.g.
743		 *		you can't move around within a R, etc.
744		 */
745		case 'R':
746			/* FALLTHROUGH */
747
748		/*
749		 * i		Insert text to an escape in the buffer.
750		 *		Text is arbitrary.  This command reminds of
751		 *		the i command in bare teco.
752		 */
753		case 'i':
754insrt:
755			/*
756			 * Common code for all the insertion commands.
757			 * Save for redo, position cursor, prepare for append
758			 * at command and in visual undo.  Note that nothing
759			 * is doomed, unless R when all is, and save the
760			 * current line in a the undo temporary buffer.
761			 */
762			vmacchng(1);
763			setLAST();
764			vcursat(cursor);
765			prepapp();
766			vnoapp();
767			doomed = c == 'R' ? 10000 : 0;
768			if(FIXUNDO)
769				vundkind = VCHNG;
770			vmoving = 0;
771			CP(vutmp, linebuf);
772
773			/*
774			 * If this is a repeated command, then suppress
775			 * fake insert mode on dumb terminals which looks
776			 * ridiculous and wastes lots of time even at 9600B.
777			 */
778			if (vglobp)
779				hold = HOLDQIK;
780			vappend(c, cnt, 0);
781			continue;
782
783		/*
784		 * 	An attention, normally a DEL, just beeps.
785		 *	If you are a vi command within ex, then
786		 *	two ATTN's will drop you back to command mode.
787		 */
788		case ATTN:
789			(void) beep();
790			if (initev || peekkey() != ATTN)
791				continue;
792			/* FALLTHROUGH */
793
794		/*
795		 * ^\		A quit always gets command mode.
796		 */
797		case QUIT:
798			/*
799			 * Have to be careful if we were called
800			 *	g/xxx/vi
801			 * since a return will just start up again.
802			 * So we simulate an interrupt.
803			 */
804			if (inglobal)
805				onintr(0);
806			/* fall into... */
807
808#ifdef notdef
809		/*
810		 * q		Quit back to command mode, unless called as
811		 *		vi on command line in which case dont do it
812		 */
813		case 'q':	/* quit */
814			if (initev) {
815				vsave();
816				CATCH
817					error(gettext("Q gets ex command mode, :q leaves vi"));
818				ENDCATCH
819				splitw = 0;
820				getDOT();
821				vrepaint(cursor);
822				continue;
823			}
824#endif
825			/* FALLTHROUGH */
826
827		/*
828		 * Q		Is like q, but always gets to command mode
829		 *		even if command line invocation was as vi.
830		 */
831		case 'Q':
832			vsave();
833			/*
834			 * If we are in the middle of a macro, throw away
835			 * the rest and fix up undo.
836			 * This code copied from getbr().
837			 */
838			if (vmacp) {
839				vmacp = 0;
840				if (inopen == -1)	/* don't mess up undo for esc esc */
841					vundkind = VMANY;
842				inopen = 1;	/* restore old setting now that macro done */
843			}
844			ixlatctl(1);
845			return;
846
847
848		/*
849		 * ZZ		Like :x
850		 */
851		 case 'Z':
852			forbid(getkey() != 'Z');
853			oglobp = globp;
854			globp = (unsigned char *)"x";
855			vclrech(0);
856			goto gogo;
857
858		/*
859		 * P		Put back text before cursor or before current
860		 *		line.  If text was whole lines goes back
861		 *		as whole lines.  If part of a single line
862		 *		or parts of whole lines splits up current
863		 *		line to form many new lines.
864		 *		May specify a named buffer, or the delete
865		 *		saving buffers 1-9.
866		 *
867		 * p		Like P but after rather than before.
868		 */
869		case 'P':
870		case 'p':
871			vmoving = 0;
872#ifdef XPG4
873			P_cursor_offset = 0;
874#endif
875#ifdef notdef
876			forbid (!vreg && value(vi_UNDOMACRO) && inopen < 0);
877#endif
878			/*
879			 * If previous delete was partial line, use an
880			 * append or insert to put it back so as to
881			 * use insert mode on intelligent terminals.
882			 */
883			if (!vreg && DEL[0]) {
884				setLAST();
885				forbid ((unsigned char)DEL[128] == 0200);
886				vglobp = DEL;
887				ungetkey(c == 'p' ? 'a' : 'i');
888				goto reread;
889			}
890
891			/*
892			 * If a register wasn't specified, then make
893			 * sure there is something to put back.
894			 */
895			forbid (!vreg && unddol == dol);
896			/*
897			 * If we just did a macro the whole buffer is in
898			 * the undo save area.  We don't want to put THAT.
899			 */
900			forbid (vundkind == VMANY && undkind==UNDALL);
901			vsave();
902			vmacchng(1);
903			setLAST();
904			i = 0;
905			if (vreg && partreg(vreg) || !vreg && pkill[0]) {
906				/*
907				 * Restoring multiple lines which were partial
908				 * lines; will leave cursor in middle
909				 * of line after shoving restored text in to
910				 * split the current line.
911				 */
912				i++;
913				if (c == 'p' && *cursor)
914					cursor = nextchr(cursor);
915			} else {
916				/*
917				 * In whole line case, have to back up dot
918				 * for P; also want to clear cursor so
919				 * cursor will eventually be positioned
920				 * at the beginning of the first put line.
921				 */
922				cursor = 0;
923				if (c == 'P') {
924					dot--, vcline--;
925					c = 'p';
926				}
927			}
928			killU();
929
930			/*
931			 * The call to putreg can potentially
932			 * bomb since there may be nothing in a named buffer.
933			 * We thus put a catch in here.  If we didn't and
934			 * there was an error we would end up in command mode.
935			 */
936			addr = dol;	/* old dol */
937			CATCH
938				vremote(1,
939				    vreg ? (int (*)())putreg : put, vreg);
940			ONERR
941				if (vreg == -1) {
942					splitw = 0;
943					if (op == 'P')
944						dot++, vcline++;
945					goto pfixup;
946				}
947			ENDCATCH
948			splitw = 0;
949			nlput = dol - addr + 1;
950			if (!i) {
951				/*
952				 * Increment undap1, undap2 to make up
953				 * for their incorrect initialization in the
954				 * routine vremote before calling put/putreg.
955				 */
956				if (FIXUNDO)
957					undap1++, undap2++;
958				vcline++;
959				nlput--;
960
961				/*
962				 * After a put want current line first line,
963				 * and dot was made the last line put in code
964				 * run so far.  This is why we increment vcline
965				 * above and decrease dot here.
966				 */
967				dot -= nlput - 1;
968			}
969#ifdef TRACE
970			if (trace)
971				fprintf(trace, "vreplace(%d, %d, %d), undap1=%d, undap2=%d, dot=%d\n", vcline, i, nlput, lineno(undap1), lineno(undap2), lineno(dot));
972#endif
973			vreplace(vcline, i, nlput);
974#ifdef XPG4
975			if (op == 'P' && i > 0) {
976				dot += nlput - 1;
977				vcline += nlput - 1;
978				cursor += P_cursor_offset;
979			}
980#endif
981			if (state != VISUAL) {
982				/*
983				 * Special case in open mode.
984				 * Force action on the screen when a single
985				 * line is put even if it is identical to
986				 * the current line, e.g. on YP; otherwise
987				 * you can't tell anything happened.
988				 */
989				vjumpto(dot, cursor, '.');
990				continue;
991			}
992pfixup:
993			vrepaint(cursor);
994			vfixcurs();
995			continue;
996
997		/*
998		 * ^^		Return to previous file.
999		 *		Like a :e #, and thus can be used after a
1000		 *		"No Write" diagnostic.
1001		 */
1002		case CTRL('^'):
1003			forbid (hadcnt);
1004			vsave();
1005			ckaw();
1006			oglobp = globp;
1007			if (value(vi_AUTOWRITE) && !value(vi_READONLY))
1008				globp = (unsigned char *)"e! #";
1009			else
1010				globp = (unsigned char *)"e #";
1011			goto gogo;
1012
1013#ifdef TAG_STACK
1014                /*
1015                 * ^T           Pop the tag stack if enabled or else reset it
1016                 *              if not.
1017                 */
1018                case CTRL('t'):
1019                        forbid (hadcnt);
1020                        vsave();
1021                        oglobp = globp;
1022                        globp = (unsigned char *) "pop";
1023                        goto gogo;
1024#endif
1025		/*
1026		 * ^]		Takes word after cursor as tag, and then does
1027		 *		tag command.  Read ``go right to''.
1028		 *		This is not a search, so the wrapscan setting
1029		 *		must be ignored.  If set, then it is unset
1030		 *		here and restored later.
1031		 */
1032		case CTRL(']'):
1033			grabtag();
1034			oglobp = globp;
1035			if (value(vi_WRAPSCAN) == 0) {
1036				tag_reset_wrap = 1;
1037				value(vi_WRAPSCAN) = 1;
1038			}
1039			globp = (unsigned char *)"tag";
1040			goto gogo;
1041
1042		/*
1043		 * &		Like :&
1044		 */
1045		 case '&':
1046			oglobp = globp;
1047			globp = (unsigned char *)"&";
1048			goto gogo;
1049
1050		/*
1051		 * ^G		Bring up a status line at the bottom of
1052		 *		the screen, like a :file command.
1053		 *
1054		 * BUG:		Was ^S but doesn't work in cbreak mode
1055		 */
1056		case CTRL('g'):
1057			oglobp = globp;
1058			globp = (unsigned char *)"file";
1059gogo:
1060			addr = dot;
1061			vsave();
1062			goto doinit;
1063
1064#ifdef SIGTSTP
1065		/*
1066		 * ^Z:	suspend editor session and temporarily return
1067		 * 	to shell.  Only works with Berkeley/IIASA process
1068		 *	control in kernel.
1069		 */
1070		case CTRL('z'):
1071			forbid(dosusp == 0);
1072			vsave();
1073			oglobp = globp;
1074			globp = (unsigned char *)"stop";
1075			goto gogo;
1076#endif
1077
1078		/*
1079		 * :		Read a command from the echo area and
1080		 *		execute it in command mode.
1081		 */
1082		case ':':
1083			forbid (hadcnt);
1084			vsave();
1085			i = tchng;
1086			addr = dot;
1087			if (readecho(c)) {
1088				esave[0] = 0;
1089				goto fixup;
1090			}
1091			getDOT();
1092			/*
1093			 * Use the visual undo buffer to store the global
1094			 * string for command mode, since it is idle right now.
1095			 */
1096			oglobp = globp; strcpy(vutmp, genbuf+1); globp = vutmp;
1097doinit:
1098			esave[0] = 0;
1099			fixech();
1100
1101			/*
1102			 * Have to finagle around not to lose last
1103			 * character after this command (when run from ex
1104			 * command mode).  This is clumsy.
1105			 */
1106			d = peekc; ungetchar(0);
1107			if (shouldpo) {
1108				/*
1109				 * So after a "Hit return..." ":", we do
1110				 * another "Hit return..." the next time
1111				 */
1112				pofix();
1113				shouldpo = 0;
1114			}
1115			CATCH
1116				/*
1117				 * Save old values of options so we can
1118				 * notice when they change; switch into
1119				 * cooked mode so we are interruptible.
1120				 */
1121				onumber = value(vi_NUMBER);
1122				olist = value(vi_LIST);
1123				OPline = Pline;
1124				OPutchar = Putchar;
1125#ifndef CBREAK
1126				vcook();
1127#endif
1128				commands(1, 1);
1129				if (dot == zero && dol > zero)
1130					dot = one;
1131#ifndef CBREAK
1132				vraw();
1133#endif
1134			ONERR
1135#ifndef CBREAK
1136				vraw();
1137#endif
1138				copy(esave, vtube[WECHO], TUBECOLS * sizeof(wchar_t));
1139			ENDCATCH
1140			fixol();
1141			Pline = OPline;
1142			Putchar = OPutchar;
1143			ungetchar(d);
1144			globp = oglobp;
1145
1146			/*
1147			 * If we ended up with no lines in the buffer, make
1148			 * a line.
1149			 */
1150			if (dot == zero) {
1151				fixzero();
1152			}
1153			splitw = 0;
1154
1155			/*
1156			 * Special case: did list/number options change?
1157			 */
1158			if (onumber != value(vi_NUMBER))
1159				setnumb(value(vi_NUMBER));
1160			if (olist != value(vi_LIST))
1161				setlist(value(vi_LIST));
1162
1163fixup:
1164			/*
1165			 * If a change occurred, other than
1166			 * a write which clears changes, then
1167			 * we should allow an undo even if .
1168			 * didn't move.
1169			 *
1170			 * BUG: You can make this wrong by
1171			 * tricking around with multiple commands
1172			 * on one line of : escape, and including
1173			 * a write command there, but it's not
1174			 * worth worrying about.
1175			 */
1176			if (FIXUNDO && tchng && tchng != i)
1177				vundkind = VMANY, cursor = 0;
1178
1179			/*
1180			 * If we are about to do another :, hold off
1181			 * updating of screen.
1182			 */
1183			if (vcnt < 0 && Peekkey == ':') {
1184				getDOT();
1185				shouldpo = 1;
1186				continue;
1187			}
1188			shouldpo = 0;
1189
1190			/*
1191			 * In the case where the file being edited is
1192			 * new; e.g. if the initial state hasn't been
1193			 * saved yet, then do so now.
1194			 */
1195			if (unddol == truedol) {
1196				vundkind = VNONE;
1197				Vlines = lineDOL();
1198				if (!inglobal)
1199					savevis();
1200				addr = zero;
1201				vcnt = 0;
1202				if (esave[0] == 0)
1203					copy(esave, vtube[WECHO], TUBECOLS * sizeof(wchar_t));
1204			}
1205
1206			/*
1207			 * If the current line moved reset the cursor position.
1208			 */
1209			if (dot != addr) {
1210				vmoving = 0;
1211				cursor = 0;
1212			}
1213
1214			/*
1215			 * If current line is not on screen or if we are
1216			 * in open mode and . moved, then redraw.
1217			 */
1218			i = vcline + (dot - addr);
1219			if(windowchg)
1220				windowinit();
1221			if (i < 0 || i >= vcnt && i >= -vcnt || state != VISUAL && dot != addr) {
1222				if (state == CRTOPEN)
1223					vup1();
1224				if (vcnt > 0)
1225					vcnt = 0;
1226				vjumpto(dot, (unsigned char *) 0, '.');
1227			} else {
1228				/*
1229				 * Current line IS on screen.
1230				 * If we did a [Hit return...] then
1231				 * restore vcnt and clear screen if in visual
1232				 */
1233				vcline = i;
1234				if (vcnt < 0) {
1235					vcnt = -vcnt;
1236					if (state == VISUAL)
1237						vclear();
1238					else if (state == CRTOPEN) {
1239						vcnt = 0;
1240					}
1241				}
1242
1243				/*
1244				 * Limit max value of vcnt based on $
1245				 */
1246				i = vcline + lineDOL() - lineDOT() + 1;
1247				if (i < vcnt)
1248					vcnt = i;
1249
1250				/*
1251				 * Dirty and repaint.
1252				 */
1253				vdirty(0, lines);
1254				vrepaint(cursor);
1255			}
1256
1257			/*
1258			 * If in visual, put back the echo area
1259			 * if it was clobbered.
1260			 */
1261			if (state == VISUAL) {
1262				int sdc = destcol, sdl = destline;
1263
1264				splitw++;
1265				vigoto(WECHO, 0);
1266				for (i = 0; i < TUBECOLS - 1; i++) {
1267					if (esave[i] == 0)
1268						break;
1269					if(esave[i] != FILLER)
1270						(void) vputchar(esave[i]);
1271				}
1272				splitw = 0;
1273				vgoto(sdl, sdc);
1274			}
1275			if (tag_reset_wrap == 1) {
1276				tag_reset_wrap = 0;
1277				value(vi_WRAPSCAN) = 0;
1278			}
1279			continue;
1280
1281		/*
1282		 * u		undo the last changing command.
1283		 */
1284		case 'u':
1285			vundo(1);
1286			continue;
1287
1288		/*
1289		 * U		restore current line to initial state.
1290		 */
1291		case 'U':
1292			vUndo();
1293			continue;
1294
1295fonfon:
1296			(void) beep();
1297			vmacp = 0;
1298			inopen = 1;	/* might have been -1 */
1299			continue;
1300		}
1301
1302		/*
1303		 * Rest of commands are decoded by the operate
1304		 * routine.
1305		 */
1306		operate(c, cnt);
1307	}
1308}
1309
1310/*
1311 * Grab the word after the cursor so we can look for it as a tag.
1312 */
1313void
1314grabtag(void)
1315{
1316	unsigned char *cp, *dp;
1317
1318	cp = vpastwh(cursor);
1319	if (*cp) {
1320		dp = lasttag;
1321		do {
1322			if (dp < &lasttag[sizeof lasttag - 2])
1323				*dp++ = *cp;
1324			cp++;
1325			/* only allow ascii alphabetics */
1326		} while ((isascii(*cp) && isalpha(*cp)) || isdigit(*cp) || *cp == '_');
1327		*dp++ = 0;
1328	}
1329}
1330
1331/*
1332 * Before appending lines, set up addr1 and
1333 * the command mode undo information.
1334 */
1335void
1336prepapp(void)
1337{
1338
1339	addr1 = dot;
1340	deletenone();
1341	addr1++;
1342	appendnone();
1343}
1344
1345/*
1346 * Execute function f with the address bounds addr1
1347 * and addr2 surrounding cnt lines starting at dot.
1348 */
1349void
1350vremote(cnt, f, arg)
1351	int cnt, (*f)(), arg;
1352{
1353	int oing = inglobal;
1354
1355	addr1 = dot;
1356	addr2 = dot + cnt - 1;
1357	inglobal = 0;
1358	if (FIXUNDO)
1359		undap1 = undap2 = dot;
1360	(*f)(arg);
1361	inglobal = oing;
1362	if (FIXUNDO)
1363		vundkind = VMANY;
1364	/*
1365	 * XPG6 assertion 273: For the following commands, don't set vmcurs
1366	 * to 0, so that undo positions the cursor column correctly when
1367	 * we've moved off the initial line that was changed eg. when G has
1368	 * moved us off the line, or when a multi-line change was done.
1369	 */
1370	if (lastcmd[0] != 'C' && lastcmd[0] != 'c' && lastcmd[0] != 'o' &&
1371	    lastcmd[0] != 'R' && lastcmd[0] != 'S' && lastcmd[0] != 's' &&
1372	    lastcmd[0] != 'i' && lastcmd[0] != 'a' && lastcmd[0] != 'A') {
1373		vmcurs = 0;
1374	}
1375}
1376
1377/*
1378 * Save the current contents of linebuf, if it has changed.
1379 */
1380void
1381vsave(void)
1382{
1383	unsigned char temp[LBSIZE];
1384
1385	strncpy(temp, linebuf, sizeof (temp));
1386	if (FIXUNDO && vundkind == VCHNG || vundkind == VCAPU) {
1387		/*
1388		 * If the undo state is saved in the temporary buffer
1389		 * vutmp, then we sync this into the temp file so that
1390		 * we will be able to undo even after we have moved off
1391		 * the line.  It would be possible to associate a line
1392		 * with vutmp but we assume that vutmp is only associated
1393		 * with line dot (e.g. in case ':') above, so beware.
1394		 */
1395		prepapp();
1396		strcLIN(vutmp);
1397		putmark(dot);
1398		vremote(1, yank, 0);
1399		vundkind = VMCHNG;
1400		notecnt = 0;
1401		undkind = UNDCHANGE;
1402	}
1403	/*
1404	 * Get the line out of the temp file and do nothing if it hasn't
1405	 * changed.  This may seem like a loss, but the line will
1406	 * almost always be in a read buffer so this may well avoid disk i/o.
1407	 */
1408	getDOT();
1409	if (strncmp(linebuf, temp, sizeof (temp)) == 0)
1410		return;
1411	strcLIN(temp);
1412	putmark(dot);
1413}
1414
1415#undef	forbid
1416#define	forbid(a)	if (a) { (void) beep(); return; }
1417
1418/*
1419 * Do a z operation.
1420 * Code here is rather long, and very uninteresting.
1421 */
1422void
1423vzop(bool hadcnt, int cnt, int c)
1424{
1425	line *addr;
1426
1427	if (state != VISUAL) {
1428		/*
1429		 * Z from open; always like a z=.
1430		 * This code is a mess and should be cleaned up.
1431		 */
1432		vmoveitup(1, 1);
1433		vgoto(outline, 0);
1434		ostop(normf);
1435		setoutt();
1436		addr2 = dot;
1437		vclear();
1438		destline = WECHO;
1439		zop2(Xhadcnt ? Xcnt : value(vi_WINDOW) - 1, '=');
1440		if (state == CRTOPEN)
1441			putnl();
1442		putNFL();
1443		termreset();
1444		Outchar = vputchar;
1445		(void)ostart();
1446		vcnt = 0;
1447		outline = destline = 0;
1448		vjumpto(dot, cursor, 0);
1449		return;
1450	}
1451	if (hadcnt) {
1452		addr = zero + cnt;
1453		if (addr < one)
1454			addr = one;
1455		if (addr > dol)
1456			addr = dol;
1457		markit(addr);
1458	} else
1459		switch (c) {
1460
1461		case '+':
1462			addr = dot + vcnt - vcline;
1463			break;
1464
1465		case '^':
1466			addr = dot - vcline - 1;
1467			forbid (addr < one);
1468			c = '-';
1469			break;
1470
1471		default:
1472			addr = dot;
1473			break;
1474		}
1475	switch (c) {
1476
1477	case '.':
1478	case '-':
1479		break;
1480
1481	case '^':
1482		forbid (addr <= one);
1483		break;
1484
1485	case '+':
1486		forbid (addr >= dol);
1487		/* FALLTHROUGH */
1488
1489	case CR:
1490	case NL:
1491		c = CR;
1492		break;
1493
1494	default:
1495		(void) beep();
1496		return;
1497	}
1498	vmoving = 0;
1499	vjumpto(addr, (unsigned char *)NOSTR, c);
1500}
1501