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