1 /*
2  * The functions in this file handle redisplay. There are two halves, the
3  * ones that update the virtual display screen, and the ones that make the
4  * physical display screen the same as the virtual display screen. These
5  * functions use hints that are left in the windows by the commands.
6  *
7  * $Id: display.c,v 1.580 2021/05/09 21:32:16 Lois.Mansot Exp $
8  */
9 
10 #include	"estruct.h"
11 #include	"edef.h"
12 #include	"pscreen.h"
13 #include	"nefsms.h"
14 
15 #define vMAXINT ((int)((unsigned)(~0)>>1))	/* 0x7fffffff */
16 #define vMAXNEG (-vMAXINT)	/* 0x80000001 */
17 
18 #define	NU_WIDTH 8
19 #define NU_GUTTER 1
20 
21 #if DISP_X11
22 #define FontHasGlyph(ch) gui_isprint(ch)
23 #define UseCellWidth(wp, ch) \
24 	(!w_val(wp, WMDUNICODE_AS_HEX) && FontHasGlyph(ch))
25 #else
26 #define FontHasGlyph(ch) TRUE
27 #define UseCellWidth(wp, ch) \
28 	(!w_val(wp, WMDUNICODE_AS_HEX))
29 #endif
30 
31 #define UseCellWidth2(wp, lp, off) \
32 	UseCellWidth(wp, get_char2(lp, off))
33 
34 #define reset_term_attrs() term.rev(0)
35 
36 #ifdef GVAL_VIDEO
37 #define VIDEOATTRS (VABOLD|VAUL|VAITAL)
38 #define set_term_attrs(a)  term.rev(a ^ ((VIDEO_ATTR) global_g_val(GVAL_VIDEO) & VIDEOATTRS))
39 #else
40 #define set_term_attrs(a)  term.rev(a)
41 #endif
42 
43 #ifdef WMDLINEWRAP		/* overrides left/right scrolling */
44 #define if_LINEWRAP(wp, t, f) w_val(wp, WMDLINEWRAP) ? (t) : (f)
45 #else
46 #define if_LINEWRAP(wp, t, f) (f)
47 #endif
48 
49 #define line_has_newline(lp, bp) \
50 	(b_val(bp, MDNEWLINE) || (lforw(lp) != buf_head(bp)))
51 
52 #define	MRK_EMPTY        "~"
53 #define	MRK_EXTEND_LEFT  "<"
54 #define	MRK_EXTEND_RIGHT ">"
55 
56 VIDEO **vscreen;		/* Virtual screen. */
57 VIDEO **pscreen;		/* Physical screen. */
58 
59 typedef struct {
60     LINE *lp;			/* the line itself */
61     int map;			/* map to line-number in window */
62     int left;			/* offset of leftmost character on line */
63     int right;			/* offset of rightmost character on line */
64 } LMAP;
65 static LMAP *lmap0;		/* where to _not_ alter attributes */
66 static LMAP *lmap;		/* workspace for computing attributes */
67 
68 #if OPT_SCROLLCODE
69 #define CAN_SCROLL 1
70 #else
71 #define CAN_SCROLL 0
72 #endif
73 
74 static int i_displayed;		/* false until we're in screen-mode */
75 static int mpresf;		/* zero if message-line empty */
76 #if OPT_WORKING
77 static int im_timing;
78 #endif
79 
80 /*
81  * MARK2COL may be greater than mark2col if the mark does not point to the end
82  * of the line, and if it points to a nonprinting character.  We use that value
83  * when setting visible attributes, to keep tabs and other nonprinting
84  * characters looking 'right'.
85  */
86 #define MARK2COL(wp, mk)  offs2col(wp, mk.l, mk.o + BytesAt(mk.l, mk.o)) - 1
87 #define mark2col(wp, mk)  offs2col(wp, mk.l, mk.o)
88 
89 #ifdef WMDLINEWRAP
90 #define TopRow(wp) (wp)->w_toprow + (wp)->w_line.o
91 static int allow_wrap;
92 #else
93 #define TopRow(wp) (wp)->w_toprow
94 #endif
95 
96 /* for window size changes */
97 static int chg_width, chg_height;
98 
99 /******************************************************************************/
100 
101 typedef int (*OutFunc) (int c);
102 
103 static OutFunc dfoutfn;
104 
105 static int vtlistc(WINDOW *wp, const char *src, unsigned limit);
106 
107 /*--------------------------------------------------------------------------*/
108 
109 /*
110  * Format a number, right-justified, returning a pointer to the formatted
111  * buffer.
112  */
113 static char *
right_num(char * buffer,int len,long value)114 right_num(char *buffer, int len, long value)
115 {
116     char temp[NSTRING];
117     char *p = lsprintf(temp, "%ld", value);
118     char *q = buffer + len;
119 
120     *q = EOS;
121     while (q != buffer)
122 	*(--q) = (char) ((p != temp) ? *(--p) : ' ');
123     return buffer;
124 }
125 
126 /*
127  * Do format a string.  Return the number of bytes by which 'width' exceeds
128  * that actually written to the output.
129  */
130 static int
dfputsn(OutFunc outfunc,const char * s,int width,int limit)131 dfputsn(OutFunc outfunc, const char *s, int width, int limit)
132 {
133     int l = 0;
134 
135     if (s != 0) {
136 	int length = (int) strlen(s);
137 
138 	if (width < 0)
139 	    width = length;
140 	if (limit > 0 && width > limit)
141 	    width = limit;
142 	if (width > length)
143 	    width = length;
144 
145 	TRACE2(("...str=%s\n", visible_buff(s, width, TRUE)));
146 	while (width-- > 0) {
147 	    (*outfunc) (*s++);
148 	    l++;
149 	}
150     }
151     return l;
152 }
153 
154 /* as above, but uses null-terminated string's length */
155 static int
dfputs(OutFunc outfunc,const char * s)156 dfputs(OutFunc outfunc, const char *s)
157 {
158     return dfputsn(outfunc, s, -1, -1);
159 }
160 
161 /*
162  * Do format an integer, in the specified radix.
163  */
164 static int
dfputi(OutFunc outfunc,UINT i,UINT r)165 dfputi(OutFunc outfunc, UINT i, UINT r)
166 {
167     int q;
168 
169     TRACE2(("...int=%d\n", i));
170 
171     q = (i >= r) ? dfputi(outfunc, i / r, r) : 0;
172 
173     (*outfunc) (hexdigits[i % r]);
174     return q + 1;		/* number of digits printed */
175 }
176 
177 /*
178  * do the same except as a long integer.
179  */
180 static int
dfputli(OutFunc outfunc,ULONG l,UINT r)181 dfputli(OutFunc outfunc, ULONG l, UINT r)
182 {
183     int q;
184 
185     TRACE2(("...long=%ld\n", l));
186 
187     q = (l >= r) ? dfputli(outfunc, (l / r), r) : 0;
188 
189     return q + dfputi(outfunc, (UINT) (l % r), r);
190 }
191 
192 /*
193  * format a floating value with two decimal places
194  */
195 static int
dfputf(OutFunc outfunc,double s)196 dfputf(OutFunc outfunc, double s)
197 {
198     int n = 3;
199     UINT i;			/* integer portion of number */
200     UINT f;			/* fractional portion of number */
201 
202     if (s < 0) {
203 	(*outfunc) ('.');
204 	s = -s;
205 	n++;
206     }
207 
208     /* break it up */
209     i = (UINT) s;
210     f = (UINT) ((s - (double) i) * 100);
211 
212     /* send out the integer portion */
213     n += dfputi(outfunc, i, 10);
214     (*outfunc) ('.');
215     (*outfunc) ((int) (f / 10) + '0');
216     (*outfunc) ((int) (f % 10) + '0');
217     return n;
218 }
219 
220 /*
221  * On entry, *fmt may point to either '*' or a digit.  If either, decode the
222  * appropriate width or limit.
223  */
224 static void
decode_length(const char ** fmt,int * result)225 decode_length(const char **fmt, int *result)
226 {
227     int c = **fmt;
228     int value = 0;
229     int found = FALSE;
230 
231     while (isDigit(c)) {
232 	value = (value * 10) + c - '0';
233 	found = TRUE;
234 	(*fmt)++;
235 	c = **fmt;
236     }
237     if (found)
238 	*result = value;
239 }
240 
241 #define DecodeLength(fmt, app, result) \
242 	if (*fmt == '*') { \
243 	    result = va_arg(app, int); \
244 	    ++fmt; \
245 	} else { \
246 	    decode_length(&fmt, &result); \
247 	}
248 
249 /*
250  * Generic string formatter.  Takes printf-like args, and calls
251  * the global function (*dfoutfn)(c) for each c
252  */
253 static void
dofmt(const char * fmt,va_list app2)254 dofmt(const char *fmt, va_list app2)
255 {
256     int c;			/* current char in format string */
257     int the_width;
258     int the_limit = 0;
259     int n;
260     int islong;
261     UINT uint_value;
262     ULONG ulong_value;
263     int int_value;
264     long long_value;
265     va_list app;
266     OutFunc outfunc = dfoutfn;	/* local copy, for recursion */
267 
268     TRACE2(("dofmt fmt='%s'\n", visible_buff(fmt, strlen(fmt), FALSE)));
269     begin_va_copy(app, app2);
270     while ((c = *fmt++) != 0) {
271 	if (c != '%') {
272 	    (*outfunc) (c);
273 	    continue;
274 	}
275 	the_width = -1;
276 	islong = FALSE;
277 	DecodeLength(fmt, app, the_width);
278 	if (*fmt == '.') {
279 	    ++fmt;
280 	    DecodeLength(fmt, app, the_limit);
281 	    if (the_width < 0)
282 		the_width = the_limit;
283 	} else {
284 	    the_limit = the_width;
285 	}
286 	c = *fmt++;
287 	if (c == 'l') {
288 	    islong = TRUE;
289 	    c = *fmt++;
290 	}
291 	TRACE2(("... fmt='%%%d.%d%c%s'\n",
292 		the_width, the_limit,
293 		c, islong ? "L" : ""));
294 	switch (c) {
295 	case EOS:
296 	    n = 0;
297 	    break;
298 	case 'c':
299 	    (*outfunc) (va_arg(app, int));
300 	    n = 1;
301 	    break;
302 
303 	case 'd':
304 	    if (!islong) {
305 		int_value = va_arg(app, int);
306 		if (int_value < 0) {
307 		    if (int_value < vMAXNEG) {
308 			n = dfputs(outfunc, "OVFL");
309 			break;
310 		    }
311 		    int_value = -int_value;
312 		    (*outfunc) ('-');
313 		}
314 		n = dfputi(outfunc, (UINT) int_value, 10);
315 		break;
316 	    }
317 	    long_value = va_arg(app, long);
318 	    if (long_value < 0) {
319 		long_value = -long_value;
320 		(*outfunc) ('-');
321 	    }
322 	    n = dfputli(outfunc, (ULONG) long_value, 10);
323 	    break;
324 
325 	case 'u':
326 	    if (!islong) {
327 		uint_value = va_arg(app, UINT);
328 		n = dfputi(outfunc, uint_value, 10);
329 		break;
330 	    }
331 	    ulong_value = va_arg(app, ULONG);
332 	    n = dfputli(outfunc, ulong_value, 10);
333 	    break;
334 
335 	case 'o':
336 	    n = dfputi(outfunc, va_arg(app, UINT), 8);
337 	    break;
338 
339 	case 'x':
340 	    if (!islong) {
341 		n = dfputi(outfunc, va_arg(app, UINT), 16);
342 		break;
343 	    }
344 	    /* FALLTHROUGH */
345 	case 'X':
346 	    n = dfputli(outfunc, va_arg(app, ULONG), 16);
347 	    break;
348 
349 	case 's':
350 	    n = dfputsn(outfunc, va_arg(app, char *), the_width, the_limit);
351 	    break;
352 
353 	case 'f':
354 	    n = dfputf(outfunc, va_arg(app, double));
355 	    break;
356 
357 	default:
358 	    (*outfunc) (c);
359 	    n = 1;
360 	    break;
361 	}
362 	the_width -= n;
363 	while (the_width-- > 0) {
364 	    (*outfunc) (' ');
365 	}
366     }
367     end_va_copy(app);
368 }
369 
370 /******************************************************************************/
371 
372 /*
373  * Line-number mode
374  */
375 int
nu_width(WINDOW * wp)376 nu_width(WINDOW *wp)
377 {
378     return w_val(wp, WMDNUMBER) ? NU_WIDTH : 0;
379 }
380 
381 int
col_limit(WINDOW * wp)382 col_limit(WINDOW *wp)
383 {
384     return if_LINEWRAP(wp, curcol + 1, term.cols - 1 - nu_width(wp));
385 }
386 
387 #if OPT_VIDEO_ATTRS
388 static void
set_vattrs(int row,int col,unsigned attr,size_t len)389 set_vattrs(int row, int col, unsigned attr, size_t len)
390 {
391     if ((long) len > 0) {
392 	while (len--)
393 	    vscreen[row]->v_attrs[col++] = (VIDEO_ATTR) attr;
394     }
395 }
396 
397 static void
preset_lmap0(void)398 preset_lmap0(void)
399 {
400     int row;
401     for (row = 0; row <= term.rows; ++row) {
402 	lmap0[row].left = 0;
403 	lmap0[row].right = term.cols;
404     }
405 }
406 
407 /* use this to markup line-numbers and line-break padding, which conveniently
408  * are both on the extreme left/right of the line on the display.
409  */
410 #if OPT_EXTRA_COLOR
411 static void
preset_vattrs(int row,int col,int attr,size_t len)412 preset_vattrs(int row, int col, int attr, size_t len)
413 {
414     set_vattrs(row, col, (unsigned) attr, len);
415     if (col == 0) {
416 	lmap0[row].left = (int) len;
417     } else {
418 	lmap0[row].right = col;
419     }
420 }
421 #endif
422 
423 #else
424 
425 #define preset_lmap0()		/* nothing */
426 #define set_vattrs(row, col, attr, len)		/*nothing */
427 #define preset_vattrs(row, col, attr, len)	/*nothing */
428 
429 #endif
430 
431 static void
freeVIDEO(VIDEO * vp)432 freeVIDEO(VIDEO * vp)
433 {
434     if (vp != 0) {
435 #if OPT_VIDEO_ATTRS
436 	FreeIfNeeded(VideoAttr(vp));
437 #endif
438 	free((char *) vp);
439     }
440 }
441 
442 int
video_alloc(VIDEO ** vpp)443 video_alloc(VIDEO ** vpp)
444 {
445     VIDEO *vp;
446     size_t have = (size_t) ((term.maxcols > 0) ? term.maxcols : 1);
447     size_t need = sizeof(VIDEO_TEXT) * (have - 1);
448 
449     if ((vp = typeallocplus(VIDEO, need)) != 0) {
450 	(void) memset((char *) vp, 0, sizeof(VIDEO) + need);
451 
452 #if OPT_VIDEO_ATTRS
453 	VideoAttr(vp) = typecallocn(VIDEO_ATTR, have);
454 	if (VideoAttr(vp) == 0) {
455 	    FreeAndNull(vp);
456 	}
457 #endif
458 	if (vp != 0) {
459 	    freeVIDEO(*vpp);
460 	    *vpp = vp;
461 	}
462     }
463     return (vp != 0);
464 }
465 
466 static int
vtalloc(void)467 vtalloc(void)
468 {
469     int i, first;
470     static int vcols, vrows;
471 
472     if (term.maxrows > vrows) {
473 	GROW(vscreen, VIDEO *, vrows, term.maxrows);
474 	GROW(pscreen, VIDEO *, vrows, term.maxrows);
475 	GROW(lmap0, LMAP, vrows, ((size_t) term.maxrows + 1));
476 	GROW(lmap, LMAP, vrows, ((size_t) term.maxrows + 1));
477     } else {
478 	for (i = term.maxrows; i < vrows; i++) {
479 	    freeVIDEO(vscreen[i]);
480 	    freeVIDEO(pscreen[i]);
481 	}
482     }
483 
484     first = (term.maxcols > vcols) ? 0 : vrows;
485 
486     for (i = first; i < term.maxrows; ++i) {
487 	if (!video_alloc(&vscreen[i]))
488 	    return FALSE;
489 	if (!video_alloc(&pscreen[i]))
490 	    return FALSE;
491     }
492     vcols = term.maxcols;
493     vrows = term.maxrows;
494 
495     return TRUE;
496 }
497 
498 /* free all video memory, in anticipation of a (growing) resize */
499 #if NO_LEAKS
500 static void
vtfree(void)501 vtfree(void)
502 {
503     int i;
504 
505     if (vscreen) {
506 	for (i = 0; i < term.maxrows; ++i) {
507 	    freeVIDEO(vscreen[i]);
508 	}
509 	free((char *) vscreen);
510 	vscreen = 0;
511     }
512     if (pscreen) {
513 	for (i = 0; i < term.maxrows; ++i) {
514 	    freeVIDEO(pscreen[i]);
515 	}
516 	free((char *) pscreen);
517 	pscreen = 0;
518     }
519     FreeIfNeeded(lmap0);
520     FreeIfNeeded(lmap);
521 }
522 #endif
523 
524 /*
525  * Returns true if the virtual screen has been allocated and initialized,
526  * so we're able to create/update windows.
527  */
528 int
is_vtinit(void)529 is_vtinit(void)
530 {
531     return (vscreen != 0);
532 }
533 
534 /*
535  * Initialize the data structures used by the display code. The edge vectors
536  * used to access the screens are set up.
537  */
538 int
vtinit(void)539 vtinit(void)
540 {
541     int rc = TRUE;
542     int i;
543     VIDEO *vp;
544 
545     beginDisplay();
546     /* allocate new display memory */
547     if (vtalloc() == FALSE) {	/* if we fail, only serious if not a realloc */
548 	rc = is_vtinit();
549     } else {
550 	for (i = 0; i < term.maxrows; ++i) {
551 	    vp = vscreen[i];
552 	    vp->v_flag = 0;
553 #if OPT_COLOR
554 	    ReqFcolor(vp) = gfcolor;
555 	    ReqBcolor(vp) = gbcolor;
556 #endif
557 	}
558 #if OPT_WORKING
559 	if (!i_displayed && !im_displaying)
560 	    imworking(0);
561 #endif
562     }
563     endofDisplay();
564     return rc;
565 }
566 
567 /*
568  * Set the virtual cursor to the specified row and column on the virtual
569  * screen. There is no checking for nonsense values.
570  */
571 static void
vtmove(int row,int col)572 vtmove(int row, int col)
573 {
574     vtrow = row;
575     vtcol = col;
576 }
577 
578 static VIDEO *
current_video(void)579 current_video(void)
580 {
581     VIDEO *vp;
582 
583 #ifdef WMDLINEWRAP
584     if (vtrow < 0) {
585 	static VIDEO *fake_line;
586 	static int length;
587 	if (length != term.maxcols || fake_line == 0) {
588 	    if (video_alloc(&fake_line)) {
589 		length = term.maxcols;
590 	    } else {
591 		freeVIDEO(fake_line);
592 		fake_line = 0;
593 	    }
594 	}
595 	vp = fake_line;
596     } else
597 #endif
598 	vp = vscreen[vtrow];
599 
600     return vp;
601 }
602 
603 static void
mark_extent(int row,int col,int ch)604 mark_extent(int row, int col, int ch)
605 {
606     if (row >= 0 && row < term.maxrows && col >= 0 && col < term.maxcols) {
607 #if OPT_EXTRA_COLOR && OPT_VIDEO_ATTRS
608 	int *attrp = lookup_extra_color(XCOLOR_LINEBREAK);
609 	if (!isEmpty(attrp)) {
610 	    set_vattrs(row, col, (unsigned) *attrp, 1);
611 	    if (ch == MRK_EXTEND_LEFT[0]) {
612 		lmap0[row].left = col + 1;
613 	    } else {
614 		lmap0[row].right = col;
615 	    }
616 	}
617 #endif /* OPT_EXTRA_COLOR */
618 	vscreen[row]->v_text[col] = (VIDEO_TEXT) ch;
619     }
620 }
621 
622 static int
current_limit(void)623 current_limit(void)
624 {
625     return ((vtrow == term.rows - 1)
626 	    ? (term.cols - 1)
627 	    : term.cols);
628 }
629 
630 /*
631  * Given an offset limit and a string, find the offset of the first blank
632  * within that limit.  Compute the corresponding number of columns.
633  */
634 #ifdef WMDLINEWRAP
635 static unsigned
cols_until(WINDOW * wp,const char * src,unsigned limit)636 cols_until(WINDOW *wp, const char *src, unsigned limit)
637 {
638     unsigned n;
639     unsigned cols = 0;
640     int used;
641     int nxt;
642     int adj;
643 
644     for (n = 0; n < limit; ++n) {
645 	if (isBlank(src[n])) {
646 	    break;
647 	}
648 	used = 1;
649 	nxt = (int) (limit - n);
650 	adj = column_sizes(wp, src + n, (unsigned) nxt, &used);
651 
652 #if OPT_MULTIBYTE
653 	if (adj == COLS_UTF8
654 	    && b_is_utfXX(wp->w_bufp)
655 	    && !w_val(wp, WMDUNICODE_AS_HEX)) {
656 	    UINT target;
657 	    int ch2;
658 
659 	    if (vl_conv_to_utf32(&target,
660 				 src + n,
661 				 (B_COUNT) (limit - n)) > 0) {
662 		ch2 = (int) target;
663 	    } else {
664 		ch2 = CharOf(src[n]);
665 	    }
666 	    (void) ch2;
667 	    if (FontHasGlyph(ch2)) {
668 		adj = mb_cellwidth(wp, src + n, nxt);
669 	    }
670 	}
671 #endif
672 	cols += (unsigned) adj;
673 	n += (unsigned) (used - 1);
674     }
675     return cols;
676 }
677 #endif
678 
679 /*
680  * Write a character to the virtual screen.  The virtual row and column are
681  * updated.  Only print characters if they would be "visible".  If the line is
682  * too long put a ">" in the last column.  This routine only puts printing
683  * characters into the virtual terminal buffers.  Only column overflow is
684  * checked.
685  *
686  * Returns the number of bytes eaten from the source.
687  */
688 static int
vtputc(WINDOW * wp,const char * src,unsigned limit)689 vtputc(WINDOW *wp, const char *src, unsigned limit)
690 {
691     /* since we don't allow wrapping on the message line, we only need
692      * to evaluate this once.  */
693     int lastcol = current_limit();
694     int rc = 1;
695     UINT ch = (UCHAR) (*src);
696     VIDEO *vp = current_video();
697 
698     if (vp != 0) {
699 	if (isPrint(ch) && vtcol >= 0 && vtcol < lastcol) {
700 #if OPT_MULTIBYTE
701 	    if (!b_is_utfXX(wp->w_bufp) && (vl_encoding >= enc_UTF8)) {
702 		int ch2;
703 		if (vl_8bit_to_ucs(&ch2, (int) ch)) {
704 		    ch = (UINT) ch2;
705 		}
706 	    }
707 #endif
708 #ifdef WMDLINEWRAP
709 	    if ((allow_wrap != 0)
710 		&& w_val(wp, WMDLINEBREAK)
711 		&& w_val(wp, WMDLINEWRAP)
712 		&& vtcol > 0
713 		&& VideoText(vp)[vtcol - 1] == ' '
714 		&& !isBlank(ch)) {
715 		unsigned n = cols_until(wp, src, limit);
716 		int have = term.cols - vtcol;
717 
718 		if (n < limit)
719 		    ++n;
720 		if ((int) n > have) {
721 #if OPT_EXTRA_COLOR
722 		    int *attrp = lookup_extra_color(XCOLOR_LINEBREAK);
723 		    if (!isEmpty(attrp)) {
724 			preset_vattrs(vtrow, vtcol, *attrp,
725 				      (size_t) term.cols - (size_t) vtcol);
726 		    }
727 #endif /* OPT_EXTRA_COLOR */
728 		    while (vtcol < term.cols)
729 			VideoText(vp)[vtcol++] = ' ';
730 		    vtcol = 0;
731 		    ++vtrow;
732 		    vp = current_video();
733 		    vp->v_flag |= VFCHG;
734 		    horscroll += lastcol;
735 		}
736 	    }
737 #endif
738 	    VideoText(vp)[vtcol++] = (VIDEO_TEXT) ch;
739 #ifdef WMDLINEWRAP
740 	    if ((allow_wrap != 0)
741 		&& (vtcol == lastcol)
742 		&& (vtrow < allow_wrap)) {
743 		vtcol = 0;
744 		if (++vtrow >= 0)
745 		    vscreen[vtrow]->v_flag |= VFCHG;
746 		horscroll += lastcol;
747 	    }
748 #endif
749 	} else if (vtcol >= lastcol) {
750 	    mark_extent(vtrow, lastcol - 1, MRK_EXTEND_RIGHT[0]);
751 	} else if (isTab(ch)) {
752 	    do {
753 		vtputc(wp, " ", 1);
754 	    } while (((vtcol + horscroll) % wp->w_tabstop) != 0
755 		     && vtcol < lastcol);
756 	} else if (!is_record_sep(wp->w_bufp, (int) ch)) {
757 	    if (isPrint(ch)) {
758 		++vtcol;
759 	    } else {
760 		rc = vtlistc(wp, src, limit);
761 	    }
762 	}
763     }
764     return rc;
765 }
766 
767 static void
vtputsn(WINDOW * wp,const char * src,size_t n)768 vtputsn(WINDOW *wp, const char *src, size_t n)
769 {
770     if (src != 0) {
771 	while (n != 0 && *src != EOS) {
772 	    vtputc(wp, src++, (unsigned) (n--));
773 	}
774     }
775 }
776 
777 /*
778  * Shows non-printing character.  Returns the number of bytes eaten from the
779  * source, which will always be one except when decoding UTF-8.
780  */
781 static int
vtlistc(WINDOW * wp,const char * src,unsigned limit)782 vtlistc(WINDOW *wp, const char *src, unsigned limit)
783 {
784     int rc = 1;
785     char temp[10];
786     unsigned n = 0;
787 
788 #if OPT_MULTIBYTE
789     if (b_is_utfXX(wp->w_bufp)) {
790 	UINT value;
791 	int need = column_sizes(wp, src, limit, &rc);
792 	int cells;
793 
794 	switch (need) {
795 	case COLS_UTF8:
796 	    rc = vl_conv_to_utf32(&value, src, (B_COUNT) limit);
797 	    cells = vl_wcwidth((int) value);
798 
799 	    if (!w_val(wp, WMDUNICODE_AS_HEX)
800 		&& cells == 1
801 		&& isPrint(value)
802 		&& FoldTo8bits((int) value)) {
803 		temp[n++] = (char) value;
804 	    } else if (cells > 0
805 		       && term_is_utfXX()
806 		       && UseCellWidth(wp, (int) value)) {
807 		/*
808 		 * It does not fit into the "8bit" mapping, but is printable
809 		 * (since the number of cells is nonzero).  Write the Unicode
810 		 * value directly to the grid (do not try to recur back here
811 		 * again).
812 		 */
813 		VIDEO *vp = current_video();
814 		int lastcol = current_limit();
815 
816 		if (vtcol >= 0 && vtcol + cells <= lastcol) {
817 		    /* FIXME - adapt the wrapping logic and margin marker too */
818 		    VideoText(vp)[vtcol++] = (VIDEO_TEXT) value;
819 		    /*
820 		     * Pad multicell values with 0's which the terminal driver
821 		     * has to remove.  The padding lets us update 'vtcol' in a
822 		     * simple way.
823 		     */
824 		    while (--cells > 0)
825 			VideoText(vp)[vtcol++] = 0;
826 #ifdef WMDLINEWRAP
827 		    if ((allow_wrap != 0)
828 			&& (vtcol == lastcol)
829 			&& (vtrow < allow_wrap)) {
830 			vtcol = 0;
831 			if (++vtrow >= 0)
832 			    vscreen[vtrow]->v_flag |= VFCHG;
833 			horscroll += lastcol;
834 		    }
835 #endif
836 		} else if (vtcol >= lastcol) {
837 		    if (VideoText(vp)[lastcol - 1] == 0)
838 			mark_extent(vtrow, lastcol - 2, MRK_EXTEND_RIGHT[0]);
839 		    mark_extent(vtrow, lastcol - 1, MRK_EXTEND_RIGHT[0]);
840 		} else {
841 		    int nulls = cells;
842 		    /* have to account for split-characters... */
843 		    while (cells-- > 0 && vtcol <= lastcol) {
844 			mark_extent(vtrow, vtcol++, MRK_EXTEND_RIGHT[0]);
845 		    }
846 #ifdef WMDLINEWRAP
847 		    if (allow_wrap != 0
848 			&& (vtrow < allow_wrap)) {
849 			vtcol = 0;
850 			if (++vtrow >= 0) {
851 			    vscreen[vtrow]->v_flag |= VFCHG;
852 			    vp = current_video();
853 			    VideoText(vp)[vtcol++] = (VIDEO_TEXT) value;
854 			    while (--nulls > 0)
855 				VideoText(vp)[vtcol++] = 0;
856 			}
857 		    }
858 #endif
859 		}
860 		return rc;
861 	    } else {
862 		/* FIXME - some of recode's UTF-8/UTF-16 gives 6-digit codes
863 		 * rather than 4 - but the low 4 digits are the same.
864 		 */
865 		sprintf(temp, "\\u%04X", value & ((1 << MaxCBits) - 1));
866 		n = (unsigned) need;
867 	    }
868 	    break;
869 	case COLS_CTRL:
870 	    if (isPrint(*src)) {
871 		temp[n++] = *src;
872 	    } else {
873 		temp[n++] = '^';
874 		temp[n++] = (char) toalpha(*src);
875 	    }
876 	    break;
877 	case COLS_8BIT:
878 	    /*
879 	     * Recover from illegal bytes...
880 	     */
881 	    TRACE2(("vtlistc illegal:%#x\n", CharOf(*src)));
882 	    sprintf(temp, "\\?%02X", CharOf(*src));
883 	    n = (unsigned) need;
884 	    break;
885 	default:
886 	    temp[n++] = *src;
887 	    break;
888 	}
889     } else
890 #endif
891     if (isPrint(*src)) {
892 	temp[n++] = *src;
893     } else {
894 	int need = column_sizes(wp, src, limit, &rc);
895 
896 	switch (need) {
897 	case COLS_8BIT:
898 	    temp[n++] = BACKSLASH;
899 	    if (w_val(wp, WMDNONPRINTOCTAL)) {
900 		temp[n++] = (char) (((*src >> 6) & 3) + '0');
901 		temp[n++] = (char) (((*src >> 3) & 7) + '0');
902 		temp[n++] = (char) (((*src) & 7) + '0');
903 	    } else {
904 		temp[n++] = 'x';
905 		temp[n++] = hexdigits[(*src >> 4) & 0xf];
906 		temp[n++] = hexdigits[(*src) & 0xf];
907 	    }
908 	    break;
909 	case COLS_CTRL:
910 	    temp[n++] = '^';
911 	    temp[n++] = (char) toalpha(*src);
912 	    break;
913 	default:
914 	    temp[n++] = *src;
915 	    break;
916 	}
917     }
918     vtputsn(wp, temp, (size_t) n);
919     return rc;
920 }
921 
922 #if OPT_MULTIBYTE
923 static int
vtset_put(WINDOW * wp,const char * src,unsigned limit)924 vtset_put(WINDOW *wp, const char *src, unsigned limit)
925 {
926     int rc;
927 
928     if (w_val(wp, WMDLIST)) {
929 	rc = vtlistc(wp, src, limit);
930     } else if (b_is_utfXX(wp->w_bufp)
931 	       && column_sizes(wp, src, limit, &rc) >= COLS_8BIT) {
932 	rc = vtlistc(wp, src, limit);
933     } else {
934 	rc = vtputc(wp, src, limit);
935     }
936     return rc;
937 }
938 #else
939 #define vtset_put(wp, src, n) (w_val(wp, WMDLIST) \
940 				? vtlistc(wp, src, n) \
941 				: vtputc(wp, src, n))
942 #endif
943 /*
944  * Write a line to the screen at the current video coordinates, allowing for
945  * line-wrap or right-shifting.
946  */
947 static void
vtset(LINE * lp,WINDOW * wp)948 vtset(LINE *lp, WINDOW *wp)
949 {
950 #if OPT_TRACE > 1
951     int save_vtrow = vtrow;
952 #endif
953     char *from;
954     int n = llength(lp);
955     BUFFER *bp = wp->w_bufp;
956     int skip = -vtcol;
957     int list = w_val(wp, WMDLIST);
958 
959     TRACE2(("vtset line %d (top %d.%d), row %d \n",
960 	    line_no(wp->w_bufp, lp),
961 	    line_no(wp->w_bufp, wp->w_line.l),
962 	    wp->w_line.o,
963 	    vtrow));
964     wp->w_tabstop = tabstop_val(wp->w_bufp);
965 
966 #ifdef WMDLINEWRAP
967     /*
968      * If the window's offset is negative, we've got a case of linewrap
969      * where the line's beginning is forced before the beginning of the
970      * window.
971      */
972     if (wp->w_line.o < 0) {
973 	vtrow -= wp->w_line.o;
974 	skip = col2offs(wp, lp, -(wp->w_line.o * term.cols));
975 	n -= skip;
976     } else
977 #endif
978     if (w_val(wp, WMDNUMBER)) {
979 	int j, k, jk;
980 	L_NUM line = line_no(bp, lp);
981 	char temp[NU_WIDTH + 2];
982 
983 	vtcol = 0;		/* make sure we always see line numbers */
984 	vtputsn(wp,
985 		right_num(temp, NU_WIDTH - NU_GUTTER, (long) line),
986 		(size_t) (NU_WIDTH - NU_GUTTER));
987 	vtputsn(wp, "  ", (size_t) NU_GUTTER);
988 #if OPT_EXTRA_COLOR
989 	{
990 	    int *attrp = lookup_extra_color(XCOLOR_LINENUMBER);
991 	    if (!isEmpty(attrp)) {
992 		preset_vattrs(vtrow, 0, *attrp, (NU_WIDTH - NU_GUTTER));
993 	    }
994 	}
995 #endif /* OPT_EXTRA_COLOR */
996 	horscroll = skip - vtcol;
997 
998 	/* account for leading fill; this repeats logic in vtputc so
999 	 * I don't have to introduce a global variable... */
1000 	from = lvalue(lp);
1001 	for (j = k = jk = 0; (j < n) && (k < skip);) {
1002 	    int c = from[j];
1003 	    int used = 1;
1004 	    if ((list || !isTab(c)) && !isPrint(c)) {
1005 		k += column_sizes(wp, from + j, (UINT) (n - j), &used);
1006 	    } else {
1007 		if (isTab(c)) {
1008 		    k += (wp->w_tabstop - (k % wp->w_tabstop));
1009 		} else if (isPrint(c)) {
1010 		    k++;
1011 		}
1012 	    }
1013 	    j += used;
1014 	    jk = j;
1015 	}
1016 	while (k-- > skip)
1017 	    vtputc(wp, " ", 1);
1018 	if ((skip = jk) < 0)
1019 	    skip = 0;
1020 	n -= skip;
1021     } else
1022 	skip = 0;
1023 
1024 #if OPT_B_LIMITS
1025     horscroll -= w_left_margin(wp);
1026 #endif
1027     from = lvalue(lp) + skip;
1028 #ifdef WMDLINEWRAP
1029     allow_wrap = w_val(wp, WMDLINEWRAP) ? mode_row(wp) - 1 : 0;
1030 #endif
1031 
1032     /*
1033      * The loops below are split up for a reason - there's a hidden side effect
1034      * which makes the final increment of vtcol inconsistent, so it won't
1035      * terminate until the end of the line.  For very long lines, that's a
1036      * performance hit.
1037      */
1038 #define VTSET_PUT() \
1039 	int used = vtset_put(wp, from, (UINT) n); \
1040 	from += used; n -= used
1041 
1042 #define VTSET_LOOP(condition) \
1043 	while ((vtcol < term.cols) && (condition)) { \
1044 	    VTSET_PUT(); \
1045 	} \
1046 	if ((vtcol <= term.cols) && (condition)) { \
1047 	    VTSET_PUT(); \
1048 	}
1049 
1050 #ifdef WMDLINEWRAP
1051     if (w_val(wp, WMDLINEWRAP)) {
1052 	VTSET_LOOP(((vtrow == term.rows - 1) || (vtrow < mode_row(wp)))
1053 		   && (n > 0));
1054     } else
1055 #endif
1056     {
1057 	VTSET_LOOP((n > 0));
1058     }
1059 
1060     /* Display a "^J" if 'list' mode is active, unless we've suppressed
1061      * it for some reason.
1062      */
1063     if (list && (n >= 0)) {
1064 	if (b_is_scratch(bp) && listrimmed(lp))
1065 	    /*EMPTY */ ;
1066 	else if (line_has_newline(lp, bp)) {
1067 	    const char *s = get_record_sep(bp);
1068 	    UINT len = (UINT) len_record_sep(bp);
1069 	    while (len != 0)
1070 		vtlistc(wp, s++, len--);
1071 	}
1072     }
1073 #ifdef WMDLINEWRAP
1074     allow_wrap = 0;
1075 #endif
1076 #if OPT_TRACE > 1
1077     while (save_vtrow <= vtrow) {
1078 	/*
1079 	 * Display the row(s) which we just wrote to the virtual screen.
1080 	 *
1081 	 * FIXME - need to compute length from the number of columns.
1082 	 * This is only assuming columns==cells.
1083 	 */
1084 	TRACE2(("TEXT %4d:%s\n",
1085 		save_vtrow,
1086 		visible_video_text(vscreen[save_vtrow]->v_text,
1087 				   (save_vtrow == vtrow)
1088 				   ? vtcol
1089 				   : term.cols)));
1090 	TRACE3(("ATTR     :%s\n",
1091 		visible_video_attr(vscreen[save_vtrow]->v_attrs,
1092 				   (save_vtrow == vtrow)
1093 				   ? vtcol
1094 				   : term.cols)));
1095 	++save_vtrow;
1096     }
1097 #endif
1098 }
1099 
1100 /*
1101  * Erase from the end of the software cursor to the end of the line on which
1102  * the software cursor is located.
1103  */
1104 static void
vteeol(void)1105 vteeol(void)
1106 {
1107     if (vtcol < term.cols) {
1108 	int n = (vtcol >= 0) ? vtcol : 0;
1109 #ifdef WMDLINEWRAP
1110 	if (vtrow >= 0)
1111 #endif
1112 	    while (n < term.cols) {
1113 		vscreen[vtrow]->v_text[n++] = ' ';
1114 	    }
1115 	vtcol = term.cols;
1116     }
1117 }
1118 
1119 /* upscreen:	user routine to force a screen update
1120 		always finishes complete update		*/
1121 #if !SMALLER
1122 /* ARGSUSED */
1123 int
upscreen(int f GCC_UNUSED,int n GCC_UNUSED)1124 upscreen(int f GCC_UNUSED, int n GCC_UNUSED)
1125 {
1126     return update(TRUE);
1127 }
1128 #endif
1129 
1130 static UINT scrflags;
1131 
1132 /*
1133  * Convert MARK's offset to virtual column.  The virtual column counts the left
1134  * margin, the sideways shift of the window, etc., up to the given MARK.
1135  *
1136  * The buffer may have a left-margin, e.g., [Registers].  Use the 'bp'
1137  * parameter to get that value.
1138  *
1139  * To allow this to be used in the OPT_CACHE_VCOL logic, it accepts a 'column'
1140  * parameter which in that case may be nonzero, e.g., the nominal column of the
1141  * left margin of the display (the column at which the sideways-shift
1142  * coincides).
1143  *
1144  * Finally, use 'adjust' to adjust for cases where the sideways-shift does
1145  * not exactly coincide with the 'column', where nonprinting data is displayed
1146  * in hex/octal/uparrow format and is split across the display's left margin.
1147  * After adding the left margin, that gives the index into the line for
1148  * reading bytes whose width is added til we read the mark's offset.
1149  */
1150 int
mk_to_vcol(WINDOW * wp,MARK mark,int expanded,int column,int adjust)1151 mk_to_vcol(WINDOW *wp, MARK mark, int expanded, int column, int adjust)
1152 {
1153     BUFFER *bp = wp->w_bufp;
1154     int i = b_left_margin(bp) + adjust;
1155     int ch, c0;
1156     int limit;
1157     int t = tabstop_val(bp);
1158     LINE *lp;
1159     int extra = ((!global_g_val(GMDALTTABPOS) && !insertmode) ? 1 : 0);
1160     const char *text;
1161     int prev_col = column;
1162     int my_bytes = BytesAt(mark.l, mark.o);
1163 #ifdef WMDLINEWRAP
1164     int nu_adj = nu_width(wp);
1165 #endif
1166 
1167     TRACE2((T_CALLED "mk_to_vcol(mark.o=%d, col=%d, adjust=%d) extra %d\n",
1168 	    mark.o, column, adjust, extra));
1169 
1170     if (i < 0) {
1171 	i = 0;
1172 	column = 0;
1173     }
1174     lp = mark.l;
1175     limit = mark.o + (extra ? my_bytes : 0);
1176     if (limit > llength(lp))
1177 	limit = llength(lp);
1178 
1179     text = lvalue(lp);
1180     if (column == 0 || !mark.o) {
1181 	ch = EOF;
1182     } else if (mark.o) {
1183 	ch = text[mark.o - 1];
1184     } else {
1185 	ch = EOF;
1186     }
1187     while (i < limit) {
1188 	int used = 1;
1189 
1190 	prev_col = column;
1191 	c0 = ch;
1192 	ch = text[i];
1193 #ifdef WMDLINEWRAP
1194 	if (column != 0
1195 	    && w_val(wp, WMDLINEBREAK)
1196 	    && w_val(wp, WMDLINEWRAP)
1197 	    && isBlank(c0)) {
1198 	    int nu_col = (column + nu_adj);
1199 	    int k = (int) cols_until(wp, text + i, (unsigned) (llength(lp) - i));
1200 	    int wide = term.cols;
1201 	    int have = (nu_col % wide) + k;
1202 
1203 	    if (have >= wide) {
1204 		int wrap = (nu_col / wide);
1205 		int need = (wrap + 1) * wide;
1206 		nu_col = need;
1207 		column = nu_col - nu_adj;
1208 		prev_col = column;
1209 	    }
1210 	}
1211 #endif
1212 	if (isTab(ch) && !expanded) {
1213 	    column += t - (column % t);
1214 	}
1215 #if OPT_MULTIBYTE
1216 	else if (b_is_utfXX(wp->w_bufp)) {
1217 	    int nxt = llength(mark.l) - i;
1218 	    int adj = column_sizes(wp, text + i, (unsigned) nxt, &used);
1219 
1220 	    if (adj == COLS_UTF8 && UseCellWidth2(wp, lp, i)) {
1221 		adj = mb_cellwidth(wp, text + i, nxt);
1222 	    }
1223 	    column += adj;
1224 	}
1225 #endif
1226 	else if (!isPrint(ch)) {
1227 	    column += column_sizes(wp,
1228 				   text + i,
1229 				   (UINT) (llength(mark.l) - i),
1230 				   &used);
1231 	} else {
1232 	    ++column;
1233 	}
1234 	i += used;
1235     }
1236     if (extra && (column != 0) && (mark.o < llength(lp))) {
1237 	column = (my_bytes == 1 && b_is_utfXX(bp)) ? (column - 1) : prev_col;
1238     }
1239     return2Code(column);
1240 }
1241 
1242 /*
1243  * Convert the window's DOT into a virtual column.  This is used both for the
1244  * ruler, as well as for updating the cursor position, and for very long lines
1245  * can be slow.  So we try to cache a good starting point to allow rapid
1246  * movement along the line.
1247  */
1248 static int
dot_to_vcol(WINDOW * wp)1249 dot_to_vcol(WINDOW *wp)
1250 {
1251     BUFFER *bp = wp->w_bufp;
1252     int result;
1253 #if OPT_CACHE_VCOL
1254     W_TRAITS *wt = &(wp->w_traits);
1255     int need_col = FALSE;
1256     int shift = (w_val(wp, WVAL_SIDEWAYS)
1257 		 ? w_val(wp, WVAL_SIDEWAYS) - 1
1258 		 : 0);
1259     int use_off = -b_left_margin(bp);
1260 #if OPT_TRACE
1261     int check;
1262 #endif
1263 
1264     TRACE2((T_CALLED "dot_to_vcol(%s)\n", bp->b_bname));
1265 
1266 #if OPT_TRACE && OPT_DEBUG
1267     TRACE(("HAVE: dot.o %d, left_dot %d+%d, left_col %d\n",
1268 	   wp->w_dot.o,
1269 	   wt->w_left_dot.o, use_off,
1270 	   wt->w_left_col));
1271     TRACE(("%s\n", lp_visible(wp->w_dot.l)));
1272     TRACE(("DOT:%s\n", visible_buff(lvalue(wp->w_dot.l) + wp->w_dot.o,
1273 				    llength(wp->w_dot.l) - wp->w_dot.o,
1274 				    FALSE)));
1275 #endif
1276 
1277     /*
1278      * If we've moved to a new line, or if the 'list' mode is changed,
1279      * we'll need to recompute the whole state.
1280      */
1281     if (wt->w_left_dot.l != wp->w_dot.l
1282 	|| wt->w_list_opt != w_val(wp, WMDLIST)
1283 	|| wt->w_num_cols != term.cols) {
1284 	wt->w_left_dot.l = wp->w_dot.l;
1285 	wt->w_left_dot.o = 0;
1286 	wt->w_left_col = 0;
1287 	wt->w_left_adj = 0;
1288 	wt->w_list_opt = w_val(wp, WMDLIST);
1289 	wt->w_num_cols = term.cols;
1290 	need_col = TRUE;
1291     }
1292 #ifdef WMDLINEWRAP
1293     if (w_val(wp, WMDLINEWRAP)) {
1294 	int on_1st = (nu_width(wp) + w_left_margin(wp));
1295 	int lo = wt->w_left_dot.o;
1296 	int hi = lo + term.cols;	/* estimate (may be lower) */
1297 	int inside;
1298 	int row;
1299 	int col = wt->w_left_col;
1300 
1301 	/*
1302 	 * Normally 'hi' is a good estimate since each column in the display
1303 	 * corresponds to one or more bytes of the displayed data.  But if we
1304 	 * are in linebreak mode, then we can get some extra columns on each
1305 	 * row.  That can make the estimate too high, causing the chunk of
1306 	 * code tested by 'inside' to not be executed.
1307 	 *
1308 	 * FIXME - it would be nice to cache the offset corresponding to the
1309 	 * right-limit of the row containing dot, e.g., wt->w_right_dot.o
1310 	 */
1311 	if (w_val(wp, WMDLINEBREAK)) {
1312 	    int chk = hi;
1313 	    int tst;
1314 	    char *text = lvalue(wp->w_dot.l);
1315 
1316 	    while (chk < llength(wp->w_dot.l) && !isBlank(text[chk])) {
1317 		++chk;
1318 	    }
1319 	    tst = offs2col(wp, wp->w_dot.l, chk);
1320 	    if (tst > hi) {
1321 		chk = hi;
1322 		do {
1323 		    while (chk >= 0 && !isBlank(text[chk])) {
1324 			--chk;
1325 		    }
1326 		    if (chk >= 0) {
1327 			tst = offs2col(wp, wp->w_dot.l, chk);
1328 		    } else {
1329 			break;
1330 		    }
1331 		} while (--chk >= 0 && tst > hi);
1332 	    }
1333 	    hi = tst;
1334 	}
1335 
1336 	inside = (wp->w_dot.o >= lo && wp->w_dot.o < hi);
1337 	TRACE(("dot.o %d is %s [%d..%d]\n",
1338 	       wp->w_dot.o,
1339 	       inside ? "inside" : "NOT inside",
1340 	       lo, hi - 1));
1341 
1342 	if (!inside) {
1343 	    col = offs2col(wp, wt->w_left_dot.l, wp->w_dot.o);
1344 	    TRACE(("w_dot offs2col(%d) = %d\n", wp->w_dot.o, col));
1345 	    row = col / term.cols;
1346 	    TRACE(("...in row %d\n", row));
1347 	    col = row * term.cols - on_1st;
1348 	    if (col < 0)
1349 		col = 0;
1350 	    TRACE(("...row %d begins with col %d\n", row, col));
1351 	}
1352 
1353 	if (wt->w_left_col != col) {
1354 	    TRACE(("left_col %d vs %d\n", wt->w_left_col, col));
1355 	    wt->w_left_col = col;
1356 
1357 	    wt->w_left_dot.o = col2offs(wp, wt->w_left_dot.l, col + on_1st);
1358 	    TRACE(("...left_dot col2offs(%d + %d) = %d\n",
1359 		   col, on_1st, wt->w_left_dot.o));
1360 	}
1361     } else
1362 #endif
1363 	if ((wt->w_left_col + wt->w_left_adj < shift)
1364 	    || (wt->w_left_col > shift)
1365 	    || (wp->w_dot.o < wt->w_left_dot.o)) {
1366 	int base = nu_width(wp) + w_left_margin(wp) - 1;
1367 	int col;
1368 
1369 	TRACE(("...change dot %d(%d) w_left_col from [%d..%d] to match shift %d, base %d\n",
1370 	       wp->w_dot.o,
1371 	       wt->w_left_dot.o,
1372 	       wt->w_left_col,
1373 	       wt->w_left_col + wt->w_left_adj,
1374 	       shift, base));
1375 
1376 	col = offs2col(wp, wt->w_left_dot.l, wp->w_dot.o) + shift - base;
1377 	TRACE(("...compare col %d to shift %d\n", col, shift));
1378 	if (col < shift) {
1379 	    wt->w_left_col = col;
1380 	} else {
1381 	    wt->w_left_col = shift;
1382 	}
1383 	wt->w_left_adj = 0;
1384 
1385 	wt->w_left_dot.o = col2offs(wp,
1386 				    wt->w_left_dot.l,
1387 				    wt->w_left_col + base - shift);
1388 	TRACE(("...col2offs(%d) = %d\n", wt->w_left_col, wt->w_left_dot.o));
1389 
1390 	TRACE(("...compare w_left_dot.o %d to w_dot.o %d\n",
1391 	       wt->w_left_dot.o, wp->w_dot.o));
1392 	if (wt->w_left_dot.o > wp->w_dot.o) {
1393 	    need_col = TRUE;	/* dot is inconsistent with shift */
1394 	    wt->w_left_dot.o = wp->w_dot.o;
1395 	} else {
1396 	    col = offs2col(wp, wt->w_left_dot.l, wt->w_left_dot.o) + shift - base;
1397 	    TRACE(("...offs2col(%d) = %d\n", wt->w_left_dot.o, col));
1398 
1399 	    TRACE(("...compare col %d to w_left_col %d\n",
1400 		   col, wt->w_left_col));
1401 	    if (col < wt->w_left_col) {
1402 		/* if there was a multi-column character at the left
1403 		 * side of the shifted screen, adjust */
1404 		wt->w_left_adj = wt->w_left_col - col;
1405 		TRACE(("...adjust for multi-column character %d..%d -> %d\n",
1406 		       col,
1407 		       wt->w_left_col,
1408 		       wt->w_left_adj));
1409 		wt->w_left_col = col;
1410 	    }
1411 	    need_col = FALSE;
1412 	}
1413     }
1414 
1415     if (need_col) {
1416 	wt->w_left_col = 0;
1417 	if (wt->w_left_dot.o > 0) {
1418 	    wt->w_left_col = mk_to_vcol(wp,
1419 					wt->w_left_dot,
1420 					w_val(wp, WMDLIST),
1421 					0,
1422 					0);
1423 	    TRACE(("...cache w_left_col %d\n", wt->w_left_col));
1424 	}
1425     }
1426     if (use_off != 0) {
1427 	if (wt->w_left_col == 0)
1428 	    use_off = -wt->w_left_dot.o;
1429     }
1430     result = mk_to_vcol(wp,
1431 			wp->w_dot,
1432 			w_val(wp, WMDLIST),
1433 			wt->w_left_col,
1434 			wt->w_left_dot.o + use_off);
1435 #if OPT_TRACE
1436     check = mk_to_vcol(wp, wp->w_dot, w_val(wp, WMDLIST), 0, 0);
1437     if (check != result) {
1438 	TRACE(("MISMATCH result %d check %d\n", result, check));
1439 	kbd_alarm();
1440 	TRACE(("-> OOPS: dot.o %d, left_dot %d+%d, left_col %d\n",
1441 	       wp->w_dot.o,
1442 	       wt->w_left_dot.o, use_off,
1443 	       wt->w_left_col));
1444 	result = check;
1445     }
1446 #endif
1447 #else
1448     (void) bp;
1449     result = mk_to_vcol(wp, wp->w_dot, w_val(wp, WMDLIST), 0, 0);
1450 #endif
1451     return2Code(result);
1452 }
1453 
1454 /*
1455  * Update a single line. This does not know how to use insert or delete
1456  * character sequences; we are using VT52 functionality. Update the physical
1457  * row and column variables. It does try an exploit erase to end of line.
1458  *
1459  * row     - row of screen to update
1460  * colfrom - col to start updating from
1461  * colto   - col to go to
1462  */
1463 
1464 #if OPT_PSCREEN
1465 /*
1466  * Update a single row on the physical screen.
1467  */
1468 static void
update_line(int row,int colfrom,int colto)1469 update_line(int row, int colfrom, int colto)
1470 {
1471     VIDEO_TEXT *vc, *pc, *evc;
1472     VIDEO_ATTR *va, *pa, xx;
1473     int nchanges = 0;
1474 
1475     if ((vscreen[row]->v_flag & VFCHG) == 0)
1476 	return;
1477 
1478     vc = &vscreen[row]->v_text[colfrom];
1479     evc = &vscreen[row]->v_text[colto];
1480     va = &vscreen[row]->v_attrs[colfrom];
1481     pc = &CELL_TEXT(row, colfrom);
1482     pa = &CELL_ATTR(row, colfrom);
1483 
1484     while (vc < evc) {
1485 	xx = *va;
1486 #ifdef GVAL_VIDEO
1487 	xx ^= (VIDEO_ATTR) global_g_val(GVAL_VIDEO);
1488 #endif
1489 	if (*vc != *pc || VATTRIB(xx) != VATTRIB(*pa)) {
1490 	    *pc = *vc;
1491 	    *pa = xx | VADIRTY;
1492 	    nchanges++;
1493 	}
1494 	vc++;
1495 	pc++;
1496 	va++;
1497 	pa++;
1498     }
1499 
1500     if (nchanges > 0)
1501 	MARK_LINE_DIRTY(row);
1502     vscreen[row]->v_flag &= ~(VFCHG | VFCOL);	/* mark virtual line updated */
1503 }
1504 
1505 #else /* !OPT_PSCREEN */
1506 
1507 /*
1508  * UPDATELINE code for all other versions
1509  */
1510 static void
update_line(int row,int colfrom,int colto)1511 update_line(int row, int colfrom, int colto)
1512 {
1513     struct VIDEO *vp1 = vscreen[row];	/* virtual screen image */
1514     struct VIDEO *vp2 = pscreen[row];	/* physical screen image */
1515     int xl = colfrom;
1516     int xr = colto;
1517     int xx;
1518 
1519     VIDEO_TEXT *cp1 = VideoText(vp1);
1520     VIDEO_TEXT *cp2 = VideoText(vp2);
1521     int nbflag;			/* non-blanks to the right flag? */
1522 
1523 #if OPT_VIDEO_ATTRS
1524     VIDEO_ATTR *ap1 = VideoAttr(vp1);
1525     VIDEO_ATTR *ap2 = VideoAttr(vp2);
1526     VIDEO_ATTR Blank = 0;	/* FIXME: Color? */
1527 #else
1528     UINT rev;			/* reverse video flag */
1529     UINT req;			/* reverse video request flag */
1530 #endif
1531 
1532 #if !OPT_VIDEO_ATTRS
1533 #if OPT_COLOR
1534     term.setfore(ReqFcolor(vp1));
1535     term.setback(ReqBcolor(vp1));
1536 #endif
1537 
1538 #if OPT_REVSTA || OPT_COLOR
1539     /* if we need to change the reverse video status of the
1540        current line, we need to re-write the entire line     */
1541     rev = (vp1->v_flag & VFREV) == VFREV;
1542     req = (vp1->v_flag & VFREQ) == VFREQ;
1543     if ((rev != req)
1544 #if OPT_COLOR
1545 	|| (CurFcolor(vp1) != ReqFcolor(vp1))
1546 	|| (CurBcolor(vp1) != ReqBcolor(vp1))
1547 #endif
1548 	) {
1549 	movecursor(row, colfrom);	/* Go to start of line. */
1550 	/* set rev video if needed */
1551 	if (req)
1552 	    set_term_attrs(req);
1553 
1554 	/* scan through the line and dump it to the screen and
1555 	   the virtual screen array                             */
1556 	for (; xl < colto; xl++) {
1557 	    term.putch(cp1[xl]);
1558 	    ++ttcol;
1559 	    cp2[xl] = cp1[xl];
1560 	}
1561 	/* turn rev video off */
1562 	if (req)
1563 	    reset_term_attrs();
1564 
1565 	/* update the needed flags */
1566 	vp1->v_flag &= ~(VFCHG | VFCOL);
1567 	if (req)
1568 	    vp1->v_flag |= VFREV;
1569 	else
1570 	    vp1->v_flag &= ~VFREV;
1571 #if OPT_COLOR
1572 	CurFcolor(vp1) = ReqFcolor(vp1);
1573 	CurBcolor(vp1) = ReqBcolor(vp1);
1574 #endif
1575 	return;
1576     }
1577 #else
1578     rev = FALSE;
1579 #endif /* OPT_REVSTA || OPT_COLOR */
1580 #endif /* !OPT_VIDEO_ATTRS */
1581 
1582     /* advance past any common chars at the left */
1583 #if !OPT_VIDEO_ATTRS
1584     if (!rev)
1585 #endif /* !OPT_VIDEO_ATTRS */
1586 	while (xl != colto
1587 	       && cp1[xl] == cp2[xl]
1588 #if OPT_VIDEO_ATTRS
1589 	       && VATTRIB(ap1[xl]) == VATTRIB(ap2[xl])
1590 #endif /* OPT_VIDEO_ATTRS */
1591 	    ) {
1592 	    ++xl;
1593 	}
1594 
1595     /* This can still happen, even though we only call this routine on changed
1596      * lines.  A hard update is always done when a line splits, a massive
1597      * change is done, or a buffer is displayed twice.  This optimizes out most
1598      * of the excess updating.  A lot of computes are used, but these tend to
1599      * be hard operations that do a lot of update, so I don't really care.
1600      */
1601 
1602     /* if both lines are the same, no update needs to be done */
1603     if (xl == colto) {
1604 	vp1->v_flag &= ~VFCHG;
1605 	return;
1606     }
1607 
1608     /* find out if there is a match on the right */
1609     nbflag = FALSE;
1610 
1611 #if !OPT_VIDEO_ATTRS
1612     if (!rev)
1613 #endif
1614 	while (cp1[xr - 1] == cp2[xr - 1]
1615 #if OPT_VIDEO_ATTRS
1616 	       && VATTRIB(ap1[xr - 1]) == VATTRIB(ap2[xr - 1])
1617 #endif
1618 	    ) {
1619 	    --xr;
1620 	    /* Note if any nonblank in right match */
1621 	    if (cp1[xr] != ' '
1622 #if OPT_VIDEO_ATTRS
1623 		|| VATTRIB(ap1[xr] != Blank)
1624 #endif
1625 		)
1626 		nbflag = TRUE;
1627 	}
1628 
1629     xx = xr;
1630 
1631     /* Erase to EOL ? */
1632     if (nbflag == FALSE
1633 	&& eolexist == TRUE
1634 #if OPT_REVSTA && !OPT_VIDEO_ATTRS
1635 	&& (req != TRUE)
1636 #endif
1637 	) {
1638 	while ((xx != xl)
1639 	       && cp1[xx - 1] == ' '
1640 #if OPT_VIDEO_ATTRS
1641 	       && VATTRIB(ap1[xx - 1]) == Blank
1642 #endif
1643 	    ) {
1644 	    xx--;
1645 	}
1646 
1647 	if ((xr - xx) <= 3)	/* Use only if erase is */
1648 	    xx = xr;		/* fewer characters. */
1649     }
1650 
1651     movecursor(row, xl - colfrom);	/* Go to start of line. */
1652 #if OPT_VIDEO_ATTRS
1653     while (xl < xx) {
1654 	int j = xl;
1655 	VIDEO_ATTR attr = VATTRIB(ap1[j]);
1656 	while ((j < xx) && (attr == VATTRIB(ap1[j])))
1657 	    j++;
1658 	set_term_attrs(attr);
1659 	for (; xl < j; xl++) {
1660 	    term.putch(cp1[xl]);
1661 	    ++ttcol;
1662 	    cp2[xl] = cp1[xl];
1663 	    ap2[xl] = ap1[xl];
1664 	}
1665     }
1666     reset_term_attrs();
1667 
1668     if (xx != xr) {		/* Erase. */
1669 	term.eeol();
1670 	for (; xl < xr; xl++) {
1671 	    if (cp2[xl] != cp1[xl]
1672 		|| VATTRIB(ap2[xl]) != VATTRIB(ap1[xl]))
1673 		ap2[xl] = ap1[xl];
1674 	    cp2[xl] = cp1[xl];
1675 	}
1676     }
1677 #else /* OPT_VIDEO_ATTRS */
1678 #if OPT_REVSTA
1679     set_term_attrs(rev);
1680 #endif
1681 
1682     for (; xl < xr; xl++) {	/* Ordinary. */
1683 	term.putch(cp1[xl]);
1684 	++ttcol;
1685 	cp2[xl] = cp1[xl];
1686     }
1687 
1688     if (xx != xr) {		/* Erase. */
1689 	term.eeol();
1690 	for (; xl < xr; xl++) {
1691 	    cp2[xl] = cp1[xl];
1692 	}
1693     }
1694 #if OPT_REVSTA
1695     reset_term_attrs();
1696 #endif
1697 #endif /* OPT_VIDEO_ATTRS */
1698     vp1->v_flag &= ~(VFCHG | VFCOL);
1699     return;
1700 }
1701 #endif /* OPT_PSCREEN(update_line) */
1702 
1703 void
kbd_openup(void)1704 kbd_openup(void)
1705 {
1706     kbd_flush();
1707     bottomleft();
1708     term.putch('\n');
1709     term.putch('\r');
1710     term.flush();
1711 
1712 #if !OPT_PSCREEN
1713     if (pscreen != 0) {
1714 	int i, j;
1715 	for (i = 0; i < term.rows - 1; ++i) {
1716 	    for (j = 0; j < term.cols; ++j) {
1717 		CELL_TEXT(i, j) = CELL_TEXT(i + 1, j);
1718 #if OPT_VIDEO_ATTRS
1719 		CELL_ATTR(i, j) = CELL_ATTR(i + 1, j);
1720 #endif
1721 	    }
1722 	}
1723 	for (j = 0; j < term.cols; ++j) {
1724 	    CELL_TEXT(i, j) = ' ';
1725 #if OPT_VIDEO_ATTRS
1726 	    CELL_ATTR(i, j) = VADIRTY;
1727 #endif
1728 	}
1729     }
1730 #endif
1731 }
1732 
1733 /* cannot be allocated since it's used by OPT_HEAPSIZE */
1734 static char my_overlay[20];
1735 
1736 /* save/erase text for the overlay on the message line */
1737 void
kbd_overlay(const char * s)1738 kbd_overlay(const char *s)
1739 {
1740     my_overlay[0] = EOS;
1741     if ((mpresf = (s != 0 && *s != EOS)) != 0) {
1742 	vl_strncpy(my_overlay, s, sizeof(my_overlay));
1743     }
1744 }
1745 
1746 void
kbd_flush(void)1747 kbd_flush(void)
1748 {
1749     int ok;
1750 
1751     beginDisplay();
1752     if (is_vtinit()) {
1753 	int row = term.rows - 1;
1754 
1755 	vtmove(row, -w_val(wminip, WVAL_SIDEWAYS));
1756 
1757 	ok = (wminip != 0
1758 	      && wminip->w_dot.l != 0
1759 	      && lvalue(wminip->w_dot.l) != 0);
1760 	if (ok) {
1761 	    TRACE(("SHOW:%2d:%s\n",
1762 		   llength(wminip->w_dot.l),
1763 		   lp_visible(wminip->w_dot.l)));
1764 	    lsettrimmed(wminip->w_dot.l);
1765 	    vtset(wminip->w_dot.l, wminip);
1766 	}
1767 
1768 	vteeol();
1769 	set_vattrs(row, 0,
1770 		   (VIDEO_ATTR) (miniedit
1771 				 ? global_g_val(GVAL_MINI_HILITE)
1772 				 : 0),
1773 		   (size_t) term.cols);
1774 	if (my_overlay[0] != EOS) {
1775 	    int j;
1776 	    int n = term.cols - (int) strlen(my_overlay) - 1;
1777 	    if (n > 0) {
1778 		for (j = 0; my_overlay[j] != EOS; ++j)
1779 		    vscreen[row]->v_text[n + j] = (VIDEO_TEXT) my_overlay[j];
1780 	    }
1781 	}
1782 	vscreen[row]->v_flag |= VFCHG;
1783 	update_line(row, 0, term.cols);
1784 	if (ok)
1785 	    movecursor(row,
1786 		       offs2col(wminip, wminip->w_dot.l, wminip->w_dot.o));
1787     }
1788     term.flush();
1789     endofDisplay();
1790 }
1791 
1792 /*
1793  * Translate offset (into a line's text) into the display-column, taking into
1794  * account the tabstop, sideways, number- and list-modes.
1795  *
1796  * Note: we intentionally compare against '\n' rather than the record separator,
1797  * since it is simpler than handling ^M^J in one case, and we really only want
1798  * to know if we're in the record-separator.
1799  */
1800 static int
offs2col0(WINDOW * wp,LINE * lp,C_NUM offset,C_NUM * cache_offset,int * cache_column)1801 offs2col0(WINDOW *wp,
1802 	  LINE *lp,
1803 	  C_NUM offset,
1804 	  C_NUM * cache_offset,
1805 	  int *cache_column)
1806 {
1807     int column;
1808 
1809     /* this makes the how-much-to-select calculation easier above */
1810     if (offset < 0) {
1811 	column = offset;
1812     } else if ((lp == win_head(wp)) || !lisreal(lp)) {
1813 	column = 0;
1814     } else {
1815 	BUFFER *bp = wp->w_bufp;
1816 	int rs = use_record_sep(bp);
1817 	int length = llength(lp);
1818 	int nums = nu_width(wp);
1819 	int tabs = tabstop_val(wp->w_bufp);
1820 	int list = w_val(wp, WMDLIST);
1821 	int last = (list ? (N_chars * 2) : rs);
1822 	int left = if_LINEWRAP(wp, 0, w_val(wp, WVAL_SIDEWAYS));
1823 	int ch = EOF, c0;
1824 	C_NUM n;
1825 	C_NUM start_offset;
1826 	const char *text = lvalue(lp);
1827 
1828 	if (offset > length + 1)
1829 	    offset = length + 1;
1830 
1831 	if (cache_offset && cache_column && *cache_offset < offset) {
1832 	    start_offset = *cache_offset;
1833 	    column = *cache_column;
1834 	} else {
1835 	    start_offset = w_left_margin(wp);
1836 	    column = 0;
1837 	}
1838 
1839 	for (n = start_offset; n < offset;) {
1840 	    int used = 1;
1841 
1842 	    c0 = ch;
1843 	    ch = (n == length) ? rs : CharOf(text[n]);
1844 #if OPT_MULTIBYTE
1845 	    if ((n < length) && b_is_utfXX(bp) && !isTab(ch)) {
1846 		int nxt = offset - n;
1847 		int adj = column_sizes(wp, text + n, (UINT) nxt, &used);
1848 
1849 		if (adj == COLS_UTF8 && UseCellWidth2(wp, lp, n)) {
1850 		    adj = mb_cellwidth(wp, text + n, nxt);
1851 		}
1852 		column += adj;
1853 	    } else
1854 #endif
1855 	    if (isPrint(ch)) {
1856 #ifdef WMDLINEWRAP
1857 		if (column != 0
1858 		    && w_val(wp, WMDLINEBREAK)
1859 		    && w_val(wp, WMDLINEWRAP)
1860 		    && isBlank(c0)) {
1861 		    int k = (int) cols_until(wp,
1862 					     text + n,
1863 					     (unsigned) (length - n));
1864 		    int col2 = column + nums;
1865 		    int wide = term.cols;
1866 		    int have;
1867 
1868 		    have = (col2 % wide) + k;
1869 		    if (have >= wide) {
1870 			int wrap = (col2 / wide);
1871 			int need = (wrap + 1) * wide;
1872 			column = need - nums;
1873 		    }
1874 		}
1875 #endif
1876 		column++;
1877 	    } else if (ch == last) {
1878 		column++;
1879 	    } else if (list || !isTab(ch)) {
1880 		column += column_sizes(wp,
1881 				       (n >= length) ? "" : (text + n),
1882 				       (UINT) (offset - n), &used);
1883 	    } else if (isTab(ch)) {
1884 		column = ((column / tabs) + 1) * tabs;
1885 	    }
1886 	    n += used;
1887 	}
1888 
1889 	if (cache_offset && cache_column) {
1890 	    *cache_offset = offset;
1891 	    *cache_column = column;
1892 	}
1893 
1894 	column += (nums + w_left_margin(wp) - left);
1895     }
1896     return column;
1897 }
1898 
1899 int
offs2col(WINDOW * wp,LINE * lp,C_NUM offset)1900 offs2col(WINDOW *wp, LINE *lp, C_NUM offset)
1901 {
1902     return offs2col0(wp, lp, offset, 0, 0);
1903 }
1904 
1905 /*
1906  * Translate a display-column (assuming an infinitely-wide display) into the
1907  * line's offset, taking into account the tabstop, sideways, number and list
1908  * modes.
1909  */
1910 #if OPT_MOUSE || defined(WMDLINEWRAP)
1911 int
col2offs(WINDOW * wp,LINE * lp,C_NUM col)1912 col2offs(WINDOW *wp, LINE *lp, C_NUM col)
1913 {
1914     int nums = nu_width(wp);
1915     int tabs = tabstop_val(wp->w_bufp);
1916     int list = w_val(wp, WMDLIST);
1917     int left = if_LINEWRAP(wp, 0, w_val(wp, WVAL_SIDEWAYS));
1918     int goal = col + left - nums - w_left_margin(wp);
1919 
1920     C_NUM n;
1921     C_NUM offset;
1922     C_NUM len = llength(lp);
1923     char *text = lvalue(lp);
1924 
1925     if (lp == win_head(wp)) {
1926 	offset = 0;
1927     } else {
1928 	for (offset = w_left_margin(wp), n = 0;
1929 	     (offset < len) && (n < goal);
1930 	    ) {
1931 	    int c = text[offset];
1932 	    int used = 1;
1933 
1934 #ifdef WMDLINEWRAP
1935 	    /*
1936 	     * A linebreak can happen after whitespace.  To avoid a confusing
1937 	     * display, we avoid doing the linebreak at the beginning or in the
1938 	     * middle of whitespace.  Also, we disallow linebreak at the
1939 	     * beginning of a line.
1940 	     */
1941 	    if (offset != 0
1942 		&& w_val(wp, WMDLINEBREAK)
1943 		&& w_val(wp, WMDLINEWRAP)
1944 		&& isBlank(text[offset - 1])
1945 		&& !isBlank(text[offset])) {
1946 		int n2 = (int) cols_until(wp,
1947 					  text + offset,
1948 					  (unsigned) (len - offset));
1949 		int col1 = ((n + nums) % term.cols);
1950 		int col2 = col1 + n2;
1951 
1952 		if (col2 >= term.cols) {
1953 		    n += (term.cols - col1);
1954 		}
1955 	    }
1956 #endif
1957 
1958 #if OPT_MULTIBYTE
1959 	    if (b_is_utfXX(wp->w_bufp) && !isTab(c)) {
1960 		int nxt = len - offset;
1961 		int adj = column_sizes(wp, text + offset, (UINT) nxt, &used);
1962 
1963 		if (adj == COLS_UTF8 && UseCellWidth2(wp, lp, offset)) {
1964 		    adj = mb_cellwidth(wp, text + offset, nxt);
1965 		}
1966 		n += adj;
1967 	    } else
1968 #endif
1969 	    if (isPrint(c)) {
1970 		n++;
1971 	    } else if (list || !isTab(c)) {
1972 		n += column_sizes(wp, text + offset, (UINT) (len - offset), &used);
1973 	    } else if (isTab(c)) {
1974 		n = ((n / tabs) + 1) * tabs;
1975 	    }
1976 	    if (n > goal)
1977 		break;
1978 	    offset += used;
1979 	}
1980     }
1981     return offset;
1982 }
1983 #endif
1984 
1985 /*
1986  * Compute the number of rows required for displaying a line.
1987  */
1988 #ifdef WMDLINEWRAP
1989 int
line_height(WINDOW * wp,LINE * lp)1990 line_height(WINDOW *wp, LINE *lp)
1991 {
1992     int hi = 1;
1993 
1994     if (w_val(wp, WMDLINEWRAP)) {
1995 	int len = llength(lp);
1996 	if (len > 0) {
1997 	    int col = offs2col(wp, lp, len) - 1;
1998 	    int rsl = ((w_val(wp, WMDLIST))
1999 		       ? ((b_val(wp->w_bufp, VAL_RECORD_SEP) == RS_CRLF) ? 4 : 2)
2000 		       : 1);
2001 	    if (ins_mode(wp) != FALSE
2002 		&& lp == DOT.l
2003 		&& len <= DOT.o) {
2004 		col += rsl - 1;	/* wrapping treats one column specially */
2005 	    } else if (w_val(wp, WMDLIST)) {
2006 		col += rsl;
2007 	    }
2008 	    hi = (col / term.cols) + 1;
2009 	}
2010     }
2011     return hi;
2012 }
2013 #endif
2014 
2015 /*
2016  * Given a row on the screen, determines which window it belongs to.  Returns
2017  * null only for the message line.
2018  */
2019 #if defined(WMDLINEWRAP) || OPT_MOUSE
2020 WINDOW *
row2window(int row)2021 row2window(int row)
2022 {
2023     WINDOW *wp;
2024 
2025     for_each_visible_window(wp) {
2026 	if (row >= wp->w_toprow && row <= mode_row(wp))
2027 	    return wp;
2028     }
2029     return 0;
2030 }
2031 #endif
2032 
2033 #if OPT_UPBUFF
2034 typedef struct {
2035     WINDOW *wp;
2036     struct VAL w_vals[MAX_W_VALUES];
2037     int top;
2038     int line;
2039     int col;
2040 } SAVEWIN;
2041 
2042 static SAVEWIN *recomp_tbl;
2043 static size_t recomp_len;
2044 static WINDOW *recomp_win;
2045 
2046 /*
2047  * Return true if the current window (or its associated buffer) is being
2048  * recomputed.
2049  */
2050 int
is_recomputing(WINDOW * wp)2051 is_recomputing(WINDOW *wp)
2052 {
2053     int result = FALSE;
2054 
2055     if (recomp_win && wp) {
2056 	if (recomp_win == wp) {
2057 	    result = TRUE;
2058 	} else if (recomp_win->w_bufp == wp->w_bufp) {
2059 	    result = TRUE;
2060 	}
2061     }
2062 
2063     return result;
2064 }
2065 
2066 BUFFER *
recomputing_buf(void)2067 recomputing_buf(void)
2068 {
2069     BUFFER *result = 0;
2070 
2071     if (recomp_win != 0)
2072 	result = recomp_win->w_bufp;
2073 
2074     return result;
2075 }
2076 
2077 WINDOW *
recomputing_win(void)2078 recomputing_win(void)
2079 {
2080     return recomp_win;
2081 }
2082 
2083 /*
2084  * Recompute the given buffer. Save/restore its modes and position information
2085  * so that a redisplay will show as little change as possible.
2086  */
2087 static void
recompute_buffer(BUFFER * bp)2088 recompute_buffer(BUFFER *bp)
2089 {
2090     if (b_val(bp, MDUPBUFF)) {
2091 	WINDOW *wp;
2092 	SAVEWIN *tbl;
2093 
2094 	struct VAL b_vals[MAX_B_VALUES];
2095 	size_t num = 0;
2096 	BUFFER *savebp = curbp;
2097 	WINDOW *savewp = curwp;
2098 	int mygoal = curgoal;
2099 
2100 	recomp_win = curwp;
2101 
2102 	if (recomp_len < bp->b_nwnd) {
2103 	    recomp_len = (size_t) bp->b_nwnd + 1;
2104 	    recomp_tbl = (recomp_tbl != 0)
2105 		? typereallocn(SAVEWIN, recomp_tbl, recomp_len)
2106 		: typeallocn(SAVEWIN, recomp_len);
2107 	    if (recomp_tbl == 0) {
2108 		recomp_len = 0;
2109 		return;
2110 	    }
2111 	}
2112 	tbl = recomp_tbl;
2113 
2114 	/* remember where we are, to reposition */
2115 	/* ...in case line is deleted from buffer-list */
2116 	relisting_b_vals = 0;
2117 	relisting_w_vals = 0;
2118 	if (curbp == bp) {
2119 	    relisting_b_vals = b_vals;
2120 	} else {
2121 	    curbp = bp;
2122 	    curwp = bp2any_wp(bp);
2123 	}
2124 	for_each_visible_window(wp) {
2125 	    if (wp->w_bufp == bp) {
2126 		if (wp == savewp)
2127 		    relisting_w_vals = tbl[num].w_vals;
2128 		curwp = wp;	/* to make 'getccol()' work */
2129 		curbp = curwp->w_bufp;
2130 		tbl[num].wp = wp;
2131 		tbl[num].top = line_no(bp, wp->w_line.l);
2132 		tbl[num].line = line_no(bp, wp->w_dot.l);
2133 		tbl[num].col = getccol(FALSE);
2134 		save_vals(NUM_W_VALUES, global_w_values.wv,
2135 			  tbl[num].w_vals, wp->w_values.wv);
2136 		if (++num >= recomp_len)
2137 		    break;
2138 	    }
2139 	}
2140 	curwp = savewp;
2141 	curbp = savebp;
2142 
2143 	save_vals(NUM_B_VALUES, global_b_values.bv, b_vals, bp->b_values.bv);
2144 	(bp->b_upbuff) (bp);
2145 	copy_mvals(NUM_B_VALUES, bp->b_values.bv, b_vals);
2146 
2147 	/* reposition and restore */
2148 	while (num-- != 0) {
2149 	    curwp = wp = tbl[num].wp;
2150 	    curbp = curwp->w_bufp;
2151 	    (void) vl_gotoline(tbl[num].top);
2152 	    wp->w_line.l = wp->w_dot.l;
2153 	    wp->w_line.o = 0;
2154 	    if (tbl[num].line != tbl[num].top)
2155 		(void) vl_gotoline(tbl[num].line);
2156 	    (void) gocol(tbl[num].col);
2157 	    wp->w_flag |= WFMOVE | WFSBAR;
2158 	    copy_mvals(NUM_W_VALUES, wp->w_values.wv, tbl[num].w_vals);
2159 	}
2160 	curwp = savewp;
2161 	curbp = savebp;
2162 	curgoal = mygoal;
2163 	relisting_b_vals = 0;
2164 	relisting_w_vals = 0;
2165 
2166 	recomp_win = 0;
2167     }
2168     b_clr_obsolete(bp);
2169 
2170     return;
2171 }
2172 #endif /* OPT_UPBUFF */
2173 
2174 static int
TypeAhead(int force)2175 TypeAhead(int force)
2176 {
2177     if (force != TRUE) {
2178 	if ((force == FALSE && !global_g_val(GMDSMOOTH_SCROLL))
2179 	    || (force == SORTOFTRUE))
2180 	    return (keystroke_avail());
2181     }
2182     return FALSE;
2183 }
2184 
2185 /*
2186  * Update virtual screen line, given a LINE pointer.
2187  */
2188 static void
update_screen_line(WINDOW * wp,LINE * lp,int sline)2189 update_screen_line(WINDOW *wp, LINE *lp, int sline)
2190 {
2191     C_NUM left;
2192 
2193     /*
2194      * Mark the screen lines changed, resetting the requests for reverse
2195      * video.  Set the global 'horscroll' to the amount of horizontal
2196      * scrolling.
2197      */
2198 #ifdef WMDLINEWRAP
2199     if (w_val(wp, WMDLINEWRAP)) {
2200 	int top_line = sline - wp->w_line.o;
2201 	int m = (top_line >= 0) ? top_line : 0;
2202 	int n = top_line + line_height(wp, lp);
2203 	while (n > m)
2204 	    if (--n < mode_row(wp)) {
2205 		vscreen[n]->v_flag |= VFCHG;
2206 		vscreen[n]->v_flag &= ~VFREQ;
2207 	    }
2208 	horscroll = 0;
2209     } else
2210 #endif
2211     if (sline >= 0) {
2212 	vscreen[sline]->v_flag |= VFCHG;
2213 	vscreen[sline]->v_flag &= ~VFREQ;
2214 	if (w_val(wp, WVAL_SIDEWAYS))
2215 	    horscroll = w_val(wp, WVAL_SIDEWAYS);
2216     }
2217     left = horscroll;
2218 
2219     if (lp != win_head(wp)) {
2220 	vtmove(sline, -left);
2221 	vtset(lp, wp);
2222 	if (left && sline >= 0) {
2223 	    int zero = nu_width(wp);
2224 	    mark_extent(sline, zero, MRK_EXTEND_LEFT[0]);
2225 	    if (vtcol <= zero)
2226 		vtcol = zero + 1;
2227 	}
2228     } else {
2229 	vtmove(sline, 0);
2230 	vtputc(wp, MRK_EMPTY, 1);
2231     }
2232     horscroll = 0;
2233 #if OPT_COLOR
2234     if (sline >= 0) {
2235 	ReqFcolor(vscreen[sline]) = gfcolor;
2236 	ReqBcolor(vscreen[sline]) = gbcolor;
2237     }
2238 #endif
2239 }
2240 
2241 /*
2242  * Update the current line to the virtual screen.
2243  *
2244  * 'wp' points to the window to update current line in.
2245  */
2246 static void
update_oneline(WINDOW * wp)2247 update_oneline(WINDOW *wp)
2248 {
2249     LINE *lp;			/* line to update */
2250     int sline;			/* physical screen line to update */
2251 
2252     /* search down the line we want */
2253     lp = wp->w_line.l;
2254     sline = TopRow(wp);
2255     while (lp != wp->w_dot.l) {
2256 	sline += line_height(wp, lp);
2257 	lp = lforw(lp);
2258     }
2259 
2260     update_screen_line(wp, lp, sline);
2261     vteeol();
2262 }
2263 
2264 /*
2265  * Update all the lines in a window on the virtual screen.
2266  *
2267  * 'wp' points to the window to update current line in.
2268  */
2269 static void
update_all(WINDOW * wp)2270 update_all(WINDOW *wp)
2271 {
2272     LINE *lp;			/* line to update */
2273     int sline;			/* physical screen line to update */
2274 
2275     /* search down the lines, updating them */
2276     lp = wp->w_line.l;
2277     sline = TopRow(wp);
2278     while (sline < mode_row(wp)) {
2279 	update_screen_line(wp, lp, sline);
2280 	vteeol();
2281 	sline += line_height(wp, lp);
2282 	if (lp != win_head(wp))
2283 	    lp = lforw(lp);
2284     }
2285 }
2286 
2287 #if OPT_VIDEO_ATTRS
2288 /* Merge (or combine) the specified attribute into the vscreen structure */
2289 static void
mergeattr(WINDOW * wp,int row,int start_col,int end_col,unsigned attr)2290 mergeattr(WINDOW *wp, int row, int start_col, int end_col, unsigned attr)
2291 {
2292     int col;
2293 
2294     if (start_col < 0)
2295 	start_col = 0;
2296 
2297 #ifdef WMDLINEWRAP
2298     if (w_val(wp, WMDLINEWRAP)) {
2299 	for (col = start_col; col <= end_col; col++) {
2300 	    int x = row + col / term.cols;
2301 	    if (x < 0)
2302 		continue;
2303 	    if (x < mode_row(wp)) {
2304 		int y = col % term.cols;
2305 		vscreen[x]->v_attrs[y] =
2306 		    (VIDEO_ATTR) ((vscreen[x]->v_attrs[y] | (attr & ~VAREV))
2307 				  ^ (attr & VAREV));
2308 	    } else {
2309 		break;
2310 	    }
2311 	}
2312     } else
2313 #endif
2314     {
2315 	if (end_col >= term.cols)
2316 	    end_col = term.cols - 1;
2317 	for (col = start_col; col <= end_col; col++)
2318 	    vscreen[row]->v_attrs[col] =
2319 		(VIDEO_ATTR) ((vscreen[row]->v_attrs[col] | (attr & ~VAREV))
2320 			      ^ (attr & VAREV));
2321     }
2322 }
2323 
2324 #if OPT_LINE_ATTRS
2325 static void
update_line_attrs(WINDOW * wp)2326 update_line_attrs(WINDOW *wp)
2327 {
2328     int row;
2329     LINE *lp;
2330     int linewrap;
2331 
2332 #ifdef WMDLINEWRAP
2333     linewrap = w_val(wp, WMDLINEWRAP);
2334 #else
2335     linewrap = 0;
2336 #endif
2337 
2338     row = TopRow(wp);
2339     lp = wp->w_line.l;
2340     while (row < wp->w_toprow + wp->w_ntrows && lp != win_head(wp)) {
2341 	int save_offset = w_left_margin(wp);
2342 	int save_column = 0;
2343 	if (lp->l_attrs) {
2344 	    C_NUM start_col, end_col;
2345 	    int i, a;
2346 	    /* Attempt to set starting point intelligently.  */
2347 	    if (wp->w_line.o < 0)
2348 		i = col2offs(wp, lp, -(wp->w_line.o * term.cols));
2349 	    else {
2350 		if (w_val(wp, WVAL_SIDEWAYS))
2351 		    i = col2offs(wp, lp, 0);
2352 		else
2353 		    i = 0;
2354 	    }
2355 	    if (!linewrap && w_val(wp, WMDNUMBER) && w_val(wp, WVAL_SIDEWAYS)) {
2356 		i += NU_WIDTH;
2357 	    }
2358 	    /* If i > 0, make sure it indexes a valid attribute.  */
2359 	    if (i > 0) {
2360 		int ii;
2361 		for (ii = 0; ii < i; ii++)
2362 		    if (lp->l_attrs[ii] == 0)
2363 			break;
2364 		i = ii;
2365 	    }
2366 	    /* Scan attributes... */
2367 	    for (;;) {
2368 		if (lp->l_attrs[i] == 0)
2369 		    break;	/* get out if at end */
2370 		while (lp->l_attrs[i] == 1)
2371 		    i++;	/* skip normals */
2372 		a = lp->l_attrs[i];
2373 		if (a == 0)
2374 		    break;	/* get out if at end */
2375 		start_col = offs2col0(wp, lp, i, &save_offset, &save_column);
2376 		i++;
2377 		while (lp->l_attrs[i] == a)
2378 		    i++;	/* find run of same attr */
2379 		if (start_col < w_left_margin(wp))
2380 		    start_col = w_left_margin(wp);
2381 		end_col = offs2col0(wp, lp, i, &save_offset, &save_column) - 1;
2382 		if (!linewrap) {
2383 		    if (start_col > term.cols)
2384 			break;
2385 		    mergeattr(wp, row, start_col, end_col,
2386 			      line_attr_tbl[a].vattr);
2387 		    if (end_col > term.cols)
2388 			break;
2389 		} else {
2390 		    /* mergeattr() handles wrapping of attributes for us.  */
2391 		    mergeattr(wp, row, start_col, end_col,
2392 			      line_attr_tbl[a].vattr);
2393 		    if (row + end_col / term.cols >= mode_row(wp))
2394 			break;	/* Get out if no more to do.  */
2395 		}
2396 	    }
2397 	}
2398 	row += line_height(wp, lp);
2399 	lp = lforw(lp);
2400     }
2401 }
2402 #endif /* OPT_LINE_ATTRS */
2403 
2404 static void
update_window_attrs(WINDOW * wp)2405 update_window_attrs(WINDOW *wp)
2406 {
2407     BUFFER *bp = wp->w_bufp;
2408     AREGION **rpp;
2409     AREGION *ap;
2410     int i, lmax;
2411 
2412     L_NUM start_wlnum, end_wlnum;
2413     LINE *lp;
2414     int rows;
2415     C_NUM col;
2416 #if OPT_TRACE
2417     int total_regions = 0;
2418     int visible_regions = 0;
2419 #endif
2420 
2421     /*
2422      * Clear portion of virtual screen associated with window pointer
2423      * of all attributes.
2424      */
2425     /* FIXME: color; need to set to value indicating fg and bg for window */
2426     for (i = wp->w_toprow + wp->w_ntrows - 1; i >= wp->w_toprow; i--) {
2427 	set_vattrs(i,
2428 		   lmap0[i].left, 0,
2429 		   (size_t) lmap0[i].right - (size_t) lmap0[i].left);
2430     }
2431 
2432 #if OPT_LINE_ATTRS
2433     update_line_attrs(wp);
2434 #endif /* OPT_LINE_ATTRS */
2435 
2436     /*
2437      * No need to do any more work on this window if there are no
2438      * attributes.
2439      */
2440     if (bp->b_attribs == NULL)
2441 	return;
2442 
2443     /*
2444      * Compute starting and ending line numbers for the window.  We
2445      * also fill in lmap which is used for mapping line numbers to
2446      * screen row numbers.
2447      */
2448     lp = wp->w_line.l;
2449     start_wlnum =
2450 	end_wlnum = line_no(bp, lp);
2451     rows = wp->w_ntrows;
2452 
2453     lmax = end_wlnum - start_wlnum;
2454     lmap[lmax].lp = lp;
2455     lmap[lmax].map = TopRow(wp);
2456     lmap[lmax].left = -1;
2457     lmap[lmax].right = -1;
2458 
2459     while ((rows -= line_height(wp, lp)) > 0) {
2460 	lp = lforw(lp);
2461 	end_wlnum++, lmax++;
2462 	lmap[lmax].lp = lp;
2463 	lmap[lmax].map = TopRow(wp) + wp->w_ntrows - rows;
2464 	lmap[lmax].left = -1;
2465 	lmap[lmax].right = -1;
2466     }
2467 
2468     ++lmax;
2469     lmap[lmax].lp = 0;
2470     lmap[lmax].map = TopRow(wp) + wp->w_ntrows;
2471     lmap[lmax].left = -1;
2472     lmap[lmax].right = -1;
2473 
2474     /*
2475      * Set current attributes in virtual screen associated with window
2476      * pointer.
2477      */
2478     rpp = &(bp->b_attribs);
2479     while ((ap = *rpp) != NULL) {
2480 	AREGION **next = &(ap->ar_next);
2481 	VIDEO_ATTR attr;
2482 	C_NUM start_col, end_col;
2483 	C_NUM rect_start_col = 0, rect_end_col = 0;
2484 	L_NUM start_rlnum, end_rlnum, lnum, start_lnum, end_lnum;
2485 	int visible = TRUE;
2486 
2487 	start_rlnum = line_no(bp, ap->ar_region.r_orig.l);
2488 	end_rlnum = line_no(bp, ap->ar_region.r_end.l);
2489 
2490 	/* Garbage collect empty visible regions */
2491 	if (start_rlnum == end_rlnum
2492 	    && VATTRIB(ap->ar_vattr) != 0
2493 	    && ap->ar_region.r_orig.o >= ap->ar_region.r_end.o) {
2494 	    free_attrib2(bp, rpp);
2495 	    continue;
2496 	}
2497 
2498 	/* compute starting and ending line-numbers, given the region's */
2499 	if (start_rlnum > start_wlnum) {
2500 	    start_lnum = start_rlnum;
2501 	    lp = ap->ar_region.r_orig.l;
2502 	} else {
2503 	    start_lnum = start_wlnum;
2504 	    lp = wp->w_line.l;
2505 	}
2506 	end_lnum = (end_rlnum < end_wlnum) ? end_rlnum : end_wlnum;
2507 
2508 	/*
2509 	 * Filter out attribute regions that will not be on the screen.
2510 	 * Most of the regions generated by syntax filters start/end on
2511 	 * the same line, which allows us to make a simple/fast check.
2512 	 */
2513 	if (start_rlnum == end_rlnum) {
2514 	    i = (start_rlnum - start_wlnum);
2515 
2516 	    /*
2517 	     * Skip lines which don't fall into the vertical range.
2518 	     */
2519 	    if (i < 0 || i >= lmax) {
2520 		visible = FALSE;
2521 	    } else {
2522 
2523 		/*
2524 		 * Compute the left/right offsets for the visible part of the
2525 		 * line the first time we need to compare it.
2526 		 */
2527 		if (lmap[i].left < 0 && lmap[i].right < 0) {
2528 #ifdef WMDLINEWRAP
2529 		    if (w_val(wp, WMDLINEWRAP)) {
2530 			int lines_remaining = (lmap[i + 1].map - lmap[i].map);
2531 
2532 			col = -((wp->w_line.o * term.cols) + nu_width(wp));
2533 			lmap[i].left = col2offs(wp, lmap[i].lp, col);
2534 
2535 			col += (lines_remaining * term.cols);
2536 			lmap[i].right = col2offs(wp, lmap[i].lp, col);
2537 		    } else
2538 #endif
2539 		    {
2540 			col = -((wp->w_line.o * term.cols) + nu_width(wp));
2541 			lmap[i].left = col2offs(wp, lmap[i].lp, col);
2542 
2543 			col += term.cols;
2544 			lmap[i].right = col2offs(wp, lmap[i].lp, col);
2545 		    }
2546 		    TRACE2(("...update_window_attrs row %d [%d..%d] (%d)\n",
2547 			    lmap[i].map,
2548 			    lmap[i].left,
2549 			    lmap[i].right,
2550 			    lmap[i].right - lmap[i].left));
2551 		}
2552 
2553 		if (ap->ar_region.r_orig.o >= lmap[i].right) {
2554 		    visible = FALSE;
2555 		} else if (ap->ar_region.r_end.o < lmap[i].left) {
2556 		    visible = FALSE;
2557 		}
2558 	    }
2559 	}
2560 #if OPT_TRACE
2561 	total_regions++;
2562 #endif
2563 	if (visible) {
2564 #if OPT_TRACE
2565 	    visible_regions++;
2566 #endif
2567 
2568 	    attr = ap->ar_vattr;
2569 
2570 	    /* if it's a rectangle, precompute the start/end columns */
2571 	    if (ap->ar_shape == rgn_RECTANGLE) {
2572 		int n;
2573 		rect_start_col = mark2col(wp, ap->ar_region.r_orig);
2574 		rect_end_col = mark2col(wp, ap->ar_region.r_end);
2575 		if (rect_end_col < rect_start_col) {
2576 		    col = rect_end_col;
2577 		    rect_end_col = rect_start_col;
2578 		    rect_start_col = col;
2579 		    n = MARK2COL(wp, ap->ar_region.r_orig);
2580 		} else {
2581 		    n = MARK2COL(wp, ap->ar_region.r_end);
2582 		}
2583 		if (rect_end_col < n)
2584 		    rect_end_col = n;
2585 	    }
2586 	    for (lnum = start_lnum; lnum <= end_lnum; lnum++, lp = lforw(lp)) {
2587 		int row;
2588 		if (ap->ar_shape == rgn_RECTANGLE) {
2589 		    start_col = rect_start_col;
2590 		} else if (lnum == start_rlnum) {
2591 		    start_col = mark2col(wp, ap->ar_region.r_orig);
2592 		} else {
2593 		    start_col = w_left_margin(wp) + nu_width(wp);
2594 		}
2595 
2596 		if (start_col < w_left_margin(wp))
2597 		    start_col = (lnum == start_rlnum)
2598 			? w_left_margin(wp) + nu_width(wp)
2599 			: w_left_margin(wp);
2600 
2601 		if (ap->ar_shape == rgn_RECTANGLE) {
2602 		    end_col = rect_end_col;
2603 		} else if (lnum == end_rlnum) {
2604 		    end_col = mark2col(wp, ap->ar_region.r_end) - 1;
2605 		} else {
2606 		    end_col = offs2col(wp, lp, llength(lp) + 1) - 1;
2607 #ifdef WMDLINEWRAP
2608 		    if (w_val(wp, WMDLINEWRAP)
2609 			&& (end_col % term.cols) == 0)
2610 			end_col--;	/* cannot highlight the newline */
2611 #endif
2612 		}
2613 		row = lmap[lnum - start_wlnum].map;
2614 		mergeattr(wp, row, start_col, end_col, attr);
2615 	    }
2616 	}
2617 	rpp = next;
2618     }
2619     TRACE2(("update_window_attrs visible %d / %d\n",
2620 	    visible_regions,
2621 	    total_regions));
2622 }
2623 #endif /* OPT_VIDEO_ATTRS */
2624 
2625 /*
2626  * If the screen is garbage, clear the physical screen and the virtual screen
2627  * and force a full update.
2628  */
2629 static void
update_garbaged_screen(void)2630 update_garbaged_screen(void)
2631 {
2632 #if !OPT_PSCREEN
2633     int j;
2634 #endif
2635     int i;
2636 
2637     TRACE((T_CALLED "update_garbaged_screen\n"));
2638 
2639     for (i = 0; i < term.rows; ++i) {
2640 	vscreen[i]->v_flag |= VFCHG;
2641 #if OPT_REVSTA
2642 	vscreen[i]->v_flag &= ~VFREV;
2643 #endif
2644 #if OPT_COLOR
2645 	CurFcolor(vscreen[i]) = -1;
2646 	CurBcolor(vscreen[i]) = -1;
2647 #endif
2648 #if !OPT_PSCREEN
2649 	for (j = 0; j < term.cols; ++j) {
2650 	    CELL_TEXT(i, j) = ' ';
2651 #if OPT_VIDEO_ATTRS
2652 	    CELL_ATTR(i, j) = 0;
2653 #endif /* OPT_VIDEO_ATTRS */
2654 	}
2655 #endif
2656     }
2657 #if !OPT_PSCREEN
2658 #if OPT_COLOR
2659     term.setfore(gfcolor);	/* Need to set before erasing. */
2660     term.setback(gbcolor);
2661 #endif
2662     movecursor(0, 0);		/* Erase the screen. */
2663     reset_term_attrs();
2664     term.eeop();
2665 #else
2666     kbd_erase_to_end(0);
2667 #endif
2668     sgarbf = FALSE;		/* Erase-page clears */
2669     need_update = FALSE;
2670     kbd_flush();
2671 
2672     returnVoid();
2673 }
2674 
2675 /*
2676  * Update the extended line which the cursor is currently on at a column less
2677  * than the terminal width.  The line will be scrolled right or left to let the
2678  * user see where the cursor is.
2679  */
2680 static int
update_extended_line(int col,int excess,int use_excess)2681 update_extended_line(int col, int excess, int use_excess)
2682 {
2683     int rcursor;
2684     int zero;
2685     int scrollsiz;
2686 
2687     /* calculate what column the real cursor will end up in */
2688     if (!use_excess) {
2689 	curcol = col;
2690 	rcursor = (col % (term.cols - term.cols / 10));
2691     } else {
2692 	scrollsiz = term.rows - ((term.rows / 10) * 2);
2693 	rcursor = ((excess - 1) % scrollsiz) + term.cols / 10;
2694     }
2695     horscroll = col - rcursor;
2696 
2697     /* Scan through the line outputting characters to the virtual screen
2698      * once we reach the left edge.  */
2699     vtmove(currow, -horscroll);	/* start scanning offscreen */
2700     vtset(DOT.l, curwp);
2701 
2702     /* truncate the virtual line */
2703     vteeol();
2704 
2705     horscroll = 0;
2706 
2707     if ((use_excess || col != rcursor)
2708 	&& (zero = nu_width(curwp)) < term.cols) {
2709 	/* ... put a marker in column 1 */
2710 	vscreen[currow]->v_text[zero] = (VIDEO_TEXT) MRK_EXTEND_LEFT[0];
2711     }
2712     vscreen[currow]->v_flag |= (VFEXT | VFCHG);
2713     return rcursor;
2714 }
2715 
2716 /*
2717  * Update the position of the hardware cursor and handle extended lines.  This
2718  * is the only update for simple moves.  Returns the screen location for the
2719  * cursor, and a boolean indicating if full sideways scroll was necessary
2720  */
2721 static int
update_cursor_position(int * screenrowp,int * screencolp)2722 update_cursor_position(int *screenrowp, int *screencolp)
2723 {
2724     LINE *lp;
2725 #ifdef WMDLINEWRAP
2726     int i;
2727 #endif
2728     int col, excess;
2729     int collimit;
2730     int moved = FALSE;
2731     int nuadj;
2732     int liadj;
2733 
2734     if (curwp == 0) {
2735 	*screenrowp = 0;
2736 	*screencolp = 0;
2737 	return FALSE;
2738     }
2739     nuadj = is_empty_buf(curwp->w_bufp) ? 0 : nu_width(curwp);
2740     liadj = (w_val(curwp, WMDLIST)) ? 1 : 0;
2741 
2742     TRACE2((T_CALLED "update_cursor_position\n"));
2743     /* find the current row */
2744     lp = curwp->w_line.l;
2745     currow = TopRow(curwp);
2746     while (lp != DOT.l) {
2747 	currow += line_height(curwp, lp);
2748 	lp = lforw(lp);
2749 	if (lp == curwp->w_line.l
2750 	    || currow > mode_row(curwp)) {
2751 	    mlforce("BUG:  lost dot updpos().  setting at top");
2752 	    DOT.l = lforw(buf_head(curbp));
2753 	    DOT.o = b_left_margin(curbp);
2754 	    lp = curwp->w_line.l = DOT.l;
2755 	    currow = TopRow(curwp);
2756 	}
2757     }
2758 
2759     /*
2760      * Find the current column.  Reuse the ruler column-value if we
2761      * computed it.
2762      */
2763 #ifdef WMDRULER
2764     if (w_val(curwp, WMDRULER))
2765 	col = curwp->w_ruler_col + w_left_margin(curwp) - 1;
2766     else
2767 #endif
2768 	col = dot_to_vcol(curwp) + w_left_margin(curwp);
2769 
2770 #ifdef WMDLINEWRAP
2771     if (w_val(curwp, WMDLINEWRAP)) {
2772 	curcol = col;
2773 	collimit = term.cols - nuadj;
2774 	*screenrowp = currow;
2775 	if (col >= collimit) {
2776 	    col -= collimit;
2777 	    *screenrowp += 1;
2778 	    if (col >= term.cols)
2779 		*screenrowp += (col / term.cols);
2780 	    *screencolp = col % term.cols;
2781 	} else {
2782 	    *screencolp = col + nuadj;
2783 	}
2784 	/* kludge to keep the cursor within the window */
2785 	i = mode_row(curwp) - 1;
2786 	if (*screenrowp > i) {
2787 	    *screenrowp = i;
2788 	    *screencolp = term.cols - 1;
2789 	}
2790 	TRACE2(("... (%d,%d)\n", *screenrowp, *screencolp));
2791 	return2Code(FALSE);
2792     } else
2793 #endif
2794 	*screenrowp = currow;
2795 
2796     /* ...adjust to offset from shift-margin */
2797     curcol = col - w_val(curwp, WVAL_SIDEWAYS);
2798     *screencolp = curcol;
2799 
2800     /* if extended, flag so and update the virtual line image */
2801     collimit = col_limit(curwp);
2802     excess = curcol - collimit + liadj;
2803     if ((excess > 0) || (excess == 0 &&
2804 			 (DOT.o < llength(DOT.l) - 1))) {
2805 	if (w_val(curwp, WMDHORSCROLL)) {
2806 	    (void) mvrightwind(TRUE, excess + collimit / 2);
2807 	    moved = TRUE;
2808 	    preset_lmap0();
2809 	} else {
2810 	    *screencolp = update_extended_line(col, excess, TRUE);
2811 	}
2812     } else if (w_val(curwp, WVAL_SIDEWAYS) && (curcol < 1)) {
2813 	if (w_val(curwp, WMDHORSCROLL)) {
2814 	    (void) mvleftwind(TRUE, -curcol + collimit / 2 + 1);
2815 	    moved = TRUE;
2816 	    preset_lmap0();
2817 	} else {
2818 	    *screencolp = update_extended_line(col, 0, FALSE);
2819 	}
2820     } else {
2821 	if (vscreen[currow]->v_flag & VFEXT) {
2822 	    update_screen_line(curwp, lp, currow);
2823 	    vteeol();
2824 	    /* this line no longer is extended */
2825 	    vscreen[currow]->v_flag &= ~VFEXT;
2826 	}
2827     }
2828     if (!moved)
2829 	*screencolp += nuadj;
2830     TRACE2(("... (%d,%d)\n", *screenrowp, *screencolp));
2831     return2Code(moved);
2832 }
2833 
2834 #if CAN_SCROLL
2835 
2836 static int
same_video_text(const VIDEO_TEXT * virtual_text,const VIDEO_TEXT * physical_text,int ncols)2837 same_video_text(const VIDEO_TEXT * virtual_text, const VIDEO_TEXT *
2838 		physical_text, int ncols)
2839 {
2840     int rc = TRUE;
2841     int j;
2842 
2843     for (j = 0; j < ncols; ++j) {
2844 	if (virtual_text[j] != physical_text[j]) {
2845 	    rc = FALSE;
2846 	    break;
2847 	}
2848     }
2849 
2850     return rc;
2851 }
2852 
2853 /*
2854  * return TRUE on text match
2855  *
2856  * vrow - virtual row
2857  * prow - physical row
2858  */
2859 static int
same_row_text(int vrow,int prow)2860 same_row_text(int vrow, int prow)
2861 {
2862     return same_video_text(vscreen[vrow]->v_text,
2863 			   pscreen[prow]->v_text,
2864 			   term.cols);
2865 }
2866 
2867 /*
2868  * return the index of the first trailing whitespace character
2869  */
2870 static int
endofline(const VIDEO_TEXT * s,int n)2871 endofline(const VIDEO_TEXT * s, int n)
2872 {
2873     int i;
2874     for (i = n - 1; i >= 0; i--)
2875 	if (s[i] != ' ')
2876 	    return (i + 1);
2877     return (0);
2878 }
2879 
2880 /* move the "count" lines starting at "from" to "to" */
2881 static void
scroll_screen(int from,int to,int count)2882 scroll_screen(int from, int to, int count)
2883 {
2884     beginDisplay();
2885     ttrow = ttcol = -1;
2886     term.scroll(from, to, count);
2887     endofDisplay();
2888 }
2889 
2890 /*
2891  * Optimize simple scrolled screen region movements.  Argument chooses between
2892  * looking for inserts or deletes.  Original code by Roger Ove, for an early
2893  * version of MicroEMACS.  used by permission.
2894  *
2895  * inserts - returns true if it does an optimization
2896  */
2897 static int
simple_scroll(int inserts)2898 simple_scroll(int inserts)
2899 {
2900     struct VIDEO *vpv;		/* virtual screen image */
2901     struct VIDEO *vpp;		/* physical screen image */
2902     int i, j, k;
2903     int rows, cols;
2904     int first, match, count, ptarget = 0, vtarget = 0;
2905     size_t end;
2906     int longmatch, longcount;
2907     int longinplace, inplace;	/* count of lines which are already
2908 				   in the right place */
2909     int from, to;
2910 
2911     if (term.scroll == nullterm_scroll)		/* can't scroll */
2912 	return FALSE;
2913 
2914     rows = term.rows - 1;
2915     cols = term.cols;
2916 
2917     /* find first line that doesn't match */
2918     first = -1;
2919     for (i = 0; i < rows; i++) {
2920 	if (!same_row_text(i, i)) {
2921 	    first = i;
2922 	    break;
2923 	}
2924     }
2925     if (first < 0)
2926 	return FALSE;		/* there isn't one */
2927 
2928     vpv = vscreen[first];
2929     vpp = pscreen[first];
2930 
2931     if (inserts) {
2932 	/* determine types of potential scrolls */
2933 	end = (size_t) endofline(vpv->v_text, cols);
2934 	if (end == 0)
2935 	    ptarget = first;	/* newlines */
2936 	else if (same_video_text(vpp->v_text, vpv->v_text, (int) end))
2937 	    ptarget = first + 1;	/* broken line newlines */
2938 	else
2939 	    ptarget = first;
2940 	from = ptarget;
2941     } else {
2942 	from = vtarget = first + 1;
2943     }
2944 
2945     /* can we find that text elsewhere ? */
2946     longmatch = -1;
2947     longcount = 0;
2948     longinplace = 0;
2949     for (i = from + 1; i < rows; i++) {
2950 	if (inserts ? same_row_text(i, from) : same_row_text(from, i)) {
2951 	    match = i;
2952 	    count = 1;
2953 	    inplace = same_row_text(match, match) ? 1 : 0;
2954 	    for (j = match + 1, k = from + 1; j < rows && k < rows; j++, k++) {
2955 		if (inserts ? same_row_text(j, k) : same_row_text(k, j)) {
2956 		    count++;
2957 		    if (same_row_text(j, j))
2958 			inplace++;
2959 		} else
2960 		    break;
2961 	    }
2962 	    if (longcount - longinplace < count - inplace) {
2963 		longcount = count;
2964 		longmatch = match;
2965 		longinplace = inplace;
2966 	    }
2967 	}
2968     }
2969     match = longmatch;
2970     count = longcount;
2971 
2972     if (!inserts) {
2973 	/* full kill case? */
2974 	if (match > 0 && same_row_text(first, match - 1)) {
2975 	    vtarget--;
2976 	    match--;
2977 	    count++;
2978 	}
2979     }
2980 
2981     if (match > 0 && count > 2) {	/* got a scroll */
2982 	/* move the count lines starting at ptarget to match */
2983 	/* mlwrite("scrolls: move the %d lines starting at %d to %d",
2984 	   count,ptarget,match);
2985 	 */
2986 	if (inserts) {
2987 	    from = ptarget;
2988 	    to = match;
2989 	} else {
2990 	    from = match;
2991 	    to = vtarget;
2992 	}
2993 #if OPT_PSCREEN
2994 	/*
2995 	 * Update lines _before_ the scroll so that they will be available for
2996 	 * any updates which need to be done (due to a GraphicsExpose event in
2997 	 * X11...these occur when scrolling a partially obscured window).  Note
2998 	 * that in the typical case of scrolling a line or two that very few
2999 	 * memory accesses are performed.  We mostly shuffle pointers around.
3000 	 */
3001 #define SWAP_PLINE(a, b) do { VIDEO *temp = pscreen[a];	\
3002 			      pscreen[a] = pscreen[b];	\
3003 			      pscreen[b] = temp; } one_time
3004 #define CLEAR_PLINE(a)  do {						\
3005 			    MARK_LINE_DIRTY(a);				\
3006 			    for (j = 0; j < term.cols; j++) {		\
3007 				CELL_TEXT(a,j) = ' ';			\
3008 				CELL_ATTR(a,j) = 0;			\
3009 			    }						\
3010 			  } one_time
3011 	if (from < to) {
3012 	    /* FIXME: color */
3013 	    for (i = from; i < to; i++)
3014 		CLEAR_PLINE(i + count);
3015 	    for (i = count - 1; i >= 0; i--)
3016 		SWAP_PLINE(from + i, to + i);
3017 	} else {
3018 	    /* FIXME: color */
3019 	    for (i = to; i < from; i++)
3020 		CLEAR_PLINE(i);
3021 	    for (i = 0; i < count; i++)
3022 		SWAP_PLINE(from + i, to + i);
3023 	}
3024 #endif /* OPT_PSCREEN */
3025 	scroll_screen(from, to, count);
3026 #if !OPT_PSCREEN
3027 	for (i = 0; i < count; i++) {
3028 	    vpp = pscreen[to + i];
3029 	    vpv = vscreen[to + i];
3030 	    for (j = 0; j < cols; ++j)
3031 		vpp->v_text[j] = vpv->v_text[j];
3032 	}
3033 #if OPT_VIDEO_ATTRS
3034 #define SWAP_ATTR_PTR(a, b) do { VIDEO_ATTR *temp = pscreen[a]->v_attrs;  \
3035 				 pscreen[a]->v_attrs = pscreen[b]->v_attrs; \
3036 				 pscreen[b]->v_attrs = temp; } one_time
3037 	if (from < to) {
3038 	    /* FIXME: color */
3039 	    for (i = from; i < to; i++)
3040 		for (j = 0; j < term.cols; j++)
3041 		    CELL_ATTR(i + count, j) = 0;
3042 	    for (i = count - 1; i >= 0; i--)
3043 		SWAP_ATTR_PTR(from + i, to + i);
3044 
3045 	} else {
3046 	    /* FIXME: color */
3047 	    for (i = to; i < from; i++)
3048 		for (j = 0; j < term.cols; j++)
3049 		    CELL_ATTR(i, j) = 0;
3050 	    for (i = 0; i < count; i++)
3051 		SWAP_ATTR_PTR(from + i, to + i);
3052 	}
3053 #undef SWAP_ATTR_PTR
3054 #endif /* OPT_VIDEO_ATTRS */
3055 	if (inserts) {
3056 	    from = ptarget;
3057 	    to = match;
3058 	} else {
3059 	    from = vtarget + count;
3060 	    to = match + count;
3061 	}
3062 	for (i = from; i < to; i++) {
3063 	    VIDEO_TEXT *txt;
3064 	    txt = pscreen[i]->v_text;
3065 	    for (j = 0; j < term.cols; ++j)
3066 		txt[j] = ' ';
3067 	    vscreen[i]->v_flag |= VFCHG;
3068 	}
3069 #endif /* !OPT_PSCREEN */
3070 	return (TRUE);
3071     }
3072     return (FALSE);
3073 }
3074 
3075 #endif /* CAN_SCROLL */
3076 
3077 /*
3078  * update the physical screen from the virtual screen.  The 'force' flag
3079  * forces an update even if there is type-ahead.
3080  */
3081 static void
update_physical_screen(int force GCC_UNUSED)3082 update_physical_screen(int force GCC_UNUSED)
3083 {
3084     int i;
3085 
3086     TRACE((T_CALLED "update_physical_screen(%d)\n", force));
3087 #if CAN_SCROLL
3088     if (scrflags & WFKILLS)
3089 	(void) simple_scroll(FALSE);
3090     if (scrflags & WFINS)
3091 	(void) simple_scroll(TRUE);
3092     scrflags = 0;
3093 #endif
3094 
3095     for (i = 0; i < term.rows; ++i) {
3096 	/* for each line that needs to be updated */
3097 	if ((vscreen[i]->v_flag & (VFCHG | VFCOL)) != 0) {
3098 #if !DISP_X11
3099 	    if (TypeAhead(force))
3100 		returnVoid();
3101 #endif
3102 	    update_line(i, 0, term.cols);
3103 	}
3104     }
3105     returnVoid();
3106 }
3107 
3108 #if OPT_MLFORMAT || OPT_POSFORMAT || OPT_TITLE
3109 static void
mlfs_prefix(const char ** fsp,char ** msp,int lchar)3110 mlfs_prefix(const char **fsp, char **msp, int lchar)
3111 {
3112     const char *fs = *fsp;
3113     char *ms = *msp;
3114 
3115     if (*fs == ':') {
3116 	fs++;
3117 	while (*fs && *fs != ':') {
3118 	    if (*fs != '%')
3119 		*ms++ = *fs++;
3120 	    else {
3121 		fs++;
3122 		switch (*fs++) {
3123 		case EOS:
3124 		    fs--;
3125 		    break;
3126 		case '%':
3127 		    *ms++ = '%';
3128 		    break;
3129 		case ':':
3130 		    *ms++ = ':';
3131 		    break;
3132 		case '-':
3133 		    *ms++ = (char) lchar;
3134 		    break;
3135 		default:
3136 		    *ms++ = '%';
3137 		    *ms++ = *(fs - 1);
3138 		    break;
3139 		}
3140 	    }
3141 	}
3142     }
3143     *fsp = fs;
3144     *msp = ms;
3145 }
3146 
3147 static void
mlfs_suffix(const char ** fsp,char ** msp,int lchar)3148 mlfs_suffix(const char **fsp, char **msp, int lchar)
3149 {
3150     mlfs_prefix(fsp, msp, lchar);
3151     if (**fsp == ':')
3152 	(*fsp)++;
3153 }
3154 
3155 static void
mlfs_skipfix(const char ** fsp)3156 mlfs_skipfix(const char **fsp)
3157 {
3158     const char *fs = *fsp;
3159     if (*fs == ':') {
3160 	for (fs++; *fs && *fs != ':'; fs++) ;
3161 	if (*fs == ':')
3162 	    fs++;
3163 	for (; *fs && *fs != ':'; fs++) ;
3164 	if (*fs == ':')
3165 	    fs++;
3166     }
3167     *fsp = fs;
3168 }
3169 #endif /* OPT_MLFORMAT */
3170 
3171 #define PutModename(format, name) { \
3172 		if (ms != 0) { \
3173 			ms = lsprintf(ms, format, \
3174 				mcnt ? ' ' : '[', \
3175 				name); \
3176 		} \
3177 		mcnt++; \
3178 	}
3179 
3180 #define PutMode(mode,name) \
3181 	if (b_val(bp, mode)) PutModename("%c%s", name)
3182 
3183 #if OPT_MAJORMODE
3184 #define PutMajormode(bp) \
3185 	if (bp->majr != 0) \
3186 	    PutModename("%c%s", \
3187 		brief \
3188 		? bp->majr->shortname \
3189 		: bp->majr->longname)
3190 #else
3191 #define PutMajormode(bp)	/*nothing */
3192 #endif
3193 
3194 static int
modeline_modes(BUFFER * bp,char ** msptr,int brief)3195 modeline_modes(BUFFER *bp, char **msptr, int brief)
3196 {
3197     char *ms = msptr ? *msptr : 0;
3198     size_t mcnt = 0;
3199 
3200     PutMajormode(bp);
3201 #if COMPLETE_FILES || COMPLETE_DIRS
3202     if (b_is_directory(bp))
3203 	PutModename("%c%s", brief ? "dir" : "directory");
3204 #endif
3205 #if !OPT_MAJORMODE
3206     PutMode(MDCMOD, "cmode");
3207 #endif
3208 #if OPT_ENCRYPT
3209     PutMode(MDCRYPT, "crypt");
3210 #endif
3211     {
3212 #if OPT_RECORDSEP_CHOICES
3213 	int show = FALSE;
3214 
3215 	switch (b_val(bp, VAL_SHOW_FORMAT)) {
3216 	case SF_ALWAYS:
3217 	    show = TRUE;
3218 	    break;
3219 	case SF_NEVER:
3220 	    show = FALSE;
3221 	    break;
3222 	case SF_DIFFERS:
3223 	    show = b_val(bp, VAL_RECORD_SEP) != global_b_val(VAL_RECORD_SEP);
3224 	    break;
3225 	case SF_FOREIGN:
3226 	    show = b_val(bp, VAL_RECORD_SEP) != RS_SYS_DEFAULT;
3227 	    break;
3228 	case SF_LOCAL:
3229 	    show = is_local_b_val(bp, VAL_RECORD_SEP);
3230 	    break;
3231 	}
3232 	if (show)
3233 	    PutModename("%c%s",
3234 			choice_to_name(&fsm_recordsep_blist,
3235 				       b_val(bp, VAL_RECORD_SEP)));
3236 #else
3237 	PutMode(MDDOS, brief ? "dos" : "dos-style");
3238 #endif
3239     }
3240     PutMode(MDREADONLY, brief ? "ro" : "read-only");
3241     PutMode(MDVIEW, brief ? "view" : "view-only");
3242     PutMode(MDLOADING, "loading");
3243 #if OPT_LCKFILES
3244     PutMode(MDLOCKED, "locked by");
3245     if (ms != 0 && b_val(bp, MDLOCKED))
3246 	ms = lsprintf(ms, " %s", b_val_ptr(bp, VAL_LOCKER));
3247 #endif
3248     if (mcnt && ms)
3249 	*ms++ = ']';
3250     if (b_is_changed(bp)) {
3251 	if (ms != 0)
3252 	    ms = lsprintf(ms, "%s[modified]", mcnt ? " " : "");
3253 	mcnt++;
3254     }
3255     if (kbd_mac_recording()) {
3256 	if (ms != 0)
3257 	    ms = lsprintf(ms, "%s[recording]", mcnt ? " " : "");
3258 	mcnt++;
3259     }
3260     if (ms != 0)
3261 	*msptr = ms;
3262     return (mcnt != 0);
3263 }
3264 
3265 static char
modeline_show(WINDOW * wp,int lchar)3266 modeline_show(WINDOW *wp, int lchar)
3267 {
3268     char ic = (char) lchar;
3269     BUFFER *bp = wp->w_bufp;
3270 
3271     if (b_val(bp, MDSHOWMODE)) {
3272 #ifdef insertmode		/* insert mode is a trait for each window */
3273 	if (wp->w_traits.insmode == INSMODE_INS)
3274 	    ic = 'I';
3275 	else if (wp->w_traits.insmode == INSMODE_RPL)
3276 	    ic = 'R';
3277 	else if (wp->w_traits.insmode == INSMODE_OVR)
3278 	    ic = 'O';
3279 #else /* insertmode is a variable global to all windows */
3280 	if (wp == curwp) {
3281 	    if (insertmode == INSMODE_INS)
3282 		ic = 'I';
3283 	    else if (insertmode == INSMODE_RPL)
3284 		ic = 'R';
3285 	    else if (insertmode == INSMODE_OVR)
3286 		ic = 'O';
3287 	}
3288 #endif /* !defined(insertmode) */
3289     }
3290     return ic;
3291 }
3292 
3293 static const char *
rough_position(WINDOW * wp)3294 rough_position(WINDOW *wp)
3295 {
3296     LINE *lp = wp->w_line.l;
3297     int rows = wp->w_ntrows;
3298     const char *msg = 0;
3299 
3300     while (rows-- > 0) {
3301 	lp = lforw(lp);
3302 	if (lp == win_head(wp)) {
3303 	    msg = "bot";
3304 	    break;
3305 	}
3306     }
3307     if (lback(wp->w_line.l) == win_head(wp)) {
3308 	if (msg) {
3309 	    if (wp->w_line.l == win_head(wp))
3310 		msg = "emp";
3311 	    else
3312 		msg = "all";
3313 	} else {
3314 	    msg = "top";
3315 	}
3316     }
3317     if (!msg)
3318 	msg = "mid";
3319     return msg;
3320 }
3321 
3322 #define MAX_FORMAT ((NFILEN + 1) * 2 * COLS_UTF8)
3323 
3324 #define MARK_COL 80
3325 
3326 #if OPT_MLFORMAT || OPT_POSFORMAT || OPT_TITLE
3327 
3328 #define L_CURL '{'
3329 #define R_CURL '}'
3330 
3331 static int
percentage(WINDOW * wp)3332 percentage(WINDOW *wp)
3333 {
3334     BUFFER *bp = wp->w_bufp;
3335     L_NUM val;
3336     L_NUM denom = vl_line_count(bp);
3337 
3338 #ifdef WMDRULER
3339     if (w_val(wp, WMDRULER) && !is_empty_buf(bp))
3340 	val = wp->w_ruler_line;
3341     else
3342 #endif
3343 	val = line_no(bp, wp->w_dot.l);
3344 
3345     return PERCENT(val, denom);
3346 }
3347 
3348 #if OPT_MULTIBYTE
3349 static int
str_col2offs(WINDOW * wp,int target_col,const char * source,int length)3350 str_col2offs(WINDOW *wp, int target_col, const char *source, int length)
3351 {
3352     const char *base = source;
3353     int result = 0;
3354     int have_col = 0;
3355 
3356     while (have_col < target_col) {
3357 	int rc;
3358 	UINT value;
3359 	int need = column_sizes(wp, source, (UINT) length, &rc);
3360 	int cells = need;
3361 
3362 	switch (need) {
3363 	case COLS_UTF8:
3364 	    rc = vl_conv_to_utf32(&value, source, (B_COUNT) length);
3365 	    cells = vl_wcwidth((int) value);
3366 	    source += rc;
3367 	    length -= rc;
3368 	    break;
3369 	case COLS_CTRL:
3370 	    source += 1;
3371 	    length -= 1;
3372 	    break;
3373 	case COLS_8BIT:
3374 	    source += 1;
3375 	    length -= 1;
3376 	    break;
3377 	default:
3378 	    source += 1;
3379 	    length -= 1;
3380 	    break;
3381 	}
3382 	have_col += cells;
3383 
3384 	if (have_col < target_col)
3385 	    result = (int) (source - base);
3386     }
3387     return result;
3388 }
3389 #else
3390 #define str_col2offs(wp,target_col,value,length) target_col
3391 #endif
3392 
3393 #define HaveEnough(string) ((ms + strlen(string)) - base < MAX_FORMAT)
3394 
3395 /*
3396  * Format special single-use messages, i.e., the modeline format, which has
3397  * a number of special variables that we would like to output quickly.
3398  */
3399 void
special_formatter(TBUFF ** result,const char * fs,WINDOW * wp)3400 special_formatter(TBUFF **result, const char *fs, WINDOW *wp)
3401 {
3402     BUFFER *bp;
3403     char *ms;
3404     char *base;
3405     const char *want;
3406     const char *save_fs;
3407     char left_ms[MAX_FORMAT];
3408     char right_ms[MAX_FORMAT];
3409     char temp[MAX_FORMAT / 2];
3410     int have_cols;
3411     int col;
3412     int fc;
3413     char lchar;
3414     int need_eighty_column_indicator;
3415     int right_cols;
3416     int right_offs;
3417     int n;
3418     int skip = TRUE;
3419 
3420     if (fs == 0)
3421 	return;
3422 
3423     if (wp == wnullp)
3424 	return;
3425 
3426     TRACE((T_CALLED "special_formatter %s\n", fs));
3427     tb_init(result, EOS);
3428 
3429     left_ms[0] = right_ms[0] = EOS;
3430     ms = base = left_ms;
3431     need_eighty_column_indicator = FALSE;
3432 
3433     bp = wp->w_bufp;
3434 
3435     if (wp == curwp) {		/* mark the current buffer */
3436 	lchar = '=';
3437     } else {
3438 #if OPT_REVSTA
3439 	if (revexist)
3440 	    lchar = ' ';
3441 	else
3442 #endif
3443 	    lchar = '-';
3444     }
3445 
3446     while (*fs) {
3447 	/* check for single-character buffer overflow */
3448 	if ((ms + (COLS_UTF8 + 2)) - base >= MAX_FORMAT) {
3449 	    if (base == right_ms)
3450 		break;
3451 	    if (strncmp(fs, "%=", (size_t) 2)) {
3452 		++fs;
3453 		continue;
3454 	    }
3455 	}
3456 	if (*fs != '%') {
3457 	    *ms++ = *fs++;
3458 	} else {
3459 	    fs++;
3460 	    switch ((fc = *fs++)) {
3461 	    case EOS:		/* Null character ! */
3462 		fs--;
3463 		break;
3464 	    case '%':
3465 	    case ':':
3466 		*ms++ = *(fs - 1);
3467 		break;
3468 	    case '|':
3469 		need_eighty_column_indicator = TRUE;
3470 		break;
3471 	    case '-':
3472 		*ms++ = lchar;
3473 		break;
3474 	    case '=':
3475 		*ms = EOS;
3476 		ms = base = right_ms;
3477 		break;
3478 	    case 'i':		/* insert mode indicator */
3479 		*ms++ = modeline_show(wp, lchar);
3480 		break;
3481 	    case 'b':
3482 		want = (bp ? bp->b_bname : UNNAMED_BufName);
3483 		if (!HaveEnough(want))
3484 		    continue;
3485 		ms = lsprintf(ms, "%s", want);
3486 		break;
3487 	    case 'M':
3488 	    case 'm':
3489 		if (bp != 0 && modeline_modes(bp, (char **) 0, (fc == 'M'))) {
3490 		    mlfs_prefix(&fs, &ms, lchar);
3491 		    (void) modeline_modes(bp, &ms, (fc == 'M'));
3492 		    mlfs_suffix(&fs, &ms, lchar);
3493 		} else
3494 		    mlfs_skipfix(&fs);
3495 		break;
3496 	    case 'f':
3497 	    case 'F':
3498 		skip = TRUE;	/* assumption */
3499 
3500 		if (bp != 0 && bp->b_fname != 0) {
3501 		    char *p;
3502 
3503 		    /*
3504 		     * when b_fname is a pipe cmd, it can be
3505 		     * arbitrarily long
3506 		     */
3507 		    vl_strncpy(temp, bp->b_fname, sizeof(temp));
3508 
3509 		    if ((p = shorten_path(temp, FALSE)) != 0
3510 			&& *(p = skip_space_tab(p)) != '\0'
3511 			&& !eql_bname(bp, p)
3512 			&& ((fc == 'f')
3513 			    ? !is_internalname(p)
3514 			    : is_internalname(p))) {
3515 			mlfs_prefix(&fs, &ms, lchar);
3516 			if (!HaveEnough(p))
3517 			    continue;
3518 			ms = lsprintf(ms, "%s", p);
3519 			mlfs_suffix(&fs, &ms, lchar);
3520 			skip = FALSE;
3521 		    }
3522 		}
3523 		if (skip)
3524 		    mlfs_skipfix(&fs);
3525 		break;
3526 	    case 'r':
3527 	    case 'n':
3528 	    case 'N':
3529 		mlfs_prefix(&fs, &ms, lchar);
3530 		if (bp != 0) {
3531 		    if (bp->b_fname != 0 && !is_internalname(bp->b_bname)) {
3532 
3533 			vl_strncpy(temp, bp->b_fname, sizeof(temp));
3534 
3535 			switch (fc) {
3536 			case 'r':
3537 			    want = shorten_path(temp, FALSE);
3538 			    if (want == 0)
3539 				want = temp;
3540 			    break;
3541 			case 'n':
3542 			    want = pathleaf(temp);
3543 			    break;
3544 			default:
3545 			    want = temp;
3546 			    break;
3547 			}
3548 		    } else {
3549 			want = bp->b_bname;
3550 		    }
3551 		} else {
3552 		    want = UNNAMED_BufName;
3553 		}
3554 		if (!HaveEnough(want))
3555 		    continue;
3556 		ms = lsprintf(ms, "%s", want);
3557 		mlfs_suffix(&fs, &ms, lchar);
3558 		break;
3559 #ifdef WMDRULER
3560 	    case 'l':		/* line number */
3561 	    case 'c':		/* column number */
3562 	    case 'p':		/* percentage */
3563 	    case 'L':		/* number of lines in buffer */
3564 
3565 		if (w_val(wp, WMDRULER) && (bp != 0 && !is_empty_buf(bp))) {
3566 		    int val = 0;
3567 		    switch (fc) {
3568 		    case 'l':
3569 			val = wp->w_ruler_line;
3570 			break;
3571 		    case 'L':
3572 			val = vl_line_count(bp);
3573 			break;
3574 		    case 'c':
3575 			val = wp->w_ruler_col;
3576 			break;
3577 		    case 'p':
3578 			val = percentage(wp);
3579 			break;
3580 		    }
3581 		    mlfs_prefix(&fs, &ms, lchar);
3582 		    ms = lsprintf(ms, "%d", val);
3583 		    mlfs_suffix(&fs, &ms, lchar);
3584 		} else
3585 		    mlfs_skipfix(&fs);
3586 		break;
3587 
3588 #endif
3589 #ifdef WMDSHOWCHAR
3590 	    case 'C':
3591 		if (w_val(wp, WMDSHOWCHAR)
3592 		    && (bp != 0 && !is_empty_buf(bp))
3593 		    && (wp->w_dot.o < llength(wp->w_dot.l)
3594 			|| line_has_newline(wp->w_dot.l, bp))) {
3595 		    sprintf(temp, "%02X", char_at_mark(wp->w_dot));
3596 		    mlfs_prefix(&fs, &ms, lchar);
3597 		    ms = lsprintf(ms, "%s", temp);
3598 		    mlfs_suffix(&fs, &ms, lchar);
3599 		} else {
3600 		    mlfs_skipfix(&fs);
3601 		}
3602 		break;
3603 #endif
3604 	    case 'P':
3605 		ms = lsprintf(ms, "%d", percentage(wp));
3606 		break;
3607 
3608 	    case 'S':
3609 		if (
3610 #ifdef WMDRULER
3611 		       !w_val(wp, WMDRULER) ||
3612 #endif
3613 		       ((bp == 0) || is_empty_buf(bp))) {
3614 		    mlfs_prefix(&fs, &ms, lchar);
3615 		    ms = lsprintf(ms, " %s ", rough_position(wp));
3616 		    mlfs_suffix(&fs, &ms, lchar);
3617 		} else
3618 		    mlfs_skipfix(&fs);
3619 		break;
3620 	    case L_CURL:
3621 		save_fs = fs;
3622 		while (*fs != EOS && *fs != R_CURL)
3623 		    fs++;
3624 
3625 		if (fs != save_fs) {
3626 		    int flag = clexec;
3627 		    char *save_execstr;
3628 		    TBUFF *tok = 0;
3629 
3630 		    save_execstr = execstr;
3631 		    clexec = TRUE;
3632 		    execstr = temp;
3633 
3634 		    strncpy0(temp, save_fs, (size_t) (fs + 1 - save_fs));
3635 		    execstr = get_token(execstr,
3636 					&tok,
3637 					eol_null,
3638 					EOS,
3639 					(int *) 0);
3640 		    vl_strncpy(temp, tokval(tb_values(tok)), sizeof(temp));
3641 
3642 		    tb_free(&tok);
3643 		    execstr = save_execstr;
3644 		    clexec = flag;
3645 		} else {
3646 		    *temp = EOS;
3647 		}
3648 		if (*fs != EOS)
3649 		    fs++;
3650 		save_fs = fs;
3651 		/*
3652 		 * Allow an optional <number><format> on the end of the
3653 		 * token, to reformat it.  Don't bother reformatting if
3654 		 * it is just a 'd' added to make the string unambiguous.
3655 		 */
3656 		fs = skip_cnumber(fs);
3657 		if (isAlpha(*fs)) {
3658 		    if ((*fs != 'd' || fs != save_fs)) {
3659 			char format[NSTRING];
3660 
3661 			format[0] = '%';
3662 			strncpy0(format + 1, save_fs, (size_t) (fs + 2 - save_fs));
3663 			if (strchr("dDxXo", *fs)) {
3664 			    int value = scan_int(temp);
3665 			    ms = lsprintf(ms, format, value);
3666 			} else {
3667 			    ms = lsprintf(ms, format, temp);
3668 			}
3669 		    } else {
3670 			ms = lsprintf(ms, "%s", temp);
3671 		    }
3672 		    fs++;
3673 		} else {
3674 		    ms = lsprintf(ms, "%s", temp);
3675 		}
3676 		break;
3677 	    default:
3678 		*ms++ = '%';
3679 		*ms++ = *(fs - 1);
3680 		break;
3681 	    }
3682 	}
3683     }
3684     *ms++ = EOS;
3685 
3686     tb_bappend(result, left_ms, strlen(left_ms));
3687 
3688     if (tb_values(*result) != 0) {
3689 	have_cols = tb_columns(*result);
3690 
3691 	if ((have_cols < term.cols)
3692 	    && (right_cols = str_columns(right_ms)) != 0) {
3693 	    for (n = term.cols - have_cols - right_cols; n > 0; n--)
3694 		tb_append(result, lchar);
3695 	    if ((n = term.cols - right_cols) < 0) {
3696 		col = right_cols + n;
3697 		n = -n;
3698 	    } else {
3699 		col = right_cols;
3700 		n = 0;
3701 	    }
3702 	    if ((col < term.cols)
3703 		&& (n < right_cols)) {
3704 		right_offs = str_col2offs(wp, n, right_ms, (int) strlen(right_ms));
3705 		if ((tb_columns(*result) + col) > term.cols) {
3706 		    tb_setlen(result,
3707 			      str_col2offs(wp, term.cols - col,
3708 					   tb_values(*result),
3709 					   (int) tb_length(*result)));
3710 		    have_cols = tb_columns(*result);
3711 		    for (n = term.cols - have_cols - right_cols; n > 0; n--)
3712 			tb_append(result, lchar);
3713 		}
3714 		tb_bappend(result, right_ms + right_offs, (size_t) col);
3715 	    }
3716 	}
3717     }
3718 
3719     /* mark column 80 */
3720     if (tb_values(*result) != 0) {
3721 	have_cols = tb_columns(*result);
3722 
3723 	if (need_eighty_column_indicator) {
3724 	    int left = -nu_width(wp);
3725 	    char *ss;
3726 #ifdef WMDLINEWRAP
3727 	    if (!w_val(wp, WMDLINEWRAP))
3728 #endif
3729 		left += w_val(wp, WVAL_SIDEWAYS);
3730 	    n = term.cols + left;
3731 	    col = MARK_COL - left;
3732 
3733 	    if ((n > MARK_COL) && (col >= 0)) {
3734 		for (n = have_cols; n < col; n++)
3735 		    tb_append(result, lchar);
3736 		right_offs = str_col2offs(wp, col,
3737 					  tb_values(*result),
3738 					  (int) tb_length(*result));
3739 		if ((ss = tb_values(*result)) != 0
3740 		    && ss[right_offs] == lchar) {
3741 		    ss[right_offs] = '|';
3742 		}
3743 	    }
3744 	}
3745     }
3746     tb_append(result, EOS);
3747     returnVoid();
3748 }
3749 #endif
3750 
3751 /*
3752  * Redisplay the mode line for the window pointed to by the "wp".
3753  * This is the only routine that has any idea of how the modeline is
3754  * formatted.  You can change the modeline format by hacking at this
3755  * routine.  Called by "update" any time there is a dirty window.
3756  */
3757 static void
update_modeline(WINDOW * wp)3758 update_modeline(WINDOW *wp)
3759 {
3760 #if OPT_MLFORMAT
3761     static TBUFF *result;
3762 #else
3763     static char lchar_pad[4];
3764     BUFFER *bp;
3765     char temp[MAX_FORMAT / 2];
3766     int col;
3767     char lchar[2];
3768     int right_cols;
3769     char left_ms[MAX_FORMAT];
3770     char *ms;
3771     char right_ms[MAX_FORMAT];
3772     int my_col;
3773 #endif
3774     int my_row;
3775 
3776     TRACE((T_CALLED "update_modeline\n"));
3777     if (is_vtinit()) {
3778 	term.cursorvis(FALSE);
3779 
3780 	my_row = mode_row(wp);	/* Location. */
3781 #if OPT_VIDEO_ATTRS
3782 	{
3783 	    VIDEO_ATTR attr;
3784 	    if (wp == curwp)
3785 		attr = VAMLFOC;
3786 	    else
3787 		attr = VAML;
3788 #if OPT_REVSTA
3789 #ifdef	GVAL_MCOLOR
3790 	    attr = (VIDEO_ATTR) (attr | (global_g_val(GVAL_MCOLOR) & ~VASPCOL));
3791 #else
3792 	    attr |= VAREV;
3793 #endif
3794 #endif
3795 	    vscreen[my_row]->v_flag |= VFCHG;
3796 	    set_vattrs(my_row, 0, attr, (size_t) term.cols);
3797 	}
3798 #else
3799 	vscreen[my_row]->v_flag |= VFCHG | VFREQ | VFCOL;	/* Redraw next time. */
3800 #endif
3801 #if OPT_COLOR
3802 	ReqFcolor(vscreen[my_row]) = gfcolor;
3803 	ReqBcolor(vscreen[my_row]) = gbcolor;
3804 #endif
3805 	vtmove(my_row, 0);	/* Seek to right line. */
3806 
3807 #if OPT_MLFORMAT
3808 	special_formatter(&result, modeline_format, wp);
3809 #if OPT_MULTIBYTE
3810 	{
3811 	    char *from = tb_values(result);
3812 	    int n = (int) strlen(from);
3813 	    VTSET_LOOP(n > 0);
3814 	}
3815 #else
3816 	vtputsn(wp, tb_values(result), tb_length(result));
3817 #endif
3818 #else /* hard-coded format */
3819 	bp = wp->w_bufp;
3820 	if (bp == 0)
3821 	    bp = curbp;
3822 	if (bp == 0)
3823 	    bp = bminip;
3824 	if (wp == curwp) {	/* current buffer shows as = */
3825 	    lchar[0] = '=';
3826 	} else {
3827 #if OPT_REVSTA
3828 	    if (revexist)
3829 		lchar[0] = ' ';
3830 	    else
3831 #endif
3832 		lchar[0] = '-';
3833 	}
3834 	left_ms[0] = right_ms[0] = EOS;
3835 	ms = left_ms;
3836 	ms = lsprintf(ms, "%c%c%c %s ",
3837 		      lchar[0], modeline_show(wp, lchar[0]), lchar[0], bp->b_bname);
3838 	if (modeline_modes(bp, &ms, FALSE))
3839 	    *ms++ = ' ';
3840 	if (bp->b_fname != 0
3841 	    && (shorten_path(vl_strncpy(temp, bp->b_fname, sizeof(temp)), FALSE))
3842 	    && !eql_bname(bp, temp)) {
3843 	    if (is_internalname(temp)) {
3844 		for (my_col = term.cols - (13 + strlen(temp));
3845 		     my_col > 0; my_col--)
3846 		    *ms++ = lchar[0];
3847 	    } else {
3848 		ms = lsprintf(ms, "is");
3849 	    }
3850 	    ms = lsprintf(ms, " %s ", temp);
3851 	}
3852 	memset(lchar_pad, lchar[0], sizeof(lchar_pad) - 1);
3853 #ifdef WMDRULER
3854 	if (w_val(wp, WMDRULER))
3855 	    (void) lsprintf(right_ms, " (%d,%d) %s",
3856 			    wp->w_ruler_line, wp->w_ruler_col, lchar_pad);
3857 	else
3858 #endif
3859 	    (void) lsprintf(right_ms, " %s %s", rough_position(wp), lchar_pad);
3860 
3861 	*ms++ = EOS;
3862 	right_cols = str_columns(right_ms);
3863 	vtputsn(wp, left_ms, term.cols);
3864 	for (my_col = term.cols - str_columns(left_ms) - right_cols;
3865 	     my_col > 0;
3866 	     my_col--)
3867 	    vtputc(wp, lchar, 1);
3868 
3869 	vtcol = term.cols - right_cols;
3870 	if (vtcol < 0) {
3871 	    my_col = -vtcol;
3872 	    vtcol = 0;
3873 	} else {
3874 	    my_col = 0;
3875 	}
3876 
3877 	if (term.cols > vtcol)
3878 	    vtputsn(wp, right_ms + my_col, term.cols - vtcol);
3879 
3880 	col = -nu_width(wp);
3881 #ifdef WMDLINEWRAP
3882 	if (!w_val(wp, WMDLINEWRAP))
3883 #endif
3884 	    col += w_val(wp, WVAL_SIDEWAYS);
3885 	my_col = term.cols + col;
3886 	col = MARK_COL - col;
3887 
3888 	if ((my_col > MARK_COL)
3889 	    && (col >= 0)
3890 	    && (vscreen[vtrow]->v_text[col] == lchar[0])) {
3891 	    vtcol = col;
3892 	    vtputc(wp, "|", 1);
3893 	}
3894 #endif /* OPT_MLFORMAT */
3895 	term.cursorvis(TRUE);
3896 	TRACE2(("MODE %4d:%s\n",
3897 		vtrow,
3898 		visible_video_text(vscreen[vtrow]->v_text, vtcol)));
3899     }
3900     returnVoid();
3901 }
3902 
3903 /*
3904  * Update all the mode lines.
3905  */
3906 void
upmode(void)3907 upmode(void)
3908 {
3909     WINDOW *wp;
3910 
3911     for_each_window(wp) {
3912 	wp->w_flag |= WFMODE;
3913     }
3914 }
3915 
3916 /*
3917  * Check to see if the cursor is on in the window and re-frame it if needed or
3918  * wanted.
3919  */
3920 static void
reframe_cursor_position(WINDOW * wp)3921 reframe_cursor_position(WINDOW *wp)
3922 {
3923     LINE *dlp;
3924     LINE *lp;
3925     int i = 0;
3926     int rows;
3927     int founddot = FALSE;	/* set to true iff we find dot */
3928     int tildecount;
3929 
3930     /* if not a requested reframe, check for a needed one */
3931     if ((wp->w_flag & WFFORCE) == 0) {
3932 	/* initial update in main.c may not set these first... */
3933 	if (!valid_line_wp(wp->w_dot.l, wp)) {
3934 	    wp->w_dot.l = lforw(win_head(wp));
3935 	    wp->w_dot.o = 0;
3936 	}
3937 	if (!valid_line_wp(wp->w_line.l, wp)) {
3938 	    wp->w_line.l = wp->w_dot.l;
3939 	    wp->w_line.o = 0;
3940 	}
3941 #if CAN_SCROLL
3942 	/* loop from one line above the window to one line after */
3943 	lp = lback(wp->w_line.l);
3944 	i = -line_height(wp, lp);
3945 #else
3946 	/* loop through the window */
3947 	lp = wp->w_line.l;
3948 	i = 0;
3949 #endif
3950 	for_ever {
3951 	    /* if the line is in the window, no reframe */
3952 	    if (lp == wp->w_dot.l) {
3953 		founddot = TRUE;
3954 #if CAN_SCROLL
3955 		/* if not _quite_ in, we'll reframe gently */
3956 		if (i < 0 || i >= wp->w_ntrows) {
3957 		    /* if the terminal can't help, then
3958 		       we're simply outside */
3959 		    if (term.scroll == nullterm_scroll)
3960 			i = wp->w_force;
3961 		    break;
3962 		}
3963 #endif
3964 #ifdef WMDLINEWRAP
3965 		if (w_val(wp, WMDLINEWRAP)
3966 		    && i > 0
3967 		    && i + line_height(wp, lp) > wp->w_ntrows) {
3968 		    i = wp->w_ntrows;
3969 		    break;
3970 		}
3971 #endif
3972 		lp = wp->w_line.l;
3973 		goto kill_tildes;
3974 	    }
3975 
3976 	    /* if we are at the end of the file, reframe */
3977 	    if (i >= 0 && lp == win_head(wp))
3978 		break;
3979 
3980 	    /* on to the next line */
3981 	    if (i >= wp->w_ntrows) {
3982 		i = 0;		/* dot-not-found */
3983 		break;
3984 	    }
3985 	    i += line_height(wp, lp);
3986 	    lp = lforw(lp);
3987 	}
3988     }
3989 #if CAN_SCROLL
3990     if (i < 0) {		/* we're just above the window */
3991 	i = 1;			/* put dot at first line */
3992 	scrflags |= WFINS;
3993     } else if (founddot && (i >= wp->w_ntrows)) {
3994 	/* we're just below the window */
3995 	i = -1;			/* put dot at last line */
3996 	scrflags |= WFKILLS;
3997     } else			/* put dot where requested */
3998 #endif
3999 	i = wp->w_force;	/* (is 0, unless reposition() was called) */
4000 
4001     wp->w_flag |= WFMODE;
4002     wp->w_line.o = 0;
4003 
4004     /* w_force specifies which line of the window dot should end up on */
4005     /*      positive --> lines from the top                         */
4006     /*      negative --> lines from the bottom                      */
4007     /*      zero --> middle of window                               */
4008 
4009     lp = wp->w_dot.l;
4010 
4011 #ifdef WMDLINEWRAP
4012     /*
4013      * Center dot in middle of screen with line-wrapping
4014      */
4015     if (i == 0 && w_val(wp, WMDLINEWRAP)) {
4016 	rows = (wp->w_ntrows - line_height(wp, lp) + 2) / 2;
4017 	while (rows > 0) {
4018 	    dlp = lback(lp);
4019 	    if (dlp == win_head(wp))
4020 		break;
4021 	    if ((rows -= line_height(wp, dlp)) < 0)
4022 		break;
4023 	    lp = dlp;
4024 	}
4025     } else
4026 #endif
4027     {
4028 	rows = (i != 0)
4029 	    ? wp->w_ntrows
4030 	    : wp->w_ntrows / 2;
4031 	while (rows > 0) {
4032 	    if ((i > 0)
4033 		&& (--i <= 0))
4034 		break;
4035 	    dlp = lback(lp);
4036 	    if (dlp == win_head(wp))
4037 		break;
4038 	    if ((rows -= line_height(wp, lp)) < 0)
4039 		break;
4040 	    lp = dlp;
4041 	}
4042 	if (rows < line_height(wp, lp)
4043 	    && (lp != wp->w_dot.l)) {
4044 	    while (i++ < 0) {
4045 		dlp = lforw(lp);
4046 		if (dlp == win_head(wp))
4047 		    break;
4048 		else
4049 		    lp = dlp;
4050 	    }
4051 	}
4052     }
4053   kill_tildes:
4054     /* Eliminate as many tildes as possible from bottom */
4055     dlp = lp;
4056     rows = wp->w_ntrows;
4057     while (rows > 0 && (dlp != win_head(wp))) {
4058 	rows -= line_height(wp, dlp);
4059 	dlp = lforw(dlp);
4060     }
4061     dlp = lback(lp);
4062 
4063     tildecount = (wp->w_ntrows * ntildes) / 100;
4064     if (tildecount == wp->w_ntrows)
4065 	tildecount--;
4066 
4067     while (dlp != win_head(wp)
4068 	   && (rows -= line_height(wp, dlp)) >= tildecount) {
4069 	lp = dlp;
4070 	dlp = lback(lp);
4071     }
4072 
4073     /* and reset the current line-at-top-of-window */
4074     if (lp != win_head(wp)	/* mouse click could be past end */
4075 	&&lp != wp->w_line.l) {	/* no need to set it if already there */
4076 	wp->w_line.l = lp;
4077 	wp->w_flag |= WFHARD;
4078 	clr_typed_flags(wp->w_flag, USHORT, WFFORCE);
4079     }
4080 #ifdef WMDLINEWRAP
4081     /*
4082      * Ensure that dot will be visible, by adjusting the w_line.o value if
4083      * necessary.  That's used to start the beginning of the first line in
4084      * a window "before" the start of the window.
4085      */
4086     if (w_val(wp, WMDLINEWRAP)
4087 	&& sameline(wp->w_line, wp->w_dot)) {
4088 	int want = mark2col(wp, wp->w_dot) / term.cols;
4089 	if (want + wp->w_line.o >= wp->w_ntrows) {
4090 	    wp->w_line.o = wp->w_ntrows - want - 1;
4091 	    wp->w_flag |= WFHARD;
4092 	    clr_typed_flags(wp->w_flag, USHORT, WFFORCE);
4093 	} else if (want + wp->w_line.o < 0) {
4094 	    wp->w_line.o = -want;
4095 	    wp->w_flag |= WFHARD;
4096 	    clr_typed_flags(wp->w_flag, USHORT, WFFORCE);
4097 	}
4098     } else if (!w_val(wp, WMDLINEWRAP)) {
4099 	if (wp->w_line.o < 0) {
4100 	    wp->w_line.o = 0;
4101 	    wp->w_flag |= WFHARD;
4102 	    clr_typed_flags(wp->w_flag, USHORT, WFFORCE);
4103 	}
4104     }
4105 #endif
4106 }
4107 
4108 /*
4109  * De-extend any line that deserves it.
4110  */
4111 static void
de_extend_lines(void)4112 de_extend_lines(void)
4113 {
4114     WINDOW *wp;
4115     LINE *lp;
4116     int i;
4117 
4118     for_each_visible_window(wp) {
4119 	if ((lp = wp->w_line.l) == NULL) {
4120 	    /* this should not happen */
4121 	    mlforce("BUG:  lost w_line for %s", wp->w_bufp->b_bname);
4122 	    lp = win_head(wp);
4123 	    wp->w_toprow = 0;
4124 	}
4125 	if (valid_line_wp(lp, wp)) {
4126 	    i = TopRow(wp);
4127 
4128 	    while (i < mode_row(wp)) {
4129 		if (i >= 0
4130 		    && (vscreen[i]->v_flag & VFEXT) != 0) {
4131 		    if ((wp != curwp)
4132 			|| (lp != wp->w_dot.l)
4133 			|| ((i != currow)
4134 			    && (curcol < col_limit(wp)))) {
4135 			update_screen_line(wp, lp, i);
4136 			vteeol();
4137 			/* this line no longer is extended */
4138 			vscreen[i]->v_flag &= ~VFEXT;
4139 		    }
4140 		}
4141 		i += line_height(wp, lp);
4142 		lp = lforw(lp);
4143 	    }
4144 	}
4145     }
4146 }
4147 
4148 /*
4149  * Make sure that the display is right. This is a three part process. First,
4150  * scan through all of the windows looking for dirty ones. Check the framing,
4151  * and refresh the screen. Second, make sure that "currow" and "curcol" are
4152  * correct for the current window. Third, make the virtual and physical
4153  * screens the same.
4154  */
4155 int
update(int force)4156 update(int force /* force update past type ahead? */ )
4157 {
4158     WINDOW *wp;
4159     int origrow, origcol;
4160     int screenrow, screencol;
4161     int updated = FALSE;
4162 
4163     TRACE((T_CALLED "update(%d)\n", force));
4164 
4165     /* Get row and column prior to doing the update in case we are
4166      * reading the message line.
4167      */
4168     origrow = ttrow;
4169     origcol = ttcol;
4170 
4171     if (clhide || (valid_window(curwp) && !is_visible_window(curwp)))
4172 	returnCode(TRUE);
4173 
4174     /*
4175      * If not initialized, just return.
4176      */
4177     if (!valid_buffer(curbp)
4178 	|| !vscreen
4179 	|| !valid_window(curwp))
4180 	returnCode(FALSE);
4181 
4182     /*
4183      * Don't try to update if we got called via a read-hook on a window
4184      * that isn't complete.
4185      */
4186     if (!valid_buffer(curwp->w_bufp)
4187 	|| curwp->w_bufp->b_nwnd == 0
4188 	|| curwp->w_ntrows < 1)
4189 	returnCode(FALSE);
4190     if (TypeAhead(force))
4191 	returnCode(SORTOFTRUE);
4192 
4193     /* don't display during keystroke replay */
4194     if (!force && kbd_replaying(TRUE) && (get_recorded_char(FALSE) != -1))
4195 	returnCode(SORTOFTRUE);
4196 
4197     beginDisplay();
4198 
4199     preset_lmap0();
4200 #if OPT_TITLE
4201     /*
4202      * Only update the title when we have nothing better to do.  The
4203      * auto_set_title logic is otherwise likely to set the title frequently if
4204      * [Buffer List] is visible.
4205      */
4206     if (tb_values(request_title) != 0
4207 	&& (tb_values(current_title) == 0
4208 	    || strcmp(tb_values(current_title), tb_values(request_title)))) {
4209 	tb_copy(&current_title, request_title);
4210 	term.set_title(tb_values(request_title));
4211     }
4212 #endif
4213 
4214     /* propagate mode line changes to all instances of
4215        a buffer displayed in more than one window */
4216     for_each_visible_window(wp) {
4217 	if (wp->w_flag & WFMODE) {
4218 	    if (wp->w_bufp->b_nwnd > 1) {
4219 		/* make sure all previous windows have this */
4220 		WINDOW *owp;
4221 		for_each_visible_window(owp) {
4222 		    if (owp->w_bufp == wp->w_bufp)
4223 			owp->w_flag |= WFMODE;
4224 		}
4225 	    }
4226 	}
4227 #ifdef WMDLINEWRAP
4228 	/* Make sure that movements in very long wrapped lines
4229 	   get updated properly.  */
4230 	if (w_val(wp, WMDLINEWRAP)
4231 	    && line_height(wp, wp->w_dot.l) > wp->w_ntrows)
4232 	    wp->w_flag |= WFMOVE;
4233 #endif
4234 #if OPT_CACHE_VCOL
4235 	if (wp->w_flag & (WFEDIT | WFHARD | WFMODE | WFKILLS | WFINS))
4236 	    wp->w_traits.w_left_dot = nullmark;
4237 #endif
4238     }
4239 #ifdef WMDSHOWVARS
4240     if (curwp != 0
4241 	&& w_val(curwp, WMDSHOWVARS)) {
4242 	updatelistvariables();
4243     }
4244 #endif
4245 
4246     /* look for scratch-buffers that should be recomputed.  */
4247 #if OPT_UPBUFF
4248     for_each_visible_window(wp) {
4249 	if (wp->w_flag) {
4250 	    reframe_cursor_position(wp);	/* check the framing */
4251 	}
4252 	if (b_is_obsolete(wp->w_bufp))
4253 	    recompute_buffer(wp->w_bufp);
4254     }
4255 #endif
4256 
4257     /* look for windows that need the ruler updated */
4258 #ifdef WMDRULER
4259     for_each_visible_window(wp) {
4260 #ifdef WMDSHOWCHAR
4261 	if (w_val(wp, WMDSHOWCHAR)) {
4262 	    wp->w_flag |= WFMODE;
4263 	    update_char_classes();
4264 	}
4265 #endif
4266 	if (w_val(wp, WMDRULER)) {
4267 	    int line = line_no(wp->w_bufp, wp->w_dot.l);
4268 	    int col = dot_to_vcol(wp) + 1;
4269 
4270 	    if (line != wp->w_ruler_line
4271 		|| col != wp->w_ruler_col) {
4272 		wp->w_ruler_line = line;
4273 		wp->w_ruler_col = col;
4274 		wp->w_flag |= WFMODE;
4275 	    }
4276 	} else if (wp->w_flag & WFSTAT) {
4277 	    wp->w_flag |= WFMODE;
4278 	}
4279 	clr_typed_flags(wp->w_flag, USHORT, WFSTAT);
4280     }
4281 #endif
4282 
4283     do {
4284 	/* update any windows that need refreshing */
4285 	for_each_visible_window(wp) {
4286 	    if (wp->w_flag) {
4287 		if ((wp->w_flag & ~(WFMOVE)) && !updated++)
4288 		    term.cursorvis(FALSE);
4289 		/* if the window has changed, service it */
4290 		if (wp->w_flag & (WFKILLS | WFINS)) {
4291 		    scrflags |= (wp->w_flag & (WFINS | WFKILLS));
4292 		    clr_typed_flags(wp->w_flag, USHORT, WFKILLS | WFINS);
4293 		}
4294 		if ((wp->w_flag & ~(WFMODE)) == WFEDIT) {
4295 		    update_oneline(wp);		/* update EDITed line */
4296 		} else if (wp->w_flag & ~(WFMOVE | WFMODE)) {
4297 		    update_all(wp);	/* update all lines */
4298 		}
4299 #if OPT_SCROLLBARS
4300 		if (wp->w_flag & (WFHARD | WFMOVE | WFSBAR))
4301 		    gui_update_scrollbar(wp);
4302 #endif /* OPT_SCROLLBARS */
4303 
4304 #if OPT_VIDEO_ATTRS
4305 		if (wp->w_flag & (WFHARD | WFEDIT))
4306 		    update_window_attrs(wp);
4307 #endif
4308 		if (scrflags || (wp->w_flag & (WFMODE | WFCOLR)))
4309 		    update_modeline(wp);	/* update modeline */
4310 		wp->w_flag = 0;
4311 		wp->w_force = 0;
4312 	    }
4313 	}
4314 
4315 	/* Recalculate the current hardware cursor location.  If true, we've
4316 	 * done a horizontal scroll.
4317 	 */
4318     } while (update_cursor_position(&screenrow, &screencol));
4319 
4320     /* check for lines to de-extend */
4321     de_extend_lines();
4322 
4323     /* if screen is garbage, re-plot it */
4324     if (sgarbf || need_update)
4325 	update_garbaged_screen();
4326 
4327     /* update the virtual screen to the physical screen */
4328     update_physical_screen(force);
4329 
4330     /* update the cursor and flush the buffers */
4331     if (reading_msg_line)
4332 	movecursor(origrow, origcol);
4333     else
4334 	movecursor(screenrow, screencol);
4335 
4336     if (updated)
4337 	term.cursorvis(TRUE);
4338 #if OPT_ICURSOR
4339     if (curwp != 0) {
4340 	static int old_insertmode = -1;
4341 
4342 	if (insertmode != old_insertmode) {
4343 	    old_insertmode = insertmode;
4344 	    term.icursor(insertmode);
4345 	}
4346     }
4347 #endif
4348     term.flush();
4349     endofDisplay();
4350     i_displayed = TRUE;
4351 
4352     while (allow_working_msg() && (chg_width && chg_height))
4353 	newscreensize(chg_height, chg_width);
4354     returnCode(TRUE);
4355 }
4356 
4357 /*
4358  * Highlight the requested portion of the screen.  We're mucking with the video
4359  * attributes on the line here, so this is NOT good code - it would be better
4360  * if there was an individual colour attribute per character, rather than per
4361  * row, but I didn't write the original code.  Anyway, hilite is called only
4362  * once so far, so it's not that big a deal.
4363  *
4364  * row       - row to start highlighting
4365  * colfrom   - column to start highlighting
4366  * colto     - column to end highlighting
4367  * on        - start highlighting
4368  */
4369 void
hilite(int row,int colfrom,int colto,int on)4370 hilite(int row, int colfrom, int colto, int on)
4371 {
4372 #if !OPT_VIDEO_ATTRS
4373     VIDEO *vp1 = vscreen[row];
4374 #endif
4375 #ifdef WMDLINEWRAP
4376     WINDOW *wp = row2window(row);
4377     if (w_val(wp, WMDLINEWRAP)) {
4378 	if (colfrom < 0)
4379 	    colfrom = 0;
4380 	if (colfrom > term.cols) {
4381 	    do {
4382 		row++;
4383 		colfrom -= term.cols;
4384 		colto -= term.cols;
4385 		hilite(row, colfrom, colto, on);
4386 	    } while (colto > term.cols);
4387 	    return;
4388 	}
4389     }
4390 #endif
4391     if (row < term.rows - 1 && (colfrom >= 0 || colto <= term.cols)) {
4392 	if (colfrom < 0)
4393 	    colfrom = 0;
4394 	if (colto > term.cols)
4395 	    colto = term.cols;
4396 #if OPT_VIDEO_ATTRS
4397 	if (on) {
4398 	    int col;
4399 	    for (col = colfrom; col < colto; col++)
4400 		vscreen[row]->v_attrs[col] |= VAREV;
4401 	} else {
4402 	    int col;
4403 	    for (col = colfrom; col < colto; col++) {
4404 		clr_typed_flags(vscreen[row]->v_attrs[col], VIDEO_ATTR, VAREV);
4405 	    }
4406 	}
4407 	vscreen[row]->v_flag |= VFCHG;
4408 	update_line(row, 0, term.cols);
4409 #else /* OPT_VIDEO_ATTRS */
4410 	if (on) {
4411 	    vp1->v_flag |= VFREQ;
4412 	} else {
4413 	    vp1->v_flag &= ~VFREQ;
4414 	}
4415 	update_line(row, colfrom, colto);
4416 #endif /* OPT_VIDEO_ATTRS */
4417     }
4418 }
4419 
4420 /*
4421  * Send a command to the terminal to move the hardware cursor to row "row"
4422  * and column "col". The row and column arguments are origin 0. Optimize out
4423  * random calls. Update "ttrow" and "ttcol".
4424  */
4425 void
movecursor(int row,int col)4426 movecursor(int row, int col)
4427 {
4428     beginDisplay();
4429     if ((row != ttrow || col != ttcol)
4430 	&& (row >= 0 && row < term.rows)
4431 	&& (col >= 0 && col < term.cols)) {
4432 	ttrow = row;
4433 	ttcol = col;
4434 	term.curmove(row, col);
4435     }
4436     endofDisplay();
4437 }
4438 
4439 void
bottomleft(void)4440 bottomleft(void)
4441 {
4442     movecursor(term.rows - 1, 0);
4443 }
4444 
4445 /*
4446  * Erase the message line. This is a special routine because the message line
4447  * is not considered to be part of the virtual screen. It always works
4448  * immediately; the terminal buffer is flushed via a call to the flusher.
4449  */
4450 void
mlerase(void)4451 mlerase(void)
4452 {
4453     if (!clhide) {
4454 	beginDisplay();
4455 	kbd_erase_to_end(0);
4456 	kbd_flush();
4457 	endofDisplay();
4458     }
4459 }
4460 
4461 int
mlsavec(int c)4462 mlsavec(int c)
4463 {
4464     tb_append(&mlsave, c);
4465     return TRUE;
4466 }
4467 
4468 /*
4469  * Do the real message-line work.  Keep track of the physical cursor position.
4470  * A small class of printf like format items is handled.  Set the "message
4471  * line" flag TRUE.
4472  */
4473 static void
mlmsg(const char * fmt,va_list app2)4474 mlmsg(const char *fmt, va_list app2)
4475 {
4476     static int recur;
4477 
4478     va_list app;
4479     int end_at;
4480 #if DISP_NTWIN
4481     int cursor_state = 0;
4482 #endif
4483     int do_crlf = (strchr(fmt, '\n') != 0
4484 		   || strchr(fmt, '\r') != 0);
4485 
4486     if (no_minimsgs) {
4487 	kbd_alarm();
4488 	return;
4489     }
4490     if (quiet)
4491 	return;
4492 
4493 #if DISP_NTWIN
4494     if (recur == 0) {
4495 	/*
4496 	 * Winvile internally manages its own cursor and this usually works
4497 	 * fine.  However, if the user activates functionality that triggers
4498 	 * windows message(s) (e.g., invoke the Win32 common open dialog) and
4499 	 * if this functionality writes message line status text as a side
4500 	 * effect, then winvile may inadvertently enable its cursor during the
4501 	 * display update.  If this happens, the GDI drops a cursor glyph in
4502 	 * the message line.  Fix this problem by forcing the winvile cursor
4503 	 * off now.
4504 	 */
4505 	cursor_state = winvile_cursor_state(FALSE, FALSE);
4506     }
4507 #endif
4508     if (recur++) {
4509 	/*EMPTY */ ;
4510     } else if (sgarbf) {
4511 	/* then we'll lose the message on the next update(),
4512 	 * so save it now */
4513 	tb_init(&mlsave, EOS);
4514 #if OPT_POPUP_MSGS
4515 	if (global_g_val(GMDPOPUP_MSGS) || !valid_window(curwp)) {
4516 	    TRACE(("mlmsg popup_msgs #1 for '%s'\n", fmt));
4517 	    popup_msgs();
4518 	    msg_putc('\n');
4519 	    dfoutfn = msg_putc;
4520 	} else
4521 #endif
4522 	    dfoutfn = mlsavec;
4523 
4524 	begin_va_copy(app, app2);
4525 	dofmt(fmt, app);
4526 	end_va_copy(app);
4527     } else {
4528 	beginDisplay();
4529 
4530 	kbd_expand = -1;
4531 #if OPT_POPUP_MSGS
4532 	if (global_g_val(GMDPOPUP_MSGS)) {
4533 	    TRACE(("mlmsg popup_msgs #2 for '%s'\n", fmt));
4534 	    popup_msgs();
4535 	    if (tb_length(mlsave) == 0) {
4536 		msg_putc('\n');
4537 		dfoutfn = msg_putc;
4538 	    } else {
4539 		dfoutfn = kbd_putc;
4540 	    }
4541 	} else
4542 #endif
4543 	    dfoutfn = kbd_putc;
4544 
4545 	if (*fmt != '\n') {
4546 
4547 	    kbd_erase_to_end(0);
4548 
4549 	    begin_va_copy(app, app2);
4550 	    dofmt(fmt, app);
4551 	    end_va_copy(app);
4552 
4553 	    kbd_expand = 0;
4554 
4555 	    /* if we can, erase to the end of screen */
4556 	    end_at = wminip->w_dot.o;
4557 	    kbd_erase_to_end(end_at);
4558 	    tb_init(&mlsave, EOS);
4559 	    kbd_flush();
4560 	}
4561 	if (do_crlf) {
4562 	    term.openup();
4563 	}
4564 	endofDisplay();
4565     }
4566     recur--;
4567 #if DISP_NTWIN
4568     if (recur == 0) {
4569 
4570 	/* restore previous cursor state if it was ON. */
4571 
4572 	if (cursor_state)
4573 	    winvile_cursor_state(TRUE, TRUE);
4574     }
4575 #endif
4576 }
4577 
4578 /*
4579  * Format a string onto the message line, but only if it's appropriate.
4580  * Keyboard macro replay, "dot" command replay, command buffer execution,
4581  * "terse" mode, and the user-accessible "discmd" state variable can all
4582  * suppress this.
4583  */
4584 /* VARARGS1 */
4585 void
mlwrite(const char * fmt,...)4586 mlwrite(const char *fmt,...)
4587 {
4588     if (fmt != 0) {
4589 	va_list ap;
4590 	if (global_b_val(MDTERSE) || kbd_replaying(FALSE) || !vl_msgs) {
4591 	    if (!clhide)
4592 		bottomleft();
4593 	    return;
4594 	}
4595 	va_start(ap, fmt);
4596 	mlmsg(fmt, ap);
4597 	va_end(ap);
4598     }
4599 }
4600 
4601 /*
4602  * Unconditionally format a string out onto the message line.
4603  */
4604 /* VARARGS1 */
4605 void
mlforce(const char * fmt,...)4606 mlforce(const char *fmt,...)
4607 {
4608     if (fmt != 0) {
4609 	va_list ap;
4610 	va_start(ap, fmt);
4611 	mlmsg(fmt, ap);
4612 	va_end(ap);
4613     }
4614 }
4615 
4616 /* VARARGS1 */
4617 void
mlprompt(const char * fmt,...)4618 mlprompt(const char *fmt,...)
4619 {
4620     if (fmt != 0) {
4621 	va_list ap;
4622 	int osgarbf = sgarbf;
4623 	if (!vl_msgs) {
4624 	    bottomleft();
4625 	    return;
4626 	}
4627 	sgarbf = FALSE;
4628 	va_start(ap, fmt);
4629 	mlmsg(fmt, ap);
4630 	va_end(ap);
4631 	sgarbf = osgarbf;
4632     }
4633 }
4634 
4635 /* VARARGS */
4636 void
dbgwrite(const char * fmt,...)4637 dbgwrite(const char *fmt,...)
4638 {
4639     if (fmt != 0) {
4640 	char temp[80];
4641 
4642 	va_list ap;		/* ptr to current data field */
4643 	va_start(ap, fmt);
4644 	lsprintf(temp, "[press ^G to continue] %s", fmt);
4645 	mlmsg(temp, ap);
4646 	va_end(ap);
4647 	beginDisplay();
4648 	while (term.getch() != '\007') {
4649 	    ;
4650 	}
4651 	endofDisplay();
4652     }
4653 }
4654 
4655 /*
4656  * Do the equivalent of 'perror()' on the message line
4657  */
4658 void
mlerror(const char * str)4659 mlerror(const char *str)
4660 {
4661     int save_err = errno;
4662     const char *t = 0;
4663 #ifdef HAVE_STRERROR
4664 #if SYS_VMS
4665     if (save_err == EVMSERR)
4666 	t = strerror(save_err, vaxc$errno);
4667     else
4668 #endif /* SYS_VMS */
4669     if (save_err > 0)
4670 	t = strerror(save_err);
4671 #else
4672 #ifdef HAVE_SYS_ERRLIST
4673     if (save_err > 0 && save_err < sys_nerr)
4674 	t = sys_errlist[save_err];
4675 #endif /* HAVE_SYS_ERRLIST */
4676 #endif /* HAVE_STRERROR */
4677     if (t != 0) {
4678 	/* Borland's strerror() returns newlines on the end of the strings */
4679 	static TBUFF *tt;
4680 	if (tb_scopy(&tt, t) != 0) {
4681 	    t = mktrimmed(tb_values(tt));
4682 	}
4683 	mlwarn("[Error %s: %s]", str, t);
4684     } else {
4685 	mlwarn("[Error %s: unknown system error %d]", str, save_err);
4686     }
4687 }
4688 
4689 /*
4690  * Emit a warning message (with alarm)
4691  */
4692 /* VARARGS1 */
4693 void
mlwarn(const char * fmt,...)4694 mlwarn(const char *fmt,...)
4695 {
4696     va_list ap;
4697     va_start(ap, fmt);
4698     mlmsg(fmt, ap);
4699     va_end(ap);
4700     kbd_alarm();
4701 }
4702 
4703 /*
4704  * Local sprintf -- similar to standard libc, but
4705  *  returns pointer to null character at end of buffer, so it can
4706  *  be called repeatedly, as in:
4707  *	cp = lsprintf(cp, fmt, args...);
4708  *
4709  */
4710 
4711 static char *lsp;
4712 
4713 static int
lspputc(int c)4714 lspputc(int c)
4715 {
4716     *lsp++ = (char) c;
4717     return TRUE;
4718 }
4719 
4720 /* VARARGS1 */
4721 char *
lsprintf(char * buf,const char * fmt,...)4722 lsprintf(char *buf, const char *fmt,...)
4723 {
4724     if ((lsp = buf) != 0) {
4725 	va_list ap;
4726 	va_start(ap, fmt);
4727 
4728 	dfoutfn = lspputc;
4729 
4730 	dofmt(fmt, ap);
4731 	va_end(ap);
4732 
4733 	*lsp = EOS;
4734     }
4735     return lsp;
4736 }
4737 
4738 int
format_int(char * buf,UINT number,UINT radix)4739 format_int(char *buf, UINT number, UINT radix)
4740 {
4741     if ((lsp = buf) != 0) {
4742 	dfputi(lspputc, number, radix);
4743 	*lsp = EOS;
4744     }
4745     return (int) (lsp - buf);
4746 }
4747 
4748 /*
4749  * Buffer printf -- like regular printf, but puts characters
4750  *	into the BUFFER.
4751  */
4752 int
bputc(int c)4753 bputc(int c)
4754 {
4755     int status;
4756 
4757     if (is_record_sep(curbp, c)) {
4758 	status = lnewline();
4759     } else {
4760 	status = lins_bytes(1, c);
4761     }
4762 
4763     return status;
4764 }
4765 
4766 int
bputsn(const char * src,int len)4767 bputsn(const char *src, int len)
4768 {
4769     int rc = TRUE;
4770 
4771     if (len < 0) {
4772 	rc = bputsn(src, (int) strlen(src));
4773     } else {
4774 	while ((len > 0) && (rc == TRUE)) {
4775 	    int n;
4776 	    int chunk;
4777 	    int rs = use_record_sep(curbp);
4778 
4779 	    if (*src == rs) {
4780 		rc = lnewline();
4781 		chunk = 1;
4782 	    } else {
4783 		for (chunk = 0; chunk < len; ++chunk) {
4784 		    if (src[chunk] == rs)
4785 			break;
4786 		}
4787 		if ((rc = lins_bytes(chunk, ' ')) == TRUE) {
4788 		    LINE *lp = DOT.l;
4789 		    int offs = DOT.o - chunk;
4790 		    for (n = 0; n < chunk; ++n) {
4791 			lvalue(lp)[n + offs] = src[n];
4792 		    }
4793 		}
4794 	    }
4795 	    len -= chunk;
4796 	    src += chunk;
4797 	}
4798     }
4799     return rc;
4800 }
4801 
4802 #if OPT_EXTRA_COLOR
4803 int
bputsn_attr(const char * src,int len,int attr)4804 bputsn_attr(const char *src, int len, int attr)
4805 {
4806     int rc;
4807 
4808     if (attr) {
4809 	REGIONSHAPE save_regn = regionshape;
4810 	VIDEO_ATTR save_attr = videoattribute;
4811 
4812 	regionshape = rgn_EXACT;
4813 	videoattribute = (VIDEO_ATTR) attr;
4814 
4815 	if (len < 0)
4816 	    len = (int) strlen(src);
4817 	rc = bputsn(src, len);
4818 
4819 	/*
4820 	 * We cannot simply save MK before writing, since writing to an empty
4821 	 * buffer for instance may split the original line, leaving MK pointing
4822 	 * to the fragment _after_ the newline.  But limiting this call to
4823 	 * strings without embedded newlines, we can reliably compute MK based
4824 	 * on the string length.
4825 	 */
4826 	MK = DOT;
4827 	MK.o -= len;
4828 	(void) attributeregion();
4829 
4830 	regionshape = save_regn;
4831 	videoattribute = save_attr;
4832 	MK = DOT;
4833     } else {
4834 	rc = bputsn(src, len);
4835     }
4836     return rc;
4837 }
4838 
4839 int
bputsn_xcolor(const char * src,int len,XCOLOR_CODES code)4840 bputsn_xcolor(const char *src, int len, XCOLOR_CODES code)
4841 {
4842     int rc;
4843     int *valuep = lookup_extra_color(code);
4844 
4845     if (isEmpty(valuep)) {
4846 	rc = bputsn(src, len);
4847     } else {
4848 	rc = bputsn_attr(src, len, *valuep);
4849     }
4850     return rc;
4851 }
4852 #endif /* OPT_EXTRA_COLOR */
4853 
4854 void
bpadc(int c,int count)4855 bpadc(int c, int count)
4856 {
4857     while (count-- > 0)
4858 	bputc(c);
4859 }
4860 
4861 /* printf into curbp, at DOT */
4862 /* VARARGS */
4863 void
bprintf(const char * fmt,...)4864 bprintf(const char *fmt,...)
4865 {
4866     va_list ap;
4867 
4868     dfoutfn = bputc;
4869 
4870     va_start(ap, fmt);
4871     dofmt(fmt, ap);
4872     va_end(ap);
4873 }
4874 
4875 /*
4876  * Print into the given internal buffer, returning a pointer to the line
4877  * written.
4878  */
4879 LINE *
b2vprintf(BUFFER * bp,const char * fmt,va_list app2)4880 b2vprintf(BUFFER *bp, const char *fmt, va_list app2)
4881 {
4882     va_list app;
4883     LINE *result = 0;
4884     WINDOW *save_wp;
4885     BUFFER *save_bp = curbp;
4886     W_TRAITS save_w_traits;
4887     OutFunc save_outfn;
4888 
4889     if ((save_wp = push_fake_win(bp)) != 0) {
4890 	int save_curgoal = curgoal;
4891 
4892 	save_outfn = dfoutfn;
4893 	dfoutfn = bputc;
4894 
4895 	save_w_traits = curwp->w_traits;
4896 
4897 	(void) gotoeob(FALSE, 1);
4898 	(void) gotoeol(FALSE, 1);
4899 
4900 	begin_va_copy(app, app2);
4901 	dofmt(fmt, app);
4902 	end_va_copy(app);
4903 
4904 	result = lback(DOT.l);
4905 
4906 	b_clr_changed(bp);
4907 	curwp->w_traits = save_w_traits;
4908 
4909 	pop_fake_win(save_wp, save_bp);
4910 	dfoutfn = save_outfn;
4911 	curgoal = save_curgoal;
4912     }
4913     return result;
4914 }
4915 
4916 /*
4917  * Write text (not necessarily a whole line) to the given buffer.
4918  */
4919 LINE *
b2printf(BUFFER * bp,const char * fmt,...)4920 b2printf(BUFFER *bp, const char *fmt,...)
4921 {
4922     LINE *result;
4923     va_list ap;
4924 
4925     va_start(ap, fmt);
4926     result = b2vprintf(bp, fmt, ap);
4927     va_end(ap);
4928     return result;
4929 }
4930 
4931 #if OPT_EVAL || OPT_DEBUGMACROS
4932 /* printf into [Trace] */
4933 void
tprintf(const char * fmt,...)4934 tprintf(const char *fmt,...)
4935 {
4936     static int nested;
4937 
4938     BUFFER *bp;
4939     va_list ap;
4940 #if OPT_TRACE
4941     LINE *line;
4942 #endif
4943 
4944     if (!nested
4945 	&& (bp = make_ro_bp(TRACE_BufName, BFINVS)) != 0) {
4946 	nested = TRUE;
4947 
4948 	va_start(ap, fmt);
4949 #if OPT_TRACE
4950 	line =
4951 #endif
4952 	    b2vprintf(bp, fmt, ap);
4953 	va_end(ap);
4954 
4955 #if OPT_TRACE
4956 	if (line)
4957 	    TRACE(("tprintf {%.*s}\n", llength(line), lvalue(line)));
4958 #endif
4959 
4960 	nested = FALSE;
4961     }
4962 }
4963 #endif
4964 
4965 #if defined(SIGWINCH) && ! DISP_X11
4966 /* ARGSUSED */
4967 SIGT
sizesignal(int ACTUAL_SIG_ARGS GCC_UNUSED)4968 sizesignal(int ACTUAL_SIG_ARGS GCC_UNUSED)
4969 {
4970     int w, h;
4971     int old_errno = errno;
4972 
4973     getscreensize(&w, &h);
4974 
4975     if ((h > 1 && h != term.rows) || (w > 1 && w != term.cols))
4976 	newscreensize(h, w);
4977 
4978     setup_handler(SIGWINCH, sizesignal);
4979     errno = old_errno;
4980     SIGRET;
4981 }
4982 #endif
4983 
4984 void
newscreensize(int h,int w)4985 newscreensize(int h, int w)
4986 {
4987     TRACE(("newscreensize(h=%d, w=%d)\n", h, w));
4988     /* do the change later */
4989     if (im_displaying
4990 #if OPT_WORKING
4991 	|| !i_displayed
4992 #endif
4993 	) {
4994 	chg_width = w;
4995 	chg_height = h;
4996     } else {
4997 	beginDisplay();
4998 	chg_width = chg_height = 0;
4999 	if ((h > term.maxrows) || (w > term.maxcols)) {
5000 	    int old_r, old_c;
5001 	    old_r = term.maxrows;
5002 	    old_c = term.maxcols;
5003 	    term.maxrows = h;
5004 	    term.maxcols = w;
5005 	    if (!vtinit()) {	/* allocation failure */
5006 		term.maxrows = old_r;
5007 		term.maxcols = old_c;
5008 		endofDisplay();
5009 		return;
5010 	    }
5011 	}
5012 	endofDisplay();
5013 
5014 	if (newlength(TRUE, h) && newwidth(TRUE, w))
5015 	    (void) update(TRUE);
5016     }
5017 }
5018 
5019 #if OPT_WORKING
5020 /*
5021  * Start the timer that controls the "working..." message.
5022  */
5023 static void
start_working(void)5024 start_working(void)
5025 {
5026     TRACE2(("start_working\n"));
5027     setup_handler(SIGALRM, imworking);
5028     (void) alarm(1);
5029     im_timing = TRUE;
5030 }
5031 
5032 /*
5033  * When we stop the timer, we should cleanup the "working..." message.
5034  */
5035 static void
stop_working(void)5036 stop_working(void)
5037 {
5038     TRACE2(("stop_working\n"));
5039     if (mpresf) {		/* erase leftover working-message */
5040 	int save_row = ttrow;
5041 	int save_col = ttcol;
5042 	kbd_overlay(0);
5043 	kbd_flush();
5044 	movecursor(save_row, save_col);
5045 	term.flush();
5046     }
5047 }
5048 
5049 /*
5050  * Displays alternate
5051  *	"working..." and
5052  *	"...working"
5053  * at the end of the message line if it has been at least a second since
5054  * displaying anything or waiting for keyboard input.  The cur_working and
5055  * max_working values are used in 'slowreadf()' to show the progress of reading
5056  * large files.
5057  */
5058 
5059 /*ARGSUSED*/
5060 SIGT
imworking(int ACTUAL_SIG_ARGS GCC_UNUSED)5061 imworking(int ACTUAL_SIG_ARGS GCC_UNUSED)
5062 {
5063     static int flip;
5064     static int skip;
5065 
5066     TRACE2(("imworking(%d)\n", signo));
5067     /* (if GMDWORKING is _not_ set, or MDTERSE is set, we're allowed to erase,
5068      * but not to write.  If we do erase, we don't reschedule the alarm, since
5069      * setting the mode will call us again to start things up)
5070      */
5071     if (allow_working_msg()) {
5072 	TRACE2(("...allow_working_msg\n"));
5073 	if (im_waiting(-1)) {
5074 	    TRACE2(("...im_waiting(-1)\n"));
5075 	    im_timing = FALSE;
5076 	    stop_working();
5077 	} else if (ShowWorking()) {
5078 	    TRACE2(("...ShowWorking()\n"));
5079 	    if (skip) {
5080 		TRACE2(("...skipped()\n"));
5081 		skip = FALSE;
5082 	    } else {
5083 #if DISP_X11
5084 		x_working();
5085 #else
5086 		static const char *const msg[] =
5087 		{"working", "..."};
5088 		char result[20];
5089 
5090 		TRACE2(("...FINALLY!()\n"));
5091 		result[0] = EOS;
5092 		if (cur_working != 0
5093 		    && cur_working != old_working) {
5094 		    char temp[20];
5095 		    int len = cur_working > 999999L ? 10 : 6;
5096 
5097 		    old_working = cur_working;
5098 		    vl_strncat(result,
5099 			       right_num(temp, len, (long) cur_working),
5100 			       sizeof(result));
5101 		    if (len == 10) {
5102 			/*EMPTY */ ;
5103 		    } else if (max_working != 0) {
5104 			vl_strncat(result, " ", sizeof(result));
5105 			vl_strncat(result,
5106 				   right_num(temp, 2,
5107 					     (long) ((100 * cur_working)
5108 						     / max_working)),
5109 				   sizeof(result));
5110 			vl_strncat(result, "%", sizeof(result));
5111 		    } else {
5112 			vl_strncat(result, " ...", sizeof(result));
5113 		    }
5114 		} else {
5115 		    vl_strncat(result, msg[flip], sizeof(result));
5116 		    vl_strncat(result, msg[!flip], sizeof(result));
5117 		}
5118 		kbd_overlay(result);
5119 		kbd_flush();
5120 #endif
5121 	    }
5122 	    start_working();
5123 	    flip = !flip;
5124 	} else {
5125 	    TRACE2(("...NOT ShowWorking()\n"));
5126 	    stop_working();
5127 	    skip = TRUE;
5128 	}
5129     } else {
5130 	TRACE2(("... NOT allow_working_msg(%d/%d/%d)%s ShowWorking\n",
5131 		vile_is_busy, im_displaying, !i_displayed,
5132 		ShowWorking()? "" : " NOT"));
5133 	if (ShowWorking()) {	/* keep the timer running */
5134 	    start_working();
5135 	    flip = !flip;
5136 	}
5137     }
5138 }
5139 #endif /* OPT_WORKING */
5140 
5141 /*
5142  * Returns true if we should/could show a "working..." message or other busy
5143  * indicator, assuming that 'working' mode is set.
5144  */
5145 int
allow_working_msg(void)5146 allow_working_msg(void)
5147 {
5148     return !(vile_is_busy || im_displaying || !i_displayed);
5149 }
5150 
5151 /*
5152  * Maintain a flag that records whether we're waiting for keyboard input.  As a
5153  * side-effect, restart the 'working' timer if we see that it's been made
5154  * inactive while we were waiting.
5155  */
5156 int
im_waiting(int flag)5157 im_waiting(int flag)
5158 {
5159     static int waiting;
5160     if (flag >= 0) {		/* TRUE or FALSE set, negative used to query */
5161 	waiting = flag;
5162 #if OPT_WORKING
5163 	if (!waiting && !im_timing && ShowWorking())
5164 	    start_working();
5165 #endif
5166     }
5167     return waiting;
5168 }
5169 
5170 #if OPT_PSCREEN
5171 /* Most of the code in this section is for making the message line work
5172  * right...it shouldn't be called to display the rest of the screen.
5173  */
5174 static int psc_row;
5175 static int psc_col;
5176 
5177 #define SWAP_INT(x,y) \
5178 	do { (x) = (x)+(y); (y) = (x)-(y); (x) = (x)-(y); } one_time
5179 #define SWAP_VT_PSC \
5180 	do { SWAP_INT(vtcol, psc_col); SWAP_INT(vtrow, psc_row); } one_time
5181 
5182 OUTC_DCL
psc_putchar(int c)5183 psc_putchar(int c)
5184 {
5185     if (c == '\b') {
5186 	if (psc_col > 0)
5187 	    psc_col--;
5188     } else {
5189 	char temp[2];
5190 
5191 	SWAP_VT_PSC;
5192 
5193 	temp[0] = (char) c;
5194 	vtputc(wminip, temp, 1);
5195 	vscreen[vtrow]->v_flag |= VFCHG;
5196 
5197 	SWAP_VT_PSC;
5198     }
5199     OUTC_RET c;
5200 }
5201 
5202 void
psc_flush(void)5203 psc_flush(void)
5204 {
5205     update_line(term.rows - 1, 0, term.cols);
5206     term.pflush();
5207 }
5208 
5209 void
psc_move(int row,int col)5210 psc_move(int row, int col)
5211 {
5212     psc_row = row;
5213     psc_col = col;
5214 }
5215 
5216 void
psc_eeol(void)5217 psc_eeol(void)
5218 {
5219     if (ttrow >= 0 && ttrow < term.rows && ttcol >= 0) {
5220 	VIDEO_ATTR *vp = &vscreen[ttrow]->v_attrs[ttcol];
5221 	VIDEO_TEXT *cp = &vscreen[ttrow]->v_text[ttcol];
5222 	VIDEO_TEXT *cplim = &vscreen[ttrow]->v_text[term.cols];
5223 	vscreen[ttrow]->v_flag |= VFCHG;
5224 	while (cp < cplim) {
5225 	    *vp++ = 0;
5226 	    *cp++ = ' ';
5227 	}
5228     }
5229 }
5230 
5231 void
psc_eeop(void)5232 psc_eeop(void)
5233 {
5234     int saverow = ttrow;
5235     int savecol = ttcol;
5236     while (ttrow < term.rows) {
5237 	psc_eeol();
5238 	ttrow++;
5239 	ttcol = 0;
5240     }
5241     ttrow = saverow;
5242     ttcol = savecol;
5243 }
5244 
5245 /* ARGSUSED */
5246 void
psc_rev(UINT huh GCC_UNUSED)5247 psc_rev(UINT huh GCC_UNUSED)
5248 {
5249     /* do nothing */
5250 }
5251 
5252 #endif /* OPT_PSCREEN */
5253 
5254 /* For memory-leak testing (only!), releases all display storage. */
5255 #if NO_LEAKS
5256 void
vt_leaks(void)5257 vt_leaks(void)
5258 {
5259     vtfree();
5260 #if OPT_UPBUFF
5261     FreeIfNeeded(recomp_tbl);
5262 #endif
5263 }
5264 #endif
5265