1 /*************************************************************************
2  *  TinyFugue - programmable mud client
3  *  Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
4  *
5  *  TinyFugue (aka "tf") is protected under the terms of the GNU
6  *  General Public License.  See the file "COPYING" for details.
7  ************************************************************************/
8 static const char RCSid[] = "$Id: output.c,v 35004.242 2007/01/14 00:44:19 kkeys Exp $";
9 
10 
11 /*****************************************************************
12  * Fugue output handling
13  *
14  * Written by Ken Keys (Hawkeye).
15  * Handles all screen-related phenomena.
16  *****************************************************************/
17 
18 #define TERM_vt100	1
19 #define TERM_vt220	2
20 #define TERM_ansi	3
21 
22 #include "tfconfig.h"
23 #include "port.h"
24 #include "tf.h"
25 #include "util.h"
26 #include "pattern.h"
27 #include "search.h"
28 #include "tfio.h"
29 #include "socket.h"	/* fgprompt(), fgname() */
30 #include "output.h"
31 #include "attr.h"
32 #include "macro.h"	/* add_ibind(), rebind_key_macros() */
33 #include "tty.h"	/* init_tty(), get_window_wize() */
34 #include "variable.h"
35 #include "expand.h"	/* current_command */
36 #include "parse.h"	/* expr_value_safe() */
37 #include "keyboard.h"	/* keyboard_pos */
38 #include "cmdlist.h"
39 
40 #ifdef EMXANSI
41 # define INCL_VIO
42 # include <os2.h>
43 #endif
44 
45 /* Terminal codes and capabilities.
46  * Visual mode requires at least clear_screen and cursor_address.  The other
47  *   codes are good to have, but are not strictly necessary.
48  * Scrolling in visual mode requires set_scroll_region (preferred), or
49  *   insert_line and delete_line (may appear jumpy), or EMXANSI.
50  * Fast insert in visual mode requires insert_start and insert_end (preferred),
51  *   or insert_char.
52  * Fast delete in visual mode requires delete_char.
53  * Attributes require the appropriate attribute code and attr_off; but if
54  *   attr_off is not defined, underline and standout (replacement for bold)
55  *   can still be used if underline_off and standout_off are defined.
56  */
57 
58 #define DEFAULT_LINES   24
59 #define DEFAULT_COLUMNS 80
60 
61 #if HARDCODE
62 # define origin 1      /* top left corner is (1,1) */
63 # if HARDCODE == TERM_vt100
64 #  define TERMCODE(id, vt100, vt220, ansi)   static const char *(id) = (vt100);
65 # elif HARDCODE == TERM_vt220
66 #  define TERMCODE(id, vt100, vt220, ansi)   static const char *(id) = (vt220);
67 # elif HARDCODE == TERM_ansi
68 #  define TERMCODE(id, vt100, vt220, ansi)   static const char *(id) = (ansi);
69 # endif
70 #else /* !HARDCODE */
71 # define origin 0      /* top left corner is (0,0) */
72 # define TERMCODE(id, vt100, vt220, ansi)   static const char *(id) = NULL;
73 #endif /* HARDCODE */
74 
75 /*				vt100		vt220		ansi */
76 /*				-----		-----		---- */
77 #ifdef __CYGWIN32__  /* "\033[J" is broken in CYGWIN32 b18. */
78 TERMCODE (clear_screen,		NULL,		NULL,		NULL)
79 TERMCODE (clear_to_eos,		NULL,		NULL,		NULL)
80 #else
81 TERMCODE (clear_screen,		"\033[H\033[J", "\033[H\033[J", "\033[H\033[J")
82 TERMCODE (clear_to_eos,		"\033[J",	"\033[J",	"\033[J")
83 #endif
84 TERMCODE (clear_to_eol,		"\033[K",	"\033[K",	"\033[K")
85 TERMCODE (cursor_address,	"\033[%d;%dH",	"\033[%d;%dH",	"\033[%d;%dH")
86 TERMCODE (enter_ca_mode,	NULL,		NULL,		NULL)
87 TERMCODE (exit_ca_mode,		NULL,		NULL,		NULL)
88 TERMCODE (set_scroll_region,	"\033[%d;%dr",	"\033[%d;%dr",	NULL)
89 TERMCODE (insert_line,		NULL,		"\033[L",	"\033[L")
90 TERMCODE (delete_line,		NULL,		"\033[M",	"\033[M")
91 TERMCODE (delete_char,		NULL,		"\033[P",	"\033[P")
92 #ifdef __CYGWIN32__  /* "\033[@" is broken in CYGWIN32 b18. */
93 TERMCODE (insert_char,		NULL,		NULL,		NULL)
94 #else
95 TERMCODE (insert_char,		NULL,		"\033[@",	"\033[@")
96 #endif
97 TERMCODE (insert_start,		NULL,		NULL,		"\033[4h")
98 TERMCODE (insert_end,		NULL,		NULL,		"\033[4l")
99 TERMCODE (keypad_on,		"\033[?1h\033=",NULL,		NULL)
100 TERMCODE (keypad_off,		"\033[?1l\033>",NULL,		NULL)
101 TERMCODE (bell,			"\007",		"\007",		"\007")
102 TERMCODE (underline,		"\033[4m",	"\033[4m",	"\033[4m")
103 TERMCODE (reverse,		"\033[7m",	"\033[7m",	"\033[7m")
104 TERMCODE (flash,		"\033[5m",	"\033[5m",	"\033[5m")
105 TERMCODE (dim,			NULL,		NULL,		NULL)
106 TERMCODE (bold,			"\033[1m",	"\033[1m",	"\033[1m")
107 TERMCODE (attr_off,		"\033[m",	"\033[m",	"\033[m")
108 TERMCODE (attr_on,		NULL,		NULL,		NULL)
109 /* these are only used if others are missing */
110 TERMCODE (underline_off,	NULL,		NULL,		NULL)
111 TERMCODE (standout,		NULL,		NULL,		NULL)
112 TERMCODE (standout_off,		NULL,		NULL,		NULL)
113 
114 /* end HARDCODE section */
115 
116 
117 /* If var==NULL and internal<0, the status is a constant string */
118 typedef struct statusfield {
119     char *name;
120     stat_id_t internal;	/* index of internal status being watched */
121     Var *var;		/* variable being watched */
122     Var *fmtvar;	/* variable containing format expression */
123     Var *attrvar;	/* variable containing attribute string */
124     int width;
125     int rightjust;
126     int column;
127     int row;
128     attr_t attrs;	/* attibutes from status_fields */
129     attr_t vattrs;	/* attibutes from status_attr_{int,var}_<name> */
130 } StatusField;
131 
132 static Var bogusvar;   /* placeholder for StatusField->var */
133 
134 #define true_status_pad (status_pad && *status_pad ? *status_pad : ' ')
135 #define sidescroll \
136     (intvar(VAR_sidescroll) <= Wrap/2 ? intvar(VAR_sidescroll) : Wrap/2)
137 
138 static void  init_term(void);
139 static int   fbufputc(int c);
140 static void  bufflush(void);
141 static void  tbufputs(const char *str);
142 static void  tdirectputs(const char *str);
143 static void  xy(int x, int y);
144 static void  clr(void);
145 static void  clear_line(void);
146 static void  clear_input_line(void);
147 static void  clear_lines(int start, int end);
148 static void  clear_input_window(void);
149 static void  setscroll(int top, int bottom);
150 static void  scroll_input(int n);
151 static void  ictrl_put(const char *s, int n);
152 static int   ioutputs(const char *str, int len);
153 static void  ioutall(int kpos);
154 static void  attributes_off(attr_t attrs);
155 static void  attributes_on(attr_t attrs);
156 static void  color_on(const char *prefix, long color);
157 static void  hwrite(conString *line, int start, int len, int indent);
158 static int   check_more(Screen *screen);
159 static int   next_physline(Screen *screen);
160 static void  output_novisual(PhysLine *pl);
161 #ifdef SCREEN
162 static void  output_noscroll(PhysLine *pl);
163 static void  output_scroll(PhysLine *pl);
164 #endif
165 
166 static void  (*tp)(const char *str);
167 
168 #if TERMCAP
169 #define tpgoto(seq,x,y)  tp(tgoto(seq, (x)-1+origin, (y)-1+origin))
170 #else
171 #define tpgoto(seq,x,y)  Sappendf(outbuf,seq,(y)-1+origin,(x)-1+origin)
172 #endif
173 
174 #define ipos()		xy(ix, iy)
175 
176 /* Buffered output */
177 
178 #define bufputStr(Str)		SStringcat(outbuf, Str)
179 #define bufputs(s)		Stringcat(outbuf, s)
180 #define bufputns(s, n)		Stringncat(outbuf, s, n)
181 #define bufputc(c)		Stringadd(outbuf, c)
182 #define bufputnc(c, n)		Stringnadd(outbuf, c, n)
183 
184 #ifdef EMXANSI /* OS2 */
185    static void crnl(int n);
186 #else
187 # if USE_SGTTY  /* CRMOD is off (tty.c) */
188 #  define crnl(count)  do { bufputc('\r'); bufputnc('\n', count); } while (0)
189 # else             /* ONLCR is on (tty.c) */
190 #  define crnl(count)  bufputnc('\n', count)
191 # endif
192 #endif
193 
194 
195 /* Others */
196 
197 #define Wrap (wrapsize ? wrapsize : columns)
198 #define moremin 1
199 #define morewait 50
200 
201 String status_line[][1] = {	    /* formatted status lines, without alert */
202     STATIC_BUFFER_INIT, STATIC_BUFFER_INIT, STATIC_BUFFER_INIT,
203     STATIC_BUFFER_INIT, STATIC_BUFFER_INIT, STATIC_BUFFER_INIT };
204 #define max_status_height   (sizeof(status_line)/sizeof(String))
205 static StatusField *variable_width_field[max_status_height];
206 
207 STATIC_BUFFER(outbuf);              /* output buffer */
208 static int top_margin = -1, bottom_margin = -1;	/* scroll region */
209 static int cx = -1, cy = -1;        /* Real cursor ((-1,-1)==unknown) */
210 static int ox = 1, oy = 1;          /* Output cursor */
211 static int ix, iy;                  /* Input cursor */
212 static int old_ix = -1;		    /* original ix before output clobbered it */
213 static int in_top, in_bot;	    /* top & bottom line # of input window */
214 #define out_top 1		    /* top line # of output window */
215 static int out_bot;		    /* bottom line # of output window */
216 static int stat_top, stat_bot;	    /* top & bottom line # of status area */
217 static int istarty, iendy, iendx;   /* start/end of current input line */
218 static conString *prompt;           /* current prompt */
219 static attr_t have_attr = 0;        /* available attributes */
220 static int screen_mode = -1;        /* -1=unset, 0=nonvisual, 1=visual */
221 static int output_disabled = 1;     /* is it safe to oflush()? */
222 static int can_have_visual = FALSE;
223 static int can_have_expnonvis = FALSE;
224 static List statusfield_list[max_status_height][1];
225 static int status_left[max_status_height];  /* size of status line left part */
226 static int status_right[max_status_height]; /* size of status line right part */
227 static int alert_row = 0;	    /* row of status area where alert appears */
228 static int alert_pos = 0;	    /* column where alert appears */
229 static int alert_len = 0;	    /* length of current alert message */
230 
231 STATIC_STRING(moreprompt, "--More--", F_BOLD | F_REVERSE);  /* pager prompt */
232 
233 #ifndef EMXANSI
234 # define has_scroll_region (set_scroll_region != NULL)
235 #else
236 # define has_scroll_region (1)
237 #endif
238 
239 
240 #if HARDCODE
241 # if HARDCODE == TERM_vt100
242 #  define KEYCODE(vt100, vt220, ansi)   (vt100)
243 # else
244 #  if HARDCODE == TERM_vt220
245 #   define KEYCODE(vt100, vt220, ansi)   (vt220)
246 #  else
247 #   if HARDCODE == TERM_ansi
248 #    define KEYCODE(vt100, vt220, ansi)   (ansi)
249 #   endif
250 #  endif
251 # endif
252 #else
253 # define KEYCODE(vt100, vt220, ansi)   NULL
254 #endif
255 
256 typedef struct Keycode {
257     const char *name, *capname, *code;
258 } Keycode;
259 
260 static Keycode keycodes[] = {  /* this list is sorted by tolower(name)! */
261 /*                                     vt100       vt220       ansi      */
262 /*                                     -----       -----       ----      */
263 /*  { "Back Tab",       "kB", KEYCODE( NULL,       NULL,       NULL ) }, */
264     { "Backspace",      "kb", KEYCODE( "\010",     "\010",     "\010" ) },
265 /*  { "Clear All Tabs", "ka", KEYCODE( NULL,       NULL,       NULL ) }, */
266     { "Clear EOL",      "kE", KEYCODE( NULL,       NULL,       NULL ) },
267     { "Clear EOS",      "kS", KEYCODE( NULL,       NULL,       NULL ) },
268     { "Clear Screen",   "kC", KEYCODE( NULL,       NULL,       NULL ) },
269 /*  { "Clear Tab",      "kt", KEYCODE( NULL,       NULL,       NULL ) }, */
270     { "Delete",         "kD", KEYCODE( NULL,       "\033[3~",  NULL ) },
271     { "Delete Line",    "kL", KEYCODE( NULL,       NULL,       NULL ) },
272     { "Down",           "kd", KEYCODE( "\033OB",   "\033[B",   "\033[B" ) },
273 /*  { "End Insert",     "kM", KEYCODE( NULL,       NULL,       NULL ) }, */
274     { "F0",             "k0", KEYCODE( "\033Oy",   NULL,       NULL ) },
275     { "F1",             "k1", KEYCODE( "\033OP",   "\033OP",   "\033[M" ) },
276     { "F10",            "k;", KEYCODE( NULL,       NULL,       NULL ) },
277     { "F11",            "F1", KEYCODE( NULL,       NULL,       NULL ) },
278     { "F12",            "F2", KEYCODE( NULL,       NULL,       NULL ) },
279     { "F13",            "F3", KEYCODE( NULL,       NULL,       NULL ) },
280     { "F14",            "F4", KEYCODE( NULL,       NULL,       NULL ) },
281     { "F15",            "F5", KEYCODE( NULL,       NULL,       NULL ) },
282     { "F16",            "F6", KEYCODE( NULL,       NULL,       NULL ) },
283     { "F17",            "F7", KEYCODE( NULL,       NULL,       NULL ) },
284     { "F18",            "F8", KEYCODE( NULL,       NULL,       NULL ) },
285     { "F19",            "F9", KEYCODE( NULL,       NULL,       NULL ) },
286     { "F2",             "k2", KEYCODE( "\033OQ",   "\033OQ",   "\033[N" ) },
287     { "F3",             "k3", KEYCODE( "\033OR",   "\033OR",   "\033[O" ) },
288     { "F4",             "k4", KEYCODE( "\033OS",   "\033OS",   "\033[P" ) },
289     { "F5",             "k5", KEYCODE( "\033Ot",   "\033[17~", "\033[Q" ) },
290     { "F6",             "k6", KEYCODE( "\033Ou",   "\033[18~", "\033[R" ) },
291     { "F7",             "k7", KEYCODE( "\033Ov",   "\033[19~", "\033[S" ) },
292     { "F8",             "k8", KEYCODE( "\033Ol",   "\033[20~", "\033[T" ) },
293     { "F9",             "k9", KEYCODE( "\033Ow",   "\033[21~", "\033[U" ) },
294     { "Home",           "kh", KEYCODE( NULL,       "\033[H",   "\033[H" ) },
295     { "Home Down",      "kH", KEYCODE( NULL,       NULL,       NULL ) },
296     { "Insert",         "kI", KEYCODE( NULL,       "\033[2~",  "\033[L" ) },
297     { "Insert Line",    "kA", KEYCODE( NULL,       NULL,       NULL ) },
298     { "KP1",            "K1", KEYCODE( "\033Oq",   NULL,       NULL ) },
299     { "KP2",            "K2", KEYCODE( "\033Or",   NULL,       NULL ) },
300     { "KP3",            "K3", KEYCODE( "\033Os",   NULL,       NULL ) },
301     { "KP4",            "K4", KEYCODE( "\033Op",   NULL,       NULL ) },
302     { "KP5",            "K5", KEYCODE( "\033Oq",   NULL,       NULL ) },
303     { "Left",           "kl", KEYCODE( "\033OD",   "\033[D",   "\033[D" ) },
304     { "PgDn",           "kN", KEYCODE( NULL,       "\033[6~",  NULL ) },
305     { "PgUp",           "kP", KEYCODE( NULL,       "\033[5~",  NULL ) },
306     { "Right",          "kr", KEYCODE( "\033OC",   "\033[C",   "\033[C" ) },
307     { "Scroll Down",    "kF", KEYCODE( NULL,       NULL,       NULL ) },
308     { "Scroll Up",      "kR", KEYCODE( NULL,       NULL,       NULL ) },
309 /*  { "Set Tab",        "kT", KEYCODE( NULL,       NULL,       NULL ) }, */
310     { "Up",             "ku", KEYCODE( "\033OA",   "\033[A",   "\033[A" ) }
311 };
312 
313 #define N_KEYCODES	(sizeof(keycodes)/sizeof(Keycode))
314 
315 int lines   = DEFAULT_LINES;
316 int columns = DEFAULT_COLUMNS;
317 ref_type_t need_refresh = 0;        /* does input need refresh? */
318 int need_more_refresh = 0;          /* does visual more prompt need refresh? */
319 struct timeval alert_timeout = { 0, 0 };    /* when to clear alert */
320 unsigned long alert_id = 0;
321 struct timeval clock_update ={0,0}; /* when clock needs to be updated */
322 PhysLine *plpool = NULL;	    /* freelist of PhysLines */
323 
324 #if TERMCAP
325 extern int   tgetent(char *buf, const char *name);
326 extern int   tgetnum(const char *id);
327 extern char *tgetstr(const char *id, char **area);
328 extern char *tgoto(const char *code, int destcol, int destline);
329 extern char *tparm(const char *code, ...);
330 extern int   tputs(const char *cp, int affcnt, int (*outc)(int));
331 static int   func_putchar(int c);
332 #endif
333 
334 /****************************
335  * BUFFERED OUTPUT ROUTINES *
336  ****************************/
337 
bufflush(void)338 static void bufflush(void)
339 {
340     int written = 0, result, n;
341     while (written < outbuf->len) {
342 	n = outbuf->len - written;
343 	if (n > 2048)
344 	    n = 2048;
345         result = write(STDOUT_FILENO, outbuf->data + written, n);
346 	if (result < 0) break;
347 	written += result;
348     }
349     Stringtrunc(outbuf, 0);
350 }
351 
fbufputc(int c)352 static int fbufputc(int c)
353 {
354     Stringadd(outbuf, c);
355     return c;
356 }
357 
358 #ifdef EMXANSI
crnl(int n)359 void crnl(int n)
360 {
361     int off = (cy + n) - bottom_margin;
362     if (off < 0 || !visual) off = 0;
363     bufputnc('\n', n - off);
364     if (off) {
365         bufflush();
366         VioScrollUp(top_margin-1, 0, bottom_margin-1, columns, off, " \x07", 0);
367         bufputc('\r');
368     }
369 }
370 #endif
371 
dobell(int n)372 void dobell(int n)
373 {
374     if (beep) {
375         while (n--) bufputs(bell);
376         bufflush();
377     }
378 }
379 
380 /********************/
381 
change_term(Var * var)382 int change_term(Var *var)
383 {
384     fix_screen();
385     init_term();
386     redraw();
387     rebind_key_macros();
388     return 1;
389 }
390 
init_line_numbers(void)391 static void init_line_numbers(void)
392 {
393     /* out_top = 1; */
394     out_bot = lines - isize - status_height;
395     stat_top = out_bot + 1;
396     stat_bot = out_bot + status_height;
397     in_top = stat_bot + 1;
398     in_bot = stat_bot + isize;
399 }
400 
401 /* Initialize output data. */
init_output(void)402 void init_output(void)
403 {
404     const char *str;
405     int i;
406 
407     tp = tbufputs;
408     init_tty();
409 
410     /* Window size: clear defaults, then try:
411      * environment, ioctl TIOCGWINSZ, termcap, defaults.
412      */
413     lines = ((str = getvar("LINES"))) ? atoi(str) : 0;
414     columns = ((str = getvar("COLUMNS"))) ? atoi(str) : 0;
415     if (lines <= 0 || columns <= 0) get_window_size();
416     init_line_numbers();
417     top_margin = 1;
418     bottom_margin = lines;
419 
420     prompt = fgprompt();
421     old_ix = -1;
422 
423     init_term();
424     for (i = 0; i < max_status_height; i++) {
425 	init_list(statusfield_list[i]);
426 	Stringninit(status_line[i], columns);
427 	check_charattrs(status_line[i], columns, 0, __FILE__, __LINE__);
428     }
429 
430     redraw();
431     output_disabled = 0;
432 }
433 
434 /********************
435  * TERMCAP ROUTINES *
436  ********************/
437 
init_term(void)438 static void init_term(void)
439 {
440 #if TERMCAP
441     int i;
442     /* Termcap entries are supposed to fit in 1024 bytes.  But, if a 'tc'
443      * field is present, some termcap libraries will just append the second
444      * entry to the original.  Also, some overzealous versions of tset will
445      * also expand 'tc', so there might be *2* entries appended to the
446      * original.  And, newer termcap databases have additional fields (e.g.,
447      * linux adds 'li' and 'co'), so it could get even longer.  To top it all
448      * off, some termcap libraries don't do any length checking in tgetent().
449      * We should be safe with 4096.
450      */
451     char termcap_entry[4096];
452     /* termcap_buffer will hold at most 1 copy of any field; 1024 is enough. */
453     static char termcap_buffer[1024];
454     char *area = termcap_buffer;
455 
456     have_attr = 0;
457     can_have_visual = FALSE;
458     can_have_expnonvis = FALSE;
459     clear_screen = clear_to_eos = clear_to_eol = NULL;
460     set_scroll_region = insert_line = delete_line = NULL;
461     delete_char = insert_char = insert_start = insert_end = NULL;
462     enter_ca_mode = exit_ca_mode = cursor_address = NULL;
463     keypad_on = keypad_off = NULL;
464     standout = underline = reverse = flash = dim = bold = bell = NULL;
465     standout_off = underline_off = attr_off = attr_on = NULL;
466 
467     {
468 	/* Sanity check:  a valid termcap entry should end in ':'.  In
469 	 * particular, csh and tcsh put a limit of 1023 bytes on the TERMCAP
470 	 * variable, which may cause libpcap to give us garbage.  (I really
471 	 * hate it when people blame tf for a bug in another program,
472 	 * especially when it's the evil csh or tcsh.)
473 	 */
474 	const char *str, *shell;
475 	int len, is_csh;
476 	if ((str = getvar("TERMCAP")) && (len = strlen(str)) > 0) {
477 	    is_csh = ((shell = getenv("SHELL")) && smatch("*csh", shell) == 0);
478 	    if (str[len-1] != ':') {
479 		wprintf("unsetting invalid TERMCAP variable%s.",
480 		    is_csh ?  ", which appears to have been corrupted by your "
481 		    "broken *csh shell" : "");
482 		unsetvar(ffindglobalvar("TERMCAP"));
483 	    } else if (len == 1023 && (!getenv("TF_FORCE_TERMCAP")) && is_csh) {
484 		wprintf("unsetting the TERMCAP environment variable because "
485 		    "it looks like it has been truncated by your broken *csh "
486 		    "shell.  To force TF to use TERMCAP, restart TF with the "
487 		    "TF_FORCE_TERMCAP environment variable set.");
488 		unsetvar(ffindglobalvar("TERMCAP"));
489 	    }
490 	}
491     }
492 
493     if (!TERM || !*TERM) {
494         wprintf("TERM undefined.");
495     } else if (tgetent(termcap_entry, TERM) <= 0) {
496         wprintf("\"%s\" terminal unsupported.", TERM);
497     } else {
498         if (columns <= 0) columns = tgetnum("co");
499         if (lines   <= 0) lines   = tgetnum("li");
500 
501         clear_screen         = tgetstr("cl", &area);
502         clear_to_eol         = tgetstr("ce", &area);
503         clear_to_eos         = tgetstr("cd", &area);
504         enter_ca_mode        = tgetstr("ti", &area);
505         exit_ca_mode         = tgetstr("te", &area);
506         cursor_address       = tgetstr("cm", &area);
507         set_scroll_region    = tgetstr("cs", &area);
508         delete_char          = tgetstr("dc", &area);
509         insert_char          = tgetstr("ic", &area);
510         insert_start         = tgetstr("im", &area);
511         insert_end           = tgetstr("ei", &area);
512         insert_line          = tgetstr("al", &area);
513         delete_line          = tgetstr("dl", &area);
514         keypad_on            = tgetstr("ks", &area);
515         keypad_off           = tgetstr("ke", &area);
516 
517         bell		= tgetstr("bl", &area);
518         underline	= tgetstr("us", &area);
519         reverse		= tgetstr("mr", &area);
520         flash		= tgetstr("mb", &area);
521         dim		= tgetstr("mh", &area);
522         bold		= tgetstr("md", &area);
523         standout	= tgetstr("so", &area);
524         underline_off	= tgetstr("ue", &area);
525         standout_off	= tgetstr("se", &area);
526         attr_off	= tgetstr("me", &area);
527         attr_on		= tgetstr("sa", &area);
528 
529         if (!attr_off) {
530             /* can't exit all attrs, but maybe can exit underline/standout */
531             reverse = flash = dim = bold = NULL;
532             if (!underline_off) underline = NULL;
533             if (!standout_off) standout = NULL;
534         }
535 
536         for (i = 0; i < N_KEYCODES; i++) {
537             keycodes[i].code = tgetstr(keycodes[i].capname, &area);
538 #if 0
539             fprintf(stderr, "(%2s) %-12s = %s\n",
540                 keycodes[i].capname, keycodes[i].name,
541                 keycodes[i].code ? ascii_to_print(keycodes[i].code)->data : "NULL");
542 #endif
543         }
544 
545 	if (!keypad_off) keypad_on = NULL;
546 
547         if (strcmp(TERM, "xterm") == 0) {
548 #if 0	    /* Now that tf has virtual screens, the secondary buffer is ok. */
549             enter_ca_mode = exit_ca_mode = NULL; /* Avoid secondary buffer. */
550 #endif
551             /* Many old "xterm" termcaps mistakenly omit "cs". */
552             if (!set_scroll_region)
553                 set_scroll_region = "\033[%i%d;%dr";
554         }
555         if (strcmp(TERM, "linux") == 0) {
556             /* Many "linux" termcaps mistakenly omit "ks" and "ke". */
557             if (!keypad_on && !keypad_off) {
558 		keypad_on = "\033[?1h\033=";
559 		keypad_off = "\033[?1l\033>";
560 	    }
561         }
562     }
563 
564     if (!bell) bell = "\007";
565 
566 #ifdef EMXANSI
567     VioSetAnsi(1,0);                   /* ensure ansi-mode */
568 #endif /* EMXANSI */
569 
570 #endif /* TERMCAP */
571 
572 #if 1
573     /* The insert_start code in iput() is apparently buggy.  Until it is
574      * fixed, we ignore the insert_start capability. */
575     insert_start = NULL;
576 #endif
577     if (!insert_end) insert_start = NULL;
578 
579     if (columns <= 0) columns = DEFAULT_COLUMNS;
580     if (lines   <= 0) lines   = DEFAULT_LINES;
581     set_var_by_id(VAR_wrapsize, columns - 1);
582     ix = 1;
583     can_have_visual = (clear_screen || clear_to_eol) && cursor_address;
584     can_have_expnonvis = delete_char && cursor_address;
585     set_var_by_id(VAR_scroll, has_scroll_region||(insert_line&&delete_line));
586     set_var_by_id(VAR_keypad, !!keypad_on);
587     have_attr = F_BELL;
588     if (underline) have_attr |= F_UNDERLINE;
589     if (reverse)   have_attr |= F_REVERSE;
590     if (flash)     have_attr |= F_FLASH;
591     if (dim)       have_attr |= F_DIM;
592     if (bold)      have_attr |= F_BOLD;
593     if (standout)  have_attr |= F_BOLD;
594 }
595 
setscroll(int top,int bottom)596 static void setscroll(int top, int bottom)
597 {
598     if (top_margin == top && bottom_margin == bottom) return; /* optimization */
599 #ifdef EMXANSI
600     bufflush();
601 #else
602     if (!set_scroll_region) return;
603     tpgoto(set_scroll_region, (bottom), (top));
604     cx = cy = -1;   /* cursor position is undefined */
605 #endif
606     bottom_margin = bottom;
607     top_margin = top;
608     if (top != 1 || bottom != lines) set_refresh_pending(REF_PHYSICAL);
609 }
610 
xy(int x,int y)611 static void xy(int x, int y)
612 {
613     if (x == cx && y == cy) return;                    /* already there */
614     if (cy < 0 || cx < 0 || cx > columns) {            /* direct movement */
615         tpgoto(cursor_address, x, y);
616     } else if (x == 1 && y == cy) {                    /* optimization */
617         bufputc('\r');
618     } else if (x == 1 && y > cy && y < cy + 5 &&       /* optimization... */
619         cy >= top_margin && y <= bottom_margin)        /* if '\n' is safe */
620     {
621         /* Some broken emulators (including CYGWIN32 b18) lose
622          * attributes when \r\n is printed, so we print \n\r instead.
623          */
624         bufputnc('\n', y - cy);
625         if (cx != 1) bufputc('\r');
626     } else if (y == cy && x < cx && x > cx - 7) {      /* optimization */
627         bufputnc('\010', cx - x);
628     } else {                                           /* direct movement */
629         tpgoto(cursor_address, x, y);
630     }
631     cx = x;  cy = y;
632 }
633 
clr(void)634 static void clr(void)
635 {
636     if (clear_screen)
637         tp(clear_screen);
638     else {
639         clear_lines(1, lines);
640         xy(1, 1);
641     }
642     cx = 1;  cy = 1;
643 }
644 
clear_line(void)645 static void clear_line(void)
646 {
647     if (cx != 1) bufputc('\r');
648     cx = 1;
649     if (clear_to_eol) {
650         tp(clear_to_eol);
651     } else {
652         bufputnc(' ', Wrap);
653         bufputc('\r');
654     }
655 }
656 
657 #if TERMCAP
658 /* in case broken lib defines a putchar macro but not a putchar function */
659 /* Note: WATCOM compiler (QNX) has a function named fputchar. */
func_putchar(int c)660 static int func_putchar(int c)
661 {
662     return putchar(c);
663 }
664 #endif
665 
tdirectputs(const char * str)666 static void tdirectputs(const char *str)
667 {
668     if (str)
669 #if TERMCAP
670         tputs(str, 1, func_putchar);
671 #else
672         puts(str);
673 #endif
674 }
675 
tbufputs(const char * str)676 static void tbufputs(const char *str)
677 {
678     if (str)
679 #if TERMCAP
680         tputs(str, 1, fbufputc);
681 #else
682         bufputs(str);
683 #endif
684 }
685 
get_keycode(const char * name)686 const char *get_keycode(const char *name)
687 {
688     Keycode *keycode;
689 
690     keycode = (Keycode *)bsearch(name, keycodes, N_KEYCODES,
691         sizeof(Keycode), cstrstructcmp);
692     return !keycode ? NULL : keycode->code ? keycode->code : "";
693 }
694 
695 /*******************
696  * WINDOW HANDLING *
697  *******************/
698 
setup_screen(void)699 void setup_screen(void)
700 {
701     setscroll(1, lines);
702     output_disabled++;
703 
704     if (visual && (!can_have_visual)) {
705         eprintf("Visual mode is not supported on this terminal.");
706         set_var_by_id(VAR_visual, 0);  /* XXX recursion problem?? */
707     } else if (visual && lines < 1/*input*/ + 1/*status*/ + 2/*output*/) {
708         eprintf("Screen is too small for visual mode.");
709         set_var_by_id(VAR_visual, 0);  /* XXX recursion problem?? */
710     }
711     screen_mode = visual;
712 
713     if (!visual) {
714         prompt = display_screen->paused ? moreprompt : fgprompt();
715 	old_ix = -1;
716 
717 #ifdef SCREEN
718     } else {
719         prompt = fgprompt();
720 	if (isize + status_height + 2 > lines) {
721 	    if ((1 + status_height + 2) <= lines) {
722 		set_var_by_id(VAR_isize, lines - (status_height + 2));
723 	    } else {
724 		set_var_by_id(VAR_isize, 1);
725 		set_var_by_id(VAR_stat_height, lines - (isize + 2));
726 	    }
727 	}
728 	init_line_numbers();
729 #if 0
730         outcount = out_bot - out_top + 1;
731 #endif
732         if (enter_ca_mode) tp(enter_ca_mode);
733 
734         if (scroll && !(has_scroll_region || (insert_line && delete_line))) {
735             set_var_by_id(VAR_scroll, 0);
736         }
737         update_status_line(NULL);
738         ix = iendx = oy = 1;
739         iy = iendy = istarty = in_top;
740         ipos();
741 #endif
742     }
743 
744     if (keypad && keypad_on)
745 	tp(keypad_on);
746 
747     set_refresh_pending(REF_LOGICAL);
748     output_disabled--;
749 }
750 
751 #define interesting(pl)  ((pl)->attrs & F_HWRITE || (pl)->charattrs)
752 
753 /* returns true if str passes filter */
screen_filter(Screen * screen,conString * str)754 static int screen_filter(Screen *screen, conString *str)
755 {
756     if (str == textdiv_str) return visual;
757     return (!screen->selflush || interesting(str))
758 	&&
759 	(!screen->filter_enabled ||
760 	(screen->filter_attr ? interesting(str) :
761 	patmatch(&screen->filter_pat, str, NULL) == screen->filter_sense));
762 }
763 
764 #define purge_old_lines(screen) \
765     do { \
766 	if (screen->nlline > screen->maxlline) \
767 	    f_purge_old_lines(screen); \
768     } while (0) \
769 
f_purge_old_lines(Screen * screen)770 static void f_purge_old_lines(Screen *screen)
771 {
772     ListEntry *node;
773     PhysLine *pl;
774 
775     /* Free old lines if over maximum and they're off the top of the screen.
776      * (Freeing them when they fall out of history doesn't work: a long-lived
777      * line from a different history could trap lines from the history
778      * corresponding to this screen.) */
779     node = screen->pline.head;
780     while (screen->nlline > screen->maxlline && node != screen->top) {
781 	node = node->next;
782 	pl = node->datum;
783 	if (pl->start == 0) { /* beginning of a new lline */
784 	    /* free all plines (corresponding to a single lline) above node */
785 	    screen->nlline--;
786 	    while (screen->pline.head != node) {
787 		pl = unlist(screen->pline.head, &screen->pline);
788 		conStringfree(pl->str);
789 		pfree(pl, plpool, str);
790 	    }
791 	}
792     }
793 }
794 
795 /* Add a line to the bottom.  bot does not move if nothing below it matches
796  * filter. */
nextbot(Screen * screen)797 static int nextbot(Screen *screen)
798 {
799     PhysLine *pl;
800     ListEntry *bot;
801     int nback = screen->nback;
802     int passed_maxbot;
803 
804     if (screen->bot) {
805 	passed_maxbot = (screen->bot == screen->maxbot);
806 	bot = screen->bot->next;
807     } else {
808 	bot = screen->pline.head;
809 	passed_maxbot = 1;
810     }
811     while (bot) {
812 	pl = bot->datum;
813 	nback--;
814 	/* shouldn't need to recalculate visible, but there's a bug somewhere
815 	 * in maintaining visible flags in (bot,tail] after /unlimit. */
816 	if ((pl->visible = screen_filter(screen, pl->str))) {
817 	    screen->bot = bot;
818 	    screen->nback_filtered--;
819 	    screen->nback = nback;
820 	    if (passed_maxbot) {
821 		screen->maxbot = bot;
822 		screen->nnew_filtered--;
823 		screen->nnew = nback;
824 	    }
825 	    if (!screen->top)
826 		screen->top = screen->bot;
827 	    screen->viewsize++;
828 	    if (screen->viewsize >= winlines())
829 		screen->partialview = 0;
830 	    return 1;
831 	}
832 	if (bot == screen->maxbot)
833 	    passed_maxbot = 1;
834 	bot = bot->next;
835     }
836     return 0;
837 }
838 
839 /* Add a line to the top.  top does not move if nothing above it matches
840  * filter. */
prevtop(Screen * screen)841 static int prevtop(Screen *screen)
842 {
843     PhysLine *pl;
844     ListEntry *prev;
845 
846     /* (top == NULL && bot != NULL) happens after clear_display_screen() */
847     if (!screen->bot) return 0;
848     prev = screen->top ? screen->top->prev : screen->bot;
849     while (prev) {
850 	pl = prev->datum;
851 	/* visible flags are not maintained above top, must recalculate */
852 	if ((pl->visible = screen_filter(screen, pl->str))) {
853 	    screen->top = prev;
854 	    screen->viewsize++;
855 	    if (screen->viewsize >= winlines())
856 		screen->partialview = 0;
857 	    return 1;
858 	}
859 	/* XXX optimize for pl's that share same ll */
860 	prev = prev->prev;
861     }
862     return 0;
863 }
864 
865 /* Remove a line from the bottom.  bot always moves. */
prevbot(Screen * screen)866 static int prevbot(Screen *screen)
867 {
868     ListEntry *node;
869     PhysLine *pl;
870     int viewsize_changed = 0;
871 
872     if (!screen->bot) return 0;
873     while (screen->bot->prev) {
874 	pl = (node = screen->bot)->datum;
875 	/* visible flags are maintained in [top,bot], we needn't recalculate */
876 	if (pl->visible) {
877 	    /* line being knocked off was visible */
878 	    screen->viewsize--;
879 	    screen->nback_filtered += !pl->tmp;
880 	    viewsize_changed = 1;
881 	}
882 	screen->bot = screen->bot->prev;
883 	screen->nback += !pl->tmp;
884 
885 	if (pl->tmp) {
886 	    /* line being knocked off was temporary */
887 	    if (screen->maxbot == node)
888 		screen->maxbot = screen->maxbot->prev;
889 	    unlist(node, &screen->pline);
890 	    conStringfree(pl->str);
891 	    pfree(pl, plpool, str);
892 	}
893 
894 	/* stop if the viewsize has changed and we've found a new visible bot */
895 	pl = screen->bot->datum;
896 	if (viewsize_changed && pl->visible)
897 	    break;
898     }
899     return viewsize_changed;
900 }
901 
902 /* Remove a line from the top.  top always moves. */
nexttop(Screen * screen)903 static int nexttop(Screen *screen)
904 {
905     PhysLine *pl;
906     ListEntry *newtop;
907     int viewsize_changed = 0;
908 
909     if (!screen->top) return 0;
910     while (screen->top->next) {
911 	pl = screen->top->datum;
912 	newtop = screen->top->next;
913 	/* visible flags are maintained in [top,bot], we needn't recalculate */
914 	if (pl->visible) {
915 	    /* line being knocked off was visible */
916 	    screen->viewsize--;
917 	    viewsize_changed = 1;
918 	}
919 	if (pl->tmp) {
920 	    /* line being knocked off was temporary */
921 	    unlist(screen->top, &screen->pline);
922 	    conStringfree(pl->str);
923 	    pfree(pl, plpool, str);
924 	}
925 	screen->top = newtop;
926 
927 	/* stop if the viewsize has changed and we've found a new visible top */
928 	pl = screen->top->datum;
929 	if (viewsize_changed && pl->visible)
930 	    break;
931     }
932 
933     purge_old_lines(screen);
934     return viewsize_changed;
935 }
936 
937 /* recalculate counters and visible flags in (bot, tail] */
screen_refilter_bottom(Screen * screen)938 static void screen_refilter_bottom(Screen *screen)
939 {
940     PhysLine *pl;
941     ListEntry *node;
942 
943     if (screen_has_filter(screen)) {
944 	screen->nback_filtered = 0;
945 	screen->nnew_filtered = 0;
946 	node = screen->pline.tail;
947 	for ( ; node != screen->maxbot; node = node->prev) {
948 	    pl = node->datum;
949 	    if ((pl->visible = screen_filter(screen, pl->str)))
950 		screen->nnew_filtered++;
951 	}
952 	screen->nback_filtered = screen->nnew_filtered;
953 	for ( ; node != screen->bot; node = node->prev) {
954 	    pl = node->datum;
955 	    if ((pl->visible = screen_filter(screen, pl->str)))
956 		screen->nback_filtered++;
957 	}
958     } else {
959 	screen->nback_filtered = screen->nback;
960 	screen->nnew_filtered = screen->nnew;
961     }
962 }
963 
screen_refilter(Screen * screen)964 static int screen_refilter(Screen *screen)
965 {
966     PhysLine *pl;
967     int want;
968     Screen oldscreen;
969     ListEntry *lowestvisible;
970 
971     screen->needs_refilter = 0;
972     if (!screen->bot)
973 	if (!(screen->bot = screen->pline.tail))
974 	    return 0;
975     oldscreen = *screen;
976     /* NB: screen->bot should stay in the same place, for when the filter
977      * is removed or changed. */
978     lowestvisible = screen->bot;
979     while (!screen_filter(screen, (pl = lowestvisible->datum)->str)) {
980 	pl->visible = 0;
981 	lowestvisible = lowestvisible->prev;
982 	if (!lowestvisible) {
983 	    if (screen_has_filter(screen)) {
984 		*screen = oldscreen; /* restore original state */
985 		clear_screen_filter(screen);
986 		screen_refilter(screen);
987 	    }
988 	    return 0;
989 	}
990     }
991     (pl = lowestvisible->datum)->visible = 1;
992     /* recalculate top: start at bot and move top up until view is full */
993     screen->viewsize = 1;
994     screen->partialview = 0;
995     screen->top = lowestvisible;
996     want = winlines();
997     while ((screen->viewsize < want) && prevtop(screen))
998 	/* empty loop */;
999 
1000     screen_refilter_bottom(screen);
1001 
1002     return screen->viewsize;
1003 }
1004 
screen_has_filter(Screen * screen)1005 int screen_has_filter(Screen *screen)
1006 {
1007     return screen->filter_enabled || screen->selflush;
1008 }
1009 
clear_screen_filter(Screen * screen)1010 void clear_screen_filter(Screen *screen)
1011 {
1012     screen->filter_enabled = 0;
1013     screen->selflush = 0;
1014     screen->needs_refilter = 1;
1015 }
1016 
set_screen_filter(Screen * screen,Pattern * pat,attr_t attr_flag,int sense)1017 void set_screen_filter(Screen *screen, Pattern *pat, attr_t attr_flag,
1018     int sense)
1019 {
1020     if (screen->filter_pat.str)
1021 	free_pattern(&screen->filter_pat);
1022     if (pat) screen->filter_pat = *pat;
1023     screen->filter_attr = attr_flag;
1024     screen->filter_sense = sense;
1025     screen->filter_enabled = 1;
1026     screen->selflush = 0;
1027     screen->needs_refilter = 1;
1028 }
1029 
enable_screen_filter(Screen * screen)1030 int enable_screen_filter(Screen *screen)
1031 {
1032     if (!screen->filter_pat.str && !screen->filter_attr)
1033 	return 0;
1034     screen->filter_enabled = 1;
1035     screen->selflush = 0;
1036     screen->needs_refilter = 1;
1037     return 1;
1038 }
1039 
winlines(void)1040 int winlines(void)
1041 {
1042     return visual ? out_bot - out_top + 1 : lines - 1;
1043 }
1044 
1045 /* wraplines
1046  * Split logical line <ll> into physical lines and append them to <plines>.
1047  */
wraplines(conString * ll,List * plines,int visible)1048 static int wraplines(conString *ll, List *plines, int visible)
1049 {
1050     PhysLine *pl;
1051     int offset = 0, n = 0;
1052 
1053     do {
1054 	palloc(pl, PhysLine, plpool, str, __FILE__, __LINE__);
1055 	pl->visible = visible;
1056 	pl->tmp = 0;
1057 	(pl->str = ll)->links++;
1058 	pl->start = offset;
1059 	pl->indent = wrapflag && pl->start && wrapspace < Wrap ? wrapspace : 0;
1060 	pl->len = wraplen(ll->data + offset, ll->len - offset, pl->indent);
1061 	offset += pl->len;
1062 	inlist(pl, plines, plines->tail);
1063 	n++;
1064     } while (offset < ll->len);
1065     return n;
1066 }
1067 
rewrap(Screen * screen)1068 static void rewrap(Screen *screen)
1069 {
1070     PhysLine *pl;
1071     ListEntry *node;
1072     List old_pline;
1073     int wrapped, old_bot_visible, old_maxbot_visible;
1074 
1075     if (!screen->bot) return;
1076     hide_screen(screen); /* delete temp lines */
1077 
1078     old_pline.head = screen->pline.head;
1079     old_pline.tail = screen->pline.tail;
1080     screen->pline.head = screen->pline.tail = NULL;
1081     screen->nnew = screen->nnew_filtered = 0;
1082     screen->nback = screen->nback_filtered = 0;
1083 
1084     pl = screen->bot->datum;
1085     old_bot_visible = pl->start + pl->len;
1086     pl = screen->maxbot->datum;
1087     old_maxbot_visible = pl->start + pl->len;
1088 
1089     /* XXX possible optimization: don't rewrap [head, top) until needed.
1090      * [top, bot] is needed for display, and (bot, tail] is needed for
1091      * nback and nnew. */
1092 
1093     /* rewrap llines corresponding to [head, bot] */
1094     do {
1095 	node = old_pline.head;
1096 	pl = unlist(old_pline.head, &old_pline);
1097 	if (pl->start == 0) {
1098 	    wrapped = wraplines(pl->str, &screen->pline, pl->visible);
1099 	    screen->npline += wrapped;
1100 	}
1101 	conStringfree(pl->str);
1102 	pfree(pl, plpool, str);
1103     } while (node != screen->bot);
1104 
1105     /* recalculate bot within last lline */
1106     screen->bot = screen->pline.tail;
1107     while (screen->bot->prev) {
1108 	pl = screen->bot->datum;
1109 	if (pl->start == 0 || pl->start < old_bot_visible) break;
1110 	screen->bot = screen->bot->prev;
1111 	screen->nback++;
1112     }
1113 
1114     /* rewrap llines corresponding to (bot, maxbot] */
1115     while (node != screen->maxbot) {
1116 	node = old_pline.head;
1117 	pl = unlist(old_pline.head, &old_pline);
1118 	if (pl->start == 0) {
1119 	    wrapped = wraplines(pl->str, &screen->pline, pl->visible);
1120 	    screen->npline += wrapped;
1121 	    screen->nback += wrapped;
1122 	}
1123 	conStringfree(pl->str);
1124 	pfree(pl, plpool, str);
1125     }
1126 
1127     /* recalculate maxbot within last lline */
1128     screen->maxbot = screen->pline.tail;
1129     while (screen->maxbot->prev) {
1130 	pl = screen->maxbot->datum;
1131 	if (pl->start == 0 || pl->start < old_maxbot_visible) break;
1132 	screen->maxbot = screen->maxbot->prev;
1133 	screen->nnew++;
1134     }
1135 
1136     /* rewrap llines corresponding to (maxbot, tail] */
1137     while (old_pline.head) {
1138 	pl = unlist(old_pline.head, &old_pline);
1139 	if (pl->start == 0) {
1140 	    wrapped = wraplines(pl->str, &screen->pline, pl->visible);
1141 	    screen->npline += wrapped;
1142 	    screen->nback += wrapped;
1143 	    screen->nnew += wrapped;
1144 	}
1145 	conStringfree(pl->str);
1146 	pfree(pl, plpool, str);
1147     }
1148 
1149     /* recalculate nback_filtered, nnew_filtered, viewsize, top */
1150     /* XXX TODO: should honor screen->partialview */
1151     screen_refilter(screen);
1152 
1153     screen->scr_wrapflag = wrapflag;
1154     screen->scr_wrapsize = Wrap;
1155     screen->scr_wrapspace = wrapspace;
1156     screen->scr_wrappunct = wrappunct;
1157 }
1158 
redraw_window(Screen * screen,int already_clear)1159 int redraw_window(Screen *screen, int already_clear)
1160 {
1161     if (screen->needs_refilter && !screen_refilter(screen))
1162 	return 0;
1163 
1164     if (!already_clear)
1165 	clear_lines(1, visual ? out_bot : lines);
1166 
1167     if (screen->scr_wrapflag != wrapflag ||
1168 	screen->scr_wrapsize != Wrap ||
1169 	screen->scr_wrapspace != wrapspace ||
1170 	screen->scr_wrappunct != wrappunct)
1171     {
1172 	rewrap(screen);
1173     } else {
1174 	/* if terminal height decreased or textdiv was inserted */
1175 	while (screen->viewsize > winlines())
1176 	    nexttop(screen);
1177 	/* if terminal height increased */
1178 	if (!screen->partialview) {
1179 	    while (screen->viewsize < winlines())
1180 		if (!prevtop(screen)) break;
1181 	}
1182     }
1183 
1184     if (!visual) xy(1, lines);
1185 
1186     /* viewsize may be 0 after clear_display_screen(), even if bot != NULL
1187      * or nplines > 0 */
1188     if (screen->viewsize) {
1189         PhysLine *pl;
1190 	ListEntry *node;
1191 	int first;
1192 
1193         if (visual) xy(1, out_bot - (screen->viewsize - 1));
1194 
1195 	node = screen->top;
1196 	first = 1;
1197 	while (1) {
1198 	    pl = node->datum;
1199 	    if (pl->visible) {
1200 		if (!first) crnl(1);
1201 		first = 0;
1202 		hwrite(pl->str, pl->start,
1203 		    pl->len < Wrap - pl->indent ? pl->len : Wrap - pl->indent,
1204 		    pl->indent);
1205 	    }
1206 	    if (node == screen->bot)
1207 		break;
1208 	    node = node->next;
1209         }
1210 	if (visual) {
1211 	    cx = 1; cy = out_bot;
1212 	} else {
1213 	    crnl(1);
1214 	    cx = 1; cy = lines;
1215 	}
1216     }
1217 
1218     bufflush();
1219     old_ix = -1;
1220     set_refresh_pending(REF_PHYSICAL);
1221     ox = cx;
1222     oy = cy;
1223 
1224     return 1;
1225 }
1226 
redraw(void)1227 int redraw(void)
1228 {
1229     alert_timeout = tvzero;
1230     alert_pos = 0;
1231     alert_len = 0;
1232     if (visual) {
1233 	top_margin = bottom_margin = -1;  /* force scroll region reset */
1234         clr();
1235     } else {
1236         bufputnc('\n', lines);     /* scroll region impossible */
1237     }
1238     setup_screen();
1239 
1240     if (virtscreen) {
1241 	hide_screen(display_screen);
1242 	unhide_screen(display_screen);
1243     }
1244     return redraw_window(display_screen, 1);
1245 }
1246 
clear_screen_view(Screen * screen)1247 static void clear_screen_view(Screen *screen)
1248 {
1249     if (screen->bot) {
1250 	screen->top = screen->bot->next;
1251 	screen->viewsize = 0;
1252 	screen->partialview = 1;
1253 	reset_outcount(screen);
1254     }
1255 }
1256 
clear_display_screen(void)1257 int clear_display_screen(void)
1258 {
1259     if (!display_screen->bot) return 0;
1260     clear_screen_view(display_screen);
1261     return redraw_window(display_screen, 0);
1262 }
1263 
parse_statusfield(StatusField * field,const char * s)1264 static const char *parse_statusfield(StatusField *field, const char *s)
1265 {
1266     const char *t;
1267 
1268     memset(field, 0, sizeof(*field));
1269     field->internal = -1;
1270 
1271     if (is_quote(*s)) {                                /* string literal */
1272 	STATIC_BUFFER(buffer);
1273 	if (!stringliteral(buffer, &s)) {
1274 	    eprintf("%S", buffer);
1275 	    return NULL;
1276 	}
1277 	field->name = STRDUP(buffer->data);
1278     } else if (*s == '@') {                            /* internal */
1279 	for (t = ++s; is_alnum(*s) || *s == '_'; s++);
1280 	field->name = strncpy(XMALLOC(s - t + 1), t, s - t);
1281 	field->name[s-t] = '\0';
1282 	field->internal = enum2int(field->name, 0, enum_status,
1283 	    "status_fields internal status");
1284 	FREE(field->name);
1285 	field->name = NULL;
1286 	if (field->internal < 0)
1287 	    return NULL;
1288     } else if (is_alnum(*s) || *s == '_') {            /* variable */
1289 	for (t = s++; is_alnum(*s) || *s == '_'; s++);
1290 	field->name = strncpy(XMALLOC(s - t + 1), t, s - t);
1291 	field->name[s-t] = '\0';
1292 	field->var = &bogusvar;
1293     } else {                                           /* blank */
1294 	field->name = NULL;
1295     }
1296 
1297     if (*s == ':') {
1298 	while (*++s == '-')
1299 	    field->rightjust = !field->rightjust;
1300 	field->width = strtoint(s, &s);
1301     }
1302 
1303     if (*s == ':') {
1304 	for (t = s + 1; *s && !is_space(*s); s++);
1305 	if (!parse_attrs(t, &field->attrs, ' '))
1306 	    return NULL;
1307     }
1308 
1309     if (*s && !is_space(*s)) {
1310 	eprintf("garbage in status field: %.8s", s);
1311 	return NULL;
1312     }
1313     return s;
1314 }
1315 
status_var(const char * type,const char * suffix)1316 static Var *status_var(const char *type, const char *suffix)
1317 {
1318     STATIC_BUFFER(name);
1319     Stringcat(Stringcat(Stringcpy(name, "status_"), type), suffix);
1320     return findorcreateglobalvar(name->data);
1321 }
1322 
status_field_text(int row)1323 conString *status_field_text(int row)
1324 {
1325     ListEntry *node;
1326     StatusField *f;
1327     String *buf = Stringnew(NULL, columns, 0);
1328     int width;
1329 
1330     if (row < 0 || row >= max_status_height)
1331 	return CS(buf);
1332     for (node = statusfield_list[row]->head; node; node = node->next) {
1333 	if (node != statusfield_list[row]->head)
1334 	    Stringadd(buf, ' ');
1335 	f = (StatusField*)node->datum;
1336 	width = f->width;
1337 	if (f->internal >= 0) {
1338 	    Stringadd(buf, '@');
1339 	    SStringcat(buf, &enum_status[f->internal]);
1340 	} else if (f->var) {
1341 	    Stringcat(buf, f->var->val.name);
1342 	} else if (f->name) {
1343 	    Sappendf(buf, "\"%q\"", '"', f->name);
1344 	    if (width == strlen(f->name)) width = 0;
1345 	}
1346 	if (!width && !f->attrs && !f->rightjust)
1347 	    continue;
1348 	Stringadd(buf, ':');
1349 	if (width || f->rightjust)
1350 	    Sappendf(buf, "%s%d", f->rightjust ? "-" : "", width);
1351 	if (!f->attrs)
1352 	    continue;
1353 	Stringadd(buf, ':');
1354 	attr2str(buf, f->attrs);
1355     }
1356     return CS(buf);
1357 }
1358 
regen_status_fields(int row)1359 static void regen_status_fields(int row)
1360 {
1361     ListEntry *node;
1362     StatusField *f;
1363     int column;
1364 
1365     /* finish calculations on status_fields_list */
1366     for (node = statusfield_list[row]->head; node; node = node->next) {
1367         f = (StatusField*)node->datum;
1368         if (f->var) { /* f->var == &bogusvar */
1369 	    if (f->var == &bogusvar) {
1370 		f->var = findorcreateglobalvar(f->name);
1371 		FREE(f->name);
1372 		f->name = NULL;
1373 	    }
1374             f->var->statuses++;
1375 	    f->fmtvar = status_var("var_", f->var->val.name);
1376 	    f->attrvar = status_var("attr_var_", f->var->val.name);
1377 	} else if (f->internal >= 0) {
1378 	    f->fmtvar = status_var("int_", enum_status[f->internal].data);
1379 	    f->attrvar = status_var("attr_int_", enum_status[f->internal].data);
1380 	} else {
1381 	    continue;
1382 	}
1383 	f->fmtvar->statusfmts++;
1384 	f->attrvar->statusattrs++;
1385 	if (ch_attr(f->attrvar))
1386 	    f->vattrs = f->attrvar->val.u.attr;
1387     }
1388 
1389     clock_update.tv_sec = 0;
1390     variable_width_field[row] = NULL;
1391     status_left[row] = status_right[row] = 0;
1392     column = 0;
1393     for (node = statusfield_list[row]->head; node; node = node->next) {
1394         f = (StatusField*)node->datum;
1395         status_left[row] = f->column = column;
1396         if (f->width == 0) {
1397 	    variable_width_field[row] = f;
1398 	    break;
1399 	}
1400         column += f->width;
1401     }
1402     column = 0;
1403     if (node) {
1404         for (node = statusfield_list[row]->tail; node; node = node->prev) {
1405             f = (StatusField*)node->datum;
1406             if (f->width == 0) break;
1407             status_right[row] = -(f->column = (column -= f->width));
1408         }
1409     }
1410 
1411     if (row == 0) {
1412 	/* regenerate the value of %status_fields for backward compatibility */
1413 	conString *buf = status_field_text(row);
1414 	/* set_str_var_direct avoids error checking and recursion */
1415 	set_str_var_direct(&special_var[VAR_stat_fields], TYPE_STR, buf);
1416     }
1417 
1418     update_status_line(NULL);
1419 }
1420 
find_statusfield_by_template(StatusField * target,int full_comparison,const char * label)1421 static ListEntry *find_statusfield_by_template(StatusField *target,
1422     int full_comparison, const char *label)
1423 {
1424     ListEntry *node;
1425     StatusField *f;
1426     int row;
1427 
1428     for (row = 0; row < status_height; row++) {
1429 	if (target->row >= 0 && row != target->row) continue;
1430 	for (node = statusfield_list[row]->head; node; node = node->next) {
1431 	    f = (StatusField*)node->datum;
1432 	    if (target->internal >= 0) {
1433 		if (target->internal != f->internal) continue;
1434 	    } else if (target->var) {
1435 		if (!f->var || strcmp(target->name, f->var->val.name) != 0)
1436 		    continue;
1437 	    } else if (target->name) {
1438 		if (!f->name || cstrcmp(target->name, f->name) != 0)
1439 		    continue;
1440 	    }
1441 	    if (full_comparison) {
1442 		if (target->width && target->width != f->width) continue;
1443 		if (target->attrs && target->attrs != f->attrs) continue;
1444 	    }
1445 	    return node;
1446 	}
1447     }
1448     if (label) {
1449 	eprintf(
1450 	    target->row<0 ? "%s: no such field" : "%s: no such field in row %d",
1451 	    label, target->row);
1452     }
1453     return NULL;
1454 }
1455 
find_statusfield_by_name(int row,const char * spec)1456 static ListEntry *find_statusfield_by_name(int row, const char *spec)
1457 {
1458     ListEntry *result = NULL;
1459     StatusField target;
1460     if (parse_statusfield(&target, spec)) {
1461 	target.row = row;
1462 	result = find_statusfield_by_template(&target, TRUE, spec);
1463     }
1464     if (target.name) FREE(target.name);
1465     return result;
1466 }
1467 
ch_status_int(Var * var)1468 int ch_status_int(Var *var)
1469 {
1470     if (warn_status)
1471 	wprintf("the default value of %s has "
1472 	    "changed between tf version 4 and 5.", var->val.name);
1473     return 1;
1474 }
1475 
free_statusfield(StatusField * field)1476 static void free_statusfield(StatusField *field)
1477 {
1478     if (field->var && field->var != &bogusvar && !--field->var->statuses)
1479 	freevar(field->var);
1480     if (field->fmtvar && !--field->fmtvar->statuses)
1481 	freevar(field->fmtvar);
1482     if (field->attrvar && !--field->attrvar->statuses)
1483 	freevar(field->attrvar);
1484     if (field->name)
1485 	FREE(field->name);
1486     FREE(field);
1487 }
1488 
status_add(int reset,int nodup,int row,ListEntry * where,const char * s,long spacer)1489 static int status_add(int reset, int nodup, int row, ListEntry *where,
1490     const char *s,
1491     long spacer) /* spacer>0 goes after new field, spacer<0 goes before */
1492 {
1493     int totwidth = 0, vwf_found = 0;
1494     List newlist[1];
1495     StatusField *field;
1496 
1497     init_list(newlist);
1498 
1499     if (!reset) {
1500 	vwf_found = !!variable_width_field[row];
1501 	totwidth = status_left[row] + status_right[row];
1502     }
1503 
1504     /* validate and insert new fields */
1505     while (1) {
1506         while(is_space(*s)) s++;
1507         if (!*s) break;
1508         field = XMALLOC(sizeof(*field));
1509 	if (!(s = parse_statusfield(field, s)))
1510             goto status_add_error;
1511 	field->row = row;
1512 
1513         if (!field->width && !field->var && field->internal < 0 && field->name)
1514             field->width = strlen(field->name);
1515 
1516 	if (nodup && find_statusfield_by_template(field, FALSE, NULL)) {
1517 	    free_statusfield(field);
1518 	    continue;
1519 	}
1520 
1521         if (field->width != 0) {
1522             totwidth += field->width;
1523 	} else {
1524 	    if (vwf_found) {
1525 		eprintf("Only one variable width status field is allowed.");
1526 		goto status_add_error;
1527 	    }
1528 	    vwf_found = 1;
1529 	}
1530 
1531 	inlist(field, newlist, newlist->tail);
1532     }
1533 
1534     if (spacer && newlist->head && !reset && statusfield_list[row]->head) {
1535         field = XCALLOC(sizeof(*field));
1536         field->internal = -1;
1537 	field->row = row;
1538 	totwidth += (field->width = spacer < 0 ? -spacer : spacer);
1539 	inlist(field, newlist, spacer >= 0 ? NULL : newlist->tail);
1540     }
1541 
1542     if (totwidth > columns) {
1543         wprintf("total status width (%d) is wider than screen (%d)",
1544 	    totwidth, columns);
1545     }
1546 
1547     if (reset) {
1548 	/* delete old fields and clean up referents */
1549 	while (statusfield_list[row]->head)
1550 	    free_statusfield(unlist(statusfield_list[row]->head,
1551 		statusfield_list[row]));
1552 	*statusfield_list[row] = *newlist;
1553     } else {
1554 	/* insert new fields into list */
1555 	/* (We could be faster with ptr swapping, but this isn't critical) */
1556 	while (newlist->head) {
1557 	    field = (StatusField*)unlist(newlist->head, newlist);
1558 	    where = inlist(field, statusfield_list[row], where);
1559 	}
1560     }
1561 
1562     regen_status_fields(row);
1563     return 1;
1564 
1565 status_add_error:
1566     free_statusfield(field);
1567     while (newlist->head)
1568 	free_statusfield(unlist(newlist->head, newlist));
1569     return 0;
1570 }
1571 
handle_status_add_command(String * args,int offset)1572 struct Value *handle_status_add_command(String *args, int offset)
1573 {
1574     int opt, nodup = FALSE, row = -1, reset = 0;
1575     ValueUnion uval;
1576     long spacer = 1;
1577     const char *ptr, *before = NULL, *after = NULL;
1578     ListEntry *where;
1579 
1580     startopt(CS(args), "A:B:s#r#xc");
1581     while ((opt = nextopt(&ptr, &uval, NULL, &offset))) {
1582 	switch (opt) {
1583 	case 'A':  after = STRDUP(ptr);   before = NULL;  break;
1584 	case 'B':  before = STRDUP(ptr);  after = NULL;   break;
1585 	case 's':  spacer = uval.ival;  break;
1586 	case 'r':  row = uval.ival;     break;
1587 	case 'x':  nodup = TRUE;  break;
1588 	case 'c':  reset = 1;     break;
1589         default:   return shareval(val_zero);
1590 	}
1591     }
1592 
1593     if (after) {
1594 	if (*after) {
1595 	    where = find_statusfield_by_name(row, after);
1596 	    if (!where) return shareval(val_zero);
1597 	    row = ((StatusField*)(where->datum))->row;
1598 	} else {
1599 	    if (row < 0) row = 0;
1600 	    where = statusfield_list[row]->tail;
1601 	}
1602 	FREE(after);
1603     } else if (before) {
1604 	spacer = -spacer;
1605 	if (*before) {
1606 	    where = find_statusfield_by_name(row, before);
1607 	    if (!where) return shareval(val_zero);
1608 	    row = ((StatusField*)(where->datum))->row;
1609 	    where = where->prev;
1610 	} else {
1611 	    if (row < 0) row = 0;
1612 	    where = NULL;
1613 	}
1614 	FREE(before);
1615     } else {
1616 	if (row < 0) row = 0;
1617 	where = statusfield_list[row]->tail;
1618     }
1619 
1620     return shareval(
1621 	status_add(reset, nodup, row, where, args->data + offset, spacer) ?
1622 	val_one : val_zero);
1623 }
1624 
ch_status_fields(Var * var)1625 int ch_status_fields(Var *var)
1626 {
1627     if (warn_status) {
1628 	wprintf("setting status_fields directly is deprecated, "
1629 	    "and may clobber useful new features introduced in version 5.  "
1630 	    "The recommended way to change "
1631 	    "status fields is with /status_add, /status_rm, or /status_edit. "
1632 	    "(This warning can be disabled with \"/set warn_status=off\".)");
1633     }
1634     return status_add(TRUE, FALSE, 0, NULL,
1635 	status_fields ? status_fields : "", 0);
1636 }
1637 
getspacer(ListEntry * node)1638 static int getspacer(ListEntry *node)
1639 {
1640     StatusField *f;
1641     if (!node) return 0;
1642     f = node->datum;
1643     return (f->var || f->internal >= 0 || f->name) ? 0 : f->width;
1644 }
1645 
handle_status_rm_command(String * args,int offset)1646 struct Value *handle_status_rm_command(String *args, int offset)
1647 {
1648     ValueUnion uval;
1649     long row = -1;
1650     int opt;
1651     ListEntry *node;
1652 
1653     startopt(CS(args), "r#");
1654     while ((opt = nextopt(NULL, &uval, NULL, &offset))) {
1655 	switch (opt) {
1656 	case 'r':  row = uval.ival; break;
1657         default:   return shareval(val_zero);
1658 	}
1659     }
1660 
1661     node = find_statusfield_by_name(row, args->data + offset);
1662     if (!node) return shareval(val_zero);
1663     row = ((StatusField*)(node->datum))->row;
1664     if (variable_width_field[row] == node->datum)
1665 	variable_width_field[row] = NULL;
1666     if (!node->prev && getspacer(node->next)) {
1667 	unlist(node->next, statusfield_list[row]); /* remove leading spacer */
1668     } else if (!node->next && getspacer(node->prev)) {
1669 	unlist(node->prev, statusfield_list[row]); /* remove trailing spacer */
1670     } else if (getspacer(node->prev) && getspacer(node->next)) {
1671 	/* remove smaller of two neighboring spacers */
1672 	if (getspacer(node->prev) < getspacer(node->next))
1673 	    unlist(node->prev, statusfield_list[row]);
1674 	else
1675 	    unlist(node->next, statusfield_list[row]);
1676     }
1677     unlist(node, statusfield_list[row]);
1678     regen_status_fields(row);
1679     return shareval(val_one);
1680 }
1681 
handle_status_edit_command(String * args,int offset)1682 struct Value *handle_status_edit_command(String *args, int offset)
1683 {
1684     ValueUnion uval;
1685     long row = -1;
1686     int opt;
1687     StatusField *field;
1688     ListEntry *node;
1689 
1690     startopt(CS(args), "r#");
1691     while ((opt = nextopt(NULL, &uval, NULL, &offset))) {
1692 	switch (opt) {
1693 	case 'r':  row = uval.ival; break;
1694         default:   return shareval(val_zero);
1695 	}
1696     }
1697 
1698     field = XMALLOC(sizeof(*field));
1699     if (!parse_statusfield(field, args->data + offset)) {
1700 	free_statusfield(field);
1701 	return shareval(val_zero);
1702     }
1703     field->row = row;
1704     node = find_statusfield_by_template(field, FALSE, args->data + offset);
1705     if (!node) {
1706 	free_statusfield(field);
1707 	return shareval(val_zero);
1708     }
1709     row = field->row = ((StatusField*)(node->datum))->row;
1710     if (!field->width && variable_width_field[row] &&
1711 	node->datum != variable_width_field[row])
1712     {
1713 	eprintf("Only one variable width status field per row is allowed.");
1714 	free_statusfield(field);
1715 	return shareval(val_zero);
1716     }
1717     free_statusfield(node->datum);
1718     node->datum = field;
1719     regen_status_fields(field->row);
1720     return shareval(val_one);
1721 }
1722 
1723 /* returns column of field, or -1 if field is obscured by alert */
statusfield_column(StatusField * field)1724 static inline int statusfield_column(StatusField *field)
1725 {
1726     int column;
1727     if (field->column >= 0)
1728 	return (field->column > columns) ? columns : field->column;
1729     column = field->column +
1730 	((status_left[field->row] + status_right[field->row] > columns) ?
1731 	status_left[field->row] + status_right[field->row] : columns);
1732     return (column > columns) ? columns : column;
1733 }
1734 
status_width(StatusField * field,int start)1735 static int status_width(StatusField *field, int start)
1736 {
1737     int width;
1738     if (start >= columns) return 0;
1739     width = (field->width == 0) ?
1740 	columns - status_right[field->row] - status_left[field->row] :
1741 	(field->width > 0) ? field->width : -field->width;
1742     if (width > columns - start)
1743         width = columns - start;
1744     if (width < 0)
1745 	width = 0;
1746     return width;
1747 }
1748 
handle_status_width_func(const char * name)1749 int handle_status_width_func(const char *name)
1750 {
1751     /* XXX need to be able to specify row */
1752     StatusField *field;
1753     field = (StatusField*)find_statusfield_by_name(-1, name)->datum;
1754     return field ? status_width(field, statusfield_column(field)) : 0;
1755 }
1756 
format_statusfield(StatusField * field)1757 static int format_statusfield(StatusField *field)
1758 {
1759     STATIC_BUFFER(scratch);
1760     const char *old_command;
1761     Value *fmtval, *val = NULL;
1762     Program *prog;
1763     int width, x, i;
1764 
1765     output_disabled++;
1766     Stringtrunc(scratch, 0);
1767     if (field->internal >= 0 || field->var) {
1768         fmtval = getvarval(field->fmtvar);
1769         old_command = current_command;
1770         if (fmtval) {
1771             current_command = fmtval->name;
1772 	    if (fmtval->type & TYPE_EXPR) {
1773 		prog = fmtval->u.prog;
1774 	    } else if (fmtval->type == TYPE_STR) {
1775 		prog = compile_tf(valstr(fmtval), 0, -1, 1, 2);
1776 		if ((fmtval->u.prog = prog))
1777 		    fmtval->type |= TYPE_EXPR;
1778 	    } else {
1779 		prog = compile_tf(valstr(fmtval), 0, -1, 1, 0);
1780 	    }
1781 	    if (prog) {
1782 		val = expr_value_safe(prog);
1783 		if (!(fmtval->type & TYPE_EXPR))
1784 		    prog_free(prog);
1785 	    }
1786 	    if (val) {
1787 		SStringcpy(scratch, valstr(val));
1788 		freeval(val);
1789 	    } else {
1790 		SStringcpy(scratch, blankline);
1791 	    }
1792         } else if (field->var) {
1793             current_command = field->var->val.name;
1794             val = getvarval(field->var);
1795             SStringcpy(scratch, val ? valstr(val) : blankline);
1796         }
1797         current_command = old_command;
1798     } else if (field->name) {   /* string literal */
1799         Stringcpy(scratch, field->name);
1800     }
1801 
1802     x = statusfield_column(field);
1803     width = status_width(field, x);
1804     if (scratch->len > width)
1805         Stringtrunc(scratch, width);
1806 
1807     if (field->rightjust && scratch->len < width) {          /* left pad */
1808 	for (i = 0; i < width - scratch->len; i++, x++) {
1809 	    status_line[field->row]->data[x] = true_status_pad;
1810 	    status_line[field->row]->charattrs[x] = status_attr;
1811 	}
1812     }
1813 
1814     if (scratch->len) {                                      /* value */
1815         attr_t attrs = scratch->attrs;
1816         attrs = adj_attr(attrs, status_attr);
1817         attrs = adj_attr(attrs, field->attrs);
1818         attrs = adj_attr(attrs, field->vattrs);
1819 	for (i = 0; i < scratch->len; i++, x++) {
1820 	    status_line[field->row]->data[x] = scratch->data[i];
1821 	    status_line[field->row]->charattrs[x] = scratch->charattrs ?
1822 		adj_attr(attrs, scratch->charattrs[i]) : attrs;
1823 	}
1824     }
1825 
1826     if (!field->rightjust && scratch->len < width) {         /* right pad */
1827 	for (i = 0; i < width - scratch->len; i++, x++) {
1828 	    status_line[field->row]->data[x] = true_status_pad;
1829 	    status_line[field->row]->charattrs[x] = status_attr;
1830 	}
1831     }
1832 
1833     if (field->internal == STAT_MORE)
1834         need_more_refresh = 0;
1835     if (field->internal == STAT_CLOCK) {
1836         struct tm *local;
1837 	time_t sec = time(NULL);
1838         /* note: localtime()->tm_sec won't compile if prototype is missing */
1839         local = localtime(&sec);
1840         clock_update.tv_sec = sec + 60 - local->tm_sec;
1841     }
1842 
1843 #if 0
1844     if (field->var && !field->var->status)   /* var was unset */
1845         field->var = NULL;
1846 #endif
1847     output_disabled--;
1848     return width;
1849 }
1850 
display_status_segment(int row,int start,int width)1851 static void display_status_segment(int row, int start, int width)
1852 {
1853     if (!alert_len || row != alert_row ||
1854 	start + width <= alert_pos || start >= alert_pos + alert_len)
1855     {
1856 	/* no overlap with alert */
1857 	xy(start + 1, stat_top + row);
1858 	hwrite(CS(status_line[row]), start, width, 0);
1859     } else {
1860 	if (start < alert_pos) {
1861 	    /* segment starts left of alert */
1862 	    xy(start + 1, stat_top + row);
1863 	    hwrite(CS(status_line[row]), start, alert_pos - start, 0);
1864 	}
1865 	if (start + width >= alert_pos) {
1866 	    /* segment ends right of alert */
1867 	    xy(alert_pos + alert_len + 1, stat_top + row);
1868 	    hwrite(CS(status_line[row]), alert_pos + alert_len,
1869 		start + width - (alert_pos + alert_len), 0);
1870 	}
1871     }
1872 }
1873 
update_status_field(Var * var,stat_id_t internal)1874 void update_status_field(Var *var, stat_id_t internal)
1875 {
1876     ListEntry *node;
1877     StatusField *field;
1878     int row, column, width;
1879     int count = 0;
1880 
1881     if (screen_mode < 1) return;
1882 
1883     if (var && var->statusattrs) {
1884 	if (!ch_attr(var))
1885 	    return; /* error */
1886     }
1887 
1888     for (row = 0; row < status_height; row++) {
1889 	for (node = statusfield_list[row]->head; node; node = node->next) {
1890 	    field = (StatusField*)node->datum;
1891 	    if (var) {
1892 		if (field->var == var)
1893 		    /* do nothing */;
1894 		else if (field->fmtvar == var)
1895 		    /* do nothing */;
1896 		else if (field->attrvar == var)
1897 		    field->vattrs = var->val.u.attr;
1898 		else
1899 		    continue;
1900 	    }
1901 	    if (internal >= 0 && field->internal != internal) continue;
1902 	    column = statusfield_column(field);
1903 	    if (column >= columns) /* doesn't fit, nor will any later fields */
1904 		break;
1905 	    count++;
1906 	    width = format_statusfield(field);
1907 	    display_status_segment(row, column, width);
1908 	}
1909     }
1910 
1911     if (count) {
1912 	bufflush();
1913 	set_refresh_pending(REF_PHYSICAL);
1914     }
1915 }
1916 
format_status_line(void)1917 void format_status_line(void)
1918 {
1919     ListEntry *node;
1920     StatusField *field;
1921     int row, column, width;
1922 
1923     for (row = 0; row < status_height; row++) {
1924 	column = 0;
1925 	width = 0;
1926 	for (node = statusfield_list[row]->head; node; node = node->next) {
1927 	    field = (StatusField*)node->datum;
1928 
1929 	    if ((column = statusfield_column(field)) >= columns)
1930 		break;
1931 	    width = format_statusfield(field);
1932 	}
1933 
1934 	for (column += width; column < columns; column++) {
1935 	    status_line[row]->data[column] = true_status_pad;
1936 	    status_line[row]->charattrs[column] = status_attr;
1937 	}
1938     }
1939 }
1940 
display_status_line(void)1941 int display_status_line(void)
1942 {
1943     int row;
1944 
1945     if (screen_mode < 1) return 0;
1946 
1947     for (row = 0; row < status_height; row++) {
1948 	display_status_segment(row, 0, columns);
1949     }
1950 
1951     bufflush();
1952     set_refresh_pending(REF_PHYSICAL);
1953     return 1;
1954 }
1955 
update_status_line(Var * var)1956 int update_status_line(Var *var)
1957 {
1958     /* XXX optimization:  some code that calls update_status_line() without
1959      * any change in status_line could call display_status_line() directly,
1960      * avoiding reformatting (in particular, status_{int,var}_* execution). */
1961     format_status_line();
1962     display_status_line();
1963     return 1; /* for variable func */
1964 }
1965 
alert(conString * msg)1966 void alert(conString *msg)
1967 {
1968     int row = 0;
1969     int new_pos, new_len;
1970     ListEntry *node;
1971     StatusField *field;
1972     attr_t orig_attrs;
1973 
1974     if (msg->attrs & F_GAG && gag)
1975 	return;
1976     alert_id++;
1977     msg->links++; /* some callers pass msg with links==0, so we ++ and free */
1978     if (!visual) {
1979 	tfputline(msg, tferr);
1980     } else {
1981 	/* default to position 0 */
1982 	new_pos = 0;
1983 	new_len = msg->len > Wrap ? Wrap : msg->len;
1984 	if (msg->len < Wrap) {
1985 	    /* if there's a field after @world, and msg fits there, use it */
1986 	    for (node = statusfield_list[row]->head; node; node = node->next) {
1987 		field = (StatusField*)node->datum;
1988 		if (field->internal == STAT_WORLD && node->next) {
1989 		    field = (StatusField*)node->next->datum;
1990 		    break;
1991 		}
1992 	    }
1993 	    if (node) {
1994 		new_pos = (field->column < 0 ? columns : 0) + field->column;
1995 		if (new_pos + new_len > Wrap)
1996 		    new_pos = 0;
1997 	    }
1998 	}
1999 
2000 	if (alert_len &&
2001 	    (alert_pos < new_pos || alert_pos + alert_len > new_pos + new_len))
2002 	{
2003 	    /* part of old alert would still be visible under new alert */
2004 	    /* XXX this could be optimized */
2005 	    clear_alert();
2006 	}
2007 
2008 	alert_len = new_len;
2009 	alert_pos = new_pos;
2010 
2011 	gettime(&alert_timeout);
2012 	tvadd(&alert_timeout, &alert_timeout, &alert_time);
2013 
2014 	xy(alert_pos + 1, stat_top + alert_row);
2015 	orig_attrs = msg->attrs;
2016 	msg->attrs = adj_attr(msg->attrs, alert_attr);
2017 	hwrite(msg, 0, alert_len, 0);
2018 	msg->attrs = orig_attrs;
2019 
2020 	bufflush();
2021 	set_refresh_pending(REF_PHYSICAL);
2022     }
2023     conStringfree(msg);
2024 }
2025 
clear_alert(void)2026 void clear_alert(void)
2027 {
2028     if (!alert_len) return;
2029     xy(alert_pos + 1, stat_top + alert_row);
2030     hwrite(CS(status_line[alert_row]), alert_pos, alert_len, 0);
2031     bufflush();
2032     set_refresh_pending(REF_PHYSICAL);
2033     alert_timeout = tvzero;
2034     alert_pos = 0;
2035     alert_len = 0;
2036     alert_id = 0;
2037 }
2038 
2039 /* used by %{visual}, %{isize}, SIGWINCH */
ch_visual(Var * var)2040 int ch_visual(Var *var)
2041 {
2042     int need_redraw = 0, resized = 0, row;
2043 
2044     for (row = 0; row < status_height; row++) {
2045 	if (status_line[row]->len < columns)
2046 	    Stringnadd(status_line[row], '?', columns - status_line[row]->len);
2047 	Stringtrunc(status_line[row], columns);
2048     }
2049 
2050     if (screen_mode < 0) {                /* e.g., called by init_variables() */
2051         return 1;
2052     } else if (var == &special_var[VAR_visual]) {      /* %visual changed */
2053         need_redraw = resized = 1;
2054 	alert_timeout = tvzero;
2055 	alert_pos = 0;
2056 	alert_len = 0;
2057     } else if (var == &special_var[VAR_isize]) {      /* %isize changed */
2058         need_redraw = resized = visual;
2059 #ifdef SCREEN
2060     } else {                              /* SIGWINCH */
2061         need_redraw = 1;
2062 	resized = 1;
2063         cx = cy = -1;  /* unknown */
2064         top_margin = bottom_margin = -1;  /* unknown */
2065 #endif
2066     }
2067 
2068     if (need_redraw)
2069         redraw();
2070     if (resized)
2071         transmit_window_size();
2072     return 1;
2073 }
2074 
ch_status_height(Var * var)2075 int ch_status_height(Var *var)
2076 {
2077     if (status_height > max_status_height) {
2078 	eprintf("%s must be <= %d", var->val.name, max_status_height);
2079 	return 0;
2080     }
2081     if (visual) redraw();
2082     transmit_window_size();
2083     return 1;
2084 }
2085 
ch_expnonvis(Var * var)2086 int ch_expnonvis(Var *var)
2087 {
2088     if (!can_have_expnonvis && expnonvis) {
2089         eprintf("expnonvis mode is not supported on this terminal.");
2090 	return 0;
2091     }
2092     if (!visual) {
2093 	old_ix = -1;
2094 	redraw();
2095     }
2096     return 1;
2097 }
2098 
2099 /* used by %{wrap}, %{wrappunct}, %{wrapsize}, %{wrapspace} */
ch_wrap(Var * var)2100 int ch_wrap(Var *var)
2101 {
2102     if (screen_mode < 0)	/* e.g., called by init_variables() */
2103 	return 1;
2104 
2105     redraw_window(display_screen, 0);
2106     transmit_window_size();
2107     return 1;
2108 }
2109 
fix_screen(void)2110 void fix_screen(void)
2111 {
2112     oflush();
2113     if (keypad && keypad_off)
2114 	tp(keypad_off);
2115     if (screen_mode <= 0) {
2116         clear_line();
2117 #ifdef SCREEN
2118     } else {
2119 	top_margin = bottom_margin = -1;  /* force scroll region reset */
2120         setscroll(1, lines);
2121         clear_lines(stat_top, lines);
2122         xy(1, stat_top);
2123         if (exit_ca_mode) tp(exit_ca_mode);
2124 #endif
2125     }
2126     cx = cy = -1;
2127     bufflush();
2128     screen_mode = -1;
2129 }
2130 
2131 /* minimal_fix_screen() avoids use of possibly corrupted structures. */
minimal_fix_screen(void)2132 void minimal_fix_screen(void)
2133 {
2134     tp = tdirectputs;
2135     fg_screen = default_screen;
2136     default_screen->pline.head = default_screen->pline.tail = NULL;
2137     default_screen->top = default_screen->bot = NULL;
2138     outbuf->data = NULL;
2139     outbuf->len = 0;
2140     outbuf->size = 0;
2141     output_disabled++;
2142     fix_screen();
2143     fflush(stdout);
2144 }
2145 
clear_lines(int start,int end)2146 static void clear_lines(int start, int end)
2147 {
2148     if (start > end) return;
2149     xy(1, start);
2150     if (end >= lines && clear_to_eos) {
2151         tp(clear_to_eos);  /* cx,cy were set by xy() */
2152     } else {
2153         clear_line();
2154         while (start++ < end) {
2155              bufputc('\n');
2156              clear_line();
2157         }
2158         cy = end;
2159     }
2160 }
2161 
2162 /* clear entire input window */
clear_input_window(void)2163 static void clear_input_window(void)
2164 {
2165     /* only called in visual mode */
2166     clear_lines(in_top, lines);
2167     ix = iendx = 1;
2168     iy = iendy = istarty = in_top;
2169     ipos();
2170 }
2171 
2172 /* clear logical input line */
clear_input_line(void)2173 static void clear_input_line(void)
2174 {
2175     if (!visual) clear_line();
2176     else clear_lines(istarty, iendy);
2177     ix = iendx = 1;
2178     iy = iendy = istarty;
2179     if (visual) ipos();
2180 }
2181 
2182 /* affects iendx, iendy, istarty.  No effect on ix, iy. */
scroll_input(int n)2183 static void scroll_input(int n)
2184 {
2185     if (n > isize) {
2186         clear_lines(in_top, lines);
2187         iendy = in_top;
2188     } else if (delete_line) {
2189         xy(1, in_top);
2190         for (iendy = lines + 1; iendy > lines - n + 1; iendy--)
2191             tp(delete_line);
2192     } else if (has_scroll_region) {
2193         setscroll(in_top, lines);
2194         xy(1, lines);
2195         crnl(n);  /* DON'T: cy += n; */
2196         iendy = lines - n + 1;
2197     }
2198     xy(iendx = 1, iendy);
2199 }
2200 
2201 
2202 /***********************************************************************
2203  *                                                                     *
2204  *                        INPUT WINDOW HANDLING                        *
2205  *                                                                     *
2206  ***********************************************************************/
2207 
2208 /* ictrl_put
2209  * display n characters of s, with control characters converted to printable
2210  * bold reverse (so the physical width is also n).
2211  */
ictrl_put(const char * s,int n)2212 static void ictrl_put(const char *s, int n)
2213 {
2214     int attrflag;
2215     char c;
2216 
2217     for (attrflag = 0; n > 0; s++, n--) {
2218         c = unmapchar(localize(*s));
2219         if (is_cntrl(c)) {
2220             if (!attrflag)
2221                 attributes_on(F_BOLD | F_REVERSE), attrflag = 1;
2222             bufputc(CTRL(c));
2223         } else {
2224             if (attrflag)
2225                 attributes_off(F_BOLD | F_REVERSE), attrflag = 0;
2226             bufputc(c);
2227         }
2228     }
2229     if (attrflag) attributes_off(F_BOLD | F_REVERSE);
2230 }
2231 
2232 /* ioutputs
2233  * Print string within bounds of input window.  len is the number of
2234  * characters to print; return value is the number actually printed,
2235  * which may be less than len if string doesn't fit in the input window.
2236  * precondition: iendx,iendy and real cursor are at the output position.
2237  */
ioutputs(const char * str,int len)2238 static int ioutputs(const char *str, int len)
2239 {
2240     int space, written;
2241 
2242     for (written = 0; len > 0; len -= space) {
2243         if ((space = Wrap - iendx + 1) <= 0) {
2244             if (!visual || iendy == lines) break;   /* at end of window */
2245             if (visual) xy(iendx = 1, ++iendy);
2246             space = Wrap;
2247         }
2248         if (space > len) space = len;
2249         ictrl_put(str, space);  cx += space;
2250         str += space;
2251         written += space;
2252         iendx += space;
2253     }
2254     return written;
2255 }
2256 
2257 /* ioutall
2258  * Performs ioutputs() for input buffer starting at kpos.
2259  * A negative kpos means to display that much of the end of the prompt.
2260  */
ioutall(int kpos)2261 static void ioutall(int kpos)
2262 {
2263     int ppos;
2264 
2265     if (kpos < 0) {                  /* posible only if there's a prompt */
2266         kpos = -(-kpos % Wrap);
2267         ppos = prompt->len + kpos;
2268         if (ppos < 0) ppos = 0;
2269         hwrite(prompt, ppos, prompt->len - ppos, 0);
2270         iendx = -kpos + 1;
2271         kpos = 0;
2272     }
2273     if (keybuf->data == 0) return;
2274     if (sockecho())
2275         ioutputs(keybuf->data + kpos, keybuf->len - kpos);
2276 }
2277 
iput(int len)2278 void iput(int len)
2279 {
2280     const char *s;
2281     int count, scrolled = 0, oiex = iendx, oiey = iendy;
2282 
2283     s = keybuf->data + keyboard_pos - len;
2284 
2285     if (!sockecho()) return;
2286     if (visual) physical_refresh();
2287 
2288     if (keybuf->len - keyboard_pos > 8 &&     /* faster than redisplay? */
2289         visual && insert && clear_to_eol &&
2290         (insert_char || insert_start) &&      /* can insert */
2291         cy + (cx + len) / Wrap <= lines &&    /* new text will fit in window */
2292         Wrap - len > 8)                       /* faster than redisplay? */
2293     {
2294         /* fast insert */
2295         iy = iy + (ix - 1 + len) / Wrap;
2296         ix = (ix - 1 + len) % Wrap + 1;
2297         iendy = iendy + (iendx - 1 + len) / Wrap;
2298         iendx = (iendx - 1 + len) % Wrap + 1;
2299         if (iendy > lines) { iendy = lines; iendx = Wrap + 1; }
2300         if (cx + len <= Wrap) {
2301             if (insert_start) tp(insert_start);
2302             else for (count = len; count; count--) tp(insert_char);
2303             ictrl_put(s, len);
2304             s += Wrap - (cx - 1);
2305             cx += len;
2306         } else {
2307             ictrl_put(s, Wrap - (cx - 1));
2308             s += Wrap - (cx - 1);
2309             cx = Wrap + 1;
2310             if (insert_start) tp(insert_start);
2311         }
2312         while (s < keybuf->data + keybuf->len) {
2313             if (Wrap < columns && cx <= Wrap) {
2314                 xy(Wrap + 1, cy);
2315                 tp(clear_to_eol);
2316             }
2317             if (cy == lines) break;
2318             xy(1, cy + 1);
2319             if (!insert_start)
2320                 for (count = len; count; count--) tp(insert_char);
2321             ictrl_put(s, len);  cx += len;
2322             s += Wrap;
2323         }
2324         if (insert_start) tp(insert_end);
2325         ipos();
2326         bufflush();
2327         return;
2328     }
2329 
2330     iendx = ix;
2331     iendy = iy;
2332 
2333     /* Display as much as possible.  If it doesn't fit, scroll and repeat
2334      * until the whole string has been displayed.
2335      */
2336     while (count = ioutputs(s, len), s += count, (len -= count) > 0) {
2337         scrolled++;
2338         if (!visual) {
2339 	    if (expnonvis) {
2340 		int i;
2341 		int prompt_len = 0;
2342 		bufputc('\r');
2343 		if (prompt) {
2344 		    prompt_len = prompt->len % Wrap;
2345 		    hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
2346 		}
2347 		for (i = 0; i < sidescroll; i++)
2348 		    tp(delete_char);
2349 		iendx -= i;
2350 		cx = cy = -1;
2351 		xy(iendx, lines);
2352 	    } else {
2353 		crnl(1);  cx = 1;
2354 		iendx = ix = 1;
2355 	    }
2356         } else if (scroll && !clearfull) {
2357             scroll_input(1);
2358             if (istarty > in_top) istarty--;
2359         } else {
2360             clear_input_window();
2361         }
2362     }
2363     ix = iendx;
2364     iy = iendy;
2365 
2366     if (insert || scrolled) {
2367         /* we must (re)display tail */
2368         ioutputs(keybuf->data + keyboard_pos, keybuf->len - keyboard_pos);
2369         if (visual) ipos();
2370         else { bufputnc('\010', iendx - ix);  cx -= (iendx - ix); }
2371 
2372     } else if ((iendy - oiey) * Wrap + iendx - oiex < 0) {
2373         /* if we didn't write past old iendx/iendy, restore them */
2374         iendx = oiex;
2375         iendy = oiey;
2376     }
2377 
2378     bufflush();
2379 }
2380 
inewline(void)2381 void inewline(void)
2382 {
2383     ix = iendx = 1;
2384     if (!visual) {
2385 	if (expnonvis) {
2386 	    clear_input_line();
2387 	} else {
2388 	    crnl(1);  cx = 1; cy++;
2389 	}
2390         if (prompt) set_refresh_pending(REF_PHYSICAL);
2391 
2392     } else {
2393         if (cleardone) {
2394             clear_input_window();
2395         } else {
2396             iy = iendy + 1;
2397             if (iy > lines) {
2398                 if (scroll && !clearfull) {
2399                     scroll_input(1);
2400                     iy--;
2401                 } else {
2402                     clear_input_window();
2403                 }
2404             }
2405         }
2406         istarty = iendy = iy;
2407         set_refresh_pending(prompt ? REF_LOGICAL : REF_PHYSICAL);
2408     }
2409 
2410     bufflush();
2411 }
2412 
2413 /* idel() assumes place is in bounds and != keyboard_pos. */
idel(int place)2414 void idel(int place)
2415 {
2416     int len;
2417     int oiey = iendy;
2418 
2419     if ((len = place - keyboard_pos) < 0) keyboard_pos = place;
2420     if (!sockecho()) return;
2421     if (len < 0) ix += len;
2422 
2423     if (!visual) {
2424 	int prompt_len = prompt ? prompt->len % Wrap : 0;
2425         if (ix < prompt_len + 1 || need_refresh) {
2426             physical_refresh();
2427             return;
2428         }
2429         if (expnonvis && ix == prompt_len + 1 && keyboard_pos == keybuf->len) {
2430 	    /* there would be nothing left; slide the window so there is */
2431             physical_refresh();
2432             return;
2433         }
2434         if (len < 0) { bufputnc('\010', -len);  cx += len; }
2435 
2436     } else {
2437         /* visual */
2438         if (ix < 1) {
2439             iy -= ((-ix) / Wrap) + 1;
2440             ix = Wrap - ((-ix) % Wrap);
2441         }
2442         if (iy < in_top) {
2443             logical_refresh();
2444             return;
2445         }
2446         physical_refresh();
2447     }
2448 
2449     if (len < 0) len = -len;
2450 
2451     if (visual && delete_char &&
2452         keybuf->len - keyboard_pos > 3 && len < Wrap/3)
2453     {
2454         /* hardware method */
2455         int i, space, pos;
2456 
2457         iendy = iy;
2458         if (ix + len <= Wrap) {
2459             for (i = len; i; i--) tp(delete_char);
2460             iendx = Wrap + 1 - len;
2461         } else {
2462             iendx = ix;
2463         }
2464         pos = keyboard_pos - ix + iendx;
2465 
2466         while (pos < keybuf->len) {
2467             if ((space = Wrap - iendx + 1) <= 0) {
2468                 if (iendy == lines) break;   /* at end of window */
2469                 xy(iendx = 1, ++iendy);
2470                 for (i = len; i; i--) tp(delete_char);
2471                 space = Wrap - len;
2472                 if (space > keybuf->len - pos) space = keybuf->len - pos;
2473             } else {
2474                 xy(iendx, iendy);
2475                 if (space > keybuf->len - pos) space = keybuf->len - pos;
2476                 ictrl_put(keybuf->data + pos, space);  cx += space;
2477             }
2478             iendx += space;
2479             pos += space;
2480         }
2481 
2482         /* erase tail */
2483         if (iendy < oiey) {
2484             crnl(1); cx = 1; cy++;
2485             clear_line();
2486         }
2487 
2488     } else {
2489         /* redisplay method */
2490         iendx = ix;
2491         iendy = iy;
2492         ioutputs(keybuf->data + keyboard_pos, keybuf->len - keyboard_pos);
2493 
2494         /* erase tail */
2495         if (len > Wrap - cx + 1) len = Wrap - cx + 1;
2496         if (visual && clear_to_eos && (len > 2 || cy < oiey)) {
2497             tp(clear_to_eos);
2498         } else if (clear_to_eol && len > 2) {
2499             tp(clear_to_eol);
2500             if (visual && cy < oiey) clear_lines(cy + 1, oiey);
2501         } else {
2502             bufputnc(' ', len);  cx += len;
2503             if (visual && cy < oiey) clear_lines(cy + 1, oiey);
2504         }
2505     }
2506 
2507     /* restore cursor */
2508     if (visual) ipos();
2509     else { bufputnc('\010', cx - ix);  cx = ix; }
2510 
2511     bufflush();
2512 }
2513 
igoto(int place)2514 int igoto(int place)
2515 {
2516     int diff;
2517 
2518     if (place < 0)
2519         place = 0;
2520     if (place > keybuf->len)
2521         place = keybuf->len;
2522     diff = place - keyboard_pos;
2523     keyboard_pos = place;
2524 
2525     if (!diff) {
2526         /* no physical change */
2527 	dobell(1);
2528 
2529     } else if (!sockecho()) {
2530         /* no physical change */
2531 
2532     } else if (!visual) {
2533 	int prompt_len = prompt ? prompt->len % Wrap : 0;
2534         ix += diff;
2535         if (ix-1 < prompt_len) { /* off left edge of screen/prompt */
2536 	    if (expnonvis && insert_char && prompt_len - (ix-1) <= Wrap/2) {
2537 		/* can scroll, and amount of scroll needed is <= half screen */
2538 		int i, n;
2539 		bufputc('\r');
2540 		if (prompt)
2541 		    hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
2542 		n = prompt_len - (ix-1); /* get ix onto screen */
2543 		if (sidescroll > n) n = sidescroll; /* bring up to minimum */
2544 		if (n > keyboard_pos-diff) n = keyboard_pos-diff; /* too far? */
2545 		for (i = 0; i < n; i++)
2546 		    tp(insert_char);
2547 		ix += i;
2548 		ictrl_put(keybuf->data + keyboard_pos + prompt_len - (ix-1), i);
2549                 bufputnc('\010', (prompt_len + i) - (ix - 1));
2550 		cx = ix;
2551 		cy = lines;
2552 	    } else {
2553 		physical_refresh();
2554 	    }
2555         } else if (ix > Wrap) { /* off right edge of screen */
2556 	    if (expnonvis) {
2557 		if (ix - Wrap > Wrap/2) {
2558 		    physical_refresh();
2559 		} else {
2560 		    /* amount of scroll needed is <= half screen */
2561 		    int offset = place - ix + iendx;
2562 		    int i;
2563 		    bufputc('\r');
2564 		    if (prompt)
2565 			hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
2566 		    for (i = 0; i < sidescroll || i < ix - Wrap; i++)
2567 			tp(delete_char);
2568 		    iendx -= i;
2569 		    ix -= i;
2570 		    cx = prompt_len + 1;
2571 		    cy = lines;
2572 		    xy(iendx, lines);
2573 		    ioutputs(keybuf->data + offset, keybuf->len - offset);
2574 		    diff -= i;
2575 		    offset += i;
2576 		    xy(ix, lines);
2577 		}
2578 	    } else {
2579 		crnl(1);  cx = 1;  /* old text scrolls up, for continutity */
2580 		physical_refresh();
2581 	    }
2582         } else { /* on screen */
2583             cx += diff;
2584             if (diff < 0)
2585                 bufputnc('\010', -diff);
2586             else
2587                 ictrl_put(keybuf->data + place - diff, diff);
2588         }
2589 
2590     /* visual */
2591     } else {
2592         int new = (ix - 1) + diff;
2593         iy += ndiv(new, Wrap);
2594         ix = nmod(new, Wrap) + 1;
2595 
2596         if ((iy > lines) && (iy - lines < isize) && scroll) {
2597             scroll_input(iy - lines);
2598             ioutall(place - (ix - 1) - (iy - lines - 1) * Wrap);
2599             iy = lines;
2600             ipos();
2601         } else if ((iy < in_top) || (iy > lines)) {
2602             logical_refresh();
2603         } else {
2604             ipos();
2605         }
2606     }
2607 
2608     bufflush();
2609     return keyboard_pos;
2610 }
2611 
do_refresh(void)2612 void do_refresh(void)
2613 {
2614     if (visual && need_more_refresh) update_status_field(NULL, STAT_MORE);
2615     if (need_refresh >= REF_LOGICAL) logical_refresh();
2616     else if (need_refresh >= REF_PHYSICAL) physical_refresh();
2617 }
2618 
physical_refresh(void)2619 void physical_refresh(void)
2620 {
2621     if (visual) {
2622         setscroll(1, lines);
2623         ipos();
2624     } else {
2625 	int start;
2626 	int prompt_len = 0;
2627         clear_input_line();
2628 	if (prompt) {
2629 	    prompt_len = (prompt->len + 1) % Wrap - 1;
2630 	    hwrite(prompt, prompt->len - prompt_len, prompt_len, 0);
2631 	    iendx = prompt_len + 1;
2632 	}
2633 	ix = (sockecho()?keyboard_pos:0) % (Wrap - prompt_len) + 1 + prompt_len;
2634         start = (sockecho()?keyboard_pos:0) - (ix - 1) + prompt_len;
2635 	if (start == keybuf->len && keybuf->len > 0) { /* would print nothing */
2636 	    /* slide window so something is visible */
2637 	    if (start > Wrap - prompt_len) {
2638 		ix += Wrap - prompt_len;
2639 		start -= Wrap - prompt_len;
2640 	    } else {
2641 		ix += start;
2642 		start = 0;
2643 	    }
2644 	}
2645 	ioutall(start);
2646         bufputnc('\010', iendx - ix);  cx -= (iendx - ix);
2647     }
2648     bufflush();
2649     if (need_refresh <= REF_PHYSICAL) need_refresh = 0;
2650     old_ix = -1; /* invalid */
2651 }
2652 
logical_refresh(void)2653 void logical_refresh(void)
2654 {
2655     int kpos, nix, niy;
2656 
2657     if (!visual)
2658         oflush();  /* no sense refreshing if there's going to be output after */
2659 
2660     kpos = prompt ? -(prompt->len % Wrap) : 0;
2661     nix = ((sockecho() ? keyboard_pos : 0) - kpos) % Wrap + 1;
2662 
2663     if (visual) {
2664         setscroll(1, lines);
2665         niy = istarty + (keyboard_pos - kpos) / Wrap;
2666         if (niy <= lines) {
2667             clear_input_line();
2668         } else {
2669             clear_input_window();
2670             kpos += (niy - lines) * Wrap;
2671             niy = lines;
2672         }
2673         ioutall(kpos);
2674         ix = nix;
2675         iy = niy;
2676         ipos();
2677     } else {
2678         clear_input_line();
2679 	if (expnonvis) {
2680 	    int plen = 0;
2681 	    if (prompt) {
2682 		plen = (prompt->len + 1) % Wrap - 1;
2683 		hwrite(prompt, prompt->len - plen, plen, 0);
2684 		iendx = plen + 1;
2685 	    }
2686 	    ix = (old_ix >= iendx && old_ix <= Wrap) ? old_ix :
2687 		(sockecho()?keyboard_pos:0) % (Wrap - plen) + 1 + plen;
2688 	    old_ix = -1; /* invalid */
2689 	    kpos = (sockecho()?keyboard_pos:0) - (ix - 1) + plen;
2690 	    if (kpos == keybuf->len && keybuf->len > 0) {
2691 		/* would print nothing; slide window so something is visible */
2692 		if (kpos > Wrap/2) {
2693 		    ix += Wrap/2;
2694 		    kpos -= Wrap/2;
2695 		} else {
2696 		    ix += kpos;
2697 		    kpos -= kpos;
2698 		}
2699 	    }
2700 	    ioutall(kpos);
2701 	} else {
2702 	    ioutall(kpos);
2703 	    kpos += Wrap;
2704 	    while ((sockecho() && kpos <= keyboard_pos) || kpos < 0) {
2705 		crnl(1);  cx = 1;
2706 		iendx = 1;
2707 		ioutall(kpos);
2708 		kpos += Wrap;
2709 	    }
2710 	    ix = nix;
2711 	}
2712 	bufputnc('\010', iendx - ix);  cx -= (iendx - ix);
2713     }
2714     bufflush();
2715     if (need_refresh <= REF_LOGICAL) need_refresh = 0;
2716 }
2717 
update_prompt(conString * newprompt,int display)2718 void update_prompt(conString *newprompt, int display)
2719 {
2720     conString *oldprompt = prompt;
2721 
2722     if (oldprompt == moreprompt) return;
2723     prompt = newprompt;
2724     old_ix = -1;
2725     if ((oldprompt || prompt) && display)
2726         set_refresh_pending(REF_LOGICAL);
2727 }
2728 
2729 
2730 /*****************************************************
2731  *                                                   *
2732  *                  OUTPUT HANDLING                  *
2733  *                                                   *
2734  *****************************************************/
2735 
2736 /*************
2737  * Utilities *
2738  *************/
2739 
attributes_off(attr_t attrs)2740 static void attributes_off(attr_t attrs)
2741 {
2742     const char *ctlseq;
2743 
2744     if (attrs & F_HILITE) attrs |= hiliteattr;
2745     if (have_attr & attrs & F_SIMPLE) {
2746         if (attr_off) tp(attr_off);
2747         else {
2748             if (have_attr & attrs & F_UNDERLINE) tp(underline_off);
2749             if (have_attr & attrs & F_BOLD     ) tp(standout_off);
2750         }
2751     }
2752     if ((attrs & F_COLORS) && (ctlseq = getvar("end_color"))) {
2753         print_to_ascii(outbuf, ctlseq);
2754     }
2755 }
2756 
attributes_on(attr_t attrs)2757 static void attributes_on(attr_t attrs)
2758 {
2759     if (attrs & F_HILITE)
2760         attrs |= hiliteattr;
2761 
2762 #if 0
2763     if (attr_on) {
2764         /* standout, underline, reverse, blink, dim, bold, blank, prot., ACS */
2765         tp(tparm(attr_on,
2766             (have_attr & attrs & F_BOLD && !bold),
2767             (have_attr & attrs & F_UNDERLINE),
2768             (have_attr & attrs & F_REVERSE),
2769             (have_attr & attrs & F_FLASH),
2770             (have_attr & attrs & F_DIM),
2771             (have_attr & attrs & F_BOLD && bold),
2772             0, 0, 0));
2773         } else
2774 #endif
2775     {
2776         /* Some emulators only show the last, so we do most important last. */
2777         if (have_attr & attrs & F_DIM)       tp(dim);
2778         if (have_attr & attrs & F_BOLD)      tp(bold ? bold : standout);
2779         if (have_attr & attrs & F_UNDERLINE) tp(underline);
2780         if (have_attr & attrs & F_REVERSE)   tp(reverse);
2781         if (have_attr & attrs & F_FLASH)     tp(flash);
2782     }
2783 
2784     if (attrs & F_FGCOLOR)  color_on("", attr2fgcolor(attrs));
2785     if (attrs & F_BGCOLOR)  color_on("bg", attr2bgcolor(attrs));
2786 }
2787 
color_on(const char * prefix,long color)2788 static void color_on(const char *prefix, long color)
2789 {
2790     const char *ctlseq;
2791     smallstr buf;
2792 
2793     sprintf(buf, "start_color_%s%s", prefix, enum_color[color].data);
2794     if ((ctlseq = getvar(buf))) {
2795         print_to_ascii(outbuf, ctlseq);
2796     } else {
2797         sprintf(buf, "start_color_%s%ld", prefix, color);
2798         if ((ctlseq = getvar(buf)))
2799             print_to_ascii(outbuf, ctlseq);
2800     }
2801 }
2802 
hwrite(conString * line,int start,int len,int indent)2803 static void hwrite(conString *line, int start, int len, int indent)
2804 {
2805     attr_t attrs = line->attrs & F_HWRITE;
2806     attr_t current = 0;
2807     attr_t new;
2808     int i, ctrl;
2809     int col = 0;
2810     char c;
2811 
2812     if (line->attrs & F_BELL && start == 0) {
2813         dobell(1);
2814     }
2815 
2816     if (indent) {
2817         bufputnc(' ', indent);
2818         cx += indent;
2819     }
2820 
2821     cx += len;
2822 
2823     if (!line->charattrs && hilite && attrs)
2824         attributes_on(current = attrs);
2825 
2826     for (i = start; i < start + len; ++i) {
2827         new = line->charattrs ? adj_attr(attrs, line->charattrs[i]) : attrs;
2828         c = unmapchar(localize(line->data[i]));
2829         ctrl = (emulation > EMUL_RAW && is_cntrl(c) && c != '\t');
2830         if (ctrl)
2831             new |= F_BOLD | F_REVERSE;
2832         if (new != current) {
2833             if (current) attributes_off(current);
2834             current = new;
2835             if (current) attributes_on(current);
2836         }
2837         if (c == '\t') {
2838             bufputnc(' ', tabsize - col % tabsize);
2839             col += tabsize - col % tabsize;
2840         } else {
2841             bufputc(ctrl ? CTRL(c) : c);
2842             col++;
2843         }
2844     }
2845     if (current) attributes_off(current);
2846 }
2847 
reset_outcount(Screen * screen)2848 void reset_outcount(Screen *screen)
2849 {
2850     if (!screen) screen = display_screen;
2851     screen->outcount = visual ?
2852         (scroll ? (out_bot - out_top + 1) : screen->outcount) :
2853         lines - 1;
2854 }
2855 
2856 /* return TRUE if okay to print */
check_more(Screen * screen)2857 static int check_more(Screen *screen)
2858 {
2859     if (!screen->paused && more && interactive && screen->outcount-- <= 0)
2860     {
2861         /* status bar is updated in oflush() to avoid scroll region problems */
2862         screen->paused = 1;
2863         do_hook(H_MORE, NULL, "");
2864     }
2865     return !screen->paused;
2866 }
2867 
pause_screen(void)2868 int pause_screen(void)
2869 {
2870     if (display_screen->paused)
2871 	return 0;
2872     display_screen->outcount = 0;
2873     display_screen->paused = 1;
2874     do_hook(H_MORE, NULL, "");
2875     update_status_field(NULL, STAT_MORE);
2876     return 1;
2877 }
2878 
clear_more(int new)2879 int clear_more(int new)
2880 {
2881     PhysLine *pl;
2882     int use_insert, need_redraw = 0, scrolled = 0;
2883 
2884     if (new < 0) {
2885 	if (!visual /* XXX || !can_scrollback */) return 0;
2886 	use_insert = insert_line && -new < winlines();
2887 	setscroll(1, out_bot);
2888 	while (scrolled > new && prevtop(display_screen)) {
2889 	    pl = display_screen->top->datum;
2890 	    if (display_screen->viewsize <= winlines()) {
2891 		/* visible area is not full:  add to the top of visible area */
2892 		xy(1, winlines() - display_screen->viewsize + 1);
2893 		hwrite(pl->str, pl->start,
2894 		    pl->len < Wrap - pl->indent ? pl->len : Wrap - pl->indent,
2895 		    pl->indent);
2896 	    } else {
2897 		/* visible area is full:  insert at top and push bottom off */
2898 		display_screen->paused = 1;
2899 		display_screen->outcount = 0;
2900 		if (use_insert) {
2901 		    xy(1, 1);
2902 		    tp(insert_line);
2903 		    hwrite(pl->str, pl->start,
2904 			pl->len < Wrap-pl->indent ? pl->len : Wrap-pl->indent,
2905 			pl->indent);
2906 		} else {
2907 		    need_redraw++;
2908 		}
2909 		prevbot(display_screen);
2910 	    }
2911 	    scrolled--;
2912 	}
2913 	while (scrolled > new && display_screen->viewsize > 1) {
2914 	    /* no more lines in list to scroll on to top. insert blanks. */
2915 	    display_screen->paused = 1;
2916 	    display_screen->outcount = 0;
2917 	    if (use_insert) {
2918 		xy(1, 1);
2919 		tp(insert_line);
2920 	    } else {
2921 		need_redraw++;
2922 	    }
2923 	    prevbot(display_screen);
2924 	    scrolled--;
2925 	}
2926 	if (need_redraw) {
2927 	    redraw();
2928 	    return scrolled;
2929 	}
2930 	update_status_field(NULL, STAT_MORE);
2931     } else { /* new >= 0 */
2932 	if (!display_screen->paused) return 0;
2933 	if (display_screen->nback_filtered) {
2934 	    if (visual) {
2935 		setscroll(1, out_bot);
2936 		if (cy != out_bot)
2937 		    xy(columns, out_bot);
2938 	    } else {
2939 		if (!need_refresh)
2940 		    old_ix = ix; /* physical_refresh() will restore ix */
2941 		clear_input_line();
2942 	    }
2943 	    while (scrolled < new && nextbot(display_screen)) {
2944 		pl = display_screen->bot->datum;
2945 		if (visual) output_scroll(pl); else output_novisual(pl);
2946 		scrolled++;
2947 	    }
2948 	}
2949 	while (display_screen->viewsize > winlines())
2950 	    nexttop(display_screen);
2951 	if (scrolled < new) {
2952 	    display_screen->paused = 0;
2953 	    display_screen->outcount = new - scrolled;
2954 	    if (visual) {
2955 		update_status_field(NULL, STAT_MORE);
2956 		if (!scroll) display_screen->outcount = out_bot;
2957 	    } else {
2958 		prompt = fgprompt();
2959 		old_ix = -1;
2960 		clear_input_line();
2961 	    }
2962 	}
2963     }
2964     set_refresh_pending(REF_PHYSICAL);
2965     return scrolled;
2966 }
2967 
tog_more(Var * var)2968 int tog_more(Var *var)
2969 {
2970     if (!more) clear_more(display_screen->outcount);
2971     else reset_outcount(display_screen);
2972     return 1;
2973 }
2974 
tog_keypad(Var * var)2975 int tog_keypad(Var *var)
2976 {
2977     if (!keypad_on) {
2978 	if (keypad)
2979 	    eprintf("don't know how to enable keypad on %s terminal", TERM);
2980 	return 0;
2981     }
2982     tp(keypad ? keypad_on : keypad_off);
2983     return 1;
2984 }
2985 
screen_end(int need_redraw)2986 int screen_end(int need_redraw)
2987 {
2988     Screen *screen = display_screen;
2989     int oldmore = more;
2990 
2991     hide_screen(screen);
2992     if (screen->nback_filtered) {
2993 	screen->nback_filtered = screen->nback = 0;
2994 	screen->nnew_filtered = screen->nnew = 0;
2995 	special_var[VAR_more].val.u.ival = 0;
2996 
2997 	/* XXX optimize if (jump < screenful) (but what about tmp lines?) */
2998 	need_redraw = 1;
2999 	screen->maxbot = screen->bot = screen->pline.tail;
3000 	screen_refilter(screen);
3001 
3002 	special_var[VAR_more].val.u.ival = oldmore;
3003     }
3004 
3005     screen->paused = 0;
3006     reset_outcount(screen);
3007     if (need_redraw) {
3008 	redraw_window(screen, 0);
3009 	if (visual) {
3010 	    update_status_field(NULL, STAT_MORE);
3011 	} else {
3012 	    prompt = fgprompt();
3013 	}
3014     }
3015     return 1;
3016 }
3017 
selflush(void)3018 int selflush(void)
3019 {
3020     display_screen->selflush = 1;
3021     screen_refilter_bottom(display_screen);
3022     clear_more(winlines());
3023     return 1;
3024 }
3025 
3026 
3027 /* next_physline
3028  * Increment bottom of screen.  Checks for More and SELFLUSH termination.
3029  * Returns 1 if there is a displayable line, 0 if not.
3030  */
next_physline(Screen * screen)3031 static int next_physline(Screen *screen)
3032 {
3033     if (screen->paused)
3034 	return 0;
3035     if (!nextbot(screen)) {
3036 	if (screen->selflush) {
3037 	    if (screen->selflush == 1) {
3038 		screen->selflush++;
3039 		screen->paused = 1;
3040 		update_status_field(NULL, STAT_MORE);
3041 	    } else {
3042 		screen->selflush = 0;
3043 		clear_screen_filter(screen);
3044 		screen_end(1);
3045 	    }
3046 	}
3047 	return 0;
3048     }
3049     if (!check_more(screen)) {
3050 	/* undo the nextbot() */
3051 	if (screen->maxbot == screen->bot) {
3052 	    screen->maxbot = screen->maxbot->prev;
3053 	    screen->nnew++;
3054 	    screen->nnew_filtered++;
3055 	}
3056 	screen->bot = screen->bot->prev;
3057 	screen->nback_filtered++;
3058 	screen->nback++;
3059 	screen->viewsize--;
3060 	return 0;
3061     }
3062     if (display_screen->viewsize > winlines())
3063 	nexttop(screen);
3064     return 1;
3065 }
3066 
3067 /* returns length of prefix of str that will fit in {wrapsize} */
wraplen(const char * str,int len,int indent)3068 int wraplen(const char *str, int len, int indent)
3069 {
3070     int total, max, visible;
3071 
3072     if (emulation == EMUL_RAW) return len;
3073 
3074     max = Wrap - indent;
3075 
3076     for (visible = total = 0; total < len && visible < max; total++) {
3077 	if (str[total] == '\t')
3078 	    visible += tabsize - visible % tabsize;
3079 	else
3080 	    visible++;
3081     }
3082 
3083     if (total == len) return len;
3084     len = total;
3085     if (wrapflag) {
3086         while (len && !is_space(str[len-1]))
3087 	    --len;
3088 	if (wrappunct > 0 && len < total - wrappunct) {
3089 	    len = total;
3090 	    while (len && !is_space(str[len-1]) && !is_punct(str[len-1]))
3091 		--len;
3092 	}
3093     }
3094     return len ? len : total;
3095 }
3096 
3097 
3098 /****************
3099  * Main drivers *
3100  ****************/
3101 
moresize(Screen * screen)3102 int moresize(Screen *screen) {
3103     if (!screen) screen = display_screen;
3104     return screen->nback_filtered;
3105 }
3106 
3107 /* write to display_screen (no history) */
screenout(conString * line)3108 void screenout(conString *line)
3109 {
3110     enscreen(display_screen, line);
3111     oflush();
3112 }
3113 
enscreen(Screen * screen,conString * line)3114 void enscreen(Screen *screen, conString *line)
3115 {
3116     int wrapped, visible;
3117 
3118     if (!hilite)
3119         line->attrs &= ~F_HWRITE;
3120     if (line->attrs & F_GAG && gag)
3121         return;
3122 
3123     if (!screen->pline.head) { /* initialize wrap state */
3124 	screen->scr_wrapflag = wrapflag;
3125 	screen->scr_wrapsize = Wrap;
3126 	screen->scr_wrapspace = wrapspace;
3127 	screen->scr_wrappunct = wrappunct;
3128     }
3129     visible = screen_filter(screen, line);
3130     wrapped = wraplines(line, &screen->pline, visible);
3131     screen->nlline++;
3132     screen->npline += wrapped;
3133     screen->nback += wrapped;
3134     screen->nnew += wrapped;
3135     if (visible) {
3136 	screen->nback_filtered += wrapped;
3137 	screen->nnew_filtered += wrapped;
3138     }
3139     purge_old_lines(screen);
3140 }
3141 
oflush(void)3142 void oflush(void)
3143 {
3144     static int lastsize;
3145     int waspaused, count = 0;
3146     PhysLine *pl;
3147     Screen *screen = display_screen;
3148 
3149     if (output_disabled) return;
3150 
3151     if (!(waspaused = screen->paused)) {
3152         lastsize = 0;
3153         while (next_physline(screen)) {
3154             pl = screen->bot->datum;
3155             if (count++ == 0) {  /* first iteration? */
3156                 if (screen_mode < 1) {
3157 		    if (!need_refresh)
3158 			old_ix = ix; /* physical_refresh() will restore ix */
3159                     clear_input_line();
3160                 } else if (scroll && has_scroll_region) {
3161                     setscroll(1, out_bot);
3162                     if (cy != out_bot)
3163 			xy(columns, out_bot);
3164                 }
3165             }
3166             if (screen_mode < 1) output_novisual(pl);
3167 #ifdef SCREEN
3168             else if (scroll) output_scroll(pl);
3169             else output_noscroll(pl);
3170 #endif
3171             set_refresh_pending(REF_PHYSICAL);
3172             bufflush();
3173         }
3174     }
3175 
3176     if (screen->paused) {
3177         if (!visual) {
3178             if (!waspaused) {
3179                 prompt = moreprompt;
3180 		old_ix = -1;
3181                 set_refresh_pending(REF_LOGICAL);
3182             }
3183         } else if (!waspaused ||
3184 	    moresize(screen) / morewait > lastsize / morewait)
3185 	{
3186             update_status_field(NULL, STAT_MORE);
3187         } else if (lastsize != moresize(screen)) {
3188             need_more_refresh = 1;
3189         }
3190         lastsize = moresize(screen);
3191     }
3192 }
3193 
output_novisual(PhysLine * pl)3194 static void output_novisual(PhysLine *pl)
3195 {
3196     hwrite(pl->str, pl->start, pl->len, pl->indent);
3197     crnl(1);  cx = 1; cy++;
3198 }
3199 
3200 #ifdef SCREEN
output_noscroll(PhysLine * pl)3201 static void output_noscroll(PhysLine *pl)
3202 {
3203     setscroll(1, lines);   /* needed after scroll_input(), etc. */
3204     xy(1, (oy + 1) % (out_bot) + 1);
3205     clear_line();
3206     xy(ox, oy);
3207     hwrite(pl->str, pl->start, pl->len, pl->indent);
3208     oy = oy % (out_bot) + 1;
3209 }
3210 
output_scroll(PhysLine * pl)3211 static void output_scroll(PhysLine *pl)
3212 {
3213     if (has_scroll_region) {
3214         crnl(1);
3215         /* Some brain damaged emulators lose attributes under cursor
3216          * when that '\n' is printed.  Too bad. */
3217     } else {
3218         xy(1, 1);
3219         tp(delete_line);
3220         xy(1, out_bot);
3221         tp(insert_line);
3222     }
3223     hwrite(pl->str, pl->start, pl->len, pl->indent);
3224 }
3225 #endif
3226 
hide_screen(Screen * screen)3227 void hide_screen(Screen *screen)
3228 {
3229     ListEntry *node, *next;
3230     PhysLine *pl;
3231 
3232     if (!screen) screen = fg_screen;
3233     if (screen->viewsize > 0) {
3234 	/* delete any temp lines in [top,bot] */
3235 	int done;
3236 	for (done = 0, node = screen->top; !done; node = next) {
3237 	    done = (node == screen->bot);
3238 	    next = node->next;
3239 	    pl = node->datum;
3240 	    if (pl->tmp) {
3241 		if (screen->top == node)
3242 		    screen->top = node->next;
3243 		if (screen->bot == node)
3244 		    screen->bot = node->prev;
3245 		if (screen->maxbot == node)
3246 		    screen->maxbot = node->prev;
3247 		unlist(node, &screen->pline);
3248 		conStringfree(pl->str);
3249 		pfree(pl, plpool, str);
3250 		screen->viewsize--;
3251 		screen->npline--;
3252 		screen->nlline--;
3253 	    }
3254 	}
3255     }
3256 }
3257 
unhide_screen(Screen * screen)3258 void unhide_screen(Screen *screen)
3259 {
3260     PhysLine *pl;
3261 
3262     if (!virtscreen || !textdiv || !visual) {
3263 	return;
3264 
3265     } else if (textdiv == TEXTDIV_CLEAR) {
3266 	clear_screen_view(screen);
3267 
3268     } else if (textdiv_str && fg_screen->maxbot &&
3269 	((PhysLine*)(fg_screen->maxbot->datum))->str != textdiv_str &&
3270 	(textdiv == TEXTDIV_ALWAYS ||
3271 	    fg_screen->maxbot != fg_screen->bot || fg_screen->maxbot->next))
3272 	/* If textdiv is enabled and there's no divider at maxbot already... */
3273     {
3274 	/* insert divider at maxbot */
3275 	palloc(pl, PhysLine, plpool, str, __FILE__, __LINE__);
3276 	pl->visible = 1;
3277 	pl->tmp = 1;
3278 	(pl->str = textdiv_str)->links++;
3279 	pl->start = 0;
3280 	pl->indent = 0;
3281 	pl->len = wraplen(textdiv_str->data, textdiv_str->len, 0);
3282 	inlist(pl, &fg_screen->pline, fg_screen->maxbot);
3283 	if (fg_screen->bot == fg_screen->maxbot) {
3284 	    /* insert ABOVE bot, so it doesn't look like new activity */
3285 	    fg_screen->bot = fg_screen->maxbot->next;
3286 	    if (fg_screen->viewsize == 0)
3287 		fg_screen->top = fg_screen->bot;
3288 	    fg_screen->viewsize++;
3289 	    if (fg_screen->viewsize >= winlines())
3290 		fg_screen->partialview = 0;
3291 	} else {
3292 	    /* inserting BELOW bot; we must increment nback* */
3293 	    fg_screen->nback++;
3294 	    fg_screen->nback_filtered++;
3295 	}
3296 	fg_screen->maxbot = fg_screen->maxbot->next;
3297 	fg_screen->npline++;
3298 	fg_screen->nlline++;
3299     }
3300 }
3301 
3302 /*
3303  * Switch to new fg_screen.
3304  */
switch_screen(int quiet)3305 void switch_screen(int quiet)
3306 {
3307     if (fg_screen != display_screen) {	/* !virtscreen */
3308         /* move lines from fg_screen to display_screen */
3309 	/* XXX optimize when no filter */
3310 	PhysLine *pl;
3311         List *dest = &display_screen->pline;
3312 	List *src = &fg_screen->pline;
3313 	while (src->head) {
3314 	    dest->tail->next = src->head;
3315 	    dest->tail = dest->tail->next;
3316 	    src->head = src->head->next;
3317 	    display_screen->nnew++;
3318 	    display_screen->nback++;
3319 	    pl = dest->tail->datum;
3320 	    if (screen_filter(display_screen, pl->str)) {
3321 		display_screen->nnew_filtered++;
3322 		display_screen->nback_filtered++;
3323 	    }
3324 	}
3325         src->head = src->tail = NULL;
3326         fg_screen->nback_filtered = fg_screen->nback = 0;
3327         fg_screen->nnew_filtered = fg_screen->nnew = 0;
3328         fg_screen->npline = 0;
3329         fg_screen->nlline = 0;
3330         fg_screen->maxbot = fg_screen->bot = fg_screen->top = NULL;
3331     }
3332     update_status_field(NULL, STAT_WORLD);
3333     if (quiet) {
3334         /* jump to end */
3335 	screen_end(1);
3336     } else {
3337 	if (virtscreen) {
3338 	    redraw_window(display_screen, 0);
3339 	}
3340 	/* display new lines */
3341 	oflush();
3342 	update_status_field(NULL, STAT_MORE);
3343     }
3344 }
3345 
3346 #if USE_DMALLOC
free_output(void)3347 void free_output(void)
3348 {
3349     int row;
3350 
3351     tfclose(tfscreen);
3352     tfclose(tfalert);
3353     free_screen(default_screen);
3354     fg_screen = default_screen = NULL;
3355     Stringfree(outbuf);
3356     for (row = 0; row < max_status_height; row++) {
3357 	Stringfree(status_line[row]);
3358 	while (statusfield_list[row]->head)
3359 	    free_statusfield(unlist(statusfield_list[row]->head,
3360 		statusfield_list[row]));
3361     }
3362 
3363     pfreepool(PhysLine, plpool, str);
3364 }
3365 #endif
3366 
3367