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