xref: /original-bsd/usr.bin/ex/ex_vput.c (revision d45fc766)
1 /*-
2  * Copyright (c) 1980, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.proprietary.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)ex_vput.c	8.1 (Berkeley) 06/09/93";
10 #endif /* not lint */
11 
12 #include "ex.h"
13 #include "ex_tty.h"
14 #include "ex_vis.h"
15 
16 /*
17  * Deal with the screen, clearing, cursor positioning, putting characters
18  * into the screen image, and deleting characters.
19  * Really hard stuff here is utilizing insert character operations
20  * on intelligent terminals which differs widely from terminal to terminal.
21  */
22 vclear()
23 {
24 
25 #ifdef ADEBUG
26 	if (trace)
27 		tfixnl(), fprintf(trace, "------\nvclear\n");
28 #endif
29 	tputs(CL, LINES, putch);
30 	destcol = 0;
31 	outcol = 0;
32 	destline = 0;
33 	outline = 0;
34 	if (inopen)
35 		vclrbyte(vtube0, WCOLS * (WECHO - ex_ZERO + 1));
36 }
37 
38 /*
39  * Clear memory.
40  */
41 vclrbyte(cp, i)
42 	register char *cp;
43 	register int i;
44 {
45 
46 	if (i > 0)
47 		do
48 			*cp++ = 0;
49 		while (--i != 0);
50 }
51 
52 /*
53  * Clear a physical display line, high level.
54  */
55 vclrlin(l, tp)
56 	int l;
57 	line *tp;
58 {
59 
60 	vigoto(l, 0);
61 	if ((hold & HOLDAT) == 0)
62 		ex_putchar(tp > dol ? ((UPPERCASE || HZ) ? '^' : '~') : '@');
63 	if (state == HARDOPEN)
64 		sethard();
65 	vclreol();
66 }
67 
68 /*
69  * Clear to the end of the current physical line
70  */
71 vclreol()
72 {
73 	register int i, j;
74 	register char *tp;
75 
76 	if (destcol == WCOLS)
77 		return;
78 	destline += destcol / WCOLS;
79 	destcol %= WCOLS;
80 	if (destline < 0 || destline > WECHO)
81 		error("Internal error: vclreol");
82 	i = WCOLS - destcol;
83 	tp = vtube[destline] + destcol;
84 	if (CE) {
85 		if (IN && *tp || !ateopr()) {
86 			vcsync();
87 			vputp(CE, 1);
88 		}
89 		vclrbyte(tp, i);
90 		return;
91 	}
92 	if (*tp == 0)
93 		return;
94 	while (i > 0 && (j = *tp & (QUOTE|TRIM))) {
95 		if (j != ' ' && (j & QUOTE) == 0) {
96 			destcol = WCOLS - i;
97 			vputchar(' ');
98 		}
99 		--i, *tp++ = 0;
100 	}
101 }
102 
103 /*
104  * Clear the echo line.
105  * If didphys then its been cleared physically (as
106  * a side effect of a clear to end of display, e.g.)
107  * so just do it logically.
108  * If work here is being held off, just remember, in
109  * heldech, if work needs to be done, don't do anything.
110  */
111 vclrech(didphys)
112 	bool didphys;
113 {
114 
115 	if (Peek_key == ATTN)
116 		return;
117 	if (hold & HOLDECH) {
118 		heldech = !didphys;
119 		return;
120 	}
121 	if (!didphys && (CD || CE)) {
122 		splitw++;
123 		/*
124 		 * If display is retained below, then MUST use CD or CE
125 		 * since we don't really know whats out there.
126 		 * Vigoto might decide (incorrectly) to do nothing.
127 		 */
128 		if (DB) {
129 			vgoto(WECHO, 0);
130 			vputp(CD ? CD : CE, 1);
131 		} else {
132 			if (XT) {
133 				/*
134 				 * This code basically handles the t1061
135 				 * where positioning at (0, 0) won't work
136 				 * because the terminal won't let you put
137 				 * the cursor on it's magic cookie.
138 				 *
139 				 * Should probably be XS above, or even a
140 				 * new X? glitch, but right now t1061 is the
141 				 * only terminal with XT.
142 				 */
143 				vgoto(WECHO, 0);
144 				vputp(DL, 1);
145 			} else {
146 				vigoto(WECHO, 0);
147 				vclreol();
148 			}
149 		}
150 		splitw = 0;
151 		didphys = 1;
152 	}
153 	if (didphys)
154 		vclrbyte(vtube[WECHO], WCOLS);
155 	heldech = 0;
156 }
157 
158 /*
159  * Fix the echo area for use, setting
160  * the state variable splitw so we wont rollup
161  * when we move the cursor there.
162  */
163 fixech()
164 {
165 
166 	splitw++;
167 	if (state != VISUAL && state != CRTOPEN) {
168 		vclean();
169 		vcnt = 0;
170 	}
171 	vgoto(WECHO, 0); flusho();
172 }
173 
174 /*
175  * Put the cursor ``before'' cp.
176  */
177 vcursbef(cp)
178 	register char *cp;
179 {
180 
181 	if (cp <= linebuf)
182 		vgotoCL(value(NUMBER) << 3);
183 	else
184 		vgotoCL(column(cp - 1) - 1);
185 }
186 
187 /*
188  * Put the cursor ``at'' cp.
189  */
190 vcursat(cp)
191 	register char *cp;
192 {
193 
194 	if (cp <= linebuf && linebuf[0] == 0)
195 		vgotoCL(value(NUMBER) << 3);
196 	else
197 		vgotoCL(column(cp - 1));
198 }
199 
200 /*
201  * Put the cursor ``after'' cp.
202  */
203 vcursaft(cp)
204 	register char *cp;
205 {
206 
207 	vgotoCL(column(cp));
208 }
209 
210 /*
211  * Fix the cursor to be positioned in the correct place
212  * to accept a command.
213  */
214 vfixcurs()
215 {
216 
217 	vsetcurs(cursor);
218 }
219 
220 /*
221  * Compute the column position implied by the cursor at ``nc'',
222  * and move the cursor there.
223  */
224 vsetcurs(nc)
225 	register char *nc;
226 {
227 	register int col;
228 
229 	col = column(nc);
230 	if (linebuf[0])
231 		col--;
232 	vgotoCL(col);
233 	cursor = nc;
234 }
235 
236 /*
237  * Move the cursor invisibly, i.e. only remember to do it.
238  */
239 vigoto(y, x)
240 	int y, x;
241 {
242 
243 	destline = y;
244 	destcol = x;
245 }
246 
247 /*
248  * Move the cursor to the position implied by any previous
249  * vigoto (or low level hacking with destcol/destline as in readecho).
250  */
251 vcsync()
252 {
253 
254 	vgoto(destline, destcol);
255 }
256 
257 /*
258  * Goto column x of the current line.
259  */
260 vgotoCL(x)
261 	register int x;
262 {
263 
264 	if (splitw)
265 		vgoto(WECHO, x);
266 	else
267 		vgoto(LINE(vcline), x);
268 }
269 
270 /*
271  * Invisible goto column x of current line.
272  */
273 vigotoCL(x)
274 	register int x;
275 {
276 
277 	if (splitw)
278 		vigoto(WECHO, x);
279 	else
280 		vigoto(LINE(vcline), x);
281 }
282 
283 /*
284  * Move cursor to line y, column x, handling wraparound and scrolling.
285  */
286 vgoto(y, x)
287 	register int y, x;
288 {
289 	register char *tp;
290 	register int c;
291 
292 	/*
293 	 * Fold the possibly too large value of x.
294 	 */
295 	if (x >= WCOLS) {
296 		y += x / WCOLS;
297 		x %= WCOLS;
298 	}
299 	if (y < 0)
300 		error("Internal error: vgoto");
301 	if (outcol >= WCOLS) {
302 		if (AM) {
303 			outline += outcol / WCOLS;
304 			outcol %= WCOLS;
305 		} else
306 			outcol = WCOLS - 1;
307 	}
308 
309 	/*
310 	 * In a hardcopy or glass crt open, print the stuff
311 	 * implied by a motion, or backspace.
312 	 */
313 	if (state == HARDOPEN || state == ONEOPEN) {
314 		if (y != outline)
315 			error("Line too long for open");
316 		if (x + 1 < outcol - x || (outcol > x && !BS))
317 			destcol = 0, fgoto();
318 		tp = vtube[WBOT] + outcol;
319 		while (outcol != x)
320 			if (outcol < x) {
321 				if (*tp == 0)
322 					*tp = ' ';
323 				c = *tp++ & TRIM;
324 				vputc(c && (!OS || EO) ? c : ' '), outcol++;
325 			} else {
326 				if (BC)
327 					vputp(BC, 0);
328 				else
329 					vputc('\b');
330 				outcol--;
331 			}
332 		destcol = outcol = x;
333 		destline = outline;
334 		return;
335 	}
336 
337 	/*
338 	 * If the destination position implies a scroll, do it.
339 	 */
340 	destline = y;
341 	if (destline > WBOT && (!splitw || destline > WECHO)) {
342 		endim();
343 		vrollup(destline);
344 	}
345 
346 	/*
347 	 * If there really is a motion involved, do it.
348 	 * The check here is an optimization based on profiling.
349 	 */
350 	destcol = x;
351 	if ((destline - outline) * WCOLS != destcol - outcol) {
352 		if (!MI)
353 			endim();
354 		fgoto();
355 	}
356 }
357 
358 /*
359  * This is the hardest code in the editor, and deals with insert modes
360  * on different kinds of intelligent terminals.  The complexity is due
361  * to the cross product of three factors:
362  *
363  *	1. Lines may display as more than one segment on the screen.
364  *	2. There are 2 kinds of intelligent terminal insert modes.
365  *	3. Tabs squash when you insert characters in front of them,
366  *	   in a way in which current intelligent terminals don't handle.
367  *
368  * The two kinds of terminals are typified by the DM2500 or HP2645 for
369  * one and the CONCEPT-100 or the FOX for the other.
370  *
371  * The first (HP2645) kind has an insert mode where the characters
372  * fall off the end of the line and the screen is shifted rigidly
373  * no matter how the display came about.
374  *
375  * The second (CONCEPT-100) kind comes from terminals which are designed
376  * for forms editing and which distinguish between blanks and ``spaces''
377  * on the screen, spaces being like blank, but never having had
378  * and data typed into that screen position (since, e.g. a clear operation
379  * like clear screen).  On these terminals, when you insert a character,
380  * the characters from where you are to the end of the screen shift
381  * over till a ``space'' is found, and the null character there gets
382  * eaten up.
383  *
384  *
385  * The code here considers the line as consisting of several parts
386  * the first part is the ``doomed'' part, i.e. a part of the line
387  * which is being typed over.  Next comes some text up to the first
388  * following tab.  The tab is the next segment of the line, and finally
389  * text after the tab.
390  *
391  * We have to consider each of these segments and the effect of the
392  * insertion of a character on them.  On terminals like HP2645's we
393  * must simulate a multi-line insert mode using the primitive one
394  * line insert mode.  If we are inserting in front of a tab, we have
395  * to either delete characters from the tab or insert white space
396  * (when the tab reaches a new spot where it gets larger) before we
397  * insert the new character.
398  *
399  * On a terminal like a CONCEPT our strategy is to make all
400  * blanks be displayed, while trying to keep the screen having ``spaces''
401  * for portions of tabs.  In this way the terminal hardward does some
402  * of the hacking for compression of tabs, although this tends to
403  * disappear as you work on the line and spaces change into blanks.
404  *
405  * There are a number of boundary conditions (like typing just before
406  * the first following tab) where we can avoid a lot of work.  Most
407  * of them have to be dealt with explicitly because performance is
408  * much, much worse if we don't.
409  *
410  * A final thing which is hacked here is two flavors of insert mode.
411  * Datamedia's do this by an insert mode which you enter and leave
412  * and by having normal motion character operate differently in this
413  * mode, notably by having a newline insert a line on the screen in
414  * this mode.  This generally means it is unsafe to move around
415  * the screen ignoring the fact that we are in this mode.
416  * This is possible on some terminals, and wins big (e.g. HP), so
417  * we encode this as a ``can move in insert capability'' mi,
418  * and terminals which have it can do insert mode with much less
419  * work when tabs are present following the cursor on the current line.
420  */
421 
422 /*
423  * Routine to expand a tab, calling the normal Outchar routine
424  * to put out each implied character.  Note that we call outchar
425  * with a QUOTE.  We use QUOTE internally to represent a position
426  * which is part of the expansion of a tab.
427  */
428 vgotab()
429 {
430 	register int i = tabcol(destcol, value(TABSTOP)) - destcol;
431 
432 	do
433 		(*Outchar)(QUOTE);
434 	while (--i);
435 }
436 
437 /*
438  * Variables for insert mode.
439  */
440 int	linend;			/* The column position of end of line */
441 int	tabstart;		/* Column of start of first following tab */
442 int	tabend;			/* Column of end of following tabs */
443 int	tabsize;		/* Size of the following tabs */
444 int	tabslack;		/* Number of ``spaces'' in following tabs */
445 int	inssiz;			/* Number of characters to be inserted */
446 int	inscol;			/* Column where insertion is taking place */
447 int	shft;			/* Amount tab expansion shifted rest of line */
448 int	slakused;		/* This much of tabslack will be used up */
449 
450 /*
451  * This routine MUST be called before insert mode is run,
452  * and brings all segments of the current line to the top
453  * of the screen image buffer so it is easier for us to
454  * maniuplate them.
455  */
456 vprepins()
457 {
458 	register int i;
459 	register char *cp = vtube0;
460 
461 	for (i = 0; i < DEPTH(vcline); i++) {
462 		vmaktop(LINE(vcline) + i, cp);
463 		cp += WCOLS;
464 	}
465 }
466 
467 vmaktop(p, cp)
468 	register int p;
469 	char *cp;
470 {
471 	register int i;
472 	char temp[TUBECOLS];
473 
474 	if (p < 0 || vtube[p] == cp)
475 		return;
476 	for (i = ex_ZERO; i <= WECHO; i++)
477 		if (vtube[i] == cp) {
478 			copy(temp, vtube[i], WCOLS);
479 			copy(vtube[i], vtube[p], WCOLS);
480 			copy(vtube[p], temp, WCOLS);
481 			vtube[i] = vtube[p];
482 			vtube[p] = cp;
483 			return;
484 		}
485 	error("Line too long");
486 }
487 
488 /*
489  * Insert character c at current cursor position.
490  * Multi-character inserts occur only as a result
491  * of expansion of tabs (i.e. inssize == 1 except
492  * for tabs) and code assumes this in several place
493  * to make life simpler.
494  */
495 vinschar(c)
496 	int c;		/* mjm: char --> int */
497 {
498 	register int i;
499 	register char *tp;
500 
501 	if ((!IM || !EI) && ((hold & HOLDQIK) || !value(REDRAW) || value(SLOWOPEN))) {
502 		/*
503 		 * Don't want to try to use terminal
504 		 * insert mode, or to try to fake it.
505 		 * Just put the character out; the screen
506 		 * will probably be wrong but we will fix it later.
507 		 */
508 		if (c == '\t') {
509 			vgotab();
510 			return;
511 		}
512 		vputchar(c);
513 		if (DEPTH(vcline) * WCOLS + !value(REDRAW) >
514 		    (destline - LINE(vcline)) * WCOLS + destcol)
515 			return;
516 		/*
517 		 * The next line is about to be clobbered
518 		 * make space for another segment of this line
519 		 * (on an intelligent terminal) or just remember
520 		 * that next line was clobbered (on a dumb one
521 		 * if we don't care to redraw the tail.
522 		 */
523 		if (AL) {
524 			vnpins(0);
525 		} else {
526 			c = LINE(vcline) + DEPTH(vcline);
527 			if (c < LINE(vcline + 1) || c > WBOT)
528 				return;
529 			i = destcol;
530 			vinslin(c, 1, vcline);
531 			DEPTH(vcline)++;
532 			vigoto(c, i);
533 			vprepins();
534 		}
535 		return;
536 	}
537 	/*
538 	 * Compute the number of positions in the line image of the
539 	 * current line.  This is done from the physical image
540 	 * since that is faster.  Note that we have no memory
541 	 * from insertion to insertion so that routines which use
542 	 * us don't have to worry about moving the cursor around.
543 	 */
544 	if (*vtube0 == 0)
545 		linend = 0;
546 	else {
547 		/*
548 		 * Search backwards for a non-null character
549 		 * from the end of the displayed line.
550 		 */
551 		i = WCOLS * DEPTH(vcline);
552 		if (i == 0)
553 			i = WCOLS;
554 		tp = vtube0 + i;
555 		while (*--tp == 0)
556 			if (--i == 0)
557 				break;
558 		linend = i;
559 	}
560 
561 	/*
562 	 * We insert at a position based on the physical location
563 	 * of the output cursor.
564 	 */
565 	inscol = destcol + (destline - LINE(vcline)) * WCOLS;
566 	if (c == '\t') {
567 		/*
568 		 * Characters inserted from a tab must be
569 		 * remembered as being part of a tab, but we can't
570 		 * use QUOTE here since we really need to print blanks.
571 		 * QUOTE|' ' is the representation of this.
572 		 */
573 		inssiz = tabcol(inscol, value(TABSTOP)) - inscol;
574 		c = ' ' | QUOTE;
575 	} else
576 		inssiz = 1;
577 
578 	/*
579 	 * If the text to be inserted is less than the number
580 	 * of doomed positions, then we don't need insert mode,
581 	 * rather we can just typeover.
582 	 */
583 	if (inssiz <= doomed) {
584 		endim();
585 		if (inscol != linend)
586 			doomed -= inssiz;
587 		do
588 			vputchar(c);
589 		while (--inssiz);
590 		return;
591 	}
592 
593 	/*
594 	 * Have to really do some insertion, thus
595 	 * stake out the bounds of the first following
596 	 * group of tabs, computing starting position,
597 	 * ending position, and the number of ``spaces'' therein
598 	 * so we can tell how much it will squish.
599 	 */
600 	tp = vtube0 + inscol;
601 	for (i = inscol; i < linend; i++)
602 		if (*tp++ & QUOTE) {
603 			--tp;
604 			break;
605 		}
606 	tabstart = tabend = i;
607 	tabslack = 0;
608 	while (tabend < linend) {
609 		i = *tp++;
610 		if ((i & QUOTE) == 0)
611 			break;
612 		if ((i & TRIM) == 0)
613 			tabslack++;
614 		tabsize++;
615 		tabend++;
616 	}
617 	tabsize = tabend - tabstart;
618 
619 	/*
620 	 * For HP's and DM's, e.g. tabslack has no meaning.
621 	 */
622 	if (!IN)
623 		tabslack = 0;
624 #ifdef IDEBUG
625 	if (trace) {
626 		fprintf(trace, "inscol %d, inssiz %d, tabstart %d, ",
627 			inscol, inssiz, tabstart);
628 		fprintf(trace, "tabend %d, tabslack %d, linend %d\n",
629 			tabend, tabslack, linend);
630 	}
631 #endif
632 
633 	/*
634 	 * The real work begins.
635 	 */
636 	slakused = 0;
637 	shft = 0;
638 	if (tabsize) {
639 		/*
640 		 * There are tabs on this line.
641 		 * If they need to expand, then the rest of the line
642 		 * will have to be shifted over.  In this case,
643 		 * we will need to make sure there are no ``spaces''
644 		 * in the rest of the line (on e.g. CONCEPT-100)
645 		 * and then grab another segment on the screen if this
646 		 * line is now deeper.  We then do the shift
647 		 * implied by the insertion.
648 		 */
649 		if (inssiz >= doomed + tabcol(tabstart, value(TABSTOP)) - tabstart) {
650 			if (IN)
651 				vrigid();
652 			vneedpos(value(TABSTOP));
653 			vishft();
654 		}
655 	} else if (inssiz > doomed)
656 		/*
657 		 * No tabs, but line may still get deeper.
658 		 */
659 		vneedpos(inssiz - doomed);
660 	/*
661 	 * Now put in the inserted characters.
662 	 */
663 	viin(c);
664 
665 	/*
666 	 * Now put the cursor in its final resting place.
667 	 */
668 	destline = LINE(vcline);
669 	destcol = inscol + inssiz;
670 	vcsync();
671 }
672 
673 /*
674  * Rigidify the rest of the line after the first
675  * group of following tabs, typing blanks over ``spaces''.
676  */
677 vrigid()
678 {
679 	register int col;
680 	register char *tp = vtube0 + tabend;
681 
682 	for (col = tabend; col < linend; col++)
683 		if ((*tp++ & TRIM) == 0) {
684 			endim();
685 			vgotoCL(col);
686 			vputchar(' ' | QUOTE);
687 		}
688 }
689 
690 /*
691  * We need cnt more positions on this line.
692  * Open up new space on the screen (this may in fact be a
693  * screen rollup).
694  *
695  * On a dumb terminal we may infact redisplay the rest of the
696  * screen here brute force to keep it pretty.
697  */
698 vneedpos(cnt)
699 	int cnt;
700 {
701 	register int d = DEPTH(vcline);
702 	register int rmdr = d * WCOLS - linend;
703 
704 	if (cnt <= rmdr - IN)
705 		return;
706 	endim();
707 	vnpins(1);
708 }
709 
710 vnpins(dosync)
711 	int dosync;
712 {
713 	register int d = DEPTH(vcline);
714 	register int e;
715 
716 	e = LINE(vcline) + DEPTH(vcline);
717 	if (e < LINE(vcline + 1)) {
718 		vigoto(e, 0);
719 		vclreol();
720 		return;
721 	}
722 	DEPTH(vcline)++;
723 	if (e < WECHO) {
724 		e = vglitchup(vcline, d);
725 		vigoto(e, 0); vclreol();
726 		if (dosync) {
727 			int (*Ooutchar)() = Outchar;
728 			Outchar = vputchar;
729 			vsync(e + 1);
730 			Outchar = Ooutchar;
731 		}
732 	} else {
733 		vup1();
734 		vigoto(WBOT, 0);
735 		vclreol();
736 	}
737 	vprepins();
738 }
739 
740 /*
741  * Do the shift of the next tabstop implied by
742  * insertion so it expands.
743  */
744 vishft()
745 {
746 	int tshft = 0;
747 	int j;
748 	register int i;
749 	register char *tp = vtube0;
750 	register char *up;
751 	short oldhold = hold;
752 
753 	shft = value(TABSTOP);
754 	hold |= HOLDPUPD;
755 	if (!IM && !EI) {
756 		/*
757 		 * Dumb terminals are easy, we just have
758 		 * to retype the text.
759 		 */
760 		vigotoCL(tabend + shft);
761 		up = tp + tabend;
762 		for (i = tabend; i < linend; i++)
763 			vputchar(*up++);
764 	} else if (IN) {
765 		/*
766 		 * CONCEPT-like terminals do most of the work for us,
767 		 * we don't have to muck with simulation of multi-line
768 		 * insert mode.  Some of the shifting may come for free
769 		 * also if the tabs don't have enough slack to take up
770 		 * all the inserted characters.
771 		 */
772 		i = shft;
773 		slakused = inssiz - doomed;
774 		if (slakused > tabslack) {
775 			i -= slakused - tabslack;
776 			slakused -= tabslack;
777 		}
778 		if (i > 0 && tabend != linend) {
779 			tshft = i;
780 			vgotoCL(tabend);
781 			goim();
782 			do
783 				vputchar(' ' | QUOTE);
784 			while (--i);
785 		}
786 	} else {
787 		/*
788 		 * HP and Datamedia type terminals have to have multi-line
789 		 * insert faked.  Hack each segment after where we are
790 		 * (going backwards to where we are.)  We then can
791 		 * hack the segment where the end of the first following
792 		 * tab group is.
793 		 */
794 		for (j = DEPTH(vcline) - 1; j > (tabend + shft) / WCOLS; j--) {
795 			vgotoCL(j * WCOLS);
796 			goim();
797 			up = tp + j * WCOLS - shft;
798 			i = shft;
799 			do {
800 				if (*up)
801 					vputchar(*up++);
802 				else
803 					break;
804 			} while (--i);
805 		}
806 		vigotoCL(tabstart);
807 		i = shft - (inssiz - doomed);
808 		if (i > 0) {
809 			tabslack = inssiz - doomed;
810 			vcsync();
811 			goim();
812 			do
813 				vputchar(' ');
814 			while (--i);
815 		}
816 	}
817 	/*
818 	 * Now do the data moving in the internal screen
819 	 * image which is common to all three cases.
820 	 */
821 	tp += linend;
822 	up = tp + shft;
823 	i = linend - tabend;
824 	if (i > 0)
825 		do
826 			*--up = *--tp;
827 		while (--i);
828 	if (IN && tshft) {
829 		i = tshft;
830 		do
831 			*--up = ' ' | QUOTE;
832 		while (--i);
833 	}
834 	hold = oldhold;
835 }
836 
837 /*
838  * Now do the insert of the characters (finally).
839  */
840 viin(c)
841 	int c;		/* mjm: char --> int */
842 {
843 	register char *tp, *up;
844 	register int i, j;
845 	register bool noim = 0;
846 	int remdoom;
847 	short oldhold = hold;
848 
849 	hold |= HOLDPUPD;
850 	if (tabsize && (IM && EI) && inssiz - doomed > tabslack)
851 		/*
852 		 * There is a tab out there which will be affected
853 		 * by the insertion since there aren't enough doomed
854 		 * characters to take up all the insertion and we do
855 		 * have insert mode capability.
856 		 */
857 		if (inscol + doomed == tabstart) {
858 			/*
859 			 * The end of the doomed characters sits right at the
860 			 * start of the tabs, then we don't need to use insert
861 			 * mode; unless the tab has already been expanded
862 			 * in which case we MUST use insert mode.
863 			 */
864 			slakused = 0;
865 			noim = !shft;
866 		} else {
867 			/*
868 			 * The last really special case to handle is case
869 			 * where the tab is just sitting there and doesn't
870 			 * have enough slack to let the insertion take
871 			 * place without shifting the rest of the line
872 			 * over.  In this case we have to go out and
873 			 * delete some characters of the tab before we start
874 			 * or the answer will be wrong, as the rest of the
875 			 * line will have been shifted.  This code means
876 			 * that terminals with only insert chracter (no
877 			 * delete character) won't work correctly.
878 			 */
879 			i = inssiz - doomed - tabslack - slakused;
880 			i %= value(TABSTOP);
881 			if (i > 0) {
882 				vgotoCL(tabstart);
883 				godm();
884 				for (i = inssiz - doomed - tabslack; i > 0; i--)
885 					vputp(DC, DEPTH(vcline));
886 				enddm();
887 			}
888 		}
889 
890 	/*
891 	 * Now put out the characters of the actual insertion.
892 	 */
893 	vigotoCL(inscol);
894 	remdoom = doomed;
895 	for (i = inssiz; i > 0; i--) {
896 		if (remdoom > 0) {
897 			remdoom--;
898 			endim();
899 		} else if (noim)
900 			endim();
901 		else if (IM && EI) {
902 			vcsync();
903 			goim();
904 		}
905 		vputchar(c);
906 	}
907 
908 	if (!IM || !EI) {
909 		/*
910 		 * We are a dumb terminal; brute force update
911 		 * the rest of the line; this is very much an n^^2 process,
912 		 * and totally unreasonable at low speed.
913 		 *
914 		 * You asked for it, you get it.
915 		 */
916 		tp = vtube0 + inscol + doomed;
917 		for (i = inscol + doomed; i < tabstart; i++)
918 			vputchar(*tp++);
919 		hold = oldhold;
920 		vigotoCL(tabstart + inssiz - doomed);
921 		for (i = tabsize - (inssiz - doomed) + shft; i > 0; i--)
922 			vputchar(' ' | QUOTE);
923 	} else {
924 		if (!IN) {
925 			/*
926 			 * On terminals without multi-line
927 			 * insert in the hardware, we must go fix the segments
928 			 * between the inserted text and the following
929 			 * tabs, if they are on different lines.
930 			 *
931 			 * Aaargh.
932 			 */
933 			tp = vtube0;
934 			for (j = (inscol + inssiz - 1) / WCOLS + 1;
935 			    j <= (tabstart + inssiz - doomed - 1) / WCOLS; j++) {
936 				vgotoCL(j * WCOLS);
937 				i = inssiz - doomed;
938 				up = tp + j * WCOLS - i;
939 				goim();
940 				do
941 					vputchar(*up++);
942 				while (--i && *up);
943 			}
944 		} else {
945 			/*
946 			 * On terminals with multi line inserts,
947 			 * life is simpler, just reflect eating of
948 			 * the slack.
949 			 */
950 			tp = vtube0 + tabend;
951 			for (i = tabsize - (inssiz - doomed); i >= 0; i--) {
952 				if ((*--tp & (QUOTE|TRIM)) == QUOTE) {
953 					--tabslack;
954 					if (tabslack >= slakused)
955 						continue;
956 				}
957 				*tp = ' ' | QUOTE;
958 			}
959 		}
960 		/*
961 		 * Blank out the shifted positions to be tab positions.
962 		 */
963 		if (shft) {
964 			tp = vtube0 + tabend + shft;
965 			for (i = tabsize - (inssiz - doomed) + shft; i > 0; i--)
966 				if ((*--tp & QUOTE) == 0)
967 					*tp = ' ' | QUOTE;
968 		}
969 	}
970 
971 	/*
972 	 * Finally, complete the screen image update
973 	 * to reflect the insertion.
974 	 */
975 	hold = oldhold;
976 	tp = vtube0 + tabstart; up = tp + inssiz - doomed;
977 	for (i = tabstart; i > inscol + doomed; i--)
978 		*--up = *--tp;
979 	for (i = inssiz; i > 0; i--)
980 		*--up = c;
981 	doomed = 0;
982 }
983 
984 /*
985  * Go into ``delete mode''.  If the
986  * sequence which goes into delete mode
987  * is the same as that which goes into insert
988  * mode, then we are in delete mode already.
989  */
990 godm()
991 {
992 
993 	if (insmode) {
994 		if (eq(DM, IM))
995 			return;
996 		endim();
997 	}
998 	vputp(DM, 0);
999 }
1000 
1001 /*
1002  * If we are coming out of delete mode, but
1003  * delete and insert mode end with the same sequence,
1004  * it wins to pretend we are now in insert mode,
1005  * since we will likely want to be there again soon
1006  * if we just moved over to delete space from part of
1007  * a tab (above).
1008  */
1009 enddm()
1010 {
1011 
1012 	if (eq(DM, IM)) {
1013 		insmode = 1;
1014 		return;
1015 	}
1016 	vputp(ED, 0);
1017 }
1018 
1019 /*
1020  * In and out of insert mode.
1021  * Note that the code here demands that there be
1022  * a string for insert mode (the null string) even
1023  * if the terminal does all insertions a single character
1024  * at a time, since it branches based on whether IM is null.
1025  */
1026 goim()
1027 {
1028 
1029 	if (!insmode)
1030 		vputp(IM, 0);
1031 	insmode = 1;
1032 }
1033 
1034 endim()
1035 {
1036 
1037 	if (insmode) {
1038 		vputp(EI, 0);
1039 		insmode = 0;
1040 	}
1041 }
1042 
1043 /*
1044  * Put the character c on the screen at the current cursor position.
1045  * This routine handles wraparound and scrolling and understands not
1046  * to roll when splitw is set, i.e. we are working in the echo area.
1047  * There is a bunch of hacking here dealing with the difference between
1048  * QUOTE, QUOTE|' ', and ' ' for CONCEPT-100 like terminals, and also
1049  * code to deal with terminals which overstrike, including CRT's where
1050  * you can erase overstrikes with some work.  CRT's which do underlining
1051  * implicitly which has to be erased (like CONCEPTS) are also handled.
1052  */
1053 vputchar(c)
1054 	register int c;
1055 {
1056 	register char *tp;
1057 	register int d;
1058 
1059 	c &= (QUOTE|TRIM);
1060 #ifdef TRACE
1061 	if (trace)
1062 		tracec(c);
1063 #endif
1064 	/* Fix problem of >79 chars on echo line. */
1065 	if (destcol >= WCOLS-1 && splitw && destline == WECHO)
1066 		pofix();
1067 	if (destcol >= WCOLS) {
1068 		destline += destcol / WCOLS;
1069 		destcol %= WCOLS;
1070 	}
1071 	if (destline > WBOT && (!splitw || destline > WECHO))
1072 		vrollup(destline);
1073 	tp = vtube[destline] + destcol;
1074 	switch (c) {
1075 
1076 	case '\t':
1077 		vgotab();
1078 		return;
1079 
1080 	case ' ':
1081 		/*
1082 		 * We can get away without printing a space in a number
1083 		 * of cases, but not always.  We get away with doing nothing
1084 		 * if we are not in insert mode, and not on a CONCEPT-100
1085 		 * like terminal, and either not in hardcopy open or in hardcopy
1086 		 * open on a terminal with no overstriking, provided,
1087 		 * in all cases, that nothing has ever been displayed
1088 		 * at this position.  Ugh.
1089 		 */
1090 		if (!insmode && !IN && (state != HARDOPEN || OS) && (*tp&TRIM) == 0) {
1091 			*tp = ' ';
1092 			destcol++;
1093 			return;
1094 		}
1095 		goto def;
1096 
1097 	case QUOTE:
1098 		if (insmode) {
1099 			/*
1100 			 * When in insert mode, tabs have to expand
1101 			 * to real, printed blanks.
1102 			 */
1103 			c = ' ' | QUOTE;
1104 			goto def;
1105 		}
1106 		if (*tp == 0) {
1107 			/*
1108 			 * A ``space''.
1109 			 */
1110 			if ((hold & HOLDPUPD) == 0)
1111 				*tp = QUOTE;
1112 			destcol++;
1113 			return;
1114 		}
1115 		/*
1116 		 * A ``space'' ontop of a part of a tab.
1117 		 */
1118 		if (*tp & QUOTE) {
1119 			destcol++;
1120 			return;
1121 		}
1122 		c = ' ' | QUOTE;
1123 		/* fall into ... */
1124 
1125 def:
1126 	default:
1127 		d = *tp & TRIM;
1128 		/*
1129 		 * Now get away with doing nothing if the characters
1130 		 * are the same, provided we are not in insert mode
1131 		 * and if we are in hardopen, that the terminal has overstrike.
1132 		 */
1133 		if (d == (c & TRIM) && !insmode && (state != HARDOPEN || OS)) {
1134 			if ((hold & HOLDPUPD) == 0)
1135 				*tp = c;
1136 			destcol++;
1137 			return;
1138 		}
1139 		/*
1140 		 * Backwards looking optimization.
1141 		 * The low level cursor motion routines will use
1142 		 * a cursor motion right sequence to step 1 character
1143 		 * right.  On, e.g., a DM3025A this is 2 characters
1144 		 * and printing is noticeably slower at 300 baud.
1145 		 * Since the low level routines are not allowed to use
1146 		 * spaces for positioning, we discover the common
1147 		 * case of a single space here and force a space
1148 		 * to be printed.
1149 		 */
1150 		if (destcol == outcol + 1 && tp[-1] == ' ' && outline == destline) {
1151 			vputc(' ');
1152 			outcol++;
1153 		}
1154 
1155 		/*
1156 		 * This is an inline expansion a call to vcsync() dictated
1157 		 * by high frequency in a profile.
1158 		 */
1159 		if (outcol != destcol || outline != destline)
1160 			vgoto(destline, destcol);
1161 
1162 		/*
1163 		 * Deal with terminals which have overstrike.
1164 		 * We handle erasing general overstrikes, erasing
1165 		 * underlines on terminals (such as CONCEPTS) which
1166 		 * do underlining correctly automatically (e.g. on nroff
1167 		 * output), and remembering, in hardcopy mode,
1168 		 * that we have overstruct something.
1169 		 */
1170 		if (!insmode && d && d != ' ' && d != (c & TRIM)) {
1171 			if (EO && (OS || UL && (c == '_' || d == '_'))) {
1172 				vputc(' ');
1173 				outcol++, destcol++;
1174 				back1();
1175 			} else
1176 				rubble = 1;
1177 		}
1178 
1179 		/*
1180 		 * Unless we are just bashing characters around for
1181 		 * inner working of insert mode, update the display.
1182 		 */
1183 		if ((hold & HOLDPUPD) == 0)
1184 			*tp = c;
1185 
1186 		/*
1187 		 * In insert mode, put out the IC sequence, padded
1188 		 * based on the depth of the current line.
1189 		 * A terminal which had no real insert mode, rather
1190 		 * opening a character position at a time could do this.
1191 		 * Actually should use depth to end of current line
1192 		 * but this rarely matters.
1193 		 */
1194 		if (insmode)
1195 			vputp(IC, DEPTH(vcline));
1196 		vputc(c & TRIM);
1197 
1198 		/*
1199 		 * In insert mode, IP is a post insert pad.
1200 		 */
1201 		if (insmode)
1202 			vputp(IP, DEPTH(vcline));
1203 		destcol++, outcol++;
1204 
1205 		/*
1206 		 * CONCEPT braindamage in early models:  after a wraparound
1207 		 * the next newline is eaten.  It's hungry so we just
1208 		 * feed it now rather than worrying about it.
1209 		 * Fixed to use	return linefeed to work right
1210 		 * on vt100/tab132 as well as concept.
1211 		 */
1212 		if (XN && outcol % WCOLS == 0) {
1213 			vputc('\r');
1214 			vputc('\n');
1215 		}
1216 	}
1217 }
1218 
1219 /*
1220  * Delete display positions stcol through endcol.
1221  * Amount of use of special terminal features here is limited.
1222  */
1223 physdc(stcol, endcol)
1224 	int stcol, endcol;
1225 {
1226 	register char *tp, *up;
1227 	char *tpe;
1228 	register int i;
1229 	register int nc = endcol - stcol;
1230 
1231 #ifdef IDEBUG
1232 	if (trace)
1233 		tfixnl(), fprintf(trace, "physdc(%d, %d)\n", stcol, endcol);
1234 #endif
1235 	if (!DC || nc <= 0)
1236 		return;
1237 	if (IN) {
1238 		/*
1239 		 * CONCEPT-100 like terminal.
1240 		 * If there are any ``spaces'' in the material to be
1241 		 * deleted, then this is too hard, just retype.
1242 		 */
1243 		vprepins();
1244 		up = vtube0 + stcol;
1245 		i = nc;
1246 		do
1247 			if ((*up++ & (QUOTE|TRIM)) == QUOTE)
1248 				return;
1249 		while (--i);
1250 		i = 2 * nc;
1251 		do
1252 			if (*up == 0 || (*up++ & QUOTE) == QUOTE)
1253 				return;
1254 		while (--i);
1255 		vgotoCL(stcol);
1256 	} else {
1257 		/*
1258 		 * HP like delete mode.
1259 		 * Compute how much text we are moving over by deleting.
1260 		 * If it appears to be faster to just retype
1261 		 * the line, do nothing and that will be done later.
1262 		 * We are assuming 2 output characters per deleted
1263 		 * characters and that clear to end of line is available.
1264 		 */
1265 		i = stcol / WCOLS;
1266 		if (i != endcol / WCOLS)
1267 			return;
1268 		i += LINE(vcline);
1269 		stcol %= WCOLS;
1270 		endcol %= WCOLS;
1271 		up = vtube[i]; tp = up + endcol; tpe = up + WCOLS;
1272 		while (tp < tpe && *tp)
1273 			tp++;
1274 		if (tp - (up + stcol) < 2 * nc)
1275 			return;
1276 		vgoto(i, stcol);
1277 	}
1278 
1279 	/*
1280 	 * Go into delete mode and do the actual delete.
1281 	 * Padding is on DC itself.
1282 	 */
1283 	godm();
1284 	for (i = nc; i > 0; i--)
1285 		vputp(DC, DEPTH(vcline));
1286 	vputp(ED, 0);
1287 
1288 	/*
1289 	 * Straighten up.
1290 	 * With CONCEPT like terminals, characters are pulled left
1291 	 * from first following null.  HP like terminals shift rest of
1292 	 * this (single physical) line rigidly.
1293 	 */
1294 	if (IN) {
1295 		up = vtube0 + stcol;
1296 		tp = vtube0 + endcol;
1297 		while (i = *tp++) {
1298 			if ((i & (QUOTE|TRIM)) == QUOTE)
1299 				break;
1300 			*up++ = i;
1301 		}
1302 		do
1303 			*up++ = i;
1304 		while (--nc);
1305 	} else {
1306 		copy(up + stcol, up + endcol, WCOLS - endcol);
1307 		vclrbyte(tpe - nc, nc);
1308 	}
1309 }
1310 
1311 #ifdef TRACE
1312 tfixnl()
1313 {
1314 
1315 	if (trubble || techoin)
1316 		fprintf(trace, "\n");
1317 	trubble = 0, techoin = 0;
1318 }
1319 
1320 tvliny()
1321 {
1322 	register int i;
1323 
1324 	if (!trace)
1325 		return;
1326 	tfixnl();
1327 	fprintf(trace, "vcnt = %d, vcline = %d, vliny = ", vcnt, vcline);
1328 	for (i = 0; i <= vcnt; i++) {
1329 		fprintf(trace, "%d", LINE(i));
1330 		if (FLAGS(i) & VDIRT)
1331 			fprintf(trace, "*");
1332 		if (DEPTH(i) != 1)
1333 			fprintf(trace, "<%d>", DEPTH(i));
1334 		if (i < vcnt)
1335 			fprintf(trace, " ");
1336 	}
1337 	fprintf(trace, "\n");
1338 }
1339 
1340 tracec(c)
1341 	int c;		/* mjm: char --> int */
1342 {
1343 
1344 	if (!techoin)
1345 		trubble = 1;
1346 	if (c == ESCAPE)
1347 		fprintf(trace, "$");
1348 	else if (c & QUOTE)	/* mjm: for 3B (no sign extension) */
1349 		fprintf(trace, "~%c", ctlof(c&TRIM));
1350 	else if (c < ' ' || c == DELETE)
1351 		fprintf(trace, "^%c", ctlof(c));
1352 	else
1353 		fprintf(trace, "%c", c);
1354 }
1355 #endif
1356 
1357 /*
1358  * Put a character with possible tracing.
1359  */
1360 vputch(c)
1361 	int c;
1362 {
1363 
1364 #ifdef TRACE
1365 	if (trace)
1366 		tracec(c);
1367 #endif
1368 	vputc(c);
1369 }
1370