contrib/less: Update READMEs
[dragonfly.git] / contrib / less / line.c
1 /*
2  * Copyright (C) 1984-2023  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9
10 /*
11  * Routines to manipulate the "line buffer".
12  * The line buffer holds a line of output as it is being built
13  * in preparation for output to the screen.
14  */
15
16 #include "less.h"
17 #include "charset.h"
18 #include "position.h"
19
20 #if MSDOS_COMPILER==WIN32C
21 #define WIN32_LEAN_AND_MEAN
22 #include <windows.h>
23 #endif
24
25 #define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1)
26 static struct {
27         char *buf;    /* Buffer which holds the current output line */
28         int *attr;   /* Parallel to buf, to hold attributes */
29         int print;    /* Index in buf of first printable char */
30         int end;      /* Number of chars in buf */
31         char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */
32         int pfx_attr[MAX_PFX_WIDTH];
33         int pfx_end;  /* Number of chars in pfx */
34 } linebuf;
35
36 /*
37  * Buffer of ansi sequences which have been shifted off the left edge 
38  * of the screen. 
39  */
40 static struct xbuffer shifted_ansi;
41
42 /*
43  * Ring buffer of last ansi sequences sent.
44  * While sending a line, these will be resent at the end
45  * of any highlighted string, to restore text modes.
46  * {{ Not ideal, since we don't really know how many to resend. }}
47  */
48 #define NUM_LAST_ANSIS 3
49 static struct xbuffer last_ansi;
50 static struct xbuffer last_ansis[NUM_LAST_ANSIS];
51 static int curr_last_ansi;
52
53 public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */
54 static struct ansi_state *line_ansi = NULL;
55 static int ansi_in_line;
56 static int hlink_in_line;
57 static int line_mark_attr;
58 static int cshift;   /* Current left-shift of output line buffer */
59 public int hshift;   /* Desired left-shift of output line buffer */
60 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
61 public int ntabstops = 1;        /* Number of tabstops */
62 public int tabdefault = 8;       /* Default repeated tabstops */
63 public POSITION highest_hilite;  /* Pos of last hilite in file found so far */
64 static POSITION line_pos;
65
66 static int end_column;  /* Printable length, accounting for backspaces, etc. */
67 static int right_curr;
68 static int right_column;
69 static int overstrike;  /* Next char should overstrike previous char */
70 static int last_overstrike = AT_NORMAL;
71 static int is_null_line;  /* There is no current line */
72 static LWCHAR pendc;
73 static POSITION pendpos;
74 static char *end_ansi_chars;
75 static char *mid_ansi_chars;
76 static int in_hilite;
77
78 static int attr_swidth(int a);
79 static int attr_ewidth(int a);
80 static int do_append(LWCHAR ch, char *rep, POSITION pos);
81
82 extern int sigs;
83 extern int bs_mode;
84 extern int proc_backspace;
85 extern int proc_tab;
86 extern int proc_return;
87 extern int linenums;
88 extern int ctldisp;
89 extern int twiddle;
90 extern int binattr;
91 extern int status_col;
92 extern int status_col_width;
93 extern int linenum_width;
94 extern int auto_wrap, ignaw;
95 extern int bo_s_width, bo_e_width;
96 extern int ul_s_width, ul_e_width;
97 extern int bl_s_width, bl_e_width;
98 extern int so_s_width, so_e_width;
99 extern int sc_width, sc_height;
100 extern int utf_mode;
101 extern POSITION start_attnpos;
102 extern POSITION end_attnpos;
103 extern char rscroll_char;
104 extern int rscroll_attr;
105 extern int use_color;
106 extern int status_line;
107
108 static char mbc_buf[MAX_UTF_CHAR_LEN];
109 static int mbc_buf_len = 0;
110 static int mbc_buf_index = 0;
111 static POSITION mbc_pos;
112 static int saved_line_end;
113 static int saved_end_column;
114
115 /* Configurable color map */
116 struct color_map { int attr; char color[12]; };
117 static struct color_map color_map[] = {
118         { AT_UNDERLINE,            "" },
119         { AT_BOLD,                 "" },
120         { AT_BLINK,                "" },
121         { AT_STANDOUT,             "" },
122         { AT_COLOR_ATTN,           "Wm" },
123         { AT_COLOR_BIN,            "kR" },
124         { AT_COLOR_CTRL,           "kR" },
125         { AT_COLOR_ERROR,          "kY" },
126         { AT_COLOR_LINENUM,        "c" },
127         { AT_COLOR_MARK,           "Wb" },
128         { AT_COLOR_PROMPT,         "kC" },
129         { AT_COLOR_RSCROLL,        "kc" },
130         { AT_COLOR_HEADER,         "" },
131         { AT_COLOR_SEARCH,         "kG" },
132         { AT_COLOR_SUBSEARCH(1),   "ky" },
133         { AT_COLOR_SUBSEARCH(2),   "wb" },
134         { AT_COLOR_SUBSEARCH(3),   "YM" },
135         { AT_COLOR_SUBSEARCH(4),   "Yr" },
136         { AT_COLOR_SUBSEARCH(5),   "Wc" },
137 };
138
139 /* State while processing an ANSI escape sequence */
140 struct ansi_state {
141         int hindex;   /* Index into hyperlink prefix */
142         int hlink;    /* Processing hyperlink address? */
143         int prev_esc; /* Prev char was ESC (to detect ESC-\ seq) */
144 };
145
146 /*
147  * Initialize from environment variables.
148  */
149 public void init_line(void)
150 {
151         int ax;
152
153         end_ansi_chars = lgetenv("LESSANSIENDCHARS");
154         if (isnullenv(end_ansi_chars))
155                 end_ansi_chars = "m";
156
157         mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
158         if (isnullenv(mid_ansi_chars))
159                 mid_ansi_chars = "0123456789:;[?!\"'#%()*+ ";
160
161         linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
162         linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int));
163         size_linebuf = LINEBUF_SIZE;
164         xbuf_init(&shifted_ansi);
165         xbuf_init(&last_ansi);
166         for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
167                 xbuf_init(&last_ansis[ax]);
168         curr_last_ansi = 0;
169 }
170
171 /*
172  * Expand the line buffer.
173  */
174 static int expand_linebuf(void)
175 {
176         /* Double the size of the line buffer. */
177         int new_size = size_linebuf * 2;
178         char *new_buf = (char *) calloc(new_size, sizeof(char));
179         int *new_attr = (int *) calloc(new_size, sizeof(int));
180         if (new_buf == NULL || new_attr == NULL)
181         {
182                 if (new_attr != NULL)
183                         free(new_attr);
184                 if (new_buf != NULL)
185                         free(new_buf);
186                 return 1;
187         }
188         /*
189          * We just calloc'd the buffers; copy the old contents.
190          */
191         memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char));
192         memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int));
193         free(linebuf.attr);
194         free(linebuf.buf);
195         linebuf.buf = new_buf;
196         linebuf.attr = new_attr;
197         size_linebuf = new_size;
198         return 0;
199 }
200
201 /*
202  * Is a character ASCII?
203  */
204 public int is_ascii_char(LWCHAR ch)
205 {
206         return (ch <= 0x7F);
207 }
208
209 /*
210  */
211 static void inc_end_column(int w)
212 {
213         if (end_column > right_column && w > 0)
214         {
215                 right_column = end_column;
216                 right_curr = linebuf.end;
217         }
218         end_column += w;
219 }
220
221 public POSITION line_position(void)
222 {
223         return line_pos;
224 }
225
226 /*
227  * Rewind the line buffer.
228  */
229 public void prewind(void)
230 {
231         int ax;
232
233         linebuf.print = 6; /* big enough for longest UTF-8 sequence */
234         linebuf.pfx_end = 0;
235         for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++)
236         {
237                 linebuf.buf[linebuf.end] = '\0';
238                 linebuf.attr[linebuf.end] = 0;
239         }
240
241         end_column = 0;
242         right_curr = 0;
243         right_column = 0;
244         cshift = 0;
245         overstrike = 0;
246         last_overstrike = AT_NORMAL;
247         mbc_buf_len = 0;
248         is_null_line = 0;
249         pendc = '\0';
250         in_hilite = 0;
251         ansi_in_line = 0;
252         hlink_in_line = 0;
253         line_mark_attr = 0;
254         line_pos = NULL_POSITION;
255         xbuf_reset(&shifted_ansi);
256         xbuf_reset(&last_ansi);
257         for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
258                 xbuf_reset(&last_ansis[ax]);
259         curr_last_ansi = 0;
260 }
261
262 /*
263  * Set a character in the line buffer.
264  */
265 static void set_linebuf(int n, char ch, int attr)
266 {
267         if (n >= size_linebuf)
268         {
269                 /*
270                  * Won't fit in line buffer.
271                  * Try to expand it.
272                  */
273                 if (expand_linebuf())
274                         return;
275         }
276         linebuf.buf[n] = ch;
277         linebuf.attr[n] = attr;
278 }
279
280 /*
281  * Append a character to the line buffer.
282  */
283 static void add_linebuf(char ch, int attr, int w)
284 {
285         set_linebuf(linebuf.end++, ch, attr);
286         inc_end_column(w);
287 }
288
289 /*
290  * Append a string to the line buffer.
291  */
292 static void addstr_linebuf(char *s, int attr, int cw)
293 {
294         for ( ;  *s != '\0';  s++)
295                 add_linebuf(*s, attr, cw);
296 }
297
298 /*
299  * Set a character in the line prefix buffer.
300  */
301 static void set_pfx(int n, char ch, int attr)
302 {
303         linebuf.pfx[n] = ch;
304         linebuf.pfx_attr[n] = attr;
305 }
306
307 /*
308  * Append a character to the line prefix buffer.
309  */
310 static void add_pfx(char ch, int attr)
311 {
312         set_pfx(linebuf.pfx_end++, ch, attr);
313 }
314
315 /*
316  * Insert the status column and line number into the line buffer.
317  */
318 public void plinestart(POSITION pos)
319 {
320         LINENUM linenum = 0;
321         int i;
322
323         if (linenums == OPT_ONPLUS)
324         {
325                 /*
326                  * Get the line number and put it in the current line.
327                  * {{ Note: since find_linenum calls forw_raw_line,
328                  *    it may seek in the input file, requiring the caller 
329                  *    of plinestart to re-seek if necessary. }}
330                  * {{ Since forw_raw_line modifies linebuf, we must
331                  *    do this first, before storing anything in linebuf. }}
332                  */
333                 linenum = find_linenum(pos);
334         }
335
336         /*
337          * Display a status column if the -J option is set.
338          */
339         if (status_col || status_line)
340         {
341                 char c = posmark(pos);
342                 if (c != 0)
343                         line_mark_attr = AT_HILITE|AT_COLOR_MARK;
344                 else if (start_attnpos != NULL_POSITION &&
345                          pos >= start_attnpos && pos <= end_attnpos)
346                         line_mark_attr = AT_HILITE|AT_COLOR_ATTN;
347                 if (status_col)
348                 {
349                         add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */
350                         while (linebuf.pfx_end < status_col_width)
351                                 add_pfx(' ', AT_NORMAL);
352                 }
353         }
354
355         /*
356          * Display the line number at the start of each line
357          * if the -N option is set.
358          */
359         if (linenums == OPT_ONPLUS)
360         {
361                 char buf[INT_STRLEN_BOUND(linenum) + 2];
362                 int len;
363
364                 linenum = vlinenum(linenum);
365                 if (linenum == 0)
366                         len = 0;
367                 else
368                 {
369                         linenumtoa(linenum, buf, 10);
370                         len = (int) strlen(buf);
371                 }
372                 for (i = 0; i < linenum_width - len; i++)
373                         add_pfx(' ', AT_NORMAL);
374                 for (i = 0; i < len; i++)
375                         add_pfx(buf[i], AT_BOLD|AT_COLOR_LINENUM);
376                 add_pfx(' ', AT_NORMAL);
377         }
378         end_column = linebuf.pfx_end;
379 }
380
381 /*
382  * Return the width of the line prefix (status column and line number).
383  * {{ Actual line number can be wider than linenum_width. }}
384  */
385 public int line_pfx_width(void)
386 {
387         int width = 0;
388         if (status_col)
389                 width += status_col_width;
390         if (linenums == OPT_ONPLUS)
391                 width += linenum_width + 1;
392         return width;
393 }
394
395 /*
396  * Shift line left so that the last char is just to the left
397  * of the first visible column.
398  */
399 public void pshift_all(void)
400 {
401         int i;
402         for (i = linebuf.print;  i < linebuf.end;  i++)
403                 if (linebuf.attr[i] == AT_ANSI)
404                         xbuf_add_byte(&shifted_ansi, (unsigned char) linebuf.buf[i]);
405         linebuf.end = linebuf.print;
406         end_column = linebuf.pfx_end;
407 }
408
409 /*
410  * Return the printing width of the start (enter) sequence
411  * for a given character attribute.
412  */
413 static int attr_swidth(int a)
414 {
415         int w = 0;
416
417         a = apply_at_specials(a);
418
419         if (a & AT_UNDERLINE)
420                 w += ul_s_width;
421         if (a & AT_BOLD)
422                 w += bo_s_width;
423         if (a & AT_BLINK)
424                 w += bl_s_width;
425         if (a & AT_STANDOUT)
426                 w += so_s_width;
427
428         return w;
429 }
430
431 /*
432  * Return the printing width of the end (exit) sequence
433  * for a given character attribute.
434  */
435 static int attr_ewidth(int a)
436 {
437         int w = 0;
438
439         a = apply_at_specials(a);
440
441         if (a & AT_UNDERLINE)
442                 w += ul_e_width;
443         if (a & AT_BOLD)
444                 w += bo_e_width;
445         if (a & AT_BLINK)
446                 w += bl_e_width;
447         if (a & AT_STANDOUT)
448                 w += so_e_width;
449
450         return w;
451 }
452
453 /*
454  * Return the printing width of a given character and attribute,
455  * if the character were added after prev_ch.
456  * Adding a character with a given attribute may cause an enter or exit
457  * attribute sequence to be inserted, so this must be taken into account.
458  */
459 public int pwidth(LWCHAR ch, int a, LWCHAR prev_ch, int prev_a)
460 {
461         int w;
462
463         if (ch == '\b')
464         {
465                 /*
466                  * Backspace moves backwards one or two positions.
467                  */
468                 if (prev_a & (AT_ANSI|AT_BINARY))
469                         return strlen(prchar('\b'));
470                 return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
471         }
472
473         if (!utf_mode || is_ascii_char(ch))
474         {
475                 if (control_char((char)ch))
476                 {
477                         /*
478                          * Control characters do unpredictable things,
479                          * so we don't even try to guess; say it doesn't move.
480                          * This can only happen if the -r flag is in effect.
481                          */
482                         return (0);
483                 }
484         } else
485         {
486                 if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
487                 {
488                         /*
489                          * Composing and combining chars take up no space.
490                          *
491                          * Some terminals, upon failure to compose a
492                          * composing character with the character(s) that
493                          * precede(s) it will actually take up one end_column
494                          * for the composing character; there isn't much
495                          * we could do short of testing the (complex)
496                          * composition process ourselves and printing
497                          * a binary representation when it fails.
498                          */
499                         return (0);
500                 }
501         }
502
503         /*
504          * Other characters take one or two columns,
505          * plus the width of any attribute enter/exit sequence.
506          */
507         w = 1;
508         if (is_wide_char(ch))
509                 w++;
510         if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a))
511                 w += attr_ewidth(linebuf.attr[linebuf.end-1]);
512         if (apply_at_specials(a) != AT_NORMAL &&
513             (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a)))
514                 w += attr_swidth(a);
515         return (w);
516 }
517
518 /*
519  * Delete to the previous base character in the line buffer.
520  */
521 static int backc(void)
522 {
523         LWCHAR ch;
524         char *p;
525
526         if (linebuf.end == 0)
527                 return (0);
528         p = &linebuf.buf[linebuf.end];
529         ch = step_char(&p, -1, linebuf.buf);
530         /* Skip back to the next nonzero-width char. */
531         while (p > linebuf.buf)
532         {
533                 LWCHAR prev_ch;
534                 int width;
535                 linebuf.end = (int) (p - linebuf.buf);
536                 prev_ch = step_char(&p, -1, linebuf.buf);
537                 width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]);
538                 end_column -= width;
539                 /* {{ right_column? }} */
540                 if (width > 0)
541                         break;
542                 ch = prev_ch;
543         }
544         return (1);
545 }
546
547 /*
548  * Preserve the current position in the line buffer (for word wrapping).
549  */
550 public void savec(void)
551 {
552         saved_line_end = linebuf.end;
553         saved_end_column = end_column;
554 }
555
556 /*
557  * Restore the position in the line buffer (start of line for word wrapping).
558  */
559 public void loadc(void)
560 {
561         linebuf.end = saved_line_end;
562         end_column = saved_end_column;
563 }
564
565 /*
566  * Is a character the end of an ANSI escape sequence?
567  */
568 public int is_ansi_end(LWCHAR ch)
569 {
570         if (!is_ascii_char(ch))
571                 return (0);
572         return (strchr(end_ansi_chars, (char) ch) != NULL);
573 }
574
575 /*
576  * Can a char appear in an ANSI escape sequence, before the end char?
577  */
578 public int is_ansi_middle(LWCHAR ch)
579 {
580         if (!is_ascii_char(ch))
581                 return (0);
582         if (is_ansi_end(ch))
583                 return (0);
584         return (strchr(mid_ansi_chars, (char) ch) != NULL);
585 }
586
587 /*
588  * Skip past an ANSI escape sequence.
589  * pp is initially positioned just after the CSI_START char.
590  */
591 public void skip_ansi(struct ansi_state *pansi, char **pp, constant char *limit)
592 {
593         LWCHAR c;
594         do {
595                 c = step_char(pp, +1, limit);
596         } while (*pp < limit && ansi_step(pansi, c) == ANSI_MID);
597         /* Note that we discard final char, for which is_ansi_end is true. */
598 }
599
600 /*
601  * Determine if a character starts an ANSI escape sequence.
602  * If so, return an ansi_state struct; otherwise return NULL.
603  */
604 public struct ansi_state * ansi_start(LWCHAR ch)
605 {
606         struct ansi_state *pansi;
607
608         if (!IS_CSI_START(ch))
609                 return NULL;
610         pansi = ecalloc(1, sizeof(struct ansi_state));
611         pansi->hindex = 0;
612         pansi->hlink = 0;
613         pansi->prev_esc = 0;
614         return pansi;
615 }
616
617 /*
618  * Determine whether the next char in an ANSI escape sequence
619  * ends the sequence.
620  */
621 public int ansi_step(struct ansi_state *pansi, LWCHAR ch)
622 {
623         if (pansi->hlink)
624         {
625                 /* Hyperlink ends with \7 or ESC-backslash. */
626                 if (ch == '\7')
627                         return ANSI_END;
628                 if (pansi->prev_esc)
629                         return (ch == '\\') ? ANSI_END : ANSI_ERR;
630                 pansi->prev_esc = (ch == ESC);
631                 return ANSI_MID;
632         }
633         if (pansi->hindex >= 0)
634         {
635                 static char hlink_prefix[] = ESCS "]8;";
636                 if (ch == hlink_prefix[pansi->hindex] ||
637                     (pansi->hindex == 0 && IS_CSI_START(ch)))
638                 {
639                         pansi->hindex++;
640                         if (hlink_prefix[pansi->hindex] == '\0')
641                                 pansi->hlink = 1; /* now processing hyperlink addr */
642                         return ANSI_MID;
643                 }
644                 pansi->hindex = -1; /* not a hyperlink */
645         }
646         /* Check for SGR sequences */
647         if (is_ansi_middle(ch))
648                 return ANSI_MID;
649         if (is_ansi_end(ch))
650                 return ANSI_END;
651         return ANSI_ERR;
652 }
653
654 /*
655  * Free an ansi_state structure.
656  */
657 public void ansi_done(struct ansi_state *pansi)
658 {
659         free(pansi);
660 }
661
662 /*
663  * Will w characters in attribute a fit on the screen?
664  */
665 static int fits_on_screen(int w, int a)
666 {
667         if (ctldisp == OPT_ON)
668                 /* We're not counting, so say that everything fits. */
669                 return 1;
670         return (end_column - cshift + w + attr_ewidth(a) <= sc_width);
671 }
672
673 /*
674  * Append a character and attribute to the line buffer.
675  */
676 #define STORE_CHAR(ch,a,rep,pos) \
677         do { \
678                 if (store_char((ch),(a),(rep),(pos))) return (1); \
679         } while (0)
680
681 static int store_char(LWCHAR ch, int a, char *rep, POSITION pos)
682 {
683         int w;
684         int i;
685         int replen;
686         char cs;
687
688         i = (a & (AT_UNDERLINE|AT_BOLD));
689         if (i != AT_NORMAL)
690                 last_overstrike = i;
691
692 #if HILITE_SEARCH
693         {
694                 int matches;
695                 int resend_last = 0;
696                 int hl_attr = 0;
697
698                 if (pos == NULL_POSITION)
699                 {
700                         /* Color the prompt unless it has ansi sequences in it. */
701                         hl_attr = ansi_in_line ? 0 : AT_STANDOUT|AT_COLOR_PROMPT;
702                 } else if (a != AT_ANSI)
703                 {
704                         hl_attr = is_hilited_attr(pos, pos+1, 0, &matches);
705                         if (hl_attr == 0 && status_line)
706                                 hl_attr = line_mark_attr;
707                 }
708                 if (hl_attr)
709                 {
710                         /*
711                          * This character should be highlighted.
712                          * Override the attribute passed in.
713                          */
714                         a |= hl_attr;
715                         if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite)
716                                 highest_hilite = pos;
717                         in_hilite = 1;
718                 } else 
719                 {
720                         if (in_hilite)
721                         {
722                                 /*
723                                  * This is the first non-hilited char after a hilite.
724                                  * Resend the last ANSI seq to restore color.
725                                  */
726                                 resend_last = 1;
727                         }
728                         in_hilite = 0;
729                 }
730                 if (resend_last)
731                 {
732                         int ai;
733                         for (ai = 0;  ai < NUM_LAST_ANSIS;  ai++)
734                         {
735                                 int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS;
736                                 for (i = 0;  i < last_ansis[ax].end;  i++)
737                                         STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos);
738                         }
739                 }
740         }
741 #endif
742
743         if (a == AT_ANSI) {
744                 w = 0;
745         } else {
746                 char *p = &linebuf.buf[linebuf.end];
747                 LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
748                 int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0;
749                 w = pwidth(ch, a, prev_ch, prev_a);
750         }
751
752         if (!fits_on_screen(w, a))
753                 return (1);
754
755         if (rep == NULL)
756         {
757                 cs = (char) ch;
758                 rep = &cs;
759                 replen = 1;
760         } else
761         {
762                 replen = utf_len(rep[0]);
763         }
764
765         if (cshift == hshift)
766         {
767                 if (line_pos == NULL_POSITION)
768                         line_pos = pos;
769                 if (shifted_ansi.end > 0)
770                 {
771                         /* Copy shifted ANSI sequences to beginning of line. */
772                         for (i = 0;  i < shifted_ansi.end;  i++)
773                                 add_linebuf(shifted_ansi.data[i], AT_ANSI, 0);
774                         xbuf_reset(&shifted_ansi);
775                 }
776         }
777
778         /* Add the char to the buf, even if we will left-shift it next. */
779         inc_end_column(w);
780         for (i = 0;  i < replen;  i++)
781                 add_linebuf(*rep++, a, 0);
782
783         if (cshift < hshift)
784         {
785                 /* We haven't left-shifted enough yet. */
786                 if (a == AT_ANSI)
787                         xbuf_add_byte(&shifted_ansi, (unsigned char) ch); /* Save ANSI attributes */
788                 if (linebuf.end > linebuf.print)
789                 {
790                         /* Shift left enough to put last byte of this char at print-1. */
791                         int i;
792                         for (i = 0; i < linebuf.print; i++)
793                         {
794                                 linebuf.buf[i] = linebuf.buf[i+replen];
795                                 linebuf.attr[i] = linebuf.attr[i+replen];
796                         }
797                         linebuf.end -= replen;
798                         cshift += w;
799                         /*
800                          * If the char we just left-shifted was double width,
801                          * the 2 spaces we shifted may be too much.
802                          * Represent the "half char" at start of line with a highlighted space.
803                          */
804                         while (cshift > hshift)
805                         {
806                                 add_linebuf(' ', rscroll_attr, 0);
807                                 cshift--;
808                         }
809                 }
810         }
811         return (0);
812 }
813
814 #define STORE_STRING(s,a,pos) \
815         do { if (store_string((s),(a),(pos))) return (1); } while (0)
816
817 static int store_string(char *s, int a, POSITION pos)
818 {
819         if (!fits_on_screen(strlen(s), a))
820                 return 1;
821         for ( ;  *s != 0;  s++)
822                 STORE_CHAR(*s, a, NULL, pos);
823         return 0;
824 }
825
826 /*
827  * Append a tab to the line buffer.
828  * Store spaces to represent the tab.
829  */
830 #define STORE_TAB(a,pos) \
831         do { if (store_tab((a),(pos))) return (1); } while (0)
832
833 static int store_tab(int attr, POSITION pos)
834 {
835         int to_tab = end_column - linebuf.pfx_end;
836
837         if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
838                 to_tab = tabdefault -
839                      ((to_tab - tabstops[ntabstops-1]) % tabdefault);
840         else
841         {
842                 int i;
843                 for (i = ntabstops - 2;  i >= 0;  i--)
844                         if (to_tab >= tabstops[i])
845                                 break;
846                 to_tab = tabstops[i+1] - to_tab;
847         }
848
849         do {
850                 STORE_CHAR(' ', attr, " ", pos);
851         } while (--to_tab > 0);
852         return 0;
853 }
854
855 #define STORE_PRCHAR(c, pos) \
856         do { if (store_prchar((c), (pos))) return 1; } while (0)
857
858 static int store_prchar(LWCHAR c, POSITION pos)
859 {
860         /*
861          * Convert to printable representation.
862          */
863         STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos);
864         return 0;
865 }
866
867 static int flush_mbc_buf(POSITION pos)
868 {
869         int i;
870
871         for (i = 0; i < mbc_buf_index; i++)
872                 if (store_prchar(mbc_buf[i], pos))
873                         return mbc_buf_index - i;
874         return 0;
875 }
876
877 /*
878  * Append a character to the line buffer.
879  * Expand tabs into spaces, handle underlining, boldfacing, etc.
880  * Returns 0 if ok, 1 if couldn't fit in buffer.
881  */
882 public int pappend(int c, POSITION pos)
883 {
884         int r;
885
886         if (pendc)
887         {
888                 if (c == '\r' && pendc == '\r')
889                         return (0);
890                 if (do_append(pendc, NULL, pendpos))
891                         /*
892                          * Oops.  We've probably lost the char which
893                          * was in pendc, since caller won't back up.
894                          */
895                         return (1);
896                 pendc = '\0';
897         }
898
899         if (c == '\r' && (proc_return == OPT_ON || (bs_mode == BS_SPECIAL && proc_return == OPT_OFF)))
900         {
901                 if (mbc_buf_len > 0)  /* utf_mode must be on. */
902                 {
903                         /* Flush incomplete (truncated) sequence. */
904                         r = flush_mbc_buf(mbc_pos);
905                         mbc_buf_index = r + 1;
906                         mbc_buf_len = 0;
907                         if (r)
908                                 return (mbc_buf_index);
909                 }
910
911                 /*
912                  * Don't put the CR into the buffer until we see 
913                  * the next char.  If the next char is a newline,
914                  * discard the CR.
915                  */
916                 pendc = c;
917                 pendpos = pos;
918                 return (0);
919         }
920
921         if (!utf_mode)
922         {
923                 r = do_append(c, NULL, pos);
924         } else
925         {
926                 /* Perform strict validation in all possible cases. */
927                 if (mbc_buf_len == 0)
928                 {
929                 retry:
930                         mbc_buf_index = 1;
931                         *mbc_buf = c;
932                         if (IS_ASCII_OCTET(c))
933                                 r = do_append(c, NULL, pos);
934                         else if (IS_UTF8_LEAD(c))
935                         {
936                                 mbc_buf_len = utf_len(c);
937                                 mbc_pos = pos;
938                                 return (0);
939                         } else
940                                 /* UTF8_INVALID or stray UTF8_TRAIL */
941                                 r = flush_mbc_buf(pos);
942                 } else if (IS_UTF8_TRAIL(c))
943                 {
944                         mbc_buf[mbc_buf_index++] = c;
945                         if (mbc_buf_index < mbc_buf_len)
946                                 return (0);
947                         if (is_utf8_well_formed(mbc_buf, mbc_buf_index))
948                                 r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
949                         else
950                                 /* Complete, but not shortest form, sequence. */
951                                 mbc_buf_index = r = flush_mbc_buf(mbc_pos);
952                         mbc_buf_len = 0;
953                 } else
954                 {
955                         /* Flush incomplete (truncated) sequence.  */
956                         r = flush_mbc_buf(mbc_pos);
957                         mbc_buf_index = r + 1;
958                         mbc_buf_len = 0;
959                         /* Handle new char.  */
960                         if (!r)
961                                 goto retry;
962                 }
963         }
964         if (r)
965         {
966                 /* How many chars should caller back up? */
967                 r = (!utf_mode) ? 1 : mbc_buf_index;
968         }
969         return (r);
970 }
971
972 static int store_control_char(LWCHAR ch, char *rep, POSITION pos)
973 {
974         if (ctldisp == OPT_ON)
975         {
976                 /* Output the character itself. */
977                 STORE_CHAR(ch, AT_NORMAL, rep, pos);
978         } else 
979         {
980                 /* Output a printable representation of the character. */
981                 STORE_PRCHAR((char) ch, pos);
982         }
983         return (0);
984 }
985
986 static int store_ansi(LWCHAR ch, char *rep, POSITION pos)
987 {
988         switch (ansi_step(line_ansi, ch))
989         {
990         case ANSI_MID:
991                 STORE_CHAR(ch, AT_ANSI, rep, pos);
992                 if (line_ansi->hlink)
993                         hlink_in_line = 1;
994                 xbuf_add_byte(&last_ansi, (unsigned char) ch);
995                 break;
996         case ANSI_END:
997                 STORE_CHAR(ch, AT_ANSI, rep, pos);
998                 ansi_done(line_ansi);
999                 line_ansi = NULL;
1000                 xbuf_add_byte(&last_ansi, (unsigned char) ch);
1001                 xbuf_set(&last_ansis[curr_last_ansi], &last_ansi);
1002                 xbuf_reset(&last_ansi);
1003                 curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS;
1004                 break;
1005         case ANSI_ERR:
1006                 {
1007                         /* Remove whole unrecognized sequence.  */
1008                         char *start = (cshift < hshift) ? xbuf_char_data(&shifted_ansi): linebuf.buf;
1009                         int *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end;
1010                         char *p = start + *end;
1011                         LWCHAR bch;
1012                         do {
1013                                 bch = step_char(&p, -1, start);
1014                         } while (p > start && !IS_CSI_START(bch));
1015                         *end = (int) (p - start);
1016                 }
1017                 xbuf_reset(&last_ansi);
1018                 ansi_done(line_ansi);
1019                 line_ansi = NULL;
1020                 break;
1021         }
1022         return (0);
1023
1024
1025 static int store_bs(LWCHAR ch, char *rep, POSITION pos)
1026 {
1027         if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF))
1028                 return store_control_char(ch, rep, pos);
1029         if (linebuf.end > 0 &&
1030                 ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') ||
1031              (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY))))
1032                 STORE_PRCHAR('\b', pos);
1033         else if (proc_backspace == OPT_OFF && bs_mode == BS_NORMAL)
1034                 STORE_CHAR(ch, AT_NORMAL, NULL, pos);
1035         else if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF))
1036                 overstrike = backc();
1037         return 0;
1038 }
1039
1040 static int do_append(LWCHAR ch, char *rep, POSITION pos)
1041 {
1042         int a = AT_NORMAL;
1043         int in_overstrike = overstrike;
1044
1045         if (ctldisp == OPT_ONPLUS && line_ansi == NULL)
1046         {
1047                 line_ansi = ansi_start(ch);
1048                 if (line_ansi != NULL)
1049                         ansi_in_line = 1;
1050         }
1051
1052         overstrike = 0;
1053         if (line_ansi != NULL)
1054                 return store_ansi(ch, rep, pos);
1055
1056         if (ch == '\b')
1057                 return store_bs(ch, rep, pos);
1058
1059         if (in_overstrike > 0)
1060         {
1061                 /*
1062                  * Overstrike the character at the current position
1063                  * in the line buffer.  This will cause either 
1064                  * underline (if a "_" is overstruck), 
1065                  * bold (if an identical character is overstruck),
1066                  * or just replacing the character in the buffer.
1067                  */
1068                 LWCHAR prev_ch;
1069                 overstrike = utf_mode ? -1 : 0;
1070                 if (utf_mode)
1071                 {
1072                         /* To be correct, this must be a base character.  */
1073                         prev_ch = get_wchar(&linebuf.buf[linebuf.end]);
1074                 } else
1075                 {
1076                         prev_ch = (unsigned char) linebuf.buf[linebuf.end];
1077                 }
1078                 a = linebuf.attr[linebuf.end];
1079                 if (ch == prev_ch)
1080                 {
1081                         /*
1082                          * Overstriking a char with itself means make it bold.
1083                          * But overstriking an underscore with itself is
1084                          * ambiguous.  It could mean make it bold, or
1085                          * it could mean make it underlined.
1086                          * Use the previous overstrike to resolve it.
1087                          */
1088                         if (ch == '_')
1089                         {
1090                                 if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
1091                                         a |= (AT_BOLD|AT_UNDERLINE);
1092                                 else if (last_overstrike != AT_NORMAL)
1093                                         a |= last_overstrike;
1094                                 else
1095                                         a |= AT_BOLD;
1096                         } else
1097                                 a |= AT_BOLD;
1098                 } else if (ch == '_')
1099                 {
1100                         a |= AT_UNDERLINE;
1101                         ch = prev_ch;
1102                         rep = &linebuf.buf[linebuf.end];
1103                 } else if (prev_ch == '_')
1104                 {
1105                         a |= AT_UNDERLINE;
1106                 }
1107                 /* Else we replace prev_ch, but we keep its attributes.  */
1108         } else if (in_overstrike < 0)
1109         {
1110                 if (   is_composing_char(ch)
1111                     || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch))
1112                         /* Continuation of the same overstrike.  */
1113                         a = last_overstrike;
1114                 else
1115                         overstrike = 0;
1116         }
1117
1118         if (ch == '\t')
1119         {
1120                 /*
1121                  * Expand a tab into spaces.
1122                  */
1123                 if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF))
1124                         return store_control_char(ch, rep, pos);
1125                 STORE_TAB(a, pos);
1126                 return (0);
1127         }
1128         if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch))
1129         {
1130                 return store_control_char(ch, rep, pos);
1131         } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
1132         {
1133                 STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1134         } else
1135         {
1136                 STORE_CHAR(ch, a, rep, pos);
1137         }
1138         return (0);
1139 }
1140
1141 /*
1142  *
1143  */
1144 public int pflushmbc(void)
1145 {
1146         int r = 0;
1147
1148         if (mbc_buf_len > 0)
1149         {
1150                 /* Flush incomplete (truncated) sequence.  */
1151                 r = flush_mbc_buf(mbc_pos);
1152                 mbc_buf_len = 0;
1153         }
1154         return r;
1155 }
1156
1157 /*
1158  * Switch to normal attribute at end of line.
1159  */
1160 static void add_attr_normal(void)
1161 {
1162         if (ctldisp != OPT_ONPLUS || !is_ansi_end('m'))
1163                 return;
1164         addstr_linebuf("\033[m", AT_ANSI, 0);
1165         if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */
1166                 addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0);
1167 }
1168
1169 /*
1170  * Terminate the line in the line buffer.
1171  */
1172 public void pdone(int endline, int chopped, int forw)
1173 {
1174         (void) pflushmbc();
1175
1176         if (pendc && (pendc != '\r' || !endline))
1177                 /*
1178                  * If we had a pending character, put it in the buffer.
1179                  * But discard a pending CR if we are at end of line
1180                  * (that is, discard the CR in a CR/LF sequence).
1181                  */
1182                 (void) do_append(pendc, NULL, pendpos);
1183
1184         if (chopped && rscroll_char)
1185         {
1186                 /*
1187                  * Display the right scrolling char.
1188                  * If we've already filled the rightmost screen char 
1189                  * (in the buffer), overwrite it.
1190                  */
1191                 if (end_column >= sc_width + cshift)
1192                 {
1193                         /* We've already written in the rightmost char. */
1194                         end_column = right_column;
1195                         linebuf.end = right_curr;
1196                 }
1197                 add_attr_normal();
1198                 while (end_column < sc_width-1 + cshift) 
1199                 {
1200                         /*
1201                          * Space to last (rightmost) char on screen.
1202                          * This may be necessary if the char we overwrote
1203                          * was double-width.
1204                          */
1205                         add_linebuf(' ', rscroll_attr, 1);
1206                 }
1207                 /* Print rscroll char. It must be single-width. */
1208                 add_linebuf(rscroll_char, rscroll_attr, 1);
1209         } else
1210         {
1211                 add_attr_normal();
1212         }
1213
1214         /*
1215          * If we're coloring a status line, fill out the line with spaces.
1216          */
1217         if (status_line && line_mark_attr != 0) {
1218                 while (end_column +1 < sc_width + cshift)
1219                         add_linebuf(' ', line_mark_attr, 1);
1220         }
1221
1222         /*
1223          * Add a newline if necessary,
1224          * and append a '\0' to the end of the line.
1225          * We output a newline if we're not at the right edge of the screen,
1226          * or if the terminal doesn't auto wrap,
1227          * or if this is really the end of the line AND the terminal ignores
1228          * a newline at the right edge.
1229          * (In the last case we don't want to output a newline if the terminal 
1230          * doesn't ignore it since that would produce an extra blank line.
1231          * But we do want to output a newline if the terminal ignores it in case
1232          * the next line is blank.  In that case the single newline output for
1233          * that blank line would be ignored!)
1234          */
1235         if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON)
1236         {
1237                 add_linebuf('\n', AT_NORMAL, 0);
1238         } 
1239         else if (ignaw && end_column >= sc_width + cshift && forw)
1240         {
1241                 /*
1242                  * Terminals with "ignaw" don't wrap until they *really* need
1243                  * to, i.e. when the character *after* the last one to fit on a
1244                  * line is output. But they are too hard to deal with when they
1245                  * get in the state where a full screen width of characters
1246                  * have been output but the cursor is sitting on the right edge
1247                  * instead of at the start of the next line.
1248                  * So we nudge them into wrapping by outputting a space 
1249                  * character plus a backspace.  But do this only if moving 
1250                  * forward; if we're moving backward and drawing this line at
1251                  * the top of the screen, the space would overwrite the first
1252                  * char on the next line.  We don't need to do this "nudge" 
1253                  * at the top of the screen anyway.
1254                  */
1255                 add_linebuf(' ', AT_NORMAL, 1);
1256                 add_linebuf('\b', AT_NORMAL, -1);
1257         }
1258         set_linebuf(linebuf.end, '\0', AT_NORMAL);
1259 }
1260
1261 /*
1262  * Set an attribute on each char of the line in the line buffer.
1263  */
1264 public void set_attr_line(int a)
1265 {
1266         int i;
1267
1268         for (i = linebuf.print;  i < linebuf.end;  i++)
1269                 if ((linebuf.attr[i] & AT_COLOR) == 0 || (a & AT_COLOR) == 0)
1270                         linebuf.attr[i] |= a;
1271 }
1272
1273 /*
1274  * Set the char to be displayed in the status column.
1275  */
1276 public void set_status_col(char c, int attr)
1277 {
1278         set_pfx(0, c, attr);
1279 }
1280
1281 /*
1282  * Get a character from the current line.
1283  * Return the character as the function return value,
1284  * and the character attribute in *ap.
1285  */
1286 public int gline(int i, int *ap)
1287 {
1288         if (is_null_line)
1289         {
1290                 /*
1291                  * If there is no current line, we pretend the line is
1292                  * either "~" or "", depending on the "twiddle" flag.
1293                  */
1294                 if (twiddle)
1295                 {
1296                         if (i == 0)
1297                         {
1298                                 *ap = AT_BOLD;
1299                                 return '~';
1300                         }
1301                         --i;
1302                 }
1303                 /* Make sure we're back to AT_NORMAL before the '\n'.  */
1304                 *ap = AT_NORMAL;
1305                 return i ? '\0' : '\n';
1306         }
1307
1308         if (i < linebuf.pfx_end)
1309         {
1310                 *ap = linebuf.pfx_attr[i];
1311                 return linebuf.pfx[i];
1312         }
1313         i += linebuf.print - linebuf.pfx_end;
1314         *ap = linebuf.attr[i];
1315         return (linebuf.buf[i] & 0xFF);
1316 }
1317
1318 /*
1319  * Indicate that there is no current line.
1320  */
1321 public void null_line(void)
1322 {
1323         is_null_line = 1;
1324         cshift = 0;
1325 }
1326
1327 /*
1328  * Analogous to forw_line(), but deals with "raw lines":
1329  * lines which are not split for screen width.
1330  * {{ This is supposed to be more efficient than forw_line(). }}
1331  */
1332 public POSITION forw_raw_line(POSITION curr_pos, char **linep, int *line_lenp)
1333 {
1334         int n;
1335         int c;
1336         POSITION new_pos;
1337
1338         if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
1339                 (c = ch_forw_get()) == EOI)
1340                 return (NULL_POSITION);
1341
1342         n = 0;
1343         for (;;)
1344         {
1345                 if (c == '\n' || c == EOI || ABORT_SIGS())
1346                 {
1347                         new_pos = ch_tell();
1348                         break;
1349                 }
1350                 if (n >= size_linebuf-1)
1351                 {
1352                         if (expand_linebuf())
1353                         {
1354                                 /*
1355                                  * Overflowed the input buffer.
1356                                  * Pretend the line ended here.
1357                                  */
1358                                 new_pos = ch_tell() - 1;
1359                                 break;
1360                         }
1361                 }
1362                 linebuf.buf[n++] = c;
1363                 c = ch_forw_get();
1364         }
1365         linebuf.buf[n] = '\0';
1366         if (linep != NULL)
1367                 *linep = linebuf.buf;
1368         if (line_lenp != NULL)
1369                 *line_lenp = n;
1370         return (new_pos);
1371 }
1372
1373 /*
1374  * Analogous to back_line(), but deals with "raw lines".
1375  * {{ This is supposed to be more efficient than back_line(). }}
1376  */
1377 public POSITION back_raw_line(POSITION curr_pos, char **linep, int *line_lenp)
1378 {
1379         int n;
1380         int c;
1381         POSITION new_pos;
1382
1383         if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
1384                 ch_seek(curr_pos-1))
1385                 return (NULL_POSITION);
1386
1387         n = size_linebuf;
1388         linebuf.buf[--n] = '\0';
1389         for (;;)
1390         {
1391                 c = ch_back_get();
1392                 if (c == '\n' || ABORT_SIGS())
1393                 {
1394                         /*
1395                          * This is the newline ending the previous line.
1396                          * We have hit the beginning of the line.
1397                          */
1398                         new_pos = ch_tell() + 1;
1399                         break;
1400                 }
1401                 if (c == EOI)
1402                 {
1403                         /*
1404                          * We have hit the beginning of the file.
1405                          * This must be the first line in the file.
1406                          * This must, of course, be the beginning of the line.
1407                          */
1408                         new_pos = ch_zero();
1409                         break;
1410                 }
1411                 if (n <= 0)
1412                 {
1413                         int old_size_linebuf = size_linebuf;
1414                         char *fm;
1415                         char *to;
1416                         if (expand_linebuf())
1417                         {
1418                                 /*
1419                                  * Overflowed the input buffer.
1420                                  * Pretend the line ended here.
1421                                  */
1422                                 new_pos = ch_tell() + 1;
1423                                 break;
1424                         }
1425                         /*
1426                          * Shift the data to the end of the new linebuf.
1427                          */
1428                         for (fm = linebuf.buf + old_size_linebuf - 1,
1429                               to = linebuf.buf + size_linebuf - 1;
1430                              fm >= linebuf.buf;  fm--, to--)
1431                                 *to = *fm;
1432                         n = size_linebuf - old_size_linebuf;
1433                 }
1434                 linebuf.buf[--n] = c;
1435         }
1436         if (linep != NULL)
1437                 *linep = &linebuf.buf[n];
1438         if (line_lenp != NULL)
1439                 *line_lenp = size_linebuf - 1 - n;
1440         return (new_pos);
1441 }
1442
1443 /*
1444  * Skip cols printable columns at the start of line.
1445  * Return number of bytes skipped.
1446  */
1447 public int skip_columns(int cols, char **linep, int *line_lenp)
1448 {
1449         char *line = *linep;
1450         char *eline = line + *line_lenp;
1451         LWCHAR pch = 0;
1452         int bytes;
1453
1454         while (cols > 0 && line < eline)
1455         {
1456                 LWCHAR ch = step_char(&line, +1, eline);
1457                 struct ansi_state *pansi = ansi_start(ch);
1458                 if (pansi != NULL)
1459                 {
1460                         skip_ansi(pansi, &line, eline);
1461                         ansi_done(pansi);
1462                         pch = 0;
1463                 } else
1464                 {
1465                         int w = pwidth(ch, 0, pch, 0);
1466                         cols -= w;
1467                         pch = ch;
1468                 }
1469         }
1470         bytes = line - *linep;
1471         *linep = line;
1472         *line_lenp -= bytes;
1473         return (bytes);
1474 }
1475
1476 /*
1477  * Append a string to the line buffer.
1478  */
1479 static int pappstr(constant char *str)
1480 {
1481         while (*str != '\0')
1482         {
1483                 if (pappend(*str++, NULL_POSITION))
1484                         /* Doesn't fit on screen. */
1485                         return 1;
1486         }
1487         return 0;
1488 }
1489
1490 /*
1491  * Load a string into the line buffer.
1492  * If the string is too long to fit on the screen,
1493  * truncate the beginning of the string to fit.
1494  */
1495 public void load_line(constant char *str)
1496 {
1497         int save_hshift = hshift;
1498
1499         hshift = 0;
1500         for (;;)
1501         {
1502                 prewind();
1503                 if (pappstr(str) == 0)
1504                         break;
1505                 /*
1506                  * Didn't fit on screen; increase left shift by one.
1507                  * {{ This gets very inefficient if the string
1508                  * is much longer than the screen width. }}
1509                  */
1510                 hshift += 1;
1511         }
1512         set_linebuf(linebuf.end, '\0', AT_NORMAL);
1513         hshift = save_hshift;
1514 }
1515
1516 /*
1517  * Find the shift necessary to show the end of the longest displayed line.
1518  */
1519 public int rrshift(void)
1520 {
1521         POSITION pos;
1522         int save_width;
1523         int line;
1524         int longest = 0;
1525
1526         save_width = sc_width;
1527         sc_width = INT_MAX;
1528         pos = position(TOP);
1529         for (line = 0; line < sc_height && pos != NULL_POSITION; line++)
1530         {
1531                 pos = forw_line(pos);
1532                 if (end_column > longest)
1533                         longest = end_column;
1534         }
1535         sc_width = save_width;
1536         if (longest < sc_width)
1537                 return 0;
1538         return longest - sc_width;
1539 }
1540
1541 /*
1542  * Get the color_map index associated with a given attribute.
1543  */
1544 static int lookup_color_index(int attr)
1545 {
1546         int cx;
1547         for (cx = 0;  cx < sizeof(color_map)/sizeof(*color_map);  cx++)
1548                 if (color_map[cx].attr == attr)
1549                         return cx;
1550         return -1;
1551 }
1552
1553 static int color_index(int attr)
1554 {
1555         if (use_color && (attr & AT_COLOR))
1556                 return lookup_color_index(attr & AT_COLOR);
1557         if (attr & AT_UNDERLINE)
1558                 return lookup_color_index(AT_UNDERLINE);
1559         if (attr & AT_BOLD)
1560                 return lookup_color_index(AT_BOLD);
1561         if (attr & AT_BLINK)
1562                 return lookup_color_index(AT_BLINK);
1563         if (attr & AT_STANDOUT)
1564                 return lookup_color_index(AT_STANDOUT);
1565         return -1;
1566 }
1567
1568 /*
1569  * Set the color string to use for a given attribute.
1570  */
1571 public int set_color_map(int attr, char *colorstr)
1572 {
1573         int cx = color_index(attr);
1574         if (cx < 0)
1575                 return -1;
1576         if (strlen(colorstr)+1 > sizeof(color_map[cx].color))
1577                 return -1;
1578         if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL) == CT_NULL)
1579                 return -1;
1580         strcpy(color_map[cx].color, colorstr);
1581         return 0;
1582 }
1583
1584 /*
1585  * Get the color string to use for a given attribute.
1586  */
1587 public char * get_color_map(int attr)
1588 {
1589         int cx = color_index(attr);
1590         if (cx < 0)
1591                 return NULL;
1592         return color_map[cx].color;
1593 }