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