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