1 /* ################################################################### */
2 /* Copyright 2015-present, Pierre Gentile (p.gen.progs@gmail.com)      */
3 /*                                                                     */
4 /* This Software is licensed under the GPL licensed Version 2,         */
5 /* please read http://www.gnu.org/copyleft/gpl.html                    */
6 /*                                                                     */
7 /* you can redistribute it and/or modify it under the terms of the GNU */
8 /* General Public License as published by the Free Software            */
9 /* Foundation; either version 2 of the License.                        */
10 /*                                                                     */
11 /* This software is distributed in the hope that it will be useful,    */
12 /* but WITHOUT ANY WARRANTY; without even the implied warranty of      */
13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   */
14 /* General Public License for more details.                            */
15 /* ################################################################### */
16 
17 #include "config.h"
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <limits.h>
21 #include <stdarg.h>
22 #include <signal.h>
23 #include <ctype.h>
24 #include <time.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <locale.h>
28 #include <langinfo.h>
29 #if (defined(__sun) && defined(__SVR4)) || defined(_AIX)
30 #include <curses.h>
31 #endif
32 #include <term.h>
33 #include <termios.h>
34 #include <regex.h>
35 #include <errno.h>
36 #include <sys/ioctl.h>
37 #include <sys/time.h>
38 #include <sys/param.h>
39 #include <wchar.h>
40 
41 #include "xmalloc.h"
42 #include "list.h"
43 #include "index.h"
44 #include "utf8.h"
45 #include "fgetc.h"
46 #include "utils.h"
47 #include "ctxopt.h"
48 #include "usage.h"
49 #include "smenu.h"
50 
51 /* ***************** */
52 /* Extern variables. */
53 /* ***************** */
54 
55 extern ll_t * tst_search_list;
56 
57 /* ***************** */
58 /* Global variables. */
59 /* ***************** */
60 
61 word_t * word_a;       /* array containing words data (size: count).      */
62 long     count = 0;    /* number of words read from stdin.                */
63 long     current;      /* index the current selection under the cursor).  */
64 long     new_current;  /* final cur. position, (used in search function). */
65 long     prev_current; /* prev. position stored when using direct access. */
66 
67 long * line_nb_of_word_a;    /* array containing the line number (from 0)   *
68                               | of each word read.                          */
69 long * first_word_in_line_a; /* array containing the index of the first     *
70                               | word of each lines.                         */
71 
72 search_mode_t search_mode     = NONE;
73 search_mode_t old_search_mode = NONE;
74 
75 int help_mode = 0; /* 1 if help is displayed else 0. */
76 
77 char * word_buffer;
78 
79 int (*my_isprint)(int);
80 
81 /* UTF-8 useful symbols. */
82 /* """"""""""""""""""""" */
83 char * left_arrow      = "\xe2\x86\x90";
84 char * up_arrow        = "\xe2\x86\x91";
85 char * right_arrow     = "\xe2\x86\x92";
86 char * down_arrow      = "\xe2\x86\x93";
87 char * vertical_bar    = "\xe2\x94\x82"; /* box drawings light vertical.      */
88 char * shift_left_sym  = "\xe2\x97\x80"; /* leftwards_arrow.                  */
89 char * shift_right_sym = "\xe2\x96\xb6"; /* rightwards_arrow.                 */
90 char * sbar_line       = "\xe2\x94\x82"; /* box_drawings_light_vertical.      */
91 char * sbar_top        = "\xe2\x94\x90"; /* box_drawings_light_down_and_left. */
92 char * sbar_down       = "\xe2\x94\x98"; /* box_drawings_light_up_and_left.   */
93 char * sbar_curs       = "\xe2\x95\x91"; /* box_drawings_double_vertical.     */
94 char * sbar_arr_up     = "\xe2\x96\xb2"; /* black_up_pointing_triangle.       */
95 char * sbar_arr_down   = "\xe2\x96\xbc"; /* black_down_pointing_triangle.     */
96 char * msg_arr_down    = "\xe2\x96\xbc"; /* black_down_pointing_triangle.     */
97 
98 /* Variables used to manage the direct access entries. */
99 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
100 daccess_t daccess;
101 char *    daccess_stack;
102 int       daccess_stack_head;
103 
104 /* Variables used for fuzzy and substring searching. */
105 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
106 long * matching_words_a;
107 long   matching_words_a_size;
108 long   matches_count;
109 long * best_matching_words_a;
110 long   best_matching_words_a_size;
111 long   best_matches_count;
112 long * alt_matching_words_a = NULL;
113 long   alt_matches_count;
114 
115 /* Variables used in signal handlers. */
116 /* """""""""""""""""""""""""""""""""" */
117 volatile sig_atomic_t got_winch        = 0;
118 volatile sig_atomic_t got_winch_alrm   = 0;
119 volatile sig_atomic_t got_help_alrm    = 0;
120 volatile sig_atomic_t got_daccess_alrm = 0;
121 volatile sig_atomic_t got_search_alrm  = 0;
122 volatile sig_atomic_t got_timeout_tick = 0;
123 volatile sig_atomic_t got_sigsegv      = 0;
124 volatile sig_atomic_t got_sigterm      = 0;
125 volatile sig_atomic_t got_sighup       = 0;
126 
127 /* Variables used when a timeout is set (option -x). */
128 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
129 timeout_t timeout;
130 char *    timeout_word;      /* printed word when the timeout type is WORD. */
131 char *    timeout_seconds;   /* string containing the number of remaining   *
132                               | seconds.                                    */
133 int       quiet_timeout = 0; /* 1 when we want no message to be displayed.  */
134 
135 /* *************** */
136 /* Help functions. */
137 /* *************** */
138 
139 /* ===================== */
140 /* Help message display. */
141 /* ===================== */
142 void
help(win_t * win,term_t * term,long last_line,toggle_t * toggles)143 help(win_t * win, term_t * term, long last_line, toggle_t * toggles)
144 {
145   int index;      /* used to identify the objects long the help line. */
146   int line   = 0; /* number of windows lines used by the help line.   */
147   int len    = 0; /* length of the help line.                         */
148   int offset = 0; /* offset from the first column of the terminal to  *
149                    | the start of the help line.                      */
150   int entries_nb; /* number of help entries to display.               */
151   int help_len;   /* total length of the help line.                   */
152 
153   struct entry_s
154   {
155     char   attr; /* r=reverse, n=normal, b=bold.                           */
156     char * str;  /* string to be displayed for an object in the help line. */
157     int    len;  /* length of one of these objects.                        */
158   };
159 
160   char * arrows = concat(left_arrow, up_arrow, right_arrow, down_arrow,
161                          (char *)0);
162 
163   struct entry_s entries[] = {
164     { 'n', "Move:", 5 }, { 'b', arrows, 4 },   { 'n', "|", 1 },
165     { 'b', "h", 1 },     { 'b', "j", 1 },      { 'b', "k", 1 },
166     { 'b', "l", 1 },     { 'n', ",", 1 },      { 'b', "PgUp", 4 },
167     { 'n', "/", 1 },     { 'b', "PgDn", 4 },   { 'n', "/", 1 },
168     { 'b', "Home", 4 },  { 'n', "/", 1 },      { 'b', "End", 3 },
169     { 'n', " ", 1 },     { 'n', "Abort:", 6 }, { 'b', "q", 1 },
170     { 'n', " ", 1 },     { 'n', "Find:", 5 },  { 'b', "/", 1 },
171     { 'n', "|", 1 },     { 'b', "\"\'", 2 },   { 'n', "|", 1 },
172     { 'b', "~*", 2 },    { 'n', "|", 1 },      { 'b', "=^", 2 },
173     { 'n', ",", 1 },     { 'b', "SP", 2 },     { 'n', "|", 1 },
174     { 'b', "nN", 2 },    { 'n', " ", 1 },      { 'n', "Select:", 7 },
175     { 'b', "CR", 2 },    { 'n', "|", 1 },      { 'b', "tTU", 3 }
176   };
177 
178   entries_nb = sizeof(entries) / sizeof(struct entry_s);
179 
180   /* Remove the last two entries if tagging is not enabled. */
181   /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
182   if (!toggles->taggable)
183     entries_nb -= 2;
184 
185   /* Get the total length of the help line. */
186   /* """""""""""""""""""""""""""""""""""""" */
187   help_len = 0;
188   for (index = 0; index < entries_nb; index++)
189     help_len += entries[index].len;
190 
191   /* Save the position of the terminal cursor so that it can be */
192   /* put back there after printing of the help line.            */
193   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
194   tputs(TPARM1(save_cursor), 1, outch);
195 
196   /* Center the help line if the -M (Middle) option is set. */
197   /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
198   if (win->offset > 0)
199     if ((offset = win->offset + win->max_width / 2 - help_len / 2) > 0)
200     {
201       int i;
202 
203       for (i = 0; i < offset; i++)
204         fputc(' ', stdout);
205     }
206 
207   /* Print the different objects forming the help line. */
208   /* """""""""""""""""""""""""""""""""""""""""""""""""" */
209   for (index = 0; index < entries_nb; index++)
210   {
211     if ((len += entries[index].len) >= term->ncolumns - 1)
212     {
213       line++;
214 
215       if (line > last_line || line == win->max_lines)
216         break;
217 
218       len = entries[index].len;
219       fputc('\n', stdout);
220     }
221 
222     switch (entries[index].attr)
223     {
224       case 'b':
225         if (term->has_bold)
226           tputs(TPARM1(enter_bold_mode), 1, outch);
227         break;
228       case 'r':
229         if (term->has_reverse)
230           tputs(TPARM1(enter_reverse_mode), 1, outch);
231         else if (term->has_standout)
232           tputs(TPARM1(enter_standout_mode), 1, outch);
233         break;
234       case 'n':
235         tputs(TPARM1(exit_attribute_mode), 1, outch);
236         break;
237     }
238     fputs(entries[index].str, stdout);
239   }
240 
241   tputs(TPARM1(exit_attribute_mode), 1, outch);
242   tputs(TPARM1(clr_eol), 1, outch);
243 
244   /* Put back the cursor to its saved position. */
245   /* """""""""""""""""""""""""""""""""""""""""" */
246   tputs(TPARM1(restore_cursor), 1, outch);
247 }
248 
249 /* *********************************** */
250 /* Attributes string parsing function. */
251 /* *********************************** */
252 
253 /* ================================= */
254 /* Decode attributes toggles if any. */
255 /* b -> bold                         */
256 /* d -> dim                          */
257 /* r -> reverse                      */
258 /* s -> standout                     */
259 /* u -> underline                    */
260 /* i -> italic                       */
261 /* l -> blink                        */
262 /*                                   */
263 /* Returns 0 if some unexpected.     */
264 /* toggle is found else 0.           */
265 /* ================================= */
266 int
decode_attr_toggles(char * s,attrib_t * attr)267 decode_attr_toggles(char * s, attrib_t * attr)
268 {
269   int rc = 1;
270 
271   attr->bold = attr->dim = attr->reverse = 0;
272   attr->standout = attr->underline = attr->italic = attr->blink = 0;
273 
274   while (*s != '\0')
275   {
276     switch (*s)
277     {
278       case 'b':
279         attr->bold   = 1;
280         attr->is_set = SET;
281         break;
282       case 'd':
283         attr->dim    = 1;
284         attr->is_set = SET;
285         break;
286       case 'r':
287         attr->reverse = 1;
288         attr->is_set  = SET;
289         break;
290       case 's':
291         attr->standout = 1;
292         attr->is_set   = SET;
293         break;
294       case 'u':
295         attr->underline = 1;
296         attr->is_set    = SET;
297         break;
298       case 'i':
299         attr->italic = 1;
300         attr->is_set = SET;
301         break;
302       case 'l':
303         attr->blink  = 1;
304         attr->is_set = SET;
305         break;
306       default:
307         rc = 0;
308         break;
309     }
310     s++;
311   }
312   return rc;
313 }
314 
315 /* =========================================================*/
316 /* Parse attributes in str in the form [fg][/bg][,toggles]  */
317 /* where:                                                   */
318 /* fg and bg are short representing a color value           */
319 /* toggles is an array of toggles (see decode_attr_toggles) */
320 /* Returns 1 on success else 0.                             */
321 /* attr will be filled by the function.                     */
322 /* =========================================================*/
323 int
parse_attr(char * str,attrib_t * attr,short max_color)324 parse_attr(char * str, attrib_t * attr, short max_color)
325 {
326   int    n;
327   char * pos;
328   char   s1[12] = { 0 };
329   char   s2[7]  = { 0 };
330   short  d1 = -1, d2 = -1;
331   int    rc = 1;
332 
333   n = sscanf(str, "%11[^,],%6s", s1, s2);
334 
335   if (n == 0)
336     return 0;
337 
338   if ((pos = strchr(s1, '/')))
339   {
340     if (pos == s1) /* s1 starts with a / */
341     {
342       d1 = -1;
343       if (sscanf(s1 + 1, "%hd", &d2) == 0)
344       {
345         d2 = -1;
346         if (n == 1)
347           return 0;
348       }
349     }
350     else if (sscanf(s1, "%hd/%hd", &d1, &d2) < 2)
351     {
352       d1 = d2 = -1;
353       if (n == 1)
354         return 0;
355     }
356   }
357   else /* no / in the first string. */
358   {
359     d2 = -1;
360     if (sscanf(s1, "%hd", &d1) == 0)
361     {
362       d1 = -1;
363       if (n == 2 || decode_attr_toggles(s1, attr) == 0)
364         return 0;
365     }
366   }
367 
368   if (d1 < -1 || d2 < -1)
369     return 0;
370 
371   if (max_color == 0)
372   {
373     attr->fg = -1;
374     attr->bg = -1;
375   }
376   else
377   {
378     attr->fg = d1;
379     attr->bg = d2;
380   }
381 
382   if (n == 2)
383     rc = decode_attr_toggles(s2, attr);
384 
385   return rc;
386 }
387 
388 /* ============================================== */
389 /* Set the terminal attributes according to attr. */
390 /* ============================================== */
391 void
apply_attr(term_t * term,attrib_t attr)392 apply_attr(term_t * term, attrib_t attr)
393 {
394   if (attr.fg >= 0)
395     set_foreground_color(term, attr.fg);
396 
397   if (attr.bg >= 0)
398     set_background_color(term, attr.bg);
399 
400   if (attr.bold > 0)
401     tputs(TPARM1(enter_bold_mode), 1, outch);
402 
403   if (attr.dim > 0)
404     tputs(TPARM1(enter_dim_mode), 1, outch);
405 
406   if (attr.reverse > 0)
407     tputs(TPARM1(enter_reverse_mode), 1, outch);
408 
409   if (attr.standout > 0)
410     tputs(TPARM1(enter_standout_mode), 1, outch);
411 
412   if (attr.underline > 0)
413     tputs(TPARM1(enter_underline_mode), 1, outch);
414 
415   if (attr.italic > 0)
416     tputs(TPARM1(enter_italics_mode), 1, outch);
417 
418   if (attr.blink > 0)
419     tputs(TPARM1(enter_blink_mode), 1, outch);
420 }
421 
422 /* ********************* */
423 /* ini parsing function. */
424 /* ********************* */
425 
426 /* ===================================================== */
427 /* Callback function called when parsing each non-header */
428 /* line of the ini file.                                 */
429 /* Returns 0 if OK, 1 if not.                            */
430 /* ===================================================== */
431 int
ini_cb(win_t * win,term_t * term,limit_t * limits,ticker_t * timers,misc_t * misc,langinfo_t * langinfo,const char * section,const char * name,char * value)432 ini_cb(win_t * win, term_t * term, limit_t * limits, ticker_t * timers,
433        misc_t * misc, langinfo_t * langinfo, const char * section,
434        const char * name, char * value)
435 {
436   int error      = 0;
437   int has_colors = (term->colors > 7);
438 
439   if (strcmp(section, "colors") == 0)
440   {
441     attrib_t v = { UNSET, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
442 
443 #define CHECK_ATTR(x)                             \
444   else if (strcmp(name, #x) == 0)                 \
445   {                                               \
446     error = !parse_attr(value, &v, term->colors); \
447     if (error)                                    \
448       goto out;                                   \
449     else                                          \
450     {                                             \
451       if (win->x##_attr.is_set != FORCED)         \
452       {                                           \
453         win->x##_attr.is_set = SET;               \
454         if (v.fg >= 0)                            \
455           win->x##_attr.fg = v.fg;                \
456         if (v.bg >= 0)                            \
457           win->x##_attr.bg = v.bg;                \
458         if (v.bold >= 0)                          \
459           win->x##_attr.bold = v.bold;            \
460         if (v.dim >= 0)                           \
461           win->x##_attr.dim = v.dim;              \
462         if (v.reverse >= 0)                       \
463           win->x##_attr.reverse = v.reverse;      \
464         if (v.standout >= 0)                      \
465           win->x##_attr.standout = v.standout;    \
466         if (v.underline >= 0)                     \
467           win->x##_attr.underline = v.underline;  \
468         if (v.italic >= 0)                        \
469           win->x##_attr.italic = v.italic;        \
470         if (v.blink >= 0)                         \
471           win->x##_attr.blink = v.blink;          \
472       }                                           \
473     }                                             \
474   }
475 
476 #define CHECK_ATT_ATTR(x, y)                            \
477   else if (strcmp(name, #x #y) == 0)                    \
478   {                                                     \
479     error = !parse_attr(value, &v, term->colors);       \
480     if (error)                                          \
481       goto out;                                         \
482     else                                                \
483     {                                                   \
484       if (win->x##_attr[y - 1].is_set != FORCED)        \
485       {                                                 \
486         win->x##_attr[y - 1].is_set = SET;              \
487         if (v.fg >= 0)                                  \
488           win->x##_attr[y - 1].fg = v.fg;               \
489         if (v.bg >= 0)                                  \
490           win->x##_attr[y - 1].bg = v.bg;               \
491         if (v.bold >= 0)                                \
492           win->x##_attr[y - 1].bold = v.bold;           \
493         if (v.dim >= 0)                                 \
494           win->x##_attr[y - 1].dim = v.dim;             \
495         if (v.reverse >= 0)                             \
496           win->x##_attr[y - 1].reverse = v.reverse;     \
497         if (v.standout >= 0)                            \
498           win->x##_attr[y - 1].standout = v.standout;   \
499         if (v.underline >= 0)                           \
500           win->x##_attr[y - 1].underline = v.underline; \
501         if (v.italic >= 0)                              \
502           win->x##_attr[y - 1].italic = v.italic;       \
503         if (v.blink >= 0)                               \
504           win->x##_attr[y - 1].blink = v.blink;         \
505       }                                                 \
506     }                                                   \
507   }
508 
509     /* [colors] section. */
510     /* """"""""""""""""" */
511     if (has_colors)
512     {
513       if (strcmp(name, "method") == 0)
514       {
515         if (strcmp(value, "classic") == 0)
516           term->color_method = 0;
517         else if (strcmp(value, "ansi") == 0)
518           term->color_method = 1;
519         else
520         {
521           error = 1;
522           goto out;
523         }
524       }
525 
526       /* clang-format off */
527       CHECK_ATTR(cursor)
528       CHECK_ATTR(bar)
529       CHECK_ATTR(shift)
530       CHECK_ATTR(message)
531       CHECK_ATTR(search_field)
532       CHECK_ATTR(search_text)
533       CHECK_ATTR(match_field)
534       CHECK_ATTR(match_text)
535       CHECK_ATTR(match_err_field)
536       CHECK_ATTR(match_err_text)
537       CHECK_ATTR(include)
538       CHECK_ATTR(exclude)
539       CHECK_ATTR(tag)
540       CHECK_ATTR(cursor_on_tag)
541       CHECK_ATTR(daccess)
542       CHECK_ATT_ATTR(special, 1)
543       CHECK_ATT_ATTR(special, 2)
544       CHECK_ATT_ATTR(special, 3)
545       CHECK_ATT_ATTR(special, 4)
546       CHECK_ATT_ATTR(special, 5)
547       CHECK_ATT_ATTR(special, 6)
548       CHECK_ATT_ATTR(special, 7)
549       CHECK_ATT_ATTR(special, 8)
550       CHECK_ATT_ATTR(special, 9)
551       /* clang-format on */
552     }
553   }
554   else if (strcmp(section, "window") == 0)
555   {
556     int v;
557 
558     /* [window] section. */
559     /* """"""""""""""""" */
560     if (strcmp(name, "lines") == 0)
561     {
562       if ((error = !(sscanf(value, "%d", &v) == 1 && v >= 0)))
563         goto out;
564       else
565         win->asked_max_lines = v;
566     }
567   }
568   else if (strcmp(section, "limits") == 0)
569   {
570     long v;
571 
572     /* [limits] section. */
573     /* """"""""""""""""" */
574     if (strcmp(name, "word_length") == 0)
575     {
576       if ((error = !(sscanf(value, "%ld", &v) == 1 && v > 0)))
577         goto out;
578       else
579         limits->word_length = v;
580     }
581     else if (strcmp(name, "words") == 0)
582     {
583       if ((error = !(sscanf(value, "%ld", &v) == 1 && v > 0)))
584         goto out;
585       else
586         limits->words = v;
587     }
588     else if (strcmp(name, "columns") == 0)
589     {
590       if ((error = !(sscanf(value, "%ld", &v) == 1 && v > 0)))
591         goto out;
592       else
593         limits->cols = v;
594     }
595   }
596   else if (strcmp(section, "timers") == 0)
597   {
598     int v;
599 
600     /* [timers] section. */
601     /* """"""""""""""""" */
602     if (strcmp(name, "help") == 0)
603     {
604       if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
605         goto out;
606       else
607         timers->help = v;
608     }
609     else if (strcmp(name, "window") == 0)
610     {
611       if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
612         goto out;
613       else
614         timers->winch = v;
615     }
616     else if (strcmp(name, "direct_access") == 0)
617     {
618       if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
619         goto out;
620       else
621         timers->direct_access = v;
622     }
623     else if (strcmp(name, "search") == 0)
624     {
625       if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
626         goto out;
627       else
628         timers->search = v;
629     }
630   }
631   else if (strcmp(section, "misc") == 0)
632   {
633     /* [misc] section. */
634     /* """"""""""""""" */
635     if (strcmp(name, "default_search_method") == 0)
636     {
637       if (misc->default_search_method == NONE)
638       {
639         if (strcmp(value, "prefix") == 0)
640           misc->default_search_method = PREFIX;
641         else if (strcmp(value, "fuzzy") == 0)
642           misc->default_search_method = FUZZY;
643         else if (strcmp(value, "substring") == 0)
644           misc->default_search_method = SUBSTRING;
645       }
646     }
647   }
648 
649 out:
650 
651   return error;
652 }
653 
654 /* ======================================================================== */
655 /* Load an .ini format file.                                                */
656 /* filename - path to a file.                                               */
657 /* report   - callback can return non-zero to stop, the callback error code */
658 /*            returned from this function.                                  */
659 /* return   - return 0 on success.                                          */
660 /*                                                                          */
661 /* This function is public domain. No copyright is claimed.                 */
662 /* Jon Mayo April 2011.                                                     */
663 /* ======================================================================== */
664 int
ini_load(const char * filename,win_t * win,term_t * term,limit_t * limits,ticker_t * timers,misc_t * misc,langinfo_t * langinfo,int (* report)(win_t * win,term_t * term,limit_t * limits,ticker_t * timers,misc_t * misc,langinfo_t * langinfo,const char * section,const char * name,char * value))665 ini_load(const char * filename, win_t * win, term_t * term, limit_t * limits,
666          ticker_t * timers, misc_t * misc, langinfo_t * langinfo,
667          int (*report)(win_t * win, term_t * term, limit_t * limits,
668                        ticker_t * timers, misc_t * misc, langinfo_t * langinfo,
669                        const char * section, const char * name, char * value))
670 {
671   char   name[64]     = "";
672   char   value[256]   = "";
673   char   section[128] = "";
674   char * s;
675   FILE * f;
676   int    cnt;
677   int    error;
678 
679   /* If the filename is empty we skip this phase and use the */
680   /* default values.                                         */
681   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
682   if (filename == NULL)
683     return 1;
684 
685   /* We do that if the file is not readable as well. */
686   /* """"""""""""""""""""""""""""""""""""""""""""""" */
687   f = fopen(filename, "r");
688   if (!f)
689     return 0; /* Returns success as the presence of this file *
690                | is optional.                                 */
691 
692   error = 0;
693 
694   /* Skip blank lines. */
695   /* """"""""""""""""" */
696   while (fscanf(f, "%*[\n]") == 1)
697   {
698   }
699 
700   while (!feof(f))
701   {
702     if (fscanf(f, " [%127[^];\n]]", section) == 1)
703     {
704       /* Do nothing. */
705       /* """"""""""" */
706     }
707     if ((cnt = fscanf(f, " %63[^=;\n] = %255[^;\n]", name, value)))
708     {
709       if (cnt == 1)
710         *value = 0;
711 
712       for (s = name + strlen(name) - 1; s > name && isspace(*s); s--)
713         *s = 0;
714 
715       for (s = value + strlen(value) - 1; s > value && isspace(*s); s--)
716         *s = 0;
717 
718       /* Callback function calling. */
719       /* """""""""""""""""""""""""" */
720       error = report(win, term, limits, timers, misc, langinfo, section, name,
721                      value);
722 
723       if (error)
724         goto out;
725     }
726     if (fscanf(f, " ;%*[^\n]"))
727     {
728       /* To silence the compiler about unused results. */
729     }
730 
731     /* Skip blank lines. */
732     /* """"""""""""""""" */
733     while (fscanf(f, "%*[\n]") == 1)
734     {
735       /* Do nothing. */
736       /* """"""""""" */
737     }
738   }
739 
740 out:
741   fclose(f);
742 
743   if (error)
744     fprintf(stderr, "Invalid entry found: %s=%s in %s.\n", name, value,
745             filename);
746 
747   return error;
748 }
749 
750 /* ======================================================= */
751 /* Return the full path on the configuration file supposed */
752 /* to be in the home directory of the user.                */
753 /* NULL is returned if the built path is too large.        */
754 /* ======================================================= */
755 char *
make_ini_path(char * name,char * base)756 make_ini_path(char * name, char * base)
757 {
758   char * path;
759   char * home;
760   long   path_max;
761   long   len;
762   char * conf;
763 
764   /* Set the prefix of the path from the environment */
765   /* base can be "HOME" or "PWD".                    */
766   /* """"""""""""""""""""""""""""""""""""""""""""""" */
767   home = getenv(base);
768 
769   if (home == NULL)
770     home = "";
771 
772   path_max = pathconf(".", _PC_PATH_MAX);
773   len      = strlen(home) + strlen(name) + 3;
774 
775   if (path_max < 0)
776     path_max = 4096; /* POSIX minimal value. */
777 
778   if (len <= path_max)
779   {
780     path = xmalloc(len);
781     conf = strrchr(name, '/');
782     if (conf != NULL)
783       conf++;
784     else
785       conf = name;
786 
787     snprintf(path, 4096, "%s/.%s", home, conf);
788   }
789   else
790     path = NULL;
791 
792   return path;
793 }
794 
795 /* ********************************* */
796 /* Functions used when sorting tags. */
797 /* ********************************* */
798 
799 /* ============================================================ */
800 /* Compare the pin order of two pinned word in the output list. */
801 /* ============================================================ */
802 int
tag_comp(void * a,void * b)803 tag_comp(void * a, void * b)
804 {
805   output_t * oa = (output_t *)a;
806   output_t * ob = (output_t *)b;
807 
808   if (oa->order == ob->order)
809     return 0;
810 
811   return (oa->order < ob->order) ? -1 : 1;
812 }
813 
814 /* ========================================================= */
815 /* Swap the values of two selected words in the output list. */
816 /* ========================================================= */
817 void
tag_swap(void * a,void * b)818 tag_swap(void * a, void * b)
819 {
820   output_t * oa = (output_t *)a;
821   output_t * ob = (output_t *)b;
822   char *     tmp_str;
823   long       tmp_order;
824 
825   tmp_str        = oa->output_str;
826   oa->output_str = ob->output_str;
827   ob->output_str = tmp_str;
828 
829   tmp_order = oa->order;
830   oa->order = ob->order;
831   ob->order = tmp_order;
832 }
833 
834 /* ****************** */
835 /* Utility functions. */
836 /* ****************** */
837 
838 /* =================================================================== */
839 /* Create a new element to be added to the tst_search_list used by the */
840 /* search mechanism.                                                   */
841 /* =================================================================== */
842 sub_tst_t *
sub_tst_new(void)843 sub_tst_new(void)
844 {
845   sub_tst_t * elem = xmalloc(sizeof(sub_tst_t));
846 
847   elem->size  = 64;
848   elem->count = 0;
849   elem->array = xmalloc(elem->size * sizeof(tst_node_t));
850 
851   return elem;
852 }
853 
854 /* ========================================= */
855 /* Emit a small (visual) beep warn the user. */
856 /* ========================================= */
857 void
my_beep(toggle_t * toggles)858 my_beep(toggle_t * toggles)
859 {
860   struct timespec ts, rem;
861   int             rc;
862 
863   if (!toggles->visual_bell)
864     fputc('\a', stdout);
865   else
866   {
867     tputs(TPARM1(cursor_visible), 1, outch);
868 
869     ts.tv_sec  = 0;
870     ts.tv_nsec = 200000000; /* 0.2s */
871 
872     errno = 0;
873     rc    = nanosleep(&ts, &rem);
874 
875     while (rc < 0 && errno == EINTR)
876     {
877       errno = 0;
878       rc    = nanosleep(&rem, &rem);
879     }
880 
881     tputs(TPARM1(cursor_invisible), 1, outch);
882   }
883 }
884 
885 /* =========================================== */
886 /* Integer verification constraint for ctxopt. */
887 /* =========================================== */
888 int
check_integer_constraint(int nb_args,char ** args,char * value,char * par)889 check_integer_constraint(int nb_args, char ** args, char * value, char * par)
890 {
891   if (!is_integer(value))
892   {
893     fprintf(stderr, "The argument of %s is not an integer: %s", par, value);
894     return 0;
895   }
896   return 1;
897 }
898 
899 /* ======================================================================== */
900 /* Update the bitmap associated with a word. This bitmap indicates the      */
901 /* positions of the UFT-8 glyphs of the search buffer in each word.         */
902 /* The disp_word function will use it to display these special characters.  */
903 /* ======================================================================== */
904 void
update_bitmaps(search_mode_t mode,search_data_t * data,bitmap_affinity_t affinity)905 update_bitmaps(search_mode_t mode, search_data_t * data,
906                bitmap_affinity_t affinity)
907 {
908   long i, j, n; /* work variables.                                        */
909 
910   long lmg; /* position of the last matching glyph of the search buffer   *
911              | in a word.                                                 */
912 
913   long sg; /* index going from lmg backward to 0 of the tested glyphs     *
914             | of the search buffer (searched glyph).                      */
915 
916   long bm_len; /* number of chars taken by the bitmask.                   */
917 
918   char * start; /* pointer on the position of the matching position       *
919                  | of the last search buffer glyph in the word.           */
920 
921   char * bm; /* the word's current bitmap.                                */
922 
923   char * str;      /* copy of the current word put in lower case.         */
924   char * str_orig; /* original version of the word.                       */
925 
926   char * first_glyph;
927 
928   char * sb_orig = data->buf; /* sb: search buffer.                       */
929   char * sb;
930   long * o    = data->utf8_off_a;   /* array of the offsets of the search *
931                                      | buffer glyphs.                     */
932   long * l    = data->utf8_len_a;   /* array of the lengths in bytes of   *
933                                      | the search buffer glyphs.          */
934   long   last = data->utf8_len - 1; /* offset of the last glyph in the    *
935                                      | search buffer.                     */
936 
937   long badness = 0; /* number of 0s between two 1s.                       */
938 
939   best_matches_count = 0;
940 
941   if (mode == FUZZY || mode == SUBSTRING)
942   {
943     first_glyph = xmalloc(5);
944 
945     if (mode == FUZZY)
946     {
947       sb = xstrdup(sb_orig);
948       utf8_strtolower(sb, sb_orig);
949     }
950     else
951       sb = sb_orig;
952 
953     for (i = 0; i < matches_count; i++)
954     {
955       n = matching_words_a[i];
956 
957       str_orig = xstrdup(word_a[n].str + daccess.flength);
958 
959       /* We need to remove the trailing spaces to use the     */
960       /* following algorithm.                                 */
961       /* .len holds the original length in bytes of the word. */
962       /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
963       rtrim(str_orig, " \t", 0);
964 
965       bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1;
966       bm     = word_a[n].bitmap;
967 
968       /* In fuzzy search mode str are converted in lower case letters */
969       /* for comparison reason.                                       */
970       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
971       if (mode == FUZZY)
972       {
973         str = xstrdup(str_orig);
974         utf8_strtolower(str, str_orig);
975       }
976       else
977         str = str_orig;
978 
979       start = str;
980       lmg   = 0;
981 
982       /* start points to the first UTF-8 glyph of the word. */
983       /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
984       while ((size_t)(start - str) < word_a[n].len - daccess.flength)
985       {
986         /* Reset the bitmap. */
987         /* """"""""""""""""" */
988         memset(bm, '\0', bm_len);
989 
990         /* Compare the glyph pointed to by start to the last glyph of */
991         /* the search buffer, the aim is to point to the first        */
992         /* occurrence of the last glyph of it.                        */
993         /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
994         if (memcmp(start, sb + o[last], l[last]) == 0)
995         {
996           char * p; /* Pointer to the beginning of an UTF-8 glyph in *
997                      | the potential lowercase version of the word.  */
998 
999           if (last == 0)
1000           {
1001             /* There is only one glyph in the search buffer, we can */
1002             /* stop here.                                           */
1003             /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
1004             BIT_ON(bm, lmg);
1005             if (affinity != END_AFFINITY)
1006               break;
1007           }
1008 
1009           /* If the search buffer contains more than one glyph, we need  */
1010           /* to search the first combination which match the buffer in   */
1011           /* the word.                                                   */
1012           /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1013           p = start;
1014           j = last; /* j counts the number of glyphs in the search buffer *
1015                      | not found in the word.                             */
1016 
1017           /* Proceed backwards from the position of last glyph of the      */
1018           /* search to check if all the previous glyphs can be fond before */
1019           /* in the word. If not try to find the next position of this     */
1020           /* last glyph in the word.                                       */
1021           /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1022           sg = lmg;
1023           while (j > 0 && (p = utf8_prev(str, p)) != NULL)
1024           {
1025             if (memcmp(p, sb + o[j - 1], l[j - 1]) == 0)
1026             {
1027               BIT_ON(bm, sg - 1);
1028               j--;
1029             }
1030             else if (mode == SUBSTRING)
1031               break;
1032 
1033             sg--;
1034           }
1035 
1036           /* All the glyphs have been found. */
1037           /* """"""""""""""""""""""""""""""" */
1038           if (j == 0)
1039           {
1040             BIT_ON(bm, lmg);
1041             if (affinity != END_AFFINITY)
1042               break;
1043           }
1044         }
1045 
1046         lmg++;
1047         start = utf8_next(start);
1048       }
1049 
1050       if (mode == FUZZY)
1051       {
1052         size_t utf8_index;
1053 
1054         free(str);
1055 
1056         /* We know that the first non blank glyph is part of the pattern, */
1057         /* so highlight it if it is not and suppresses the highlighting   */
1058         /* of the next occurrence that must be here because this word has */
1059         /* already been filtered by select_starting_pattern().            */
1060         /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1061         if (affinity == START_AFFINITY)
1062         {
1063           char *ptr1, *ptr2;
1064           long  i;
1065           long  utf8_len;
1066 
1067           /* Skip leading spaces and tabs. */
1068           /* """"""""""""""""""""""""""""" */
1069           for (i = 0; i < word_a[n].mb; i++)
1070             if (!isblank(*(word_a[n].str + daccess.flength + i)))
1071               break;
1072 
1073           first_glyph = utf8_strprefix(first_glyph, word_a[n].str + i, 1,
1074                                        &utf8_len);
1075 
1076           if (!BIT_ISSET(word_a[n].bitmap, i))
1077           {
1078             BIT_ON(word_a[n].bitmap, i);
1079 
1080             ptr1 = word_a[n].str + i;
1081             i++;
1082             while ((ptr2 = utf8_next(ptr1)) != NULL)
1083             {
1084               if (memcmp(ptr2, first_glyph, utf8_len) == 0)
1085               {
1086                 if (BIT_ISSET(word_a[n].bitmap, i))
1087                 {
1088                   BIT_OFF(word_a[n].bitmap, i);
1089                   break;
1090                 }
1091                 else
1092                   ptr1 = ptr2;
1093               }
1094               else
1095                 ptr1 = ptr2;
1096 
1097               i++;
1098             }
1099           }
1100         }
1101 
1102         /* Compute the number of 'holes' in the bitmap to determine the  */
1103         /* badness of a match. Th goal is to put the cursor on the word  */
1104         /* with the smallest badness.                                    */
1105         /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1106         utf8_index = 0;
1107         j          = 0;
1108         badness    = 0;
1109 
1110         while (utf8_index < word_a[n].mb
1111                && !BIT_ISSET(word_a[n].bitmap, utf8_index))
1112           utf8_index++;
1113 
1114         while (utf8_index < word_a[n].mb)
1115         {
1116           if (!BIT_ISSET(word_a[n].bitmap, utf8_index))
1117             badness++;
1118           else
1119             j++;
1120 
1121           /* Stop here if all the possible bits has been checked as they  */
1122           /* cannot be more numerous than the number of UTF-8 glyphs in   */
1123           /* the search buffer.                                           */
1124           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1125           if (j == data->utf8_len)
1126             break;
1127 
1128           utf8_index++;
1129         }
1130       }
1131       free(str_orig);
1132 
1133       if (search_mode == FUZZY)
1134       {
1135         /* When the badness is zero (best match), add the word position. */
1136         /* at the end of a special array which will be used to move the. */
1137         /* cursor among this category of words.                          */
1138         /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1139         if (badness == 0)
1140         {
1141           if (best_matches_count == best_matching_words_a_size)
1142           {
1143             best_matching_words_a = xrealloc(best_matching_words_a,
1144                                              (best_matching_words_a_size + 16)
1145                                                * sizeof(long));
1146             best_matching_words_a_size += 16;
1147           }
1148 
1149           best_matching_words_a[best_matches_count] = n;
1150 
1151           best_matches_count++;
1152         }
1153       }
1154     }
1155     if (mode == FUZZY)
1156       free(sb);
1157 
1158     free(first_glyph);
1159   }
1160   else if (mode == PREFIX)
1161   {
1162     for (i = 0; i < matches_count; i++)
1163     {
1164       n      = matching_words_a[i];
1165       bm     = word_a[n].bitmap;
1166       bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1;
1167 
1168       memset(bm, '\0', bm_len);
1169 
1170       for (j = 0; j <= last; j++)
1171         BIT_ON(bm, j);
1172     }
1173   }
1174 }
1175 
1176 /* ====================================================== */
1177 /* Find the next word index in the list of matching words */
1178 /* returns -1 if not found.                               */
1179 /* ====================================================== */
1180 long
find_next_matching_word(long * array,long nb,long value,long * index)1181 find_next_matching_word(long * array, long nb, long value, long * index)
1182 {
1183   long left = 0, right = nb, middle;
1184 
1185   /* Use the cached value when possible. */
1186   /* """"""""""""""""""""""""""""""""""" */
1187   if (*index >= 0 && *index < nb - 1 && array[*index] == value)
1188     return (array[++(*index)]);
1189 
1190   if (nb > 0)
1191   {
1192     /* Bisection search. */
1193     /* ''''''''''''''''' */
1194     while (left < right)
1195     {
1196       middle = (left + right) / 2;
1197 
1198       if (value < array[middle])
1199         right = middle;
1200       else
1201         left = middle + 1;
1202     }
1203     if (left < nb - 1)
1204     {
1205       *index = left;
1206       return array[*index];
1207     }
1208     else
1209     {
1210       if (value > array[nb - 1])
1211       {
1212         *index = -1;
1213         return *index;
1214       }
1215       else
1216       {
1217         *index = nb - 1;
1218         return array[*index];
1219       }
1220     }
1221   }
1222   else
1223   {
1224     *index = -1;
1225     return *index;
1226   }
1227 }
1228 
1229 /* ========================================================== */
1230 /* Find the previous word index in the list of matching words */
1231 /* returns -1 if not found.                                   */
1232 /* ========================================================== */
1233 long
find_prev_matching_word(long * array,long nb,long value,long * index)1234 find_prev_matching_word(long * array, long nb, long value, long * index)
1235 {
1236   long left = 0, right = nb, middle;
1237 
1238   /* Use the cached value when possible. */
1239   /* """"""""""""""""""""""""""""""""""" */
1240   if (*index > 0 && array[*index] == value)
1241     return (array[--(*index)]);
1242 
1243   if (nb > 0)
1244   {
1245     /* Bisection search. */
1246     /* ''''''''''''''''' */
1247     while (left < right)
1248     {
1249       middle = (left + right) / 2;
1250 
1251       if (array[middle] == value)
1252       {
1253         if (middle > 0)
1254         {
1255           *index = middle - 1;
1256           return array[*index];
1257         }
1258         else
1259         {
1260           *index = -1;
1261           return *index;
1262         }
1263       }
1264 
1265       if (value < array[middle])
1266         right = middle;
1267       else
1268         left = middle + 1;
1269     }
1270     if (left > 0)
1271     {
1272       *index = left - 1;
1273       return array[*index];
1274     }
1275     else
1276     {
1277       *index = -1;
1278       return *index;
1279     }
1280   }
1281   else
1282   {
1283     *index = -1;
1284     return *index;
1285   }
1286 }
1287 
1288 /* ============================================================= */
1289 /* Remove all traces of matched words and redisplay the windows. */
1290 /* ============================================================= */
1291 void
clean_matches(search_data_t * search_data,long size)1292 clean_matches(search_data_t * search_data, long size)
1293 {
1294   sub_tst_t * sub_tst_data;
1295   ll_node_t * fuzzy_node;
1296   long        i;
1297 
1298   /* Clean the list of lists data-structure containing the search levels */
1299   /* Note that the first element of the list is never deleted.           */
1300   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1301   if (tst_search_list)
1302   {
1303     fuzzy_node = tst_search_list->tail;
1304     while (tst_search_list->len > 1)
1305     {
1306       sub_tst_data = (sub_tst_t *)(fuzzy_node->data);
1307 
1308       free(sub_tst_data->array);
1309       free(sub_tst_data);
1310 
1311       ll_delete(tst_search_list, tst_search_list->tail);
1312       fuzzy_node = tst_search_list->tail;
1313     }
1314     sub_tst_data        = (sub_tst_t *)(fuzzy_node->data);
1315     sub_tst_data->count = 0;
1316   }
1317 
1318   search_data->fuzzy_err = 0;
1319 
1320   /* Clean the search buffer. */
1321   /* """""""""""""""""""""""" */
1322   memset(search_data->buf, '\0', size - daccess.flength);
1323 
1324   search_data->len           = 0;
1325   search_data->utf8_len      = 0;
1326   search_data->only_ending   = 0;
1327   search_data->only_starting = 0;
1328 
1329   /* Clean the match flags and bitmaps. */
1330   /* """""""""""""""""""""""""""""""""" */
1331   for (i = 0; i < matches_count; i++)
1332   {
1333     long n = matching_words_a[i];
1334 
1335     word_a[n].is_matching = 0;
1336 
1337     memset(word_a[n].bitmap, '\0',
1338            (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
1339   }
1340 
1341   matches_count = 0;
1342 }
1343 
1344 /* *************************** */
1345 /* Terminal utility functions. */
1346 /* *************************** */
1347 
1348 /* ===================================================================== */
1349 /* outch is a function version of putchar that can be passed to tputs as */
1350 /* a routine to call.                                                    */
1351 /* ===================================================================== */
1352 int
1353 #ifdef __sun
outch(char c)1354 outch(char c)
1355 #else
1356 outch(int c)
1357 #endif
1358 {
1359   putchar(c);
1360   return 1;
1361 }
1362 
1363 /* =============================================== */
1364 /* Set the terminal in non echo/non canonical mode */
1365 /* wait for at least one byte, no timeout.         */
1366 /* =============================================== */
1367 void
setup_term(int const fd)1368 setup_term(int const fd)
1369 {
1370   /* Save the terminal parameters and configure it in row mode. */
1371   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1372   tcgetattr(fd, &old_in_attrs);
1373 
1374   new_in_attrs.c_iflag = 0;
1375   new_in_attrs.c_oflag = old_in_attrs.c_oflag;
1376   new_in_attrs.c_cflag = old_in_attrs.c_cflag;
1377   new_in_attrs.c_lflag = old_in_attrs.c_lflag & ~(ICANON | ECHO | ISIG);
1378 
1379   new_in_attrs.c_cc[VMIN]  = 1; /* wait for at least 1 byte. */
1380   new_in_attrs.c_cc[VTIME] = 0; /* no timeout.               */
1381 
1382   tcsetattr(fd, TCSANOW, &new_in_attrs);
1383 }
1384 
1385 /* ====================================== */
1386 /* Set the terminal in its previous mode. */
1387 /* ====================================== */
1388 void
restore_term(int const fd)1389 restore_term(int const fd)
1390 {
1391   tcsetattr(fd, TCSANOW, &old_in_attrs);
1392 }
1393 
1394 /* ============================================== */
1395 /* Get the terminal numbers of lines and columns  */
1396 /* Assume that the TIOCGWINSZ, ioctl is available */
1397 /* Defaults to 80x24.                             */
1398 /* ============================================== */
1399 void
get_terminal_size(int * const r,int * const c,term_t * term)1400 get_terminal_size(int * const r, int * const c, term_t * term)
1401 {
1402   struct winsize ws;
1403 
1404   *r = *c = -1;
1405 
1406   if (ioctl(0, TIOCGWINSZ, &ws) == 0)
1407   {
1408     *r = ws.ws_row;
1409     *c = ws.ws_col;
1410 
1411     if (*r > 0 && *c > 0)
1412       return;
1413   }
1414 
1415   *r = tigetnum("lines");
1416   *c = tigetnum("cols");
1417 
1418   if (*r <= 0 || *c <= 0)
1419   {
1420     *r = 80;
1421     *c = 24;
1422   }
1423 }
1424 
1425 /* ======================================================= */
1426 /* Get cursor position the terminal.                       */
1427 /* Assume that the Escape sequence ESC [ 6 n is available. */
1428 /* Returns 1 on success and 0 on error.                    */
1429 /* ======================================================= */
1430 int
get_cursor_position(int * const r,int * const c)1431 get_cursor_position(int * const r, int * const c)
1432 {
1433   char   buf[32] = { 0 };
1434   char * s;
1435 
1436   int count = 64;
1437   int v;
1438   int rc = 1;
1439 
1440   int ask; /* Number of asked characters.    */
1441   int got; /* Number of characters obtained. */
1442 
1443   int buf_size = sizeof(buf);
1444 
1445   /* Report cursor location. */
1446   /* """"""""""""""""""""""" */
1447   while ((v = write(STDOUT_FILENO, "\x1b[6n", 4)) == -1 && count)
1448   {
1449     if (errno == EINTR)
1450       count--;
1451     else
1452     {
1453       rc = 0;
1454       goto read;
1455     }
1456 
1457     errno = 0;
1458   }
1459 
1460   if (v != 4)
1461     rc = 0;
1462 
1463 read:
1464 
1465   /* Read the response: ESC [ rows ; cols R. */
1466   /* """"""""""""""""""""""""""""""""""""""" */
1467   *(s = buf) = 0;
1468 
1469   do
1470   {
1471     ask = buf_size - 1 - (s - buf);
1472     got = read(STDIN_FILENO, s, ask);
1473 
1474     if (got < 0 && errno == EINTR)
1475       got = 0;
1476     else if (got == 0)
1477       break;
1478 
1479     s += got;
1480   } while (strchr(buf, 'R') == NULL);
1481 
1482   /* Parse it. */
1483   /* """"""""" */
1484   if (buf[0] != 0x1b || buf[1] != '[')
1485     return 0;
1486 
1487   if (sscanf(buf + 2, "%d;%d", r, c) != 2)
1488     rc = 0;
1489 
1490   return rc;
1491 }
1492 
1493 /* ====================================================== */
1494 /* Returns 1 if a string is empty or only made of spaces. */
1495 /* ====================================================== */
1496 int
isempty(const char * s)1497 isempty(const char * s)
1498 {
1499   while (*s != '\0')
1500   {
1501     if (my_isprint(*s) && *s != ' ' && *s != '\t')
1502       return 0;
1503     s++;
1504   }
1505   return 1;
1506 }
1507 
1508 /* ======================================================================== */
1509 /* Parse a regular expression based selector.                               */
1510 /* The string to parse is bounded by a delimiter so we must parse something */
1511 /* like: <delim><regex string><delim> as in /a.*b/ by example.              */
1512 /*                                                                          */
1513 /* str            (in)  delimited string to parse                           */
1514 /* filter         (in)  INCLUDE_FILTER or EXCLUDE_FILTER                    */
1515 /* inc_regex_list (out) INCLUDE_FILTER or EXCLUDE_FILTER                    */
1516 /*                      regular expression if the filter is INCLUDE_FILTER  */
1517 /* exc_regex_list (out) INCLUDE_FILTER or EXCLUDE_FILTER                    */
1518 /*                      regular expression if the filter is EXCLUDE_FILTER  */
1519 /* ======================================================================== */
1520 void
parse_regex_selector_part(char * str,filters_t filter,ll_t ** inc_regex_list,ll_t ** exc_regex_list)1521 parse_regex_selector_part(char * str, filters_t filter, ll_t ** inc_regex_list,
1522                           ll_t ** exc_regex_list)
1523 {
1524   regex_t * regex;
1525 
1526   str[strlen(str) - 1] = '\0';
1527 
1528   regex = xmalloc(sizeof(regex_t));
1529   if (regcomp(regex, str + 1, REG_EXTENDED | REG_NOSUB) == 0)
1530   {
1531     if (filter == INCLUDE_FILTER)
1532     {
1533       if (*inc_regex_list == NULL)
1534         *inc_regex_list = ll_new();
1535 
1536       ll_append(*inc_regex_list, regex);
1537     }
1538     else
1539     {
1540       if (*exc_regex_list == NULL)
1541         *exc_regex_list = ll_new();
1542 
1543       ll_append(*exc_regex_list, regex);
1544     }
1545   }
1546 }
1547 
1548 /* ===================================================================== */
1549 /* Parse a description string.                                           */
1550 /* <letter><range1>,<range2>,...                                         */
1551 /* <range> is n1-n2 | n1 where n1 starts with 1.                         */
1552 /*                                                                       */
1553 /* <letter> is a|A|s|S|r|R|u|U where:                                    */
1554 /* a|A is for Add.                                                       */
1555 /* s|S is for Select (same as Add).                                      */
1556 /* r|R is for Remove.                                                    */
1557 /* d|D is for Deselect (same as Remove).                                 */
1558 /*                                                                       */
1559 /* str               (in)  string to parse.                              */
1560 /* filter            (out) is INCLUDE_FILTER or EXCLUDE_FILTER according */
1561 /*                         to <letter>.                                  */
1562 /* unparsed          (out) is empty on success and contains the unparsed */
1563 /*                         part on failure.                              */
1564 /* inc_interval_list (out) is a list of the interval of elements to      */
1565 /*                         be included.                                  */
1566 /* inc_regex_list    (out) is a list of extended regular expressions of  */
1567 /*                         elements to be included.                      */
1568 /* exc_interval_list (out) is a list of the interval of elements to be   */
1569 /*                         excluded.                                     */
1570 /* exc_regex_list    (out) is a list of extended interval of elements to */
1571 /*                         be excluded.                                  */
1572 /* ===================================================================== */
1573 void
parse_selectors(char * str,filters_t * filter,char * unparsed,ll_t ** inc_interval_list,ll_t ** inc_regex_list,ll_t ** exc_interval_list,ll_t ** exc_regex_list,langinfo_t * langinfo,misc_t * misc)1574 parse_selectors(char * str, filters_t * filter, char * unparsed,
1575                 ll_t ** inc_interval_list, ll_t ** inc_regex_list,
1576                 ll_t ** exc_interval_list, ll_t ** exc_regex_list,
1577                 langinfo_t * langinfo, misc_t * misc)
1578 {
1579   char         mark; /* Value to set */
1580   char         c;
1581   size_t       start = 1;     /* column string offset in the parsed string. */
1582   size_t       first, second; /* range starting and ending values.          */
1583   char *       ptr;           /* pointer to the remaining string to parse.  */
1584   interval_t * interval;
1585 
1586   /* Replace the UTF-8 ascii representation in the selector by */
1587   /* their binary values.                                      */
1588   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1589   utf8_interpret(str, langinfo, misc->invalid_char_substitute);
1590 
1591   /* Get the first character to see if this is */
1592   /* an additive or restrictive operation.     */
1593   /* """"""""""""""""""""""""""""""""""""""""" */
1594   if (sscanf(str, "%c", &c) == 0)
1595     return;
1596 
1597   switch (c)
1598   {
1599     case 'i':
1600     case 'I':
1601       *filter = INCLUDE_FILTER;
1602       mark    = INCLUDE_MARK;
1603       break;
1604 
1605     case 'e':
1606     case 'E':
1607       *filter = EXCLUDE_FILTER;
1608       mark    = EXCLUDE_MARK;
1609       break;
1610 
1611     case '1':
1612     case '2':
1613     case '3':
1614     case '4':
1615     case '5':
1616     case '6':
1617     case '7':
1618     case '8':
1619     case '9':
1620       *filter = INCLUDE_FILTER;
1621       mark    = INCLUDE_MARK;
1622       start   = 0;
1623       break;
1624 
1625     default:
1626       if (!isgraph(c))
1627         return;
1628 
1629       *filter = INCLUDE_FILTER;
1630       mark    = INCLUDE_MARK;
1631       start   = 0;
1632       break;
1633   }
1634 
1635   /* Set ptr to the start of the interval list to parse. */
1636   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
1637   ptr = str + start;
1638 
1639   first = second = -1;
1640 
1641   /* Scan the comma separated ranges. */
1642   /* """""""""""""""""""""""""""""""" */
1643   while (*ptr)
1644   {
1645     size_t swap;
1646     int    is_range = 0;
1647     char   delim1, delim2 = '\0';
1648     char * oldptr;
1649 
1650     oldptr = ptr;
1651     while (*ptr && *ptr != ',')
1652     {
1653       if (*ptr == '-')
1654         is_range = 1;
1655       ptr++;
1656     }
1657 
1658     /* Forbid the trailing comma (ex: xxx,). */
1659     /* """"""""""""""""""""""""""""""""""""" */
1660     if (*ptr == ',' && (*(ptr + 1) == '\0' || *(ptr + 1) == '-'))
1661     {
1662       my_strcpy(unparsed, ptr);
1663       return;
1664     }
1665 
1666     /* Forbid the empty patterns (ex: xxx,,yyy). */
1667     /* """"""""""""""""""""""""""""""""""""""""" */
1668     if (oldptr == ptr)
1669     {
1670       my_strcpy(unparsed, ptr);
1671       return;
1672     }
1673 
1674     /* Mark the end of the interval found. */
1675     /* """"""""""""""""""""""""""""""""""" */
1676     if (*ptr)
1677       *ptr++ = '\0';
1678 
1679     /* If the regex contains at least three characters then delim1 */
1680     /* and delim2 point to the first and last delimiter of the     */
1681     /* regular expression. Ex /abc/                                */
1682     /*                        ^   ^                                */
1683     /*                        |   |                                */
1684     /*                   delim1   delim2                           */
1685     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1686     delim1 = *(str + start);
1687     if (*ptr == '\0')
1688       delim2 = *(ptr - 1);
1689     else if (ptr > str + start + 2)
1690       delim2 = *(ptr - 2);
1691 
1692     /* Check is we have found a well describes regular expression. */
1693     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1694     if (ptr > str + start + 2 && delim1 == delim2 && isgraph(delim1)
1695         && isgraph(delim2) && !isdigit(delim1) && !isdigit(delim2))
1696     {
1697       /* Process the regex. */
1698       /* """""""""""""""""" */
1699       parse_regex_selector_part(str + start, *filter, inc_regex_list,
1700                                 exc_regex_list);
1701 
1702       /* Adjust the start of the new interval to read in the */
1703       /* initial string.                                     */
1704       /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
1705       start = ptr - str;
1706 
1707       continue;
1708     }
1709     else if (delim2 != '\0' && (!isdigit(delim1) || !isdigit(delim2)))
1710     {
1711       /* Both delimiter must be numeric if delim2 exist. */
1712       /* """"""""""""""""""""""""""""""""""""""""""""""" */
1713       my_strcpy(unparsed, str + start);
1714       return;
1715     }
1716 
1717     if (is_range)
1718     {
1719       int rc;
1720       int pos;
1721 
1722       rc = sscanf(str + start, "%zu-%zu%n", &first, &second, &pos);
1723       if (rc != 2 || *(str + start + pos) != '\0')
1724       {
1725         my_strcpy(unparsed, str + start);
1726         return;
1727       }
1728 
1729       if (first < 1 || second < 1)
1730       {
1731         /* Both interval boundaries must be strictly positive. */
1732         /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
1733         my_strcpy(unparsed, str + start);
1734         return;
1735       }
1736 
1737       /* Ensure that the low bound of the interval is lower or equal */
1738       /* to the high one. Swap them if needed.                       */
1739       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1740       interval = interval_new();
1741 
1742       if (first > second)
1743       {
1744         swap   = first;
1745         first  = second;
1746         second = swap;
1747       }
1748 
1749       interval->low  = first - 1;
1750       interval->high = second - 1;
1751     }
1752     else
1753     {
1754       /* Read the number given. */
1755       /* """""""""""""""""""""" */
1756       if (sscanf(str + start, "%zu", &first) != 1)
1757       {
1758         my_strcpy(unparsed, str + start);
1759         return;
1760       }
1761 
1762       interval      = interval_new();
1763       interval->low = interval->high = first - 1;
1764     }
1765 
1766     /* Adjust the start of the new interval to read in the */
1767     /* initial string.                                     */
1768     /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
1769     start = ptr - str;
1770 
1771     /* Add the new interval to the correct list. */
1772     /* """"""""""""""""""""""""""""""""""""""""" */
1773     if (mark == EXCLUDE_MARK)
1774     {
1775       if (*exc_interval_list == NULL)
1776         *exc_interval_list = ll_new();
1777 
1778       ll_append(*exc_interval_list, interval);
1779     }
1780     else
1781     {
1782       if (*inc_interval_list == NULL)
1783         *inc_interval_list = ll_new();
1784 
1785       ll_append(*inc_interval_list, interval);
1786     }
1787   }
1788 
1789   /* If we are here, then all the intervals have be successfully parsed */
1790   /* Ensure that the unparsed string is empty.                          */
1791   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1792   *unparsed = '\0';
1793 }
1794 
1795 /* ========================================================= */
1796 /* Parse the sed like string passed as argument to -S/-I/-E. */
1797 /* Update the sed parameter.                                 */
1798 /* ========================================================= */
1799 int
parse_sed_like_string(sed_t * sed)1800 parse_sed_like_string(sed_t * sed)
1801 {
1802   char   sep;
1803   char * first_sep_pos;
1804   char * last_sep_pos;
1805   char * buf;
1806   long   index;
1807   int    icase;
1808   char   c;
1809 
1810   if (strlen(sed->pattern) < 4)
1811     return 0;
1812 
1813   /* Get the separator (the 1st character). */
1814   /* """""""""""""""""""""""""""""""""""""" */
1815   buf = xstrdup(sed->pattern);
1816   sep = buf[0];
1817 
1818   /* Space like separators are not permitted. */
1819   /* """""""""""""""""""""""""""""""""""""""" */
1820   if (isspace(sep))
1821     goto err;
1822 
1823   /* Get the extended regular expression. */
1824   /* """""""""""""""""""""""""""""""""""" */
1825   if ((first_sep_pos = strchr(buf + 1, sep)) == NULL)
1826     goto err;
1827 
1828   *first_sep_pos = '\0';
1829 
1830   /* Get the substitution string. */
1831   /* """""""""""""""""""""""""""" */
1832   if ((last_sep_pos = strchr(first_sep_pos + 1, sep)) == NULL)
1833     goto err;
1834 
1835   *last_sep_pos = '\0';
1836 
1837   sed->substitution = xstrdup(first_sep_pos + 1);
1838 
1839   /* Get the global indicator (trailing g) */
1840   /* and the visual indicator (trailing v) */
1841   /* and the stop indicator (trailing s).  */
1842   /* """"""""""""""""""""""""""""""""""""" */
1843   sed->global = sed->visual = icase = 0;
1844 
1845   index = 1;
1846   while ((c = *(last_sep_pos + index)) != '\0')
1847   {
1848     if (c == 'g')
1849       sed->global = 1;
1850     else if (c == 'v')
1851       sed->visual = 1;
1852     else if (c == 's')
1853       sed->stop = 1;
1854     else if (c == 'i')
1855       icase = 1;
1856     else
1857       goto err;
1858 
1859     index++;
1860   }
1861 
1862   /* Empty regular expression ? */
1863   /* """""""""""""""""""""""""" */
1864   if (*(buf + 1) == '\0')
1865     goto err;
1866 
1867   /* Compile the regular expression and abort on failure. */
1868   /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
1869   if (regcomp(&(sed->re), buf + 1,
1870               !icase ? REG_EXTENDED : (REG_EXTENDED | REG_ICASE))
1871       != 0)
1872     goto err;
1873 
1874   free(buf);
1875 
1876   return 1;
1877 
1878 err:
1879   free(buf);
1880 
1881   return 0;
1882 }
1883 
1884 /* ===================================================================== */
1885 /* Utility function used by replace to expand the replacement string.    */
1886 /* IN:                                                                   */
1887 /* orig:            matching part of the original string to be replaced. */
1888 /* repl:            string containing the replacement directives         */
1889 /* subs_a:          array of ranges containing the start and end offset  */
1890 /*                  of the remembered parts of the strings referenced    */
1891 /*                  by the sequence \n where n is in [1,10].             */
1892 /* match_start/end: offset in orig for the current matched string        */
1893 /* subs_nb:         number of elements containing significant values in  */
1894 /*                  the array described above.                           */
1895 /* match:           current match number in the original string.         */
1896 /*                                                                       */
1897 /* OUT:                                                                  */
1898 /* The modified string according to the content of repl.                 */
1899 /* ===================================================================== */
1900 char *
build_repl_string(char * orig,char * repl,long match_start,long match_end,range_t * subs_a,long subs_nb,long match)1901 build_repl_string(char * orig, char * repl, long match_start, long match_end,
1902                   range_t * subs_a, long subs_nb, long match)
1903 {
1904   size_t rsize     = 0;
1905   size_t allocated = 16;
1906   char * str       = xmalloc(allocated);
1907   int    special   = 0;
1908   long   offset    = match * subs_nb; /* offset of the 1st sub       *
1909                                        | corresponding to the match. */
1910 
1911   if (*repl == '\0')
1912     str = xstrdup("");
1913   else
1914     while (*repl)
1915     {
1916       switch (*repl)
1917       {
1918         case '\\':
1919           if (special)
1920           {
1921             if (allocated == rsize)
1922               str = xrealloc(str, allocated += 16);
1923             str[rsize] = '\\';
1924             rsize++;
1925             str[rsize] = '\0';
1926             special    = 0;
1927           }
1928           else
1929             special = 1;
1930           break;
1931 
1932         case '1':
1933         case '2':
1934         case '3':
1935         case '4':
1936         case '5':
1937         case '6':
1938         case '7':
1939         case '8':
1940         case '9':
1941           if (special)
1942           {
1943             if ((*repl) - '0' <= subs_nb)
1944             {
1945               long index = (*repl) - '0' - 1 + offset;
1946               long delta = subs_a[index].end - subs_a[index].start;
1947 
1948               if (allocated <= rsize + delta)
1949                 str = xrealloc(str, allocated += (delta + 16));
1950 
1951               memcpy(str + rsize, orig + subs_a[index].start, delta);
1952 
1953               rsize += delta;
1954               str[rsize] = '\0';
1955             }
1956 
1957             special = 0;
1958           }
1959           else
1960           {
1961             if (allocated == rsize)
1962               str = xrealloc(str, allocated += 16);
1963             str[rsize] = *repl;
1964             rsize++;
1965             str[rsize] = '\0';
1966           }
1967           break;
1968 
1969         case '&':
1970           if (!special)
1971           {
1972             long delta = match_end - match_start;
1973 
1974             if (allocated <= rsize + delta)
1975               str = xrealloc(str, allocated += (delta + 16));
1976 
1977             memcpy(str + rsize, orig + match_start, delta);
1978 
1979             rsize += delta;
1980             str[rsize] = '\0';
1981 
1982             break;
1983           }
1984 
1985           /* No break here, '&' must be treated as a normal */
1986           /* character when protected.                      */
1987           /* '''''''''''''''''''''''''''''''''''''''''''''' */
1988 
1989         default:
1990           special = 0;
1991           if (allocated == rsize)
1992             str = xrealloc(str, allocated += 16);
1993           str[rsize] = *repl;
1994           rsize++;
1995           str[rsize] = '\0';
1996       }
1997       repl++;
1998     }
1999   return str;
2000 }
2001 
2002 /* ====================================================================== */
2003 /* Replace the part of a string matched by an extender regular expression */
2004 /* by the substitution string.                                            */
2005 /* The regex used must have been previously compiled.                     */
2006 /*                                                                        */
2007 /* orig: original string                                                  */
2008 /* sed:      composite variable containing the regular expression, a      */
2009 /*           substitution string and various other informations.          */
2010 /* output:   destination buffer.                                          */
2011 /*                                                                        */
2012 /* RC:                                                                    */
2013 /* return 1 if the replacement has been successful else 0.                */
2014 /*                                                                        */
2015 /* NOTE:                                                                  */
2016 /* uses the global variable word_buffer.                                  */
2017 /* ====================================================================== */
2018 int
replace(char * orig,sed_t * sed)2019 replace(char * orig, sed_t * sed)
2020 {
2021   size_t match_nb   = 0; /* number of matches in the original string. */
2022   int    sub_nb     = 0; /* number of remembered matches in the       *
2023                           | original sting.                           */
2024   size_t target_len = 0; /* length of the resulting string.           */
2025   size_t subs_max   = 0;
2026 
2027   if (*orig == '\0')
2028     return 1;
2029 
2030   range_t * matches_a = xmalloc(strlen(orig) * sizeof(range_t));
2031   range_t * subs_a    = xmalloc(10 * sizeof(range_t));
2032 
2033   const char * p = orig; /* points to the end of the previous match. */
2034   regmatch_t   m[10];    /* array containing the start/end offsets   *
2035                           | of the matches found.                    */
2036 
2037   while (1)
2038   {
2039     size_t i = 0;
2040     size_t match;       /* current match index.                        */
2041     size_t index   = 0; /* current char index in the original string.  */
2042     int    nomatch = 0; /* equals to 1 when there is no more matching. */
2043     char * exp_repl;    /* expanded replacement string.                */
2044 
2045     if (*p == '\0')
2046       nomatch = 1;
2047     else
2048       nomatch = regexec(&sed->re, p, 10, m, 0);
2049 
2050     if (nomatch)
2051     {
2052       if (match_nb > 0)
2053       {
2054         for (index = 0; index < matches_a[0].start; index++)
2055           word_buffer[target_len++] = orig[index];
2056 
2057         for (match = 0; match < match_nb; match++)
2058         {
2059           size_t len;
2060           size_t end;
2061 
2062           exp_repl = build_repl_string(orig, sed->substitution,
2063                                        matches_a[match].start,
2064                                        matches_a[match].end, subs_a, subs_max,
2065                                        match);
2066 
2067           len = strlen(exp_repl);
2068 
2069           my_strcpy(word_buffer + target_len, exp_repl);
2070           target_len += len;
2071           free(exp_repl);
2072 
2073           index += matches_a[match].end - matches_a[match].start;
2074 
2075           if (match < match_nb - 1 && sed->global)
2076             end = matches_a[match + 1].start;
2077           else
2078             end = strlen(orig);
2079 
2080           while (index < end)
2081             word_buffer[target_len++] = orig[index++];
2082 
2083           word_buffer[target_len] = '\0';
2084 
2085           if (!sed->global)
2086             break;
2087         }
2088       }
2089       else
2090       {
2091         my_strcpy(word_buffer, orig);
2092         return 0;
2093       }
2094 
2095       return nomatch;
2096     }
2097 
2098     subs_max = 0;
2099     for (i = 0; i < 10; i++)
2100     {
2101       size_t start;
2102       size_t finish;
2103 
2104       if (m[i].rm_so == -1)
2105         break;
2106 
2107       start  = m[i].rm_so + (p - orig);
2108       finish = m[i].rm_eo + (p - orig);
2109 
2110       if (i == 0)
2111       {
2112         matches_a[match_nb].start = start;
2113         matches_a[match_nb].end   = finish;
2114         match_nb++;
2115         if (match_nb > utf8_strlen(orig))
2116           goto fail;
2117       }
2118       else
2119       {
2120         subs_a[sub_nb].start = start;
2121         subs_a[sub_nb].end   = finish;
2122         sub_nb++;
2123         subs_max++;
2124       }
2125     }
2126     if (m[0].rm_eo > 0)
2127       p += m[0].rm_eo;
2128     else
2129       p++; /* Empty match. */
2130   }
2131 
2132 fail:
2133   return 0;
2134 }
2135 
2136 /* ============================================================ */
2137 /* Remove all ANSI color codes from s and puts the result in d. */
2138 /* Memory space for d must have been allocated before.          */
2139 /* ============================================================ */
2140 void
strip_ansi_color(char * s,toggle_t * toggles,misc_t * misc)2141 strip_ansi_color(char * s, toggle_t * toggles, misc_t * misc)
2142 {
2143   char * p   = s;
2144   long   len = strlen(s);
2145 
2146   while (*s != '\0')
2147   {
2148     /* Remove a sequence of \x1b[...m from s. */
2149     /* """""""""""""""""""""""""""""""""""""" */
2150     if ((*s == 0x1b) && (*(s + 1) == '['))
2151     {
2152       while ((*s != '\0') && (*s++ != 'm'))
2153         ;
2154     }
2155     /* Convert a single \x1b in the invalid substitute character. */
2156     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2157     else if (*s == 0x1b)
2158     {
2159       if (toggles->blank_nonprintable && len > 1)
2160         *s++ = ' ';
2161       else
2162         *s++ = misc->invalid_char_substitute;
2163       p++;
2164     }
2165     /* No ESC char, we can move on. */
2166     /* """""""""""""""""""""""""""" */
2167     else
2168       *p++ = *s++;
2169   }
2170 
2171   *p = '\0';
2172 }
2173 
2174 /* ================================================================= */
2175 /* Callback function to insert the index of a matching word index in */
2176 /* the sorted list of the already matched words.                     */
2177 /* ================================================================= */
2178 int
set_matching_flag(void * elem)2179 set_matching_flag(void * elem)
2180 {
2181   ll_t * list = (ll_t *)elem;
2182 
2183   ll_node_t * node = list->head;
2184 
2185   while (node)
2186   {
2187     size_t pos;
2188 
2189     pos = *(size_t *)(node->data);
2190     if (word_a[pos].is_selectable)
2191       word_a[pos].is_matching = 1;
2192 
2193     insert_sorted_index(&matching_words_a, &matching_words_a_size,
2194                         &matches_count, pos);
2195 
2196     node = node->next;
2197   }
2198   return 1;
2199 }
2200 
2201 /* ======================================================================= */
2202 /* Callback function used by tst_traverse.                                 */
2203 /* Iterate the linked list attached to the string containing the index of  */
2204 /* the words in the input flow. Each page number is then added in a sorted */
2205 /* array avoiding duplications keeping the array sorted.                   */
2206 /* Mark the identified words as a matching word.                           */
2207 /* ======================================================================= */
2208 int
tst_cb(void * elem)2209 tst_cb(void * elem)
2210 {
2211   /* The data attached to the string in the tst is a linked list of   */
2212   /* position of the string in the input flow, This list is naturally */
2213   /* sorted.                                                          */
2214   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2215   ll_t * list = (ll_t *)elem;
2216 
2217   ll_node_t * node = list->head;
2218 
2219   while (node)
2220   {
2221     size_t pos;
2222 
2223     pos = *(long *)(node->data);
2224 
2225     word_a[pos].is_matching = 1;
2226     insert_sorted_index(&matching_words_a, &matching_words_a_size,
2227                         &matches_count, pos);
2228 
2229     node = node->next;
2230   }
2231   return 1;
2232 }
2233 
2234 /* ======================================================================= */
2235 /* Callback function used by tst_traverse.                                 */
2236 /* Iterate the linked list attached to the string containing the index of  */
2237 /* the words in the input flow. Each page number is then used to determine */
2238 /* the lower page greater than the cursor position                         */
2239 /* ----------------------------------------------------------------------- */
2240 /* This is a special version of tst_cb which permit to find the first      */
2241 /* word.                                                                   */
2242 /* ----------------------------------------------------------------------- */
2243 /* Require new_current to be set to count - 1 at start.                   */
2244 /* Update new_current to the smallest greater position than current.       */
2245 /* ======================================================================= */
2246 int
tst_cb_cli(void * elem)2247 tst_cb_cli(void * elem)
2248 {
2249   long n  = 0;
2250   int  rc = 0;
2251 
2252   /* The data attached to the string in the tst is a linked list of   */
2253   /* position of the string in the input flow, This list is naturally */
2254   /* sorted.                                                          */
2255   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2256   ll_t * list = (ll_t *)elem;
2257 
2258   ll_node_t * node = list->head;
2259 
2260   while (n++ < list->len)
2261   {
2262     long pos;
2263 
2264     pos = *(long *)(node->data);
2265 
2266     word_a[pos].is_matching = 1;
2267     insert_sorted_index(&matching_words_a, &matching_words_a_size,
2268                         &matches_count, pos);
2269 
2270     /* We already are at the last word, report the finding. */
2271     /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
2272     if (pos == count - 1)
2273       return 1;
2274 
2275     /* Only consider the indexes above the current cursor position. */
2276     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2277     if (pos >= current) /* Enable the search of the current word. */
2278     {
2279       /* As the future new current index has been set to the highest */
2280       /* possible value, each new possible position can only improve */
2281       /* the estimation we set rc to 1 to mark that.                 */
2282       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2283       if (pos < new_current)
2284       {
2285         new_current = pos;
2286         rc          = 1;
2287       }
2288     }
2289 
2290     node = node->next;
2291   }
2292   return rc;
2293 }
2294 
2295 /* **************** */
2296 /* Input functions. */
2297 /* **************** */
2298 
2299 /* =============================================== */
2300 /* Non delay reading of a scancode.                */
2301 /* Update a scancodes buffer and return its length */
2302 /* in bytes.                                       */
2303 /* =============================================== */
2304 int
get_scancode(unsigned char * s,size_t max)2305 get_scancode(unsigned char * s, size_t max)
2306 {
2307   int            c;
2308   size_t         i = 1;
2309   struct termios original_ts, nowait_ts;
2310 
2311   if ((c = my_fgetc(stdin)) == EOF)
2312     return 0;
2313 
2314   /* Initialize the string with the first byte. */
2315   /* """""""""""""""""""""""""""""""""""""""""" */
2316   memset(s, '\0', max);
2317   s[0] = c;
2318 
2319   /* 0x1b (ESC) has been found, proceed to check if additional codes */
2320   /* are available.                                                  */
2321   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2322   if (c == 0x1b || c > 0x80)
2323   {
2324     /* Save the terminal parameters and configure getchar() */
2325     /* to return immediately.                               */
2326     /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
2327     tcgetattr(0, &original_ts);
2328     nowait_ts = original_ts;
2329     nowait_ts.c_lflag &= ~ISIG;
2330     nowait_ts.c_cc[VMIN]  = 0;
2331     nowait_ts.c_cc[VTIME] = 0;
2332     tcsetattr(0, TCSADRAIN, &nowait_ts);
2333 
2334     /* Check if additional code is available after 0x1b. */
2335     /* """"""""""""""""""""""""""""""""""""""""""""""""" */
2336     if ((c = my_fgetc(stdin)) != EOF)
2337     {
2338       s[1] = c;
2339 
2340       i = 2;
2341       while (i < max && (c = my_fgetc(stdin)) != EOF)
2342         s[i++] = c;
2343     }
2344     else
2345     {
2346       /* There isn't a new code, this mean 0x1b came from ESC key. */
2347       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2348     }
2349 
2350     /* Restore the save terminal parameters. */
2351     /* """"""""""""""""""""""""""""""""""""" */
2352     tcsetattr(0, TCSADRAIN, &original_ts);
2353 
2354     /* Ignore EOF when a scancode contains an escape sequence. */
2355     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
2356     clearerr(stdin);
2357   }
2358 
2359   return i;
2360 }
2361 
2362 /* ============================================================ */
2363 /* Helper function to compare to delimiters for use by ll_find. */
2364 /* ============================================================ */
2365 int
buffer_cmp(const void * a,const void * b)2366 buffer_cmp(const void * a, const void * b)
2367 {
2368   return strcmp((const char *)a, (const char *)b);
2369 }
2370 
2371 /* ===================================================================== */
2372 /* Get bytes from stdin. If the first byte is the leading character of a */
2373 /* UTF-8 glyph, the following ones are also read.                        */
2374 /* The utf8_get_length function is used to get the number of bytes of    */
2375 /* the character.                                                        */
2376 /* ===================================================================== */
2377 int
get_bytes(FILE * input,char * utf8_buffer,ll_t * zapped_glyphs_list,langinfo_t * langinfo,misc_t * misc)2378 get_bytes(FILE * input, char * utf8_buffer, ll_t * zapped_glyphs_list,
2379           langinfo_t * langinfo, misc_t * misc)
2380 {
2381   int byte;
2382   int last;
2383   int n;
2384 
2385   do
2386   {
2387     last = 0;
2388 
2389     /* Read the first byte. */
2390     /* """""""""""""""""""" */
2391     byte = my_fgetc(input);
2392 
2393     if (byte == EOF)
2394       return EOF;
2395 
2396     utf8_buffer[last++] = byte;
2397 
2398     /* Check if we need to read more bytes to form a sequence */
2399     /* and put the number of bytes of the sequence in last.   */
2400     /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
2401     if (langinfo->utf8 && ((n = utf8_get_length(byte)) > 1))
2402     {
2403       while (last < n && (byte = my_fgetc(input)) != EOF
2404              && (byte & 0xc0) == 0x80)
2405         utf8_buffer[last++] = byte;
2406 
2407       if (byte == EOF)
2408         return EOF;
2409     }
2410 
2411     utf8_buffer[last] = '\0';
2412 
2413     /* Replace an invalid UTF-8 byte sequence by a single dot.  */
2414     /* In this case the original sequence is lost (unsupported  */
2415     /* encoding).                                               */
2416     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2417     if (langinfo->utf8 && utf8_validate(utf8_buffer) != NULL)
2418     {
2419       byte = utf8_buffer[0] = misc->invalid_char_substitute;
2420       utf8_buffer[1]        = '\0';
2421     }
2422   } while (ll_find(zapped_glyphs_list, utf8_buffer, buffer_cmp) != NULL);
2423 
2424   return byte;
2425 }
2426 
2427 /* =======================================================================*/
2428 /* Expand the string str by replacing all its embedded special characters */
2429 /* by their corresponding escape sequence.                                */
2430 /* dest must be long enough to contain the expanded string.               */
2431 /* ====================================================================== */
2432 size_t
expand(char * src,char * dest,langinfo_t * langinfo,toggle_t * toggles,misc_t * misc)2433 expand(char * src, char * dest, langinfo_t * langinfo, toggle_t * toggles,
2434        misc_t * misc)
2435 {
2436   char   c;
2437   int    n;
2438   int    all_spaces = 1;
2439   char * ptr        = dest;
2440   size_t len        = 0;
2441 
2442   while ((c = *(src++)))
2443   {
2444     /* UTF-8 codepoints take more than on character. */
2445     /* """"""""""""""""""""""""""""""""""""""""""""" */
2446     if ((n = utf8_get_length(c)) > 1)
2447     {
2448       all_spaces = 0;
2449 
2450       if (langinfo->utf8)
2451         /* If the locale is UTF-8 aware, copy src into ptr. */
2452         /* """""""""""""""""""""""""""""""""""""""""""""""" */
2453         do
2454         {
2455           *(ptr++) = c;
2456           len++;
2457         } while (--n && (c = *(src++)));
2458       else
2459       {
2460         /* If not, ignore the bytes composing the UTF-8 */
2461         /* glyph and replace them with a single '.'.    */
2462         /* """""""""""""""""""""""""""""""""""""""""""" */
2463         do
2464         {
2465           /* Skip this byte. */
2466           /* ''''''''''''''' */
2467         } while (--n && ('\0' != *(src++)));
2468 
2469         *(ptr++) = misc->invalid_char_substitute;
2470         len++;
2471       }
2472     }
2473     else
2474       /* This is not an UTF-8 glyph. */
2475       /* """"""""""""""""""""""""""" */
2476       switch (c)
2477       {
2478         case '\a':
2479           *(ptr++) = '\\';
2480           *(ptr++) = 'a';
2481           len += 2;
2482           all_spaces = 0;
2483           break;
2484         case '\b':
2485           *(ptr++) = '\\';
2486           *(ptr++) = 'b';
2487           len += 2;
2488           all_spaces = 0;
2489           break;
2490         case '\t':
2491           *(ptr++) = '\\';
2492           *(ptr++) = 't';
2493           len += 2;
2494           all_spaces = 0;
2495           break;
2496         case '\n':
2497           *(ptr++) = '\\';
2498           *(ptr++) = 'n';
2499           len += 2;
2500           all_spaces = 0;
2501           break;
2502         case '\v':
2503           *(ptr++) = '\\';
2504           *(ptr++) = 'v';
2505           len += 2;
2506           all_spaces = 0;
2507           break;
2508         case '\f':
2509           *(ptr++) = '\\';
2510           *(ptr++) = 'f';
2511           len += 2;
2512           all_spaces = 0;
2513           break;
2514         case '\r':
2515           *(ptr++) = '\\';
2516           *(ptr++) = 'r';
2517           len += 2;
2518           all_spaces = 0;
2519           break;
2520         case '\\':
2521           *(ptr++) = '\\';
2522           *(ptr++) = '\\';
2523           len += 2;
2524           all_spaces = 0;
2525           break;
2526         default:
2527           if (my_isprint(c))
2528           {
2529             *(ptr++)   = c;
2530             all_spaces = 0;
2531           }
2532           else
2533           {
2534             if (toggles->blank_nonprintable)
2535               *(ptr++) = ' ';
2536             else
2537             {
2538               *(ptr++)   = misc->invalid_char_substitute;
2539               all_spaces = 0;
2540             }
2541           }
2542           len++;
2543       }
2544   }
2545 
2546   /* If the word contains only spaces, replace them */
2547   /* by underscores so that it can be seen.         */
2548   /* """""""""""""""""""""""""""""""""""""""""""""" */
2549   if (all_spaces)
2550     memset(dest, ' ', len);
2551 
2552   *ptr = '\0'; /* Ensure that dest has a nul terminator. */
2553 
2554   return len;
2555 }
2556 
2557 /* ===================================================================== */
2558 /* get_word(input): return a char pointer to the next word (as a string) */
2559 /* Accept: a FILE * for the input stream.                                */
2560 /* Return: a char *                                                      */
2561 /*    On Success: the return value will point to a nul-terminated        */
2562 /*                string.                                                */
2563 /*    On Failure: the return value will be set to NULL.                  */
2564 /* ===================================================================== */
2565 char *
get_word(FILE * input,ll_t * word_delims_list,ll_t * record_delims_list,ll_t * zapped_glyphs_list,char * utf8_buffer,unsigned char * is_last,toggle_t * toggles,langinfo_t * langinfo,win_t * win,limit_t * limits,misc_t * misc)2566 get_word(FILE * input, ll_t * word_delims_list, ll_t * record_delims_list,
2567          ll_t * zapped_glyphs_list, char * utf8_buffer, unsigned char * is_last,
2568          toggle_t * toggles, langinfo_t * langinfo, win_t * win,
2569          limit_t * limits, misc_t * misc)
2570 {
2571   char * temp = NULL;
2572   int    byte;
2573   long   utf8_count = 0; /* count chars used in current allocation. */
2574   long   wordsize;       /* size of current allocation in chars.    */
2575   int    is_dquote;      /* double quote presence indicator.        */
2576   int    is_squote;      /* single quote presence indicator.        */
2577   int    is_special;     /* a character is special after a \        */
2578 
2579   /* Skip leading delimiters. */
2580   /* """""""""""""""""""""""" */
2581   byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
2582 
2583   while (byte == EOF
2584          || ll_find(word_delims_list, utf8_buffer, buffer_cmp) != NULL)
2585   {
2586     if (byte == EOF)
2587       return NULL;
2588 
2589     byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
2590   }
2591 
2592   /* Allocate initial word storage space. */
2593   /* """""""""""""""""""""""""""""""""""" */
2594   temp = xmalloc(wordsize = CHARSCHUNK);
2595 
2596   /* Start stashing bytes. Stop when we meet a non delimiter or EOF. */
2597   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2598   utf8_count = 0;
2599   is_dquote  = 0;
2600   is_squote  = 0;
2601   is_special = 0;
2602 
2603   while (byte != EOF)
2604   {
2605     size_t i = 0;
2606 
2607     if (utf8_count >= limits->word_length)
2608     {
2609       fprintf(stderr,
2610               "The length of a word has reached the limit of "
2611               "%ld characters.\n",
2612               limits->word_length);
2613 
2614       exit(EXIT_FAILURE);
2615     }
2616 
2617     if (byte == '\\' && !is_special)
2618     {
2619       is_special = 1;
2620       goto next;
2621     }
2622 
2623     /* Parse special characters. */
2624     /* """"""""""""""""""""""""" */
2625     if (is_special)
2626       switch (byte)
2627       {
2628         case 'a':
2629           utf8_buffer[0] = byte = '\a';
2630           utf8_buffer[1]        = '\0';
2631           break;
2632 
2633         case 'b':
2634           utf8_buffer[0] = byte = '\b';
2635           utf8_buffer[1]        = '\0';
2636           break;
2637 
2638         case 't':
2639           utf8_buffer[0] = byte = '\t';
2640           utf8_buffer[1]        = '\0';
2641           break;
2642 
2643         case 'n':
2644           utf8_buffer[0] = byte = '\n';
2645           utf8_buffer[1]        = '\0';
2646           break;
2647 
2648         case 'v':
2649           utf8_buffer[0] = byte = '\v';
2650           utf8_buffer[1]        = '\0';
2651           break;
2652 
2653         case 'f':
2654           utf8_buffer[0] = byte = '\f';
2655           utf8_buffer[1]        = '\0';
2656           break;
2657 
2658         case 'r':
2659           utf8_buffer[0] = byte = '\r';
2660           utf8_buffer[1]        = '\0';
2661           break;
2662 
2663         case 'u':
2664           utf8_buffer[0] = '\\';
2665           utf8_buffer[1] = 'u';
2666           utf8_buffer[2] = '\0';
2667           break;
2668 
2669         case 'U':
2670           utf8_buffer[0] = '\\';
2671           utf8_buffer[1] = 'U';
2672           utf8_buffer[2] = '\0';
2673           break;
2674 
2675         case '\\':
2676           utf8_buffer[0] = byte = '\\';
2677           utf8_buffer[1]        = '\0';
2678           break;
2679       }
2680     else
2681     {
2682       if (!misc->ignore_quotes)
2683       {
2684         /* Manage double quotes. */
2685         /* """"""""""""""""""""" */
2686         if (byte == '"' && !is_squote)
2687           is_dquote = !is_dquote;
2688 
2689         /* Manage single quotes. */
2690         /* """"""""""""""""""""" */
2691         if (byte == '\'' && !is_dquote)
2692           is_squote = !is_squote;
2693       }
2694     }
2695 
2696     /* Only consider delimiters when outside quotations. */
2697     /* """"""""""""""""""""""""""""""""""""""""""""""""" */
2698     if ((!is_dquote && !is_squote)
2699         && ll_find(word_delims_list, utf8_buffer, buffer_cmp) != NULL)
2700       break;
2701 
2702     if (!misc->ignore_quotes)
2703     {
2704       /* We no dot count the significant quotes. */
2705       /* """"""""""""""""""""""""""""""""""""""" */
2706       if (!is_special
2707           && ((byte == '"' && !is_squote) || (byte == '\'' && !is_dquote)))
2708       {
2709         is_special = 0;
2710         goto next;
2711       }
2712     }
2713 
2714     /* Feed temp with the content of utf8_buffer. */
2715     /* """""""""""""""""""""""""""""""""""""""""" */
2716     while (utf8_buffer[i] != '\0')
2717     {
2718       if (utf8_count >= wordsize - 1)
2719         temp = xrealloc(temp,
2720                         wordsize += (utf8_count / CHARSCHUNK + 1) * CHARSCHUNK);
2721 
2722       *(temp + utf8_count++) = utf8_buffer[i];
2723       i++;
2724     }
2725 
2726     is_special = 0;
2727 
2728   next:
2729     byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
2730   }
2731 
2732   /* Nul-terminate the word to make it a string. */
2733   /* """"""""""""""""""""""""""""""""""""""""""" */
2734   *(temp + utf8_count) = '\0';
2735 
2736   /* Replace the UTF-8 ASCII representations in the word just */
2737   /* read by their binary values.                             */
2738   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2739   utf8_interpret(temp, langinfo, misc->invalid_char_substitute);
2740 
2741   /* Skip all field delimiters before a record delimiter. */
2742   /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
2743   if (ll_find(record_delims_list, utf8_buffer, buffer_cmp) == NULL)
2744   {
2745     byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
2746 
2747     while (byte != EOF
2748            && ll_find(word_delims_list, utf8_buffer, buffer_cmp) != NULL
2749            && ll_find(record_delims_list, utf8_buffer, buffer_cmp) == NULL)
2750       byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
2751 
2752     if (langinfo->utf8 && utf8_get_length(utf8_buffer[0]) > 1)
2753     {
2754       size_t pos;
2755 
2756       pos = strlen(utf8_buffer);
2757       while (pos > 0)
2758         my_ungetc(utf8_buffer[--pos]);
2759     }
2760     else
2761       my_ungetc(byte);
2762   }
2763 
2764   /* Mark it as the last word of a record if its sequence matches a */
2765   /* record delimiter.                                              */
2766   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2767   if (byte == EOF
2768       || ((win->col_mode || win->line_mode || win->tab_mode)
2769           && ll_find(record_delims_list, utf8_buffer, buffer_cmp) != NULL))
2770     *is_last = 1;
2771   else
2772     *is_last = 0;
2773 
2774   /* Remove the ANSI color escape sequences from the word. */
2775   /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
2776   strip_ansi_color(temp, toggles, misc);
2777 
2778   return temp;
2779 }
2780 
2781 /* ================================================================ */
2782 /* Convert the 8 first colors from setf/setaf coding to setaf/setf. */
2783 /* ================================================================ */
2784 short
color_transcode(short color)2785 color_transcode(short color)
2786 {
2787   switch (color)
2788   {
2789     case 1:
2790       return 4;
2791     case 3:
2792       return 6;
2793     case 4:
2794       return 1;
2795     case 6:
2796       return 3;
2797     default:
2798       return color;
2799   }
2800 }
2801 
2802 /* ========================================================== */
2803 /* Set a foreground color according to terminal capabilities. */
2804 /* ========================================================== */
2805 void
set_foreground_color(term_t * term,short color)2806 set_foreground_color(term_t * term, short color)
2807 {
2808   if (term->color_method == 0)
2809   {
2810     if (term->has_setf)
2811       tputs(TPARM2(set_foreground, color), 1, outch);
2812     if (term->has_setaf)
2813       tputs(TPARM2(set_a_foreground, color_transcode(color)), 1, outch);
2814   }
2815 
2816   else if (term->color_method == 1)
2817   {
2818     if (term->has_setaf)
2819       tputs(TPARM2(set_a_foreground, color), 1, outch);
2820     if (term->has_setf)
2821       tputs(TPARM2(set_foreground, color_transcode(color)), 1, outch);
2822   }
2823 }
2824 
2825 /* ========================================================== */
2826 /* Set a background color according to terminal capabilities. */
2827 /* ========================================================== */
2828 void
set_background_color(term_t * term,short color)2829 set_background_color(term_t * term, short color)
2830 {
2831   if (term->color_method == 0)
2832   {
2833     if (term->has_setb)
2834       tputs(TPARM2(set_background, color), 1, outch);
2835     if (term->has_setab)
2836       tputs(TPARM2(set_a_background, color_transcode(color)), 1, outch);
2837   }
2838 
2839   else if (term->color_method == 1)
2840   {
2841     if (term->has_setab)
2842       tputs(TPARM2(set_a_background, color), 1, outch);
2843     if (term->has_setb)
2844       tputs(TPARM2(set_background, color_transcode(color)), 1, outch);
2845   }
2846 }
2847 
2848 /* ======================================================= */
2849 /* Put a scrolling symbol at the first column of the line. */
2850 /* ======================================================= */
2851 void
left_margin_putp(char * s,term_t * term,win_t * win)2852 left_margin_putp(char * s, term_t * term, win_t * win)
2853 {
2854   apply_attr(term, win->shift_attr);
2855 
2856   /* We won't print this symbol when not in column mode. */
2857   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2858   if (*s != '\0')
2859     fputs(s, stdout);
2860 
2861   tputs(TPARM1(exit_attribute_mode), 1, outch);
2862 }
2863 
2864 /* ====================================================== */
2865 /* Put a scrolling symbol at the last column of the line. */
2866 /* ====================================================== */
2867 void
right_margin_putp(char * s1,char * s2,langinfo_t * langinfo,term_t * term,win_t * win,long line,long offset)2868 right_margin_putp(char * s1, char * s2, langinfo_t * langinfo, term_t * term,
2869                   win_t * win, long line, long offset)
2870 {
2871   apply_attr(term, win->bar_attr);
2872 
2873   if (term->has_hpa)
2874     tputs(TPARM2(column_address, offset + win->max_width + 1), 1, outch);
2875   else if (term->has_cursor_address)
2876     tputs(TPARM3(cursor_address, term->curs_line + line - 2,
2877                  offset + win->max_width + 1),
2878           1, outch);
2879   else if (term->has_parm_right_cursor)
2880   {
2881     fputc('\r', stdout);
2882     tputs(TPARM2(parm_right_cursor, offset + win->max_width + 1), 1, outch);
2883   }
2884   else
2885   {
2886     long i;
2887 
2888     fputc('\r', stdout);
2889     for (i = 0; i < offset + win->max_width + 1; i++)
2890       tputs(TPARM1(cursor_right), 1, outch);
2891   }
2892 
2893   if (langinfo->utf8)
2894     fputs(s1, stdout);
2895   else
2896     fputs(s2, stdout);
2897 
2898   tputs(TPARM1(exit_attribute_mode), 1, outch);
2899 }
2900 
2901 /* *************** */
2902 /* Core functions. */
2903 /* *************** */
2904 
2905 /* ============================================================== */
2906 /* Split the lines of the message given to -m to a linked list of */
2907 /* lines.                                                         */
2908 /* Also fill the maximum screen width and the maximum number      */
2909 /* of bytes of the longest line.                                  */
2910 /* ============================================================== */
2911 void
get_message_lines(char * message,ll_t * message_lines_list,long * message_max_width,long * message_max_len)2912 get_message_lines(char * message, ll_t * message_lines_list,
2913                   long * message_max_width, long * message_max_len)
2914 {
2915   char *    str;
2916   char *    ptr;
2917   char *    cr_ptr;
2918   long      n;
2919   wchar_t * w = NULL;
2920 
2921   *message_max_width = 0;
2922   *message_max_len   = 0;
2923   ptr                = message;
2924 
2925   /* For each line terminated with a EOL character. */
2926   /* """""""""""""""""""""""""""""""""""""""""""""" */
2927   while (*ptr != '\0' && (cr_ptr = strchr(ptr, '\n')) != NULL)
2928   {
2929     if (cr_ptr > ptr)
2930     {
2931       str               = xmalloc(cr_ptr - ptr + 1);
2932       str[cr_ptr - ptr] = '\0';
2933       memcpy(str, ptr, cr_ptr - ptr);
2934     }
2935     else
2936       str = xstrdup("");
2937 
2938     ll_append(message_lines_list, str);
2939 
2940     /* If needed, update the message maximum width. */
2941     /* """""""""""""""""""""""""""""""""""""""""""" */
2942     n = wcswidth((w = utf8_strtowcs(str)), utf8_strlen(str));
2943     free(w);
2944 
2945     if (n > *message_max_width)
2946       *message_max_width = n;
2947 
2948     /* If needed, update the message maximum number */
2949     /* of bytes used by the longest line.           */
2950     /* """""""""""""""""""""""""""""""""""""""""""" */
2951     if ((n = (long)strlen(str)) > *message_max_len)
2952       *message_max_len = n;
2953 
2954     ptr = cr_ptr + 1;
2955   }
2956 
2957   /* For the last line. */
2958   /* """""""""""""""""" */
2959   if (*ptr != '\0')
2960   {
2961     ll_append(message_lines_list, xstrdup(ptr));
2962 
2963     n = wcswidth((w = utf8_strtowcs(ptr)), utf8_strlen(ptr));
2964     free(w);
2965 
2966     if (n > *message_max_width)
2967       *message_max_width = n;
2968 
2969     /* If needed, update the message maximum number */
2970     /* of bytes used by the longest line.           */
2971     /* """""""""""""""""""""""""""""""""""""""""""" */
2972     if ((n = (long)strlen(ptr)) > *message_max_len)
2973       *message_max_len = n;
2974   }
2975   else
2976     ll_append(message_lines_list, xstrdup(""));
2977 }
2978 
2979 /* =================================================================== */
2980 /* Set the new start and the new end of the window structure according */
2981 /* to the current cursor position.                                     */
2982 /* =================================================================== */
2983 void
set_win_start_end(win_t * win,long current,long last)2984 set_win_start_end(win_t * win, long current, long last)
2985 {
2986   long cur_line, end_line;
2987 
2988   cur_line = line_nb_of_word_a[current];
2989   if (cur_line == last)
2990     win->end = count - 1;
2991   else
2992   {
2993     /* In help mode we must not modify the windows start/end position as */
2994     /* It must be redrawn exactly as it was before.                      */
2995     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2996     if (!help_mode)
2997     {
2998       if (cur_line + win->max_lines / 2 + 1 <= last)
2999         win->end = first_word_in_line_a[cur_line + win->max_lines / 2 + 1] - 1;
3000       else
3001         win->end = first_word_in_line_a[last];
3002     }
3003   }
3004   end_line = line_nb_of_word_a[win->end];
3005 
3006   if (end_line < win->max_lines)
3007     win->start = 0;
3008   else
3009     win->start = first_word_in_line_a[end_line - win->max_lines + 1];
3010 }
3011 
3012 /* ======================================================================== */
3013 /* Set the metadata associated with a word, its starting and ending         */
3014 /* position, the line in which it is put and so on.                         */
3015 /* Set win.start win.end and the starting and ending position of each word. */
3016 /* This function is only called initially, when resizing the terminal and   */
3017 /* potentially when the search function is used.                            */
3018 /* ======================================================================== */
3019 long
build_metadata(term_t * term,long count,win_t * win)3020 build_metadata(term_t * term, long count, win_t * win)
3021 {
3022   long      i = 0;
3023   long      word_len;
3024   long      len  = 0;
3025   long      last = 0;
3026   long      word_width;
3027   long      tab_count; /* Current number of words in the line, *
3028                         | used in tab_mode.                    */
3029   wchar_t * w;
3030 
3031   line_nb_of_word_a[0]    = 0;
3032   first_word_in_line_a[0] = 0;
3033 
3034   /* In column mode we need to calculate win->max_width, first initialize */
3035   /* it to 0 and increment it later in the loop.                          */
3036   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3037   if (!win->col_mode)
3038     win->max_width = 0;
3039 
3040   tab_count = 0;
3041   while (i < count)
3042   {
3043     /* Determine the number of screen positions used by the word. */
3044     /* Note: mbstowcs will always succeed here as word_a[i].str   */
3045     /*       has already been utf8_validated/repaired.            */
3046     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3047     word_len   = mbstowcs(NULL, word_a[i].str, 0);
3048     word_width = wcswidth((w = utf8_strtowcs(word_a[i].str)), word_len);
3049 
3050     /* Manage the case where the word is larger than the terminal width. */
3051     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3052     if (word_width >= term->ncolumns - 2)
3053     {
3054       /* Shorten the word until it fits. */
3055       /* """"""""""""""""""""""""""""""" */
3056       do
3057       {
3058         word_width = wcswidth(w, word_len--);
3059       } while (word_len > 0 && word_width >= term->ncolumns - 2);
3060     }
3061     free(w);
3062 
3063     /* Look if there is enough remaining place on the line when not in   */
3064     /* column mode. Force a break if the 'is_last' flag is set in all    */
3065     /* modes or if we hit the max number of allowed columns in tab mode. */
3066     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3067     if ((!win->col_mode && !win->line_mode
3068          && (len + word_width + 1) >= term->ncolumns - 1)
3069         || ((win->col_mode || win->line_mode || win->tab_mode) && i > 0
3070             && word_a[i - 1].is_last)
3071         || (win->tab_mode && win->max_cols > 0 && tab_count >= win->max_cols))
3072     {
3073 
3074       /* We must build another line. */
3075       /* """"""""""""""""""""""""""" */
3076       line_nb_of_word_a[i]       = ++last;
3077       first_word_in_line_a[last] = i;
3078 
3079       word_a[i].start = 0;
3080 
3081       len           = word_width + 1; /* Resets the current line length.    */
3082       tab_count     = 1;              /* Resets the current number of words *
3083                                        | in the line.                       */
3084       word_a[i].end = word_width - 1;
3085       word_a[i].mb  = word_len;
3086     }
3087     else
3088     {
3089       word_a[i].start      = len;
3090       word_a[i].end        = word_a[i].start + word_width - 1;
3091       word_a[i].mb         = word_len;
3092       line_nb_of_word_a[i] = last;
3093 
3094       len += word_width + 1; /* Increase line length.                */
3095       tab_count++;           /* We've seen another word in the line/ */
3096     }
3097 
3098     /* If not in column mode, we need to calculate win->(real_)max_width */
3099     /* as it hasn't been already done.                                   */
3100     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3101     if (len > win->max_width)
3102     {
3103       if (len > term->ncolumns)
3104         win->max_width = term->ncolumns - 2;
3105       else
3106         win->max_width = len;
3107     }
3108 
3109     if (len > win->real_max_width)
3110       win->real_max_width = len;
3111 
3112     i++;
3113   }
3114 
3115   if (!win->center || win->max_width > term->ncolumns - 2)
3116     win->offset = 0;
3117   else
3118     win->offset = (term->ncolumns - 2 - win->max_width) / 2;
3119 
3120   /* We need to recalculate win->start and win->end here */
3121   /* because of a possible terminal resizing.            */
3122   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
3123   set_win_start_end(win, current, last);
3124 
3125   return last;
3126 }
3127 
3128 /* ======================================================================= */
3129 /* Helper function used by disp_word to print the cursor with the matching */
3130 /* characters of the word highlighted.                                     */
3131 /* ======================================================================= */
3132 void
disp_cursor_word(long pos,win_t * win,term_t * term,int err)3133 disp_cursor_word(long pos, win_t * win, term_t * term, int err)
3134 {
3135   size_t i;
3136   int    att_set = 0;
3137   char * p       = word_a[pos].str + daccess.flength;
3138   char * np;
3139 
3140   /* Set the cursor attribute. */
3141   /* """"""""""""""""""""""""" */
3142   tputs(TPARM1(exit_attribute_mode), 1, outch);
3143 
3144   tputs(TPARM1(exit_attribute_mode), 1, outch);
3145   if (word_a[pos].is_tagged)
3146     apply_attr(term, win->cursor_on_tag_attr);
3147   else
3148     apply_attr(term, win->cursor_attr);
3149 
3150   for (i = 0; i < word_a[pos].mb - daccess.flength; i++)
3151   {
3152     if (BIT_ISSET(word_a[pos].bitmap, i))
3153     {
3154       if (!att_set)
3155       {
3156         att_set = 1;
3157 
3158         /* Set the buffer display attribute. */
3159         /* """"""""""""""""""""""""""""""""" */
3160         tputs(TPARM1(exit_attribute_mode), 1, outch);
3161         if (err)
3162           apply_attr(term, win->match_err_text_attr);
3163         else
3164           apply_attr(term, win->match_text_attr);
3165 
3166         if (word_a[pos].is_tagged)
3167           apply_attr(term, win->cursor_on_tag_attr);
3168         else
3169           apply_attr(term, win->cursor_attr);
3170       }
3171     }
3172     else
3173     {
3174       if (att_set)
3175       {
3176         att_set = 0;
3177 
3178         /* Set the search cursor attribute. */
3179         /* """""""""""""""""""""""""""""""" */
3180         tputs(TPARM1(exit_attribute_mode), 1, outch);
3181         if (word_a[pos].is_tagged)
3182           apply_attr(term, win->cursor_on_tag_attr);
3183         else
3184           apply_attr(term, win->cursor_attr);
3185       }
3186     }
3187     np = utf8_next(p);
3188     if (np == NULL)
3189       fputs(p, stdout);
3190     else
3191       printf("%.*s", (int)(np - p), p);
3192     p = np;
3193   }
3194 }
3195 
3196 /* ========================================================== */
3197 /* Helper function used by disp_word to print a matching word */
3198 /* with the matching characters of the word highlighted.      */
3199 /* ========================================================== */
3200 void
disp_matching_word(long pos,win_t * win,term_t * term,int is_current,int err)3201 disp_matching_word(long pos, win_t * win, term_t * term, int is_current,
3202                    int err)
3203 {
3204   size_t i;
3205   int    att_set = 0;
3206   char * p       = word_a[pos].str + daccess.flength;
3207   char * np;
3208   long   level = 0;
3209 
3210   level = word_a[pos].special_level;
3211 
3212   /* Set the search cursor attribute. */
3213   /* """""""""""""""""""""""""""""""" */
3214   tputs(TPARM1(exit_attribute_mode), 1, outch);
3215 
3216   if (!is_current)
3217   {
3218     if (err)
3219       apply_attr(term, win->match_err_field_attr);
3220     else
3221     {
3222       if (level > 0)
3223         apply_attr(term, win->special_attr[level - 1]);
3224       else
3225         apply_attr(term, win->match_field_attr);
3226     }
3227   }
3228   else
3229   {
3230     if (err)
3231       apply_attr(term, win->search_err_field_attr);
3232     else
3233       apply_attr(term, win->search_field_attr);
3234   }
3235 
3236   if (word_a[pos].is_tagged)
3237     apply_attr(term, win->tag_attr);
3238 
3239   for (i = 0; i < word_a[pos].mb - daccess.flength; i++)
3240   {
3241     if (BIT_ISSET(word_a[pos].bitmap, i))
3242     {
3243       if (!att_set)
3244       {
3245         att_set = 1;
3246 
3247         /* Set the buffer display attribute. */
3248         /* """"""""""""""""""""""""""""""""" */
3249         tputs(TPARM1(exit_attribute_mode), 1, outch);
3250         if (!is_current)
3251         {
3252           if (err)
3253             apply_attr(term, win->match_err_text_attr);
3254           else
3255             apply_attr(term, win->match_text_attr);
3256         }
3257         else
3258           apply_attr(term, win->search_text_attr);
3259 
3260         if (word_a[pos].is_tagged)
3261           apply_attr(term, win->tag_attr);
3262       }
3263     }
3264     else
3265     {
3266       if (att_set)
3267       {
3268         att_set = 0;
3269 
3270         /* Set the search cursor attribute. */
3271         /* """""""""""""""""""""""""""""""" */
3272         tputs(TPARM1(exit_attribute_mode), 1, outch);
3273         if (!is_current)
3274         {
3275           if (err)
3276             apply_attr(term, win->match_err_field_attr);
3277           else
3278           {
3279             if (level > 0)
3280               apply_attr(term, win->special_attr[level - 1]);
3281             else
3282               apply_attr(term, win->match_field_attr);
3283           }
3284         }
3285         else
3286         {
3287           if (err)
3288             apply_attr(term, win->search_err_field_attr);
3289           else
3290             apply_attr(term, win->search_field_attr);
3291         }
3292 
3293         if (word_a[pos].is_tagged)
3294           apply_attr(term, win->tag_attr);
3295       }
3296     }
3297 
3298     np = utf8_next(p);
3299     if (np == NULL)
3300       fputs(p, stdout);
3301     else
3302       printf("%.*s", (int)(np - p), p);
3303     p = np;
3304   }
3305 }
3306 
3307 /* ====================================================================== */
3308 /* Display a word in, the windows. Manages the following different cases: */
3309 /* - Search mode display                                                  */
3310 /* - Cursor display                                                       */
3311 /* - Normal display                                                       */
3312 /* - Color or mono display                                                */
3313 /* ====================================================================== */
3314 void
disp_word(long pos,search_mode_t search_mode,search_data_t * search_data,term_t * term,win_t * win,char * tmp_word)3315 disp_word(long pos, search_mode_t search_mode, search_data_t * search_data,
3316           term_t * term, win_t * win, char * tmp_word)
3317 {
3318   long s = word_a[pos].start;
3319   long e = word_a[pos].end;
3320   long p;
3321 
3322   char * buffer = search_data->buf;
3323 
3324   if (pos == current)
3325   {
3326     if (search_mode != NONE)
3327     {
3328       utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
3329       if (word_a[pos].is_numbered)
3330       {
3331         /* Set the direct access number attribute. */
3332         /* """"""""""""""""""""""""""""""""""""""" */
3333         apply_attr(term, win->daccess_attr);
3334 
3335         /* And print it. */
3336         /* """"""""""""" */
3337         fputs(daccess.left, stdout);
3338         printf("%.*s", daccess.length, tmp_word + 1);
3339         fputs(daccess.right, stdout);
3340         tputs(TPARM1(exit_attribute_mode), 1, outch);
3341         fputc(' ', stdout);
3342       }
3343       else if (daccess.length > 0)
3344       {
3345         /* Prints the leading spaces. */
3346         /* """""""""""""""""""""""""" */
3347         tputs(TPARM1(exit_attribute_mode), 1, outch);
3348         printf("%.*s", daccess.flength, tmp_word);
3349       }
3350 
3351       /* Set the search cursor attribute. */
3352       /* """""""""""""""""""""""""""""""" */
3353       if (search_data->fuzzy_err)
3354         apply_attr(term, win->search_err_field_attr);
3355       else
3356         apply_attr(term, win->search_field_attr);
3357 
3358       /* The tab attribute must complete the attributes already set. */
3359       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3360       if (word_a[pos].is_tagged)
3361         apply_attr(term, win->tag_attr);
3362 
3363       /* Print the word part. */
3364       /* """""""""""""""""""" */
3365       fputs(tmp_word + daccess.flength, stdout);
3366 
3367       if (buffer[0] != '\0')
3368       {
3369         long i = 0;
3370 
3371         /* Put the cursor at the beginning of the word. */
3372         /* """""""""""""""""""""""""""""""""""""""""""" */
3373         for (i = 0; i < e - s + 1 - daccess.flength; i++)
3374           tputs(TPARM1(cursor_left), 1, outch);
3375 
3376         tputs(TPARM1(exit_attribute_mode), 1, outch);
3377 
3378         /* Set the search cursor attribute. */
3379         /* """""""""""""""""""""""""""""""" */
3380         if (search_data->fuzzy_err)
3381           apply_attr(term, win->search_err_field_attr);
3382         else
3383           apply_attr(term, win->search_field_attr);
3384 
3385         disp_matching_word(pos, win, term, 1, search_data->fuzzy_err);
3386       }
3387     }
3388     else
3389     {
3390       if (daccess.length > 0)
3391       {
3392         /* If this word is not numbered, reset the display */
3393         /* attributes before printing the leading spaces.  */
3394         /* """"""""""""""""""""""""""""""""""""""""""""""" */
3395         if (!word_a[pos].is_numbered)
3396         {
3397           /* Print the non significant part of the word. */
3398           /* """"""""""""""""""""""""""""""""""""""""""" */
3399           tputs(TPARM1(exit_attribute_mode), 1, outch);
3400           printf("%.*s", daccess.flength - 1, word_a[pos].str);
3401           tputs(TPARM1(exit_attribute_mode), 1, outch);
3402           fputc(' ', stdout);
3403         }
3404         else
3405         {
3406           apply_attr(term, win->daccess_attr);
3407 
3408           /* Print the non significant part of the word. */
3409           /* """"""""""""""""""""""""""""""""""""""""""" */
3410           fputs(daccess.left, stdout);
3411           printf("%.*s", daccess.length, word_a[pos].str + 1);
3412           fputs(daccess.right, stdout);
3413           tputs(TPARM1(exit_attribute_mode), 1, outch);
3414           fputc(' ', stdout);
3415         }
3416       }
3417 
3418       /* If we are not in search mode, display a normal cursor. */
3419       /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
3420       utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
3421       if (word_a[pos].is_matching)
3422         disp_cursor_word(pos, win, term, search_data->fuzzy_err);
3423       else
3424       {
3425         if (word_a[pos].is_tagged)
3426           apply_attr(term, win->cursor_on_tag_attr);
3427         else
3428           apply_attr(term, win->cursor_attr);
3429 
3430         fputs(tmp_word + daccess.flength, stdout);
3431       }
3432     }
3433     tputs(TPARM1(exit_attribute_mode), 1, outch);
3434   }
3435   else
3436   {
3437     /* Display a normal word without any attribute. */
3438     /* """""""""""""""""""""""""""""""""""""""""""" */
3439     utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
3440 
3441     /* If words are numbered, emphasis their numbers. */
3442     /* """""""""""""""""""""""""""""""""""""""""""""" */
3443     if (word_a[pos].is_numbered)
3444     {
3445       apply_attr(term, win->daccess_attr);
3446 
3447       fputs(daccess.left, stdout);
3448       printf("%.*s", daccess.length, tmp_word + 1);
3449       fputs(daccess.right, stdout);
3450 
3451       tputs(TPARM1(exit_attribute_mode), 1, outch);
3452       fputc(' ', stdout);
3453     }
3454     else if (daccess.length > 0)
3455     {
3456       long i;
3457 
3458       /* Insert leading spaces if the word is non numbered and */
3459       /* padding for all words is set.                         */
3460       /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
3461       tputs(TPARM1(exit_attribute_mode), 1, outch);
3462       if (daccess.padding == 'a')
3463         for (i = 0; i < daccess.flength; i++)
3464           fputc(' ', stdout);
3465     }
3466 
3467     if (!word_a[pos].is_selectable)
3468       apply_attr(term, win->exclude_attr);
3469     else if (word_a[pos].special_level > 0)
3470     {
3471       long level = word_a[pos].special_level - 1;
3472 
3473       apply_attr(term, win->special_attr[level]);
3474     }
3475     else
3476       apply_attr(term, win->include_attr);
3477 
3478     if (word_a[pos].is_matching)
3479       disp_matching_word(pos, win, term, 0, search_data->fuzzy_err);
3480     else
3481     {
3482       if (word_a[pos].is_tagged)
3483         apply_attr(term, win->tag_attr);
3484 
3485       if ((daccess.length > 0 && daccess.padding == 'a')
3486           || word_a[pos].is_numbered)
3487         fputs(tmp_word + daccess.flength, stdout);
3488       else
3489         fputs(tmp_word, stdout);
3490     }
3491 
3492     tputs(TPARM1(exit_attribute_mode), 1, outch);
3493   }
3494 }
3495 
3496 /* ======================================== */
3497 /* Display a message line above the window. */
3498 /* ======================================== */
3499 void
disp_message(ll_t * message_lines_list,long message_max_width,long message_max_len,term_t * term,win_t * win,langinfo_t * langinfo)3500 disp_message(ll_t * message_lines_list, long message_max_width,
3501              long message_max_len, term_t * term, win_t * win,
3502              langinfo_t * langinfo)
3503 {
3504   ll_node_t * node;
3505   char *      line;
3506   char *      buf;
3507   size_t      len;
3508   long        size;
3509   long        offset;
3510   wchar_t *   w;
3511   int         n   = 0; /* Counter used to display message lines. */
3512   int         cut = 0; /* Will be 1 if the message is shortened. */
3513 
3514   sigset_t mask;
3515 
3516   win->message_lines = 0;
3517 
3518   /* Do nothing if there is no message to display. */
3519   /* """"""""""""""""""""""""""""""""""""""""""""" */
3520   if (message_lines_list == NULL)
3521     return;
3522 
3523   /* Recalculate the number of to-be-displayed lines in the messages */
3524   /* if space is missing.                                            */
3525   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3526   if (term->nlines < 3)
3527     return;
3528 
3529   win->message_lines = message_lines_list->len;
3530   if (win->message_lines > term->nlines - 2)
3531   {
3532     win->message_lines = term->nlines - 2;
3533     win->max_lines     = term->nlines - win->message_lines - 1;
3534     cut                = 1;
3535   }
3536   win->message_lines++;
3537 
3538   /* Deactivate the periodic timer to prevent the interruptions to corrupt */
3539   /* screen by altering the timing of the decoding of the terminfo         */
3540   /* capabilities.                                                         */
3541   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3542   sigemptyset(&mask);
3543   sigaddset(&mask, SIGALRM);
3544   sigprocmask(SIG_BLOCK, &mask, NULL);
3545 
3546   node = message_lines_list->head;
3547   buf  = xmalloc(message_max_len + 1);
3548 
3549   /* Follow the message lines list and display each line. */
3550   /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
3551   for (n = 1; n < win->message_lines; n++)
3552   {
3553     long i;
3554 
3555     line = node->data;
3556     len  = utf8_strlen(line);
3557     w    = utf8_strtowcs(line);
3558 
3559     /* Adjust size and len if the terminal is not large enough. */
3560     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3561     size = wcswidth(w, len);
3562     while (len > 0 && size > term->ncolumns)
3563       size = wcswidth(w, --len);
3564 
3565     free(w);
3566 
3567     /* Compute the offset from the left screen border if -M option is set. */
3568     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3569     offset = (term->ncolumns - message_max_width - 3) / 2;
3570 
3571     if (win->center && offset > 0)
3572       for (i = 0; i < offset; i++)
3573         fputc(' ', stdout);
3574 
3575     apply_attr(term, win->message_attr);
3576 
3577     /* Only print the start of a line if the screen width if too small. */
3578     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3579     utf8_strprefix(buf, line, len, &size);
3580 
3581     /* Print the line without the ending \n. */
3582     /* ''''''''''''''''''''''''''''''''''''' */
3583     if (n > 1 && cut && n == win->message_lines - 1)
3584     {
3585       if (langinfo->utf8)
3586         fputs(msg_arr_down, stdout);
3587       else
3588         fputc('v', stdout);
3589     }
3590     else
3591       printf("%s", buf);
3592 
3593     /* Complete the short line with spaces until it reach the */
3594     /* message max size.                                      */
3595     /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */
3596     for (i = size; i < message_max_width; i++)
3597     {
3598       if (i + (offset < 0 ? 0 : offset) >= term->ncolumns)
3599         break;
3600       fputc(' ', stdout);
3601     }
3602 
3603     /* Drop the attributes and print a \n. */
3604     /* ''''''''''''''''''''''''''''''''''' */
3605     if (term->nlines > 2)
3606     {
3607       tputs(TPARM1(exit_attribute_mode), 1, outch);
3608       puts("");
3609     }
3610 
3611     node = node->next;
3612   }
3613 
3614   /* Add an empty line without attribute to separate the menu title */
3615   /* and the menu content.                                          */
3616   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3617   puts("");
3618 
3619   free(buf);
3620 
3621   /* Re-enable the periodic timer. */
3622   /* """"""""""""""""""""""""""""" */
3623   sigprocmask(SIG_UNBLOCK, &mask, NULL);
3624 }
3625 
3626 /* ============================= */
3627 /* Display the selection window. */
3628 /* ============================= */
3629 long
disp_lines(win_t * win,toggle_t * toggles,long current,long count,search_mode_t search_mode,search_data_t * search_data,term_t * term,long last_line,char * tmp_word,langinfo_t * langinfo)3630 disp_lines(win_t * win, toggle_t * toggles, long current, long count,
3631            search_mode_t search_mode, search_data_t * search_data,
3632            term_t * term, long last_line, char * tmp_word,
3633            langinfo_t * langinfo)
3634 {
3635   long lines_disp;
3636   long i;
3637   char scroll_symbol[5];
3638   long len;
3639   long display_bar;
3640 
3641   sigset_t mask;
3642 
3643   /* Disable the periodic timer to prevent the interruptions to corrupt */
3644   /* screen by altering the timing of the decoding of the terminfo      */
3645   /* capabilities.                                                      */
3646   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3647   sigemptyset(&mask);
3648   sigaddset(&mask, SIGALRM);
3649   sigprocmask(SIG_BLOCK, &mask, NULL);
3650 
3651   scroll_symbol[0] = ' ';
3652   scroll_symbol[1] = '\0';
3653 
3654   lines_disp = 1;
3655 
3656   tputs(TPARM1(save_cursor), 1, outch);
3657 
3658   i = win->start;
3659 
3660   /* Modify the max number of displayed lines if we do not have */
3661   /* enough place.                                              */
3662   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3663   if (win->max_lines > term->nlines - win->message_lines)
3664     win->max_lines = term->nlines - win->message_lines;
3665 
3666   if (last_line >= win->max_lines)
3667     display_bar = 1;
3668   else
3669     display_bar = 0;
3670 
3671   if (win->col_mode || win->line_mode)
3672     len = term->ncolumns - 3;
3673   else
3674     len = term->ncolumns - 2;
3675 
3676   /* If in column mode and the sum of the columns sizes + gutters is      */
3677   /* greater than the terminal width,  then prepend a space to be able to */
3678   /* display the left arrow indicating that the first displayed column    */
3679   /* is not the first one.                                                */
3680   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3681   if (len > 1
3682       && ((win->col_mode || win->line_mode)
3683           && win->real_max_width > term->ncolumns - 2))
3684   {
3685     if (win->first_column > 0)
3686     {
3687       if (langinfo->utf8)
3688         strcpy(scroll_symbol, shift_left_sym);
3689       else
3690         strcpy(scroll_symbol, "<");
3691     }
3692   }
3693   else
3694     scroll_symbol[0] = '\0';
3695 
3696   /* Center the display ? */
3697   /* """""""""""""""""""" */
3698   if (win->offset > 0)
3699   {
3700     long i;
3701     for (i = 0; i < win->offset; i++)
3702       fputc(' ', stdout);
3703   }
3704 
3705   left_margin_putp(scroll_symbol, term, win);
3706   while (len > 1 && i <= count - 1)
3707   {
3708     /* Display one word and the space or symbol following it. */
3709     /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
3710     if (word_a[i].start >= win->first_column
3711         && word_a[i].end < len + win->first_column)
3712     {
3713       disp_word(i, search_mode, search_data, term, win, tmp_word);
3714 
3715       /* If there are more element to be displayed after the right margin. */
3716       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3717       if ((win->col_mode || win->line_mode) && i < count - 1
3718           && word_a[i + 1].end >= len + win->first_column)
3719       {
3720         apply_attr(term, win->shift_attr);
3721 
3722         if (langinfo->utf8)
3723           fputs(shift_right_sym, stdout);
3724         else
3725           fputc('>', stdout);
3726 
3727         tputs(TPARM1(exit_attribute_mode), 1, outch);
3728       }
3729 
3730       /* If we want to display the gutter. */
3731       /* """"""""""""""""""""""""""""""""" */
3732       else if (!word_a[i].is_last && win->col_sep
3733                && (win->tab_mode || win->col_mode))
3734       {
3735         long pos;
3736 
3737         /* Make sure that we are using the right gutter character even */
3738         /* if the first displayed word is * not the first of its line. */
3739         /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3740         pos = i - first_word_in_line_a[line_nb_of_word_a[i]];
3741 
3742         if (pos >= win->gutter_nb) /* Use the last gutter character. */
3743           fputs(win->gutter_a[win->gutter_nb - 1], stdout);
3744         else
3745           fputs(win->gutter_a[pos], stdout);
3746       }
3747       else
3748         /* Else just display a space. */
3749         /* """""""""""""""""""""""""" */
3750         fputc(' ', stdout);
3751     }
3752 
3753     /* Mark the line as the current line, the line containing the cursor. */
3754     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3755     if (i == current)
3756       win->cur_line = lines_disp;
3757 
3758     /* Check if we must start a new line. */
3759     /* """""""""""""""""""""""""""""""""" */
3760     if (i == count - 1 || word_a[i + 1].start == 0)
3761     {
3762       tputs(TPARM1(clr_eol), 1, outch);
3763       if (lines_disp < win->max_lines)
3764       {
3765         /* If we have more than one line to display. */
3766         /* """"""""""""""""""""""""""""""""""""""""" */
3767         if (display_bar && !toggles->no_scrollbar
3768             && (lines_disp > 1 || i < count - 1))
3769         {
3770           /* Display the next element of the scrollbar. */
3771           /* """""""""""""""""""""""""""""""""""""""""" */
3772           if (line_nb_of_word_a[i] == 0)
3773           {
3774             if (win->max_lines > 1)
3775               right_margin_putp(sbar_top, "\\", langinfo, term, win, lines_disp,
3776                                 win->offset);
3777             else
3778               right_margin_putp(sbar_arr_down, "^", langinfo, term, win,
3779                                 lines_disp, win->offset);
3780           }
3781           else if (lines_disp == 1)
3782             right_margin_putp(sbar_arr_up, "^", langinfo, term, win, lines_disp,
3783                               win->offset);
3784           else if (line_nb_of_word_a[i] == last_line)
3785           {
3786             if (win->max_lines > 1)
3787               right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp,
3788                                 win->offset);
3789             else
3790               right_margin_putp(sbar_arr_up, "^", langinfo, term, win,
3791                                 lines_disp, win->offset);
3792           }
3793           else if (last_line + 1 > win->max_lines
3794                    && (long)((float)(line_nb_of_word_a[current])
3795                                / (last_line + 1) * (win->max_lines - 2)
3796                              + 2)
3797                         == lines_disp)
3798             right_margin_putp(sbar_curs, "+", langinfo, term, win, lines_disp,
3799                               win->offset);
3800           else
3801             right_margin_putp(sbar_line, "|", langinfo, term, win, lines_disp,
3802                               win->offset);
3803         }
3804 
3805         /* Print a newline character if we are not at the end of */
3806         /* the input nor at the end of the window.               */
3807         /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
3808         if (i < count - 1 && lines_disp < win->max_lines)
3809         {
3810           fputc('\n', stdout);
3811 
3812           if (win->offset > 0)
3813           {
3814             long i;
3815             for (i = 0; i < win->offset; i++)
3816               fputc(' ', stdout);
3817           }
3818 
3819           left_margin_putp(scroll_symbol, term, win);
3820         }
3821 
3822         /* We do not increment the number of lines seen after */
3823         /* a premature end of input.                          */
3824         /* """""""""""""""""""""""""""""""""""""""""""""""""" */
3825         if (i < count - 1)
3826           lines_disp++;
3827 
3828         if (win->max_lines == 1)
3829           break;
3830       }
3831       else if (i <= count - 1 && lines_disp == win->max_lines)
3832       {
3833         /* The last line of the window has been displayed. */
3834         /* """"""""""""""""""""""""""""""""""""""""""""""" */
3835         if (display_bar && line_nb_of_word_a[i] == last_line)
3836         {
3837           if (!toggles->no_scrollbar)
3838           {
3839             if (win->max_lines > 1)
3840               right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp,
3841                                 win->offset);
3842             else
3843               right_margin_putp(sbar_arr_up, "^", langinfo, term, win,
3844                                 lines_disp, win->offset);
3845           }
3846         }
3847         else
3848         {
3849           if (display_bar && !toggles->no_scrollbar)
3850             right_margin_putp(sbar_arr_down, "v", langinfo, term, win,
3851                               lines_disp, win->offset);
3852           break;
3853         }
3854       }
3855       else
3856         /* These lines were not in the widows and so we have nothing to do. */
3857         /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3858         break;
3859     }
3860 
3861     /* Next word. */
3862     /* """""""""" */
3863     i++;
3864   }
3865 
3866   /* Update win->end, this is necessary because we only   */
3867   /* call build_metadata on start and on terminal resize. */
3868   /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
3869   if (i == count)
3870     win->end = i - 1;
3871   else
3872     win->end = i;
3873   /* We restore the cursor position saved before the display of the window. */
3874   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3875   tputs(TPARM1(restore_cursor), 1, outch);
3876 
3877   /* Re-enable the periodic timer. */
3878   /* """"""""""""""""""""""""""""" */
3879   sigprocmask(SIG_UNBLOCK, &mask, NULL);
3880 
3881   return lines_disp;
3882 }
3883 
3884 /* ============================================= */
3885 /* Signal handler. Manages SIGWINCH and SIGALRM. */
3886 /* ============================================= */
3887 void
sig_handler(int s)3888 sig_handler(int s)
3889 {
3890   switch (s)
3891   {
3892     /* Standard termination signals. */
3893     /* """"""""""""""""""""""""""""" */
3894     case SIGSEGV:
3895       got_sigsegv = 1;
3896       break;
3897 
3898     case SIGTERM:
3899       got_sigterm = 1;
3900       break;
3901 
3902     case SIGHUP:
3903       got_sighup = 1;
3904       break;
3905 
3906     /* Terminal resize. */
3907     /* """""""""""""""" */
3908     case SIGWINCH:
3909       got_winch = 1;
3910       break;
3911 
3912     /* Alarm triggered, This signal is used by the search mechanism to     */
3913     /* forces a window refresh.                                            */
3914     /* The help mechanism uses it to clear the message                     */
3915     /* It is also used to redisplay the window after the end of a terminal */
3916     /* resizing.                                                           */
3917     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3918     case SIGALRM:
3919       if (timeout.initial_value > 0)
3920         got_timeout_tick = 1;
3921 
3922       if (help_timer > 0)
3923         help_timer--;
3924 
3925       if (help_timer == 0 && help_mode)
3926         got_help_alrm = 1;
3927 
3928       if (daccess_timer > 0)
3929         daccess_timer--;
3930 
3931       if (daccess_timer == 0)
3932         got_daccess_alrm = 1;
3933 
3934       if (winch_timer > 0)
3935         winch_timer--;
3936 
3937       if (winch_timer == 0)
3938       {
3939         got_winch      = 0;
3940         got_help_alrm  = 0;
3941         got_winch_alrm = 1;
3942       }
3943 
3944       if (search_timer > 0)
3945         search_timer--;
3946 
3947       if (search_timer == 0 && search_mode != NONE)
3948         got_search_alrm = 1;
3949 
3950       break;
3951   }
3952 }
3953 
3954 /* ========================================================= */
3955 /* Set new first column to display when horizontal scrolling */
3956 /* Alter win->first_column.                                  */
3957 /* ========================================================= */
3958 void
set_new_first_column(win_t * win,term_t * term)3959 set_new_first_column(win_t * win, term_t * term)
3960 {
3961   long pos;
3962 
3963   if (word_a[current].start < win->first_column)
3964   {
3965     pos = current;
3966 
3967     while (win->first_column > 0 && word_a[current].start < win->first_column)
3968     {
3969       win->first_column = word_a[pos].start;
3970       pos--;
3971     }
3972   }
3973   else if (word_a[current].end - win->first_column >= term->ncolumns - 3)
3974   {
3975     pos = first_word_in_line_a[line_nb_of_word_a[current]];
3976 
3977     while (!word_a[pos].is_last
3978            && word_a[current].end - win->first_column >= term->ncolumns - 3)
3979     {
3980       pos++;
3981       win->first_column = word_a[pos].start;
3982     }
3983   }
3984 }
3985 
3986 /* ===================================================== */
3987 /* Restrict the matches to word ending with the pattern. */
3988 /* ===================================================== */
3989 void
select_ending_matches(win_t * win,term_t * term,search_data_t * search_data,long * last_line)3990 select_ending_matches(win_t * win, term_t * term, search_data_t * search_data,
3991                       long * last_line)
3992 {
3993   if (matches_count > 0)
3994   {
3995     long   i;
3996     long   j = 0;
3997     long   index;
3998     long   nb;
3999     long * tmp;
4000     char * ptr;
4001     char * last_glyph;
4002     int    utf8_len;
4003 
4004     /* Creation of an alternate array which will      */
4005     /* contain only the candidates having potentially */
4006     /* an ending pattern, if this array becomes non   */
4007     /* empty then it will replace the original array. */
4008     /* """""""""""""""""""""""""""""""""""""""""""""" */
4009     alt_matching_words_a = xrealloc(alt_matching_words_a,
4010                                     matches_count * (sizeof(long)));
4011 
4012     for (i = 0; i < matches_count; i++)
4013     {
4014       index      = matching_words_a[i];
4015       char * str = word_a[index].str;
4016 
4017       /* count the trailing blanks non counted in the bitmap. */
4018       /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
4019       ptr = str + strlen(str);
4020 
4021       nb = 0;
4022       while ((ptr = utf8_prev(str, ptr)) != NULL && isblank(*ptr))
4023         if (ptr - str > 0)
4024           nb++;
4025         else
4026           break;
4027 
4028       /* Check the bit corresponding to the last non blank glyph  */
4029       /* If set we add the index to an alternate array, if not we */
4030       /* clear the bitmap of the corresponding word.              */
4031       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4032       if (BIT_ISSET(word_a[index].bitmap,
4033                     word_a[index].mb - nb - daccess.flength - 1))
4034         alt_matching_words_a[j++] = index;
4035       else
4036       {
4037         /* Look if the end of the word potentially contain an */
4038         /* ending pattern.                                    */
4039         /* """""""""""""""""""""""""""""""""""""""""""""""""" */
4040         if (search_mode == FUZZY)
4041         {
4042           utf8_len   = mblen(ptr, 4);
4043           last_glyph = search_data->buf + search_data->len - utf8_len;
4044 
4045           /* in fuzzy search mode we only look the last glyph. */
4046           /* """"""""""""""""""""""""""""""""""""""""""""""""" */
4047           if (memcmp(ptr, last_glyph, utf8_len) == 0)
4048             alt_matching_words_a[j++] = index;
4049           else
4050             memset(word_a[index].bitmap, '\0',
4051                    (word_a[index].mb - daccess.flength) / CHAR_BIT + 1);
4052         }
4053         else
4054         {
4055           /* in not fuzzy search mode use all the pattern. */
4056           /* """"""""""""""""""""""""""""""""""""""""""""" */
4057           for (nb = 0; nb < search_data->utf8_len - 1; nb++)
4058             ptr = utf8_prev(str, ptr);
4059           if (memcmp(ptr, search_data->buf, search_data->len) == 0)
4060             alt_matching_words_a[j++] = index;
4061           else
4062             memset(word_a[index].bitmap, '\0',
4063                    (word_a[index].mb - daccess.flength) / CHAR_BIT + 1);
4064         }
4065       }
4066     }
4067 
4068     /* Swap the normal and alt array. */
4069     /* """""""""""""""""""""""""""""" */
4070     matches_count         = j;
4071     matching_words_a_size = j;
4072 
4073     tmp                  = matching_words_a;
4074     matching_words_a     = alt_matching_words_a;
4075     alt_matching_words_a = tmp;
4076 
4077     if (j > 0)
4078     {
4079       /* Adjust the bitmap to the ending version. */
4080       /* """""""""""""""""""""""""""""""""""""""" */
4081       update_bitmaps(search_mode, search_data, END_AFFINITY);
4082 
4083       current = matching_words_a[0];
4084 
4085       if (current < win->start || current > win->end)
4086         *last_line = build_metadata(term, count, win);
4087 
4088       /* Set new first column to display. */
4089       /* """""""""""""""""""""""""""""""" */
4090       set_new_first_column(win, term);
4091     }
4092   }
4093 }
4094 
4095 /* ======================================================= */
4096 /* Restrict the matches to word starting with the pattern. */
4097 /* ======================================================= */
4098 void
select_starting_matches(win_t * win,term_t * term,search_data_t * search_data,long * last_line)4099 select_starting_matches(win_t * win, term_t * term, search_data_t * search_data,
4100                         long * last_line)
4101 {
4102   if (matches_count > 0)
4103   {
4104     long   i;
4105     long   j = 0;
4106     long   index;
4107     long   nb;
4108     long * tmp;
4109     long   pos;
4110     char * first_glyph;
4111     int    utf8_len;
4112 
4113     alt_matching_words_a = xrealloc(alt_matching_words_a,
4114                                     matches_count * (sizeof(long)));
4115 
4116     first_glyph = xmalloc(5);
4117 
4118     for (i = 0; i < matches_count; i++)
4119     {
4120       index = matching_words_a[i];
4121 
4122       for (nb = 0; nb < word_a[index].mb; nb++)
4123         if (!isblank(*(word_a[index].str + daccess.flength + nb)))
4124           break;
4125 
4126       if (BIT_ISSET(word_a[index].bitmap, nb))
4127         alt_matching_words_a[j++] = index;
4128       else
4129       {
4130 
4131         if (search_mode == FUZZY)
4132         {
4133           first_glyph = utf8_strprefix(first_glyph,
4134                                        word_a[index].str + nb + daccess.flength,
4135                                        1, &pos);
4136           utf8_len    = pos;
4137 
4138           /* in fuzzy search mode we only look the first glyph. */
4139           /* """""""""""""""""""""""""""""""""""""""""""""""""" */
4140           if (memcmp(search_data->buf, first_glyph, utf8_len) == 0)
4141             alt_matching_words_a[j++] = index;
4142           else
4143             memset(word_a[index].bitmap, '\0',
4144                    (word_a[index].mb + nb - daccess.flength) / CHAR_BIT + 1);
4145         }
4146         else
4147         {
4148           /* in not fuzzy search mode use all the pattern. */
4149           /* """"""""""""""""""""""""""""""""""""""""""""" */
4150           if (memcmp(search_data->buf, word_a[index].str + nb,
4151                      search_data->len - nb)
4152               == 0)
4153             alt_matching_words_a[j++] = index;
4154           else
4155             memset(word_a[index].bitmap, '\0',
4156                    (word_a[index].mb + nb - daccess.flength) / CHAR_BIT + 1);
4157         }
4158       }
4159     }
4160 
4161     free(first_glyph);
4162 
4163     matches_count         = j;
4164     matching_words_a_size = j;
4165 
4166     tmp                  = matching_words_a;
4167     matching_words_a     = alt_matching_words_a;
4168     alt_matching_words_a = tmp;
4169 
4170     if (j > 0)
4171     {
4172       /* Adjust the bitmap to the ending version. */
4173       /* """""""""""""""""""""""""""""""""""""""" */
4174       update_bitmaps(search_mode, search_data, START_AFFINITY);
4175 
4176       current = matching_words_a[0];
4177 
4178       if (current < win->start || current > win->end)
4179         *last_line = build_metadata(term, count, win);
4180 
4181       /* Set new first column to display. */
4182       /* """""""""""""""""""""""""""""""" */
4183       set_new_first_column(win, term);
4184     }
4185   }
4186 }
4187 
4188 /* ====================== */
4189 /* Moves the cursor left. */
4190 /* ====================== */
4191 void
move_left(win_t * win,term_t * term,toggle_t * toggles,search_data_t * search_data,langinfo_t * langinfo,long * nl,long last_line,char * tmp_word)4192 move_left(win_t * win, term_t * term, toggle_t * toggles,
4193           search_data_t * search_data, langinfo_t * langinfo, long * nl,
4194           long last_line, char * tmp_word)
4195 {
4196   long old_current      = current;
4197   long old_start        = win->start;
4198   long old_first_column = win->first_column;
4199   long wi; /* Word index. */
4200 
4201   do
4202   {
4203     if (current > 0)
4204     {
4205       /* Sets the new win->start and win->end if the cursor */
4206       /* is at the beginning of the windows.                */
4207       /* """""""""""""""""""""""""""""""""""""""""""""""""" */
4208       if (current == win->start)
4209         if (win->start > 0)
4210         {
4211           for (wi = win->start - 1; wi >= 0 && word_a[wi].start != 0; wi--)
4212           {
4213           }
4214           win->start = wi;
4215 
4216           if (word_a[wi].str != NULL)
4217             win->start = wi;
4218 
4219           if (win->end < count - 1)
4220           {
4221             for (wi = win->end + 2; wi < count - 1 && word_a[wi].start != 0;
4222                  wi++)
4223             {
4224             }
4225             if (word_a[wi].str != NULL)
4226               win->end = wi;
4227           }
4228         }
4229 
4230       /* In column mode we need to take care of the */
4231       /* horizontal scrolling.                      */
4232       /* """""""""""""""""""""""""""""""""""""""""" */
4233       if (win->col_mode || win->line_mode)
4234       {
4235         long pos;
4236 
4237         if (word_a[current].start == 0)
4238         {
4239           long len;
4240 
4241           len = term->ncolumns - 3;
4242           pos = first_word_in_line_a[line_nb_of_word_a[current - 1]];
4243 
4244           while (word_a[current - 1].end - win->first_column >= len)
4245           {
4246             win->first_column += word_a[pos].end - word_a[pos].start + 2;
4247 
4248             pos++;
4249           }
4250         }
4251         else if (word_a[current - 1].start < win->first_column)
4252           win->first_column = word_a[current - 1].start;
4253       }
4254       current--;
4255     }
4256     else
4257       break;
4258   } while (current != old_current && !word_a[current].is_selectable);
4259 
4260   /* The old settings need to be restored if the */
4261   /* new current word is not selectable.         */
4262   /* """"""""""""""""""""""""""""""""""""""""""" */
4263   if (!word_a[current].is_selectable)
4264   {
4265     current    = old_current;
4266     win->start = old_start;
4267     if (win->col_mode || win->line_mode)
4268       win->first_column = old_first_column;
4269   }
4270 
4271   if (current != old_current)
4272     *nl = disp_lines(win, toggles, current, count, search_mode, search_data,
4273                      term, last_line, tmp_word, langinfo);
4274 }
4275 
4276 /* ======================= */
4277 /* Moves the cursor right. */
4278 /* ======================= */
4279 void
move_right(win_t * win,term_t * term,toggle_t * toggles,search_data_t * search_data,langinfo_t * langinfo,long * nl,long last_line,char * tmp_word)4280 move_right(win_t * win, term_t * term, toggle_t * toggles,
4281            search_data_t * search_data, langinfo_t * langinfo, long * nl,
4282            long last_line, char * tmp_word)
4283 {
4284   long old_current      = current;
4285   long old_start        = win->start;
4286   long old_first_column = win->first_column;
4287   long wi; /* word index */
4288 
4289   do
4290   {
4291     if (current < count - 1)
4292     {
4293       /* Sets the new win->start and win->end if the cursor */
4294       /* is at the end of the windows.                      */
4295       /* """""""""""""""""""""""""""""""""""""""""""""""""" */
4296       if (current == win->end)
4297         if (win->start < count - 1 && win->end != count - 1)
4298         {
4299           for (wi = win->start + 1; wi < count - 1 && word_a[wi].start != 0;
4300                wi++)
4301           {
4302           }
4303 
4304           if (word_a[wi].str != NULL)
4305             win->start = wi;
4306 
4307           if (win->end < count - 1)
4308           {
4309             for (wi = win->end + 2; wi < count - 1 && word_a[wi].start != 0;
4310                  wi++)
4311             {
4312             }
4313             if (word_a[wi].str != NULL)
4314               win->end = wi;
4315           }
4316         }
4317 
4318       /* In column mode we need to take care of the */
4319       /* horizontal scrolling.                      */
4320       /* """""""""""""""""""""""""""""""""""""""""" */
4321       if (win->col_mode || win->line_mode)
4322       {
4323         if (word_a[current].is_last)
4324           win->first_column = 0;
4325         else
4326         {
4327           long pos;
4328           long len;
4329 
4330           len = term->ncolumns - 3;
4331 
4332           if (word_a[current + 1].end >= len + win->first_column)
4333           {
4334             /* Find the first word to be displayed in this line. */
4335             /* """"""""""""""""""""""""""""""""""""""""""""""""" */
4336             pos = first_word_in_line_a[line_nb_of_word_a[current]];
4337 
4338             while (word_a[pos].start <= win->first_column)
4339               pos++;
4340 
4341             pos--;
4342 
4343             /* If the new current word cannot be displayed, search */
4344             /* the first word in the line that can be displayed by */
4345             /* iterating on pos.                                   */
4346             /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
4347             while (word_a[current + 1].end - word_a[pos].start >= len)
4348               pos++;
4349 
4350             if (word_a[pos].start > 0)
4351               win->first_column = word_a[pos].start;
4352           }
4353         }
4354       }
4355       current++;
4356     }
4357     else
4358       break;
4359   } while (current != old_current && !word_a[current].is_selectable);
4360 
4361   /* The old settings need to be restored if the */
4362   /* new current word is not selectable.         */
4363   /* """"""""""""""""""""""""""""""""""""""""""" */
4364   if (!word_a[current].is_selectable)
4365   {
4366     current    = old_current;
4367     win->start = old_start;
4368     if (win->col_mode || win->line_mode)
4369       win->first_column = old_first_column;
4370   }
4371 
4372   if (current != old_current)
4373     *nl = disp_lines(win, toggles, current, count, search_mode, search_data,
4374                      term, last_line, tmp_word, langinfo);
4375 }
4376 
4377 /* ================================================================== */
4378 /* Get the last word of a line after it has been formed to fit in the */
4379 /* terminal.                                                          */
4380 /* ================================================================== */
4381 long
get_line_last_word(long line,long last_line)4382 get_line_last_word(long line, long last_line)
4383 {
4384   if (line == last_line)
4385     return count - 1;
4386   else
4387     return first_word_in_line_a[line + 1] - 1;
4388 }
4389 
4390 /* ==================================================================== */
4391 /* Try to locate the best word in the target line when trying to move   */
4392 /* the cursor upward.                                                   */
4393 /* returns 1 if a word has been found else 0.                           */
4394 /* This function has the side effect to potentially change the value of */
4395 /* the variable 'current' if an adequate word is found.                 */
4396 /* ==================================================================== */
4397 int
find_best_word_upward(long last_word,long s,long e)4398 find_best_word_upward(long last_word, long s, long e)
4399 {
4400   int  found = 0;
4401   long index;
4402   long cursor;
4403 
4404   /* Look for the first word whose start position in the line is */
4405   /* less or equal to the source word starting position.         */
4406   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4407   cursor = last_word;
4408   while (word_a[cursor].start > s)
4409     cursor--;
4410 
4411   /* In case no word is eligible, keep the cursor on */
4412   /* the last word.                                  */
4413   /* """"""""""""""""""""""""""""""""""""""""""""""" */
4414   if (cursor == last_word && word_a[cursor].start > 0)
4415     cursor--;
4416 
4417   /* Try to guess the best choice if we have multiple choices. */
4418   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4419   if (word_a[cursor].end >= s
4420       && word_a[cursor].end - s >= e - word_a[cursor + 1].start)
4421     current = cursor;
4422   else
4423   {
4424     if (cursor < last_word)
4425       current = cursor + 1;
4426     else
4427       current = cursor;
4428   }
4429 
4430   /* If the word is not selectable, try to find a selectable word */
4431   /* in the line.                                                 */
4432   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4433   if (!word_a[current].is_selectable)
4434   {
4435     index = 0;
4436     while (word_a[current - index].start > 0
4437            && !word_a[current - index].is_selectable)
4438       index++;
4439 
4440     if (word_a[current - index].is_selectable)
4441     {
4442       current -= index;
4443       found = 1;
4444     }
4445     else
4446     {
4447       index = 0;
4448       while (current + index < last_word
4449              && !word_a[current + index].is_selectable)
4450         index++;
4451 
4452       if (word_a[current + index].is_selectable)
4453       {
4454         current += index;
4455         found = 1;
4456       }
4457     }
4458   }
4459   else
4460     found = 1;
4461 
4462   return found;
4463 }
4464 
4465 /* ==================================================================== */
4466 /* Try to locate the best word in the target line when trying to move   */
4467 /* the cursor downward.                                                 */
4468 /* returns 1 if a word has been found else 0.                           */
4469 /* This function has the side effect to potentially change the value of */
4470 /* the variable 'current' if an adequate word is found.                 */
4471 /* ==================================================================== */
4472 int
find_best_word_downward(long last_word,long s,long e)4473 find_best_word_downward(long last_word, long s, long e)
4474 {
4475   int  found = 0;
4476   long index;
4477   long cursor;
4478 
4479   /* Look for the first word whose start position in the line is */
4480   /* less or equal than the source word starting position.       */
4481   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4482   cursor = last_word;
4483   while (word_a[cursor].start > s)
4484     cursor--;
4485 
4486   /* In case no word is eligible, keep the cursor on */
4487   /* the last word.                                  */
4488   /* """"""""""""""""""""""""""""""""""""""""""""""" */
4489   if (cursor == last_word && word_a[cursor].start > 0)
4490     cursor--;
4491 
4492   /* Try to guess the best choice if we have multiple choices. */
4493   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4494   if (cursor < count - 1
4495       && word_a[cursor].end - s >= e - word_a[cursor + 1].start)
4496     current = cursor;
4497   else
4498   {
4499     if (cursor < count - 1)
4500     {
4501       if (cursor < last_word)
4502         current = cursor + 1;
4503       else
4504         current = cursor;
4505     }
4506     else
4507       current = count - 1;
4508   }
4509 
4510   /* If the word is not selectable, try to find a selectable word */
4511   /* in ts line.                                                  */
4512   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4513   if (!word_a[current].is_selectable)
4514   {
4515     index = 0;
4516     while (word_a[current - index].start > 0
4517            && !word_a[current - index].is_selectable)
4518       index++;
4519 
4520     if (word_a[current - index].is_selectable)
4521     {
4522       current -= index;
4523       found = 1;
4524     }
4525     else
4526     {
4527       index = 0;
4528       while (current + index < last_word
4529              && !word_a[current + index].is_selectable)
4530         index++;
4531 
4532       if (word_a[current + index].is_selectable)
4533       {
4534         current += index;
4535         found = 1;
4536       }
4537     }
4538   }
4539   else
4540     found = 1;
4541 
4542   return found;
4543 }
4544 
4545 /* ==================== */
4546 /* Moves the cursor up. */
4547 /* ==================== */
4548 void
move_up(win_t * win,term_t * term,toggle_t * toggles,search_data_t * search_data,langinfo_t * langinfo,long * nl,long page,long first_selectable,long last_line,char * tmp_word)4549 move_up(win_t * win, term_t * term, toggle_t * toggles,
4550         search_data_t * search_data, langinfo_t * langinfo, long * nl,
4551         long page, long first_selectable, long last_line, char * tmp_word)
4552 {
4553   long line;                  /* The line being processed (target line).    */
4554   long start_line;            /* The first line of the window.              */
4555   long cur_line;              /* The line of the cursor.                    */
4556   long nlines;                /* Number of line in the window.              */
4557   long first_selectable_line; /* the line containing the first              *
4558                                | selectable word.                           */
4559   long lines_skipped; /* The number of line between the target line and the *
4560                        | first line containing a selectable word in case of *
4561                        | exclusions.                                        */
4562   long last_word;     /* The last word on the target line.                  */
4563   long s, e;          /* Starting and ending terminal position of a word.   */
4564   int  found;         /* 1 if a line could be fond else 0.                  */
4565 
4566   /* Store the initial starting and ending positions of */
4567   /* the word under the cursor.                         */
4568   /* """""""""""""""""""""""""""""""""""""""""""""""""" */
4569   s = word_a[current].start;
4570   e = word_a[current].end;
4571 
4572   /* Identify the line number of the first window's line */
4573   /* and the line number of the current line.            */
4574   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
4575   start_line            = line_nb_of_word_a[win->start];
4576   cur_line              = line_nb_of_word_a[current];
4577   first_selectable_line = line_nb_of_word_a[first_selectable];
4578   lines_skipped         = 0;
4579   found                 = 0;
4580   nlines = win->max_lines < last_line + 1 ? win->max_lines : last_line + 1;
4581 
4582   /* initialise the target line. */
4583   /* """"""""""""""""""""""""""" */
4584   line = cur_line;
4585 
4586   /* Special case if the cursor is already in the line containing the */
4587   /* first selectable word.                                           */
4588   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4589   if (line == first_selectable_line)
4590   {
4591     /* we can't move the cursor up but we still can try to show the */
4592     /* more non selectable words as we can.                         */
4593     /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4594     if (line <= start_line + nlines - 1 - page)
4595     {
4596       /* We are scrolling one line at a time and the cursor is not in */
4597       /* the last line of the window.                                 */
4598       /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4599       if (start_line - page > 0)
4600         /* There is enough remaining line to fill a window. */
4601         /* '''''''''''''''''''''''''''''''''''''''''''''''' */
4602         start_line -= page;
4603       else
4604         /* We cannot scroll further. */
4605         /* ''''''''''''''''''''''''' */
4606         start_line = 0;
4607     }
4608     else
4609     {
4610       /* The cursor is already in the last line of the windows. */
4611       /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4612       if (line >= nlines)
4613         /* There is enough remaining line to fill a window. */
4614         /* '''''''''''''''''''''''''''''''''''''''''''''''' */
4615         start_line = line - nlines + 1;
4616       else
4617         /* We cannot scroll further. */
4618         /* ''''''''''''''''''''''''' */
4619         start_line = 0;
4620     }
4621   }
4622   else
4623   {
4624     if (line - page < 0)
4625     {
4626       /* Trivial case, we are already on the first page */
4627       /* just jump to the first selectable line.        */
4628       /* """""""""""""""""""""""""""""""""""""""""""""" */
4629       line      = first_selectable_line;
4630       last_word = get_line_last_word(line, last_line);
4631       find_best_word_upward(last_word, s, e);
4632     }
4633     else
4634     {
4635       /* Temporarily move up one page. */
4636       /* """"""""""""""""""""""""""""" */
4637       line -= page;
4638 
4639       /* The target line cannot be before the line containing the first */
4640       /* selectable word.                                               */
4641       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4642       if (line < first_selectable_line)
4643       {
4644         line      = first_selectable_line;
4645         last_word = get_line_last_word(line, last_line);
4646         find_best_word_upward(last_word, s, e);
4647       }
4648       else
4649       {
4650         /* If this is not the case, search upward for the line with a */
4651         /* selectable word. This line is guaranteed to exist.         */
4652         /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4653         while (line >= first_selectable_line)
4654         {
4655           last_word = get_line_last_word(line, last_line);
4656 
4657           if (find_best_word_upward(last_word, s, e))
4658           {
4659             found = 1;
4660             break;
4661           }
4662 
4663           line--;
4664           lines_skipped++;
4665         }
4666       }
4667     }
4668   }
4669 
4670   /* Look if we need to adjust the window to follow the cursor. */
4671   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4672   if (!found && start_line - page >= 0)
4673   {
4674     /* We are on the first line containing a selectable word and  */
4675     /* There is enough place to scroll up a page but only scrolls */
4676     /* up if the cursor remains in the window.                    */
4677     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4678     if (start_line + nlines - line > page)
4679       start_line -= page;
4680   }
4681   else if (line < start_line)
4682   {
4683     /* The target line is above the windows. */
4684     /* """"""""""""""""""""""""""""""""""""" */
4685     if (start_line - page - lines_skipped < 0)
4686       /* There isn't enough remaining lines to scroll up */
4687       /* a page size.                                    */
4688       /* """"""""""""""""""""""""""""""""""""""""""""""" */
4689       start_line = 0;
4690     else
4691       start_line -= page + lines_skipped;
4692   }
4693 
4694   /* And set the new value of the starting word of the window. */
4695   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4696   win->start = first_word_in_line_a[start_line];
4697 
4698   /* Set the new first column to display */
4699   /* """"""""""""""""""""""""""""""""""" */
4700   set_new_first_column(win, term);
4701 
4702   /* Redisplay the window. */
4703   /* """"""""""""""""""""" */
4704   *nl = disp_lines(win, toggles, current, count, search_mode, search_data, term,
4705                    last_line, tmp_word, langinfo);
4706 }
4707 
4708 /* ====================== */
4709 /* Moves the cursor down. */
4710 /* ====================== */
4711 void
move_down(win_t * win,term_t * term,toggle_t * toggles,search_data_t * search_data,langinfo_t * langinfo,long * nl,long page,long last_selectable,long last_line,char * tmp_word)4712 move_down(win_t * win, term_t * term, toggle_t * toggles,
4713           search_data_t * search_data, langinfo_t * langinfo, long * nl,
4714           long page, long last_selectable, long last_line, char * tmp_word)
4715 {
4716   long line;                 /* The line being processed (target line).     */
4717   long start_line;           /* The first line of the window.               */
4718   long cur_line;             /* The line of the cursor.                     */
4719   long nlines;               /* Number of line in the window.               */
4720   long last_selectable_line; /* the line containing the last                *
4721                               | selectable word.                            */
4722   long lines_skipped; /* The number of line between the target line and the *
4723                        | first line containing a selectable word in case of *
4724                        | exclusions.                                        */
4725   long last_word;     /* The last word on the target line.                  */
4726   long s, e;          /* Starting and ending terminal position of a word.   */
4727   int  found;         /* 1 if a line could be fond in the next page else 0. */
4728 
4729   /* Store the initial starting and ending positions of */
4730   /* the word under the cursor.                         */
4731   /* """""""""""""""""""""""""""""""""""""""""""""""""" */
4732   s = word_a[current].start;
4733   e = word_a[current].end;
4734 
4735   /* Identify the line number of the first window's line */
4736   /* and the line number of the current line.            */
4737   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
4738   start_line           = line_nb_of_word_a[win->start];
4739   cur_line             = line_nb_of_word_a[current];
4740   last_selectable_line = line_nb_of_word_a[last_selectable];
4741   lines_skipped        = 0;
4742   found                = 0;
4743   nlines = win->max_lines < last_line + 1 ? win->max_lines : last_line + 1;
4744 
4745   /* initialise the target line. */
4746   /* """"""""""""""""""""""""""" */
4747   line = cur_line;
4748 
4749   /* Special case if the cursor is already in the line containing the */
4750   /* last selectable word.                                            */
4751   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4752   if (line == last_selectable_line)
4753   {
4754     /* we can't move the cursor down but we still can try to show the */
4755     /* more non selectable words as we can.                           */
4756     /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4757     if (line >= start_line + page)
4758     {
4759       /* We are scrolling one line at a time and the cursor is not in */
4760       /* the first line of the window.                                */
4761       /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4762       if (start_line + page + nlines - 1 <= last_line)
4763         /* There is enough remaining line to fill a window. */
4764         /* '''''''''''''''''''''''''''''''''''''''''''''''' */
4765         start_line += page;
4766       else
4767         /* We cannot scroll further. */
4768         /* ''''''''''''''''''''''''' */
4769         start_line = last_line - nlines + 1;
4770     }
4771     else
4772     {
4773       /* The cursor is already in the first line of the windows. */
4774       /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4775       if (last_line - line + 1 > nlines)
4776         /* There is enough remaining line to fill a window. */
4777         /* '''''''''''''''''''''''''''''''''''''''''''''''' */
4778         start_line = line;
4779       else
4780         /* We cannot scroll further. */
4781         /* ''''''''''''''''''''''''' */
4782         start_line = last_line - nlines + 1;
4783     }
4784   }
4785   else
4786   {
4787     /* The cursor is above the line containing the last selectable word. */
4788     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4789     if (last_line - line - page < 0)
4790     {
4791       /* Trivial case, we are already on the last page */
4792       /* just jump to the last selectable line.        */
4793       /* """"""""""""""""""""""""""""""""""""""""""""" */
4794       line      = last_selectable_line;
4795       last_word = get_line_last_word(line, last_line);
4796       find_best_word_downward(last_word, s, e);
4797     }
4798     else
4799     {
4800       /* Temporarily move down one page. */
4801       /* """"""""""""""""""""""""""""""" */
4802       line += page;
4803 
4804       /* The target line cannot be before the line containing the first */
4805       /* selectable word.                                               */
4806       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4807       if (line > last_selectable_line)
4808       {
4809         line      = last_selectable_line;
4810         last_word = get_line_last_word(line, last_line);
4811         find_best_word_downward(last_word, s, e);
4812       }
4813       else
4814       {
4815         /* If this is not the case, search upward for the line with a */
4816         /* selectable word. This line is guaranteed to exist.         */
4817         /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4818         while (line <= last_selectable_line)
4819         {
4820           last_word = get_line_last_word(line, last_line);
4821 
4822           if (find_best_word_downward(last_word, s, e))
4823           {
4824             found = 1;
4825             break;
4826           }
4827 
4828           line++;
4829           lines_skipped++;
4830         }
4831       }
4832     }
4833 
4834     /* Look if we need to adjust the window to follow the cursor. */
4835     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4836     if (!found && start_line + nlines - 1 + page <= last_line)
4837     {
4838       /* We are on the last line containing a selectable word and     */
4839       /* There is enough place to scroll down a page but only scrolls */
4840       /* down if the cursor remains in the window.                    */
4841       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4842       if (line - start_line >= page)
4843         start_line += page;
4844     }
4845     else if (line > start_line + nlines - 1)
4846     {
4847       /* The target line is below the windows. */
4848       /* """"""""""""""""""""""""""""""""""""" */
4849       if (start_line + nlines + page + lines_skipped - 1 > last_line)
4850         /* There isn't enough remaining lines to scroll down */
4851         /* a page size.                                      */
4852         /* """"""""""""""""""""""""""""""""""""""""""""""""" */
4853         start_line = last_line - nlines + 1;
4854       else
4855         start_line += page + lines_skipped;
4856     }
4857   }
4858 
4859   /* And set the new value of the starting word of the window. */
4860   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4861   win->start = first_word_in_line_a[start_line];
4862 
4863   /* Set the new first column to display. */
4864   /* """""""""""""""""""""""""""""""""""" */
4865   set_new_first_column(win, term);
4866 
4867   /* Redisplay the window. */
4868   /* """"""""""""""""""""" */
4869   *nl = disp_lines(win, toggles, current, count, search_mode, search_data, term,
4870                    last_line, tmp_word, langinfo);
4871 }
4872 
4873 /* ========================================= */
4874 /* Initialize some internal data structures. */
4875 /* ========================================= */
4876 void
init_main_ds(attrib_t * init_attr,win_t * win,limit_t * limits,ticker_t * timers,toggle_t * toggles,misc_t * misc,timeout_t * timeout,daccess_t * daccess)4877 init_main_ds(attrib_t * init_attr, win_t * win, limit_t * limits,
4878              ticker_t * timers, toggle_t * toggles, misc_t * misc,
4879              timeout_t * timeout, daccess_t * daccess)
4880 {
4881   int i;
4882 
4883   /* Initial attribute settings. */
4884   /* """"""""""""""""""""""""""" */
4885   init_attr->is_set    = UNSET;
4886   init_attr->fg        = -1;
4887   init_attr->bg        = -1;
4888   init_attr->bold      = -1;
4889   init_attr->dim       = -1;
4890   init_attr->reverse   = -1;
4891   init_attr->standout  = -1;
4892   init_attr->underline = -1;
4893   init_attr->italic    = -1;
4894   init_attr->blink     = -1;
4895 
4896   /* Win fields initialization. */
4897   /* """""""""""""""""""""""""" */
4898   win->max_lines       = 5;
4899   win->message_lines   = 0;
4900   win->asked_max_lines = -1;
4901   win->center          = 0;
4902   win->max_cols        = 0;
4903   win->col_sep         = 0;
4904   win->wide            = 0;
4905   win->tab_mode        = 0;
4906   win->col_mode        = 0;
4907   win->line_mode       = 0;
4908   win->first_column    = 0;
4909   win->real_max_width  = 0;
4910 
4911   win->cursor_attr           = *init_attr;
4912   win->cursor_on_tag_attr    = *init_attr;
4913   win->bar_attr              = *init_attr;
4914   win->shift_attr            = *init_attr;
4915   win->message_attr          = *init_attr;
4916   win->search_field_attr     = *init_attr;
4917   win->search_text_attr      = *init_attr;
4918   win->search_err_field_attr = *init_attr;
4919   win->search_err_text_attr  = *init_attr;
4920   win->match_field_attr      = *init_attr;
4921   win->match_text_attr       = *init_attr;
4922   win->match_err_field_attr  = *init_attr;
4923   win->match_err_text_attr   = *init_attr;
4924   win->include_attr          = *init_attr;
4925   win->exclude_attr          = *init_attr;
4926   win->tag_attr              = *init_attr;
4927   win->daccess_attr          = *init_attr;
4928 
4929   win->sel_sep = NULL;
4930 
4931   for (i = 0; i < 9; i++)
4932     win->special_attr[i] = *init_attr;
4933 
4934   /* Default limits initialization. */
4935   /* """""""""""""""""""""""""""""" */
4936   limits->words       = 32767;
4937   limits->cols        = 256;
4938   limits->word_length = 512;
4939 
4940   /* Default timers in 1/10 s. */
4941   /* """"""""""""""""""""""""" */
4942   timers->search        = 100 * FREQ / 10;
4943   timers->help          = 150 * FREQ / 10;
4944   timers->winch         = 20 * FREQ / 10;
4945   timers->direct_access = 6 * FREQ / 10;
4946 
4947   /* Toggles initialization. */
4948   /* """"""""""""""""""""""" */
4949   toggles->del_line            = 0;
4950   toggles->enter_val_in_search = 0;
4951   toggles->no_scrollbar        = 0;
4952   toggles->blank_nonprintable  = 0;
4953   toggles->keep_spaces         = 0;
4954   toggles->taggable            = 0;
4955   toggles->autotag             = 0;
4956   toggles->noautotag           = 0;
4957   toggles->pinable             = 0;
4958   toggles->visual_bell         = 0;
4959   toggles->incremental_search  = 0;
4960 
4961   /* Misc default values. */
4962   /* """""""""""""""""""" */
4963   misc->default_search_method = NONE;
4964   misc->ignore_quotes         = 0;
4965 
4966   /* Set the default timeout to 0 (no expiration). */
4967   /* """"""""""""""""""""""""""""""""""""""""""""" */
4968   timeout->initial_value = 0;
4969   timeout->remain        = 0;
4970   timeout->reached       = 0;
4971 
4972   /* Initialize Direct Access settings. */
4973   /* """""""""""""""""""""""""""""""""" */
4974   daccess->mode       = DA_TYPE_NONE;
4975   daccess->left       = xstrdup(" ");
4976   daccess->right      = xstrdup(")");
4977   daccess->alignment  = 'r';
4978   daccess->padding    = 'a';
4979   daccess->head       = 'k'; /* Keep by default. */
4980   daccess->length     = -2;
4981   daccess->flength    = 0;
4982   daccess->offset     = 0;
4983   daccess->plus       = 0;
4984   daccess->size       = 0;
4985   daccess->ignore     = 0;
4986   daccess->follow     = 'y';
4987   daccess->missing    = 'y';
4988   daccess->num_sep    = NULL;
4989   daccess->def_number = -1;
4990 }
4991 
4992 /* *********************************** */
4993 /* ctxopt contexts callback functions. */
4994 /* *********************************** */
4995 
4996 /* ******************************** */
4997 /* ctxopt option callback function. */
4998 /* ******************************** */
4999 
5000 void
help_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5001 help_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5002             char ** values, int nb_opt_data, void ** opt_data, int nb_ctx_data,
5003             void ** ctx_data)
5004 {
5005   if (strcmp(ctx_name, "Columns") == 0)
5006     columns_help();
5007   else if (strcmp(ctx_name, "Lines") == 0)
5008     lines_help();
5009   else if (strcmp(ctx_name, "Tabulations") == 0)
5010     tabulations_help();
5011   else if (strcmp(ctx_name, "Tagging") == 0)
5012     tagging_help();
5013   else
5014     main_help();
5015 
5016   exit(EXIT_FAILURE);
5017 }
5018 
5019 void
long_help_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5020 long_help_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5021                  char ** values, int nb_opt_data, void ** opt_data,
5022                  int nb_ctx_data, void ** ctx_data)
5023 {
5024   ctxopt_disp_usage(continue_after);
5025 
5026   printf("\nRead the manual for more information.\n");
5027 
5028   exit(EXIT_FAILURE);
5029 }
5030 
5031 void
usage_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5032 usage_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5033              char ** values, int nb_opt_data, void ** opt_data, int nb_ctx_data,
5034              void ** ctx_data)
5035 {
5036   ctxopt_ctx_disp_usage(ctx_name, exit_after);
5037 }
5038 
5039 void
lines_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5040 lines_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5041              char ** values, int nb_opt_data, void ** opt_data, int nb_ctx_data,
5042              void ** ctx_data)
5043 {
5044   win_t * win = opt_data[0];
5045 
5046   if (nb_values == 1)
5047     sscanf(values[0], "%ld", &(win->asked_max_lines));
5048   else
5049     win->asked_max_lines = 0;
5050 }
5051 
5052 void
tab_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5053 tab_mode_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5054                 char ** values, int nb_opt_data, void ** opt_data,
5055                 int nb_ctx_data, void ** ctx_data)
5056 {
5057   win_t * win = opt_data[0];
5058 
5059   long max_cols;
5060 
5061   if (nb_values == 1)
5062   {
5063     sscanf(values[0], "%ld", &max_cols); /* Numericity and range were  *
5064                                           | already checked by ctxopt. */
5065     win->max_cols = max_cols;
5066   }
5067 
5068   win->tab_mode  = 1;
5069   win->col_mode  = 0;
5070   win->line_mode = 0;
5071 }
5072 
5073 void
set_pattern_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5074 set_pattern_action(char * ctx_name, char * opt_name, char * param,
5075                    int nb_values, char ** values, int nb_opt_data,
5076                    void ** opt_data, int nb_ctx_data, void ** ctx_data)
5077 {
5078   char **      pattern  = opt_data[0];
5079   langinfo_t * langinfo = opt_data[1];
5080   misc_t *     misc     = opt_data[2];
5081 
5082   *pattern = xstrdup(values[0]);
5083   utf8_interpret(*pattern, langinfo, misc->invalid_char_substitute);
5084 }
5085 
5086 void
int_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5087 int_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5088            char ** values, int nb_opt_data, void ** opt_data, int nb_ctx_data,
5089            void ** ctx_data)
5090 {
5091   char **      string     = opt_data[0];
5092   int *        shell_like = opt_data[1];
5093   langinfo_t * langinfo   = opt_data[2];
5094   misc_t *     misc       = opt_data[3];
5095 
5096   if (nb_values == 1)
5097   {
5098     *string = xstrdup(values[0]);
5099     if (!langinfo->utf8)
5100       utf8_sanitize(*string, misc->invalid_char_substitute);
5101     utf8_interpret(*string, langinfo, misc->invalid_char_substitute);
5102   }
5103 
5104   *shell_like = 0;
5105 }
5106 
5107 void
set_string_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5108 set_string_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5109                   char ** values, int nb_opt_data, void ** opt_data,
5110                   int nb_ctx_data, void ** ctx_data)
5111 {
5112   char **      string   = opt_data[0];
5113   langinfo_t * langinfo = opt_data[1];
5114   misc_t *     misc     = opt_data[2];
5115 
5116   *string = xstrdup(values[0]);
5117   if (!langinfo->utf8)
5118     utf8_sanitize(*string, misc->invalid_char_substitute);
5119   utf8_interpret(*string, langinfo, misc->invalid_char_substitute);
5120 }
5121 
5122 void
wide_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5123 wide_mode_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5124                  char ** values, int nb_opt_data, void ** opt_data,
5125                  int nb_ctx_data, void ** ctx_data)
5126 {
5127   win_t * win = opt_data[0];
5128 
5129   win->wide = 1;
5130 }
5131 
5132 void
center_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5133 center_mode_action(char * ctx_name, char * opt_name, char * param,
5134                    int nb_values, char ** values, int nb_opt_data,
5135                    void ** opt_data, int nb_ctx_data, void ** ctx_data)
5136 {
5137   win_t * win = opt_data[0];
5138 
5139   win->center = 1;
5140 }
5141 
5142 void
columns_select_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5143 columns_select_action(char * ctx_name, char * opt_name, char * param,
5144                       int nb_values, char ** values, int nb_opt_data,
5145                       void ** opt_data, int nb_ctx_data, void ** ctx_data)
5146 {
5147   int     v;
5148   ll_t ** cols_selector_list = opt_data[0];
5149 
5150   if (*cols_selector_list == NULL)
5151     *cols_selector_list = ll_new();
5152 
5153   for (v = 0; v < nb_values; v++)
5154     ll_append(*cols_selector_list, xstrdup(values[v]));
5155 }
5156 
5157 void
rows_select_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5158 rows_select_action(char * ctx_name, char * opt_name, char * param,
5159                    int nb_values, char ** values, int nb_opt_data,
5160                    void ** opt_data, int nb_ctx_data, void ** ctx_data)
5161 {
5162   int     v;
5163   ll_t ** rows_selector_list = opt_data[0];
5164   win_t * win                = opt_data[1];
5165 
5166   if (*rows_selector_list == NULL)
5167     *rows_selector_list = ll_new();
5168 
5169   for (v = 0; v < nb_values; v++)
5170     ll_append(*rows_selector_list, xstrdup(values[v]));
5171 
5172   win->max_cols = 0; /* Disable the window column restriction. */
5173 }
5174 
5175 void
toggle_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5176 toggle_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5177               char ** values, int nb_opt_data, void ** opt_data,
5178               int nb_ctx_data, void ** ctx_data)
5179 {
5180   toggle_t * toggles = opt_data[0];
5181 
5182   if (strcmp(opt_name, "clean") == 0)
5183     toggles->del_line = 1;
5184   else if (strcmp(opt_name, "keep_spaces") == 0)
5185     toggles->keep_spaces = 1;
5186   else if (strcmp(opt_name, "visual_bell") == 0)
5187     toggles->visual_bell = 1;
5188   else if (strcmp(opt_name, "validate_in_search_mode") == 0)
5189     toggles->enter_val_in_search = 1;
5190   else if (strcmp(opt_name, "blank_nonprintable") == 0)
5191     toggles->blank_nonprintable = 1;
5192   else if (strcmp(opt_name, "no_scroll_bar") == 0)
5193     toggles->no_scrollbar = 1;
5194   else if (strcmp(opt_name, "auto_tag") == 0)
5195     toggles->autotag = 1;
5196   else if (strcmp(opt_name, "no_auto_tag") == 0)
5197     toggles->noautotag = 1;
5198   else if (strcmp(opt_name, "incremental_search") == 0)
5199     toggles->incremental_search = 1;
5200 }
5201 
5202 void
invalid_char_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5203 invalid_char_action(char * ctx_name, char * opt_name, char * param,
5204                     int nb_values, char ** values, int nb_opt_data,
5205                     void ** opt_data, int nb_ctx_data, void ** ctx_data)
5206 {
5207   misc_t * misc = opt_data[0];
5208 
5209   char ic = *values[0];
5210 
5211   if (isprint(ic))
5212     misc->invalid_char_substitute = ic;
5213   else
5214     misc->invalid_char_substitute = '.';
5215 }
5216 
5217 void
gutter_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5218 gutter_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5219               char ** values, int nb_opt_data, void ** opt_data,
5220               int nb_ctx_data, void ** ctx_data)
5221 {
5222   win_t *      win      = opt_data[0];
5223   langinfo_t * langinfo = opt_data[1];
5224   misc_t *     misc     = opt_data[2];
5225 
5226   if (nb_values == 0)
5227   {
5228     /* As there is no argument, the gutter array will only contain */
5229     /* a vertical bar.                                             */
5230     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5231     win->gutter_a = xmalloc(1 * sizeof(char *));
5232 
5233     if (langinfo->utf8)
5234       win->gutter_a[0] = xstrdup(vertical_bar);
5235     else
5236       win->gutter_a[0] = xstrdup("|");
5237 
5238     win->gutter_nb = 1;
5239   }
5240   else
5241   {
5242     /* The argument is used to feed the gutter array, each of its character */
5243     /* Will serve as gutter in a round-robin way.                           */
5244     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5245     long      n;
5246     wchar_t * w;
5247     long      i, offset;
5248     int       mblen;
5249     char *    gutter;
5250 
5251     gutter = xstrdup(values[0]);
5252 
5253     utf8_interpret(gutter, langinfo,
5254                    misc->invalid_char_substitute); /* Guarantees a well    *
5255                                                     | formed UTF-8 string/ */
5256 
5257     win->gutter_nb = utf8_strlen(gutter);
5258     win->gutter_a  = xmalloc(win->gutter_nb * sizeof(char *));
5259 
5260     offset = 0;
5261 
5262     for (i = 0; i < win->gutter_nb; i++)
5263     {
5264       mblen            = utf8_get_length(*(gutter + offset));
5265       win->gutter_a[i] = xcalloc(1, mblen + 1);
5266       memcpy(win->gutter_a[i], gutter + offset, mblen);
5267 
5268       n = wcswidth((w = utf8_strtowcs(win->gutter_a[i])), 1);
5269       free(w);
5270 
5271       if (n > 1)
5272       {
5273         fprintf(stderr, "%s: A multi columns gutter is not allowed.\n", param);
5274         ctxopt_ctx_disp_usage(ctx_name, exit_after);
5275       }
5276       offset += mblen;
5277     }
5278     free(gutter);
5279   }
5280   win->col_sep = 1; /* Activate the gutter. */
5281 }
5282 
5283 void
column_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5284 column_mode_action(char * ctx_name, char * opt_name, char * param,
5285                    int nb_values, char ** values, int nb_opt_data,
5286                    void ** opt_data, int nb_ctx_data, void ** ctx_data)
5287 {
5288   win_t * win = opt_data[0];
5289 
5290   win->tab_mode  = 0;
5291   win->col_mode  = 1;
5292   win->line_mode = 0;
5293   win->max_cols  = 0;
5294 }
5295 
5296 void
line_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5297 line_mode_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5298                  char ** values, int nb_opt_data, void ** opt_data,
5299                  int nb_ctx_data, void ** ctx_data)
5300 {
5301   win_t * win = opt_data[0];
5302 
5303   win->line_mode = 1;
5304   win->tab_mode  = 0;
5305   win->col_mode  = 0;
5306   win->max_cols  = 0;
5307 }
5308 
5309 void
include_re_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5310 include_re_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5311                   char ** values, int nb_opt_data, void ** opt_data,
5312                   int nb_ctx_data, void ** ctx_data)
5313 {
5314   int *        pattern_def_include = opt_data[0];
5315   char **      include_pattern     = opt_data[1];
5316   langinfo_t * langinfo            = opt_data[2];
5317   misc_t *     misc                = opt_data[3];
5318 
5319   /* Set the default behaviour if not already set. */
5320   /* """"""""""""""""""""""""""""""""""""""""""""" */
5321   if (*pattern_def_include == -1)
5322     *pattern_def_include = 0;
5323 
5324   if (*include_pattern == NULL)
5325     *include_pattern = concat("(", values[0], ")", (char *)0);
5326   else
5327     *include_pattern = concat(*include_pattern, "|(", values[0], ")",
5328                               (char *)0);
5329 
5330   /* Replace the UTF-8 ASCII representations by their binary values in */
5331   /* inclusion patterns.                                               */
5332   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5333   utf8_interpret(*include_pattern, langinfo, misc->invalid_char_substitute);
5334 }
5335 
5336 void
exclude_re_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5337 exclude_re_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5338                   char ** values, int nb_opt_data, void ** opt_data,
5339                   int nb_ctx_data, void ** ctx_data)
5340 {
5341   int *        pattern_def_exclude = opt_data[0];
5342   char **      exclude_pattern     = opt_data[1];
5343   langinfo_t * langinfo            = opt_data[2];
5344   misc_t *     misc                = opt_data[3];
5345 
5346   /* Set the default behaviour if not already set. */
5347   /* """"""""""""""""""""""""""""""""""""""""""""" */
5348   if (*pattern_def_exclude == -1)
5349     *pattern_def_exclude = 0;
5350 
5351   if (*exclude_pattern == NULL)
5352     *exclude_pattern = concat("(", values[0], ")", (char *)0);
5353   else
5354     *exclude_pattern = concat(*exclude_pattern, "|(", values[0], ")",
5355                               (char *)0);
5356 
5357   /* Replace the UTF-8 ASCII representations by their binary values in */
5358   /* exclusion patterns.                                               */
5359   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5360   utf8_interpret(*exclude_pattern, langinfo, misc->invalid_char_substitute);
5361 }
5362 
5363 void
post_subst_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5364 post_subst_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5365                   char ** values, int nb_opt_data, void ** opt_data,
5366                   int nb_ctx_data, void ** ctx_data)
5367 {
5368   ll_t **      list     = opt_data[0];
5369   langinfo_t * langinfo = opt_data[1];
5370   misc_t *     misc     = opt_data[2];
5371 
5372   sed_t * sed_node;
5373   int     i;
5374 
5375   if (*list == NULL)
5376     *list = ll_new();
5377 
5378   for (i = 0; i < nb_values; i++)
5379   {
5380     sed_node          = xmalloc(sizeof(sed_t));
5381     sed_node->pattern = xstrdup(values[i]);
5382     utf8_interpret(sed_node->pattern, langinfo, misc->invalid_char_substitute);
5383     sed_node->stop = 0;
5384     ll_append(*list, sed_node);
5385   }
5386 }
5387 
5388 void
special_level_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5389 special_level_action(char * ctx_name, char * opt_name, char * param,
5390                      int nb_values, char ** values, int nb_opt_data,
5391                      void ** opt_data, int nb_ctx_data, void ** ctx_data)
5392 {
5393   char **      special_pattern = opt_data[0];
5394   win_t *      win             = opt_data[1];
5395   term_t *     term            = opt_data[2];
5396   langinfo_t * langinfo        = opt_data[3];
5397   attrib_t *   init_attr       = opt_data[4];
5398   misc_t *     misc            = opt_data[5];
5399 
5400   attrib_t attr = *init_attr;
5401   char     opt  = param[strlen(param) - 1]; /* last character of param. */
5402   int      i;
5403 
5404   special_pattern[opt - '1'] = xstrdup(values[0]);
5405   utf8_interpret(special_pattern[opt - '1'], langinfo,
5406                  misc->invalid_char_substitute);
5407 
5408   /* Parse optional additional arguments. */
5409   /* """""""""""""""""""""""""""""""""""" */
5410   for (i = 1; i < nb_values; i++)
5411   {
5412     /* Colors must respect the format: <fg color>/<bg color>. */
5413     /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
5414     if (parse_attr(values[i], &attr, term->colors))
5415     {
5416       win->special_attr[opt - '1'].is_set    = FORCED;
5417       win->special_attr[opt - '1'].fg        = attr.fg;
5418       win->special_attr[opt - '1'].bg        = attr.bg;
5419       win->special_attr[opt - '1'].bold      = attr.bold;
5420       win->special_attr[opt - '1'].dim       = attr.dim;
5421       win->special_attr[opt - '1'].reverse   = attr.reverse;
5422       win->special_attr[opt - '1'].standout  = attr.standout;
5423       win->special_attr[opt - '1'].underline = attr.underline;
5424       win->special_attr[opt - '1'].italic    = attr.italic;
5425       win->special_attr[opt - '1'].blink     = attr.blink;
5426     }
5427   }
5428 }
5429 
5430 void
attributes_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5431 attributes_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5432                   char ** values, int nb_opt_data, void ** opt_data,
5433                   int nb_ctx_data, void ** ctx_data)
5434 {
5435   win_t *    win       = opt_data[0];
5436   term_t *   term      = opt_data[1];
5437   attrib_t * init_attr = opt_data[2];
5438 
5439   long i, a;       /* loop index.                               */
5440   long offset = 0; /* nb of chars to ship to find the attribute *
5441                     | representation (prefix size).             */
5442 
5443   attrib_t   attr;
5444   attrib_t * attr_to_set = NULL;
5445 
5446   /* Flags to check if an attribute is already set */
5447   /* """"""""""""""""""""""""""""""""""""""""""""" */
5448   int inc_attr_set           = 0; /* included words.                  */
5449   int exc_attr_set           = 0; /* excluded words.                  */
5450   int cur_attr_set           = 0; /* highlighted word (cursor).       */
5451   int bar_attr_set           = 0; /* scroll bar.                      */
5452   int shift_attr_set         = 0; /* horizontal scrolling arrows.     */
5453   int message_attr_set       = 0; /* message (title).                 */
5454   int tag_attr_set           = 0; /* selected (tagged) words.         */
5455   int cursor_on_tag_attr_set = 0; /* selected words under the cursor. */
5456   int sf_attr_set            = 0; /* currently searched field color.  */
5457   int st_attr_set            = 0; /* currently searched text color.   */
5458   int mf_attr_set            = 0; /* matching word field color.       */
5459   int mt_attr_set            = 0; /* matching word text color.        */
5460   int daccess_attr_set       = 0; /* Direct access text color.        */
5461 
5462   /* Information relatives to the attributes to be searched and set. */
5463   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5464   struct
5465   {
5466     attrib_t * attr;
5467     char *     msg;
5468     int *      flag;
5469     char *     prefix;
5470     int        prefix_len;
5471   } attr_infos[] = {
5472     { &win->exclude_attr, "The exclude attribute is already set.",
5473       &exc_attr_set, "e:", 2 },
5474     { &win->include_attr, "The include attribute is already set.",
5475       &inc_attr_set, "i:", 2 },
5476     { &win->cursor_attr, "The cursor attribute is already set.", &cur_attr_set,
5477       "c:", 2 },
5478     { &win->bar_attr, "The scroll bar attribute is already set.", &bar_attr_set,
5479       "b:", 2 },
5480     { &win->shift_attr, "The shift attribute is already set.", &shift_attr_set,
5481       "s:", 2 },
5482     { &win->message_attr, "The message attribute is already set.",
5483       &message_attr_set, "m:", 2 },
5484     { &win->tag_attr, "The tag attribute is already set.", &tag_attr_set,
5485       "t:", 2 },
5486     { &win->cursor_on_tag_attr,
5487       "The cursor on tagged word attribute is already set.",
5488       &cursor_on_tag_attr_set, "ct:", 3 },
5489     { &win->search_field_attr, "The search field attribute is already set.",
5490       &sf_attr_set, "sf:", 3 },
5491     { &win->search_text_attr, "The search text attribute is already set.",
5492       &st_attr_set, "st:", 3 },
5493     { &win->search_err_field_attr,
5494       "The search with error field attribute is already set.", &sf_attr_set,
5495       "sfe:", 4 },
5496     { &win->search_err_text_attr,
5497       "The search text with error attribute is already set.", &st_attr_set,
5498       "ste:", 4 },
5499     { &win->match_field_attr,
5500       "The matching word field attribute is already set.", &mf_attr_set,
5501       "mf:", 3 },
5502     { &win->match_text_attr, "The matching word text attribute is already set.",
5503       &mt_attr_set, "mt:", 3 },
5504     { &win->match_err_field_attr,
5505       "The matching word with error field attribute is already set.",
5506       &mf_attr_set, "mfe:", 4 },
5507     { &win->match_err_text_attr,
5508       "The matching word with error text attribute is already set.",
5509       &mt_attr_set, "mte:", 4 },
5510     { &win->daccess_attr, "The direct access tag attribute is already set.",
5511       &daccess_attr_set, "da:", 3 },
5512     { NULL, NULL, NULL, NULL, 0 }
5513   };
5514 
5515   /* Parse the arguments. */
5516   /* """""""""""""""""""" */
5517   for (a = 0; a < nb_values; a++)
5518   {
5519     attr = *init_attr;
5520 
5521     i = 0;
5522     while (attr_infos[i].flag != NULL)
5523     {
5524       if (strncmp(values[a], attr_infos[i].prefix, attr_infos[i].prefix_len)
5525           == 0)
5526       {
5527         if (*attr_infos[i].flag)
5528         {
5529           fprintf(stderr, "%s: ", param);
5530           fputs(attr_infos[i].msg, stderr);
5531           fputs("\n", stderr);
5532           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5533         }
5534 
5535         attr_to_set         = attr_infos[i].attr;
5536         *attr_infos[i].flag = 1;
5537         offset              = attr_infos[i].prefix_len;
5538         break; /* We have found a matching prefix, *
5539                 | no need to continue.             */
5540       }
5541       i++;
5542     }
5543     if (attr_infos[i].flag == NULL)
5544     {
5545       fprintf(stderr, "%s: Bad attribute prefix in %s\n", param, values[a]);
5546       ctxopt_ctx_disp_usage(ctx_name, exit_after);
5547     }
5548 
5549     /* Attributes must respect the format: */
5550     /* <fg color>/<bg color>,<styles>.     */
5551     /* """"""""""""""""""""""""""""""""""" */
5552     if (parse_attr(values[a] + offset, &attr, term->colors))
5553     {
5554       attr_to_set->is_set    = FORCED;
5555       attr_to_set->fg        = attr.fg;
5556       attr_to_set->bg        = attr.bg;
5557       attr_to_set->bold      = attr.bold;
5558       attr_to_set->dim       = attr.dim;
5559       attr_to_set->reverse   = attr.reverse;
5560       attr_to_set->standout  = attr.standout;
5561       attr_to_set->underline = attr.underline;
5562       attr_to_set->italic    = attr.italic;
5563       attr_to_set->blink     = attr.blink;
5564     }
5565     else
5566     {
5567       fprintf(stderr, "%s: Bad attribute settings %s\n", param, values[a]);
5568       ctxopt_ctx_disp_usage(ctx_name, exit_after);
5569     }
5570   }
5571 }
5572 
5573 void
limits_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5574 limits_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5575               char ** values, int nb_opt_data, void ** opt_data,
5576               int nb_ctx_data, void ** ctx_data)
5577 {
5578   limit_t * limits = opt_data[0];
5579 
5580   long l;
5581   long val;
5582 
5583   char * lim;
5584   char * endptr;
5585   char * p;
5586 
5587   /* Parse the arguments. */
5588   /* """""""""""""""""""" */
5589   for (l = 0; l < nb_values; l++)
5590   {
5591     errno = 0;
5592     lim   = values[l];
5593     p     = lim + 2;
5594 
5595     switch (*lim)
5596     {
5597       case 'l': /* word length. */
5598         val = strtol(p, &endptr, 0);
5599         if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
5600             || *endptr != '\0')
5601         {
5602           fprintf(stderr, "%s: Invalid word length limit. ", p);
5603           fprintf(stderr, "Using the default value: %ld\n",
5604                   limits->word_length);
5605         }
5606         else
5607           limits->word_length = val;
5608         break;
5609 
5610       case 'w': /* max number of words. */
5611         val = strtol(p, &endptr, 0);
5612         if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
5613             || *endptr != '\0')
5614         {
5615           fprintf(stderr, "%s: Invalid words number limit. ", p);
5616           fprintf(stderr, "Using the default value: %ld\n", limits->words);
5617         }
5618         else
5619           limits->words = val;
5620         break;
5621 
5622       case 'c': /* max number of words. */
5623         val = strtol(p, &endptr, 0);
5624         if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
5625             || *endptr != '\0')
5626         {
5627           fprintf(stderr, "%s: Invalid columns number limit. ", p);
5628           fprintf(stderr, "Using the default value: %ld\n", limits->cols);
5629         }
5630         else
5631           limits->cols = val;
5632         break;
5633 
5634       default:
5635         fprintf(stderr, "%s: Invalid limit keyword, should be l, w or c.\n", p);
5636         break;
5637     }
5638   }
5639 }
5640 
5641 void
version_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5642 version_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5643                char ** values, int nb_opt_data, void ** opt_data,
5644                int nb_ctx_data, void ** ctx_data)
5645 {
5646   fputs("Version: " VERSION "\n", stdout);
5647   exit(EXIT_SUCCESS);
5648 }
5649 
5650 void
timeout_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5651 timeout_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5652                char ** values, int nb_opt_data, void ** opt_data,
5653                int nb_ctx_data, void ** ctx_data)
5654 {
5655   langinfo_t * langinfo = opt_data[0];
5656   misc_t *     misc     = opt_data[1];
5657 
5658   if (strcmp(opt_name, "hidden_timeout") == 0)
5659     quiet_timeout = 1;
5660 
5661   if (strprefix("current", values[0]))
5662     timeout.mode = CURRENT;
5663   else if (strprefix("quit", values[0]))
5664     timeout.mode = QUIT;
5665   else if (strprefix("word", values[0]))
5666   {
5667     if (nb_values == 3)
5668     {
5669       timeout.mode = WORD;
5670       timeout_word = xstrdup(values[1]);
5671       utf8_interpret(timeout_word, langinfo, misc->invalid_char_substitute);
5672     }
5673     else
5674     {
5675       fprintf(stderr, "%s: Missing timeout selected word or delay.\n", param);
5676       ctxopt_ctx_disp_usage(ctx_name, exit_after);
5677     }
5678   }
5679 
5680   if (sscanf(values[nb_values - 1], "%5u", &timeout.initial_value) == 1)
5681   {
5682     timeout.initial_value *= FREQ;
5683     timeout.remain = timeout.initial_value;
5684   }
5685   else
5686   {
5687     fprintf(stderr, "%s: Invalid timeout delay.\n", param);
5688     ctxopt_ctx_disp_usage(ctx_name, exit_after);
5689   }
5690 }
5691 
5692 void
tag_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5693 tag_mode_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5694                 char ** values, int nb_opt_data, void ** opt_data,
5695                 int nb_ctx_data, void ** ctx_data)
5696 {
5697   toggle_t *   toggles  = opt_data[0];
5698   win_t *      win      = opt_data[1];
5699   langinfo_t * langinfo = opt_data[2];
5700   misc_t *     misc     = opt_data[3];
5701 
5702   toggles->taggable = 1;
5703 
5704   if (nb_values == 1)
5705   {
5706     win->sel_sep = xstrdup(values[0]);
5707     utf8_interpret(win->sel_sep, langinfo, misc->invalid_char_substitute);
5708   }
5709 }
5710 
5711 void
pin_mode_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5712 pin_mode_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5713                 char ** values, int nb_opt_data, void ** opt_data,
5714                 int nb_ctx_data, void ** ctx_data)
5715 {
5716   toggle_t *   toggles  = opt_data[0];
5717   win_t *      win      = opt_data[1];
5718   langinfo_t * langinfo = opt_data[2];
5719   misc_t *     misc     = opt_data[3];
5720 
5721   toggles->taggable = 1;
5722   toggles->pinable  = 1;
5723 
5724   if (nb_values == 1)
5725   {
5726     win->sel_sep = xstrdup(values[0]);
5727     utf8_interpret(win->sel_sep, langinfo, misc->invalid_char_substitute);
5728   }
5729 }
5730 
5731 void
search_method_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5732 search_method_action(char * ctx_name, char * opt_name, char * param,
5733                      int nb_values, char ** values, int nb_opt_data,
5734                      void ** opt_data, int nb_ctx_data, void ** ctx_data)
5735 {
5736   misc_t * misc = opt_data[0];
5737 
5738   if (strprefix("prefix", values[0]))
5739     misc->default_search_method = PREFIX;
5740   else if (strprefix("fuzzy", values[0]))
5741     misc->default_search_method = FUZZY;
5742   else if (strprefix("substring", values[0]))
5743     misc->default_search_method = SUBSTRING;
5744   else
5745   {
5746     fprintf(stderr, "%s: Bad search method: %s\n", param, values[0]);
5747     ctxopt_ctx_disp_usage(ctx_name, exit_after);
5748   }
5749 }
5750 
5751 void
auto_da_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5752 auto_da_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5753                char ** values, int nb_opt_data, void ** opt_data,
5754                int nb_ctx_data, void ** ctx_data)
5755 {
5756   char ** daccess_pattern = opt_data[0];
5757   char *  value;
5758   int     i;
5759 
5760   if (nb_values == 0)
5761   {
5762     if (daccess.missing == 'y' || ((daccess.mode & DA_TYPE_POS) == 0))
5763     {
5764       if (*daccess_pattern == NULL)
5765       {
5766         *daccess_pattern = xstrdup("(.)");
5767         daccess.mode |= DA_TYPE_AUTO; /* Auto. */
5768       }
5769       else
5770         *daccess_pattern = concat(*daccess_pattern, "|(.)", (char *)0);
5771     }
5772   }
5773   else
5774     for (i = 0; i < nb_values; i++)
5775     {
5776       if (*values[i] == '\0'
5777           && (daccess.missing == 'y' || ((daccess.mode & DA_TYPE_POS) == 0)))
5778         value = ".";
5779       else
5780         value = values[i];
5781 
5782       if (*daccess_pattern == NULL)
5783       {
5784         *daccess_pattern = concat("(", value, ")", (char *)0);
5785         daccess.mode |= DA_TYPE_AUTO; /* Auto. */
5786       }
5787       else
5788         *daccess_pattern = concat(*daccess_pattern, "|(", value, ")",
5789                                   (char *)0);
5790     }
5791 
5792   if (daccess.def_number < 0)
5793   {
5794     if (strcmp(param, "-N") == 0)
5795       daccess.def_number = 0; /* Words are unnumbered by default. */
5796     else
5797       daccess.def_number = 1; /* Words are numbered by default.   */
5798   }
5799 }
5800 
5801 void
field_da_number_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5802 field_da_number_action(char * ctx_name, char * opt_name, char * param,
5803                        int nb_values, char ** values, int nb_opt_data,
5804                        void ** opt_data, int nb_ctx_data, void ** ctx_data)
5805 {
5806   daccess.mode |= DA_TYPE_POS;
5807 }
5808 
5809 void
da_options_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)5810 da_options_action(char * ctx_name, char * opt_name, char * param, int nb_values,
5811                   char ** values, int nb_opt_data, void ** opt_data,
5812                   int nb_ctx_data, void ** ctx_data)
5813 {
5814   langinfo_t * langinfo      = opt_data[0];
5815   long *       daccess_index = opt_data[1];
5816   misc_t *     misc          = opt_data[2];
5817 
5818   int       pos;
5819   wchar_t * w;
5820   int       n;
5821   int       i;
5822 
5823   /* Parse optional additional arguments. */
5824   /* """""""""""""""""""""""""""""""""""" */
5825   for (i = 0; i < nb_values; i++)
5826   {
5827     char * value = values[i];
5828 
5829     switch (*value)
5830     {
5831       case 'l': /* Left char .*/
5832         free(daccess.left);
5833 
5834         daccess.left = xstrdup(value + 2);
5835         utf8_interpret(daccess.left, langinfo, misc->invalid_char_substitute);
5836 
5837         if (utf8_strlen(daccess.left) != 1)
5838         {
5839           fprintf(stderr, "%s: Too many characters after l:\n", param);
5840           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5841         }
5842 
5843         n = wcswidth((w = utf8_strtowcs(daccess.left)), 1);
5844         free(w);
5845 
5846         if (n > 1)
5847         {
5848           fprintf(stderr,
5849                   "%s: A multi columns character is not allowed "
5850                   "after l:\n",
5851                   param);
5852           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5853         }
5854         break;
5855 
5856       case 'r': /* Right char. */
5857         free(daccess.right);
5858 
5859         daccess.right = xstrdup(value + 2);
5860         utf8_interpret(daccess.right, langinfo, misc->invalid_char_substitute);
5861 
5862         if (utf8_strlen(daccess.right) != 1)
5863         {
5864           fprintf(stderr, "%s: Too many characters after r:\n", param);
5865           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5866         }
5867 
5868         n = wcswidth((w = utf8_strtowcs(daccess.right)), 1);
5869         free(w);
5870 
5871         if (n > 1)
5872         {
5873           fprintf(stderr,
5874                   "%s: A multi columns character is not allowed "
5875                   "after r:\n",
5876                   param);
5877           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5878         }
5879         break;
5880 
5881       case 'a': /* Alignment. */
5882         if (strprefix("left", value + 2))
5883           daccess.alignment = 'l';
5884         else if (strprefix("right", value + 2))
5885           daccess.alignment = 'r';
5886         else
5887         {
5888           fprintf(stderr,
5889                   "%s: The value after a: must be "
5890                   "l(eft) or r(ight)\n",
5891                   param);
5892           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5893         }
5894         break;
5895 
5896       case 'p': /* Padding. */
5897         if (strprefix("all", value + 2))
5898           daccess.padding = 'a';
5899         else if (strprefix("included", value + 2))
5900           daccess.padding = 'i';
5901         else
5902         {
5903           fprintf(stderr, "%s: Bad value after p:\n", param);
5904           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5905         }
5906         break;
5907 
5908       case 'w': /* Width. */
5909         if (sscanf(value + 2, "%d%n", &daccess.length, &pos) != 1)
5910         {
5911           fprintf(stderr, "%s: Bad value after w:\n", param);
5912           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5913         }
5914         if (value[pos + 2] != '\0')
5915         {
5916           fprintf(stderr, "%s: Bad value after w:\n", param);
5917           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5918         }
5919         if (daccess.length <= 0 || daccess.length > 5)
5920         {
5921           fprintf(stderr, "%s: w sub-option must be between 1 and 5\n", param);
5922           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5923         }
5924         break;
5925 
5926       case 'o': /* Start offset. */
5927         if (sscanf(value + 2, "%zu%n+", &daccess.offset, &pos) == 1)
5928         {
5929           if (value[pos + 2] == '+')
5930           {
5931             daccess.plus = 1;
5932 
5933             if (value[pos + 3] != '\0')
5934             {
5935               fprintf(stderr, "%s: Bad value after o:\n", param);
5936               ctxopt_ctx_disp_usage(ctx_name, exit_after);
5937             }
5938           }
5939           else if (value[pos + 2] != '\0')
5940           {
5941             fprintf(stderr, "%s: Bad value after o:\n", param);
5942             ctxopt_ctx_disp_usage(ctx_name, exit_after);
5943           }
5944         }
5945         else
5946         {
5947           fprintf(stderr, "%s: Bad value after o:\n", param);
5948           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5949         }
5950 
5951         break;
5952 
5953       case 'n': /* Number of digits to extract. */
5954         if (sscanf(value + 2, "%d%n", &daccess.size, &pos) != 1)
5955         {
5956           fprintf(stderr, "%s: Bad value after n:\n", param);
5957           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5958         }
5959         if (value[pos + 2] != '\0')
5960         {
5961           fprintf(stderr, "%s: Bad value after n:\n", param);
5962           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5963         }
5964         if (daccess.size <= 0 || daccess.size > 5)
5965         {
5966           fprintf(stderr, "n sub-option must have a value between 1 and 5.\n");
5967           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5968         }
5969         break;
5970 
5971       case 'i': /* Number of UTF-8 glyphs to ignore after the *
5972                  | selector to extract.                       */
5973         if (sscanf(value + 2, "%zu%n", &daccess.ignore, &pos) != 1)
5974         {
5975           fprintf(stderr, "%s: Bad value after i:\n", param);
5976           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5977         }
5978         if (value[pos + 2] != '\0')
5979         {
5980           fprintf(stderr, "%s: Bad value after i:\n", param);
5981           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5982         }
5983         break;
5984 
5985       case 'f': /* Follow. */
5986         if (strprefix("yes", value + 2))
5987           daccess.follow = 'y';
5988         else if (strprefix("no", value + 2))
5989           daccess.follow = 'n';
5990         else
5991         {
5992           fprintf(stderr, "%s: Bad value after f:\n", param);
5993           ctxopt_ctx_disp_usage(ctx_name, exit_after);
5994         }
5995         break;
5996 
5997       case 'm': /* Possibly number missing embedded numbers. */
5998         if (strprefix("yes", value + 2))
5999           daccess.missing = 'y';
6000         else if (strprefix("no", value + 2))
6001           daccess.missing = 'n';
6002         else
6003         {
6004           fprintf(stderr, "%s: Bad value after m:\n", param);
6005           ctxopt_ctx_disp_usage(ctx_name, exit_after);
6006         }
6007         break;
6008 
6009       case 'd': /* Decorate. */
6010         free(daccess.num_sep);
6011 
6012         daccess.num_sep = xstrdup(value + 2);
6013         utf8_interpret(daccess.num_sep, langinfo,
6014                        misc->invalid_char_substitute);
6015 
6016         if (utf8_strlen(daccess.num_sep) != 1)
6017         {
6018           fprintf(stderr, "%s: Too many characters after d:\n", param);
6019           ctxopt_ctx_disp_usage(ctx_name, exit_after);
6020         }
6021 
6022         n = wcswidth((w = utf8_strtowcs(daccess.num_sep)), 1);
6023         free(w);
6024 
6025         if (n > 1)
6026         {
6027           fprintf(stderr,
6028                   "%s: A multi columns separator is not allowed "
6029                   "after d:\n",
6030                   param);
6031           ctxopt_ctx_disp_usage(ctx_name, exit_after);
6032         }
6033         break;
6034 
6035       case 's': /* Start index. */
6036       {
6037         long pos;
6038 
6039         if (sscanf(value + 2, "%ld%ln", daccess_index, &pos) == 1)
6040         {
6041           if (*daccess_index < 0 || *(value + 2 + pos) != '\0')
6042             *daccess_index = 1;
6043         }
6044         else
6045         {
6046           fprintf(stderr, "%s: Invalid first index after s:\n", param);
6047           ctxopt_ctx_disp_usage(ctx_name, exit_after);
6048         }
6049       }
6050       break;
6051 
6052       case 'h': /* Head. */
6053         if (strprefix("trim", value + 2))
6054           daccess.head = 't';
6055         else if (strprefix("cut", value + 2))
6056           daccess.head = 'c';
6057         else if (strprefix("keep", value + 2))
6058           daccess.head = 'k';
6059         else
6060         {
6061           fprintf(stderr, "%s: Bad value after :h\n", param);
6062           ctxopt_ctx_disp_usage(ctx_name, exit_after);
6063         }
6064         break;
6065 
6066       default:
6067       {
6068         fprintf(stderr, "%s: Bad sub-command: %s\n", param, value);
6069         ctxopt_ctx_disp_usage(ctx_name, exit_after);
6070       }
6071     }
6072 
6073     if (daccess.length <= 0 || daccess.length > 5)
6074       daccess.length = -2; /* special value -> auto. */
6075   }
6076 }
6077 
6078 void
ignore_quotes_action(char * ctx_name,char * opt_name,char * param,int nb_values,char ** values,int nb_opt_data,void ** opt_data,int nb_ctx_data,void ** ctx_data)6079 ignore_quotes_action(char * ctx_name, char * opt_name, char * param,
6080                      int nb_values, char ** values, int nb_opt_data,
6081                      void ** opt_data, int nb_ctx_data, void ** ctx_data)
6082 {
6083   misc_t * misc = opt_data[0];
6084 
6085   misc->ignore_quotes = 1;
6086 }
6087 
6088 /* =================================================================== */
6089 /* Cancels a search. Called when ESC is hit or a new search session is */
6090 /* initiated and the incremental_search option is not used.            */
6091 /* =================================================================== */
6092 void
reset_search_buffer(win_t * win,search_data_t * search_data,ticker_t * timers,toggle_t * toggles,term_t * term,daccess_t * daccess,langinfo_t * langinfo,long last_line,char * tmp_word,long word_real_max_size)6093 reset_search_buffer(win_t * win, search_data_t * search_data, ticker_t * timers,
6094                     toggle_t * toggles, term_t * term, daccess_t * daccess,
6095                     langinfo_t * langinfo, long last_line, char * tmp_word,
6096                     long word_real_max_size)
6097 {
6098   /* ESC key has been pressed. */
6099   /* """"""""""""""""""""""""" */
6100   search_mode_t old_search_mode = search_mode;
6101 
6102   /* Cancel the search timer. */
6103   /* """""""""""""""""""""""" */
6104   search_timer = 0;
6105 
6106   search_data->fuzzy_err     = 0;
6107   search_data->only_starting = 0;
6108   search_data->only_ending   = 0;
6109 
6110   if (help_mode)
6111     disp_lines(win, toggles, current, count, search_mode, search_data, term,
6112                last_line, tmp_word, langinfo);
6113 
6114   /* Reset the direct access selector stack. */
6115   /* """"""""""""""""""""""""""""""""""""""" */
6116   memset(daccess_stack, '\0', 6);
6117   daccess_stack_head = 0;
6118   daccess_timer      = timers->direct_access;
6119 
6120   /* Clean the potential matching words non empty list. */
6121   /* """""""""""""""""""""""""""""""""""""""""""""""""" */
6122   search_mode = NONE;
6123 
6124   if (matches_count > 0 || old_search_mode != search_mode)
6125   {
6126     clean_matches(search_data, word_real_max_size);
6127 
6128     disp_lines(win, toggles, current, count, search_mode, search_data, term,
6129                last_line, tmp_word, langinfo);
6130   }
6131 }
6132 
6133 /* ================= */
6134 /* Main entry point. */
6135 /* ================= */
6136 int
main(int argc,char * argv[])6137 main(int argc, char * argv[])
6138 {
6139   /* Mapping of supported charsets and the number of bits used in them. */
6140   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6141   charsetinfo_t all_supported_charsets[] = {
6142     { "UTF-8", 8 },
6143 
6144     { "ANSI_X3.4-1968", 7 },
6145     { "ANSI_X3.4-1986", 7 },
6146     { "646", 7 },
6147     { "ASCII", 7 },
6148     { "CP367", 7 },
6149     { "IBM367", 7 },
6150     { "ISO_646.BASIC", 7 },
6151     { "ISO_646.IRV:1991", 7 },
6152     { "ISO_646.IRV", 7 },
6153     { "ISO646-US", 7 },
6154     { "ISO-IR-6", 7 },
6155     { "US", 7 },
6156     { "US-ASCII", 7 },
6157 
6158     { "hp-roman8", 8 },
6159     { "roman8", 8 },
6160     { "r8", 8 },
6161 
6162     { "ISO8859-1", 8 },
6163     { "ISO-8859-1", 8 },
6164     { "ISO-IR-100", 8 },
6165     { "ISO_8859-1:1987", 8 },
6166     { "ISO_8859-1", 8 },
6167     { "LATIN1", 8 },
6168     { "L1", 8 },
6169     { "IBM819", 8 },
6170     { "CP819", 8 },
6171 
6172     { "ISO8859-2", 8 },
6173     { "ISO-8859-2", 8 },
6174     { "ISO-IR-101", 8 },
6175     { "ISO_8859-2:1987", 8 },
6176     { "ISO_8859-2", 8 },
6177     { "LATIN2", 8 },
6178     { "L2", 8 },
6179     { "CP28592", 8 },
6180 
6181     { "ISO8859-3", 8 },
6182     { "ISO-8859-3", 8 },
6183     { "ISO-IR-109", 8 },
6184     { "ISO_8859-3:1988", 8 },
6185     { "ISO_8859-3", 8 },
6186     { "LATIN3", 8 },
6187     { "L3", 8 },
6188     { "CP28593", 8 },
6189 
6190     { "ISO8859-4", 8 },
6191     { "ISO-8859-4", 8 },
6192     { "ISO-IR-110", 8 },
6193     { "ISO_8859-4:1988", 8 },
6194     { "LATIN4", 8 },
6195     { "L4", 8 },
6196     { "CP28594", 8 },
6197 
6198     { "ISO8859-5", 8 },
6199     { "ISO-8859-5", 8 },
6200     { "ISO-IR-144", 8 },
6201     { "ISO_8859-5:1988", 8 },
6202     { "CYRILLIC", 8 },
6203     { "CP28595", 8 },
6204 
6205     { "KOI8-R", 8 },
6206     { "KOI8-RU", 8 },
6207     { "KOI8-U", 8 },
6208 
6209     { "ISO8859-6", 8 },
6210     { "ISO-8859-6", 8 },
6211     { "ISO-IR-127", 8 },
6212     { "ISO_8859-6:1987", 8 },
6213     { "ECMA-114", 8 },
6214     { "ASMO-708", 8 },
6215     { "ARABIC", 8 },
6216     { "CP28596", 8 },
6217 
6218     { "ISO8859-7", 8 },
6219     { "ISO-8859-7", 8 },
6220     { "ISO-IR-126", 8 },
6221     { "ISO_8859-7:2003", 8 },
6222     { "ISO_8859-7:1987", 8 },
6223     { "ELOT_928", 8 },
6224     { "ECMA-118", 8 },
6225     { "GREEK", 8 },
6226     { "GREEK8", 8 },
6227     { "CP28597", 8 },
6228 
6229     { "ISO8859-8", 8 },
6230     { "ISO-8859-8", 8 },
6231     { "ISO-IR-138", 8 },
6232     { "ISO_8859-8:1988", 8 },
6233     { "HEBREW", 8 },
6234     { "CP28598", 8 },
6235 
6236     { "ISO8859-9", 8 },
6237     { "ISO-8859-9", 8 },
6238     { "ISO-IR-148", 8 },
6239     { "ISO_8859-9:1989", 8 },
6240     { "LATIN5", 8 },
6241     { "L5", 8 },
6242     { "CP28599", 8 },
6243 
6244     { "ISO8859-10", 8 },
6245     { "ISO-8859-10", 8 },
6246     { "ISO-IR-157", 8 },
6247     { "ISO_8859-10:1992", 8 },
6248     { "LATIN6", 8 },
6249     { "L6", 8 },
6250     { "CP28600", 8 },
6251 
6252     { "ISO8859-11", 8 },
6253     { "ISO-8859-11", 8 },
6254     { "ISO-8859-11:2001", 8 },
6255     { "ISO-IR-166", 8 },
6256     { "CP474", 8 },
6257 
6258     { "TIS-620", 8 },
6259     { "TIS620", 8 },
6260     { "TIS620-0", 8 },
6261     { "TIS620.2529-1", 8 },
6262     { "TIS620.2533-0", 8 },
6263 
6264     /* ISO-8859-12 was abandoned in 1997. */
6265     /* """""""""""""""""""""""""""""""""" */
6266 
6267     { "ISO8859-13", 8 },
6268     { "ISO-8859-13", 8 },
6269     { "ISO-IR-179", 8 },
6270     { "LATIN7", 8 },
6271     { "L7", 8 },
6272     { "CP28603", 8 },
6273 
6274     { "ISO8859-14", 8 },
6275     { "ISO-8859-14", 8 },
6276     { "LATIN8", 8 },
6277     { "L8", 8 },
6278 
6279     { "ISO8859-15", 8 },
6280     { "ISO-8859-15", 8 },
6281     { "LATIN-9", 8 },
6282     { "CP28605", 8 },
6283 
6284     { "ISO8859-16", 8 },
6285     { "ISO-8859-16", 8 },
6286     { "ISO-IR-226", 8 },
6287     { "ISO_8859-16:2001", 8 },
6288     { "LATIN10", 8 },
6289     { "L10", 8 },
6290 
6291     { "CP1250", 8 },
6292     { "CP1251", 8 },
6293 
6294     { "CP1252", 8 },
6295     { "MS-ANSI", 8 },
6296     { NULL, 0 }
6297   };
6298 
6299   int     nb_rem_args = 0;
6300   char ** rem_args    = NULL;
6301 
6302   char * message = NULL; /* message to be displayed above the selection      *
6303                           | window.                                          */
6304   ll_t * message_lines_list = NULL; /* list of the lines in the message to   *
6305                                      | be displayed.                         */
6306   long message_max_width = 0; /* total width of the message (longest line).  */
6307   long message_max_len   = 0; /* max number of bytes taken by a message      *
6308                                | line.                                       */
6309 
6310   char * int_string = NULL; /* String to be output when typing ^C.           */
6311   int    int_as_in_shell = 1; /* CTRL-C mimics the shell behaviour.          */
6312 
6313   FILE * input_file; /* The name of the file passed as argument if any.      */
6314 
6315   long index; /* generic counter.                                            */
6316 
6317   long daccess_index = 1; /* First index of the numbered words.              */
6318 
6319   char *  daccess_np = NULL; /* direct access numbered pattern.              */
6320   regex_t daccess_np_re; /* variable to store the compiled direct access     *
6321                           | pattern (-N) RE.                                 */
6322 
6323   char *  daccess_up = NULL; /* direct access not numbered pattern.          */
6324   regex_t daccess_up_re; /* variable to store the compiled direct access     *
6325                           | pattern (-U) RE.                                 */
6326 
6327   char *  include_pattern     = NULL;
6328   char *  exclude_pattern     = NULL;
6329   int     pattern_def_include = -1; /* Set to -1 until an -i or -e option    *
6330                                      | is specified, This variable remembers *
6331                                      | if the words not matched will be      *
6332                                      | included (value 1) or excluded        *
6333                                      | (value 0) by default.                 */
6334   regex_t include_re; /* variable to store the compiled include (-i) REs.    */
6335   regex_t exclude_re; /* variable to store the compiled exclude (-e) REs.    */
6336 
6337   ll_t * early_sed_list   = NULL; /* List of sed like string representation  *
6338                                    | of regex given after (-ES).             */
6339   ll_t * sed_list         = NULL; /* List of sed like string representation  *
6340                                    | of regex given after (-S).              */
6341   ll_t * include_sed_list = NULL; /* idem for -I.                            */
6342   ll_t * exclude_sed_list = NULL; /* idem for -E.                            */
6343 
6344   ll_t * inc_col_interval_list = NULL; /* list of included or                */
6345   ll_t * exc_col_interval_list = NULL; /* excluded numerical intervals       */
6346   ll_t * inc_row_interval_list = NULL; /* for lines and columns.             */
6347   ll_t * exc_row_interval_list = NULL;
6348 
6349   ll_t * inc_col_regex_list = NULL; /* same for lines and columns specified. */
6350   ll_t * exc_col_regex_list = NULL; /* by regular expressions.               */
6351   ll_t * inc_row_regex_list = NULL;
6352   ll_t * exc_row_regex_list = NULL;
6353 
6354   filters_t rows_filter_type = UNKNOWN_FILTER;
6355 
6356   char *  first_word_pattern = NULL; /* used by -A/-Z.                       */
6357   char *  last_word_pattern  = NULL;
6358   regex_t first_word_re;
6359   regex_t last_word_re;
6360 
6361   char *  special_pattern[9] = { NULL, NULL, NULL, NULL, NULL,
6362                                 NULL, NULL, NULL, NULL }; /* -1 .. -9 */
6363   regex_t special_re[9];
6364 
6365   int include_visual_only = 0; /* If set to 1, the original word which is    *
6366                                 | read from stdin will be output even if its */
6367   int exclude_visual_only = 0; /* visual representation was modified via     *
6368                                 | -S/-I/-E.                                  */
6369 
6370   ll_t * cols_selector_list = NULL;
6371   char * cols_selector      = NULL;
6372 
6373   ll_t * rows_selector_list = NULL;
6374   char * rows_selector      = NULL;
6375 
6376   long wi; /* word index.                                                    */
6377 
6378   term_t term; /* Terminal structure.                                        */
6379 
6380   tst_node_t * tst_word    = NULL; /* TST used by the search function.       */
6381   tst_node_t * tst_daccess = NULL; /* TST used by the direct access system.  */
6382 
6383   long   page;     /* Step for the vertical cursor moves.                    */
6384   char * word;     /* Temporary variable to work on words.                   */
6385   char * tmp_word; /* Temporary variable able to contain  the beginning of   *
6386                     | the word to be displayed.                              */
6387 
6388   long     last_line = 0; /* last logical line number (from 0).              */
6389   win_t    win;
6390   limit_t  limits;  /* set of various limitations.                           */
6391   ticker_t timers;  /* timers contents.                                      */
6392   misc_t   misc;    /* misc contents.                                        */
6393   toggle_t toggles; /* set of binary indicators.                             */
6394 
6395   int    old_fd0; /* backups of the old stdin file descriptor.               */
6396   int    old_fd1; /* backups of the old stdout file descriptor.              */
6397   FILE * old_stdin;
6398   FILE * old_stdout; /* The selected word will go there.                     */
6399 
6400   long nl;     /* Number of lines displayed in the window.                   */
6401   long offset; /* Used to correctly put the cursor at the start of the       *
6402                 | selection window, even after a terminal vertical scroll.   */
6403 
6404   long first_selectable; /* Index of the first selectable word in the input  *
6405                           | stream.                                          */
6406   long last_selectable;  /* Index of the last selectable word in the input   *
6407                           | stream.                                          */
6408 
6409   long min_size; /* Minimum screen width of a column in tabular mode.        */
6410 
6411   long tab_max_size;      /* Maximum screen width of a column in tabular     *
6412                            | mode.                                           */
6413   long tab_real_max_size; /* Maximum size in bytes of a column in tabular    *
6414                            | mode.                                           */
6415 
6416   long * col_real_max_size = NULL; /* Array of maximum sizes (bytes) of each */
6417                                    /* column in column mode.                 */
6418   long * col_max_size = NULL;      /* Array of maximum sizes of each column  */
6419                                    /* in column mode.                        */
6420 
6421   long word_real_max_size = 0; /* size of the longer word after expansion.   */
6422   long cols_real_max_size = 0; /* Max real width of all columns used when    *
6423                                 | -w and -c are both set.                    */
6424   long cols_max_size      = 0; /* Same as above for the columns widths       */
6425 
6426   long col_index   = 0; /* Index of the current column when reading words,   *
6427                          | used  in column mode.                             */
6428   long cols_number = 0; /* Number of columns in column mode.                 */
6429 
6430   char * pre_selection_index = NULL; /* pattern used to set the initial      *
6431                                       | cursor position.                     */
6432   unsigned char buffer[16];          /* Input buffer.                        */
6433 
6434   search_data_t search_data;
6435   search_data.buf = NULL;    /* Search buffer                                */
6436   search_data.len = 0;       /* Current position in the search buffer        */
6437   search_data.utf8_len  = 0; /* Current position in the search buffer in     *
6438                               | UTF-8 units.                                 */
6439   search_data.fuzzy_err = 0; /* reset the error indicator.                   */
6440   search_data.fuzzy_err_pos = -1; /* no last error position in search        *
6441                                   buffer.                                    */
6442 
6443   long matching_word_cur_index = -1; /* cache for the next/previous moves    *
6444                                       | in the matching words array.         */
6445 
6446   struct sigaction sa; /* Signal structure.                                  */
6447 
6448   char * iws = NULL, *ils = NULL, *zg = NULL;
6449   ll_t * word_delims_list   = NULL;
6450   ll_t * zapped_glyphs_list = NULL;
6451   ll_t * record_delims_list = NULL;
6452 
6453   char utf8_buffer[5]; /* buffer to store the bytes of a UTF-8 glyph         *
6454                         | (4 chars max).                                     */
6455   unsigned char is_last;
6456   char *        charset;
6457 
6458   char * home_ini_file;  /* init file full path.                             */
6459   char * local_ini_file; /* init file full path.                             */
6460 
6461   charsetinfo_t * charset_ptr;
6462   langinfo_t      langinfo;
6463   int             is_supported_charset;
6464 
6465   long line_count = 0; /* Only used when -R is selected.                     */
6466 
6467   attrib_t init_attr;
6468 
6469   ll_node_t * inc_interval_node = NULL; /* one node of this list.            */
6470   ll_node_t * exc_interval_node = NULL; /* one node of this list.            */
6471 
6472   interval_t * inc_interval;       /* the data in each node.                 */
6473   interval_t * exc_interval;       /* the data in each node.                 */
6474   int          row_def_selectable; /* default selectable value.              */
6475 
6476   int line_selected_by_regex = 0;
6477   int line_excluded          = 0;
6478 
6479   char * timeout_message;
6480 
6481   char * common_options;
6482   char * main_options, *main_spec_options;
6483   char * col_options, *col_spec_options;
6484   char * line_options, *line_spec_options;
6485   char * tab_options, *tab_spec_options;
6486   char * tag_options, *tag_spec_options;
6487 
6488   /* Used to check the usablility of the DSR terminal feature. */
6489   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6490   int row; /* absolute line position in terminal (1...)   */
6491   int col; /* absolute column position in terminal (1...) */
6492 
6493   /* Initialize some internal data structures. */
6494   /* """"""""""""""""""""""""""""""""""""""""" */
6495   init_main_ds(&init_attr, &win, &limits, &timers, &toggles, &misc, &timeout,
6496                &daccess);
6497 
6498   /* direct access variable initialization. */
6499   /* """""""""""""""""""""""""""""""""""""" */
6500   daccess_stack      = xcalloc(6, 1);
6501   daccess_stack_head = 0;
6502 
6503   /* fuzzy variables initialization. */
6504   /* """"""""""""""""""""""""""""""" */
6505   tst_search_list = ll_new();
6506   ll_append(tst_search_list, sub_tst_new());
6507 
6508   matching_words_a_size = 64;
6509   matching_words_a      = xmalloc(matching_words_a_size * sizeof(long));
6510   matches_count         = 0;
6511 
6512   best_matching_words_a_size = 16;
6513   best_matching_words_a = xmalloc(best_matching_words_a_size * sizeof(long));
6514   best_matches_count    = 0;
6515 
6516   /* Initialize the tag hit number which will permit to sort the */
6517   /* pinned words when displayed.                                */
6518   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6519   long next_tag_nb = 0;
6520 
6521   /* Columns selection variables. */
6522   /* """""""""""""""""""""""""""" */
6523   char * cols_filter = NULL;
6524 
6525   /* Initialize the count of tagged words. */
6526   /* """"""""""""""""""""""""""""""""""""" */
6527   long tagged_words = 0;
6528 
6529   /* Get the current locale. */
6530   /* """"""""""""""""""""""" */
6531   setlocale(LC_ALL, "");
6532   charset = nl_langinfo(CODESET);
6533 
6534   /* Check if the local charset is supported. */
6535   /* """""""""""""""""""""""""""""""""""""""" */
6536   is_supported_charset = 0;
6537   charset_ptr          = all_supported_charsets;
6538 
6539   while (charset_ptr->name != NULL)
6540   {
6541     if (my_strcasecmp(charset, charset_ptr->name) == 0)
6542     {
6543       is_supported_charset = 1;
6544       langinfo.bits        = charset_ptr->bits;
6545       break;
6546     }
6547     charset_ptr++;
6548   }
6549 
6550   if (!is_supported_charset)
6551   {
6552     fprintf(stderr, "%s is not a supported charset.", charset);
6553 
6554     exit(EXIT_FAILURE);
6555   }
6556 
6557   /* Remember the fact that the charset is UTF-8. */
6558   /* """""""""""""""""""""""""""""""""""""""""""" */
6559   if (strcmp(charset, "UTF-8") == 0)
6560     langinfo.utf8 = 1;
6561   else
6562     langinfo.utf8 = 0;
6563 
6564   /* my_isprint is a function pointer that points to the 7 or 8-bit */
6565   /* version of isprint according to the content of UTF-8.          */
6566   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6567   if (langinfo.utf8 || langinfo.bits == 8)
6568     my_isprint = isprint8;
6569   else
6570     my_isprint = isprint7;
6571 
6572   /* Set terminal in noncanonical, noecho mode and  */
6573   /* if TERM is unset or unknown, vt100 is assumed. */
6574   /* """""""""""""""""""""""""""""""""""""""""""""" */
6575   if (getenv("TERM") == NULL)
6576     setupterm("vt100", 1, (int *)0);
6577   else
6578     setupterm((char *)0, 1, (int *)0);
6579 
6580   /* Get the number of colors if the use of colors is available */
6581   /* and authorized.                                            */
6582   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6583   if (getenv("NO_COLOR") != NULL)
6584     term.colors = 0;
6585   else
6586   {
6587     term.colors = tigetnum("colors");
6588     if (term.colors < 0)
6589       term.colors = 0;
6590   }
6591 
6592   /* Ignore SIGTTIN. */
6593   /* """"""""""""""" */
6594   sigset_t sigs, oldsigs;
6595 
6596   sigemptyset(&sigs);
6597   sigaddset(&sigs, SIGTTIN);
6598   sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
6599 
6600   /* Temporarily set /dev/tty as stdin/stdout to get its size */
6601   /* even in a pipe.                                          */
6602   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6603   old_fd0    = dup(0);
6604   old_stdin  = freopen("/dev/tty", "r", stdin);
6605   old_fd1    = dup(1);
6606   old_stdout = freopen("/dev/tty", "w", stdout);
6607 
6608   if (old_stdin == NULL || old_stdout == NULL)
6609   {
6610     fprintf(stderr, "A terminal is required to use this program.\n");
6611     exit(EXIT_FAILURE);
6612   }
6613 
6614   /* Get the number of lines/columns of the terminal. */
6615   /* """""""""""""""""""""""""""""""""""""""""""""""" */
6616   get_terminal_size(&term.nlines, &term.ncolumns, &term);
6617 
6618   /* Restore the old stdin and stdout. */
6619   /* """"""""""""""""""""""""""""""""" */
6620   dup2(old_fd0, 0);
6621   dup2(old_fd1, 1);
6622   close(old_fd0);
6623   close(old_fd1);
6624 
6625   /* Default substitution character on invalid input. */
6626   /* """""""""""""""""""""""""""""""""""""""""""""""" */
6627   misc.invalid_char_substitute = '.';
6628 
6629   /* Build the full path of the .ini file. */
6630   /* """"""""""""""""""""""""""""""""""""" */
6631   home_ini_file  = make_ini_path(argv[0], "HOME");
6632   local_ini_file = make_ini_path(argv[0], "PWD");
6633 
6634   /* Set the attributes from the configuration file if possible. */
6635   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6636   if (ini_load(home_ini_file, &win, &term, &limits, &timers, &misc, &langinfo,
6637                ini_cb))
6638     exit(EXIT_FAILURE);
6639 
6640   if (ini_load(local_ini_file, &win, &term, &limits, &timers, &misc, &langinfo,
6641                ini_cb))
6642     exit(EXIT_FAILURE);
6643 
6644   free(home_ini_file);
6645   free(local_ini_file);
6646 
6647   /* Command line options setting using ctxopt. */
6648   /* """""""""""""""""""""""""""""""""""""""""" */
6649   ctxopt_init(argv[0], "stop_if_non_option=No "
6650                        "allow_abbreviations=No "
6651                        "display_usage_on_error=Yes ");
6652 
6653   common_options = "[*help] "
6654                    "[*usage] "
6655                    "[include_re... #regex] "
6656                    "[exclude_re... #regex] "
6657                    "[title #message] "
6658                    "[int [#string]] "
6659                    "[attributes #prefix:attr...] "
6660                    "[special_level_1 #...<3] "
6661                    "[special_level_2 #...<3] "
6662                    "[special_level_3 #...<3] "
6663                    "[special_level_4 #...<3] "
6664                    "[special_level_5 #...<3] "
6665                    "[special_level_6 #...<3] "
6666                    "[special_level_7 #...<3] "
6667                    "[special_level_8 #...<3] "
6668                    "[special_level_9 #...<3] "
6669                    "[zapped_glyphs #bytes] "
6670                    "[lines [#height]] "
6671                    "[blank_nonprintable] "
6672                    "[*invalid_character #invalid_char_subst] "
6673                    "[center_mode] "
6674                    "[clean] "
6675                    "[keep_spaces] "
6676                    "[word_separators #bytes] "
6677                    "[no_scroll_bar] "
6678                    "[early_subst_all... #/regex/repl/opts] "
6679                    "[post_subst_all... #/regex/repl/opts] "
6680                    "[post_subst_included... #/regex/repl/opts] "
6681                    "[post_subst_excluded... #/regex/repl/opts] "
6682                    "[search_method #prefix|substring|fuzzy] "
6683                    "[start_pattern #pattern] "
6684                    "[timeout #...] "
6685                    "[hidden_timeout #...] "
6686                    "[validate_in_search_mode] "
6687                    "[visual_bell] "
6688                    "[ignore_quotes] "
6689                    "[incremental_search] "
6690                    "[limits #limit:value...] "; /* keep the last space! */
6691 
6692   main_spec_options = "[*version] "
6693                       "[*long_help] "
6694                       "[da_options #prefix:attr...] "
6695                       "[auto_da_number... [#regex...]] "
6696                       "[auto_da_unnumber... [#regex...]] "
6697                       "[field_da_number] "
6698                       "[column_mode>Columns] "
6699                       "[line_mode>Lines] "
6700                       "[tab_mode>Tabulations [#cols]] "
6701                       "[tag_mode>Tagging [#delim]] "
6702                       "[pin_mode>Tagging [#delim]]";
6703 
6704   col_spec_options = "[wide_mode] "
6705                      "[columns_select... #selector...] "
6706                      "[rows_select... #selector...] "
6707                      "[gutter [#string]] "
6708                      "[line_separators #bytes] "
6709                      "[da_options #prefix:attr...] "
6710                      "[auto_da_number... [#regex...]] "
6711                      "[auto_da_unnumber... [#regex...]] "
6712                      "[field_da_number] "
6713                      "[tag_mode>Tagging [#delim]] "
6714                      "[pin_mode>Tagging [#delim]] "
6715                      "[force_first_column #regex] "
6716                      "[force_last_column #regex]";
6717 
6718   line_spec_options = "[rows_select... #selector...] "
6719                       "[line_separators #bytes] "
6720                       "[da_options #prefix:attr...] "
6721                       "[auto_da_number... [#regex...]] "
6722                       "[auto_da_unnumber... [#regex...]] "
6723                       "[field_da_number] "
6724                       "[tag_mode>Tagging [#delim]] "
6725                       "[pin_mode>Tagging [#delim]] "
6726                       "[force_first_column #regex] "
6727                       "[force_last_column #regex]";
6728 
6729   tab_spec_options = "[wide_mode] "
6730                      "[gutter [#string]] "
6731                      "[line_separators #bytes] "
6732                      "[da_options #prefix:attr...] "
6733                      "[auto_da_number... [#regex...]] "
6734                      "[auto_da_unnumber... [#regex...]] "
6735                      "[field_da_number] "
6736                      "[tag_mode>Tagging [#delim]] "
6737                      "[pin_mode>Tagging [#delim]] "
6738                      "[force_first_column #regex] "
6739                      "[force_last_column #regex]";
6740 
6741   tag_spec_options = "[auto_tag] "
6742                      "[no_auto_tag] "
6743                      "[column_mode>Columns] "
6744                      "[line_mode>Lines] "
6745                      "[tab_mode>Tabulations [#cols]]";
6746 
6747   main_options = concat(common_options, main_spec_options, (char *)0);
6748   col_options  = concat(common_options, col_spec_options, (char *)0);
6749   line_options = concat(common_options, line_spec_options, (char *)0);
6750   tab_options  = concat(common_options, tab_spec_options, (char *)0);
6751   tag_options  = concat(common_options, tag_spec_options, (char *)0);
6752 
6753   ctxopt_new_ctx("Main", main_options);
6754   ctxopt_new_ctx("Columns", col_options);
6755   ctxopt_new_ctx("Lines", line_options);
6756   ctxopt_new_ctx("Tabulations", tab_options);
6757   ctxopt_new_ctx("Tagging", tag_options);
6758 
6759   free(main_options);
6760   free(col_options);
6761   free(line_options);
6762   free(tab_options);
6763   free(tag_options);
6764 
6765   /* ctxopt parameters. */
6766   /* """""""""""""""""" */
6767 
6768   ctxopt_add_opt_settings(parameters, "help", "-h -help");
6769   ctxopt_add_opt_settings(parameters, "long_help", "-H -long-help");
6770   ctxopt_add_opt_settings(parameters, "usage", "-? -u -usage");
6771   ctxopt_add_opt_settings(parameters, "version", "-V -version");
6772   ctxopt_add_opt_settings(parameters, "include_re",
6773                           "-i -in -inc -incl -include");
6774   ctxopt_add_opt_settings(parameters, "exclude_re",
6775                           "-e -ex -exc -excl -exclude");
6776   ctxopt_add_opt_settings(parameters, "lines", "-n -lines -height");
6777   ctxopt_add_opt_settings(parameters, "title", "-m -msg -message -title");
6778   ctxopt_add_opt_settings(parameters, "int", "-! -int -int_string");
6779   ctxopt_add_opt_settings(parameters, "attributes", "-a -attr -attributes");
6780   ctxopt_add_opt_settings(parameters, "special_level_1", "-1 -l1 -level1");
6781   ctxopt_add_opt_settings(parameters, "special_level_2", "-2 -l2 -level2");
6782   ctxopt_add_opt_settings(parameters, "special_level_3", "-3 -l3 -level3");
6783   ctxopt_add_opt_settings(parameters, "special_level_4", "-4 -l4 -level4");
6784   ctxopt_add_opt_settings(parameters, "special_level_5", "-5 -l5 -level5");
6785   ctxopt_add_opt_settings(parameters, "special_level_6", "-6 -l6 -level6");
6786   ctxopt_add_opt_settings(parameters, "special_level_7", "-7 -l7 -level7");
6787   ctxopt_add_opt_settings(parameters, "special_level_8", "-8 -l8 -level8");
6788   ctxopt_add_opt_settings(parameters, "special_level_9", "-9 -l9 -level9");
6789   ctxopt_add_opt_settings(parameters, "tag_mode", "-T -tm -tag -tag_mode");
6790   ctxopt_add_opt_settings(parameters, "pin_mode", "-P -pm -pin -pin_mode");
6791   ctxopt_add_opt_settings(parameters, "auto_tag", "-p -at -auto_tag");
6792   ctxopt_add_opt_settings(parameters, "no_auto_tag", "-0 -noat -no_auto_tag");
6793   ctxopt_add_opt_settings(parameters, "auto_da_number", "-N -number");
6794   ctxopt_add_opt_settings(parameters, "auto_da_unnumber", "-U -unnumber");
6795   ctxopt_add_opt_settings(parameters, "field_da_number",
6796                           "-F -en -embedded_number");
6797   ctxopt_add_opt_settings(parameters, "da_options", "-D -data -options");
6798   ctxopt_add_opt_settings(parameters, "invalid_character", "-. -dot -invalid");
6799   ctxopt_add_opt_settings(parameters, "blank_nonprintable", "-b -blank");
6800   ctxopt_add_opt_settings(parameters, "center_mode", "-M -middle -center");
6801   ctxopt_add_opt_settings(parameters, "clean",
6802                           "-d -restore -delete -clean "
6803                           "-delete_window -clean_window");
6804   ctxopt_add_opt_settings(parameters, "column_mode",
6805                           "-c -col -col_mode -column");
6806   ctxopt_add_opt_settings(parameters, "line_mode", "-l -line -line_mode");
6807   ctxopt_add_opt_settings(parameters, "tab_mode",
6808                           "-t -tab -tab_mode -tabulate_mode");
6809   ctxopt_add_opt_settings(parameters, "wide_mode", "-w -wide -wide_mode");
6810   ctxopt_add_opt_settings(parameters, "columns_select",
6811                           "-C -cs -cols -cols_select");
6812   ctxopt_add_opt_settings(parameters, "rows_select",
6813                           "-R -rs -rows -rows_select");
6814   ctxopt_add_opt_settings(parameters, "force_first_column",
6815                           "-A -fc -first_column");
6816   ctxopt_add_opt_settings(parameters, "force_last_column",
6817                           "-Z -lc -last_column");
6818   ctxopt_add_opt_settings(parameters, "gutter", "-g -gutter");
6819   ctxopt_add_opt_settings(parameters, "keep_spaces", "-k -ks -keep_spaces");
6820   ctxopt_add_opt_settings(parameters, "word_separators",
6821                           "-W -ws -wd -word_delimiters -word_separators");
6822   ctxopt_add_opt_settings(parameters, "line_separators",
6823                           "-L -ls -ld -line-delimiters -line_separators");
6824   ctxopt_add_opt_settings(parameters, "zapped_glyphs", "-z -zap -zap-glyphs");
6825   ctxopt_add_opt_settings(parameters, "no_scroll_bar",
6826                           "-q -no_bar -no-scroll_bar");
6827   ctxopt_add_opt_settings(parameters, "early_subst_all", "-ES -early_subst");
6828   ctxopt_add_opt_settings(parameters, "post_subst_all", "-S -subst");
6829   ctxopt_add_opt_settings(parameters, "post_subst_included",
6830                           "-I -si -subst_included");
6831   ctxopt_add_opt_settings(parameters, "post_subst_excluded",
6832                           "-E -se -subst_excluded");
6833   ctxopt_add_opt_settings(parameters, "search_method", "-/ -search_method");
6834   ctxopt_add_opt_settings(parameters, "start_pattern",
6835                           "-s -sp -start -start_pattern");
6836   ctxopt_add_opt_settings(parameters, "timeout", "-x -tmout -timeout");
6837   ctxopt_add_opt_settings(parameters, "hidden_timeout",
6838                           "-X -htmout -hidden_timeout");
6839   ctxopt_add_opt_settings(parameters, "validate_in_search_mode",
6840                           "-r -auto_validate");
6841   ctxopt_add_opt_settings(parameters, "visual_bell", "-v -vb -visual_bell");
6842   ctxopt_add_opt_settings(parameters, "ignore_quotes", "-Q -ignore_quotes");
6843   ctxopt_add_opt_settings(parameters, "incremental_search",
6844                           "-is -incremental_search");
6845   ctxopt_add_opt_settings(parameters, "limits", "-lim -limits");
6846 
6847   /* ctxopt options incompatibilities. */
6848   /* """"""""""""""""""""""""""""""""" */
6849 
6850   ctxopt_add_ctx_settings(incompatibilities, "Main",
6851                           "column_mode line_mode tab_mode");
6852   ctxopt_add_ctx_settings(incompatibilities, "Main", "tag_mode pin_mode");
6853   ctxopt_add_ctx_settings(incompatibilities, "Main", "help usage");
6854   ctxopt_add_ctx_settings(incompatibilities, "Main", "timeout hidden_timeout");
6855 
6856   /* ctxopt options requirements. */
6857   /* """""""""""""""""""""""""""" */
6858 
6859   ctxopt_add_ctx_settings(requirements, "Main",
6860                           "da_options "
6861                           "field_da_number auto_da_number auto_da_unnumber");
6862   ctxopt_add_ctx_settings(requirements, "Columns",
6863                           "da_options "
6864                           "field_da_number auto_da_number auto_da_unnumber");
6865   ctxopt_add_ctx_settings(requirements, "Lines",
6866                           "da_options "
6867                           "field_da_number auto_da_number auto_da_unnumber");
6868   ctxopt_add_ctx_settings(requirements, "Tabulations",
6869                           "da_options "
6870                           "field_da_number auto_da_number auto_da_unnumber");
6871 
6872   /* ctxopt actions. */
6873   /* """"""""""""""" */
6874 
6875   ctxopt_add_opt_settings(actions, "auto_tag", toggle_action, &toggles,
6876                           (char *)0);
6877   ctxopt_add_opt_settings(actions, "no_auto_tag", toggle_action, &toggles,
6878                           (char *)0);
6879   ctxopt_add_opt_settings(actions, "invalid_character", invalid_char_action,
6880                           &misc, (char *)0);
6881   ctxopt_add_opt_settings(actions, "blank_nonprintable", toggle_action,
6882                           &toggles, (char *)0);
6883   ctxopt_add_opt_settings(actions, "center_mode", center_mode_action, &win,
6884                           (char *)0);
6885   ctxopt_add_opt_settings(actions, "clean", toggle_action, &toggles, (char *)0);
6886   ctxopt_add_opt_settings(actions, "column_mode", column_mode_action, &win,
6887                           (char *)0);
6888   ctxopt_add_opt_settings(actions, "line_mode", line_mode_action, &win,
6889                           (char *)0);
6890   ctxopt_add_opt_settings(actions, "tab_mode", tab_mode_action, &win,
6891                           (char *)0);
6892   ctxopt_add_opt_settings(actions, "columns_select", columns_select_action,
6893                           &cols_selector_list, (char *)0);
6894   ctxopt_add_opt_settings(actions, "rows_select", rows_select_action,
6895                           &rows_selector_list, &win, (char *)0);
6896   ctxopt_add_opt_settings(actions, "include_re", include_re_action,
6897                           &pattern_def_include, &include_pattern, &langinfo,
6898                           &misc, (char *)0);
6899   ctxopt_add_opt_settings(actions, "exclude_re", exclude_re_action,
6900                           &pattern_def_include, &exclude_pattern, &langinfo,
6901                           &misc, (char *)0);
6902   ctxopt_add_opt_settings(actions, "gutter", gutter_action, &win, &langinfo,
6903                           &misc, (char *)0);
6904   ctxopt_add_opt_settings(actions, "help", help_action, (char *)0);
6905   ctxopt_add_opt_settings(actions, "long_help", long_help_action, (char *)0);
6906   ctxopt_add_opt_settings(actions, "usage", usage_action, (char *)0);
6907   ctxopt_add_opt_settings(actions, "keep_spaces", toggle_action, &toggles,
6908                           (char *)0);
6909   ctxopt_add_opt_settings(actions, "lines", lines_action, &win, (char *)0);
6910   ctxopt_add_opt_settings(actions, "no_scroll_bar", toggle_action, &toggles,
6911                           (char *)0);
6912   ctxopt_add_opt_settings(actions, "start_pattern", set_pattern_action,
6913                           &pre_selection_index, &langinfo, &misc, (char *)0);
6914   ctxopt_add_opt_settings(actions, "title", set_string_action, &message,
6915                           &langinfo, &misc, (char *)0);
6916   ctxopt_add_opt_settings(actions, "int", int_action, &int_string,
6917                           &int_as_in_shell, &langinfo, &misc, (char *)0);
6918   ctxopt_add_opt_settings(actions, "validate_in_search_mode", toggle_action,
6919                           &toggles, (char *)0);
6920   ctxopt_add_opt_settings(actions, "version", version_action, (char *)0);
6921   ctxopt_add_opt_settings(actions, "visual_bell", toggle_action, &toggles,
6922                           (char *)0);
6923   ctxopt_add_opt_settings(actions, "incremental_search", toggle_action,
6924                           &toggles, (char *)0);
6925   ctxopt_add_opt_settings(actions, "wide_mode", wide_mode_action, &win,
6926                           (char *)0);
6927   ctxopt_add_opt_settings(actions, "early_subst_all", post_subst_action,
6928                           &early_sed_list, &langinfo, &misc, (char *)0);
6929   ctxopt_add_opt_settings(actions, "post_subst_all", post_subst_action,
6930                           &sed_list, &langinfo, &misc, (char *)0);
6931   ctxopt_add_opt_settings(actions, "post_subst_included", post_subst_action,
6932                           &include_sed_list, &langinfo, &misc, (char *)0);
6933   ctxopt_add_opt_settings(actions, "post_subst_excluded", post_subst_action,
6934                           &exclude_sed_list, &langinfo, &misc, (char *)0);
6935   ctxopt_add_opt_settings(actions, "special_level_1", special_level_action,
6936                           special_pattern, &win, &term, &langinfo, &init_attr,
6937                           &misc, (char *)0);
6938   ctxopt_add_opt_settings(actions, "special_level_2", special_level_action,
6939                           special_pattern, &win, &term, &langinfo, &init_attr,
6940                           &misc, (char *)0);
6941   ctxopt_add_opt_settings(actions, "special_level_3", special_level_action,
6942                           special_pattern, &win, &term, &langinfo, &init_attr,
6943                           &misc, (char *)0);
6944   ctxopt_add_opt_settings(actions, "special_level_4", special_level_action,
6945                           special_pattern, &win, &term, &langinfo, &init_attr,
6946                           &misc, (char *)0);
6947   ctxopt_add_opt_settings(actions, "special_level_5", special_level_action,
6948                           special_pattern, &win, &term, &langinfo, &init_attr,
6949                           &misc, (char *)0);
6950   ctxopt_add_opt_settings(actions, "special_level_6", special_level_action,
6951                           special_pattern, &win, &term, &langinfo, &init_attr,
6952                           &misc, (char *)0);
6953   ctxopt_add_opt_settings(actions, "special_level_7", special_level_action,
6954                           special_pattern, &win, &term, &langinfo, &init_attr,
6955                           &misc, (char *)0);
6956   ctxopt_add_opt_settings(actions, "special_level_8", special_level_action,
6957                           special_pattern, &win, &term, &langinfo, &init_attr,
6958                           &misc, (char *)0);
6959   ctxopt_add_opt_settings(actions, "special_level_9", special_level_action,
6960                           special_pattern, &win, &term, &langinfo, &init_attr,
6961                           &misc, (char *)0);
6962   ctxopt_add_opt_settings(actions, "attributes", attributes_action, &win, &term,
6963                           &init_attr, (char *)0);
6964   ctxopt_add_opt_settings(actions, "timeout", timeout_action, &langinfo, &misc,
6965                           (char *)0);
6966   ctxopt_add_opt_settings(actions, "hidden_timeout", timeout_action, &langinfo,
6967                           (char *)0);
6968   ctxopt_add_opt_settings(actions, "force_first_column", set_pattern_action,
6969                           &first_word_pattern, &langinfo, &misc, (char *)0);
6970   ctxopt_add_opt_settings(actions, "force_last_column", set_pattern_action,
6971                           &last_word_pattern, &langinfo, &misc, (char *)0);
6972   ctxopt_add_opt_settings(actions, "word_separators", set_pattern_action, &iws,
6973                           &langinfo, &misc, (char *)0);
6974   ctxopt_add_opt_settings(actions, "line_separators", set_pattern_action, &ils,
6975                           &langinfo, &misc, (char *)0);
6976   ctxopt_add_opt_settings(actions, "zapped_glyphs", set_pattern_action, &zg,
6977                           &langinfo, &misc, (char *)0);
6978   ctxopt_add_opt_settings(actions, "tag_mode", tag_mode_action, &toggles, &win,
6979                           &langinfo, &misc, (char *)0);
6980   ctxopt_add_opt_settings(actions, "pin_mode", pin_mode_action, &toggles, &win,
6981                           &langinfo, &misc, (char *)0);
6982   ctxopt_add_opt_settings(actions, "search_method", search_method_action, &misc,
6983                           (char *)0);
6984   ctxopt_add_opt_settings(actions, "auto_da_number", auto_da_action,
6985                           &daccess_np, (char *)0);
6986   ctxopt_add_opt_settings(actions, "auto_da_unnumber", auto_da_action,
6987                           &daccess_up, (char *)0);
6988   ctxopt_add_opt_settings(actions, "field_da_number", field_da_number_action,
6989                           (char *)0);
6990   ctxopt_add_opt_settings(actions, "da_options", da_options_action, &langinfo,
6991                           &daccess_index, &misc, (char *)0);
6992   ctxopt_add_opt_settings(actions, "ignore_quotes", ignore_quotes_action, &misc,
6993                           (char *)0);
6994   ctxopt_add_opt_settings(actions, "limits", limits_action, &limits, (char *)0);
6995 
6996   /* ctxopt constraints. */
6997   /* """"""""""""""""""" */
6998 
6999   ctxopt_add_opt_settings(constraints, "attributes", ctxopt_re_constraint,
7000                           "[^:]+:.+");
7001   ctxopt_add_opt_settings(constraints, "da_options", ctxopt_re_constraint,
7002                           "[^:]+:.+");
7003   ctxopt_add_opt_settings(constraints, "lines", check_integer_constraint, "");
7004 
7005   ctxopt_add_opt_settings(constraints, "tab_mode", check_integer_constraint,
7006                           "");
7007   ctxopt_add_opt_settings(constraints, "tab_mode", ctxopt_range_constraint,
7008                           "1 .");
7009 
7010   /* Evaluation order. */
7011   /* """"""""""""""""" */
7012 
7013   ctxopt_add_opt_settings(after, "field_da_number",
7014                           "auto_da_number auto_da_unnumber");
7015 
7016   ctxopt_add_opt_settings(after, "da_options",
7017                           "field_da_number auto_da_number auto_da_unnumber");
7018 
7019   /* Command line options analysis. */
7020   /* """""""""""""""""""""""""""""" */
7021   ctxopt_analyze(argc - 1, argv + 1, &nb_rem_args, &rem_args);
7022 
7023   /* Command line options evaluation. */
7024   /* """""""""""""""""""""""""""""""" */
7025   ctxopt_evaluate();
7026 
7027   /* Check remaining non analyzed command line arguments. */
7028   /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
7029   if (nb_rem_args == 1)
7030   {
7031     input_file = fopen(rem_args[0], "r");
7032     if (input_file == NULL)
7033     {
7034       fprintf(stderr, "The file \"%s\" does not exist or cannot be read.\n",
7035               rem_args[0]);
7036       ctxopt_disp_usage(exit_after);
7037       exit(EXIT_FAILURE); /* Avoid a compiler warning. */
7038     }
7039   }
7040   else if (nb_rem_args == 0)
7041     input_file = stdin;
7042   else
7043   {
7044     fprintf(stderr, "Extra arguments detected:\n");
7045 
7046     fprintf(stderr, "%s", rem_args[1]);
7047     for (int i = 2; i < nb_rem_args; i++)
7048       fprintf(stderr, ", %s", rem_args[i]);
7049     fprintf(stderr, ".\n");
7050 
7051     ctxopt_disp_usage(exit_after);
7052     exit(EXIT_FAILURE); /* Avoid a compiler warning. */
7053   }
7054 
7055   /* Free the memory used internally by ctxopt. */
7056   /* """""""""""""""""""""""""""""""""""""""""" */
7057   ctxopt_free_memory();
7058 
7059   /* If we did not impose the number of columns, use the whole */
7060   /* terminal width.                                           */
7061   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7062   if (win.tab_mode && !win.max_cols)
7063     win.wide = 1;
7064 
7065   win.start = 0;
7066 
7067   term.color_method = 1; /* We default to setaf/setbf to set colors. */
7068   term.curs_line = term.curs_column = 0;
7069 
7070   {
7071     char * str;
7072 
7073     str                        = tigetstr("cuu1");
7074     term.has_cursor_up         = (str == (char *)-1 || str == NULL) ? 0 : 1;
7075     str                        = tigetstr("cud1");
7076     term.has_cursor_down       = (str == (char *)-1 || str == NULL) ? 0 : 1;
7077     str                        = tigetstr("cub1");
7078     term.has_cursor_left       = (str == (char *)-1 || str == NULL) ? 0 : 1;
7079     str                        = tigetstr("cuf1");
7080     term.has_cursor_right      = (str == (char *)-1 || str == NULL) ? 0 : 1;
7081     str                        = tigetstr("cup");
7082     term.has_cursor_address    = (str == (char *)-1 || str == NULL) ? 0 : 1;
7083     str                        = tigetstr("sc");
7084     term.has_save_cursor       = (str == (char *)-1 || str == NULL) ? 0 : 1;
7085     str                        = tigetstr("rc");
7086     term.has_restore_cursor    = (str == (char *)-1 || str == NULL) ? 0 : 1;
7087     str                        = tigetstr("setf");
7088     term.has_setf              = (str == (char *)-1 || str == NULL) ? 0 : 1;
7089     str                        = tigetstr("setb");
7090     term.has_setb              = (str == (char *)-1 || str == NULL) ? 0 : 1;
7091     str                        = tigetstr("setaf");
7092     term.has_setaf             = (str == (char *)-1 || str == NULL) ? 0 : 1;
7093     str                        = tigetstr("setab");
7094     term.has_setab             = (str == (char *)-1 || str == NULL) ? 0 : 1;
7095     str                        = tigetstr("hpa");
7096     term.has_hpa               = (str == (char *)-1 || str == NULL) ? 0 : 1;
7097     str                        = tigetstr("cuf");
7098     term.has_parm_right_cursor = (str == (char *)-1 || str == NULL) ? 0 : 1;
7099     str                        = tigetstr("bold");
7100     term.has_bold              = (str == (char *)-1 || str == NULL) ? 0 : 1;
7101     str                        = tigetstr("dim");
7102     term.has_dim               = (str == (char *)-1 || str == NULL) ? 0 : 1;
7103     str                        = tigetstr("rev");
7104     term.has_reverse           = (str == (char *)-1 || str == NULL) ? 0 : 1;
7105     str                        = tigetstr("smul");
7106     term.has_underline         = (str == (char *)-1 || str == NULL) ? 0 : 1;
7107     str                        = tigetstr("smso");
7108     term.has_standout          = (str == (char *)-1 || str == NULL) ? 0 : 1;
7109     str                        = tigetstr("sitm");
7110     term.has_italic            = (str == (char *)-1 || str == NULL) ? 0 : 1;
7111     str                        = tigetstr("blink");
7112     term.has_blink             = (str == (char *)-1 || str == NULL) ? 0 : 1;
7113   }
7114 
7115   if (!term.has_cursor_up || !term.has_cursor_down || !term.has_cursor_left
7116       || !term.has_cursor_right || !term.has_save_cursor
7117       || !term.has_restore_cursor)
7118   {
7119     fprintf(stderr, "The terminal does not have the required cursor "
7120                     "management capabilities.\n");
7121 
7122     exit(EXIT_FAILURE);
7123   }
7124 
7125   word_buffer = xcalloc(1, daccess.flength + limits.word_length + 1);
7126 
7127   /* default_search_method is not set in the command line nor in a config */
7128   /* file, set it to fuzzy.                                               */
7129   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7130   if (misc.default_search_method == NONE)
7131     misc.default_search_method = FUZZY;
7132 
7133   /* If some attributes were not set, set their default values. */
7134   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7135   if (term.colors > 7)
7136   {
7137     int special_def_attr[9] = { 1, 2, 3, 5, 6, 7, 7, 7, 7 };
7138 
7139     if (!win.cursor_attr.is_set)
7140     {
7141       if (term.has_reverse)
7142         win.cursor_attr.reverse = 1;
7143       else if (term.has_standout)
7144         win.cursor_attr.standout = 1;
7145       else
7146       {
7147         win.cursor_attr.fg = 0;
7148         win.cursor_attr.bg = 1;
7149       }
7150 
7151       win.cursor_attr.is_set = SET;
7152     }
7153 
7154     if (!win.cursor_on_tag_attr.is_set)
7155     {
7156       if (term.has_reverse)
7157         win.cursor_on_tag_attr.reverse = 1;
7158 
7159       if (term.has_underline)
7160         win.cursor_on_tag_attr.underline = 1;
7161       else
7162         win.cursor_on_tag_attr.fg = 2;
7163 
7164       win.cursor_on_tag_attr.is_set = SET;
7165     }
7166 
7167     if (!win.bar_attr.is_set)
7168     {
7169       win.bar_attr.fg     = 2;
7170       win.bar_attr.is_set = SET;
7171     }
7172 
7173     if (!win.shift_attr.is_set)
7174     {
7175       win.shift_attr.fg     = 2;
7176       win.shift_attr.is_set = SET;
7177     }
7178 
7179     if (!win.message_attr.is_set)
7180     {
7181       if (term.has_bold)
7182         win.message_attr.bold = 1;
7183       else if (term.has_reverse)
7184         win.message_attr.reverse = 1;
7185       else
7186       {
7187         win.message_attr.fg = 0;
7188         win.message_attr.bg = 7;
7189       }
7190 
7191       win.message_attr.is_set = SET;
7192     }
7193 
7194     if (!win.search_field_attr.is_set)
7195     {
7196       win.search_field_attr.bg     = 5;
7197       win.search_field_attr.is_set = SET;
7198     }
7199 
7200     if (!win.search_text_attr.is_set)
7201     {
7202       win.search_text_attr.fg = 0;
7203       win.search_text_attr.bg = 6;
7204 
7205       win.search_text_attr.is_set = SET;
7206     }
7207 
7208     if (!win.search_err_field_attr.is_set)
7209     {
7210       win.search_err_field_attr.bg     = 1;
7211       win.search_err_field_attr.is_set = SET;
7212     }
7213 
7214     if (!win.search_err_text_attr.is_set)
7215     {
7216       if (term.has_reverse)
7217         win.search_err_text_attr.reverse = 1;
7218 
7219       win.search_err_text_attr.fg     = 1;
7220       win.search_err_text_attr.is_set = SET;
7221     }
7222 
7223     if (!win.match_field_attr.is_set)
7224     {
7225       win.match_field_attr.is_set = SET;
7226     }
7227 
7228     if (!win.match_text_attr.is_set)
7229     {
7230       win.match_text_attr.fg     = 5;
7231       win.match_text_attr.is_set = SET;
7232     }
7233 
7234     if (!win.match_err_field_attr.is_set)
7235     {
7236       win.match_err_field_attr.is_set = SET;
7237     }
7238 
7239     if (!win.match_err_text_attr.is_set)
7240     {
7241       win.match_err_text_attr.fg     = 1;
7242       win.match_err_text_attr.is_set = SET;
7243     }
7244 
7245     if (!win.exclude_attr.is_set)
7246     {
7247       win.exclude_attr.fg = 6;
7248 
7249       win.exclude_attr.is_set = SET;
7250     }
7251 
7252     /* This attribute should complete the attributes already set. */
7253     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7254     if (!win.tag_attr.is_set)
7255     {
7256       if (term.has_underline)
7257         win.tag_attr.underline = 1;
7258       else if (term.has_bold)
7259         win.tag_attr.bold = 1;
7260       else
7261         win.tag_attr.fg = 2;
7262 
7263       win.tag_attr.is_set = SET;
7264     }
7265 
7266     if (!win.daccess_attr.is_set)
7267     {
7268       if (term.has_bold)
7269         win.daccess_attr.bold = 1;
7270 
7271       win.daccess_attr.is_set = SET;
7272     }
7273 
7274     for (index = 0; index < 9; index++)
7275     {
7276       if (!win.special_attr[index].is_set)
7277       {
7278         win.special_attr[index].fg     = special_def_attr[index];
7279         win.special_attr[index].is_set = SET;
7280       }
7281     }
7282   }
7283   else
7284   {
7285     if (!win.cursor_attr.is_set)
7286     {
7287       if (term.has_reverse)
7288         win.cursor_attr.reverse = 1;
7289 
7290       win.cursor_attr.is_set = SET;
7291     }
7292 
7293     if (!win.cursor_on_tag_attr.is_set)
7294     {
7295       if (term.has_reverse)
7296         win.cursor_on_tag_attr.reverse = 1;
7297 
7298       if (term.has_underline)
7299         win.cursor_on_tag_attr.underline = 1;
7300       else if (term.has_bold)
7301         win.cursor_on_tag_attr.bold = 1;
7302 
7303       win.cursor_on_tag_attr.is_set = SET;
7304     }
7305 
7306     if (!win.bar_attr.is_set)
7307     {
7308       if (term.has_bold)
7309         win.bar_attr.bold = 1;
7310 
7311       win.bar_attr.is_set = SET;
7312     }
7313 
7314     if (!win.shift_attr.is_set)
7315     {
7316       if (term.has_reverse)
7317         win.shift_attr.reverse = 1;
7318 
7319       win.shift_attr.is_set = SET;
7320     }
7321 
7322     if (!win.message_attr.is_set)
7323     {
7324       if (term.has_bold)
7325         win.message_attr.bold = 1;
7326       else if (term.has_reverse)
7327         win.message_attr.reverse = 1;
7328 
7329       win.message_attr.is_set = SET;
7330     }
7331 
7332     if (!win.search_field_attr.is_set)
7333     {
7334       if (term.has_reverse)
7335         win.search_field_attr.reverse = 1;
7336 
7337       win.search_field_attr.is_set = SET;
7338     }
7339 
7340     if (!win.search_text_attr.is_set)
7341     {
7342       if (term.has_bold)
7343         win.search_text_attr.bold = 1;
7344 
7345       win.search_text_attr.is_set = SET;
7346     }
7347 
7348     if (!win.search_err_field_attr.is_set)
7349     {
7350       if (term.has_bold)
7351         win.search_err_field_attr.bold = 1;
7352 
7353       win.search_err_field_attr.is_set = SET;
7354     }
7355 
7356     if (!win.search_err_text_attr.is_set)
7357     {
7358       if (term.has_reverse)
7359         win.search_err_text_attr.reverse = 1;
7360 
7361       win.search_err_text_attr.is_set = SET;
7362     }
7363 
7364     if (!win.match_field_attr.is_set)
7365     {
7366       if (term.has_bold)
7367         win.match_field_attr.bold = 1;
7368       else if (term.has_reverse)
7369         win.match_field_attr.reverse = 1;
7370 
7371       win.match_field_attr.is_set = SET;
7372     }
7373 
7374     if (!win.match_text_attr.is_set)
7375     {
7376       if (term.has_reverse)
7377         win.match_text_attr.reverse = 1;
7378       else if (term.has_bold)
7379         win.match_text_attr.bold = 1;
7380 
7381       win.match_text_attr.is_set = SET;
7382     }
7383 
7384     if (!win.exclude_attr.is_set)
7385     {
7386       if (term.has_dim)
7387         win.exclude_attr.dim = 1;
7388       else if (term.has_italic)
7389         win.exclude_attr.italic = 1;
7390       else if (term.has_bold)
7391         win.exclude_attr.bold = 1;
7392 
7393       win.exclude_attr.is_set = SET;
7394     }
7395 
7396     if (!win.tag_attr.is_set)
7397     {
7398       if (term.has_underline)
7399         win.tag_attr.underline = 1;
7400       else if (term.has_standout)
7401         win.tag_attr.standout = 1;
7402       else if (term.has_reverse)
7403         win.tag_attr.reverse = 1;
7404 
7405       win.tag_attr.is_set = SET;
7406     }
7407 
7408     if (!win.daccess_attr.is_set)
7409     {
7410       if (term.has_bold)
7411         win.daccess_attr.bold = 1;
7412 
7413       win.daccess_attr.is_set = SET;
7414     }
7415 
7416     for (index = 0; index < 9; index++)
7417     {
7418       if (!win.special_attr[index].is_set)
7419       {
7420         if (term.has_bold)
7421           win.special_attr[index].bold = 1;
7422         else if (term.has_standout)
7423           win.special_attr[index].standout = 1;
7424 
7425         win.special_attr[index].is_set = SET;
7426       }
7427     }
7428   }
7429 
7430   /* Initialize the timeout message when the x/X option is set. */
7431   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7432   if (!quiet_timeout && timeout.initial_value > 0)
7433   {
7434     switch (timeout.mode)
7435     {
7436       case QUIT:
7437         timeout_message = xstrdup(
7438           "[     s before quitting without selecting anything]");
7439         break;
7440       case CURRENT:
7441         timeout_message = xstrdup(
7442           "[     s before selecting the current highlighted word]");
7443         break;
7444       case WORD:
7445       {
7446         char * s = "[     s before selecting the word \"";
7447 
7448         timeout_message = xcalloc(1, 4 + strlen(s) + strlen(timeout_word));
7449 
7450         strcpy(timeout_message, s);
7451         strcat(timeout_message, timeout_word);
7452         strcat(timeout_message, "\"]");
7453 
7454         break;
7455       }
7456 
7457       default:
7458         /* The other cases are impossible due to options analysis. */
7459         /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
7460         timeout_message = xstrdup("      "); /* Just in case. */
7461     }
7462 
7463     timeout_seconds = xcalloc(1, 6);
7464     sprintf(timeout_seconds, "%5u", timeout.initial_value / FREQ);
7465     memcpy(timeout_message + 1, timeout_seconds, 5);
7466 
7467     message_lines_list = ll_new();
7468 
7469     if (message)
7470     {
7471       long len;
7472 
7473       get_message_lines(message, message_lines_list, &message_max_width,
7474                         &message_max_len);
7475       ll_append(message_lines_list, timeout_message);
7476 
7477       if ((len = strlen(timeout_message)) > message_max_len)
7478         message_max_len = message_max_width = len;
7479     }
7480     else
7481     {
7482       ll_append(message_lines_list, timeout_message);
7483       message_max_len = message_max_width = strlen(timeout_message);
7484     }
7485   }
7486   else if (message)
7487   {
7488     message_lines_list = ll_new();
7489     get_message_lines(message, message_lines_list, &message_max_width,
7490                       &message_max_len);
7491   }
7492 
7493   /* Force the maximum number of window's line if -n is used. */
7494   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7495   if (term.nlines <= win.message_lines)
7496   {
7497     win.message_lines = term.nlines - 1;
7498     win.max_lines     = 1;
7499   }
7500   else if (win.asked_max_lines >= 0)
7501   {
7502     if (win.asked_max_lines == 0)
7503       win.max_lines = term.nlines - win.message_lines;
7504     else
7505     {
7506       if (win.asked_max_lines > term.nlines - win.message_lines)
7507         win.max_lines = term.nlines - win.message_lines;
7508       else
7509         win.max_lines = win.asked_max_lines;
7510     }
7511   }
7512   else /* -n was not used. Set win.asked_max_lines to its default value. */
7513     win.asked_max_lines = win.max_lines;
7514 
7515   /* Allocate the memory for our words structures. */
7516   /* """"""""""""""""""""""""""""""""""""""""""""" */
7517   word_a = xmalloc(WORDSCHUNK * sizeof(word_t));
7518 
7519   /* Fill an array of word_t elements obtained from stdin. */
7520   /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
7521   tab_real_max_size = 0;
7522   tab_max_size      = 0;
7523   min_size          = 0;
7524 
7525   /* Parse the list of glyphs to be zapped (option -z). */
7526   /* """""""""""""""""""""""""""""""""""""""""""""""""" */
7527   zapped_glyphs_list = ll_new();
7528   if (zg != NULL)
7529   {
7530     int    utf8_len;
7531     char * zg_ptr = zg;
7532     char * tmp;
7533 
7534     utf8_len = mblen(zg_ptr, 4);
7535 
7536     while (utf8_len != 0)
7537     {
7538       tmp = xmalloc(utf8_len + 1);
7539       memcpy(tmp, zg_ptr, utf8_len);
7540       tmp[utf8_len] = '\0';
7541       ll_append(zapped_glyphs_list, tmp);
7542 
7543       zg_ptr += utf8_len;
7544       utf8_len = mblen(zg_ptr, 4);
7545     }
7546   }
7547 
7548   /* Parse the word separators string (option -W). If it is empty then  */
7549   /* the standard delimiters (space, tab and EOL) are used. Each of its */
7550   /* UTF-8 sequences are stored in a linked list.                       */
7551   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7552   word_delims_list = ll_new();
7553 
7554   if (iws == NULL)
7555   {
7556     ll_append(word_delims_list, " ");
7557     ll_append(word_delims_list, "\t");
7558     ll_append(word_delims_list, "\n");
7559   }
7560   else
7561   {
7562     int    utf8_len;
7563     char * iws_ptr = iws;
7564     char * tmp;
7565 
7566     utf8_len = mblen(iws_ptr, 4);
7567 
7568     while (utf8_len != 0)
7569     {
7570       tmp = xmalloc(utf8_len + 1);
7571       memcpy(tmp, iws_ptr, utf8_len);
7572       tmp[utf8_len] = '\0';
7573       ll_append(word_delims_list, tmp);
7574 
7575       iws_ptr += utf8_len;
7576       utf8_len = mblen(iws_ptr, 4);
7577     }
7578   }
7579 
7580   /* Parse the line separators string (option -L). If it is empty then */
7581   /* the standard delimiter (newline) is used. Each of its UTF-8       */
7582   /* sequences are stored in a linked list.                            */
7583   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7584   record_delims_list = ll_new();
7585 
7586   /* A default line separator is set to '\n' except in tab_mode */
7587   /* where it should be explicitly set.                         */
7588   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7589   if (ils == NULL && !win.tab_mode)
7590     ll_append(record_delims_list, "\n");
7591   else
7592   {
7593     int    utf8_len;
7594     char * ils_ptr = ils;
7595     char * tmp;
7596 
7597     utf8_len = mblen(ils_ptr, 4);
7598 
7599     while (utf8_len != 0)
7600     {
7601       tmp = xmalloc(utf8_len + 1);
7602       memcpy(tmp, ils_ptr, utf8_len);
7603       tmp[utf8_len] = '\0';
7604       ll_append(record_delims_list, tmp);
7605 
7606       /* Add this record delimiter as a word delimiter. */
7607       /* """""""""""""""""""""""""""""""""""""""""""""" */
7608       if (ll_find(word_delims_list, tmp, buffer_cmp) == NULL)
7609         ll_append(word_delims_list, tmp);
7610 
7611       ils_ptr += utf8_len;
7612       utf8_len = mblen(ils_ptr, 4);
7613     }
7614   }
7615 
7616   /* Initialize the first chunks of the arrays which will contain the */
7617   /* maximum length of each column in column mode.                    */
7618   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7619   if (win.col_mode)
7620   {
7621     long ci; /* Column index. */
7622 
7623     col_real_max_size = xmalloc(COLSCHUNK * sizeof(long));
7624     col_max_size      = xmalloc(COLSCHUNK * sizeof(long));
7625 
7626     for (ci = 0; ci < COLSCHUNK; ci++)
7627       col_real_max_size[ci] = col_max_size[ci] = 0;
7628 
7629     col_index = cols_number = 0;
7630   }
7631 
7632   /* Compile the regular expression patterns. */
7633   /* """""""""""""""""""""""""""""""""""""""" */
7634   if (daccess_np
7635       && regcomp(&daccess_np_re, daccess_np, REG_EXTENDED | REG_NOSUB) != 0)
7636   {
7637     fprintf(stderr, "%s: Bad regular expression.\n", daccess_np);
7638 
7639     exit(EXIT_FAILURE);
7640   }
7641 
7642   if (daccess_up
7643       && regcomp(&daccess_up_re, daccess_up, REG_EXTENDED | REG_NOSUB) != 0)
7644   {
7645     fprintf(stderr, "%s: Bad regular expression.\n", daccess_up);
7646 
7647     exit(EXIT_FAILURE);
7648   }
7649 
7650   if (include_pattern
7651       && regcomp(&include_re, include_pattern, REG_EXTENDED | REG_NOSUB) != 0)
7652   {
7653     fprintf(stderr, "%s: Bad regular expression.\n", include_pattern);
7654 
7655     exit(EXIT_FAILURE);
7656   }
7657 
7658   if (exclude_pattern
7659       && regcomp(&exclude_re, exclude_pattern, REG_EXTENDED | REG_NOSUB) != 0)
7660   {
7661     fprintf(stderr, "%s: Bad regular expression.\n", exclude_pattern);
7662 
7663     exit(EXIT_FAILURE);
7664   }
7665 
7666   if (first_word_pattern
7667       && regcomp(&first_word_re, first_word_pattern, REG_EXTENDED | REG_NOSUB)
7668            != 0)
7669   {
7670     fprintf(stderr, "%s: Bad regular expression.\n", first_word_pattern);
7671 
7672     exit(EXIT_FAILURE);
7673   }
7674 
7675   if (last_word_pattern
7676       && regcomp(&last_word_re, last_word_pattern, REG_EXTENDED | REG_NOSUB)
7677            != 0)
7678   {
7679     fprintf(stderr, "%s: Bad regular expression.\n", last_word_pattern);
7680 
7681     exit(EXIT_FAILURE);
7682   }
7683 
7684   for (index = 0; index < 9; index++)
7685   {
7686     if (special_pattern[index]
7687         && regcomp(&special_re[index], special_pattern[index],
7688                    REG_EXTENDED | REG_NOSUB)
7689              != 0)
7690     {
7691       fprintf(stderr, "%s: Bad regular expression.\n", special_pattern[index]);
7692 
7693       exit(EXIT_FAILURE);
7694     }
7695   }
7696 
7697   /* Parse the post-processing patterns and extract its values. */
7698   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7699   if (early_sed_list != NULL)
7700   {
7701     ll_node_t * node = early_sed_list->head;
7702 
7703     while (node != NULL)
7704     {
7705       if (!parse_sed_like_string((sed_t *)(node->data)))
7706       {
7707         fprintf(stderr, "Bad -ES argument. Must be something like: "
7708                         "/regex/repl_string/[g][v][s][i].\n");
7709 
7710         exit(EXIT_FAILURE);
7711       }
7712 
7713       node = node->next;
7714     }
7715   }
7716 
7717   if (sed_list != NULL)
7718   {
7719     ll_node_t * node = sed_list->head;
7720 
7721     while (node != NULL)
7722     {
7723       if (!parse_sed_like_string((sed_t *)(node->data)))
7724       {
7725         fprintf(stderr, "Bad -S argument. Must be something like: "
7726                         "/regex/repl_string/[g][v][s][i].\n");
7727 
7728         exit(EXIT_FAILURE);
7729       }
7730       if ((!include_visual_only || !exclude_visual_only)
7731           && ((sed_t *)(node->data))->visual)
7732       {
7733         include_visual_only = 1;
7734         exclude_visual_only = 1;
7735       }
7736 
7737       node = node->next;
7738     }
7739   }
7740 
7741   if (include_sed_list != NULL)
7742   {
7743     ll_node_t * node = include_sed_list->head;
7744 
7745     while (node != NULL)
7746     {
7747       if (!parse_sed_like_string((sed_t *)(node->data)))
7748       {
7749         fprintf(stderr, "Bad -I argument. Must be something like: "
7750                         "/regex/repl_string/[g][v][s][i].\n");
7751 
7752         exit(EXIT_FAILURE);
7753       }
7754       if (!include_visual_only && ((sed_t *)(node->data))->visual)
7755         include_visual_only = 1;
7756 
7757       node = node->next;
7758     }
7759   }
7760 
7761   if (exclude_sed_list != NULL)
7762   {
7763     ll_node_t * node = exclude_sed_list->head;
7764 
7765     while (node != NULL)
7766     {
7767       if (!parse_sed_like_string((sed_t *)(node->data)))
7768       {
7769         fprintf(stderr, "Bad -E argument. Must be something like: "
7770                         "/regex/repl_string/[g][v][s][i].\n");
7771 
7772         exit(EXIT_FAILURE);
7773       }
7774       if (!exclude_visual_only && ((sed_t *)(node->data))->visual)
7775         exclude_visual_only = 1;
7776 
7777       node = node->next;
7778     }
7779   }
7780 
7781   /* Parse the row selection string if any. */
7782   /* """""""""""""""""""""""""""""""""""""" */
7783   if (rows_selector_list != NULL)
7784   {
7785     ll_node_t * node_selector = rows_selector_list->head;
7786     filters_t   filter_type;
7787 
7788     rows_filter_type = UNKNOWN_FILTER;
7789     while (node_selector != NULL)
7790     {
7791       rows_selector   = node_selector->data;
7792       char * unparsed = xstrdup((char *)rows_selector);
7793 
7794       parse_selectors(rows_selector, &filter_type, unparsed,
7795                       &inc_row_interval_list, &inc_row_regex_list,
7796                       &exc_row_interval_list, &exc_row_regex_list, &langinfo,
7797                       &misc);
7798 
7799       if (*unparsed != '\0')
7800       {
7801         fprintf(stderr, "%s: Bad -R argument. Unparsed part.\n", unparsed);
7802 
7803         exit(EXIT_FAILURE);
7804       }
7805 
7806       if (rows_filter_type == UNKNOWN_FILTER)
7807         rows_filter_type = filter_type;
7808 
7809       node_selector = node_selector->next;
7810 
7811       free(unparsed);
7812     }
7813     merge_intervals(inc_row_interval_list);
7814     merge_intervals(exc_row_interval_list);
7815   }
7816 
7817   /* Parse the column selection string if any. */
7818   /* """"""""""""""""""""""""""""""""""""""""" */
7819   if (cols_selector_list != NULL)
7820   {
7821     filters_t    filter_type, cols_filter_type;
7822     interval_t * data;
7823     ll_node_t *  node;
7824     ll_node_t *  node_selector = cols_selector_list->head;
7825 
7826     cols_filter = xmalloc(limits.cols);
7827 
7828     cols_filter_type = UNKNOWN_FILTER;
7829     while (node_selector != NULL)
7830     {
7831       cols_selector   = node_selector->data;
7832       char * unparsed = xstrdup((char *)cols_selector);
7833 
7834       parse_selectors(cols_selector, &filter_type, unparsed,
7835                       &inc_col_interval_list, &inc_col_regex_list,
7836                       &exc_col_interval_list, &exc_col_regex_list, &langinfo,
7837                       &misc);
7838 
7839       if (*unparsed != '\0')
7840       {
7841         fprintf(stderr, "%s: Bad -C argument. Unparsed part.\n", unparsed);
7842 
7843         exit(EXIT_FAILURE);
7844       }
7845 
7846       merge_intervals(inc_col_interval_list);
7847       merge_intervals(exc_col_interval_list);
7848 
7849       free(unparsed);
7850 
7851       if (cols_filter_type == UNKNOWN_FILTER)
7852         cols_filter_type = filter_type;
7853 
7854       /* Only initialize the whole set when -C is encountered for the */
7855       /* first time.                                                  */
7856       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7857       if (cols_filter_type == INCLUDE_FILTER)
7858         memset(cols_filter, SOFT_EXCLUDE_MARK, limits.cols);
7859       else
7860         memset(cols_filter, SOFT_INCLUDE_MARK, limits.cols);
7861 
7862       /* Process the explicitly included columns intervals. */
7863       /* """""""""""""""""""""""""""""""""""""""""""""""""" */
7864       if (inc_col_interval_list != NULL)
7865         for (node = inc_col_interval_list->head; node; node = node->next)
7866         {
7867           data = node->data;
7868 
7869           if (data->low >= limits.cols)
7870             break;
7871 
7872           if (data->high >= limits.cols)
7873             data->high = limits.cols - 1;
7874 
7875           memset(cols_filter + data->low, INCLUDE_MARK,
7876                  data->high - data->low + 1);
7877         }
7878 
7879       /* Process the explicitly excluded column intervals. */
7880       /* """"""""""""""""""""""""""""""""""""""""""""""""" */
7881       if (exc_col_interval_list != NULL)
7882         for (node = exc_col_interval_list->head; node; node = node->next)
7883         {
7884           data = node->data;
7885 
7886           if (data->low >= limits.cols)
7887             break;
7888 
7889           if (data->high >= limits.cols)
7890             data->high = limits.cols - 1;
7891 
7892           memset(cols_filter + data->low, EXCLUDE_MARK,
7893                  data->high - data->low + 1);
7894         }
7895 
7896       node_selector = node_selector->next;
7897     }
7898   }
7899 
7900   /* Initialize the useful values needed to walk through */
7901   /* the rows intervals.                                 */
7902   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
7903   if (rows_filter_type == INCLUDE_FILTER)
7904     row_def_selectable = SOFT_EXCLUDE_MARK;
7905   else if (rows_filter_type == EXCLUDE_FILTER)
7906     row_def_selectable = SOFT_INCLUDE_MARK;
7907   else
7908   {
7909     if (pattern_def_include == 0)
7910       row_def_selectable = SOFT_EXCLUDE_MARK;
7911     else
7912       row_def_selectable = SOFT_INCLUDE_MARK;
7913   }
7914 
7915   /* Set the head of the interval list. */
7916   /* """""""""""""""""""""""""""""""""" */
7917   if (inc_row_interval_list)
7918     inc_interval_node = inc_row_interval_list->head;
7919   else
7920     inc_interval_node = NULL;
7921 
7922   if (exc_row_interval_list)
7923     exc_interval_node = exc_row_interval_list->head;
7924   else
7925     exc_interval_node = NULL;
7926 
7927   /* And get the first interval.*/
7928   /* """"""""""""""""""""""""""" */
7929   if (inc_interval_node)
7930     inc_interval = (interval_t *)inc_interval_node->data;
7931   else
7932     inc_interval = NULL;
7933 
7934   if (exc_interval_node)
7935     exc_interval = (interval_t *)exc_interval_node->data;
7936   else
7937     exc_interval = NULL;
7938 
7939   /* First pass:                                                  */
7940   /* Get and process the input stream words.                      */
7941   /* In this pass, the different actions will occur:              */
7942   /* - A new word is read from stdin                              */
7943   /* - A new SOL and or EOL is possibly set                       */
7944   /* - A special level is possibly affected to the word just read */
7945   /* - The -R is taken into account                               */
7946   /* - The first part of the -C option is done                    */
7947   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7948   while ((word = get_word(input_file, word_delims_list, record_delims_list,
7949                           zapped_glyphs_list, utf8_buffer, &is_last, &toggles,
7950                           &langinfo, &win, &limits, &misc))
7951          != NULL)
7952   {
7953     int         selectable;
7954     int         is_first = 0;
7955     int         special_level;
7956     int         row_inc_matched = 0;
7957     ll_node_t * node;
7958 
7959     if (*word == '\0')
7960       continue;
7961 
7962     /* Early substitution. */
7963     /* """"""""""""""""""" */
7964     if (early_sed_list != NULL)
7965     {
7966       char * tmp;
7967 
7968       node = early_sed_list->head;
7969 
7970       while (node != NULL)
7971       {
7972         tmp = xstrdup(word);
7973         if (replace(word, (sed_t *)(node->data)))
7974         {
7975 
7976           free(word);
7977           word = xstrdup(word_buffer);
7978 
7979           if (((sed_t *)(node->data))->stop)
7980             break;
7981         }
7982 
7983         *word_buffer = '\0';
7984         node         = node->next;
7985         free(tmp);
7986       }
7987     }
7988 
7989     if (*word == '\0')
7990       continue;
7991 
7992     /* Manipulates the is_last flag word indicator to make this word      */
7993     /* the first or last one of the current line in column/line/tab mode. */
7994     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7995     if (win.col_mode || win.line_mode || win.tab_mode)
7996     {
7997       if (first_word_pattern
7998           && regexec(&first_word_re, word, (int)0, NULL, 0) == 0)
7999         is_first = 1;
8000 
8001       if (last_word_pattern && !is_last
8002           && regexec(&last_word_re, word, (int)0, NULL, 0) == 0)
8003         is_last = 1;
8004     }
8005 
8006     /* Check if the word is special. */
8007     /* """"""""""""""""""""""""""""" */
8008     special_level = 0;
8009     for (index = 0; index < 9; index++)
8010     {
8011       if (special_pattern[index] != NULL
8012           && regexec(&special_re[index], word, (int)0, NULL, 0) == 0)
8013       {
8014         special_level = (int)index + 1;
8015         break;
8016       }
8017     }
8018 
8019     /* Default selectable state. */
8020     /* """"""""""""""""""""""""" */
8021     selectable = SOFT_INCLUDE_MARK;
8022 
8023     /* For each new line check if the line is in the current   */
8024     /* interval or if we need to get the next interval if any .*/
8025     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
8026     if (rows_selector)
8027     {
8028       if (count > 0 && word_a[count - 1].is_last)
8029       {
8030         /* We are in a new line, reset the flag indicating that we are on */
8031         /* a line selected by a regular expression  and the flag saying   */
8032         /* that the whole line has been excluded.                         */
8033         /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8034         line_selected_by_regex = 0;
8035         line_excluded          = 0;
8036 
8037         /* And also reset the flag telling that the row has been explicitly */
8038         /* removed from the selectable list of words.                       */
8039         /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8040         row_inc_matched = 0;
8041 
8042         /* Increment the line counter used to see if we are an include or */
8043         /* exclude set of lines.                                          */
8044         /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8045         line_count++;
8046 
8047         /* Look if we need to use the next interval of the list. */
8048         /* ''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8049         if (inc_interval_node && line_count > inc_interval->high)
8050         {
8051           inc_interval_node = inc_interval_node->next;
8052           if (inc_interval_node)
8053             inc_interval = (interval_t *)inc_interval_node->data;
8054         }
8055 
8056         if (exc_interval_node && line_count > exc_interval->high)
8057         {
8058           exc_interval_node = exc_interval_node->next;
8059           if (exc_interval_node)
8060             exc_interval = (interval_t *)exc_interval_node->data;
8061         }
8062       }
8063 
8064       /* Look if the line is in an excluded or included line.             */
8065       /* The included line intervals are only checked if the word didn't  */
8066       /* belong to an excluded line interval before.                      */
8067       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8068       if (exc_interval)
8069       {
8070         if (line_count >= exc_interval->low && line_count <= exc_interval->high)
8071           selectable = EXCLUDE_MARK;
8072       }
8073       if (selectable != EXCLUDE_MARK && inc_interval)
8074       {
8075         if (line_count >= inc_interval->low && line_count <= inc_interval->high)
8076         {
8077           selectable = INCLUDE_MARK;
8078 
8079           /* As the raw has been explicitly selected, record that so than */
8080           /* we can distinguish that from the implicit selection.         */
8081           /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8082           row_inc_matched = 1;
8083         }
8084       }
8085     }
8086 
8087     /* Check if the all the words in the current row must be included or */
8088     /* excluded from the selectable set of words.                        */
8089     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8090     if (selectable != EXCLUDE_MARK)
8091     {
8092       /* Look in the excluded list of regular expressions. */
8093       /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
8094       if (exc_row_regex_list != NULL)
8095       {
8096         regex_t * row_re;
8097 
8098         ll_node_t * row_regex_node = exc_row_regex_list->head;
8099 
8100         while (row_regex_node != NULL)
8101         {
8102           row_re = row_regex_node->data;
8103           if (regexec(row_re, word, (int)0, NULL, 0) == 0)
8104           {
8105             long c = count - 1;
8106 
8107             /* Mark all the next words of the line as excluded. */
8108             /* '''''''''''''''''''''''''''''''''''''''''''''''' */
8109             line_selected_by_regex = 1;
8110             line_excluded          = 1;
8111 
8112             /* Mark all the previous words of the line as excluded. */
8113             /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
8114             while (c >= 0 && !word_a[c].is_last)
8115             {
8116               word_a[c].is_selectable = EXCLUDE_MARK;
8117               c--;
8118             }
8119 
8120             /* Mark the current word as not excluded. */
8121             /* '''''''''''''''''''''''''''''''''''''' */
8122             selectable = EXCLUDE_MARK;
8123 
8124             /* No need to continue as the line is already marked as */
8125             /* excluded.                                            */
8126             /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
8127             break;
8128           }
8129 
8130           row_regex_node = row_regex_node->next;
8131         }
8132       }
8133 
8134       /* If the line has not yet been excluded and the list of explicitly  */
8135       /* include regular expressions is not empty then give them a chance. */
8136       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8137       if (selectable != EXCLUDE_MARK && inc_row_regex_list != NULL)
8138       {
8139         regex_t * row_re;
8140 
8141         ll_node_t * row_regex_node = inc_row_regex_list->head;
8142 
8143         while (row_regex_node != NULL)
8144         {
8145           row_re = row_regex_node->data;
8146           if (regexec(row_re, word, (int)0, NULL, 0) == 0)
8147           {
8148             long c = count - 1;
8149 
8150             while (c >= 0 && !word_a[c].is_last)
8151             {
8152               /* Do not include an already excluded word. */
8153               /* """""""""""""""""""""""""""""""""""""""" */
8154               if (word_a[c].is_selectable)
8155                 word_a[c].is_selectable = INCLUDE_MARK;
8156 
8157               c--;
8158             }
8159 
8160             /* Mark all the next words of the line as included. */
8161             /* '''''''''''''''''''''''''''''''''''''''''''''''' */
8162             line_selected_by_regex = 1;
8163 
8164             /* Mark all the previous words of the line as included. */
8165             /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
8166             selectable = INCLUDE_MARK;
8167           }
8168 
8169           row_regex_node = row_regex_node->next;
8170         }
8171       }
8172     }
8173 
8174     /* If the line contains a word that matched a regex which determines */
8175     /* the inclusion of exclusion of this line, then use the regex       */
8176     /* selection flag to determine the inclusion/exclusion of the future */
8177     /* words in the line.                                                */
8178     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8179     if (line_excluded)
8180       selectable = EXCLUDE_MARK;
8181     else
8182     {
8183       if (line_selected_by_regex)
8184         selectable = (row_def_selectable == ROW_REGEX_EXCLUDE)
8185                        ? SOFT_EXCLUDE_MARK
8186                        : INCLUDE_MARK;
8187 
8188       /* Check if the current word is matching an include or exclude */
8189       /* pattern                                                     */
8190       /* Only do it if if hasn't be explicitly deselected before.    */
8191       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8192       if (selectable != EXCLUDE_MARK)
8193       {
8194         /* Check if the word will be excluded in the list of selectable */
8195         /* words or not.                                                */
8196         /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8197         if (exclude_pattern)
8198         {
8199           if (regexec(&exclude_re, word, (int)0, NULL, 0) == 0)
8200             selectable = EXCLUDE_MARK;
8201         }
8202 
8203         if (selectable != 0 && !line_selected_by_regex)
8204         {
8205           /* Check if the word will be included in the list of selectable */
8206           /* words or not.                                                */
8207           /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8208           if (include_pattern)
8209           {
8210             if (regexec(&include_re, word, (int)0, NULL, 0) == 0)
8211               selectable = INCLUDE_MARK;
8212             else if (!row_inc_matched)
8213               selectable = row_def_selectable;
8214           }
8215           else if (rows_selector && !row_inc_matched)
8216             selectable = row_def_selectable;
8217         }
8218       }
8219     }
8220 
8221     if (win.col_mode)
8222     {
8223       /* In column mode we must manage the allocation space for some       */
8224       /* column's related data structures and check if some limits ave not */
8225       /* been reached.                                                     */
8226       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8227 
8228       if (is_first)
8229         col_index = 1;
8230       else
8231       {
8232         col_index++;
8233 
8234         if (col_index > cols_number)
8235         {
8236           /* Check the limits. */
8237           /* ''''''''''''''''' */
8238           if (col_index == limits.cols)
8239           {
8240             fprintf(stderr,
8241                     "The number of columns has reached the limit of %ld.\n",
8242                     limits.cols);
8243 
8244             exit(EXIT_FAILURE);
8245           }
8246 
8247           cols_number++;
8248 
8249           /* Look if we need to enlarge the arrays indexed by the */
8250           /* number of columns.                                   */
8251           /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
8252           if (cols_number % COLSCHUNK == 0)
8253           {
8254             long ci; /* column index */
8255 
8256             col_real_max_size = xrealloc(col_real_max_size,
8257                                          (cols_number + COLSCHUNK)
8258                                            * sizeof(long));
8259 
8260             col_max_size = xrealloc(col_max_size,
8261                                     (cols_number + COLSCHUNK) * sizeof(long));
8262 
8263             /* Initialize the max size for the new columns. */
8264             /* '''''''''''''''''''''''''''''''''''''''''''' */
8265             for (ci = 0; ci < COLSCHUNK; ci++)
8266             {
8267               col_real_max_size[cols_number + ci] = 0;
8268               col_max_size[cols_number + ci]      = 0;
8269             }
8270           }
8271         }
8272       }
8273 
8274       /* We must now check if the word matches a RE that */
8275       /* exclude the whole column.                       */
8276       /* """"""""""""""""""""""""""""""""""""""""""""""" */
8277       if (cols_selector != NULL)
8278       {
8279         long ci; /* column index. */
8280 
8281         regex_t * col_re;
8282 
8283         if (cols_filter[col_index - 1] == EXCLUDE_MARK)
8284           selectable = EXCLUDE_MARK;
8285         else
8286         {
8287           if (exc_col_regex_list != NULL)
8288           {
8289             /* Some columns must be excluded by regex. */
8290             /* ''''''''''''''''''''''''''''''''''''''' */
8291             ll_node_t * col_regex_node = exc_col_regex_list->head;
8292 
8293             while (col_regex_node != NULL)
8294             {
8295               col_re = col_regex_node->data;
8296 
8297               if (regexec(col_re, word, (int)0, NULL, 0) == 0)
8298               {
8299                 cols_filter[col_index - 1] = EXCLUDE_MARK;
8300                 selectable                 = EXCLUDE_MARK;
8301 
8302                 /* Mark non selectable the items above in the column. */
8303                 /* '''''''''''''''''''''''''''''''''''''''''''''''''' */
8304                 ci = 0;
8305                 for (wi = 0; wi < count; wi++)
8306                 {
8307                   if (ci == col_index - 1)
8308                     word_a[wi].is_selectable = EXCLUDE_MARK;
8309 
8310                   if (word_a[wi].is_last)
8311                     ci = 0;
8312                   else
8313                     ci++;
8314                 }
8315                 break;
8316               }
8317 
8318               col_regex_node = col_regex_node->next;
8319             }
8320           }
8321 
8322           if (inc_col_regex_list != NULL)
8323           {
8324             /* Some columns must be included by regex. */
8325             /* ''''''''''''''''''''''''''''''''''''''' */
8326             ll_node_t * col_regex_node = inc_col_regex_list->head;
8327 
8328             while (col_regex_node != NULL)
8329             {
8330               col_re = col_regex_node->data;
8331 
8332               if (regexec(col_re, word, (int)0, NULL, 0) == 0)
8333               {
8334                 cols_filter[col_index - 1] = INCLUDE_MARK;
8335                 break;
8336               }
8337 
8338               col_regex_node = col_regex_node->next;
8339             }
8340           }
8341         }
8342       }
8343     }
8344 
8345     /* Store some known values in the current word's structure. */
8346     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8347     word_a[count].start = word_a[count].end = 0;
8348 
8349     word_a[count].str           = word;
8350     word_a[count].is_selectable = selectable;
8351 
8352     word_a[count].special_level = special_level;
8353     word_a[count].is_matching   = 0;
8354     word_a[count].is_tagged     = 0;
8355     word_a[count].is_numbered   = 0;
8356     word_a[count].tag_order     = 0;
8357 
8358     if (win.col_mode || win.line_mode || win.tab_mode)
8359     {
8360       /* Set the last word in line indicator when in */
8361       /* column/line/tab mode.                       */
8362       /* ''''''''''''''''''''''''''''''''''''''''''' */
8363       if (is_first && count > 0)
8364         word_a[count - 1].is_last = 1;
8365       word_a[count].is_last = is_last;
8366       if (is_last)
8367         col_index = 0;
8368     }
8369     else
8370       word_a[count].is_last = 0;
8371 
8372     /* One more word... */
8373     /* """""""""""""""" */
8374     if (count == limits.words)
8375     {
8376       fprintf(stderr,
8377               "The number of read words has reached the limit of %ld.\n",
8378               limits.words);
8379 
8380       exit(EXIT_FAILURE);
8381     }
8382 
8383     count++;
8384 
8385     if (count % WORDSCHUNK == 0)
8386       word_a = xrealloc(word_a, (count + WORDSCHUNK) * sizeof(word_t));
8387   }
8388 
8389   /* Early exit if there is no input or if no word is selected. */
8390   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8391   if (count == 0)
8392     exit(EXIT_FAILURE);
8393 
8394   /* Ignore SIGINT */
8395   /* """"""""""""" */
8396   sigaddset(&sigs, SIGINT);
8397   sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
8398 
8399   /* The last word is always the last of its line. */
8400   /* """"""""""""""""""""""""""""""""""""""""""""" */
8401   if (win.col_mode || win.line_mode || win.tab_mode)
8402     word_a[count - 1].is_last = 1;
8403 
8404   /* Second pass to modify  the word according to all/include/exclude       */
8405   /* regular expressions and the columns settings set in the previous pass. */
8406   /* This must be done separately because in the first  pass, some word     */
8407   /* could have been marked as excluded before the currently processed word */
8408   /*  (second part of the -C option)                                        */
8409   /* In this pass the following actions will also be done:                  */
8410   /* - Finish the work on columns.                                          */
8411   /* - Possibly modify the word according to -S/-I/-E arguments             */
8412   /* - Replace unprintable characters in the word by mnemonics              */
8413   /* - Remember the max size of the words/columns/tabs                      */
8414   /* - Insert the word in a TST (Ternary Search Tree) index to facilitate   */
8415   /*   word search (each node pf the TST will contain an UTF-8 glyph).      */
8416   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8417   col_index = 0;
8418   for (wi = 0; wi < count; wi++)
8419   {
8420     char *    unaltered_word;
8421     long      size;
8422     long      word_len;
8423     wchar_t * tmpw;
8424     word_t *  word;
8425     long      s;
8426     long      len;
8427     char *    expanded_word;
8428     long      i;
8429 
8430     /* If the column section argument is set, then adjust the final        */
8431     /* selectable attribute  according to the already set words and column */
8432     /* selectable flag contents.                                           */
8433     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8434     if (cols_selector_list != NULL)
8435     {
8436       if (cols_filter[col_index] == EXCLUDE_MARK)
8437         word_a[wi].is_selectable = EXCLUDE_MARK;
8438       else if (word_a[wi].is_selectable != EXCLUDE_MARK)
8439       {
8440         switch (cols_filter[col_index])
8441         {
8442           case INCLUDE_MARK:
8443             word_a[wi].is_selectable = INCLUDE_MARK;
8444             break;
8445 
8446           case SOFT_EXCLUDE_MARK:
8447             if (word_a[wi].is_selectable == SOFT_EXCLUDE_MARK
8448                 || word_a[wi].is_selectable == SOFT_INCLUDE_MARK)
8449               word_a[wi].is_selectable = EXCLUDE_MARK;
8450             else
8451               word_a[wi].is_selectable = INCLUDE_MARK;
8452             break;
8453 
8454           case SOFT_INCLUDE_MARK:
8455             if (word_a[wi].is_selectable == SOFT_EXCLUDE_MARK)
8456               word_a[wi].is_selectable = EXCLUDE_MARK;
8457             else
8458               word_a[wi].is_selectable = INCLUDE_MARK;
8459             break;
8460         }
8461       }
8462     }
8463 
8464     word = &word_a[wi];
8465 
8466     /* Make sure that daccess.length >= daccess.size */
8467     /* with DA_TYPE_POS.                             */
8468     /* """"""""""""""""""""""""""""""""""""""""""""" */
8469     if (daccess.mode != DA_TYPE_NONE)
8470     {
8471       if (daccess.mode & DA_TYPE_POS)
8472       {
8473         if (daccess.size > 0)
8474           if (daccess.size > daccess.length)
8475             daccess.length = daccess.size;
8476       }
8477 
8478       /* Auto determination of the length of the selector */
8479       /* with DA_TYPE_AUTO.                               */
8480       /* """""""""""""""""""""""""""""""""""""""""""""""" */
8481       if ((daccess.mode & DA_TYPE_AUTO) && daccess.length == -2)
8482       {
8483         long n = count;
8484 
8485         daccess.length = 0;
8486 
8487         while (n)
8488         {
8489           n /= 10;
8490           daccess.length++;
8491         }
8492       }
8493 
8494       /* Set the full length of the prefix in case of numbering. */
8495       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
8496       if (daccess.length > 0)
8497         daccess.flength = 3 + daccess.length;
8498 
8499       if (word->is_selectable != EXCLUDE_MARK
8500           && word->is_selectable != SOFT_EXCLUDE_MARK)
8501       {
8502         char * selector;
8503         char * tmp      = xmalloc(strlen(word->str) + 4 + daccess.length);
8504         long * word_pos = xmalloc(sizeof(long));
8505         int    may_number;
8506 
8507         if (!isempty(word->str))
8508         {
8509           *word_pos = wi;
8510 
8511           tmp[0] = ' ';
8512 
8513           /* Check if the word is eligible to the numbering process. */
8514           /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
8515           if (daccess_up == NULL && daccess_np == NULL)
8516           {
8517             if (daccess.mode & DA_TYPE_POS)
8518               may_number = 1;
8519             else
8520               may_number = 0;
8521           }
8522           else
8523           {
8524             if (daccess_up != NULL
8525                 && !!regexec(&daccess_up_re, word->str, (int)0, NULL, 0) == 0)
8526               may_number = 0;
8527             else
8528             {
8529               if (daccess_np != NULL
8530                   && !!regexec(&daccess_np_re, word->str, (int)0, NULL, 0) == 0)
8531                 may_number = 1;
8532               else
8533                 may_number = daccess.def_number;
8534             }
8535           }
8536 
8537           /* It is... */
8538           /* """""""" */
8539           if (may_number)
8540           {
8541             if (daccess.mode & DA_TYPE_POS)
8542             {
8543               if (!word->is_numbered)
8544               {
8545                 if (daccess.size > 0
8546                     && daccess.offset + daccess.size + daccess.ignore
8547                          <= utf8_strlen(word->str))
8548                 {
8549                   unsigned selector_value;  /* numerical value of the         *
8550                                              | extracted selector.            */
8551                   long     selector_offset; /* offset in byte to the selector *
8552                                              | to extract.                    */
8553                   char *   ptr;             /* points just after the selector *
8554                                              | to extract.                    */
8555                   long     plus_offset;     /* points to the first occurrence *
8556                                              | of a number in word->str after *
8557                                              | the offset given.              */
8558 
8559                   selector_offset = utf8_offset(word->str, daccess.offset);
8560 
8561                   if (daccess.plus)
8562                   {
8563                     plus_offset = strcspn(word->str + selector_offset,
8564                                           "0123456789");
8565 
8566                     if (plus_offset + daccess.size + daccess.ignore
8567                         <= strlen(word->str))
8568                       selector_offset += plus_offset;
8569                   }
8570 
8571                   ptr      = word->str + selector_offset;
8572                   selector = xstrndup(ptr, daccess.size);
8573 
8574                   /* read the embedded number and, if correct, format */
8575                   /* it according to daccess.alignment.               */
8576                   /* """""""""""""""""""""""""""""""""""""""""""""""" */
8577                   if (sscanf(selector, "%u", &selector_value) == 1)
8578                   {
8579                     sprintf(selector, "%u", selector_value);
8580 
8581                     sprintf(tmp + 1, "%*u",
8582                             daccess.alignment == 'l' ? -daccess.length
8583                                                      : daccess.length,
8584                             selector_value);
8585 
8586                     /* Overwrite the end of the word to erase */
8587                     /* the selector.                          */
8588                     /* """""""""""""""""""""""""""""""""""""" */
8589                     my_strcpy(ptr, ptr + daccess.size
8590                                      + utf8_offset(ptr + daccess.size,
8591                                                    daccess.ignore));
8592 
8593                     /* Modify the word according to the 'h' directive */
8594                     /* of -D.                                         */
8595                     /* """""""""""""""""""""""""""""""""""""""""""""" */
8596                     if (daccess.head == 'c')
8597                       /* h:c is present cut the leading characters */
8598                       /* before the selector.                      */
8599                       /* ''''''''''''''''''''''''''''''''''''''''' */
8600                       memmove(word->str, ptr, strlen(ptr) + 1);
8601                     else if (daccess.head == 't')
8602                     {
8603                       /* h:t is present trim the leading characters   */
8604                       /* before the selector if they are ' ' or '\t'. */
8605                       /* '''''''''''''''''''''''''''''''''''''''''''' */
8606                       char * p = word->str;
8607 
8608                       while (p != ptr && (*p == ' ' || *p == '\t'))
8609                         p++;
8610 
8611                       if (p == ptr)
8612                         memmove(word->str, ptr, strlen(ptr) + 1);
8613                     }
8614 
8615                     ltrim(selector, " ");
8616                     rtrim(selector, " ", 0);
8617 
8618                     tst_daccess = tst_insert(tst_daccess,
8619                                              utf8_strtowcs(selector), word_pos);
8620 
8621                     if (daccess.follow == 'y')
8622                       daccess_index = selector_value + 1;
8623 
8624                     word->is_numbered = 1;
8625                   }
8626                   free(selector);
8627                 }
8628               }
8629             }
8630 
8631             /* Try to number this word if it is still non numbered and */
8632             /* the -N/-U option is given.                              */
8633             /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
8634             if (!word->is_numbered && (daccess.mode & DA_TYPE_AUTO))
8635             {
8636               sprintf(tmp + 1, "%*ld",
8637                       daccess.alignment == 'l' ? -daccess.length
8638                                                : daccess.length,
8639                       daccess_index);
8640 
8641               selector = xstrdup(tmp + 1);
8642               ltrim(selector, " ");
8643               rtrim(selector, " ", 0);
8644 
8645               /* Insert it in the tst tree containing the selector's */
8646               /* digits.                                             */
8647               /* ''''''''''''''''''''''''''''''''''''''''''''''''''' */
8648               tst_daccess = tst_insert(tst_daccess, utf8_strtowcs(selector),
8649                                        word_pos);
8650               daccess_index++;
8651 
8652               free(selector);
8653 
8654               word->is_numbered = 1;
8655             }
8656           }
8657 
8658           /* Fill the space taken by the numbering by space if the word */
8659           /* is not numbered.                                           */
8660           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8661           if (daccess.length > 0 && !word->is_numbered)
8662           {
8663             for (i = 0; i < daccess.flength; i++)
8664               tmp[i] = ' ';
8665           }
8666 
8667           /* Make sure that the 2 character after this placeholder */
8668           /* are initialized.                                      */
8669           /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
8670           if (daccess.length > 0)
8671           {
8672             tmp[1 + daccess.length] = ' ';
8673             tmp[2 + daccess.length] = ' ';
8674           }
8675         }
8676         else if (daccess.length > 0)
8677         {
8678           /* make sure that the prefix of empty word is blank */
8679           /* as they may be display in column mode.           */
8680           /* """""""""""""""""""""""""""""""""""""""""""""""" */
8681           for (i = 0; i < daccess.flength; i++)
8682             tmp[i] = ' ';
8683         }
8684 
8685         if (daccess.length > 0)
8686         {
8687           my_strcpy(tmp + daccess.flength, word->str);
8688           free(word->str);
8689           word->str = tmp;
8690         }
8691         else
8692           free(tmp);
8693       }
8694       else
8695       {
8696         /* Should we also add space at the beginning of excluded words ? */
8697         /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8698         if (daccess.padding == 'a')
8699         {
8700           char * tmp = xmalloc(strlen(word->str) + 4 + daccess.length);
8701           for (i = 0; i < daccess.flength; i++)
8702             tmp[i] = ' ';
8703           my_strcpy(tmp + daccess.flength, word->str);
8704           free(word->str);
8705           word->str = tmp;
8706         }
8707       }
8708     }
8709     else
8710     {
8711       daccess.size   = 0;
8712       daccess.length = 0;
8713     }
8714 
8715     /* Save the original word. */
8716     /* """"""""""""""""""""""" */
8717     unaltered_word = xstrdup(word->str);
8718 
8719     /* Possibly modify the word according to -S/-I/-E arguments. */
8720     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8721     {
8722       ll_node_t * node = NULL;
8723       char *      tmp;
8724 
8725       /* Manage the -S case. */
8726       /* """"""""""""""""""" */
8727       if (sed_list != NULL)
8728       {
8729         node = sed_list->head;
8730 
8731         while (node != NULL)
8732         {
8733           tmp = xstrndup(word->str, daccess.flength);
8734           if (replace(word->str + daccess.flength, (sed_t *)(node->data)))
8735           {
8736 
8737             free(word->str);
8738             memmove(word_buffer + daccess.flength, word_buffer,
8739                     strlen(word_buffer) + 1);
8740             memmove(word_buffer, tmp, daccess.flength);
8741 
8742             word->str = xstrdup(word_buffer);
8743 
8744             if (((sed_t *)(node->data))->stop)
8745               break;
8746           }
8747 
8748           *word_buffer = '\0';
8749           node         = node->next;
8750           free(tmp);
8751         }
8752       }
8753       else
8754       {
8755         /* Manage the -I/-E case. */
8756         /* """""""""""""""""""""" */
8757         if ((word->is_selectable == INCLUDE_MARK
8758              || word->is_selectable == SOFT_INCLUDE_MARK)
8759             && include_sed_list != NULL)
8760           node = include_sed_list->head;
8761         else if ((word->is_selectable == EXCLUDE_MARK
8762                   || word->is_selectable == SOFT_EXCLUDE_MARK)
8763                  && exclude_sed_list != NULL)
8764           node = exclude_sed_list->head;
8765         else
8766           node = NULL;
8767 
8768         *word_buffer = '\0';
8769 
8770         while (node != NULL)
8771         {
8772           tmp = xstrndup(word->str, daccess.flength);
8773           if (replace(word->str + daccess.flength, (sed_t *)(node->data)))
8774           {
8775 
8776             free(word->str);
8777             memmove(word_buffer + daccess.flength, word_buffer,
8778                     strlen(word_buffer) + 1);
8779             memmove(word_buffer, tmp, daccess.flength);
8780 
8781             word->str = xstrdup(word_buffer);
8782 
8783             if (((sed_t *)(node->data))->stop)
8784               break;
8785           }
8786           *word_buffer = '\0';
8787           node         = node->next;
8788           free(tmp);
8789         }
8790       }
8791     }
8792 
8793     /* A substitution leading to an empty word is invalid in column mode. */
8794     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8795     if (win.col_mode)
8796     {
8797       long len;
8798 
8799       if (daccess.padding == 'a')
8800         len = daccess.flength;
8801       else
8802         len = 0;
8803 
8804       if (*(word->str + len) == '\0')
8805         exit(EXIT_FAILURE);
8806     }
8807 
8808     /* Alter the word just read be replacing special chars  by their */
8809     /* escaped equivalents.                                          */
8810     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8811     word_len = strlen(word->str);
8812 
8813     expanded_word = xmalloc(5 * word_len + 1);
8814     len = expand(word->str, expanded_word, &langinfo, &toggles, &misc);
8815 
8816     /* Update it if needed. */
8817     /* '''''''''''''''''''' */
8818     if (strcmp(expanded_word, word->str) != 0)
8819     {
8820       word_len = len;
8821       free(word->str);
8822       word->str = xstrdup(expanded_word);
8823     }
8824 
8825     free(expanded_word);
8826 
8827     if (win.col_mode)
8828     {
8829       /* Update the max values of col_real_max_size[col_index] */
8830       /* and col_max_size[col_index].                          */
8831       /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
8832       if ((s = (long)word_len) > col_real_max_size[col_index])
8833       {
8834         col_real_max_size[col_index] = s;
8835 
8836         /* Also update the real max size of all columns seen. */
8837         /* """""""""""""""""""""""""""""""""""""""""""""""""" */
8838         if (s > cols_real_max_size)
8839           cols_real_max_size = s;
8840       }
8841 
8842       s = (long)mbstowcs(NULL, word->str, 0);
8843       s = wcswidth((tmpw = utf8_strtowcs(word->str)), s);
8844       free(tmpw);
8845 
8846       if (s > col_max_size[col_index])
8847       {
8848         col_max_size[col_index] = s;
8849 
8850         /* Also update the max size of all columns seen. */
8851         /* """"""""""""""""""""""""""""""""""""""""""""" */
8852         if (s > cols_max_size)
8853           cols_max_size = s;
8854       }
8855       /* Update the size of the longest expanded word. */
8856       /* """"""""""""""""""""""""""""""""""""""""""""" */
8857       word_real_max_size = cols_real_max_size;
8858     }
8859     else if (win.tab_mode)
8860     {
8861       /* Store the new max number of bytes in a word       */
8862       /* and update the size of the longest expanded word. */
8863       /* """"""""""""""""""""""""""""""""""""""""""""""""" */
8864       if ((long)word_len > tab_real_max_size)
8865         word_real_max_size = tab_real_max_size = (long)word_len;
8866 
8867       /* Store the new max word width. */
8868       /* """"""""""""""""""""""""""""" */
8869       size = (long)mbstowcs(NULL, word->str, 0);
8870 
8871       if ((size = wcswidth((tmpw = utf8_strtowcs(word->str)), size))
8872           > tab_max_size)
8873         tab_max_size = size;
8874 
8875       free(tmpw);
8876     }
8877     else if (word_real_max_size < word_len)
8878       /* Update the size of the longest expanded word. */
8879       /* """"""""""""""""""""""""""""""""""""""""""""" */
8880       word_real_max_size = word_len;
8881 
8882     /* When the visual only flag is set, we keep the unaltered word so */
8883     /* that it can be restored even if its visual and searchable       */
8884     /* representation may have been altered by the previous code.      */
8885     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8886 
8887     /* Record the length of the word in bytes. This information will be */
8888     /* used if the -k option (keep spaces ) is not set.                 */
8889     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8890     word->len = strlen(word->str);
8891 
8892     /* Save the non modified word in .orig if it has been altered. */
8893     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8894     if ((strcmp(word->str, unaltered_word) != 0)
8895         && ((word->is_selectable && include_visual_only)
8896             || (!word->is_selectable && exclude_visual_only)))
8897     {
8898       word->orig = unaltered_word;
8899     }
8900     else
8901     {
8902       word->orig = NULL;
8903       free(unaltered_word);
8904     }
8905 
8906     if (win.col_mode)
8907     {
8908       if (word_a[wi].is_last)
8909         col_index = 0;
8910       else
8911         col_index++;
8912     }
8913   }
8914 
8915   /* Set the minimum width of a column (-w and -t or -c option). */
8916   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8917   if (win.wide)
8918   {
8919     if (win.tab_mode)
8920     {
8921       if (win.max_cols > 0)
8922         min_size = (term.ncolumns - 2) / win.max_cols - 1;
8923 
8924       if (min_size < tab_max_size)
8925         min_size = tab_max_size;
8926 
8927       word_real_max_size = min_size + tab_real_max_size - tab_max_size;
8928     }
8929     else /* Column mode. */
8930     {
8931       min_size = (term.ncolumns - 2) / cols_number;
8932       if (min_size < cols_max_size)
8933         min_size = cols_max_size;
8934 
8935       word_real_max_size = cols_real_max_size;
8936     }
8937   }
8938 
8939   /* Third (compress) pass: remove all empty word and words containing */
8940   /* only spaces when not in column mode.                              */
8941   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8942   if (!win.col_mode)
8943   {
8944     long offset;
8945 
8946     offset = 0;
8947     for (wi = 0; wi < count - offset; wi++)
8948     {
8949       long len;
8950 
8951       while (wi + offset < count)
8952       {
8953         if (daccess.padding == 'a' || word_a[wi + offset].is_numbered)
8954           len = daccess.flength;
8955         else
8956           len = 0;
8957 
8958         if (!isempty(word_a[wi + offset].str + len))
8959           break;
8960 
8961         /* Keep non selectable empty words to allow special effects. */
8962         /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8963         if (word_a[wi + offset].is_selectable == SOFT_EXCLUDE_MARK
8964             || word_a[wi + offset].is_selectable == EXCLUDE_MARK)
8965           break;
8966 
8967         offset++;
8968       }
8969 
8970       if (offset > 0)
8971         word_a[wi] = word_a[wi + offset];
8972     }
8973     count -= offset;
8974   }
8975 
8976   if (count == 0)
8977     exit(EXIT_FAILURE);
8978 
8979   /* Allocate the space for the satellites arrays. */
8980   /* """"""""""""""""""""""""""""""""""""""""""""" */
8981   line_nb_of_word_a    = xmalloc(count * sizeof(long));
8982   first_word_in_line_a = xmalloc(count * sizeof(long));
8983 
8984   /* Fourth pass:                                                         */
8985   /* When in column or tabulating mode, we need to adjust the length of   */
8986   /* all the words by adding the right number of spaces so that they will */
8987   /* be aligned correctly. In column mode the size of each column is      */
8988   /* variable; in tabulate mode it is constant.                           */
8989   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8990   if (win.col_mode)
8991   {
8992     char * temp;
8993 
8994     /* Sets all columns to the same size when -w and -c are both set. */
8995     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8996     if (win.wide)
8997       for (col_index = 0; col_index < cols_number; col_index++)
8998       {
8999         col_max_size[col_index]      = cols_max_size;
9000         col_real_max_size[col_index] = cols_real_max_size;
9001       }
9002 
9003     /* Total space taken by all the columns plus the gutter. */
9004     /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9005     win.max_width      = 0;
9006     win.real_max_width = 0;
9007     for (col_index = 0; col_index < cols_number; col_index++)
9008     {
9009       if (win.max_width + col_max_size[col_index] + 1 <= term.ncolumns - 2)
9010         win.max_width += col_max_size[col_index] + 1;
9011 
9012       win.real_max_width += col_max_size[col_index] + 1;
9013     }
9014 
9015     col_index = 0;
9016     for (wi = 0; wi < count; wi++)
9017     {
9018       long      s1, s2;
9019       long      word_width;
9020       wchar_t * w;
9021 
9022       s1         = (long)strlen(word_a[wi].str);
9023       word_width = mbstowcs(NULL, word_a[wi].str, 0);
9024       s2         = wcswidth((w = utf8_strtowcs(word_a[wi].str)), word_width);
9025       free(w);
9026       temp = xcalloc(1, col_real_max_size[col_index] + s1 - s2 + 1);
9027       memset(temp, ' ', col_max_size[col_index] + s1 - s2);
9028       memcpy(temp, word_a[wi].str, s1);
9029       temp[col_real_max_size[col_index] + s1 - s2] = '\0';
9030       free(word_a[wi].str);
9031       word_a[wi].str = temp;
9032 
9033       if (word_a[wi].is_last)
9034         col_index = 0;
9035       else
9036         col_index++;
9037     }
9038   }
9039   else if (win.tab_mode)
9040   {
9041     char * temp;
9042 
9043     if (tab_max_size < min_size)
9044     {
9045       tab_max_size = min_size;
9046       if (tab_max_size > tab_real_max_size)
9047         tab_real_max_size = tab_max_size;
9048     }
9049 
9050     for (wi = 0; wi < count; wi++)
9051     {
9052       long      s1, s2;
9053       long      word_width;
9054       wchar_t * w;
9055 
9056       s1         = (long)strlen(word_a[wi].str);
9057       word_width = mbstowcs(NULL, word_a[wi].str, 0);
9058       s2         = wcswidth((w = utf8_strtowcs(word_a[wi].str)), word_width);
9059       free(w);
9060       temp = xcalloc(1, tab_real_max_size + s1 - s2 + 1);
9061       memset(temp, ' ', tab_max_size + s1 - s2);
9062       memcpy(temp, word_a[wi].str, s1);
9063       temp[tab_real_max_size + s1 - s2] = '\0';
9064       free(word_a[wi].str);
9065       word_a[wi].str = temp;
9066     }
9067   }
9068 
9069   /* Fifth pass: transforms the remaining SOFT_EXCLUDE_MARKs with */
9070   /* EXCLUDE_MARKs.                                               */
9071   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9072   for (wi = 0; wi < count; wi++)
9073   {
9074     long *    data;
9075     wchar_t * w;
9076     ll_t *    list;
9077 
9078     if (word_a[wi].is_selectable == SOFT_EXCLUDE_MARK)
9079       word_a[wi].is_selectable = EXCLUDE_MARK;
9080 
9081     /* If the word is selectable insert it in the TST tree */
9082     /* with its associated index in the input stream.      */
9083     /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
9084     if (word_a[wi].is_selectable)
9085     {
9086       data  = xmalloc(sizeof(long));
9087       *data = wi;
9088 
9089       /* Create a wide characters string from the word screen */
9090       /* representation to be able to store in in the TST.    */
9091       /* Note that the direct access selector,if any, is not  */
9092       /* stored.                                              */
9093       /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
9094       if (word_a[wi].is_numbered)
9095         w = utf8_strtowcs(word_a[wi].str + daccess.flength);
9096       else
9097         w = utf8_strtowcs(word_a[wi].str);
9098 
9099       /* If we didn't already encounter this word, then create a new */
9100       /* entry in the TST for it and store its index in its list.    */
9101       /* Otherwise, add its index in its index list.                 */
9102       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9103       if (tst_word && (list = tst_search(tst_word, w)) != NULL)
9104         ll_append(list, data);
9105       else
9106       {
9107         list = ll_new();
9108         ll_append(list, data);
9109         tst_word = tst_insert(tst_word, w, list);
9110       }
9111       free(w);
9112     }
9113   }
9114 
9115   /* The word after the last one is set to NULL. */
9116   /* """"""""""""""""""""""""""""""""""""""""""" */
9117   word_a[count].str = NULL;
9118 
9119   /* We can now allocate the space for our tmp_word work variable. */
9120   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9121   tmp_word = xcalloc(word_real_max_size + 1, 1);
9122 
9123   search_data.utf8_off_a = xmalloc(word_real_max_size * sizeof(long));
9124   search_data.utf8_len_a = xmalloc(word_real_max_size * sizeof(long));
9125 
9126   win.start = 0; /* index of the first element in the    *
9127                   | words array to be  displayed.        */
9128 
9129   /* We can now build the first metadata. */
9130   /* """""""""""""""""""""""""""""""""""" */
9131   last_line = build_metadata(&term, count, &win);
9132 
9133   /* Index of the selected element in the array words                */
9134   /* The string can be:                                              */
9135   /*   "last"    The string "last"   put the cursor on the last word */
9136   /*   n         a number            put the cursor on the word n    */
9137   /*   /pref     /+a regexp          put the cursor on the first     */
9138   /*                                 word matching the prefix "pref" */
9139   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9140 
9141   for (wi = 0; wi < count; wi++)
9142   {
9143     long len;
9144 
9145     if (daccess.padding == 'a' || word_a[wi].is_numbered)
9146       len = daccess.flength;
9147     else
9148       len = 0;
9149 
9150     word_a[wi].bitmap = xcalloc(1, (word_a[wi].mb - len) / CHAR_BIT + 1);
9151   }
9152 
9153   /* Find the first selectable word (if any) in the input stream. */
9154   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9155   for (first_selectable = 0;
9156        first_selectable < count && !word_a[first_selectable].is_selectable;
9157        first_selectable++)
9158     ;
9159 
9160   /* If not found, abort. */
9161   /* """""""""""""""""""" */
9162   if (first_selectable == count)
9163   {
9164     fprintf(stderr, "No selectable word found.\n");
9165 
9166     exit(EXIT_FAILURE);
9167   }
9168 
9169   /* Else find the last selectable word in the input stream. */
9170   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
9171   last_selectable = count - 1;
9172   while (last_selectable > 0 && !word_a[last_selectable].is_selectable)
9173     last_selectable--;
9174 
9175   if (pre_selection_index == NULL)
9176     /* Option -s was not used. */
9177     /* """"""""""""""""""""""" */
9178     current = first_selectable;
9179   else if (*pre_selection_index == '/')
9180   {
9181     /* A regular expression is expected. */
9182     /* """"""""""""""""""""""""""""""""" */
9183     regex_t re;
9184     long    index;
9185 
9186     if (regcomp(&re, pre_selection_index + 1, REG_EXTENDED | REG_NOSUB) != 0)
9187     {
9188       fprintf(stderr, "%s: Invalid regular expression.\n", pre_selection_index);
9189 
9190       exit(EXIT_FAILURE);
9191     }
9192     else
9193     {
9194       int    found = 0;
9195       char * word;
9196 
9197       for (index = first_selectable; index <= last_selectable; index++)
9198       {
9199         if (!word_a[index].is_selectable)
9200           continue;
9201 
9202         if (word_a[index].orig != NULL)
9203           word = word_a[index].orig;
9204         else
9205           word = word_a[index].str;
9206 
9207         if (regexec(&re, word, (int)0, NULL, 0) == 0)
9208         {
9209           current = index;
9210           found   = 1;
9211           break;
9212         }
9213       }
9214 
9215       if (!found)
9216         current = first_selectable;
9217     }
9218   }
9219   else if (*pre_selection_index == '=') /* exact search. */
9220   {
9221     /* A prefix is expected. */
9222     /* """"""""""""""""""""" */
9223     wchar_t * w;
9224 
9225     ll_t *      list;
9226     ll_node_t * node;
9227 
9228     list = tst_search(tst_word, w = utf8_strtowcs(pre_selection_index + 1));
9229     if (list != NULL)
9230     {
9231       node    = list->head;
9232       current = *(long *)(node->data);
9233     }
9234     else
9235       current = first_selectable;
9236 
9237     free(w);
9238   }
9239   else if (*pre_selection_index != '\0')
9240   {
9241     /* A prefix string or an index is expected. */
9242     /* """""""""""""""""""""""""""""""""""""""" */
9243     int    len;
9244     char * ptr = pre_selection_index;
9245 
9246     if (*ptr == '#')
9247     {
9248       /* An index is expected. */
9249       /* """"""""""""""""""""" */
9250       ptr++;
9251 
9252       if (sscanf(ptr, "%ld%n", &current, &len) == 1 && len == (int)strlen(ptr))
9253       {
9254         /* We got an index (numeric value). */
9255         /* """""""""""""""""""""""""""""""" */
9256         if (current < 0)
9257           current = first_selectable;
9258 
9259         if (current >= count)
9260           current = count - 1;
9261 
9262         if (!word_a[current].is_selectable)
9263         {
9264           if (current > last_selectable)
9265             current = last_selectable;
9266           else if (current < first_selectable)
9267             current = first_selectable;
9268           else
9269             while (current > first_selectable && !word_a[current].is_selectable)
9270               current--;
9271         }
9272       }
9273       else if (*ptr == '\0' || strcmp(ptr, "last") == 0)
9274         /* We got a special index (empty or last). */
9275         /* """"""""""""""""""""""""""""""""""""""" */
9276         current = last_selectable;
9277       else
9278       {
9279         fprintf(stderr, "%s: Invalid index.\n", ptr);
9280 
9281         exit(EXIT_FAILURE);
9282       }
9283     }
9284     else
9285     {
9286       /* A prefix is expected. */
9287       /* """"""""""""""""""""" */
9288       wchar_t * w;
9289 
9290       new_current = last_selectable;
9291       if (NULL
9292           != tst_prefix_search(tst_word, w = utf8_strtowcs(ptr), tst_cb_cli))
9293         current = new_current;
9294       else
9295         current = first_selectable;
9296       free(w);
9297     }
9298   }
9299   else
9300     current = first_selectable;
9301 
9302   /* We now need to adjust the 'start'/'end' fields of the */
9303   /* structure 'win'.                                      */
9304   /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9305   set_win_start_end(&win, current, last_line);
9306 
9307   /* Re-associates /dev/tty with stdin and stdout. */
9308   /* """"""""""""""""""""""""""""""""""""""""""""" */
9309   if (freopen("/dev/tty", "r", stdin) == NULL)
9310   {
9311     fprintf(stderr, "Unable to associate /dev/tty with stdin.\n");
9312     exit(EXIT_FAILURE);
9313   }
9314 
9315   old_fd1    = dup(1);
9316   old_stdout = fdopen(old_fd1, "w");
9317 
9318   setbuf(old_stdout, NULL);
9319 
9320   if (freopen("/dev/tty", "w", stdout) == NULL)
9321   {
9322     fprintf(stderr, "Unable to associate /dev/tty with stdout.\n");
9323     exit(EXIT_FAILURE);
9324   }
9325 
9326   setvbuf(stdout, NULL, _IONBF, 0);
9327 
9328   /* Set the characteristics of the terminal. */
9329   /* """""""""""""""""""""""""""""""""""""""" */
9330   setup_term(fileno(stdin));
9331 
9332   if (!get_cursor_position(&row, &col))
9333   {
9334     fprintf(stderr, "The terminal does not have the capability to report "
9335                     "the cursor position.\n");
9336     restore_term(fileno(stdin));
9337 
9338     exit(EXIT_FAILURE);
9339   }
9340 
9341   /* Initialize the search buffer with tab_real_max_size+1 NULs  */
9342   /* It will never be reallocated, only cleared.                 */
9343   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9344   search_data.buf = xcalloc(1, word_real_max_size + 1 - daccess.flength);
9345 
9346   /* Hide the cursor. */
9347   /* """""""""""""""" */
9348   tputs(TPARM1(cursor_invisible), 1, outch);
9349 
9350   /* Force the display to start at a beginning of line. */
9351   /* """""""""""""""""""""""""""""""""""""""""""""""""" */
9352   get_cursor_position(&term.curs_line, &term.curs_column);
9353   if (term.curs_column > 1)
9354     puts("");
9355 
9356   /* Display the words window and its title for the first time. */
9357   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9358   disp_message(message_lines_list, message_max_width, message_max_len, &term,
9359                &win, &langinfo);
9360 
9361   /* Before displaying the word windows for the first time when ins   */
9362   /* column or line mode, we need to ensure that the word under the   */
9363   /* cursor will be visible by setting the number of the first column */
9364   /* to be displayed.                                                 */
9365   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9366   if (win.col_mode || win.line_mode)
9367   {
9368     long pos;
9369     long len;
9370 
9371     len = term.ncolumns - 3;
9372 
9373     /* Adjust win.first_column if the cursor is not visible. */
9374     /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9375     pos = first_word_in_line_a[line_nb_of_word_a[current]];
9376 
9377     while (word_a[current].end - word_a[pos].start >= len)
9378       pos++;
9379 
9380     win.first_column = word_a[pos].start;
9381   }
9382 
9383   /* Save the initial cursor line and column, here only the line is    */
9384   /* interesting us. This will tell us if we are in need to compensate */
9385   /* a terminal automatic scrolling.                                   */
9386   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9387   get_cursor_position(&term.curs_line, &term.curs_column);
9388 
9389   nl = disp_lines(&win, &toggles, current, count, search_mode, &search_data,
9390                   &term, last_line, tmp_word, &langinfo);
9391 
9392   /* Determine the number of lines to move the cursor up if the window */
9393   /* display needed a terminal scrolling.                              */
9394   /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9395   if (nl + term.curs_line - 1 > term.nlines)
9396     offset = term.curs_line + nl - term.nlines;
9397   else
9398     offset = 0;
9399 
9400   /* Set the cursor to the first line of the window. */
9401   /* """"""""""""""""""""""""""""""""""""""""""""""" */
9402   {
9403     long i; /* generic index in this block. */
9404 
9405     for (i = 1; i < offset; i++)
9406       tputs(TPARM1(cursor_up), 1, outch);
9407   }
9408 
9409   /* Save again the cursor current line and column positions so that we */
9410   /* will be able to put the terminal cursor back here.                 */
9411   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9412   get_cursor_position(&term.curs_line, &term.curs_column);
9413 
9414   /* Arm the periodic timer. */
9415   /* """"""""""""""""""""""" */
9416   periodic_itv.it_value.tv_sec     = 0;
9417   periodic_itv.it_value.tv_usec    = TCK;
9418   periodic_itv.it_interval.tv_sec  = 0;
9419   periodic_itv.it_interval.tv_usec = TCK;
9420   setitimer(ITIMER_REAL, &periodic_itv, NULL);
9421 
9422   /* Signal management. */
9423   /* """""""""""""""""" */
9424   void sig_handler(int s);
9425 
9426   sa.sa_handler = sig_handler;
9427   sa.sa_flags   = 0;
9428   sigemptyset(&sa.sa_mask);
9429   sigaction(SIGWINCH, &sa, NULL);
9430   sigaction(SIGALRM, &sa, NULL);
9431   sigaction(SIGTERM, &sa, NULL);
9432   sigaction(SIGHUP, &sa, NULL);
9433   sigaction(SIGSEGV, &sa, NULL);
9434 
9435   /* Main loop. */
9436   /* """""""""" */
9437   while (1)
9438   {
9439     int sc = 0; /* scancode */
9440 
9441     /* Manage a segmentation fault by exiting with failure and restoring */
9442     /* the terminal and the cursor.                                      */
9443     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9444     if (got_sigsegv)
9445     {
9446       fputs("SIGSEGV received!\n", stderr);
9447       tputs(TPARM1(carriage_return), 1, outch);
9448       tputs(TPARM1(cursor_normal), 1, outch);
9449       restore_term(fileno(stdin));
9450 
9451       exit(128 + SIGSEGV);
9452     }
9453 
9454     /* Manage the hangup and termination signal by exiting with failure */
9455     /* and restoring the terminal and the cursor.                       */
9456     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9457     if (got_sigterm || got_sighup)
9458     {
9459       fputs("Interrupted!\n", stderr);
9460       tputs(TPARM1(carriage_return), 1, outch);
9461       tputs(TPARM1(cursor_normal), 1, outch);
9462       restore_term(fileno(stdin));
9463 
9464       if (got_sigterm)
9465         exit(128 + SIGTERM);
9466       else
9467         exit(128 + SIGHUP);
9468     }
9469 
9470     /* If this alarm is triggered, then redisplay the window */
9471     /* to remove the help message and disable this timer.    */
9472     /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9473     if (got_help_alrm)
9474     {
9475       got_help_alrm = 0;
9476 
9477       /* Calculate the new metadata and draw the window again. */
9478       /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9479       last_line = build_metadata(&term, count, &win);
9480 
9481       help_mode = 0;
9482       nl = disp_lines(&win, &toggles, current, count, search_mode, &search_data,
9483                       &term, last_line, tmp_word, &langinfo);
9484     }
9485 
9486     /* Reset the direct access selector if the direct access alarm rang. */
9487     /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9488     if (got_daccess_alrm)
9489     {
9490       got_daccess_alrm = 0;
9491       memset(daccess_stack, '\0', 6);
9492       daccess_stack_head = 0;
9493 
9494       daccess_timer = timers.direct_access;
9495     }
9496 
9497     if (got_search_alrm)
9498     {
9499       got_search_alrm = 0;
9500 
9501       /* Calculate the new metadata and draw the window again. */
9502       /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9503       last_line = build_metadata(&term, count, &win);
9504 
9505       search_mode = NONE;
9506 
9507       nl = disp_lines(&win, &toggles, current, count, search_mode, &search_data,
9508                       &term, last_line, tmp_word, &langinfo);
9509     }
9510 
9511     if (got_winch)
9512     {
9513       got_winch      = 0;
9514       got_winch_alrm = 0;
9515       winch_timer    = timers.winch; /* Rearm the refresh timer. */
9516     }
9517 
9518     /* If the timeout is set then decrement its remaining value   */
9519     /* Upon expiration of this alarm, we trigger a content update */
9520     /* of the window.                                             */
9521     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9522     if (got_winch_alrm)
9523     {
9524       long i; /* generic index in this block */
9525       int  nlines, ncolumns;
9526       int  line, column;
9527       int  original_message_lines;
9528 
9529       got_winch_alrm = 0; /* Reset the flag signaling the need for a refresh. */
9530       winch_timer    = -1; /* Disarm the timer used for this refresh.         */
9531 
9532       if (message_lines_list != NULL && message_lines_list->len > 0)
9533         original_message_lines = message_lines_list->len + 1;
9534       else
9535         original_message_lines = 0;
9536 
9537       get_terminal_size(&nlines, &ncolumns, &term);
9538 
9539       /* Update term with the new number of lines and columns */
9540       /* of the real terminal.                                */
9541       /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
9542       term.nlines   = nlines;
9543       term.ncolumns = ncolumns;
9544 
9545       /* Reset the number of lines if the terminal has enough lines. */
9546       /* message_lines_list->len+1 is used here instead of           */
9547       /* win.message_lines because win.message_lines may have been   */
9548       /* altered by a previous scrolling and not yet recalculated.   */
9549       /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9550       if (term.nlines <= original_message_lines)
9551       {
9552         win.message_lines = term.nlines - 1;
9553         win.max_lines     = 1;
9554       }
9555       else
9556       {
9557         win.message_lines = original_message_lines;
9558 
9559         if (win.max_lines < term.nlines - win.message_lines)
9560         {
9561           if (win.asked_max_lines == 0)
9562             win.max_lines = term.nlines - win.message_lines;
9563           else
9564           {
9565             if (win.asked_max_lines > term.nlines - win.message_lines)
9566               win.max_lines = term.nlines - win.message_lines;
9567             else
9568               win.max_lines = win.asked_max_lines;
9569           }
9570         }
9571         else
9572           win.max_lines = term.nlines - win.message_lines;
9573       }
9574 
9575       /* Erase the visible part of the displayed window. */
9576       /* """"""""""""""""""""""""""""""""""""""""""""""" */
9577       for (i = 0; i < win.message_lines; i++)
9578       {
9579         tputs(TPARM1(clr_bol), 1, outch);
9580         tputs(TPARM1(clr_eol), 1, outch);
9581         tputs(TPARM1(cursor_up), 1, outch);
9582       }
9583 
9584       tputs(TPARM1(clr_bol), 1, outch);
9585       tputs(TPARM1(clr_eol), 1, outch);
9586       tputs(TPARM1(save_cursor), 1, outch);
9587 
9588       get_cursor_position(&line, &column);
9589 
9590       for (i = 1; i < nl + win.message_lines; i++)
9591       {
9592         if (line + i >= nlines)
9593           break; /* We have reached the last terminal line. */
9594 
9595         tputs(TPARM1(cursor_down), 1, outch);
9596         tputs(TPARM1(clr_bol), 1, outch);
9597         tputs(TPARM1(clr_eol), 1, outch);
9598       }
9599       tputs(TPARM1(restore_cursor), 1, outch);
9600 
9601       /* Get new cursor position. */
9602       /* """""""""""""""""""""""" */
9603       get_cursor_position(&term.curs_line, &term.curs_column);
9604 
9605       /* Calculate the new metadata and draw the window again. */
9606       /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
9607       last_line = build_metadata(&term, count, &win);
9608 
9609       if (win.col_mode || win.line_mode)
9610       {
9611         long pos;
9612         long len;
9613 
9614         len = term.ncolumns - 3;
9615 
9616         /* Adjust win.first_column if the cursor is no more visible. */
9617         /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9618         pos = first_word_in_line_a[line_nb_of_word_a[current]];
9619 
9620         while (word_a[current].end - word_a[pos].start >= len)
9621           pos++;
9622 
9623         win.first_column = word_a[pos].start;
9624       }
9625 
9626       disp_message(message_lines_list, message_max_width, message_max_len,
9627                    &term, &win, &langinfo);
9628 
9629       nl = disp_lines(&win, &toggles, current, count, search_mode, &search_data,
9630                       &term, last_line, tmp_word, &langinfo);
9631 
9632       /* Determine the number of lines to move the cursor up if the window  */
9633       /* display needed a terminal scrolling.                               */
9634       /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9635       if (nl + win.message_lines + term.curs_line > term.nlines)
9636         offset = term.curs_line + nl + win.message_lines - term.nlines;
9637       else
9638         offset = 0;
9639 
9640       /* Set the cursor to the first line of the window. */
9641       /* """"""""""""""""""""""""""""""""""""""""""""""" */
9642       for (i = 1; i < offset; i++)
9643         tputs(TPARM1(cursor_up), 1, outch);
9644 
9645       /* Get new cursor position. */
9646       /* """""""""""""""""""""""" */
9647       get_cursor_position(&term.curs_line, &term.curs_column);
9648 
9649       /* Short-circuit the loop. */
9650       /* """"""""""""""""""""""" */
9651       continue;
9652     }
9653 
9654     /* and possibly set its reached value.                      */
9655     /* The counter is frozen in search and help mode.           */
9656     /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9657     if (timeout.initial_value && search_mode == NONE && !help_mode)
9658     {
9659       if (got_timeout_tick)
9660       {
9661         long   i;
9662         char * timeout_string;
9663 
9664         got_timeout_tick = 0;
9665 
9666         timeout.remain--;
9667 
9668         if (!quiet_timeout && timeout.remain % FREQ == 0)
9669         {
9670           sprintf(timeout_seconds, "%5u", timeout.remain / FREQ);
9671           timeout_string =
9672             (char *)(((ll_node_t *)(message_lines_list->tail))->data);
9673           memcpy(timeout_string + 1, timeout_seconds, 5);
9674 
9675           /* Erase the current window. */
9676           /* """"""""""""""""""""""""" */
9677           for (i = 0; i < win.message_lines; i++)
9678           {
9679             tputs(TPARM1(cursor_up), 1, outch);
9680             tputs(TPARM1(clr_bol), 1, outch);
9681             tputs(TPARM1(clr_eol), 1, outch);
9682           }
9683 
9684           tputs(TPARM1(clr_bol), 1, outch);
9685           tputs(TPARM1(clr_eol), 1, outch);
9686 
9687           /* Display the words window and its title for the first time. */
9688           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9689           disp_message(message_lines_list, message_max_width, message_max_len,
9690                        &term, &win, &langinfo);
9691         }
9692         /* The timeout has expired. */
9693         /* """""""""""""""""""""""" */
9694         if (timeout.remain == 0)
9695           timeout.reached = 1;
9696       }
9697     }
9698 
9699     if (timeout.reached)
9700     {
9701       if (timeout.mode == QUIT)
9702         goto quit;
9703       else if (timeout.mode == CURRENT || timeout.mode == WORD)
9704         goto enter;
9705     }
9706 
9707     /* Pressed keys scancodes processing. */
9708     /* """""""""""""""""""""""""""""""""" */
9709     page = 1; /* Default number of lines to do down/up *
9710                | with PgDn/PgUp.                       */
9711 
9712     sc = get_scancode(buffer, 15);
9713 
9714     if (sc && winch_timer < 0) /* Do not allow input when a window *
9715                                 | refresh is scheduled.            */
9716     {
9717       if (timeout.initial_value && buffer[0] != 0x0d && buffer[0] != 'q'
9718           && buffer[0] != 'Q' && buffer[0] != 3)
9719       {
9720         long i;
9721 
9722         char * timeout_string;
9723 
9724         /* Reset the timeout to its initial value. */
9725         /* """"""""""""""""""""""""""""""""""""""" */
9726         timeout.remain = timeout.initial_value;
9727 
9728         if (!quiet_timeout)
9729         {
9730           sprintf(timeout_seconds, "%5u", timeout.initial_value / FREQ);
9731           timeout_string =
9732             (char *)(((ll_node_t *)(message_lines_list->tail))->data);
9733           memcpy(timeout_string + 1, timeout_seconds, 5);
9734 
9735           /* Clear the message. */
9736           /* """""""""""""""""" */
9737           for (i = 0; i < win.message_lines; i++)
9738           {
9739             tputs(TPARM1(cursor_up), 1, outch);
9740             tputs(TPARM1(clr_bol), 1, outch);
9741             tputs(TPARM1(clr_eol), 1, outch);
9742           }
9743 
9744           tputs(TPARM1(clr_bol), 1, outch);
9745           tputs(TPARM1(clr_eol), 1, outch);
9746 
9747           /* Display the words window and its title for the first time. */
9748           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9749           disp_message(message_lines_list, message_max_width, message_max_len,
9750                        &term, &win, &langinfo);
9751         }
9752 
9753         setitimer(ITIMER_REAL, &periodic_itv, NULL);
9754       }
9755 
9756       if (search_mode == NONE)
9757         if (help_mode && buffer[0] != '?')
9758         {
9759           got_help_alrm = 1;
9760           continue;
9761         }
9762 
9763       switch (buffer[0])
9764       {
9765         case 0x01: /* ^A */
9766           if (search_mode != NONE)
9767             goto khome;
9768 
9769           break;
9770 
9771         case 0x1a: /* ^Z */
9772           if (search_mode != NONE)
9773             goto kend;
9774 
9775           break;
9776 
9777         case 0x1b: /* ESC */
9778           /* An escape sequence or a UTF-8 sequence has been pressed. */
9779           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9780           if (memcmp("\x1bOH", buffer, 3) == 0
9781               || memcmp("\x1bk", buffer, 2) == 0
9782               || memcmp("\x1b[H", buffer, 3) == 0
9783               || memcmp("\x1b[1~", buffer, 4) == 0
9784               || memcmp("\x1b[7~", buffer, 4) == 0)
9785           {
9786             /* HOME key has been pressed. */
9787             /* """""""""""""""""""""""""" */
9788             if (search_mode != NONE)
9789             {
9790             khome:
9791               search_data.only_starting = 1;
9792               search_data.only_ending   = 0;
9793               select_starting_matches(&win, &term, &search_data, &last_line);
9794             }
9795             else
9796             {
9797               /* Find the first selectable word. */
9798               /* """"""""""""""""""""""""""""""" */
9799               current = win.start;
9800 
9801               while (current < win.end && !word_a[current].is_selectable)
9802                 current++;
9803 
9804               /* In column mode we need to take care of the */
9805               /* horizontal scrolling.                      */
9806               /* """""""""""""""""""""""""""""""""""""""""" */
9807               if (win.col_mode || win.line_mode)
9808                 if (word_a[current].end < win.first_column)
9809                   win.first_column = word_a[current].start;
9810             }
9811 
9812             nl = disp_lines(&win, &toggles, current, count, search_mode,
9813                             &search_data, &term, last_line, tmp_word,
9814                             &langinfo);
9815             break;
9816           }
9817 
9818           if (memcmp("\x1b[1;5H", buffer, 6) == 0
9819               || memcmp("\x1b[1;2H", buffer, 6) == 0
9820               || memcmp("\x1b[7^", buffer, 4) == 0)
9821             /* SHIFT/CTRL HOME key has been pressed. */
9822             /* """"""""""""""""""""""""""""""""""""" */
9823             goto kschome;
9824 
9825           if (memcmp("\x1bOF", buffer, 3) == 0
9826               || memcmp("\x1bj", buffer, 2) == 0
9827               || memcmp("\x1b[F", buffer, 3) == 0
9828               || memcmp("\x1b[4~", buffer, 4) == 0
9829               || memcmp("\x1b[8~", buffer, 4) == 0)
9830           {
9831             /* END key has been pressed. */
9832             /* """"""""""""""""""""""""" */
9833             if (search_mode != NONE)
9834             {
9835             kend:
9836 
9837               if (matches_count > 0 && search_mode != PREFIX)
9838               {
9839                 search_data.only_starting = 0;
9840                 search_data.only_ending   = 1;
9841                 select_ending_matches(&win, &term, &search_data, &last_line);
9842               }
9843             }
9844             else
9845             {
9846               /* Find the last selectable word. */
9847               /* """""""""""""""""""""""""""""" */
9848               current = win.end;
9849 
9850               while (current > win.start && !word_a[current].is_selectable)
9851                 current--;
9852 
9853               /* In column mode we need to take care of the */
9854               /* horizontal scrolling.                      */
9855               /* """""""""""""""""""""""""""""""""""""""""" */
9856               if (win.col_mode || win.line_mode)
9857               {
9858                 long pos;
9859                 long len;
9860 
9861                 len = term.ncolumns - 3;
9862 
9863                 if (word_a[current].end >= len + win.first_column)
9864                 {
9865                   /* Find the first word to be displayed in this line. */
9866                   /* """"""""""""""""""""""""""""""""""""""""""""""""" */
9867                   pos = first_word_in_line_a[line_nb_of_word_a[current]];
9868 
9869                   while (word_a[pos].start <= win.first_column)
9870                     pos++;
9871 
9872                   /* If the new current word cannot be displayed, search */
9873                   /* the first word in the line that can be displayed by */
9874                   /* iterating on pos.                                   */
9875                   /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
9876                   pos--;
9877 
9878                   while (word_a[current].end - word_a[pos].start >= len)
9879                     pos++;
9880 
9881                   if (word_a[pos].start > 0)
9882                     win.first_column = word_a[pos].start;
9883                 }
9884               }
9885             }
9886 
9887             nl = disp_lines(&win, &toggles, current, count, search_mode,
9888                             &search_data, &term, last_line, tmp_word,
9889                             &langinfo);
9890             break;
9891           }
9892 
9893           if (memcmp("\x1b[1;5F", buffer, 6) == 0
9894               || memcmp("\x1b[1;2F", buffer, 6) == 0
9895               || memcmp("\x1b[8^", buffer, 4) == 0)
9896             /* SHIFT/CTRL END key has been pressed. */
9897             /* """""""""""""""""""""""""""""""""""" */
9898             goto kscend;
9899 
9900           if (memcmp("\x1bOD", buffer, 3) == 0
9901               || memcmp("\x1b[D", buffer, 3) == 0)
9902             /* Left arrow key has been pressed. */
9903             /* """""""""""""""""""""""""""""""" */
9904             goto kl;
9905 
9906           if (memcmp("\x1bOC", buffer, 3) == 0
9907               || memcmp("\x1b[C", buffer, 3) == 0)
9908             /* Right arrow key has been pressed. */
9909             /* """"""""""""""""""""""""""""""""" */
9910             goto kr;
9911 
9912           if (memcmp("\x1bOA", buffer, 3) == 0
9913               || memcmp("\x1b[A", buffer, 3) == 0)
9914             /* Up arrow key has been pressed. */
9915             /* """""""""""""""""""""""""""""" */
9916             goto ku;
9917 
9918           if (memcmp("\x1bOB", buffer, 3) == 0
9919               || memcmp("\x1b[B", buffer, 3) == 0)
9920             /* Down arrow key has been pressed. */
9921             /* """""""""""""""""""""""""""""""" */
9922             goto kd;
9923 
9924           if (memcmp("\x1b[I", buffer, 3) == 0
9925               || memcmp("\x1b[5~", buffer, 4) == 0)
9926             /* PgUp key has been pressed. */
9927             /* """""""""""""""""""""""""" */
9928             goto kpp;
9929 
9930           if (memcmp("\x1b[G", buffer, 3) == 0
9931               || memcmp("\x1b[6~", buffer, 4) == 0)
9932             /* PgDn key has been pressed. */
9933             /* """""""""""""""""""""""""" */
9934             goto knp;
9935 
9936           if (memcmp("\x1b[L", buffer, 3) == 0
9937               || memcmp("\x1b[2~", buffer, 4) == 0)
9938             /* Ins key has been pressed. */
9939             /* """"""""""""""""""""""""" */
9940             goto kins;
9941 
9942           if (memcmp("\x1b[3~", buffer, 4) == 0)
9943             /* Del key has been pressed. */
9944             /* """"""""""""""""""""""""" */
9945             goto kdel;
9946 
9947           if (memcmp("\x1b[1;5C", buffer, 6) == 0
9948               || memcmp("\x1bOc", buffer, 3) == 0)
9949             /* CTRL -> has been pressed. */
9950             /* """""""""""""""""""""""""" */
9951             goto keol;
9952 
9953           if (memcmp("\x1b[1;5D", buffer, 6) == 0
9954               || memcmp("\x1bOd", buffer, 3) == 0)
9955             /* CTRL <- key has been pressed. */
9956             /* """"""""""""""""""""""""""""" */
9957             goto ksol;
9958 
9959           if (buffer[0] == 0x1b && buffer[1] == '\0')
9960           {
9961             /* ESC key has been pressed. */
9962             /* """"""""""""""""""""""""" */
9963             reset_search_buffer(&win, &search_data, &timers, &toggles, &term,
9964                                 &daccess, &langinfo, last_line, tmp_word,
9965                                 word_real_max_size);
9966 
9967             break;
9968           }
9969 
9970           /* Else ignore key. */
9971           break;
9972 
9973         quit:
9974         case 'q':
9975         case 'Q':
9976         case 3: /* ^C */
9977           /* q or Q of ^C has been pressed. */
9978           /* """""""""""""""""""""""""""""" */
9979           if (search_mode != NONE && buffer[0] != 3)
9980             goto special_cmds_when_searching;
9981 
9982           {
9983             long i; /* Generic index in this block. */
9984 
9985             for (i = 0; i < win.message_lines; i++)
9986               tputs(TPARM1(cursor_up), 1, outch);
9987 
9988             if (toggles.del_line)
9989             {
9990               tputs(TPARM1(clr_eol), 1, outch);
9991               tputs(TPARM1(clr_bol), 1, outch);
9992               tputs(TPARM1(save_cursor), 1, outch);
9993 
9994               for (i = 1; i < nl + win.message_lines; i++)
9995               {
9996                 tputs(TPARM1(cursor_down), 1, outch);
9997                 tputs(TPARM1(clr_eol), 1, outch);
9998                 tputs(TPARM1(clr_bol), 1, outch);
9999               }
10000               tputs(TPARM1(restore_cursor), 1, outch);
10001             }
10002             else
10003             {
10004               for (i = 1; i < nl + win.message_lines; i++)
10005                 tputs(TPARM1(cursor_down), 1, outch);
10006               puts("");
10007             }
10008           }
10009 
10010           /* Restore the visibility of the cursor. */
10011           /* """"""""""""""""""""""""""""""""""""" */
10012           tputs(TPARM1(cursor_normal), 1, outch);
10013 
10014           if (buffer[0] == 3)
10015           {
10016             if (int_string != NULL)
10017               fprintf(old_stdout, "%s", int_string);
10018 
10019             /* Set the cursor at the start on the line an restore the */
10020             /* original terminal state before exiting.                */
10021             /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
10022             tputs(TPARM1(carriage_return), 1, outch);
10023             restore_term(fileno(stdin));
10024 
10025             if (int_as_in_shell)
10026               exit(128 + SIGINT);
10027           }
10028           else
10029             restore_term(fileno(stdin));
10030 
10031           exit(EXIT_SUCCESS);
10032 
10033         case 0x0c:
10034           /* Form feed (^L) is a traditional method to redraw a screen. */
10035           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10036           if (current < win.start || current > win.end)
10037             last_line = build_metadata(&term, count, &win);
10038 
10039           nl = disp_lines(&win, &toggles, current, count, search_mode,
10040                           &search_data, &term, last_line, tmp_word, &langinfo);
10041           break;
10042 
10043         case 'n':
10044         case ' ':
10045           /* n or the space bar has been pressed. */
10046           /* """""""""""""""""""""""""""""""""""" */
10047           if (search_mode != NONE)
10048             goto special_cmds_when_searching;
10049 
10050           if (matches_count > 0)
10051           {
10052             long pos = find_next_matching_word(matching_words_a, matches_count,
10053                                                current,
10054                                                &matching_word_cur_index);
10055             if (pos >= 0)
10056               current = pos;
10057 
10058             if (current < win.start || current > win.end)
10059               last_line = build_metadata(&term, count, &win);
10060 
10061             /* Set new first column to display. */
10062             /* """""""""""""""""""""""""""""""" */
10063             set_new_first_column(&win, &term);
10064 
10065             nl = disp_lines(&win, &toggles, current, count, search_mode,
10066                             &search_data, &term, last_line, tmp_word,
10067                             &langinfo);
10068           }
10069           break;
10070 
10071         case 'N':
10072           /* N has been pressed.*/
10073           /* """"""""""""""""""" */
10074           if (search_mode != NONE)
10075             goto special_cmds_when_searching;
10076 
10077           if (matches_count > 0)
10078           {
10079             long pos = find_prev_matching_word(matching_words_a, matches_count,
10080                                                current,
10081                                                &matching_word_cur_index);
10082             if (pos >= 0)
10083               current = pos;
10084           }
10085 
10086           if (current < win.start || current > win.end)
10087             last_line = build_metadata(&term, count, &win);
10088 
10089           /* Set new first column to display. */
10090           /* """""""""""""""""""""""""""""""" */
10091           set_new_first_column(&win, &term);
10092 
10093           nl = disp_lines(&win, &toggles, current, count, search_mode,
10094                           &search_data, &term, last_line, tmp_word, &langinfo);
10095           break;
10096 
10097         case 's':
10098           /* s has been pressed. */
10099           /* """"""""""""""""""" */
10100           if (search_mode != NONE)
10101             goto special_cmds_when_searching;
10102 
10103           if (matches_count > 0)
10104           {
10105             long pos;
10106 
10107             if (best_matches_count > 0)
10108               pos = find_next_matching_word(best_matching_words_a,
10109                                             best_matches_count, current,
10110                                             &matching_word_cur_index);
10111             else
10112               pos = find_next_matching_word(matching_words_a, matches_count,
10113                                             current, &matching_word_cur_index);
10114 
10115             if (pos >= 0)
10116               current = pos;
10117 
10118             if (current < win.start || current > win.end)
10119               last_line = build_metadata(&term, count, &win);
10120 
10121             /* Set new first column to display. */
10122             /* """""""""""""""""""""""""""""""" */
10123             set_new_first_column(&win, &term);
10124 
10125             nl = disp_lines(&win, &toggles, current, count, search_mode,
10126                             &search_data, &term, last_line, tmp_word,
10127                             &langinfo);
10128           }
10129           break;
10130 
10131         case 'S':
10132           /* S has been pressed. */
10133           /* """"""""""""""""""" */
10134           if (search_mode != NONE)
10135             goto special_cmds_when_searching;
10136 
10137           if (matches_count > 0)
10138           {
10139             long pos;
10140 
10141             if (best_matches_count > 0)
10142               pos = find_prev_matching_word(best_matching_words_a,
10143                                             best_matches_count, current,
10144                                             &matching_word_cur_index);
10145             else
10146               pos = find_prev_matching_word(matching_words_a, matches_count,
10147                                             current, &matching_word_cur_index);
10148 
10149             if (pos >= 0)
10150               current = pos;
10151           }
10152 
10153           if (current < win.start || current > win.end)
10154             last_line = build_metadata(&term, count, &win);
10155 
10156           /* Set new first column to display. */
10157           /* """""""""""""""""""""""""""""""" */
10158           set_new_first_column(&win, &term);
10159 
10160           nl = disp_lines(&win, &toggles, current, count, search_mode,
10161                           &search_data, &term, last_line, tmp_word, &langinfo);
10162           break;
10163 
10164         enter:
10165         case 0x0d: /* CR */
10166         {
10167           /* <Enter> has been pressed. */
10168           /* """"""""""""""""""""""""" */
10169 
10170           int        extra_lines;
10171           char *     output_str;
10172           output_t * output_node;
10173 
10174           int width = 0;
10175 
10176           wchar_t * w;
10177           long      i; /* Generic index in this block. */
10178 
10179           if (help_mode)
10180             nl = disp_lines(&win, &toggles, current, count, search_mode,
10181                             &search_data, &term, last_line, tmp_word,
10182                             &langinfo);
10183 
10184           if (search_mode != NONE)
10185           {
10186             /* Cancel the search timer. */
10187             /* """""""""""""""""""""""" */
10188             search_timer = 0;
10189 
10190             search_mode               = NONE;
10191             search_data.only_starting = 0;
10192             search_data.only_ending   = 0;
10193 
10194             nl = disp_lines(&win, &toggles, current, count, search_mode,
10195                             &search_data, &term, last_line, tmp_word,
10196                             &langinfo);
10197 
10198             if (!toggles.enter_val_in_search)
10199               break;
10200           }
10201 
10202           /* Erase or jump after the window before printing the */
10203           /* selected string.                                   */
10204           /* """""""""""""""""""""""""""""""""""""""""""""""""" */
10205           if (toggles.del_line)
10206           {
10207             for (i = 0; i < win.message_lines; i++)
10208               tputs(TPARM1(cursor_up), 1, outch);
10209 
10210             tputs(TPARM1(clr_eol), 1, outch);
10211             tputs(TPARM1(clr_bol), 1, outch);
10212             tputs(TPARM1(save_cursor), 1, outch);
10213 
10214             for (i = 1; i < nl + win.message_lines; i++)
10215             {
10216               tputs(TPARM1(cursor_down), 1, outch);
10217               tputs(TPARM1(clr_eol), 1, outch);
10218               tputs(TPARM1(clr_bol), 1, outch);
10219             }
10220 
10221             tputs(TPARM1(restore_cursor), 1, outch);
10222           }
10223           else
10224           {
10225             for (i = 1; i < nl; i++)
10226               tputs(TPARM1(cursor_down), 1, outch);
10227             puts("");
10228           }
10229 
10230           /* When a timeout of type WORD is set, prints the specified word */
10231           /* else prints the current selected entries.                     */
10232           /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10233           if (timeout.initial_value > 0 && timeout.remain == 0
10234               && timeout.mode == WORD)
10235             fprintf(old_stdout, "%s", timeout_word);
10236           else
10237           {
10238             char * num_str;
10239             char * str;
10240 
10241             if (toggles.taggable)
10242             {
10243               ll_t *      output_list = ll_new();
10244               ll_node_t * node;
10245 
10246               /* When using -P, updates the tagging order of this word to */
10247               /* make sure that the output will be correctly sorted.      */
10248               /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10249               if (toggles.pinable)
10250                 word_a[current].tag_order = next_tag_nb++;
10251 
10252               for (wi = 0; wi < count; wi++)
10253               {
10254                 if (word_a[wi].is_tagged || wi == current)
10255                 {
10256                   /* If the -p option is not used we do not take into      */
10257                   /* account an untagged word under the cursor if at least */
10258                   /* on word is tagged.                                    */
10259                   /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
10260                   if (wi == current && tagged_words > 0 && !toggles.autotag
10261                       && !word_a[wi].is_tagged)
10262                     continue;
10263 
10264                   /* In tagged mode, do not autotag the word under the cursor */
10265                   /* if toggles.noautotag is set and no word are tagged.      */
10266                   /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10267                   if (tagged_words == 0 && toggles.taggable
10268                       && toggles.noautotag)
10269                     continue;
10270 
10271                   /* Chose the original string to print if the current one */
10272                   /* has been altered by a possible expansion.             */
10273                   /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
10274                   output_node = xmalloc(sizeof(output_t));
10275 
10276                   if (word_a[wi].orig != NULL)
10277                     str = word_a[wi].orig;
10278                   else
10279                     str = word_a[wi].str;
10280 
10281                   if (word_a[wi].is_numbered && daccess.num_sep)
10282                   {
10283                     num_str = xstrndup(str + 1, daccess.length);
10284 
10285                     ltrim(num_str, " ");
10286                     rtrim(num_str, " ", 0);
10287 
10288                     output_node->output_str = concat(num_str, daccess.num_sep,
10289                                                      str + daccess.flength,
10290                                                      (char *)0);
10291 
10292                     free(num_str);
10293                   }
10294                   else
10295                     output_node->output_str = xstrdup(str + daccess.flength);
10296 
10297                   output_node->order = word_a[wi].tag_order;
10298 
10299                   /* Trim the spaces if -k is not given. */
10300                   /* """"""""""""""""""""""""""""""""""" */
10301                   if (!toggles.keep_spaces)
10302                   {
10303                     ltrim(output_node->output_str, " \t");
10304                     rtrim(output_node->output_str, " \t", 0);
10305                   }
10306 
10307                   ll_append(output_list, output_node);
10308                 }
10309               }
10310 
10311               /* If nothing is selected, exist without printing anything. */
10312               /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10313               if (output_list->head == NULL)
10314                 goto exit;
10315 
10316               /* If -P is in use, then sort the output list. */
10317               /* """"""""""""""""""""""""""""""""""""""""""" */
10318               if (toggles.pinable)
10319                 ll_sort(output_list, tag_comp, tag_swap);
10320 
10321               /* And print them. */
10322               /* """"""""""""""" */
10323               node = output_list->head;
10324               while (node->next != NULL)
10325               {
10326                 str = ((output_t *)(node->data))->output_str;
10327 
10328                 fprintf(old_stdout, "%s", str);
10329                 width += wcswidth((w = utf8_strtowcs(str)), 65535);
10330                 free(w);
10331                 free(str);
10332                 free(node->data);
10333 
10334                 if (win.sel_sep != NULL)
10335                 {
10336                   fprintf(old_stdout, "%s", win.sel_sep);
10337                   width += wcswidth((w = utf8_strtowcs(win.sel_sep)), 65535);
10338                   free(w);
10339                 }
10340                 else
10341                 {
10342                   fprintf(old_stdout, " ");
10343                   width++;
10344                 }
10345 
10346                 node = node->next;
10347               }
10348 
10349               str = ((output_t *)(node->data))->output_str;
10350               fprintf(old_stdout, "%s", str);
10351               width += wcswidth((w = utf8_strtowcs(str)), 65535);
10352               free(w);
10353               free(str);
10354               free(node->data);
10355             }
10356             else
10357             {
10358               /* Chose the original string to print if the current one has */
10359               /* been altered by a possible expansion.                     */
10360               /* Once this made, print it.                                 */
10361               /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10362               if (word_a[current].orig != NULL)
10363                 str = word_a[current].orig;
10364               else
10365                 str = word_a[current].str;
10366 
10367               if (word_a[current].is_numbered && daccess.num_sep)
10368               {
10369                 num_str = xstrndup(str + 1, daccess.length);
10370 
10371                 ltrim(num_str, " ");
10372                 rtrim(num_str, " ", 0);
10373 
10374                 output_str = concat(num_str, daccess.num_sep,
10375                                     str + daccess.flength, (char *)0);
10376 
10377                 free(num_str);
10378               }
10379               else
10380                 output_str = str + daccess.flength;
10381 
10382               /* Trim the spaces if -k is not given. */
10383               /* """"""""""""""""""""""""""""""""""" */
10384               if (!toggles.keep_spaces)
10385               {
10386                 ltrim(output_str, " \t");
10387                 rtrim(output_str, " \t", 0);
10388               }
10389 
10390               width = wcswidth((w = utf8_strtowcs(output_str)), 65535);
10391               free(w);
10392 
10393               /* And print it. */
10394               /* """"""""""""" */
10395               fprintf(old_stdout, "%s", output_str);
10396             }
10397 
10398             /* If the output stream is a terminal. */
10399             /* """"""""""""""""""""""""""""""""""" */
10400             if (isatty(old_fd1))
10401             {
10402               /* width is (in term of terminal columns) the size of the    */
10403               /* string to be displayed.                                   */
10404               /*                                                           */
10405               /* With that information, count the number of terminal lines */
10406               /* printed.                                                  */
10407               /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10408               extra_lines = width / term.ncolumns;
10409 
10410               /* Clean the printed line and all the extra lines used. */
10411               /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
10412               tputs(TPARM1(delete_line), 1, outch);
10413 
10414               for (i = 0; i < extra_lines; i++)
10415               {
10416                 tputs(TPARM1(cursor_up), 1, outch);
10417                 tputs(TPARM1(clr_eol), 1, outch);
10418                 tputs(TPARM1(clr_bol), 1, outch);
10419               }
10420             }
10421           }
10422 
10423         exit:
10424 
10425           /* Restore the visibility of the cursor. */
10426           /* """"""""""""""""""""""""""""""""""""" */
10427           tputs(TPARM1(cursor_normal), 1, outch);
10428 
10429           /* Set the cursor at the start on the line an restore the */
10430           /* original terminal state before exiting.                */
10431           /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
10432           tputs(TPARM1(carriage_return), 1, outch);
10433           restore_term(fileno(stdin));
10434 
10435           exit(EXIT_SUCCESS);
10436         }
10437 
10438         ksol:
10439           /* Go to the start of the line. */
10440           /* """""""""""""""""""""""""""" */
10441           if (search_mode != NONE)
10442             search_mode = NONE;
10443 
10444           /* Fall through. */
10445           /* """"""""""""" */
10446 
10447         case 'H':
10448           if (search_mode == NONE)
10449           {
10450             current = first_word_in_line_a[line_nb_of_word_a[current]];
10451 
10452             while (!word_a[current].is_selectable)
10453               current++;
10454 
10455             win.first_column = 0;
10456             set_new_first_column(&win, &term);
10457 
10458             nl = disp_lines(&win, &toggles, current, count, search_mode,
10459                             &search_data, &term, last_line, tmp_word,
10460                             &langinfo);
10461           }
10462           else
10463             goto special_cmds_when_searching;
10464 
10465           break;
10466 
10467         kl:
10468           /* Cursor Left key has been pressed. */
10469           /* """"""""""""""""""""""""""""""""" */
10470           if (search_mode != NONE)
10471             search_mode = NONE;
10472 
10473           /* Fall through. */
10474           /* """"""""""""" */
10475 
10476         case 'h':
10477           if (search_mode == NONE)
10478             move_left(&win, &term, &toggles, &search_data, &langinfo, &nl,
10479                       last_line, tmp_word);
10480           else
10481             goto special_cmds_when_searching;
10482 
10483           break;
10484 
10485         keol:
10486           /* Go to the end of the line. */
10487           /* """""""""""""""""""""""""" */
10488           if (search_mode != NONE)
10489             search_mode = NONE;
10490 
10491           /* Fall through. */
10492           /* """"""""""""" */
10493 
10494         case 'L':
10495           if (search_mode == NONE)
10496           {
10497             long cur_line = line_nb_of_word_a[current];
10498 
10499             current          = get_line_last_word(cur_line, last_line);
10500             win.first_column = win.real_max_width - 1 - (term.ncolumns - 3);
10501 
10502             while (!word_a[current].is_selectable)
10503               current--;
10504 
10505             set_new_first_column(&win, &term);
10506 
10507             nl = disp_lines(&win, &toggles, current, count, search_mode,
10508                             &search_data, &term, last_line, tmp_word,
10509                             &langinfo);
10510           }
10511           else
10512             goto special_cmds_when_searching;
10513 
10514           break;
10515 
10516         kr:
10517           /* Right key has been pressed. */
10518           /* """"""""""""""""""""""""""" */
10519           if (search_mode != NONE)
10520             search_mode = NONE;
10521 
10522           /* Fall through. */
10523           /* """"""""""""" */
10524 
10525         case 'l':
10526           if (search_mode == NONE)
10527             move_right(&win, &term, &toggles, &search_data, &langinfo, &nl,
10528                        last_line, tmp_word);
10529           else
10530             goto special_cmds_when_searching;
10531 
10532           break;
10533 
10534         case 0x0b:
10535           /* ^K key has been pressed. */
10536           /* """""""""""""""""""""""" */
10537           goto kschome;
10538 
10539         case 'K':
10540           if (search_mode != NONE)
10541             goto special_cmds_when_searching;
10542 
10543         kpp:
10544           /* PgUp key has been pressed. */
10545           /* """""""""""""""""""""""""" */
10546           page = win.max_lines;
10547 
10548         ku:
10549           /* Cursor Up key has been pressed. */
10550           /* """"""""""""""""""""""""""""""" */
10551           if (search_mode != NONE)
10552             search_mode = NONE;
10553 
10554           /* Fall through. */
10555           /* """"""""""""" */
10556 
10557         case 'k':
10558           if (search_mode == NONE)
10559             move_up(&win, &term, &toggles, &search_data, &langinfo, &nl, page,
10560                     first_selectable, last_line, tmp_word);
10561           else
10562             goto special_cmds_when_searching;
10563 
10564           break;
10565 
10566         kschome:
10567           /* Go to the first selectable word. */
10568           /* """""""""""""""""""""""""""""""" */
10569           current = 0;
10570 
10571           if (search_mode != NONE)
10572             search_mode = NONE;
10573 
10574           /* Find the first selectable word. */
10575           /* """"""""""""""""""""""""""""""" */
10576           while (!word_a[current].is_selectable)
10577             current++;
10578 
10579           if (current < win.start || current > win.end)
10580             last_line = build_metadata(&term, count, &win);
10581 
10582           /* In column mode we need to take care of the */
10583           /* horizontal scrolling.                      */
10584           /* """""""""""""""""""""""""""""""""""""""""" */
10585           if (win.col_mode || win.line_mode)
10586           {
10587             long pos;
10588 
10589             /* Adjust win.first_column if the cursor is */
10590             /* no more visible.                         */
10591             /* """""""""""""""""""""""""""""""""""""""" */
10592             pos = first_word_in_line_a[line_nb_of_word_a[current]];
10593 
10594             while (word_a[current].end - word_a[pos].start >= term.ncolumns - 3)
10595               pos++;
10596 
10597             win.first_column = word_a[pos].start;
10598           }
10599 
10600           nl = disp_lines(&win, &toggles, current, count, search_mode,
10601                           &search_data, &term, last_line, tmp_word, &langinfo);
10602           break;
10603 
10604         case 0x0a:
10605           /* ^J key has been pressed. */
10606           /* """""""""""""""""""""""" */
10607           goto kscend;
10608 
10609         case 'J':
10610           if (search_mode != NONE)
10611             goto special_cmds_when_searching;
10612 
10613         knp:
10614           /* PgDn key has been pressed. */
10615           /* """""""""""""""""""""""""" */
10616           page = win.max_lines;
10617 
10618         kd:
10619           /* Cursor Down key has been pressed. */
10620           /* """"""""""""""""""""""""""""""""" */
10621           if (search_mode != NONE)
10622             search_mode = NONE;
10623 
10624           /* Fall through. */
10625           /* """"""""""""" */
10626 
10627         case 'j':
10628           if (search_mode == NONE)
10629             move_down(&win, &term, &toggles, &search_data, &langinfo, &nl, page,
10630                       last_selectable, last_line, tmp_word);
10631           else
10632             goto special_cmds_when_searching;
10633 
10634           break;
10635 
10636         kscend:
10637           /* Go to the last selectable word. */
10638           /* """"""""""""""""""""""""""""""" */
10639           current = count - 1;
10640 
10641           if (search_mode != NONE)
10642             search_mode = NONE;
10643 
10644           /* Find the first selectable word. */
10645           /* """"""""""""""""""""""""""""""" */
10646           while (!word_a[current].is_selectable)
10647             current--;
10648 
10649           if (current < win.start || current > win.end)
10650             last_line = build_metadata(&term, count, &win);
10651 
10652           /* In column mode we need to take care of the */
10653           /* horizontal scrolling.                      */
10654           /* """""""""""""""""""""""""""""""""""""""""" */
10655           if (win.col_mode || win.line_mode)
10656           {
10657             long pos;
10658 
10659             /* Adjust win.first_column if the cursor is no more visible. */
10660             /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10661             pos = first_word_in_line_a[line_nb_of_word_a[current]];
10662 
10663             while (word_a[current].end - word_a[pos].start >= term.ncolumns - 3)
10664               pos++;
10665 
10666             win.first_column = word_a[pos].start;
10667           }
10668 
10669           nl = disp_lines(&win, &toggles, current, count, search_mode,
10670                           &search_data, &term, last_line, tmp_word, &langinfo);
10671           break;
10672 
10673         case '/':
10674           /* Generic search method according the misc settings. */
10675           /* """""""""""""""""""""""""""""""""""""""""""""""""" */
10676           if (misc.default_search_method == PREFIX)
10677             goto prefix_method;
10678           else if (misc.default_search_method == SUBSTRING)
10679             goto substring_method;
10680           else if (misc.default_search_method == FUZZY)
10681             goto fuzzy_method;
10682 
10683           break;
10684 
10685         case '~':
10686         case '*':
10687         /* ~ or * key has been pressed        */
10688         /* (start of a fuzzy search session). */
10689         /* """""""""""""""""""""""""""""""""" */
10690         fuzzy_method:
10691 
10692           if (search_mode == NONE)
10693           {
10694             if (!toggles.incremental_search)
10695               reset_search_buffer(&win, &search_data, &timers, &toggles, &term,
10696                                   &daccess, &langinfo, last_line, tmp_word,
10697                                   word_real_max_size);
10698 
10699             /* Set the search timer. */
10700             /* """"""""""""""""""""" */
10701             search_timer = timers.search; /* default 10 s. */
10702 
10703             search_mode = FUZZY;
10704 
10705             if (old_search_mode != FUZZY)
10706             {
10707               old_search_mode = FUZZY;
10708               clean_matches(&search_data, word_real_max_size);
10709             }
10710 
10711             nl = disp_lines(&win, &toggles, current, count, search_mode,
10712                             &search_data, &term, last_line, tmp_word,
10713                             &langinfo);
10714           }
10715           else
10716             goto special_cmds_when_searching;
10717 
10718           break;
10719 
10720         case '\'':
10721         case '\"':
10722         /* ' or " key has been pressed            */
10723         /* (start of a substring search session). */
10724         /* """""""""""""""""""""""""""""""""""""" */
10725         substring_method:
10726 
10727           if (search_mode == NONE)
10728           {
10729             if (!toggles.incremental_search)
10730               reset_search_buffer(&win, &search_data, &timers, &toggles, &term,
10731                                   &daccess, &langinfo, last_line, tmp_word,
10732                                   word_real_max_size);
10733 
10734             /* Set the search timer. */
10735             /* """"""""""""""""""""" */
10736             search_timer = timers.search; /* default 10 s. */
10737 
10738             search_mode = SUBSTRING;
10739 
10740             if (old_search_mode != SUBSTRING)
10741             {
10742               old_search_mode = SUBSTRING;
10743               clean_matches(&search_data, word_real_max_size);
10744             }
10745 
10746             nl = disp_lines(&win, &toggles, current, count, search_mode,
10747                             &search_data, &term, last_line, tmp_word,
10748                             &langinfo);
10749           }
10750           else
10751             goto special_cmds_when_searching;
10752 
10753           break;
10754 
10755         case '=':
10756         case '^':
10757         /* ^ or = key has been pressed         */
10758         /* (start of a prefix search session). */
10759         /* """"""""""""""""""""""""""""""""""" */
10760         prefix_method:
10761 
10762           if (search_mode == NONE)
10763           {
10764             if (!toggles.incremental_search)
10765               reset_search_buffer(&win, &search_data, &timers, &toggles, &term,
10766                                   &daccess, &langinfo, last_line, tmp_word,
10767                                   word_real_max_size);
10768 
10769             /* Set the search timer. */
10770             /* """"""""""""""""""""" */
10771             search_timer = timers.search; /* default 10 s. */
10772 
10773             search_mode = PREFIX;
10774 
10775             if (old_search_mode != PREFIX)
10776             {
10777               old_search_mode = PREFIX;
10778               clean_matches(&search_data, word_real_max_size);
10779             }
10780 
10781             nl = disp_lines(&win, &toggles, current, count, search_mode,
10782                             &search_data, &term, last_line, tmp_word,
10783                             &langinfo);
10784             break;
10785           }
10786           else
10787             goto special_cmds_when_searching;
10788 
10789           break;
10790 
10791         kins:
10792           /* The INS key has been pressed to tag a word if */
10793           /* tagging is enabled.                           */
10794           /* """"""""""""""""""""""""""""""""""""""""""""" */
10795           if (toggles.taggable)
10796           {
10797             if (word_a[current].is_tagged == 0)
10798             {
10799               tagged_words++;
10800               word_a[current].is_tagged = 1;
10801 
10802               if (toggles.pinable)
10803                 word_a[current].tag_order = next_tag_nb++;
10804 
10805               nl = disp_lines(&win, &toggles, current, count, search_mode,
10806                               &search_data, &term, last_line, tmp_word,
10807                               &langinfo);
10808             }
10809           }
10810           break;
10811 
10812         kdel:
10813           /* The DEL key has been pressed to untag a word if */
10814           /* tagging is enabled.                             */
10815           /* """"""""""""""""""""""""""""""""""""""""""""""" */
10816           if (toggles.taggable)
10817           {
10818             if (word_a[current].is_tagged == 1)
10819             {
10820               word_a[current].is_tagged = 0;
10821               tagged_words--;
10822 
10823               nl = disp_lines(&win, &toggles, current, count, search_mode,
10824                               &search_data, &term, last_line, tmp_word,
10825                               &langinfo);
10826             }
10827           }
10828           break;
10829 
10830         case 't':
10831           /* t has been pressed to tag/untag a word if */
10832           /* tagging is enabled.                       */
10833           /* """"""""""""""""""""""""""""""""""""""""" */
10834           if (search_mode == NONE)
10835           {
10836             if (toggles.taggable)
10837             {
10838               if (word_a[current].is_tagged)
10839               {
10840                 word_a[current].is_tagged = 0;
10841                 tagged_words--;
10842               }
10843               else
10844               {
10845                 word_a[current].is_tagged = 1;
10846                 tagged_words++;
10847 
10848                 if (toggles.pinable)
10849                   word_a[current].tag_order = next_tag_nb++;
10850               }
10851               nl = disp_lines(&win, &toggles, current, count, search_mode,
10852                               &search_data, &term, last_line, tmp_word,
10853                               &langinfo);
10854             }
10855           }
10856           else
10857             goto special_cmds_when_searching;
10858           break;
10859 
10860         case 'T':
10861           /* T has been pressed to tag all matching words resulting */
10862           /* from a previous search if tagging is enabled.          */
10863           /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
10864           if (search_mode == NONE)
10865           {
10866             if (toggles.taggable)
10867             {
10868               long i, n;
10869 
10870               for (i = 0; i < matches_count; i++)
10871               {
10872                 n = matching_words_a[i];
10873 
10874                 if (!word_a[n].is_tagged)
10875                 {
10876                   word_a[n].is_tagged = 1;
10877                   tagged_words++;
10878 
10879                   if (toggles.pinable)
10880                     word_a[n].tag_order = next_tag_nb++;
10881                 }
10882               }
10883               nl = disp_lines(&win, &toggles, current, count, search_mode,
10884                               &search_data, &term, last_line, tmp_word,
10885                               &langinfo);
10886             }
10887           }
10888           else
10889             goto special_cmds_when_searching;
10890           break;
10891 
10892         case 'U':
10893           /* U has been pressed to untag all matching words resulting */
10894           /* from a previous search if tagging is enabled.            */
10895           /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10896           if (search_mode == NONE)
10897           {
10898             if (toggles.taggable)
10899             {
10900               long i, n;
10901 
10902               for (i = 0; i < matches_count; i++)
10903               {
10904                 n = matching_words_a[i];
10905 
10906                 if (word_a[n].is_tagged)
10907                 {
10908                   word_a[n].is_tagged = 0;
10909                   tagged_words--;
10910                 }
10911               }
10912               nl = disp_lines(&win, &toggles, current, count, search_mode,
10913                               &search_data, &term, last_line, tmp_word,
10914                               &langinfo);
10915             }
10916           }
10917           else
10918             goto special_cmds_when_searching;
10919           break;
10920 
10921         case '0':
10922         case '1':
10923         case '2':
10924         case '3':
10925         case '4':
10926         case '5':
10927         case '6':
10928         case '7':
10929         case '8':
10930         case '9':
10931           /* A digit has been pressed to build a number to be used for */
10932           /* A direct acces to a words if direct access is enabled.    */
10933           /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10934           {
10935             if (search_mode == NONE && daccess.mode != DA_TYPE_NONE)
10936             {
10937               wchar_t * w;
10938               long *    pos;
10939 
10940               /* Set prev_current to the initial current word to be    */
10941               /* able to return here if the first direct access fails. */
10942               /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
10943               if (daccess_stack_head == 0)
10944                 prev_current = current;
10945 
10946               if (daccess_stack_head == daccess.length)
10947                 break;
10948 
10949               daccess_stack[daccess_stack_head] = buffer[0];
10950               daccess_stack_head++;
10951               w   = utf8_strtowcs(daccess_stack);
10952               pos = tst_search(tst_daccess, w);
10953               free(w);
10954 
10955               if (pos != NULL)
10956               {
10957                 current = *pos;
10958 
10959                 if (current < win.start || current > win.end)
10960                   last_line = build_metadata(&term, count, &win);
10961 
10962                 /* Set new first column to display. */
10963                 /* """""""""""""""""""""""""""""""" */
10964                 set_new_first_column(&win, &term);
10965 
10966                 nl = disp_lines(&win, &toggles, current, count, search_mode,
10967                                 &search_data, &term, last_line, tmp_word,
10968                                 &langinfo);
10969               }
10970               else
10971               {
10972                 if (current != prev_current)
10973                 {
10974                   current = prev_current;
10975 
10976                   if (current < win.start || current > win.end)
10977                     last_line = build_metadata(&term, count, &win);
10978 
10979                   /* Set new first column to display. */
10980                   /* """""""""""""""""""""""""""""""" */
10981                   set_new_first_column(&win, &term);
10982 
10983                   nl = disp_lines(&win, &toggles, current, count, search_mode,
10984                                   &search_data, &term, last_line, tmp_word,
10985                                   &langinfo);
10986                 }
10987               }
10988 
10989               daccess_timer = timers.direct_access;
10990             }
10991             else
10992               goto special_cmds_when_searching;
10993           }
10994           break;
10995 
10996         case 0x08: /* ^H */
10997         case 0x7f: /* BS */
10998           /* backspace/CTRL-H management. */
10999           /* """""""""""""""""""""""""""" */
11000           {
11001             long i;
11002 
11003             if (daccess_stack_head > 0)
11004               daccess_stack[--daccess_stack_head] = '\0';
11005 
11006             if (search_mode != NONE)
11007             {
11008               if (search_data.utf8_len > 0)
11009               {
11010                 char * prev;
11011 
11012                 prev = utf8_prev(search_data.buf,
11013                                  search_data.buf + search_data.len - 1);
11014 
11015                 if (search_data.utf8_len == search_data.fuzzy_err_pos - 1)
11016                 {
11017                   search_data.fuzzy_err     = 0;
11018                   search_data.fuzzy_err_pos = -1;
11019                 }
11020                 search_data.utf8_len--;
11021 
11022                 if (prev)
11023                 {
11024                   *(utf8_next(prev)) = '\0';
11025                   search_data.len    = prev - search_data.buf + 1;
11026                 }
11027                 else
11028                 {
11029                   *search_data.buf = '\0';
11030                   search_data.len  = 0;
11031 
11032                   for (i = 0; i < matches_count; i++)
11033                   {
11034                     long n = matching_words_a[i];
11035 
11036                     word_a[n].is_matching = 0;
11037 
11038                     memset(word_a[n].bitmap, '\0',
11039                            (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
11040                   }
11041 
11042                   matches_count = 0;
11043 
11044                   nl = disp_lines(&win, &toggles, current, count, search_mode,
11045                                   &search_data, &term, last_line, tmp_word,
11046                                   &langinfo);
11047                 }
11048               }
11049               else
11050                 my_beep(&toggles);
11051 
11052               if (search_data.utf8_len > 0)
11053                 goto special_cmds_when_searching;
11054               else
11055                 /* When there is only one glyph in the search list in       */
11056                 /* FUZZY and SUBSTRING mode then all is already done except */
11057                 /* the cleanup of the first level of the tst_search_list.   */
11058                 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11059                 if (search_mode != PREFIX)
11060               {
11061                 sub_tst_t * sub_tst_data;
11062                 ll_node_t * node;
11063 
11064                 node         = tst_search_list->tail;
11065                 sub_tst_data = (sub_tst_t *)(node->data);
11066 
11067                 search_data.fuzzy_err = 0;
11068 
11069                 sub_tst_data->count = 0;
11070               }
11071             }
11072           }
11073 
11074           break;
11075 
11076         case '?':
11077           /* Help mode. */
11078           /* """""""""" */
11079           if (search_mode == NONE)
11080           {
11081             help(&win, &term, last_line, &toggles);
11082             help_mode = 1;
11083 
11084             /* Arm the help timer. */
11085             /* """"""""""""""""""" */
11086             help_timer = timers.help; /* default 15 s. */
11087           }
11088           else
11089             goto special_cmds_when_searching;
11090           break;
11091 
11092         special_cmds_when_searching:
11093         default:
11094         {
11095           int                c; /* byte index in the scancode string .*/
11096           sub_tst_t *        sub_tst_data;
11097           unsigned long long index;
11098           long               i;
11099 
11100           if (search_mode != NONE)
11101           {
11102             long        old_len      = search_data.len;
11103             long        old_utf8_len = search_data.utf8_len;
11104             ll_node_t * node;
11105             wchar_t *   ws;
11106 
11107             /* Copy all the bytes included in the key press to buffer. */
11108             /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
11109             if (buffer[0] != 0x08 && buffer[0] != 0x7f) /* Backspace. */
11110             {
11111               /* The only case where we have to manage backspace hits */
11112               /* here is if the user has entered them in fuzzy search */
11113               /* mode. As the search buffer has already been amended, */
11114               /* we do not have to update the search buffer again.    */
11115               /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
11116               for (c = 0; c < sc
11117                           && search_data.utf8_len
11118                                < word_real_max_size - daccess.flength;
11119                    c++)
11120                 search_data.buf[search_data.len++] = buffer[c];
11121 
11122               /* Update the glyph array with the content of the search */
11123               /* buffer.                                               */
11124               /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
11125               if (search_data.utf8_len < word_real_max_size - daccess.flength)
11126               {
11127                 search_data.utf8_off_a[search_data.utf8_len] = old_len;
11128                 search_data.utf8_len_a[search_data.utf8_len] = search_data.len
11129                                                                - old_len;
11130                 search_data.utf8_len++;
11131               }
11132             }
11133 
11134             /* Restart the search timer. */
11135             /* """"""""""""""""""""""""" */
11136             search_timer = timers.search; /* default 10 s. */
11137 
11138             if (search_mode == PREFIX)
11139             {
11140               ws = utf8_strtowcs(search_data.buf);
11141 
11142               /* Purge the matching words list. */
11143               /* """""""""""""""""""""""""""""" */
11144               for (i = 0; i < matches_count; i++)
11145               {
11146                 long n = matching_words_a[i];
11147 
11148                 word_a[n].is_matching = 0;
11149 
11150                 memset(word_a[n].bitmap, '\0',
11151                        (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
11152               }
11153 
11154               matches_count = 0;
11155 
11156               tst_prefix_search(tst_word, ws, tst_cb);
11157 
11158               /* Latches_count is updated by tst_cb. */
11159               /* """"""""""""""""""""""""""""""""""" */
11160               if (matches_count > 0)
11161               {
11162                 if (search_data.len == old_len && matches_count == 1
11163                     && buffer[0] != 0x08 && buffer[0] != 0x7f)
11164                   my_beep(&toggles);
11165                 else
11166                 {
11167                   /* Adjust the bitmap to the ending version. */
11168                   /* """""""""""""""""""""""""""""""""""""""" */
11169                   update_bitmaps(search_mode, &search_data, NO_AFFINITY);
11170 
11171                   current = matching_words_a[0];
11172 
11173                   if (current < win.start || current > win.end)
11174                     last_line = build_metadata(&term, count, &win);
11175 
11176                   /* Set new first column to display. */
11177                   /* """""""""""""""""""""""""""""""" */
11178                   set_new_first_column(&win, &term);
11179 
11180                   nl = disp_lines(&win, &toggles, current, count, search_mode,
11181                                   &search_data, &term, last_line, tmp_word,
11182                                   &langinfo);
11183                 }
11184               }
11185               else
11186               {
11187                 my_beep(&toggles);
11188 
11189                 search_data.len                  = old_len;
11190                 search_data.utf8_len             = old_utf8_len;
11191                 search_data.buf[search_data.len] = '\0';
11192               }
11193             }
11194             else if (search_mode == FUZZY)
11195             {
11196               /* tst_search_list: [sub_tst_t *] -> [sub_tst_t *]...     */
11197               /*                    ^               ^                   */
11198               /*                    |               |                   */
11199               /*                  level 1         level_2               */
11200               /*                                                        */
11201               /* Each sub_tst_t * points to a data structure including  */
11202               /* a sorted array to nodes in the words tst.              */
11203               /* Each of these node starts a matching candidate.        */
11204               /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
11205               wchar_t * w = utf8_strtowcs(search_data.buf + old_len);
11206 
11207               /* zero previous matching indicators. */
11208               /* """""""""""""""""""""""""""""""""" */
11209               for (i = 0; i < matches_count; i++)
11210               {
11211                 long n = matching_words_a[i];
11212 
11213                 word_a[n].is_matching = 0;
11214 
11215                 memset(word_a[n].bitmap, '\0',
11216                        (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
11217               }
11218 
11219               matches_count = 0;
11220 
11221               if (buffer[0] == 0x08 || buffer[0] == 0x7f) /* Backspace */
11222               {
11223                 node         = tst_search_list->tail;
11224                 sub_tst_data = (sub_tst_t *)(node->data);
11225 
11226                 sub_tst_data->count = 0;
11227 
11228                 if (tst_search_list->len > 0)
11229                 {
11230                   free(sub_tst_data->array);
11231                   free(sub_tst_data);
11232 
11233                   ll_delete(tst_search_list, tst_search_list->tail);
11234                 }
11235 
11236                 if (search_data.utf8_len == search_data.fuzzy_err_pos - 1)
11237                 {
11238                   search_data.fuzzy_err     = 0;
11239                   search_data.fuzzy_err_pos = -1;
11240                 }
11241               }
11242               else
11243               {
11244                 if (search_data.utf8_len == 1)
11245                 {
11246                   /* Search all the sub-tst trees having the searched     */
11247                   /* character as children, the resulting sub-tst are put */
11248                   /* in the sub tst array attached to the currently       */
11249                   /* searched symbol.                                     */
11250                   /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
11251                   tst_fuzzy_traverse(tst_word, NULL, 0, w[0]);
11252 
11253                   node         = tst_search_list->tail;
11254                   sub_tst_data = (sub_tst_t *)(node->data);
11255                   if (sub_tst_data->count == 0)
11256                   {
11257                     my_beep(&toggles);
11258 
11259                     search_data.len      = 0;
11260                     search_data.utf8_len = 0;
11261                     search_data.buf[0]   = '\0';
11262 
11263                     break;
11264                   }
11265                 }
11266                 else
11267                 {
11268                   /* Prevent the list to grow larger than the maximal */
11269                   /* word's length.                                   */
11270                   /* """""""""""""""""""""""""""""""""""""""""""""""" */
11271                   if (tst_search_list->len
11272                       < word_real_max_size - daccess.flength)
11273                   {
11274                     /* use the results in the level n-1 list to build the */
11275                     /* level n list.                                      */
11276                     /* """""""""""""""""""""""""""""""""""""""""""""""""" */
11277                     int rc;
11278 
11279                     sub_tst_t * tst_fuzzy_level_data;
11280 
11281                     tst_fuzzy_level_data = sub_tst_new();
11282 
11283                     ll_append(tst_search_list, tst_fuzzy_level_data);
11284 
11285                     node         = tst_search_list->tail->prev;
11286                     sub_tst_data = (sub_tst_t *)(node->data);
11287 
11288                     rc = 0;
11289                     for (index = 0; index < sub_tst_data->count; index++)
11290                       rc += tst_fuzzy_traverse(sub_tst_data->array[index], NULL,
11291                                                1, w[0]);
11292 
11293                     if (rc == 0)
11294                     {
11295                       free(tst_fuzzy_level_data->array);
11296                       free(tst_fuzzy_level_data);
11297 
11298                       ll_delete(tst_search_list, tst_search_list->tail);
11299 
11300                       search_data.fuzzy_err = 1;
11301                       if (search_data.fuzzy_err_pos == -1)
11302                         search_data.fuzzy_err_pos = search_data.utf8_len;
11303 
11304                       my_beep(&toggles);
11305 
11306                       search_data.len                  = old_len;
11307                       search_data.utf8_len             = old_utf8_len;
11308                       search_data.buf[search_data.len] = '\0';
11309                     }
11310                   }
11311                   else
11312                     my_beep(&toggles);
11313                 }
11314               }
11315               free(w);
11316 
11317               /* Process this level to mark the word found as a matching */
11318               /* word if any.                                            */
11319               /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
11320               node         = tst_search_list->tail;
11321               sub_tst_data = (sub_tst_t *)(node->data);
11322 
11323               for (index = 0; index < sub_tst_data->count; index++)
11324                 tst_traverse(sub_tst_data->array[index], set_matching_flag, 0);
11325 
11326               /* Update the bitmap and re-display the window. */
11327               /* """""""""""""""""""""""""""""""""""""""""""" */
11328               if (matches_count > 0)
11329               {
11330                 if (search_data.only_starting)
11331                   select_starting_matches(&win, &term, &search_data,
11332                                           &last_line);
11333                 else if (search_data.only_ending)
11334                   select_ending_matches(&win, &term, &search_data, &last_line);
11335                 else
11336                   /* Adjust the bitmap to the ending version. */
11337                   /* """""""""""""""""""""""""""""""""""""""" */
11338                   update_bitmaps(search_mode, &search_data, NO_AFFINITY);
11339 
11340                 current = matching_words_a[0];
11341 
11342                 if (current < win.start || current > win.end)
11343                   last_line = build_metadata(&term, count, &win);
11344 
11345                 /* Set new first column to display. */
11346                 /* """""""""""""""""""""""""""""""" */
11347                 set_new_first_column(&win, &term);
11348 
11349                 nl = disp_lines(&win, &toggles, current, count, search_mode,
11350                                 &search_data, &term, last_line, tmp_word,
11351                                 &langinfo);
11352               }
11353               else
11354                 my_beep(&toggles);
11355             }
11356             else /* SUBSTRING. */
11357             {
11358               wchar_t * w = utf8_strtowcs(search_data.buf);
11359 
11360               /* Purge the matching words list. */
11361               /* """""""""""""""""""""""""""""" */
11362               for (i = 0; i < matches_count; i++)
11363               {
11364                 long n = matching_words_a[i];
11365 
11366                 word_a[n].is_matching = 0;
11367 
11368                 memset(word_a[n].bitmap, '\0',
11369                        (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
11370               }
11371 
11372               matches_count = 0;
11373 
11374               if (search_data.utf8_len == 1)
11375               {
11376                 /* Search all the sub-tst trees having the searched     */
11377                 /* character as children, the resulting sub-tst are put */
11378                 /* in the level list corresponding to the letter order. */
11379                 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
11380                 tst_substring_traverse(tst_word, NULL, 0, w[0]);
11381 
11382                 node         = tst_search_list->tail;
11383                 sub_tst_data = (sub_tst_t *)(node->data);
11384 
11385                 for (index = 0; index < sub_tst_data->count; index++)
11386                   tst_traverse(sub_tst_data->array[index], set_matching_flag,
11387                                0);
11388               }
11389               else
11390               {
11391                 /* Search for the rest of the word in all the sub-tst */
11392                 /* trees previously found.                            */
11393                 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
11394                 node         = tst_search_list->tail;
11395                 sub_tst_data = (sub_tst_t *)(node->data);
11396 
11397                 matches_count = 0;
11398 
11399                 for (index = 0; index < sub_tst_data->count; index++)
11400                   tst_prefix_search(sub_tst_data->array[index], w + 1, tst_cb);
11401               }
11402 
11403               if (matches_count > 0)
11404               {
11405                 if (search_data.len == old_len && matches_count == 1
11406                     && buffer[0] != 0x08 && buffer[0] != 0x7f)
11407                   my_beep(&toggles);
11408                 else
11409                 {
11410                   if (search_data.only_starting)
11411                     select_starting_matches(&win, &term, &search_data,
11412                                             &last_line);
11413                   else if (search_data.only_ending)
11414                     select_ending_matches(&win, &term, &search_data,
11415                                           &last_line);
11416                   else
11417                     update_bitmaps(search_mode, &search_data, NO_AFFINITY);
11418 
11419                   current = matching_words_a[0];
11420 
11421                   if (current < win.start || current > win.end)
11422                     last_line = build_metadata(&term, count, &win);
11423 
11424                   /* Set new first column to display. */
11425                   /* """""""""""""""""""""""""""""""" */
11426                   set_new_first_column(&win, &term);
11427 
11428                   nl = disp_lines(&win, &toggles, current, count, search_mode,
11429                                   &search_data, &term, last_line, tmp_word,
11430                                   &langinfo);
11431                 }
11432               }
11433               else
11434               {
11435                 my_beep(&toggles);
11436 
11437                 search_data.len = old_len;
11438                 search_data.utf8_len--;
11439                 search_data.buf[search_data.len] = '\0';
11440               }
11441             }
11442           }
11443         }
11444       }
11445     }
11446   }
11447 }
11448