Take UTF-8 spaces into account for empty strings
[smenu.git] / smenu.c
blob3266f8e08a449417a0f256915f096ed0ec0bb450
1 /* ################################################################### */
2 /* Copyright 2015, Pierre Gentile (p.gen.progs@gmail.com) */
3 /* */
4 /* This Source Code Form is subject to the terms of the Mozilla Public */
5 /* License, v. 2.0. If a copy of the MPL was not distributed with this */
6 /* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7 /* ################################################################### */
9 #include "config.h"
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <limits.h>
13 #include <stdarg.h>
14 #include <signal.h>
15 #include <ctype.h>
16 #include <time.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <locale.h>
20 #include <langinfo.h>
21 #if (defined(__sun) && defined(__SVR4)) || defined(_AIX)
22 #include <curses.h>
23 #endif
24 #include <term.h>
25 #include <termios.h>
26 #include <regex.h>
27 #include <errno.h>
28 #include <sys/ioctl.h>
29 #include <sys/time.h>
30 #include <sys/param.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <wchar.h>
35 #include "xmalloc.h"
36 #include "list.h"
37 #include "index.h"
38 #include "utf8.h"
39 #include "fgetc.h"
40 #include "utils.h"
41 #include "ctxopt.h"
42 #include "usage.h"
43 #include "safe.h"
44 #include "smenu.h"
46 /* ***************** */
47 /* Extern variables. */
48 /* ***************** */
50 extern ll_t *tst_search_list;
52 /* ***************** */
53 /* Global variables. */
54 /* ***************** */
56 word_t *word_a; /* array containing words data (size: count). */
57 long count = 0; /* number of words read from stdin. */
58 long current; /* index the current selection under the cursor). */
59 long new_current; /* final cur. position, (used in search function). */
60 long prev_current; /* prev. position stored when using direct access. */
62 long *line_nb_of_word_a; /* array containing the line number (from 0) *
63 | of each word read. */
64 long *first_word_in_line_a; /* array containing the index of the first *
65 | word of each lines. */
66 long *shift_right_sym_pos_a; /* screen column number of the right *
67 scrolling symbol if any when in line or *
68 column mode. */
70 int forgotten_timer = -1;
71 int help_timer = -1;
72 int winch_timer = -1;
73 int daccess_timer = -1;
74 int search_timer = -1;
76 search_mode_t search_mode = NONE;
77 search_mode_t old_search_mode = NONE;
79 int help_mode = 0; /* 1 if help is displayed else 0. */
81 int marked = -1; /* Index of the marked word or -1. */
83 char *word_buffer;
85 int (*my_isprint)(int);
86 int (*my_isempty)(const unsigned char *);
88 /* UTF-8 useful symbols. */
89 /* """"""""""""""""""""" */
90 /* clang-format off */
91 char * left_arrow = "\xe2\x86\x90"; /* ← leftwards arrow. */
92 char * up_arrow = "\xe2\x86\x91"; /* ↑ upwards arrow. */
93 char * right_arrow = "\xe2\x86\x92"; /* → rightwards arrow. */
94 char * down_arrow = "\xe2\x86\x93"; /* ↓ downwards arrow. */
95 char * vertical_bar = "\xe2\x94\x82"; /* │ box drawings light vertical. */
96 char * shift_left_sym = "\xe2\x97\x80"; /* ◀ black left-pointing triangle. */
97 char * shift_right_sym = "\xe2\x96\xb6"; /* ▶ black right-pointing triangle. */
98 char * sbar_line = "\xe2\x94\x82"; /* │ box drawings light vertical. */
99 char * hbar_line = "\xe2\x94\x80"; /* ─ box drawings light horizontal. */
100 char * hbar_left = "\xe2\x97\x80"; /* ◀ black left-pointing triangle. */
101 char * hbar_right = "\xe2\x96\xb6"; /* ▶ black right-pointing triangle. */
102 char * sbar_top = "\xe2\x94\x90"; /* ┐ box drawings light down and l. */
103 char * sbar_down = "\xe2\x94\x98"; /* ┘ box drawings light up and l. */
104 char * hbar_begin = "\xe2\x94\x94"; /* └ box drawings light up and r. */
105 char * hbar_end = "\xe2\x94\x98"; /* ┘ box drawings light up and l. */
106 char * sbar_curs = "\xe2\x94\x83"; /* ┃ box drawings heavy vertical. */
107 char * hbar_curs = "\xe2\x94\x81"; /* ━ box drawings heavy horizontal. */
108 char * sbar_arr_up = "\xe2\x96\xb2"; /* ▲ black up pointing triangle. */
109 char * sbar_arr_down = "\xe2\x96\xbc"; /* ▼ black down pointing triangle. */
110 char * msg_arr_down = "\xe2\x96\xbc"; /* ▼ black down pointing triangle. */
111 /* clang-format on */
113 /* Mouse tracking. */
114 /* """"""""""""""" */
115 char *mouse_trk_on;
116 char *mouse_trk_off;
118 /* Variables used to manage the direct access entries. */
119 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
120 daccess_t daccess;
121 char *daccess_stack;
122 int daccess_stack_head;
124 /* Variables used for fuzzy and substring searching. */
125 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
126 long matches_count; /* Number of all matching words. */
128 long *matching_words_a; /* Array containing the index of all matching *
129 | words. */
131 long matching_words_a_size; /* Allocated size for the previous array. */
133 long best_matches_count; /* Number of matching words whose matching *
134 | part form a consecutive suite of matching *
135 | glyphs. */
137 long *best_matching_words_a; /* Array containing the index of matching *
138 | words containing a consecutive suite of *
139 | matching glyphs. */
141 long best_matching_words_a_size; /* Allocated size for the previous array. */
143 long *alt_matching_words_a = NULL; /* Alternate array to contain only the *
144 | matching candidates having potentially *
145 | a starting/ending pattern. */
147 /* Variables used in signal handlers. */
148 /* """""""""""""""""""""""""""""""""" */
149 volatile sig_atomic_t got_winch = 0;
150 volatile sig_atomic_t got_winch_alrm = 0;
151 volatile sig_atomic_t got_forgotten_alrm = 0;
152 volatile sig_atomic_t got_help_alrm = 0;
153 volatile sig_atomic_t got_daccess_alrm = 0;
154 volatile sig_atomic_t got_search_alrm = 0;
155 volatile sig_atomic_t got_timeout_tick = 0;
156 volatile sig_atomic_t got_sigpipe = 0;
157 volatile sig_atomic_t got_sigsegv = 0;
158 volatile sig_atomic_t got_sigterm = 0;
159 volatile sig_atomic_t got_sighup = 0;
161 /* Variables used when a timeout is set (option -x). */
162 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
163 timeout_t timeout;
164 char *timeout_word; /* printed word when the timeout type is WORD. */
165 char *timeout_seconds; /* string containing the number of remaining *
166 | seconds. */
167 int quiet_timeout = 0; /* 1 when we want no message to be displayed. */
169 /* *************** */
170 /* Help functions. */
171 /* *************** */
173 /* ===================== */
174 /* Help message display. */
175 /* ===================== */
176 void
177 help(win_t *win, term_t *term, long last_line)
179 int index; /* used to identify the objects long the help line. */
180 int line = 0; /* number of windows lines used by the help line. */
181 int len = 0; /* length of the help line. */
182 int max_col; /* when to split the help line. */
183 int entries_nb; /* number of help entries to display. */
184 int i;
186 struct entry_s
188 char *str; /* string to be displayed for an object in the help line. */
189 int len; /* screen space taken by of one of these objects. */
190 char attr; /* r=reverse, n=normal, b=bold, uu=underlined. */
193 char *arrows = concat(left_arrow,
194 up_arrow,
195 right_arrow,
196 down_arrow,
197 (char *)0);
199 struct entry_s entries[] = {
200 { "Move:", 5, 'r' }, { "Mouse:", 6, 'u' }, { "B1", 2, 'b' },
201 { ",", 1, 'n' }, { "B3", 2, 'b' }, { ",", 1, 'n' },
202 { "Wheel", 5, 'b' }, { " ", 1, 'n' }, { "Keyb:", 5, 'u' },
203 { "hjkl", 4, 'b' }, { ",", 1, 'n' }, { arrows, 4, 'b' },
204 { ",", 1, 'n' }, { "PgUp", 4, 'b' }, { "/", 1, 'n' },
205 { "Dn", 2, 'b' }, { "... ", 4, 'n' }, { "Abort:", 6, 'r' },
206 { "q", 1, 'b' }, { ",", 1, 'n' }, { "^C", 2, 'b' },
207 { " ", 1, 'n' }, { "Find:", 5, 'r' }, { "/", 1, 'b' },
208 { "\"\'", 2, 'b' }, { "~*", 2, 'b' }, { "=^", 2, 'b' },
209 { ",", 1, 'n' }, { "SP", 2, 'b' }, { ",", 1, 'n' },
210 { "nN", 2, 'b' }, { " ", 1, 'n' }, { "Select:", 7, 'r' },
211 { "CR", 2, 'b' }, { ",", 1, 'n' }, { "D-click", 7, 'b' },
212 { " ", 1, 'n' }, { " ", 1, 'n' }, { "Mark:", 5, 'r' },
213 { "mM", 2, 'b' }, { " ", 1, 'n' }, { "Tag:", 4, 'r' },
214 { "t", 1, 'b' }, { ",", 1, 'n' }, { "r", 1, 'b' },
215 { ",", 1, 'n' }, { "c", 1, 'b' }, { " ", 1, 'n' },
216 { "T", 1, 'b' }, { "...", 3, 'n' }, { "T", 1, 'b' },
217 { ",", 1, 'n' }, { "Z", 1, 'b' }, { "...", 3, 'n' },
218 { "Z", 1, 'b' }, { ",", 1, 'n' }, { "R", 1, 'b' },
219 { "...", 3, 'n' }, { "R", 1, 'b' }, { ",", 1, 'n' },
220 { "C", 1, 'b' }, { "...", 3, 'n' }, { "C", 1, 'b' },
221 { " ", 1, 'n' }, { "U", 1, 'b' }, { ",", 1, 'n' },
222 { "^T", 2, 'b' }, { " ", 1, 'n' }, { "Cancel:", 7, 'r' },
223 { "ESC", 3, 'b' },
226 entries_nb = sizeof(entries) / sizeof(struct entry_s);
228 /* Save the position of the terminal cursor so that it can be */
229 /* put back there after printing of the help line. */
230 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
231 (void)tputs(TPARM1(save_cursor), 1, outch);
233 /* Determine when to split the help line if necessary. */
234 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
235 if (win->sb_column > 0)
236 max_col = term->ncolumns > win->sb_column ? win->sb_column - 1
237 : term->ncolumns - 1;
239 else
240 max_col = term->ncolumns - 1;
242 /* Print the different objects forming the help line. */
243 /* A new line is added each time the next entry does not fit in the */
244 /* width of the window. This is done as long as there is space left in */
245 /* the window. */
246 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
247 for (index = 0; index < entries_nb; index++)
249 if (entries[index].len >= max_col)
250 continue;
252 len += entries[index].len;
253 if (len >= max_col)
255 line++;
257 /* Exit early if we do not have enough space. */
258 /* '''''''''''''''''''''''''''''''''''''''''' */
259 if (line > last_line || line == win->max_lines)
260 break;
262 len -= entries[index].len;
264 /* Fill the rest on the line with space, preserving the scroll bar. */
265 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
266 for (i = len; i < max_col; i++)
267 fputc_safe(' ', stdout);
269 len = entries[index].len;
270 fputc_safe('\n', stdout);
273 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
274 switch (entries[index].attr)
276 case 'b': /* bold. */
277 if (term->has_bold)
278 (void)tputs(TPARM1(enter_bold_mode), 1, outch);
279 break;
280 case 'u': /* underline. */
281 if (term->has_underline)
282 (void)tputs(TPARM1(enter_underline_mode), 1, outch);
283 break;
284 case 'r': /* reverse. */
285 if (term->has_reverse)
286 (void)tputs(TPARM1(enter_reverse_mode), 1, outch);
287 else if (term->has_standout)
288 (void)tputs(TPARM1(enter_standout_mode), 1, outch);
289 break;
290 case 'n': /* no attributes. */
291 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
292 break;
294 fputs_safe(entries[index].str, stdout);
295 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
298 /* Fill the remaining space with spaces. */
299 /* """"""""""""""""""""""""""""""""""""" */
300 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
301 for (i = len; i < max_col; i++)
302 fputc_safe(' ', stdout);
304 /* Put back the cursor to its saved position. */
305 /* """""""""""""""""""""""""""""""""""""""""" */
306 (void)tputs(TPARM1(restore_cursor), 1, outch);
308 free(arrows);
311 /* *********************************** */
312 /* Attributes string parsing function. */
313 /* *********************************** */
315 /* =========================================================== */
316 /* Allocation and initialization of a new attribute structure. */
317 /* =========================================================== */
318 attrib_t *
319 attr_new(void)
321 attrib_t *attr;
323 attr = xmalloc(sizeof(attrib_t));
325 attr->is_set = UNSET;
326 attr->fg = -1;
327 attr->bg = -1;
328 attr->bold = (signed char)-1;
329 attr->dim = (signed char)-1;
330 attr->reverse = (signed char)-1;
331 attr->standout = (signed char)-1;
332 attr->underline = (signed char)-1;
333 attr->italic = (signed char)-1;
334 attr->invis = (signed char)-1;
335 attr->blink = (signed char)-1;
337 return attr;
340 /* ================================= */
341 /* Decode attributes toggles if any. */
342 /* b -> bold */
343 /* d -> dim */
344 /* r -> reverse */
345 /* s -> standout */
346 /* u -> underline */
347 /* i -> italic */
348 /* x -> invis */
349 /* l -> blink */
350 /* */
351 /* Returns 0 if some unexpected. */
352 /* toggle is found else 0. */
353 /* ================================= */
355 decode_attr_toggles(char *s, attrib_t *attr)
357 int rc = 1;
359 attr->bold = (signed char)0;
360 attr->dim = (signed char)0;
361 attr->reverse = (signed char)0;
362 attr->standout = (signed char)0;
363 attr->underline = (signed char)0;
364 attr->italic = (signed char)0;
365 attr->invis = (signed char)0;
366 attr->blink = (signed char)0;
368 while (*s != '\0')
370 switch (*s)
372 case 'b':
373 attr->bold = (signed char)1;
374 attr->is_set = SET;
375 break;
376 case 'd':
377 attr->dim = (signed char)1;
378 attr->is_set = SET;
379 break;
380 case 'r':
381 attr->reverse = (signed char)1;
382 attr->is_set = SET;
383 break;
384 case 's':
385 attr->standout = (signed char)1;
386 attr->is_set = SET;
387 break;
388 case 'u':
389 attr->underline = (signed char)1;
390 attr->is_set = SET;
391 break;
392 case 'i':
393 attr->italic = (signed char)1;
394 attr->is_set = SET;
395 break;
396 case 'n':
397 attr->invis = (signed char)1;
398 attr->is_set = SET;
399 break;
400 case 'l':
401 attr->blink = (signed char)1;
402 attr->is_set = SET;
403 break;
404 default:
405 rc = 0;
406 break;
408 s++;
410 return rc;
413 /* =============================================================*/
414 /* Parse attributes in str in the form [fg][/bg][[,.+]toggles] */
415 /* where: */
416 /* fg and bg are short representing a color value */
417 /* toggles is an array of toggles (see decode_attr_toggles) */
418 /* Returns 1 on success else 0. */
419 /* attr will be filled by the function. */
420 /* =============================================================*/
422 parse_attr(char *str, attrib_t *attr, short colors)
424 int n;
425 char *pos;
426 char s1[12] = { (char)0 }; /* For the colors. */
427 char s2[9] = { (char)0 }; /* For the attributes. */
428 short d1 = -1, d2 = -1; /* colors. */
429 int rc = 1;
430 char c = '\0';
432 /* 11: 4 type+colon,2x3 for colors, 1 for slash. */
433 /* 8 : max size for the concatenation of attributes. */
434 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
435 n = sscanf(str, "%11[^,.+]%*[,.+]%8s%c", s1, s2, &c);
437 if (n == 0 || c != '\0')
438 goto error;
440 if ((pos = strchr(s1, '/')))
442 if (pos == s1) /* s1 starts with a / */
444 d1 = -1;
445 if (sscanf(s1 + 1, "%hd", &d2) == 0)
447 d2 = -1;
448 if (n == 1)
449 goto error;
451 else if (d2 < 0)
452 goto error;
454 else if (sscanf(s1, "%hd/%hd", &d1, &d2) < 2)
456 d1 = d2 = -1;
457 if (n == 1)
458 goto error;
460 else if (d1 < 0 || d2 < 0)
461 goto error;
463 else /* no / in the first string. */
465 d2 = -1;
466 if (sscanf(s1, "%hd", &d1) == 0)
468 d1 = -1;
469 if (n == 2 || decode_attr_toggles(s1, attr) == 0)
470 goto error;
474 if (colors == 0) /* Monochrome. */
476 attr->fg = -1;
477 attr->bg = -1;
479 else
481 attr->fg = d1 < colors ? d1 : -1;
482 attr->bg = d2 < colors ? d2 : -1;
485 if (n == 2)
486 rc = decode_attr_toggles(s2, attr);
488 return rc;
490 error:
491 return 0;
494 /* ============================================== */
495 /* Set the terminal attributes according to attr. */
496 /* ============================================== */
497 void
498 apply_attr(term_t *term, attrib_t attr)
500 if (attr.fg >= 0)
501 set_foreground_color(term, attr.fg);
503 if (attr.bg >= 0)
504 set_background_color(term, attr.bg);
506 if (attr.bold > (signed char)0)
507 (void)tputs(TPARM1(enter_bold_mode), 1, outch);
509 if (attr.dim > (signed char)0)
510 (void)tputs(TPARM1(enter_dim_mode), 1, outch);
512 if (attr.reverse > (signed char)0)
513 (void)tputs(TPARM1(enter_reverse_mode), 1, outch);
515 if (attr.standout > (signed char)0)
516 (void)tputs(TPARM1(enter_standout_mode), 1, outch);
518 if (attr.underline > (signed char)0)
519 (void)tputs(TPARM1(enter_underline_mode), 1, outch);
521 if (attr.italic > (signed char)0)
522 (void)tputs(TPARM1(enter_italics_mode), 1, outch);
524 if (attr.invis > (signed char)0)
525 (void)tputs(TPARM1(enter_secure_mode), 1, outch);
527 if (attr.blink > (signed char)0)
528 (void)tputs(TPARM1(enter_blink_mode), 1, outch);
531 /* ********************* */
532 /* ini parsing function. */
533 /* ********************* */
535 /* ===================================================== */
536 /* Callback function called when parsing each non-header */
537 /* line of the ini file. */
538 /* Returns 0 if OK, 1 if not. */
539 /* ===================================================== */
541 ini_cb(win_t *win,
542 term_t *term,
543 limit_t *limits,
544 ticker_t *timers,
545 misc_t *misc,
546 mouse_t *mouse,
547 const char *section,
548 const char *name,
549 char *value)
551 int error = 0;
552 int has_colors = term->colors > 7;
554 if (strcmp(section, "colors") == 0)
556 attrib_t v = { UNSET,
557 /* fg */ -1,
558 /* bg */ -1,
559 /* bold */ (signed char)-1,
560 /* dim */ (signed char)-1,
561 /* reverse */ (signed char)-1,
562 /* standout */ (signed char)-1,
563 /* underline */ (signed char)-1,
564 /* italic */ (signed char)-1,
565 /* invis */ (signed char)-1,
566 /* blink */ (signed char)-1 };
568 #define CHECK_ATTR(x) \
569 else if (strcmp(name, #x) == 0) \
571 error = !parse_attr(value, &v, term->colors); \
572 if (error) \
573 goto out; \
574 else \
576 if (win->x##_attr.is_set != FORCED) \
578 win->x##_attr.is_set = SET; \
579 if (v.fg >= 0) \
580 win->x##_attr.fg = v.fg; \
581 if (v.bg >= 0) \
582 win->x##_attr.bg = v.bg; \
583 if (v.bold >= (signed char)0) \
584 win->x##_attr.bold = v.bold; \
585 if (v.dim >= (signed char)0) \
586 win->x##_attr.dim = v.dim; \
587 if (v.reverse >= (signed char)0) \
588 win->x##_attr.reverse = v.reverse; \
589 if (v.standout >= (signed char)0) \
590 win->x##_attr.standout = v.standout; \
591 if (v.underline >= (signed char)0) \
592 win->x##_attr.underline = v.underline; \
593 if (v.italic >= (signed char)0) \
594 win->x##_attr.italic = v.italic; \
595 if (v.invis >= (signed char)0) \
596 win->x##_attr.invis = v.invis; \
597 if (v.blink >= (signed char)0) \
598 win->x##_attr.blink = v.blink; \
603 #define CHECK_ATT_ATTR(x, y) \
604 else if (strcmp(name, #x #y) == 0) \
606 error = !parse_attr(value, &v, term->colors); \
607 if (error) \
608 goto out; \
609 else \
611 if (win->x##_attr[y - 1].is_set != FORCED) \
613 win->x##_attr[y - 1].is_set = SET; \
614 if (v.fg >= 0) \
615 win->x##_attr[y - 1].fg = v.fg; \
616 if (v.bg >= 0) \
617 win->x##_attr[y - 1].bg = v.bg; \
618 if (v.bold >= (signed char)0) \
619 win->x##_attr[y - 1].bold = v.bold; \
620 if (v.dim >= (signed char)0) \
621 win->x##_attr[y - 1].dim = v.dim; \
622 if (v.reverse >= (signed char)0) \
623 win->x##_attr[y - 1].reverse = v.reverse; \
624 if (v.standout >= (signed char)0) \
625 win->x##_attr[y - 1].standout = v.standout; \
626 if (v.underline >= (signed char)0) \
627 win->x##_attr[y - 1].underline = v.underline; \
628 if (v.italic >= (signed char)0) \
629 win->x##_attr[y - 1].italic = v.italic; \
630 if (v.invis >= (signed char)0) \
631 win->x##_attr[y - 1].invis = v.invis; \
632 if (v.blink >= (signed char)0) \
633 win->x##_attr[y - 1].blink = v.blink; \
638 /* [colors] section. */
639 /* """"""""""""""""" */
640 if (has_colors)
642 if (strcmp(name, "method") == 0)
644 if (strcmp(value, "classic") == 0)
645 term->color_method = CLASSIC;
646 else if (strcmp(value, "ansi") == 0)
647 term->color_method = ANSI;
648 else
650 error = 1;
651 goto out;
655 /* clang-format off */
656 CHECK_ATTR(cursor)
657 CHECK_ATTR(bar)
658 CHECK_ATTR(shift)
659 CHECK_ATTR(message)
660 CHECK_ATTR(search_field)
661 CHECK_ATTR(search_text)
662 CHECK_ATTR(match_field)
663 CHECK_ATTR(match_text)
664 CHECK_ATTR(match_err_field)
665 CHECK_ATTR(match_err_text)
666 CHECK_ATTR(include)
667 CHECK_ATTR(exclude)
668 CHECK_ATTR(tag)
669 CHECK_ATTR(cursor_on_tag)
670 CHECK_ATTR(daccess)
671 CHECK_ATT_ATTR(special, 1)
672 CHECK_ATT_ATTR(special, 2)
673 CHECK_ATT_ATTR(special, 3)
674 CHECK_ATT_ATTR(special, 4)
675 CHECK_ATT_ATTR(special, 5)
676 CHECK_ATT_ATTR(special, 6)
677 CHECK_ATT_ATTR(special, 7)
678 CHECK_ATT_ATTR(special, 8)
679 CHECK_ATT_ATTR(special, 9)
680 /* clang-format on */
683 else if (strcmp(section, "window") == 0)
685 int v;
687 /* [window] section. */
688 /* """"""""""""""""" */
689 if (strcmp(name, "lines") == 0)
691 if ((error = !(sscanf(value, "%d", &v) == 1 && v >= 0)))
692 goto out;
693 else
694 win->asked_max_lines = v;
697 else if (strcmp(section, "limits") == 0)
699 long v;
701 /* [limits] section. */
702 /* """"""""""""""""" */
703 if (strcmp(name, "word_length") == 0)
705 if ((error = !(sscanf(value, "%ld", &v) == 1 && v > 0)))
706 goto out;
707 else
708 limits->word_length = v;
710 else if (strcmp(name, "words") == 0)
712 if ((error = !(sscanf(value, "%ld", &v) == 1 && v > 0)))
713 goto out;
714 else
715 limits->words = v;
717 else if (strcmp(name, "columns") == 0)
719 if ((error = !(sscanf(value, "%ld", &v) == 1 && v > 0)))
720 goto out;
721 else
722 limits->cols = v;
725 else if (strcmp(section, "timers") == 0)
727 int v;
729 /* [timers] section. */
730 /* """"""""""""""""" */
731 if (strcmp(name, "help") == 0)
733 if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
734 goto out;
735 else
736 timers->help = v;
738 else if (strcmp(name, "forgotten") == 0)
740 if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
741 goto out;
742 else
743 timers->forgotten = v;
745 else if (strcmp(name, "window") == 0)
747 if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
748 goto out;
749 else
750 timers->winch = v;
752 else if (strcmp(name, "direct_access") == 0)
754 if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
755 goto out;
756 else
757 timers->direct_access = v;
759 else if (strcmp(name, "search") == 0)
761 if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
762 goto out;
763 else
764 timers->search = v;
767 else if (strcmp(section, "mouse") == 0)
769 int v;
771 /* [mouse] section. */
772 /* """"""""""""""" */
773 if (strcmp(name, "double_click_delay") == 0)
775 if ((error = !(sscanf(value, "%d", &v) == 1 && v > 0)))
776 goto out;
777 else
778 mouse->double_click_delay = v;
781 else if (strcmp(section, "misc") == 0)
783 /* [misc] section. */
784 /* """"""""""""""" */
785 if (strcmp(name, "default_search_method") == 0)
787 if (misc->default_search_method == NONE)
789 if (strcmp(value, "prefix") == 0)
790 misc->default_search_method = PREFIX;
791 else if (strcmp(value, "fuzzy") == 0)
792 misc->default_search_method = FUZZY;
793 else if (strcmp(value, "substring") == 0)
794 misc->default_search_method = SUBSTRING;
799 out:
801 return error;
804 /* ======================================================================== */
805 /* Load an .ini format file. */
806 /* filename - path to a file. */
807 /* report - callback can return non-zero to stop, the callback error code */
808 /* returned from this function. */
809 /* return - return 0 on success. */
810 /* */
811 /* This function is public domain. No copyright is claimed. */
812 /* Jon Mayo April 2011. */
813 /* ======================================================================== */
815 ini_load(const char *filename,
816 win_t *win,
817 term_t *term,
818 limit_t *limits,
819 ticker_t *timers,
820 misc_t *misc,
821 mouse_t *mouse,
822 int (*report)(win_t *win,
823 term_t *term,
824 limit_t *limits,
825 ticker_t *timers,
826 misc_t *misc,
827 mouse_t *mouse,
828 const char *section,
829 const char *name,
830 char *value))
832 char name[64] = "";
833 char value[256] = "";
834 char section[128] = "";
835 char *s;
836 FILE *f;
837 int cnt;
838 int error;
840 /* If the filename is empty we skip this phase and use the */
841 /* default values. */
842 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
843 if (filename == NULL)
844 return 1;
846 /* We do that if the file is not readable as well. */
847 /* """"""""""""""""""""""""""""""""""""""""""""""" */
848 f = fopen_safe(filename, "r");
849 if (f == NULL)
850 return 0; /* Returns success as the presence of this file *
851 | is optional. */
853 error = 0;
855 /* Skip blank lines. */
856 /* """"""""""""""""" */
857 while (fscanf(f, "%*[\n]") == 1)
861 while (!feof(f))
863 if (fscanf(f, " [%127[^];\n]]", section) == 1)
865 /* Do nothing. */
866 /* """"""""""" */
869 if ((cnt = fscanf(f, " %63[^=;\n] = %255[^;\n]", name, value)))
871 if (cnt == 1)
872 *value = 0;
874 for (s = name + strlen(name) - 1; s > name && isspace(*s); s--)
875 *s = 0;
877 for (s = value + strlen(value) - 1; s > value && isspace(*s); s--)
878 *s = 0;
880 /* Callback function calling. */
881 /* """""""""""""""""""""""""" */
882 error =
883 report(win, term, limits, timers, misc, mouse, section, name, value);
885 if (error)
886 goto out;
889 if (fscanf(f, " ;%*[^\n]"))
891 /* To silence the compiler about unused results. */
894 /* Skip blank lines. */
895 /* """"""""""""""""" */
896 while (fscanf(f, "%*[\n]") == 1)
898 /* Do nothing. */
899 /* """"""""""" */
903 out:
904 fclose(f);
906 if (error)
907 fprintf(stderr,
908 "Invalid entry found: %s=%s in %s.\n",
909 name,
910 value,
911 filename);
913 return error;
916 /* ======================================================= */
917 /* Return the full path on the configuration file supposed */
918 /* to be in the home directory of the user. */
919 /* NULL is returned if the built path is too large. */
920 /* ======================================================= */
921 char *
922 make_ini_path(char *name, char *base)
924 char *path;
925 char *home;
926 long path_max;
927 long len;
929 /* Set the prefix of the path from the environment */
930 /* base can be "HOME" or "PWD". */
931 /* """"""""""""""""""""""""""""""""""""""""""""""" */
932 home = getenv(base);
934 if (home == NULL)
935 home = "";
937 path_max = pathconf(".", _PC_PATH_MAX);
938 len = strlen(home) + strlen(name) + 3;
940 if (path_max < 0)
941 path_max = 4096; /* POSIX minimal value. path_max >= 4096. */
943 if (len <= path_max)
945 char *conf;
947 path = xmalloc(len);
948 conf = strrchr(name, '/');
950 if (conf != NULL)
951 conf++;
952 else
953 conf = name;
955 snprintf(path, len, "%s/.%s", home, conf);
957 else
958 path = NULL;
960 return path;
963 /* ********************************* */
964 /* Functions used when sorting tags. */
965 /* ********************************* */
967 /* =================================================================== */
968 /* Compare the pin order of two pinned word in the output list. */
969 /* Returns -1 if the pinning value is less than the second, 0 if it is */
970 /* equal to the second and 1 if it is greater than the second. */
971 /* =================================================================== */
973 tag_comp(void const *a, void const *b)
975 output_t *oa = (output_t *)a;
976 output_t *ob = (output_t *)b;
978 if (oa->order == ob->order)
979 return 0;
981 return (oa->order < ob->order) ? -1 : 1;
984 /* ========================================================= */
985 /* Swap the values of two selected words in the output list. */
986 /* ========================================================= */
987 void
988 tag_swap(void **a, void **b)
990 output_t *oa = (output_t *)*a;
991 output_t *ob = (output_t *)*b;
993 char *tmp_str;
994 long tmp_order;
996 tmp_str = oa->output_str;
997 oa->output_str = ob->output_str;
998 ob->output_str = tmp_str;
1000 tmp_order = oa->order;
1001 oa->order = ob->order;
1002 ob->order = tmp_order;
1005 /* ****************** */
1006 /* Utility functions. */
1007 /* ****************** */
1009 /* =================================================================== */
1010 /* Create a new element to be added to the tst_search_list used by the */
1011 /* search mechanism. */
1012 /* Return the newly created element. */
1013 /* =================================================================== */
1014 sub_tst_t *
1015 sub_tst_new(void)
1017 sub_tst_t *elem = xmalloc(sizeof(sub_tst_t));
1019 elem->size = 64;
1020 elem->count = 0;
1021 elem->array = xmalloc(elem->size * sizeof(tst_node_t));
1023 return elem;
1026 /* ========================================= */
1027 /* Emit a small (visual) beep warn the user. */
1028 /* ========================================= */
1029 void
1030 my_beep(toggle_t *toggles)
1032 struct timespec ts, rem;
1034 if (!toggles->visual_bell)
1035 fputc_safe('\a', stdout);
1036 else
1038 int rc;
1040 (void)tputs(TPARM1(cursor_visible), 1, outch);
1042 ts.tv_sec = 0;
1043 ts.tv_nsec = 200000000; /* 0.2s */
1045 errno = 0;
1046 rc = nanosleep(&ts, &rem);
1048 while (rc < 0 && errno == EINTR)
1050 errno = 0;
1051 rc = nanosleep(&rem, &rem);
1054 (void)tputs(TPARM1(cursor_invisible), 1, outch);
1058 /* ================================================================== */
1059 /* Integer verification constraint for ctxopt. */
1060 /* Return 1 if par is the representation of an integer else return 0. */
1061 /* ================================================================== */
1063 check_integer_constraint(int nb_args, char **args, char *value, char *par)
1065 if (!is_integer(value))
1067 fprintf(stderr, "The argument of %s is not an integer: %s", par, value);
1068 return 0;
1070 return 1;
1073 /* ======================================================================= */
1074 /* Update the bitmap associated with a word. The bits set to 1 in this */
1075 /* bitmap indicate the positions of the UFT-8 glyphs of the search buffer */
1076 /* in the word. */
1077 /* */
1078 /* The disp_word function will use it to display these special characters. */
1079 /* */
1080 /* mode is the search method. */
1081 /* data contains information about the search buffer. */
1082 /* affinity determines if we must only consider matches that occur at */
1083 /* the start, the end or if we just don't care. */
1084 /* ======================================================================= */
1085 void
1086 update_bitmaps(search_mode_t mode,
1087 search_data_t *data,
1088 bitmap_affinity_t affinity)
1090 long i, j, n; /* work variables. */
1092 long bm_len; /* number of chars taken by the bit mask. */
1094 char *start; /* pointer on the position of the matching position *
1095 | of the last search buffer glyph in the word. */
1097 char *bm; /* the word's current bitmap. */
1099 char *str; /* copy of the current word put in lower case. */
1100 char *str_orig; /* original version of the word. */
1102 char *sb_orig = data->buf; /* sb: search buffer. */
1104 long *o = data->utf8_off_a; /* array of the offsets of the search *
1105 | buffer glyphs. */
1106 long *l = data->utf8_len_a; /* array of the lengths in bytes of *
1107 | the search buffer glyphs. */
1108 long last = data->utf8_len - 1; /* offset of the last glyph in the *
1109 | search buffer. */
1111 best_matches_count = 0;
1113 if (mode == FUZZY || mode == SUBSTRING)
1115 char *sb;
1116 char *first_glyph;
1117 long badness = 0; /* number of 0s between two 1s. */
1119 first_glyph = xmalloc(5);
1121 if (mode == FUZZY)
1123 sb = xstrdup(sb_orig); /* sb initially points to sb_orig. */
1124 utf8_strtolower(sb, sb_orig);
1126 else
1127 sb = sb_orig;
1129 for (i = 0; i < matches_count; i++)
1131 long lmg; /* position of the last matching glyph of the search buffer *
1132 | in a word. */
1134 n = matching_words_a[i];
1136 str_orig = xstrdup(word_a[n].str + daccess.flength);
1138 /* We need to remove the trailing spaces to use the */
1139 /* following algorithm. */
1140 /* .len holds the original length in bytes of the word. */
1141 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
1142 rtrim(str_orig, " \t", 0);
1144 bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1;
1145 bm = word_a[n].bitmap;
1147 /* In fuzzy search mode str are converted in lower case letters */
1148 /* for comparison reason. */
1149 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1150 if (mode == FUZZY)
1152 str = xstrdup(str_orig);
1153 utf8_strtolower(str, str_orig);
1155 else
1156 str = str_orig;
1158 start = str;
1159 lmg = 0;
1161 /* Start points to the first UTF-8 glyph of the word. */
1162 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
1163 while ((size_t)(start - str) < word_a[n].len - daccess.flength)
1165 /* Reset the bitmap. */
1166 /* """"""""""""""""" */
1167 memset(bm, '\0', bm_len);
1169 /* Compare the glyph pointed to by start to the last glyph of */
1170 /* the search buffer, the aim is to point to the first */
1171 /* occurrence of the last glyph of it. */
1172 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1173 if (memcmp(start, sb + o[last], l[last]) == 0)
1175 char *p; /* Pointer to the beginning of an UTF-8 glyph in *
1176 | the potential lowercase version of the word. */
1178 long sg; /* Index going from lmg backward to 0 of the tested *
1179 | glyphs of the search buffer (searched glyph). */
1181 if (last == 0)
1183 /* There is only one glyph in the search buffer, we can */
1184 /* stop here. */
1185 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
1186 BIT_ON(bm, lmg);
1187 if (affinity != END_AFFINITY)
1188 break;
1191 /* If the search buffer contains more than one glyph, we need */
1192 /* to search the first combination which match the buffer in */
1193 /* the word. */
1194 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1195 p = start;
1196 j = last; /* j counts the number of glyphs in the search buffer *
1197 | not found in the word. */
1199 /* Proceed backwards from the position of last glyph of the */
1200 /* search to check if all the previous glyphs can be fond before */
1201 /* in the word. If not try to find the next position of this */
1202 /* last glyph in the word. */
1203 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1204 sg = lmg;
1205 while (j > 0 && (p = utf8_prev(str, p)) != NULL)
1207 if (memcmp(p, sb + o[j - 1], l[j - 1]) == 0)
1209 BIT_ON(bm, sg - 1);
1210 j--;
1212 else if (mode == SUBSTRING)
1213 break;
1215 sg--;
1218 /* All the glyphs have been found. */
1219 /* """"""""""""""""""""""""""""""" */
1220 if (j == 0)
1222 BIT_ON(bm, lmg);
1223 if (affinity != END_AFFINITY)
1224 break;
1228 lmg++;
1229 start = utf8_next(start);
1232 if (mode == FUZZY)
1234 size_t utf8_index;
1236 free(str);
1238 /* We know that the first non blank glyph is part of the pattern, */
1239 /* so highlight it if it is not and suppresses the highlighting */
1240 /* of the next occurrence that must be here because this word has */
1241 /* already been filtered by select_starting_pattern(). */
1242 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1243 if (affinity == START_AFFINITY)
1245 size_t i;
1246 long utf8_len;
1248 /* Skip leading spaces and tabs. */
1249 /* """"""""""""""""""""""""""""" */
1250 for (i = 0; i < word_a[n].mb; i++)
1251 if (!isblank(*(word_a[n].str + daccess.flength + i)))
1252 break;
1254 first_glyph = utf8_strprefix(first_glyph,
1255 word_a[n].str + i,
1257 &utf8_len);
1259 if (!BIT_ISSET(word_a[n].bitmap, i))
1261 char *ptr1, *ptr2;
1263 BIT_ON(word_a[n].bitmap, i);
1265 ptr1 = word_a[n].str + i;
1266 i++;
1267 while ((ptr2 = utf8_next(ptr1)) != NULL)
1269 if (memcmp(ptr2, first_glyph, utf8_len) == 0)
1271 if (BIT_ISSET(word_a[n].bitmap, i))
1273 BIT_OFF(word_a[n].bitmap, i);
1274 break;
1276 else
1277 ptr1 = ptr2;
1279 else
1280 ptr1 = ptr2;
1282 i++;
1287 /* Compute the number of 'holes' in the bitmap to determine the */
1288 /* badness of a match. Th goal is to put the cursor on the word */
1289 /* with the smallest badness. */
1290 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1291 utf8_index = 0;
1292 j = 0;
1293 badness = 0;
1295 while (utf8_index < word_a[n].mb
1296 && !BIT_ISSET(word_a[n].bitmap, utf8_index))
1297 utf8_index++;
1299 while (utf8_index < word_a[n].mb)
1301 if (!BIT_ISSET(word_a[n].bitmap, utf8_index))
1302 badness++;
1303 else
1304 j++;
1306 /* Stop here if all the possible bits has been checked as they */
1307 /* cannot be more numerous than the number of UTF-8 glyphs in */
1308 /* the search buffer. */
1309 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1310 if (j == data->utf8_len)
1311 break;
1313 utf8_index++;
1316 free(str_orig);
1318 if (search_mode == FUZZY)
1320 /* When the badness is zero (best match), add the word position. */
1321 /* at the end of a special array which will be used to move the. */
1322 /* cursor among this category of words. */
1323 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1324 if (badness == 0)
1326 if (best_matches_count == best_matching_words_a_size)
1328 best_matching_words_a = xrealloc(best_matching_words_a,
1329 (best_matching_words_a_size + 16)
1330 * sizeof(long));
1331 best_matching_words_a_size += 16;
1334 best_matching_words_a[best_matches_count] = n;
1336 best_matches_count++;
1341 if (mode == FUZZY)
1342 free(sb);
1344 free(first_glyph);
1346 else if (mode == PREFIX)
1348 for (i = 0; i < matches_count; i++)
1350 n = matching_words_a[i];
1351 bm = word_a[n].bitmap;
1352 bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1;
1354 memset(bm, '\0', bm_len);
1356 for (j = 0; j <= last; j++)
1357 BIT_ON(bm, j);
1362 /* ========================================================= */
1363 /* Find the next word index in the list of matching words */
1364 /* using the bisection search algorithm. */
1365 /* Set the value of index to -1 and returns -1 if not found. */
1366 /* ========================================================= */
1367 long
1368 find_next_matching_word(long *array, long nb, long value, long *index)
1370 /* Use the cached value when possible. */
1371 /* """"""""""""""""""""""""""""""""""" */
1372 if (*index >= 0 && *index < nb - 1 && array[*index] == value)
1373 return (array[++(*index)]);
1375 if (nb > 0)
1377 long left = 0, right = nb;
1379 /* Bisection search. */
1380 /* ''''''''''''''''' */
1381 while (left < right)
1383 long middle = (left + right) / 2;
1385 if (value < array[middle])
1386 right = middle;
1387 else
1388 left = middle + 1;
1391 if (left < nb - 1)
1393 *index = left;
1394 return array[*index];
1397 if (value > array[nb - 1])
1399 *index = -1;
1400 return -1;
1403 *index = nb - 1;
1404 return array[*index];
1407 *index = -1;
1408 return -1;
1411 /* ========================================================== */
1412 /* Find the previous word index in the list of matching words */
1413 /* using the bisection search algorithm. */
1414 /* set the value of index to -1 and returns -1 if not found. */
1415 /* ========================================================== */
1416 long
1417 find_prev_matching_word(long *array, long nb, long value, long *index)
1419 /* Use the cached value when possible. */
1420 /* """"""""""""""""""""""""""""""""""" */
1421 if (*index > 0 && array[*index] == value)
1422 return (array[--(*index)]);
1424 if (nb > 0)
1426 /* Bisection search. */
1427 /* ''''''''''''''''' */
1429 long left = 0, right = nb;
1431 while (left < right)
1433 long middle = (left + right) / 2;
1435 if (array[middle] == value)
1437 if (middle > 0)
1439 *index = middle - 1;
1440 return array[*index];
1443 *index = -1;
1444 return -1;
1447 if (value < array[middle])
1448 right = middle;
1449 else
1450 left = middle + 1;
1453 if (left > 0)
1455 *index = left - 1;
1456 return array[*index];
1459 *index = -1;
1460 return -1;
1463 *index = -1;
1464 return -1;
1467 /* ============================================================= */
1468 /* Remove all traces of matched words and redisplay the windows. */
1469 /* ============================================================= */
1470 void
1471 clean_matches(search_data_t *search_data, long size)
1473 long i;
1475 /* Clean the list of lists data-structure containing the search levels */
1476 /* Note that the first element of the list is never deleted. */
1477 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1478 if (tst_search_list != NULL)
1480 ll_node_t *fuzzy_node;
1481 sub_tst_t *sub_tst_data;
1483 fuzzy_node = tst_search_list->tail;
1485 while (tst_search_list->len > 1)
1487 sub_tst_data = (sub_tst_t *)(fuzzy_node->data);
1489 free(sub_tst_data->array);
1490 free(sub_tst_data);
1492 ll_delete(tst_search_list, tst_search_list->tail);
1493 fuzzy_node = tst_search_list->tail;
1495 sub_tst_data = (sub_tst_t *)(fuzzy_node->data);
1496 sub_tst_data->count = 0;
1499 search_data->err = 0;
1501 /* Clean the search buffer. */
1502 /* """""""""""""""""""""""" */
1503 memset(search_data->buf, '\0', size - daccess.flength);
1505 search_data->len = 0;
1506 search_data->utf8_len = 0;
1507 search_data->only_ending = 0;
1508 search_data->only_starting = 0;
1510 /* Clean the match flags and bitmaps. */
1511 /* """""""""""""""""""""""""""""""""" */
1512 for (i = 0; i < matches_count; i++)
1514 long n = matching_words_a[i];
1516 word_a[n].is_matching = 0;
1518 memset(word_a[n].bitmap,
1519 '\0',
1520 (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
1523 matches_count = 0;
1526 /* *************************** */
1527 /* Terminal utility functions. */
1528 /* *************************** */
1530 /* ===================================================================== */
1531 /* outch is a function version of putchar that can be passed to tputs as */
1532 /* a routine to call. */
1533 /* ===================================================================== */
1535 #ifdef __sun
1536 outch(char c)
1537 #else
1538 outch(int c)
1539 #endif
1541 putchar(c);
1542 return 1;
1545 /* =============================================== */
1546 /* Set the terminal in non echo/non canonical mode */
1547 /* wait for at least one byte, no timeout. */
1548 /* =============================================== */
1549 void
1550 setup_term(int const fd,
1551 struct termios *old_in_attrs,
1552 struct termios *new_in_attrs)
1554 int error;
1556 /* Save the terminal parameters and configure it in row mode. */
1557 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1558 tcgetattr(fd, old_in_attrs);
1560 new_in_attrs->c_iflag = 0;
1561 new_in_attrs->c_oflag = old_in_attrs->c_oflag;
1562 new_in_attrs->c_cflag = old_in_attrs->c_cflag;
1563 new_in_attrs->c_lflag = old_in_attrs->c_lflag & ~(ICANON | ECHO | ISIG);
1565 new_in_attrs->c_cc[VMIN] = 1; /* wait for at least 1 byte. */
1566 new_in_attrs->c_cc[VTIME] = 0; /* no timeout. */
1568 error = tcsetattr_safe(fd, TCSANOW, new_in_attrs);
1570 if (error == -1)
1572 perror("smenu");
1573 exit(EXIT_FAILURE);
1577 /* ====================================== */
1578 /* Set the terminal in its previous mode. */
1579 /* ====================================== */
1580 void
1581 restore_term(int const fd, struct termios *old_in_attrs)
1583 int error;
1585 error = tcsetattr_safe(fd, TCSANOW, old_in_attrs);
1587 if (error == -1)
1589 perror("smenu");
1590 exit(EXIT_FAILURE);
1594 /* ============================================== */
1595 /* Get the terminal numbers of lines and columns */
1596 /* Assume that the TIOCGWINSZ, ioctl is available */
1597 /* Defaults to 80x24. */
1598 /* ============================================== */
1599 void
1600 get_terminal_size(int * const r, int * const c, term_t *term)
1602 struct winsize ws;
1604 *r = *c = -1;
1606 if (ioctl(0, TIOCGWINSZ, &ws) == 0)
1608 *r = ws.ws_row;
1609 *c = ws.ws_col;
1611 if (*r > 0 && *c > 0)
1612 return;
1615 *r = tigetnum("lines");
1616 *c = tigetnum("cols");
1618 if (*r <= 0 || *c <= 0)
1620 *r = 80;
1621 *c = 24;
1625 /* =========================================================== */
1626 /* Gets the cursor position in the terminal. */
1627 /* Assume that the Escape sequence ESC [ 6 n is available. */
1628 /* Retries up to 64 times in case of system call interruption. */
1629 /* Returns 1 on success and 0 on error. */
1630 /* =========================================================== */
1632 get_cursor_position(int * const r, int * const c)
1634 char buf[32] = { 0 };
1635 char *s;
1637 int attempts = 64;
1638 int v;
1639 int rc = 1;
1641 int ask; /* Number of asked characters. */
1642 int got; /* Number of characters obtained. */
1644 int buf_size = sizeof(buf);
1646 /* Report cursor location. */
1647 /* """"""""""""""""""""""" */
1648 while ((v = write(STDOUT_FILENO, "\x1b[6n", 4)) == -1 && attempts)
1650 if (errno == EINTR)
1651 attempts--;
1652 else
1654 rc = 0;
1655 goto read;
1658 errno = 0;
1661 if (v != 4)
1662 rc = 0;
1664 read:
1666 /* Read the response: ESC [ rows ; cols R. */
1667 /* """"""""""""""""""""""""""""""""""""""" */
1668 *(s = buf) = 0;
1672 ask = buf_size - 1 - (s - buf);
1673 got = read(STDIN_FILENO, s, ask);
1675 if (got < 0 && errno == EINTR)
1676 got = 0;
1677 else if (got == 0)
1678 break;
1680 s += got;
1681 } while (strchr(buf, 'R') == NULL);
1683 /* Parse it. */
1684 /* """"""""" */
1685 if (buf[0] != 0x1b || buf[1] != '[')
1686 return 0;
1688 if (sscanf(buf + 2, "%d;%d", r, c) != 2)
1689 rc = 0;
1691 return rc;
1694 /* ====================================================== */
1695 /* Returns 1 if a string is empty or only made of spaces. */
1696 /* Non UTF-8 version. */
1697 /* ====================================================== */
1699 isempty_non_utf8(const unsigned char *s)
1701 while (*s != '\0')
1703 if (*s != ' ' && *s != '\t')
1704 return 0;
1705 s++;
1707 return 1;
1710 /* ====================================================== */
1711 /* Returns 1 if a string is empty or only made of spaces. */
1712 /* UTF-8 version. */
1713 /* ====================================================== */
1715 isempty_utf8(const unsigned char *s)
1717 unsigned char c, d;
1719 while (*s != '\0')
1721 if (*s == ' ' || *s == '\t') /* Normal ASCII spaces. */
1722 goto next;
1724 if (*s < 0xc2) /* Not an UTF-8 space -> return FALSE. */
1725 return 0;
1727 /* Scanning for a potential non UTF-8 spaces scanning. */
1728 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
1729 if ((c = *(s + 1)) != '\0')
1731 if (*s == 0xc2 && (c == 0x85 || c == 0xa0))
1733 s++;
1734 goto next; /* Unnamed control character or NO-BREAK SPACE. */
1737 if ((d = *(s + 2)) == '\0')
1738 return 0;
1740 if (*s == 0xe1 && c == 0x9a && d == 0x80)
1742 s += 2;
1743 goto next; /* OGHAM SPACE MARK. */
1746 if (*s == 0xe1 && c == 0xa0 && d == 0x8e)
1748 s += 2;
1749 goto next; /* MONGOLIAN VOWEL SEPARATOR. */
1752 if (*s == 0xe2 && c == 0x80 && d == 0x80)
1754 s += 2;
1755 goto next; /* EN QUAD. */
1758 if (*s == 0xe2 && c == 0x80 && d == 0x81)
1760 s += 2;
1761 goto next; /* EM QUAD. */
1764 if (*s == 0xe2 && c == 0x80 && d == 0x82)
1766 s += 2;
1767 goto next; /* EN SPACE. */
1769 if (*s == 0xe2 && c == 0x80 && d == 0x83)
1771 s += 2;
1772 goto next; /* EM SPACE. */
1775 if (*s == 0xe2 && c == 0x80 && d == 0x84)
1777 s += 2;
1778 goto next; /* THREE-PER-EM SPACE. */
1781 if (*s == 0xe2 && c == 0x80 && d == 0x85)
1783 s += 2;
1784 goto next; /* FOUR-PER-EM SPACE. */
1787 if (*s == 0xe2 && c == 0x80 && d == 0x86)
1789 s += 2;
1790 goto next; /* SIX-PER-EM SPACE. */
1792 if (*s == 0xe2 && c == 0x80 && d == 0x87)
1794 s += 2;
1795 goto next; /* FIGURE SPACE. */
1798 if (*s == 0xe2 && c == 0x80 && d == 0x88)
1800 s += 2;
1801 goto next; /* PUNCTUATION SPACE. */
1804 if (*s == 0xe2 && c == 0x80 && d == 0x89)
1806 s += 2;
1807 goto next; /* THIN SPACE. */
1810 if (*s == 0xe2 && c == 0x80 && d == 0x8a)
1812 s += 2;
1813 goto next; /* HAIR SPACE. */
1816 if (*s == 0xe2 && c == 0x80 && d == 0xa8)
1818 s += 2;
1819 goto next; /* LINE SEPARATOR. */
1822 if (*s == 0xe2 && c == 0x80 && d == 0xa9)
1824 s += 2;
1825 goto next; /* PARAGRAPH SEPARATOR. */
1828 if (*s == 0xe2 && c == 0x80 && d == 0xaf)
1830 s += 2;
1831 goto next; /* NARROW NO-BREAK SPACE. */
1834 if (*s == 0xe2 && c == 0x81 && d == 0x9f)
1836 s += 2;
1837 goto next; /* MEDIUM MATHEMATICAL SPACE. */
1840 if (*s == 0xe3 && c == 0x80 && d == 0x80)
1842 s += 2;
1843 goto next; /* IDEOGRAPHIC SPACE. */
1846 return 0;
1848 next:
1849 s++;
1851 return 1;
1854 /* ======================================================================== */
1855 /* Parse a regular expression based selector. */
1856 /* The string to parse is bounded by a delimiter so we must parse something */
1857 /* like: <delim><regex string><delim> as in /a.*b/ by example. */
1858 /* */
1859 /* str (in) delimited string to parse */
1860 /* regex_list (out) regex list to modify. */
1861 /* ======================================================================== */
1862 void
1863 parse_regex_selector_part(char *str, ll_t **regex_list)
1865 regex_t *regex;
1867 /* Remove the last character of str (the delimiter).*/
1868 /* """""""""""""""""""""""""""""""""""""""""""""""" */
1869 str[strlen(str) - 1] = '\0';
1871 /* Ignore the first character of str (the delimiter). */
1872 /* compile it and add a compiled regex in a dedicated list. */
1873 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1874 regex = xmalloc(sizeof(regex_t));
1875 if (regcomp(regex, str + 1, REG_EXTENDED | REG_NOSUB) == 0)
1877 if (*regex_list == NULL)
1878 *regex_list = ll_new();
1880 ll_append(*regex_list, regex);
1884 /* ================================================================= */
1885 /* Compare two elements of type attr_elem_t. */
1886 /* The comparison key is the first member of the structure which is */
1887 /* of type attrib_t. */
1888 /* Returns 0 is the two elements are equals otherwise returns 1. */
1889 /* ================================================================= */
1891 attr_elem_cmp(void const *a, void const *b)
1893 const attr_elem_t *ai = a;
1894 const attr_elem_t *bi = b;
1896 if (ai->attr->fg != bi->attr->fg)
1897 return 1;
1898 if (ai->attr->bg != bi->attr->bg)
1899 return 1;
1900 if (ai->attr->bold != bi->attr->bold)
1901 return 1;
1902 if (ai->attr->dim != bi->attr->dim)
1903 return 1;
1904 if (ai->attr->reverse != bi->attr->reverse)
1905 return 1;
1906 if (ai->attr->standout != bi->attr->standout)
1907 return 1;
1908 if (ai->attr->underline != bi->attr->underline)
1909 return 1;
1910 if (ai->attr->italic != bi->attr->italic)
1911 return 1;
1912 if (ai->attr->invis != bi->attr->invis)
1913 return 1;
1914 if (ai->attr->blink != bi->attr->blink)
1915 return 1;
1917 return 0;
1920 /* ===================================================================== */
1921 /* Parse a column or row selector string whose syntax is defined as: */
1922 /* [<letter>]<item1>|,<item>|,... */
1923 /* <item> is <range>| <delimited regex> */
1924 /* <item> is (<range>| <delimited regex>):<attribute> if letter is a|A. */
1925 /* <range> is n1-n2 | n1 | -n2 | n1- where n1 starts with 1. */
1926 /* <delimited regex> is <char><regex><char> (e.g. /<regex>/). */
1927 /* */
1928 /* <letter> is a|A|s|S|r|R|u|U where: */
1929 /* i|I is for 'include'. */
1930 /* e|E is for 'exclude'. */
1931 /* l|L is for 'left' alignment. */
1932 /* r|R is for 'right' alignment. */
1933 /* c|C is for 'center' alignment. */
1934 /* a|A for defining attributes for rows or columns. */
1935 /* */
1936 /* str (in) string to parse. */
1937 /* filter (out) is INCLUDE_FILTER or EXCLUDE_FILTER according */
1938 /* to <letter>. */
1939 /* unparsed (out) is empty on success and contains the unparsed */
1940 /* part on failure. */
1941 /* inc_interval_list (out) is a list of the interval of elements to */
1942 /* be included. */
1943 /* inc_regex_list (out) is a list of extended regular expressions of */
1944 /* elements to be included. */
1945 /* exc_interval_list (out) is a list of the interval of elements to be */
1946 /* excluded. */
1947 /* exc_regex_list (out) is a list of regex matching elements to */
1948 /* be excluded. */
1949 /* aX_interval_list (out) is a list of the interval of elements to be */
1950 /* aligned to the left, right or centered. */
1951 /* aX_regex_list (out) is a list of regex matching elements to */
1952 /* be aligned to the left, right or centered. */
1953 /* at_interval_list (out) is a list of the interval of elements with */
1954 /* a given attribute. */
1955 /* at_regex_list (out) is a list of regex matching elements with */
1956 /* a given attribute. */
1957 /* ===================================================================== */
1958 void
1959 parse_selectors(char *str,
1960 filters_t *filter,
1961 char **unparsed,
1962 ll_t **inc_interval_list,
1963 ll_t **inc_regex_list,
1964 ll_t **exc_interval_list,
1965 ll_t **exc_regex_list,
1966 ll_t **al_interval_list,
1967 ll_t **al_regex_list,
1968 ll_t **ar_interval_list,
1969 ll_t **ar_regex_list,
1970 ll_t **ac_interval_list,
1971 ll_t **ac_regex_list,
1972 ll_t **at_interval_list,
1973 ll_t **at_regex_list,
1974 alignment_t *default_alignment,
1975 win_t *win,
1976 misc_t *misc,
1977 term_t *term)
1979 char c;
1980 long start = 1; /* column string offset in the parsed string. */
1981 long first, second; /* range starting and ending values. */
1982 int l_open_range; /* 1 if the range is left-open. */
1983 int r_open_range; /* 1 if the range is right-open. */
1984 char *ptr; /* pointer to the remaining string to parse. */
1985 interval_t *interval;
1986 selector_t type;
1987 char *attr_str = NULL;
1988 attrib_t *attr;
1989 attr_elem_t *attr_elem;
1991 /* Replace the UTF-8 string representation in the selector by */
1992 /* their binary values. */
1993 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
1994 utf8_interpret(str, misc->invalid_char_substitute);
1996 /* Get the first character to see if this is */
1997 /* an additive or restrictive operation. */
1998 /* """"""""""""""""""""""""""""""""""""""""" */
1999 if (sscanf(str, "%c", &c) == 0)
2000 return;
2002 switch (c)
2004 case 'l':
2005 case 'L':
2006 if (!win->col_mode)
2008 *unparsed = xstrdup(str);
2009 return;
2011 type = ALEFT;
2012 break;
2014 case 'r':
2015 case 'R':
2016 if (!win->col_mode)
2018 *unparsed = xstrdup(str);
2019 return;
2021 type = ARIGHT;
2022 break;
2024 case 'c':
2025 case 'C':
2026 if (!win->col_mode)
2028 *unparsed = xstrdup(str);
2029 return;
2031 type = ACENTER;
2032 break;
2034 case 'i':
2035 case 'I':
2036 type = IN;
2037 *filter = INCLUDE_FILTER;
2038 break;
2040 case 'e':
2041 case 'E':
2042 type = EX;
2043 *filter = EXCLUDE_FILTER;
2044 break;
2046 case '1':
2047 case '2':
2048 case '3':
2049 case '4':
2050 case '5':
2051 case '6':
2052 case '7':
2053 case '8':
2054 case '9':
2055 type = IN;
2056 *filter = INCLUDE_FILTER;
2057 start = 0;
2058 break;
2060 case 'a': /* Attribute. */
2061 type = ATTR;
2062 break;
2064 default:
2065 if (!isgraph(c))
2067 *unparsed = strprint(str);
2068 return;
2071 type = IN;
2072 *filter = INCLUDE_FILTER;
2073 start = 0;
2074 break;
2077 /* Set ptr to the start of the interval list to parse. */
2078 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2079 ptr = str + start;
2081 if (*ptr == '\0' && *default_alignment == AL_NONE)
2082 switch (type)
2084 case ALEFT:
2085 *default_alignment = AL_LEFT;
2086 break;
2087 case ARIGHT:
2088 *default_alignment = AL_RIGHT;
2089 break;
2090 case ACENTER:
2091 *default_alignment = AL_CENTERED;
2092 break;
2093 default:
2094 *unparsed = xstrdup(str);
2095 return;
2098 /* Scan the comma separated ranges. */
2099 /* '\' can be used to escape a ','. */
2100 /* """""""""""""""""""""""""""""""" */
2101 while (*ptr)
2103 int is_range = 0;
2104 char delim1, delim2 = '\0';
2105 char *oldptr;
2106 char *colon = NULL;
2108 l_open_range = r_open_range = 0;
2109 first = second = -1;
2110 oldptr = ptr;
2112 while (*ptr && *ptr != ',')
2114 if (*ptr == '-')
2116 is_range = 1;
2117 ptr++;
2119 else if (*ptr == '\\' && *(ptr + 1) != '\0' && *(ptr + 1) == ',')
2120 ptr += 2;
2121 else if (type == ATTR && *ptr == '\\' && *(ptr + 1) != '\0'
2122 && *(ptr + 1) == ':')
2123 ptr += 2;
2124 else if (type == ATTR && *ptr && *ptr == ':')
2126 colon = ptr;
2127 ptr++;
2129 else
2130 ptr++;
2133 /* Forbid the trailing comma (ex: xxx,). */
2134 /* """"""""""""""""""""""""""""""""""""" */
2135 if (*ptr == ',' && *(ptr + 1) == '\0')
2137 *unparsed = strprint(ptr);
2138 return;
2141 /* Forbid the empty patterns (ex: xxx,,yyy). */
2142 /* """"""""""""""""""""""""""""""""""""""""" */
2143 if (oldptr == ptr)
2145 *unparsed = strprint(ptr);
2146 return;
2149 /* Mark the end of the interval found. */
2150 /* """"""""""""""""""""""""""""""""""" */
2151 if (*ptr)
2152 *ptr++ = '\0';
2154 delim1 = *(str + start);
2155 if (delim1 == '-')
2156 l_open_range = 1;
2158 if (type != ATTR)
2160 if (*ptr == '\0')
2161 delim2 = *(ptr - 1);
2162 else if (ptr > str + start + 2)
2163 delim2 = *(ptr - 2);
2165 else
2167 if (colon == NULL || colon == str + start)
2169 *unparsed = strprint(str + start);
2170 return;
2173 delim2 = *(colon - 1);
2176 /* Regex ranges are not yet supported in selectors. */
2177 /* """""""""""""""""""""""""""""""""""""""""""""""" */
2178 if (!isdigit(delim1) && delim1 != '-' && is_range)
2180 *unparsed = strprint(str + start);
2181 return;
2184 if (delim2 == '-')
2185 r_open_range = 1;
2187 /* Check is we have found a well described regular expression. */
2188 /* If the segment to parse contains at least three characters */
2189 /* then delim1 and delim2 point to the first and last */
2190 /* delimiter of the regular expression. */
2191 /* E.g. /abc/ */
2192 /* ^ ^ */
2193 /* | | */
2194 /* delim1 delim2 */
2195 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2196 if (ptr > str + start + 2 && delim1 == delim2 && isgraph(delim1)
2197 && isgraph(delim2) && !isdigit(delim1) && !isdigit(delim2))
2199 /* Process the regex. */
2200 /* """""""""""""""""" */
2201 switch (type)
2203 case IN:
2204 parse_regex_selector_part(str + start, inc_regex_list);
2205 break;
2206 case EX:
2207 parse_regex_selector_part(str + start, exc_regex_list);
2208 break;
2209 case ALEFT:
2210 parse_regex_selector_part(str + start, al_regex_list);
2211 break;
2212 case ARIGHT:
2213 parse_regex_selector_part(str + start, ar_regex_list);
2214 break;
2215 case ACENTER:
2216 parse_regex_selector_part(str + start, ac_regex_list);
2217 break;
2218 case ATTR:
2219 *colon = '\0';
2220 /* xxxxx\0yyyy */
2221 /* | | | */
2222 /* | | attr part */
2223 /* | colon */
2224 /* str+start */
2225 /* """""""""""""""" */
2227 attr = attr_new();
2229 /* parse the attribute part (after the colon) */
2230 /* """""""""""""""""""""""""""""""""""""""""" */
2231 if (!parse_attr(colon + 1, attr, term->colors))
2233 *unparsed = strprint(str + start);
2234 free(attr);
2235 return;
2238 /* Parse the regex part (before the colon) and add it to the */
2239 /* list of elements of type attr_elem_t. */
2240 /* In each of these elements a list of regex for the same */
2241 /* attributes is updated. */
2242 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2243 if (*at_regex_list == NULL) /* The list doesn't already exists. */
2245 *at_regex_list = ll_new();
2246 attr_elem_t *elem = xmalloc(sizeof(attr_elem_t));
2247 elem->attr = attr;
2248 elem->list = NULL;
2249 parse_regex_selector_part(str + start, &elem->list);
2250 ll_append(*at_regex_list, elem);
2252 else
2254 attr_elem_t e;
2255 ll_node_t *node;
2256 e.attr = attr;
2257 /* Update the list of regex of the attribute attr. */
2258 /* """"""""""""""""""""""""""""""""""""""""""""""" */
2259 if ((node = ll_find(*at_regex_list, &e, attr_elem_cmp)) != NULL)
2261 ((attr_elem_t *)(node->data))->attr = attr;
2262 parse_regex_selector_part(str + start,
2263 &((attr_elem_t *)(node->data))->list);
2265 else
2267 attr_elem_t *elem = xmalloc(sizeof(attr_elem_t));
2268 elem->attr = attr;
2269 elem->list = NULL;
2270 parse_regex_selector_part(str + start, &elem->list);
2271 ll_append(*at_regex_list, elem);
2275 break;
2278 /* Adjust the start of the new interval to read in the */
2279 /* initial string. */
2280 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2281 start = ptr - str;
2283 continue;
2286 if (is_range)
2288 /* We must parse 2 numbers separated by a dash. */
2289 /* """""""""""""""""""""""""""""""""""""""""""" */
2291 int rc;
2292 int pos;
2294 if (l_open_range == 0 && r_open_range == 0)
2296 rc = sscanf(str + start, "%ld-%ld%n", &first, &second, &pos);
2298 if (type != ATTR)
2300 if (rc != 2 || *(str + start + pos) != '\0')
2302 *unparsed = strprint(str + start);
2303 return;
2306 else
2308 if (*(str + start + pos) != ':')
2310 if (rc != 2 || *(str + start + pos) != '\0')
2311 *unparsed = strprint(str + start + pos);
2312 else
2313 *unparsed = strprint(str + start);
2314 return;
2316 else
2317 attr_str = xstrdup(str + start + pos + 1);
2320 else if (l_open_range == 1 && r_open_range == 0)
2322 rc = sscanf(str + start, "-%ld%n", &second, &pos);
2324 if (type != ATTR)
2326 if (rc != 1 || *(str + start + pos) != '\0')
2328 *unparsed = strprint(str + start);
2329 return;
2332 else
2334 if (*(str + start + pos) != ':')
2336 if (rc != 1 || *(str + start + pos) != '\0')
2337 *unparsed = strprint(str + start + pos);
2338 else
2339 *unparsed = strprint(str + start);
2340 return;
2342 else
2343 attr_str = xstrdup(str + start + pos + 1);
2346 first = 1;
2348 else if (l_open_range == 0 && r_open_range == 1)
2350 rc = sscanf(str + start, "%ld-%n", &first, &pos);
2352 if (type != ATTR)
2354 if (rc != 1 || *(str + start + pos) != '\0')
2356 *unparsed = strprint(str + start);
2357 return;
2360 else
2362 if (*(str + start + pos) != ':')
2364 if (rc != 1 || *(str + start + pos) != '\0')
2365 *unparsed = strprint(str + start + pos);
2366 else
2367 *unparsed = strprint(str + start);
2368 return;
2370 else
2371 attr_str = xstrdup(str + start + pos + 1);
2374 second = LONG_MAX;
2377 if (first < 1 || second < 1)
2379 /* Both interval boundaries must be strictly positive. */
2380 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2381 *unparsed = strprint(str + start);
2382 return;
2385 /* Ensure that the low bound of the interval is lower or equal */
2386 /* to the high one. Swap them if needed. */
2387 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2388 interval = interval_new();
2390 if (first > second)
2392 size_t swap;
2394 swap = first;
2395 first = second;
2396 second = swap;
2399 interval->low = first - 1;
2400 interval->high = second - 1;
2402 else
2404 /* We must parse a single number. */
2405 /* """""""""""""""""""""""""""""" */
2407 int rc;
2408 int pos;
2410 rc = sscanf(str + start, "%ld%n", &first, &pos);
2412 if (type != ATTR)
2414 if (rc != 1 || *(str + start + pos) != '\0')
2416 *unparsed = strprint(str + start);
2417 return;
2420 else
2422 if (*(str + start + pos) != ':')
2424 if (rc != 1 || *(str + start + pos) != '\0')
2425 *unparsed = strprint(str + start + pos);
2426 else
2427 *unparsed = strprint(str + start);
2428 return;
2430 else
2431 attr_str = xstrdup(str + start + pos + 1);
2434 interval = interval_new();
2435 interval->low = interval->high = first - 1;
2438 /* Adjust the start of the new interval to read in the */
2439 /* initial string. */
2440 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2441 start = ptr - str;
2443 /* Add the new interval to the correct list. */
2444 /* """"""""""""""""""""""""""""""""""""""""" */
2445 switch (type)
2447 case IN:
2448 if (*inc_interval_list == NULL)
2449 *inc_interval_list = ll_new();
2451 ll_append(*inc_interval_list, interval);
2452 break;
2454 case EX:
2455 if (*exc_interval_list == NULL)
2456 *exc_interval_list = ll_new();
2458 ll_append(*exc_interval_list, interval);
2459 break;
2461 case ALEFT:
2462 if (*al_interval_list == NULL)
2463 *al_interval_list = ll_new();
2465 ll_append(*al_interval_list, interval);
2466 break;
2468 case ARIGHT:
2469 if (*ar_interval_list == NULL)
2470 *ar_interval_list = ll_new();
2472 ll_append(*ar_interval_list, interval);
2473 break;
2475 case ACENTER:
2476 if (*ac_interval_list == NULL)
2477 *ac_interval_list = ll_new();
2479 ll_append(*ac_interval_list, interval);
2480 break;
2482 case ATTR:
2483 attr = attr_new();
2484 if (parse_attr(attr_str, attr, term->colors))
2486 free(attr_str);
2487 attr_elem = xmalloc(sizeof(attr_elem_t));
2488 attr_elem->attr = attr;
2490 if (*at_interval_list == NULL)
2492 *at_interval_list = ll_new();
2493 attr_elem->list = ll_new();
2494 ll_append(attr_elem->list, interval);
2495 ll_append(*at_interval_list, attr_elem);
2497 else
2499 ll_node_t *node;
2500 if ((node = ll_find(*at_interval_list, attr_elem, attr_elem_cmp))
2501 != NULL)
2503 free(attr_elem);
2504 attr_elem = (attr_elem_t *)node->data;
2506 ll_append(attr_elem->list, interval);
2508 else
2510 attr_elem->list = ll_new();
2511 ll_append(attr_elem->list, interval);
2512 ll_append(*at_interval_list, attr_elem);
2516 else
2518 free(attr_str);
2519 *unparsed = strprint(str + start);
2520 free(attr);
2521 return;
2523 break;
2527 /* If we are here, then all the intervals have be successfully parsed */
2528 /* Ensure that the unparsed string is empty. */
2529 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2530 *unparsed = xstrdup("");
2533 /* ===================================================================== */
2534 /* Parse a commas separated sequence of regular expression. */
2535 /* Uses to align to the left, right or center some words base on regular */
2536 /* expressions. */
2537 /* */
2538 /* str (in) sequence of regular expression. */
2539 /* al_regex_list (out) list of RE for left-aligned words. */
2540 /* ar_regex_list (out) list of RE for left-aligned words. */
2541 /* ac_regex_list (out) list of RE for centered words. */
2542 /* default_alignment (out) new default alignment. */
2543 /* misc (in) used by utf8_interpret. */
2544 /* ===================================================================== */
2545 void
2546 parse_al_selectors(char *str,
2547 char **unparsed,
2548 ll_t **al_regex_list,
2549 ll_t **ar_regex_list,
2550 ll_t **ac_regex_list,
2551 alignment_t *default_alignment,
2552 misc_t *misc)
2554 char c;
2555 size_t start = 1; /* column string offset in the parsed string. */
2556 char *ptr; /* pointer to the remaining string to parse. */
2557 int type;
2559 /* Replace the UTF-8 string representation in the selector by */
2560 /* their binary values. */
2561 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2562 utf8_interpret(str, misc->invalid_char_substitute);
2564 /* Get the first character to see if this is */
2565 /* an additive or restrictive operation. */
2566 /* """"""""""""""""""""""""""""""""""""""""" */
2567 if (sscanf(str, "%c", &c) == 0)
2568 return;
2570 type = *default_alignment;
2572 switch (c)
2574 case 'l':
2575 case 'L':
2576 type = ALEFT;
2577 break;
2579 case 'r':
2580 case 'R':
2581 type = ARIGHT;
2582 break;
2584 case 'c':
2585 case 'C':
2586 type = ACENTER;
2587 break;
2589 default:
2590 if (!isgraph(c))
2591 return;
2593 start = 0;
2594 break;
2597 /* Set ptr to the start of the interval list to parse. */
2598 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2599 ptr = str + start;
2601 if (*ptr == '\0' && *default_alignment == AL_NONE)
2602 switch (type)
2604 case ALEFT:
2605 *default_alignment = AL_LEFT;
2606 break;
2607 case ARIGHT:
2608 *default_alignment = AL_RIGHT;
2609 break;
2610 case ACENTER:
2611 *default_alignment = AL_CENTERED;
2612 break;
2613 default:
2614 *unparsed = xstrdup(str);
2615 return;
2618 /* Scan the comma separated ranges. */
2619 /* '\' can be used to escape a ','. */
2620 /* """""""""""""""""""""""""""""""" */
2621 while (*ptr)
2623 char delim1, delim2 = '\0';
2624 char *oldptr;
2626 oldptr = ptr;
2627 while (*ptr && *ptr != ',')
2629 if (*ptr == '\\' && *(ptr + 1) != '\0' && *(ptr + 1) == ',')
2630 ptr += 2;
2631 else
2632 ptr++;
2635 /* Forbid the trailing comma (ex: xxx,). */
2636 /* """"""""""""""""""""""""""""""""""""" */
2637 if (*ptr == ',' && (*(ptr + 1) == '\0'))
2639 *unparsed = xstrdup(ptr);
2640 return;
2643 /* Forbid the empty patterns (ex: xxx,,yyy). */
2644 /* """"""""""""""""""""""""""""""""""""""""" */
2645 if (oldptr == ptr)
2647 *unparsed = xstrdup(ptr);
2648 return;
2651 /* Mark the end of the interval found. */
2652 /* """"""""""""""""""""""""""""""""""" */
2653 if (*ptr)
2654 *ptr++ = '\0';
2656 /* If the regex contains at least three characters then delim1 */
2657 /* and delim2 point to the first and last delimiter of the */
2658 /* regular expression. E.g. /abc/ */
2659 /* ^ ^ */
2660 /* | | */
2661 /* delim1 delim2 */
2662 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2663 delim1 = *(str + start);
2664 if (*ptr == '\0')
2665 delim2 = *(ptr - 1);
2666 else if (ptr > str + start + 2)
2667 delim2 = *(ptr - 2);
2669 /* Forbid the empty patterns (ex: xxx,,yyy) and check is we have */
2670 /* found a well described regular expression. */
2671 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2672 if (ptr > str + start + 2 && delim1 == delim2 && isgraph(delim1)
2673 && isgraph(delim2) && !isdigit(delim1) && !isdigit(delim2))
2675 /* Process the regex. */
2676 /* """""""""""""""""" */
2677 switch (type)
2679 case ALEFT:
2680 parse_regex_selector_part(str + start, al_regex_list);
2681 break;
2682 case ARIGHT:
2683 parse_regex_selector_part(str + start, ar_regex_list);
2684 break;
2685 case ACENTER:
2686 parse_regex_selector_part(str + start, ac_regex_list);
2687 break;
2690 /* Adjust the start of the new interval to read in the */
2691 /* initial string. */
2692 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
2693 start = ptr - str;
2695 continue;
2697 else
2699 *unparsed = xstrdup(ptr - 1);
2700 return;
2704 /* If we are here, then all the intervals have be successfully parsed */
2705 /* Ensure that the unparsed string is empty. */
2706 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
2707 *unparsed = xstrdup("");
2710 /* ========================================================= */
2711 /* Parse the sed like string passed as argument to -S/-I/-E. */
2712 /* This updates the sed parameter. */
2713 /* Return 1 on success and 0 on error. */
2714 /* ========================================================= */
2716 parse_sed_like_string(sed_t *sed)
2718 char sep;
2719 char *first_sep_pos;
2720 char *last_sep_pos;
2721 char *buf;
2722 long index;
2723 unsigned char icase;
2724 char c;
2726 if (strlen(sed->pattern) < 4)
2727 return 0;
2729 /* Get the separator (the 1st character). */
2730 /* """""""""""""""""""""""""""""""""""""" */
2731 buf = xstrdup(sed->pattern);
2732 sep = buf[0];
2734 /* Space like separators are not permitted. */
2735 /* """""""""""""""""""""""""""""""""""""""" */
2736 if (isspace(sep))
2737 goto err;
2739 /* Get the extended regular expression. */
2740 /* """""""""""""""""""""""""""""""""""" */
2741 if ((first_sep_pos = strchr(buf + 1, sep)) == NULL)
2742 goto err;
2744 *first_sep_pos = '\0';
2746 /* Get the substitution string. */
2747 /* """""""""""""""""""""""""""" */
2748 if ((last_sep_pos = strchr(first_sep_pos + 1, sep)) == NULL)
2749 goto err;
2751 *last_sep_pos = '\0';
2753 sed->substitution = xstrdup(first_sep_pos + 1);
2755 /* Get the global indicator (trailing g) */
2756 /* and the visual indicator (trailing v) */
2757 /* and the stop indicator (trailing s). */
2758 /* """""""""""""""""""""""""""""""""""""" */
2759 sed->global = sed->visual = icase = (unsigned char)0;
2761 index = 1;
2762 while ((c = *(last_sep_pos + index)) != '\0')
2764 if (c == 'g')
2765 sed->global = (unsigned char)1;
2766 else if (c == 'v')
2767 sed->visual = (unsigned char)1;
2768 else if (c == 's')
2769 sed->stop = (unsigned char)1;
2770 else if (c == 'i')
2771 icase = (unsigned char)1;
2772 else
2773 goto err;
2775 index++;
2778 /* Empty regular expression ? */
2779 /* """""""""""""""""""""""""" */
2780 if (*(buf + 1) == '\0')
2781 goto err;
2783 /* Compile the regular expression and abort on failure. */
2784 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
2785 if (regcomp(&(sed->re),
2786 buf + 1,
2787 !icase ? REG_EXTENDED : (REG_EXTENDED | REG_ICASE))
2788 != 0)
2789 goto err;
2791 free(buf);
2793 return 1;
2795 err:
2796 free(buf);
2798 return 0;
2801 /* ===================================================================== */
2802 /* Utility function used by replace to expand the replacement string. */
2803 /* IN: */
2804 /* orig: matching part of the original string to be replaced. */
2805 /* repl: string containing the replacement directives */
2806 /* subs_a: array of ranges containing the start and end offset */
2807 /* of the remembered parts of the strings referenced */
2808 /* by the sequence \n where n is in [1,10]. */
2809 /* match_start/end: offset in orig for the current matched string */
2810 /* subs_nb: number of elements containing significant values in */
2811 /* the array described above. */
2812 /* error: set to 0 if no error in replacement string and to 1 */
2813 /* otherwise. */
2814 /* match: current match number in the original string. */
2815 /* */
2816 /* Return: */
2817 /* The modified string according to the content of repl. */
2818 /* ===================================================================== */
2819 char *
2820 build_repl_string(char *orig,
2821 char *repl,
2822 long match_start,
2823 long match_end,
2824 range_t *subs_a,
2825 long subs_nb,
2826 long match,
2827 int *error)
2829 size_t allocated = 16;
2830 size_t rsize = 0;
2831 char *str = xmalloc(allocated);
2832 int special = 0;
2833 long offset = match * subs_nb; /* offset of the 1st sub *
2834 | corresponding to the match. */
2836 *error = 0;
2838 if (*repl == '\0')
2839 str = xstrdup("");
2840 else
2841 while (!*error && *repl)
2843 switch (*repl)
2845 case '\\':
2846 if (special)
2848 if (allocated == rsize)
2849 str = xrealloc(str, allocated += 16);
2850 str[rsize] = '\\';
2851 rsize++;
2852 str[rsize] = '\0';
2853 special = 0;
2855 else
2856 special = 1;
2857 break;
2859 case '1':
2860 case '2':
2861 case '3':
2862 case '4':
2863 case '5':
2864 case '6':
2865 case '7':
2866 case '8':
2867 case '9':
2868 if (special)
2870 if ((*repl) - '0' <= subs_nb)
2872 long index = (*repl) - '0' - 1 + offset;
2873 long delta = subs_a[index].end - subs_a[index].start;
2875 if (allocated <= rsize + delta)
2876 str = xrealloc(str, allocated += (delta + 16));
2878 memcpy(str + rsize, orig + subs_a[index].start, delta);
2880 rsize += delta;
2881 str[rsize] = '\0';
2883 else
2885 *error = 1;
2886 break;
2888 special = 0;
2890 else
2892 if (allocated == rsize)
2893 str = xrealloc(str, allocated += 16);
2894 str[rsize] = *repl;
2895 rsize++;
2896 str[rsize] = '\0';
2898 break;
2900 case '0':
2901 if (special)
2903 long delta = match_end - match_start;
2905 if (allocated <= rsize + delta)
2906 str = xrealloc(str, allocated += (delta + 16));
2908 memcpy(str + rsize, orig + match_start, delta);
2910 rsize += delta;
2911 str[rsize] = '\0';
2913 special = 0;
2915 else
2917 if (allocated == rsize)
2918 str = xrealloc(str, allocated += 16);
2919 str[rsize] = *repl;
2920 rsize++;
2921 str[rsize] = '\0';
2922 special = 0;
2924 break;
2926 case '&':
2927 if (!special)
2929 long delta = match_end - match_start;
2931 if (allocated <= rsize + delta)
2932 str = xrealloc(str, allocated += (delta + 16));
2934 memcpy(str + rsize, orig + match_start, delta);
2936 rsize += delta;
2937 str[rsize] = '\0';
2938 break;
2941 /* No break here, '&' must be treated as a normal */
2942 /* character when protected. */
2943 /* '''''''''''''''''''''''''''''''''''''''''''''' */
2945 /* FALLTHROUGH */
2947 default:
2948 if (allocated == rsize)
2949 str = xrealloc(str, allocated += 16);
2950 str[rsize] = *repl;
2951 rsize++;
2952 str[rsize] = '\0';
2953 special = 0;
2955 repl++;
2958 if (special > 0)
2959 *error = 1;
2961 return str;
2964 /* ====================================================================== */
2965 /* Replace the part of a string matched by an extender regular expression */
2966 /* by the substitution string. */
2967 /* The regex used must have been previously compiled. */
2968 /* */
2969 /* orig: original string */
2970 /* sed: composite variable containing the regular expression, a */
2971 /* substitution string and various other information. */
2972 /* output: destination buffer. */
2973 /* */
2974 /* return 1 if the replacement has been successful else 0. */
2975 /* */
2976 /* NOTE: */
2977 /* uses the global variable word_buffer. */
2978 /* ====================================================================== */
2980 replace(char *orig, sed_t *sed)
2982 size_t match_nb = 0; /* number of matches in the original string. */
2983 int sub_nb = 0; /* number of remembered matches in the *
2984 | original sting. */
2985 size_t target_len = 0; /* length of the resulting string. */
2986 size_t subs_max = 0;
2988 if (*orig == '\0')
2989 return 1;
2991 range_t *matches_a = xmalloc(strlen(orig) * sizeof(range_t));
2992 range_t *subs_a = xmalloc(10 * sizeof(range_t));
2994 const char *p = orig; /* points to the end of the previous match. */
2995 regmatch_t m[10]; /* array containing the start/end offsets *
2996 | of the matches found. */
2998 while (1)
3000 size_t i = 0;
3001 size_t match; /* current match index. */
3002 size_t index = 0; /* current char index in the original string. */
3003 int nomatch = 0; /* equals to 1 when there is no more matching. */
3004 char *exp_repl; /* expanded replacement string. */
3006 if (*p == '\0')
3007 nomatch = 1;
3008 else
3009 nomatch = regexec(&sed->re, p, 10, m, 0);
3011 if (nomatch)
3013 if (match_nb > 0)
3015 for (index = 0; index < matches_a[0].start; index++)
3016 word_buffer[target_len++] = orig[index];
3018 for (match = 0; match < match_nb; match++)
3020 size_t len;
3021 size_t end;
3022 int error;
3024 exp_repl = build_repl_string(orig,
3025 sed->substitution,
3026 matches_a[match].start,
3027 matches_a[match].end,
3028 subs_a,
3029 subs_max,
3030 match,
3031 &error);
3033 if (error)
3035 fprintf(stderr,
3036 "Invalid matching group reference "
3037 "in the replacement string \"%s\".\n",
3038 sed->substitution);
3039 exit(EXIT_FAILURE);
3042 len = strlen(exp_repl);
3044 my_strcpy(word_buffer + target_len, exp_repl);
3045 target_len += len;
3046 free(exp_repl);
3048 index += matches_a[match].end - matches_a[match].start;
3050 if (match < match_nb - 1 && sed->global)
3051 end = matches_a[match + 1].start;
3052 else
3053 end = strlen(orig);
3055 while (index < end)
3056 word_buffer[target_len++] = orig[index++];
3058 word_buffer[target_len] = '\0';
3060 if (!sed->global)
3061 break;
3064 else
3066 my_strcpy(word_buffer, orig);
3067 free(matches_a);
3068 free(subs_a);
3069 return 0;
3072 free(matches_a);
3073 free(subs_a);
3074 return nomatch;
3077 subs_max = 0;
3078 for (i = 0; i < 10; i++)
3080 size_t start;
3081 size_t finish;
3083 if (m[i].rm_so == -1)
3084 break;
3086 start = m[i].rm_so + (p - orig);
3087 finish = m[i].rm_eo + (p - orig);
3089 if (i == 0)
3091 matches_a[match_nb].start = start;
3092 matches_a[match_nb].end = finish;
3093 match_nb++;
3094 if (match_nb > utf8_strlen(orig))
3095 goto fail;
3097 else
3099 subs_a[sub_nb].start = start;
3100 subs_a[sub_nb].end = finish;
3101 sub_nb++;
3102 subs_max++;
3105 if (m[0].rm_eo > 0)
3106 p += m[0].rm_eo;
3107 else
3108 p++; /* Empty match. */
3111 fail:
3112 free(matches_a);
3113 free(subs_a);
3114 return 0;
3117 /* ============================================================ */
3118 /* Remove all ANSI color codes from s and puts the result in d. */
3119 /* Memory space for d must have been allocated before. */
3120 /* ============================================================ */
3121 void
3122 strip_ansi_color(char *s, toggle_t *toggles, misc_t *misc)
3124 char *p = s;
3125 long len = strlen(s);
3127 while (*s != '\0')
3129 /* Remove a sequence of \x1b[...m from s. */
3130 /* """""""""""""""""""""""""""""""""""""" */
3131 if ((*s == 0x1b) && (*(s + 1) == '['))
3133 while ((*s != '\0') && (*s++ != 'm'))
3136 /* Convert a single \x1b in the invalid substitute character. */
3137 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3138 else if (*s == 0x1b)
3140 if (toggles->blank_nonprintable && len > 1)
3141 *s++ = ' '; /* Non printable character -> ' '. */
3142 else
3143 *s++ = misc->invalid_char_substitute;
3144 p++;
3146 /* No ESC char, we can move on. */
3147 /* """""""""""""""""""""""""""" */
3148 else
3149 *p++ = *s++;
3152 *p = '\0';
3155 /* ================================================================== */
3156 /* Callback function used by tst_traverse to insert the index */
3157 /* of a matching word index in the sorted list of the already matched */
3158 /* words. */
3159 /* Always succeeds and returns 1. */
3160 /* ================================================================== */
3162 set_matching_flag(void *elem)
3164 ll_t *list = (ll_t *)elem;
3166 ll_node_t *node = list->head;
3168 while (node)
3170 size_t pos;
3172 pos = *(size_t *)(node->data);
3173 if (word_a[pos].is_selectable)
3174 word_a[pos].is_matching = 1;
3176 insert_sorted_index(&matching_words_a,
3177 &matching_words_a_size,
3178 &matches_count,
3179 pos);
3181 node = node->next;
3183 return 1;
3186 /* ======================================================================= */
3187 /* Callback function used by tst_traverse applied to tst_word so this */
3188 /* function is applied on each node of this tst. */
3189 /* */
3190 /* Each node of this tst contains a linked list storing the indexes of */
3191 /* the words in the input flow. */
3192 /* Each position in this list is used to: */
3193 /* - mark the word at that position as matching, */
3194 /* - add this position in the sorted array matching_words_a. */
3195 /* Always succeeds and returns 1. */
3196 /* ======================================================================= */
3198 tst_cb(void *elem)
3200 /* The data attached to the string in the tst is a linked list of */
3201 /* position of the string in the input flow, This list is naturally */
3202 /* sorted. */
3203 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3204 ll_t *list = (ll_t *)elem;
3206 ll_node_t *node = list->head;
3208 while (node)
3210 size_t pos;
3212 pos = *(long *)(node->data);
3214 word_a[pos].is_matching = 1;
3215 insert_sorted_index(&matching_words_a,
3216 &matching_words_a_size,
3217 &matches_count,
3218 pos);
3220 node = node->next;
3222 return 1; /* OK. */
3225 /* **************** */
3226 /* Input functions. */
3227 /* **************** */
3229 /* ===================================================================== */
3230 /* Non delay reading of a scancode. */
3231 /* Update a scancodes buffer and return its length in bytes. */
3232 /* The length of the scancode cannot reach max which must be lower of */
3233 /* equal than 63 which is the size minus one of the buffer array defined */
3234 /* in main. */
3235 /* ===================================================================== */
3237 get_scancode(unsigned char *s, size_t max)
3239 int c;
3240 size_t i = 1;
3241 struct termios original_ts, nowait_ts;
3243 /* Wait until all data has been transmitted to stdin. */
3244 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
3245 tcdrain(0);
3247 if ((c = my_fgetc(stdin)) == EOF)
3248 return 0;
3250 /* Initialize the string with the first byte. */
3251 /* """""""""""""""""""""""""""""""""""""""""" */
3252 memset(s, '\0', max);
3253 s[0] = c;
3255 /* 0x1b (ESC) has been found, proceed to check if additional codes */
3256 /* are available. */
3257 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3258 if (c == 0x1b || c > 0x80)
3260 /* Save the terminal parameters and configure getchar() */
3261 /* to return immediately. */
3262 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
3263 tcgetattr(0, &original_ts);
3264 nowait_ts = original_ts;
3265 nowait_ts.c_lflag &= ~ISIG;
3266 nowait_ts.c_cc[VMIN] = 0;
3267 nowait_ts.c_cc[VTIME] = 0;
3269 /* tcsetattr_safe cannot fail here. */
3270 /* """""""""""""""""""""""""""""""" */
3271 (void)tcsetattr_safe(0, TCSADRAIN, &nowait_ts);
3273 /* Check if additional code is available after 0x1b. */
3274 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
3275 if ((c = my_fgetc(stdin)) != EOF)
3277 s[1] = c;
3279 i = 2;
3280 while (i < max && (c = my_fgetc(stdin)) != EOF)
3281 s[i++] = c;
3283 else
3285 /* There isn't a new code, this mean 0x1b came from ESC key. */
3286 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3289 /* Restore the save terminal parameters. */
3290 /* tcsetattr_safe cannot fail here. */
3291 /* """"""""""""""""""""""""""""""""""""" */
3292 (void)tcsetattr_safe(0, TCSADRAIN, &original_ts);
3294 /* Ignore EOF when a scancode contains an escape sequence. */
3295 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
3296 clearerr(stdin);
3299 return i;
3302 /* ============================================================ */
3303 /* Helper function to compare to delimiters for use by ll_find. */
3304 /* ============================================================ */
3306 buffer_cmp(const void *a, const void *b)
3308 return strcmp((const char *)a, (const char *)b);
3311 /* ===================================================================== */
3312 /* Get bytes from stdin. If the first byte is the leading character of a */
3313 /* UTF-8 glyph, the following ones are also read. */
3314 /* The utf8_get_length function is used to get the number of bytes of */
3315 /* the character. */
3316 /* ===================================================================== */
3318 get_bytes(FILE *input,
3319 char *utf8_buffer,
3320 ll_t *zapped_glyphs_list,
3321 langinfo_t *langinfo,
3322 misc_t *misc)
3324 int byte;
3325 int last;
3326 int n;
3330 last = 0;
3332 /* Read the first byte. */
3333 /* """""""""""""""""""" */
3334 byte = my_fgetc(input);
3336 if (byte == EOF)
3337 return EOF;
3339 utf8_buffer[last++] = byte;
3341 /* Check if we need to read more bytes to form a sequence */
3342 /* and put the number of bytes of the sequence in last. */
3343 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
3344 if (langinfo->utf8 && ((n = utf8_get_length(byte)) > 1))
3346 while (last < n && (byte = my_fgetc(input)) != EOF
3347 && (byte & 0xc0) == 0x80)
3348 utf8_buffer[last++] = byte;
3350 if (byte == EOF)
3351 return EOF;
3354 utf8_buffer[last] = '\0';
3356 /* Replace an invalid UTF-8 byte sequence by a single dot. */
3357 /* In this case the original sequence is lost (unsupported */
3358 /* encoding). */
3359 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3360 if (langinfo->utf8 && utf8_validate(utf8_buffer) != NULL)
3362 byte = utf8_buffer[0] = misc->invalid_char_substitute;
3363 utf8_buffer[1] = '\0';
3365 } while (ll_find(zapped_glyphs_list, utf8_buffer, buffer_cmp) != NULL);
3367 return byte;
3370 /* =======================================================================*/
3371 /* Expand the string str by replacing all its embedded special characters */
3372 /* by their corresponding escape sequence. */
3373 /* */
3374 /* dest must be long enough to contain the expanded string. */
3375 /* */
3376 /* Replace also UTF-8 glyphs by the substitution character if the */
3377 /* current locale if not UTF-8. */
3378 /* */
3379 /* Return the number of resulting glyphs. */
3380 /* ====================================================================== */
3381 size_t
3382 expand(char *src,
3383 char *dest,
3384 langinfo_t *langinfo,
3385 toggle_t *toggles,
3386 misc_t *misc)
3388 char c;
3389 int n;
3390 int all_spaces = 1;
3391 char *ptr = dest;
3392 size_t len = 0;
3394 while ((c = *(src++)))
3396 /* UTF-8 codepoints may take more than on character. */
3397 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
3398 if ((n = utf8_get_length(c)) > 1)
3400 all_spaces = 0;
3402 if (langinfo->utf8)
3403 /* If the locale is UTF-8 aware, copy src into ptr. */
3404 /* """""""""""""""""""""""""""""""""""""""""""""""" */
3407 *(ptr++) = c;
3408 len++;
3409 } while (--n && (c = *(src++)));
3410 else
3412 /* If not, ignore the bytes composing the UTF-8 glyph and replace */
3413 /* them with the substitution character. */
3414 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3417 /* Skip this byte. */
3418 /* ''''''''''''''' */
3419 } while (--n && ('\0' != *(src++)));
3421 *(ptr++) = misc->invalid_char_substitute;
3422 len++;
3425 else
3426 /* This is not a multibyte UTF-8 glyph. */
3427 /* """""""""""""""""""""""""""""""""""" */
3428 switch (c)
3430 case '\a':
3431 *(ptr++) = '\\';
3432 *(ptr++) = 'a';
3433 goto common_code;
3434 case '\b':
3435 *(ptr++) = '\\';
3436 *(ptr++) = 'b';
3437 goto common_code;
3438 case '\t':
3439 *(ptr++) = '\\';
3440 *(ptr++) = 't';
3441 goto common_code;
3442 case '\n':
3443 *(ptr++) = '\\';
3444 *(ptr++) = 'n';
3445 goto common_code;
3446 case '\v':
3447 *(ptr++) = '\\';
3448 *(ptr++) = 'v';
3449 goto common_code;
3450 case '\f':
3451 *(ptr++) = '\\';
3452 *(ptr++) = 'f';
3453 goto common_code;
3454 case '\r':
3455 *(ptr++) = '\\';
3456 *(ptr++) = 'r';
3457 goto common_code;
3458 case '\\':
3459 *(ptr++) = '\\';
3460 *(ptr++) = '\\';
3461 goto common_code;
3463 common_code:
3464 len += 2;
3465 all_spaces = 0;
3466 break;
3468 default:
3469 if (my_isprint(c))
3471 if (c != ' ')
3472 all_spaces = 0;
3474 *(ptr++) = c;
3476 else
3478 if (toggles->blank_nonprintable)
3479 *(ptr++) = ' '; /* Non printable character -> ' '. */
3480 else
3482 *(ptr++) = misc->invalid_char_substitute;
3483 all_spaces = 0;
3486 len++;
3490 /* If the word contains only spaces, replace them */
3491 /* by underscores so that it can be seen. */
3492 /* """""""""""""""""""""""""""""""""""""""""""""" */
3493 if (toggles->show_blank_words && all_spaces)
3494 memset(dest, misc->blank_char_substitute, len);
3496 *ptr = '\0'; /* Ensure that dest has a nul terminator. */
3498 return len;
3501 /* ===================================================================== */
3502 /* get_word(input): return a char pointer to the next word (as a string) */
3503 /* Accept: a FILE * for the input stream. */
3504 /* Return: a char * */
3505 /* On Success: the return value will point to a nul-terminated */
3506 /* string. */
3507 /* On Failure: the return value will be set to NULL. */
3508 /* ===================================================================== */
3509 char *
3510 get_word(FILE *input,
3511 ll_t *word_delims_list,
3512 ll_t *line_delims_list,
3513 ll_t *zapped_glyphs_list,
3514 char *utf8_buffer,
3515 unsigned char *is_last,
3516 toggle_t *toggles,
3517 langinfo_t *langinfo,
3518 win_t *win,
3519 limit_t *limits,
3520 misc_t *misc)
3522 char *temp = NULL;
3523 int byte;
3524 long utf8_count = 0; /* count chars used in current allocation. */
3525 long wordsize; /* size of current allocation in chars. */
3526 unsigned char is_dquote; /* double quote presence indicator. */
3527 unsigned char is_squote; /* single quote presence indicator. */
3528 int is_special; /* a character is special after a \ */
3530 /* Skip leading delimiters. */
3531 /* """""""""""""""""""""""" */
3533 byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
3534 while (byte != EOF
3535 && ll_find(word_delims_list, utf8_buffer, buffer_cmp) != NULL);
3537 if (byte == EOF)
3538 return NULL;
3540 /* Allocate initial word storage space. */
3541 /* """""""""""""""""""""""""""""""""""" */
3542 wordsize = CHARSCHUNK;
3543 temp = xmalloc(wordsize);
3545 /* Start stashing bytes. Stop when we meet a non delimiter or EOF. */
3546 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3547 utf8_count = 0;
3548 is_dquote = 0;
3549 is_squote = 0;
3550 is_special = 0;
3552 while (byte != EOF)
3554 size_t i = 0;
3556 if (utf8_count >= limits->word_length)
3558 fprintf(stderr,
3559 "The length of a word has reached the limit of "
3560 "%ld characters.\n",
3561 limits->word_length);
3563 exit(EXIT_FAILURE);
3566 if (byte == '\\' && !is_special)
3568 is_special = 1;
3569 goto next;
3572 /* Parse special characters. */
3573 /* """"""""""""""""""""""""" */
3574 if (is_special)
3575 switch (byte)
3577 case 'a':
3578 utf8_buffer[0] = byte = '\a';
3579 utf8_buffer[1] = '\0';
3580 break;
3582 case 'b':
3583 utf8_buffer[0] = byte = '\b';
3584 utf8_buffer[1] = '\0';
3585 break;
3587 case 't':
3588 utf8_buffer[0] = byte = '\t';
3589 utf8_buffer[1] = '\0';
3590 break;
3592 case 'n':
3593 utf8_buffer[0] = byte = '\n';
3594 utf8_buffer[1] = '\0';
3595 break;
3597 case 'v':
3598 utf8_buffer[0] = byte = '\v';
3599 utf8_buffer[1] = '\0';
3600 break;
3602 case 'f':
3603 utf8_buffer[0] = byte = '\f';
3604 utf8_buffer[1] = '\0';
3605 break;
3607 case 'r':
3608 utf8_buffer[0] = byte = '\r';
3609 utf8_buffer[1] = '\0';
3610 break;
3612 case 'u':
3613 utf8_buffer[0] = '\\';
3614 utf8_buffer[1] = 'u';
3615 utf8_buffer[2] = '\0';
3616 break;
3618 case 'U':
3619 utf8_buffer[0] = '\\';
3620 utf8_buffer[1] = 'U';
3621 utf8_buffer[2] = '\0';
3622 break;
3624 case '\\':
3625 utf8_buffer[0] = byte = '\\';
3626 utf8_buffer[1] = '\0';
3627 break;
3629 else
3631 if (!misc->ignore_quotes)
3633 /* Manage double quotes. */
3634 /* """"""""""""""""""""" */
3635 if (byte == '"' && !is_squote)
3636 is_dquote ^= 1;
3638 /* Manage single quotes. */
3639 /* """"""""""""""""""""" */
3640 if (byte == '\'' && !is_dquote)
3641 is_squote ^= 1;
3645 /* Only consider delimiters when outside quotations. */
3646 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
3647 if ((!is_dquote && !is_squote)
3648 && ll_find(word_delims_list, utf8_buffer, buffer_cmp) != NULL)
3649 break;
3651 /* We no dot count the significant quotes. */
3652 /* """"""""""""""""""""""""""""""""""""""" */
3653 if (!misc->ignore_quotes && !is_special
3654 && ((byte == '"' && !is_squote) || (byte == '\'' && !is_dquote)))
3656 is_special = 0;
3657 goto next;
3660 /* Feed temp with the content of utf8_buffer. */
3661 /* """""""""""""""""""""""""""""""""""""""""" */
3662 while (utf8_buffer[i] != '\0')
3664 if (utf8_count >= wordsize - 1)
3665 temp = xrealloc(temp,
3666 wordsize += (utf8_count / CHARSCHUNK + 1) * CHARSCHUNK);
3668 *(temp + utf8_count++) = utf8_buffer[i];
3669 i++;
3672 is_special = 0;
3674 next:
3675 byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
3678 /* Nul-terminate the word to make it a string. */
3679 /* """"""""""""""""""""""""""""""""""""""""""" */
3680 *(temp + utf8_count) = '\0';
3682 /* Replace the UTF-8 ASCII representations in the word just */
3683 /* read by their binary values. */
3684 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3685 utf8_interpret(temp, misc->invalid_char_substitute);
3687 /* Skip all field delimiters before a record delimiter. */
3688 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
3689 if (ll_find(line_delims_list, utf8_buffer, buffer_cmp) == NULL)
3691 byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
3693 while (byte != EOF
3694 && ll_find(word_delims_list, utf8_buffer, buffer_cmp) != NULL
3695 && ll_find(line_delims_list, utf8_buffer, buffer_cmp) == NULL)
3696 byte = get_bytes(input, utf8_buffer, zapped_glyphs_list, langinfo, misc);
3698 if (byte != EOF)
3700 if (langinfo->utf8 && utf8_get_length(utf8_buffer[0]) > 1)
3702 size_t pos;
3704 pos = strlen(utf8_buffer);
3705 while (pos > 0)
3706 my_ungetc(utf8_buffer[--pos], input);
3708 else
3709 my_ungetc(byte, input);
3713 /* Mark it as the last word of a record if its sequence matches a */
3714 /* record delimiter. */
3715 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
3716 if (byte == EOF
3717 || ((win->col_mode || win->line_mode || win->tab_mode)
3718 && ll_find(line_delims_list, utf8_buffer, buffer_cmp) != NULL))
3719 *is_last = 1;
3720 else
3721 *is_last = 0;
3723 /* Remove the ANSI color escape sequences from the word. */
3724 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
3725 strip_ansi_color(temp, toggles, misc);
3727 return temp;
3730 /* ================================================================ */
3731 /* Convert the 8 first colors from setf/setaf coding to setaf/setf. */
3732 /* ================================================================ */
3733 short
3734 color_transcode(short color)
3736 switch (color)
3738 case 1:
3739 return 4;
3740 case 3:
3741 return 6;
3742 case 4:
3743 return 1;
3744 case 6:
3745 return 3;
3746 default:
3747 return color;
3751 /* ========================================================== */
3752 /* Set a foreground color according to terminal capabilities. */
3753 /* ========================================================== */
3754 void
3755 set_foreground_color(term_t *term, short color)
3757 if (term->color_method == CLASSIC)
3759 if (term->has_setf)
3760 (void)tputs(TPARM2(set_foreground, color), 1, outch);
3761 if (term->has_setaf)
3762 (void)tputs(TPARM2(set_a_foreground, color_transcode(color)), 1, outch);
3765 else if (term->color_method == ANSI)
3767 if (term->has_setaf)
3768 (void)tputs(TPARM2(set_a_foreground, color), 1, outch);
3769 if (term->has_setf)
3770 (void)tputs(TPARM2(set_foreground, color_transcode(color)), 1, outch);
3774 /* ========================================================== */
3775 /* Set a background color according to terminal capabilities. */
3776 /* ========================================================== */
3777 void
3778 set_background_color(term_t *term, short color)
3780 if (term->color_method == CLASSIC)
3782 if (term->has_setb)
3783 (void)tputs(TPARM2(set_background, color), 1, outch);
3784 if (term->has_setab)
3785 (void)tputs(TPARM2(set_a_background, color_transcode(color)), 1, outch);
3788 else if (term->color_method == ANSI)
3790 if (term->has_setab)
3791 (void)tputs(TPARM2(set_a_background, color), 1, outch);
3792 if (term->has_setb)
3793 (void)tputs(TPARM2(set_background, color_transcode(color)), 1, outch);
3797 /* ======================================================= */
3798 /* Put a scrolling symbol at the first column of the line. */
3799 /* ======================================================= */
3800 void
3801 left_margin_putp(char *s, term_t *term, win_t *win)
3803 apply_attr(term, win->shift_attr);
3805 /* We won't print this symbol when not in column mode. */
3806 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
3807 if (*s != '\0')
3808 fputs_safe(s, stdout);
3810 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
3813 /* ====================================================== */
3814 /* Put a scrolling symbol at the last column of the line. */
3815 /* ====================================================== */
3816 void
3817 right_margin_putp(char *s1,
3818 char *s2,
3819 langinfo_t *langinfo,
3820 term_t *term,
3821 win_t *win,
3822 long line,
3823 long offset)
3825 apply_attr(term, win->bar_attr);
3827 if (term->has_hpa)
3828 (void)tputs(TPARM2(column_address, offset + win->max_width + 1), 1, outch);
3829 else if (term->has_cursor_address)
3830 (void)tputs(TPARM3(cursor_address,
3831 term->curs_line + line - 2,
3832 offset + win->max_width + 1),
3834 outch);
3835 else if (term->has_parm_right_cursor)
3837 fputc_safe('\r', stdout);
3838 (void)tputs(TPARM2(parm_right_cursor, offset + win->max_width + 1),
3840 outch);
3842 else
3844 long i;
3846 fputc_safe('\r', stdout);
3847 for (i = 0; i < offset + win->max_width + 1; i++)
3848 (void)tputs(TPARM1(cursor_right), 1, outch);
3851 if (langinfo->utf8)
3852 fputs_safe(s1, stdout);
3853 else
3854 fputs_safe(s2, stdout);
3856 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
3859 /* ==========================================================*/
3860 /* Displays an horizontal scroll bar below the window. */
3861 /* The bar if only displayed in line or column mode when the */
3862 /* line containing the cursor is truncated. */
3863 /* */
3864 /* mark is the offset of the cursor in the bar. */
3865 /* ==========================================================*/
3866 void
3867 disp_hbar(win_t *win, term_t *term, langinfo_t *langinfo, int pos1, int pos2)
3869 int i;
3871 apply_attr(term, win->bar_attr);
3873 (void)tputs(TPARM1(clr_eol), 1, outch);
3875 /* Draw the left symbol arrow. */
3876 /* """""""""""""""""""""""""""" */
3877 if (langinfo->utf8)
3879 if (pos1 == 0)
3880 fputs_safe(hbar_begin, stdout);
3881 else
3882 fputs_safe(hbar_left, stdout);
3884 else
3886 if (pos1 == 0)
3887 fputc_safe('<', stdout);
3888 else
3889 fputc_safe('\\', stdout);
3892 /* Draw the line in the horizontal bar. */
3893 /* """""""""""""""""""""""""""""""""""" */
3894 if (term->has_rep && !langinfo->utf8)
3895 (void)tputs(TPARM3(repeat_char, '-', term->ncolumns - 2), 1, outch);
3896 else
3898 char *s;
3900 if (langinfo->utf8)
3901 s = hbar_line;
3902 else
3903 s = "-";
3905 for (i = 0; i < term->ncolumns - 3; i++)
3906 fputs_safe(s, stdout);
3909 /* Draw the cursor. */
3910 /* """""""""""""""" */
3911 if (term->has_hpa)
3913 char *s;
3915 if (langinfo->utf8)
3916 s = hbar_curs;
3917 else
3918 s = "#";
3920 (void)tputs(TPARM2(column_address, pos1 + 1), 1, outch);
3921 for (i = pos1 + 1; i <= pos2 + 1; i++)
3922 fputs_safe(s, stdout);
3924 (void)tputs(TPARM2(column_address, term->ncolumns - 2), 1, outch);
3926 else if (term->has_parm_right_cursor)
3928 char *s;
3930 if (langinfo->utf8)
3931 s = hbar_curs;
3932 else
3933 s = "#";
3935 fputs_safe("\r", stdout);
3936 (void)tputs(TPARM2(parm_right_cursor, pos1 + 1), 1, outch);
3938 for (i = pos1; i <= pos2; i++)
3939 fputs_safe(s, stdout);
3941 fputs_safe("\r", stdout);
3942 (void)tputs(TPARM2(parm_right_cursor, term->ncolumns - 2), 1, outch);
3944 else
3946 char *s;
3948 if (langinfo->utf8)
3949 s = hbar_curs;
3950 else
3951 s = "#";
3953 fputs_safe("\r", stdout);
3954 (void)tputs(TPARM2(cursor_right, 1), 1, outch);
3955 for (i = 0; i < pos1; i++)
3956 (void)tputs(TPARM1(cursor_right), 1, outch);
3958 for (i = pos1 + 2; i <= pos2 + 1; i++)
3959 fputs_safe(s, stdout);
3961 for (i = pos1; i < term->ncolumns - 3 - pos2 - 2; i++)
3962 (void)tputs(TPARM1(cursor_right), 1, outch);
3965 /* Draw the right symbol arrow. */
3966 /* """""""""""""""""""""""""""" */
3967 if (langinfo->utf8)
3969 if (pos2 == term->ncolumns - 4)
3970 fputs_safe(hbar_end, stdout);
3971 else
3972 fputs_safe(hbar_right, stdout);
3974 else
3976 if (pos2 == term->ncolumns - 4)
3977 fputc_safe('>', stdout);
3978 else
3979 fputc_safe('/', stdout);
3982 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
3985 /* *************** */
3986 /* Core functions. */
3987 /* *************** */
3989 /* ============================================================== */
3990 /* Split the lines of the message given to -m to a linked list of */
3991 /* lines. */
3992 /* Also fill the maximum screen width and the maximum number */
3993 /* of bytes of the longest line. */
3994 /* ============================================================== */
3995 void
3996 get_message_lines(char *message,
3997 ll_t *message_lines_list,
3998 long *message_max_width,
3999 long *message_max_len)
4001 char *str;
4002 char *ptr;
4003 char *cr_ptr;
4004 long n;
4005 wchar_t *w = NULL;
4007 *message_max_width = 0;
4008 *message_max_len = 0;
4009 ptr = message;
4011 /* For each line terminated with a EOL character. */
4012 /* """""""""""""""""""""""""""""""""""""""""""""" */
4013 while (*ptr != '\0' && (cr_ptr = strchr(ptr, '\n')) != NULL)
4015 if (cr_ptr > ptr)
4017 str = xmalloc(cr_ptr - ptr + 1);
4018 str[cr_ptr - ptr] = '\0';
4019 memcpy(str, ptr, cr_ptr - ptr);
4021 else
4022 str = xstrdup("");
4024 ll_append(message_lines_list, str);
4026 /* If needed, update the message maximum width. */
4027 /* """""""""""""""""""""""""""""""""""""""""""" */
4028 n = wcswidth((w = utf8_strtowcs(str)), utf8_strlen(str));
4029 free(w);
4031 if (n > *message_max_width)
4032 *message_max_width = n;
4034 /* If needed, update the message maximum number */
4035 /* of bytes used by the longest line. */
4036 /* """""""""""""""""""""""""""""""""""""""""""" */
4037 if ((n = (long)strlen(str)) > *message_max_len)
4038 *message_max_len = n;
4040 ptr = cr_ptr + 1;
4043 /* For the last line. */
4044 /* """""""""""""""""" */
4045 if (*ptr != '\0')
4047 ll_append(message_lines_list, xstrdup(ptr));
4049 n = wcswidth((w = utf8_strtowcs(ptr)), utf8_strlen(ptr));
4050 free(w);
4052 if (n > *message_max_width)
4053 *message_max_width = n;
4055 /* If needed, update the message maximum number */
4056 /* of bytes used by the longest line. */
4057 /* """""""""""""""""""""""""""""""""""""""""""" */
4058 if ((n = (long)strlen(ptr)) > *message_max_len)
4059 *message_max_len = n;
4061 else
4062 ll_append(message_lines_list, xstrdup(""));
4065 /* =================================================================== */
4066 /* Set the new start and the new end of the window structure according */
4067 /* to the current cursor position. */
4068 /* =================================================================== */
4069 void
4070 set_win_start_end(win_t *win, long current, long last)
4072 long cur_line, end_line;
4074 cur_line = line_nb_of_word_a[current];
4075 if (cur_line == last)
4076 win->end = count - 1;
4077 else
4079 /* In help mode we must not modify the windows start/end position as */
4080 /* It must be redrawn exactly as it was before. */
4081 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4082 if (!help_mode)
4084 if (cur_line + win->max_lines / 2 + 1 <= last)
4085 win->end = first_word_in_line_a[cur_line + win->max_lines / 2 + 1] - 1;
4086 else
4087 win->end = first_word_in_line_a[last];
4090 end_line = line_nb_of_word_a[win->end];
4092 if (end_line < win->max_lines)
4093 win->start = 0;
4094 else
4095 win->start = first_word_in_line_a[end_line - win->max_lines + 1];
4098 /* ======================================================================== */
4099 /* Set the metadata associated with a word, its starting and ending */
4100 /* position, the line in which it is put and so on. */
4101 /* Set win.start win.end and the starting and ending position of each word. */
4102 /* This function is only called initially, when resizing the terminal and */
4103 /* potentially when the search function is used. */
4104 /* ======================================================================== */
4105 long
4106 build_metadata(term_t *term, long count, win_t *win)
4108 long i = 0;
4109 long word_len;
4110 long len = 0;
4111 long last = 0;
4112 long word_width; /* Number of screen positions taken by the word. */
4113 long tab_count; /* Current number of words in the line, used in *
4114 | tab_mode. */
4115 wchar_t *w;
4117 line_nb_of_word_a[0] = 0;
4118 first_word_in_line_a[0] = 0;
4120 /* In column mode we need to calculate win->max_width, first initialize */
4121 /* it to 0 and increment it later in the loop. */
4122 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4123 if (!win->col_mode)
4125 win->max_width = 0;
4126 win->real_max_width = 0;
4129 tab_count = 0;
4130 while (i < count)
4132 /* Determine the number of screen positions taken by the word. */
4133 /* Note: mbstowcs will always succeed here as word_a[i].str */
4134 /* has already been utf8_validated/repaired. */
4135 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4136 word_len = mbstowcs(NULL, word_a[i].str, 0);
4137 word_width = wcswidth((w = utf8_strtowcs(word_a[i].str)), word_len);
4139 /* Manage the case where the word is larger than the terminal width. */
4140 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4141 if (word_width >= term->ncolumns - 2)
4143 /* Shorten the word until it fits. */
4144 /* """"""""""""""""""""""""""""""" */
4147 word_width = wcswidth(w, word_len--);
4148 } while (word_len > 0 && word_width >= term->ncolumns - 2);
4150 free(w);
4152 /* Look if there is enough remaining place on the line when not in */
4153 /* column mode. Force a break if the 'is_last' flag is set in all */
4154 /* modes or if we hit the max number of allowed columns in tab mode. */
4155 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4156 if ((!win->col_mode && !win->line_mode
4157 && (len + word_width + 1) >= term->ncolumns - 1)
4158 || ((win->col_mode || win->line_mode || win->tab_mode) && i > 0
4159 && word_a[i - 1].is_last)
4160 || (win->tab_mode && win->max_cols > 0 && tab_count >= win->max_cols))
4163 /* We must build another line. */
4164 /* """"""""""""""""""""""""""" */
4165 line_nb_of_word_a[i] = ++last;
4166 first_word_in_line_a[last] = i;
4168 word_a[i].start = 0;
4170 len = word_width + 1; /* Resets the current line length. */
4171 tab_count = 1; /* Resets the current number of words *
4172 | in the line. */
4173 word_a[i].end = word_width - 1;
4174 word_a[i].mb = word_len;
4176 else
4178 word_a[i].start = len;
4179 word_a[i].end = word_a[i].start + word_width - 1;
4180 word_a[i].mb = word_len;
4181 line_nb_of_word_a[i] = last;
4183 len += word_width + 1; /* Increase line length. */
4184 tab_count++; /* We've seen another word in the line/ */
4187 /* Update win->(real_)max_width if necessary. */
4188 /* """""""""""""""""""""""""""""""""""""""""" */
4189 if (len > win->max_width)
4191 /* Update the effective line width. */
4192 /* '''''''''''''''''''''''''''''''' */
4193 if (len > term->ncolumns)
4195 win->max_width = term->ncolumns - 2;
4197 else
4198 win->max_width = len;
4201 /* Update the real line width. */
4202 /* ''''''''''''''''''''''''''' */
4203 if (len > win->real_max_width)
4204 win->real_max_width = len;
4206 i++;
4209 /* Set the left margin when in centered mode. */
4210 /* """""""""""""""""""""""""""""""""""""""""" */
4211 if (!win->center || win->max_width > term->ncolumns - 2)
4212 win->offset = 0;
4213 else
4214 win->offset = (term->ncolumns - 2 - win->max_width) / 2;
4216 /* We need to recalculate win->start and win->end here */
4217 /* because of a possible terminal resizing. */
4218 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
4219 set_win_start_end(win, current, last);
4221 return last;
4224 /* ======================================================================= */
4225 /* Helper function used by disp_word to print the cursor with the matching */
4226 /* characters of the word highlighted. */
4227 /* ======================================================================= */
4228 void
4229 disp_cursor_word(long pos, win_t *win, term_t *term, int err)
4231 size_t i;
4232 int att_set = 0;
4233 char *p = word_a[pos].str + daccess.flength;
4234 char *np;
4236 /* Set the cursor attribute. */
4237 /* """"""""""""""""""""""""" */
4238 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4240 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4241 if (word_a[pos].tag_id > 0)
4243 if (marked == -1)
4244 apply_attr(term, win->cursor_on_tag_attr);
4245 else
4246 apply_attr(term, win->cursor_on_tag_marked_attr);
4248 else
4250 if (marked == -1)
4251 apply_attr(term, win->cursor_attr);
4252 else
4253 apply_attr(term, win->cursor_marked_attr);
4256 for (i = 0; i < word_a[pos].mb - daccess.flength; i++)
4258 if (BIT_ISSET(word_a[pos].bitmap, i))
4260 if (!att_set)
4262 att_set = 1;
4264 /* Set the buffer display attribute. */
4265 /* """"""""""""""""""""""""""""""""" */
4266 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4267 if (err)
4268 apply_attr(term, win->match_err_text_attr);
4269 else
4270 apply_attr(term, win->match_text_attr);
4272 if (word_a[pos].tag_id > 0)
4274 if (marked == -1)
4275 apply_attr(term, win->cursor_on_tag_attr);
4276 else
4277 apply_attr(term, win->cursor_on_tag_marked_attr);
4279 else
4281 if (marked == -1)
4282 apply_attr(term, win->cursor_attr);
4283 else
4284 apply_attr(term, win->cursor_marked_attr);
4288 else
4290 if (att_set)
4292 att_set = 0;
4294 /* Set the search cursor attribute. */
4295 /* """""""""""""""""""""""""""""""" */
4296 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4297 if (word_a[pos].tag_id > 0)
4299 if (marked == -1)
4300 apply_attr(term, win->cursor_on_tag_attr);
4301 else
4302 apply_attr(term, win->cursor_on_tag_marked_attr);
4304 else
4306 if (marked == -1)
4307 apply_attr(term, win->cursor_attr);
4308 else
4309 apply_attr(term, win->cursor_marked_attr);
4313 np = utf8_next(p);
4314 if (np == NULL)
4315 fputs_safe(p, stdout);
4316 else
4317 printf("%.*s", (int)(np - p), p);
4318 p = np;
4322 /* ========================================================== */
4323 /* Helper function used by disp_word to print a matching word */
4324 /* with the matching characters of the word highlighted. */
4325 /* ========================================================== */
4326 void
4327 disp_matching_word(long pos, win_t *win, term_t *term, int is_current, int err)
4329 size_t i;
4330 int att_set = 0;
4331 char *p = word_a[pos].str + daccess.flength;
4332 char *np;
4333 unsigned char level = 0;
4335 level = word_a[pos].special_level;
4337 /* Set the search cursor attribute. */
4338 /* """""""""""""""""""""""""""""""" */
4339 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4341 if (!is_current)
4343 if (err)
4344 apply_attr(term, win->match_err_field_attr);
4345 else
4347 if (level > 0)
4348 apply_attr(term, win->special_attr[level - 1]);
4349 else
4350 apply_attr(term, win->match_field_attr);
4353 else
4355 if (err)
4356 apply_attr(term, win->search_err_field_attr);
4357 else
4358 apply_attr(term, win->search_field_attr);
4361 if (word_a[pos].tag_id > 0)
4362 apply_attr(term, win->tag_attr);
4364 for (i = 0; i < word_a[pos].mb - daccess.flength; i++)
4366 if (BIT_ISSET(word_a[pos].bitmap, i))
4368 if (!att_set)
4370 att_set = 1;
4372 /* Set the buffer display attribute. */
4373 /* """"""""""""""""""""""""""""""""" */
4374 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4375 if (!is_current)
4377 if (err)
4378 apply_attr(term, win->match_err_text_attr);
4379 else
4380 apply_attr(term, win->match_text_attr);
4382 else
4383 apply_attr(term, win->search_text_attr);
4385 if (word_a[pos].tag_id > 0)
4386 apply_attr(term, win->tag_attr);
4389 else
4391 if (att_set)
4393 att_set = 0;
4395 /* Set the search cursor attribute. */
4396 /* """""""""""""""""""""""""""""""" */
4397 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4398 if (!is_current)
4400 if (err)
4401 apply_attr(term, win->match_err_field_attr);
4402 else
4404 if (level > 0)
4405 apply_attr(term, win->special_attr[level - 1]);
4406 else
4407 apply_attr(term, win->match_field_attr);
4410 else
4412 if (err)
4413 apply_attr(term, win->search_err_field_attr);
4414 else
4415 apply_attr(term, win->search_field_attr);
4418 if (word_a[pos].tag_id > 0)
4419 apply_attr(term, win->tag_attr);
4423 np = utf8_next(p);
4424 if (np == NULL)
4425 fputs_safe(p, stdout);
4426 else
4427 printf("%.*s", (int)(np - p), p);
4428 p = np;
4432 /* ====================================================================== */
4433 /* Display a word in, the windows. Manages the following different cases: */
4434 /* - Search mode display */
4435 /* - Cursor display */
4436 /* - Normal display */
4437 /* - Color or mono display */
4438 /* ====================================================================== */
4439 void
4440 disp_word(long pos,
4441 search_mode_t search_mode,
4442 search_data_t *search_data,
4443 term_t *term,
4444 win_t *win,
4445 toggle_t *toggles,
4446 char *tmp_word)
4448 long s = word_a[pos].start;
4449 long e = word_a[pos].end;
4450 long p;
4452 char *buffer = search_data->buf;
4454 if (pos == current)
4456 if (search_mode != NONE)
4458 utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
4459 if (word_a[pos].is_numbered)
4461 /* Set the direct access number attribute. */
4462 /* """"""""""""""""""""""""""""""""""""""" */
4463 apply_attr(term, win->daccess_attr);
4465 /* And print it. */
4466 /* """"""""""""" */
4467 fputs_safe(daccess.left, stdout);
4468 printf("%.*s", daccess.length, tmp_word + 1);
4469 fputs_safe(daccess.right, stdout);
4470 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4471 fputc_safe(' ', stdout);
4473 else if (daccess.length > 0)
4475 /* Prints the leading spaces. */
4476 /* """""""""""""""""""""""""" */
4477 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4478 printf("%.*s", daccess.flength, tmp_word);
4481 /* Set the search cursor attribute. */
4482 /* """""""""""""""""""""""""""""""" */
4483 if (search_data->err)
4484 apply_attr(term, win->search_err_field_attr);
4485 else
4486 apply_attr(term, win->search_field_attr);
4488 /* The tab attribute must complete the attributes already set. */
4489 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4490 if (word_a[pos].tag_id > 0)
4491 apply_attr(term, win->tag_attr);
4493 /* Print the word part. */
4494 /* """""""""""""""""""" */
4495 fputs_safe(tmp_word + daccess.flength, stdout);
4497 if (buffer[0] != '\0')
4499 long i = 0;
4501 /* Put the cursor at the beginning of the word. */
4502 /* """""""""""""""""""""""""""""""""""""""""""" */
4503 for (i = 0; i < e - s + 1 - daccess.flength; i++)
4504 (void)tputs(TPARM1(cursor_left), 1, outch);
4506 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4508 /* Set the search cursor attribute. */
4509 /* """""""""""""""""""""""""""""""" */
4510 if (search_data->err)
4511 apply_attr(term, win->search_err_field_attr);
4512 else
4513 apply_attr(term, win->search_field_attr);
4515 disp_matching_word(pos, win, term, 1, search_data->err);
4518 else
4520 if (daccess.length > 0)
4522 /* If this word is not numbered, reset the display */
4523 /* attributes before printing the leading spaces. */
4524 /* """"""""""""""""""""""""""""""""""""""""""""""" */
4525 if (!word_a[pos].is_numbered)
4527 /* Print the non significant part of the word. */
4528 /* """"""""""""""""""""""""""""""""""""""""""" */
4529 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4530 printf("%.*s", daccess.flength - 1, word_a[pos].str);
4531 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4532 fputc_safe(' ', stdout);
4534 else
4536 apply_attr(term, win->daccess_attr);
4538 /* Print the non significant part of the word. */
4539 /* """"""""""""""""""""""""""""""""""""""""""" */
4540 fputs_safe(daccess.left, stdout);
4541 printf("%.*s", daccess.length, word_a[pos].str + 1);
4542 fputs_safe(daccess.right, stdout);
4543 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4544 fputc_safe(' ', stdout);
4548 /* If we are not in search mode, display a normal cursor. */
4549 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
4550 utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
4551 if (word_a[pos].is_matching)
4552 disp_cursor_word(pos, win, term, search_data->err);
4553 else
4555 if (word_a[pos].tag_id > 0)
4557 if (marked == -1)
4558 apply_attr(term, win->cursor_on_tag_attr);
4559 else
4561 if (pos == marked)
4562 apply_attr(term, win->cursor_on_marked_attr);
4564 apply_attr(term, win->cursor_on_tag_marked_attr);
4567 else
4569 if (marked == -1)
4570 apply_attr(term, win->cursor_attr);
4571 else
4573 if (pos == marked)
4574 apply_attr(term, win->cursor_on_marked_attr);
4576 apply_attr(term, win->cursor_marked_attr);
4580 fputs_safe(tmp_word + daccess.flength, stdout);
4583 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4585 else
4587 /* Display a normal word without any attribute. */
4588 /* """""""""""""""""""""""""""""""""""""""""""" */
4589 utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p);
4591 /* If words are numbered, emphasis their numbers. */
4592 /* """""""""""""""""""""""""""""""""""""""""""""" */
4593 if (word_a[pos].is_numbered)
4595 apply_attr(term, win->daccess_attr);
4597 fputs_safe(daccess.left, stdout);
4598 printf("%.*s", daccess.length, tmp_word + 1);
4599 fputs_safe(daccess.right, stdout);
4601 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4602 fputc_safe(' ', stdout);
4604 else if (daccess.length > 0)
4606 long i;
4608 /* Insert leading spaces if the word is non numbered and */
4609 /* padding for all words is set. */
4610 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
4611 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4612 if (daccess.padding == 'a')
4613 for (i = 0; i < daccess.flength; i++)
4614 fputc_safe(' ', stdout);
4617 if (!word_a[pos].is_selectable)
4618 apply_attr(term, win->exclude_attr);
4619 else if (word_a[pos].special_level > 0)
4621 unsigned char level = word_a[pos].special_level - 1;
4623 apply_attr(term, win->special_attr[level]);
4625 else if (toggles->taggable && pos == marked)
4626 apply_attr(term, win->marked_attr);
4627 else
4629 if (word_a[pos].iattr != NULL) /* is a specific attribute set? */
4630 apply_attr(term, *(word_a[pos].iattr));
4631 else
4632 apply_attr(term, win->include_attr);
4635 if (word_a[pos].is_matching)
4636 disp_matching_word(pos, win, term, 0, search_data->err);
4637 else
4639 if (word_a[pos].tag_id > 0)
4640 apply_attr(term, win->tag_attr);
4642 if ((daccess.length > 0 && daccess.padding == 'a')
4643 || word_a[pos].is_numbered)
4644 fputs_safe(tmp_word + daccess.flength, stdout);
4645 else
4646 fputs_safe(tmp_word, stdout);
4649 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4653 /* =================================== */
4654 /* Display a message above the window. */
4655 /* =================================== */
4656 void
4657 disp_message(ll_t *message_lines_list,
4658 long message_max_width,
4659 long message_max_len,
4660 term_t *term,
4661 win_t *win,
4662 langinfo_t *langinfo)
4664 ll_node_t *node;
4665 char *line;
4666 char *buf;
4667 size_t len;
4668 long size;
4669 long offset;
4670 wchar_t *w;
4671 int n = 0; /* Counter used to display message lines. */
4672 int cut = 0; /* Will be 1 if the message is shortened. */
4674 sigset_t mask;
4676 win->message_lines = 0;
4678 /* Do nothing if there is no message to display. */
4679 /* """"""""""""""""""""""""""""""""""""""""""""" */
4680 if (message_lines_list == NULL)
4681 return;
4683 /* Recalculate the number of to-be-displayed lines in the messages */
4684 /* if space is missing. */
4685 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4686 if (term->nlines < 3)
4687 return;
4689 win->message_lines = message_lines_list->len;
4690 if (win->message_lines > term->nlines - 2)
4692 win->message_lines = term->nlines - 2;
4693 win->max_lines = term->nlines - win->message_lines - 1;
4694 cut = 1;
4696 win->message_lines++;
4698 /* Deactivate the periodic timer to prevent the interruptions to corrupt */
4699 /* screen by altering the timing of the decoding of the terminfo */
4700 /* capabilities. */
4701 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4702 sigemptyset(&mask);
4703 sigaddset(&mask, SIGALRM);
4704 sigprocmask(SIG_BLOCK, &mask, NULL);
4706 node = message_lines_list->head;
4707 buf = xmalloc(message_max_len + 1);
4709 /* Follow the list of message lines and display each line. */
4710 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
4711 for (n = 1; n < win->message_lines; n++)
4713 long i;
4715 line = node->data;
4716 len = utf8_strlen(line);
4717 w = utf8_strtowcs(line);
4719 /* Adjust size and len if the terminal is not large enough. */
4720 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4721 size = wcswidth(w, len);
4722 while (len > 0 && size > term->ncolumns)
4723 size = wcswidth(w, --len);
4725 free(w);
4727 /* Compute the offset from the left screen border if -M option is set. */
4728 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4729 offset = (term->ncolumns - message_max_width - 3) / 2;
4731 if (win->center && offset > 0)
4732 for (i = 0; i < offset; i++)
4733 fputc_safe(' ', stdout);
4735 apply_attr(term, win->message_attr);
4737 /* Only print the start of a line if the screen width if too small. */
4738 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4739 utf8_strprefix(buf, line, len, &size);
4741 /* Print the line without the ending \n. */
4742 /* ''''''''''''''''''''''''''''''''''''' */
4743 if (n > 1 && cut && n == win->message_lines - 1)
4745 if (langinfo->utf8)
4746 fputs_safe(msg_arr_down, stdout);
4747 else
4748 fputc_safe('v', stdout);
4750 else
4751 printf("%s", buf);
4753 /* Complete the short line with spaces until it reach the */
4754 /* message max size. */
4755 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */
4756 for (i = size; i < message_max_width; i++)
4758 if (i + (offset < 0 ? 0 : offset) >= term->ncolumns)
4759 break;
4760 fputc_safe(' ', stdout);
4763 /* Drop the attributes and print a \n. */
4764 /* ''''''''''''''''''''''''''''''''''' */
4765 if (term->nlines > 2)
4767 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4768 puts("");
4771 node = node->next;
4774 /* Add an empty line without attribute to separate the menu title */
4775 /* and the menu content. */
4776 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4777 puts("");
4779 free(buf);
4781 /* Re-enable the periodic timer. */
4782 /* """"""""""""""""""""""""""""" */
4783 sigprocmask(SIG_UNBLOCK, &mask, NULL);
4786 /* ============================= */
4787 /* Display the selection window. */
4788 /* ============================= */
4789 long
4790 disp_lines(win_t *win,
4791 toggle_t *toggles,
4792 long current,
4793 long count,
4794 search_mode_t search_mode,
4795 search_data_t *search_data,
4796 term_t *term,
4797 long last_line,
4798 char *tmp_word,
4799 langinfo_t *langinfo)
4801 long lines_disp;
4802 long i;
4803 char left_margin_symbol[5]; /* Placeholder for the arrow symbols. */
4804 long len;
4805 long has_vbar; /* Flag to signal the presence of the vertical bar. */
4806 long first_start;
4807 int leftmost_start = 0; /* Starting position of the leftmost selectable *
4808 | word in the window lines. */
4809 int rightmost_end = 0; /* Ending position of the rightmost selectable *
4810 | word in the window lines. */
4812 long first_line; /* real line # on the first line of the window. */
4814 int row1, row2, col; /* Only the rows are used to detect a *
4815 | bottom-of-page scrolling, col is *
4816 | necessary but not required here. */
4818 sigset_t mask;
4820 /* Disable the periodic timer to prevent the interruptions to corrupt */
4821 /* screen by altering the timing of the decoding of the terminfo */
4822 /* capabilities. */
4823 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4824 sigemptyset(&mask);
4825 sigaddset(&mask, SIGALRM);
4826 sigprocmask(SIG_BLOCK, &mask, NULL);
4828 left_margin_symbol[0] = ' ';
4829 left_margin_symbol[1] = '\0';
4831 lines_disp = 1;
4832 first_start = -1;
4833 leftmost_start = 0;
4835 /* Initialize the truncated lines flag. */
4836 /* """""""""""""""""""""""""""""""""""" */
4837 win->has_truncated_lines = 0;
4839 /* Initialize the necessity of displaying the horizontal scroll bar or not. */
4840 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4841 win->has_hbar = 0;
4843 /* Initialize the selectable column guard to its maximum position. */
4844 /* for the first window line. */
4845 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4846 shift_right_sym_pos_a[line_nb_of_word_a[win->start] + lines_disp - 1] =
4847 term->ncolumns;
4849 (void)tputs(TPARM1(save_cursor), 1, outch);
4851 i = win->start; /* Index of the first word in the window. */
4853 /* Modify the max number of displayed lines if we do not have */
4854 /* enough place. */
4855 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4856 if (win->max_lines > term->nlines - win->message_lines)
4857 win->max_lines = term->nlines - win->message_lines;
4859 if (last_line >= win->max_lines)
4860 has_vbar = 1;
4861 else
4862 has_vbar = 0;
4864 if (win->col_mode || win->line_mode)
4865 len = term->ncolumns - 3;
4866 else
4867 len = term->ncolumns - 2;
4869 /* If in column mode and the sum of the columns sizes + gutters is */
4870 /* greater than the terminal width, then prepend a space to be able to */
4871 /* display the left arrow indicating that the first displayed column */
4872 /* is not the first one. */
4873 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4874 if (len > 1
4875 && ((win->col_mode || win->line_mode)
4876 && win->real_max_width > term->ncolumns - 2))
4878 if (win->first_column > 0)
4880 if (langinfo->utf8)
4881 strcpy(left_margin_symbol, shift_left_sym);
4882 else
4883 strcpy(left_margin_symbol, "<");
4885 if (!toggles->no_hor_scrollbar)
4886 win->has_hbar = 1;
4889 if (!win->has_truncated_lines)
4890 win->has_truncated_lines = 1;
4892 else
4893 left_margin_symbol[0] = '\0';
4895 /* Center the display ? */
4896 /* """""""""""""""""""" */
4897 if (win->offset > 0)
4899 long i;
4900 for (i = 0; i < win->offset; i++)
4901 fputc_safe(' ', stdout);
4904 left_margin_putp(left_margin_symbol, term, win);
4906 first_line = line_nb_of_word_a[i];
4908 while (len > 1 && i <= count - 1)
4910 /* Display one word and the space or symbol following it. */
4911 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
4912 if (word_a[i].start >= win->first_column
4913 && word_a[i].end < len + win->first_column)
4915 if (first_start < 0)
4916 first_start = word_a[i].start;
4918 disp_word(i, search_mode, search_data, term, win, toggles, tmp_word);
4920 /* Calculate the start offset of the last word of the line */
4921 /* containing the cursor in column or line mode. */
4922 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
4923 if ((win->col_mode || win->line_mode)
4924 && line_nb_of_word_a[current] - first_line + 1 == lines_disp)
4926 long wi;
4928 wi = first_word_in_line_a[lines_disp + first_line - 1];
4929 while (wi < current && !word_a[wi].is_selectable)
4930 wi++;
4932 leftmost_start = word_a[wi++].start;
4934 if (lines_disp + first_line > last_line)
4935 wi = count - 1;
4936 else
4937 wi = first_word_in_line_a[lines_disp + first_line] - 1;
4939 while (!word_a[wi].is_selectable)
4940 wi--;
4942 rightmost_end = word_a[wi].end;
4945 /* If there are more element to be displayed after the right margin */
4946 /* in column or line mode. */
4947 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4948 if ((win->col_mode || win->line_mode) && i < count - 1
4949 && word_a[i + 1].end >= len + win->first_column)
4951 if (!win->has_truncated_lines)
4952 win->has_truncated_lines = 1;
4954 /* Toggle the presence of the horizontal bar if allowed and */
4955 /* if this line contains the cursor in column or line mode. */
4956 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4957 if (!toggles->no_hor_scrollbar
4958 && line_nb_of_word_a[current] - first_line + 1 == lines_disp)
4959 win->has_hbar = 1; /* the line containing the cursor is *
4960 | truncated in the window. */
4962 apply_attr(term, win->shift_attr);
4964 if (langinfo->utf8)
4965 fputs_safe(shift_right_sym, stdout);
4966 else
4967 fputc_safe('>', stdout);
4969 (void)tputs(TPARM1(exit_attribute_mode), 1, outch);
4971 /* Adjust the selectable column guard to the column just after */
4972 /* the last displayed word. */
4973 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4974 shift_right_sym_pos_a[line_nb_of_word_a[i]] = word_a[i].end + 1
4975 - first_start + 2;
4977 else
4978 /* If we want to display the gutter. */
4979 /* """"""""""""""""""""""""""""""""" */
4980 if (!word_a[i].is_last && win->col_sep
4981 && (win->tab_mode || win->col_mode))
4983 long pos;
4985 /* Make sure that we are using the right gutter character even */
4986 /* if the first displayed word is * not the first of its line. */
4987 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
4988 pos = i - first_word_in_line_a[line_nb_of_word_a[i]];
4990 if (pos >= win->gutter_nb) /* Use the last gutter character. */
4991 fputs_safe(win->gutter_a[win->gutter_nb - 1], stdout);
4992 else
4993 fputs_safe(win->gutter_a[pos], stdout);
4995 else
4996 /* Else just display a space. */
4997 /* """""""""""""""""""""""""" */
4998 fputc_safe(' ', stdout);
5001 /* Mark the line as the current line, the line containing the cursor. */
5002 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5003 if (i == current)
5004 win->cur_line = lines_disp;
5006 /* Check if we must start a new line. */
5007 /* """""""""""""""""""""""""""""""""" */
5008 if (i == count - 1 || word_a[i + 1].start == 0)
5010 (void)tputs(TPARM1(clr_eol), 1, outch);
5011 if (lines_disp < win->max_lines)
5013 /* If we have more than one line to display. */
5014 /* """"""""""""""""""""""""""""""""""""""""" */
5015 if (has_vbar && !toggles->no_scrollbar
5016 && (lines_disp > 1 || i < count - 1))
5018 /* Store the scroll bar column position. */
5019 /* """"""""""""""""""""""""""""""""""""" */
5020 win->sb_column = win->offset + win->max_width + 1;
5022 /* Display the next element of the scrollbar. */
5023 /* """""""""""""""""""""""""""""""""""""""""" */
5024 if (line_nb_of_word_a[i] == 0)
5026 if (win->max_lines > 1)
5027 right_margin_putp(sbar_top,
5028 "\\",
5029 langinfo,
5030 term,
5031 win,
5032 lines_disp,
5033 win->offset);
5034 else
5035 right_margin_putp(sbar_arr_down,
5036 "^",
5037 langinfo,
5038 term,
5039 win,
5040 lines_disp,
5041 win->offset);
5043 else if (lines_disp == 1)
5044 right_margin_putp(sbar_arr_up,
5045 "^",
5046 langinfo,
5047 term,
5048 win,
5049 lines_disp,
5050 win->offset);
5051 else if (line_nb_of_word_a[i] == last_line)
5053 if (win->max_lines > 1)
5054 right_margin_putp(sbar_down,
5055 "/",
5056 langinfo,
5057 term,
5058 win,
5059 lines_disp,
5060 win->offset);
5061 else
5062 right_margin_putp(sbar_arr_up,
5063 "^",
5064 langinfo,
5065 term,
5066 win,
5067 lines_disp,
5068 win->offset);
5070 else if (last_line + 1 > win->max_lines
5071 && (long)((float)(line_nb_of_word_a[current])
5072 / (last_line + 1) * (win->max_lines - 2)
5073 + 2)
5074 == lines_disp)
5075 right_margin_putp(sbar_curs,
5076 "#",
5077 langinfo,
5078 term,
5079 win,
5080 lines_disp,
5081 win->offset);
5082 else
5083 right_margin_putp(sbar_line,
5084 "|",
5085 langinfo,
5086 term,
5087 win,
5088 lines_disp,
5089 win->offset);
5092 /* Print a newline character if we are not at the end of */
5093 /* the input nor at the end of the window. */
5094 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
5095 if (i < count - 1 && lines_disp < win->max_lines)
5097 fputc_safe('\n', stdout);
5099 if (win->offset > 0)
5101 long i;
5102 for (i = 0; i < win->offset; i++)
5103 fputc_safe(' ', stdout);
5106 left_margin_putp(left_margin_symbol, term, win);
5109 /* We do not increment the number of lines seen after */
5110 /* a premature end of input. */
5111 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
5112 if (i < count - 1)
5114 lines_disp++;
5115 first_start = -1;
5117 /* Initialize the selectable column guard to its maximum position. */
5118 /* for the next window line. */
5119 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5120 shift_right_sym_pos_a[line_nb_of_word_a[win->start] + lines_disp
5121 - 1] = term->ncolumns;
5124 if (win->max_lines == 1)
5125 break;
5127 else if (lines_disp == win->max_lines)
5129 /* The last line of the window has been displayed. */
5130 /* """"""""""""""""""""""""""""""""""""""""""""""" */
5131 if (has_vbar && line_nb_of_word_a[i] == last_line)
5133 if (!toggles->no_scrollbar)
5135 if (win->max_lines > 1)
5136 right_margin_putp(sbar_down,
5137 "/",
5138 langinfo,
5139 term,
5140 win,
5141 lines_disp,
5142 win->offset);
5143 else
5144 right_margin_putp(sbar_arr_up,
5145 "^",
5146 langinfo,
5147 term,
5148 win,
5149 lines_disp,
5150 win->offset);
5153 else
5155 if (has_vbar && !toggles->no_scrollbar)
5156 right_margin_putp(sbar_arr_down,
5157 "v",
5158 langinfo,
5159 term,
5160 win,
5161 lines_disp,
5162 win->offset);
5163 break;
5166 else
5167 /* These lines were not in the widows and so we have nothing to do. */
5168 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5169 break;
5172 /* Next word. */
5173 /* """""""""" */
5174 i++;
5177 /* Display the horizontal bar when needed. */
5178 /* """"""""""""""""""""""""""""""""""""""" */
5179 if (win->col_mode || win->line_mode)
5181 /* Save again the cursor position before drawing the horizontal bar. */
5182 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5183 get_cursor_position(&row1, &col); /* col is not needed here. */
5185 if (win->has_hbar)
5187 int pos1; /* Pos. of the cursor's start in the horizontal scroll bar. */
5188 int pos2; /* Pos. of the cursor's end in the horizontal scroll bar. */
5190 /* Note: in the following expression, rightmost_start is always */
5191 /* greater then leftmost_start as a line containing a single word */
5192 /* cannot be truncated and have an horizontal scroll bar hence */
5193 /* win->has_hbar = 0. */
5194 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
5195 pos1 = (int)(word_a[current].start - leftmost_start)
5196 / (float)(rightmost_end - leftmost_start) * (term->ncolumns - 4);
5197 pos2 = (int)(word_a[current].end - leftmost_start)
5198 / (float)(rightmost_end - leftmost_start) * (term->ncolumns - 4);
5200 if (pos2 > term->ncolumns - 4)
5201 pos2 = term->ncolumns
5202 - 4; /* just to make sure but should not happen. */
5204 fputs_safe("\n", stdout);
5205 disp_hbar(win, term, langinfo, pos1, pos2);
5207 /* Mark the fact that an horizontal scroll bar has been displayed */
5208 /* and its space allocated. */
5209 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5210 if (win->hbar_displayed == 0)
5211 win->hbar_displayed = 1;
5213 else if (win->hbar_displayed) /* The horizontal scroll bar has already *
5214 | been displayed, keep this space empty *
5215 | to not disturb the display. */
5217 fputs_safe("\n", stdout);
5218 (void)tputs(TPARM1(clr_eol), 1, outch);
5221 /* Save again the cursor position again (especially the row) to detect */
5222 /* an automatic scroll up when the cursor is at the bottom of the window */
5223 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5224 get_cursor_position(&row2, &col);
5227 /* Update win->end, this is necessary because we only */
5228 /* call build_metadata on start and on terminal resize. */
5229 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
5230 if (i == count)
5231 win->end = i - 1;
5232 else
5233 win->end = i;
5235 /* We restore the cursor position saved before the display of the window. */
5236 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5237 (void)tputs(TPARM1(restore_cursor), 1, outch);
5239 /* Make sure the cursor is correctly moved when the horizontal scroll */
5240 /* bar is displayed and the window is at the bottom of the screen. */
5241 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5242 if (win->col_mode || win->line_mode)
5244 if (win->has_hbar && row1 == row2) /* Screen scrolled up. */
5246 (void)tputs(TPARM1(cursor_up), 1, outch);
5247 term->curs_line--;
5251 /* Re-enable the periodic timer. */
5252 /* """"""""""""""""""""""""""""" */
5253 sigprocmask(SIG_UNBLOCK, &mask, NULL);
5255 return lines_disp + (win->hbar_displayed ? 1 : 0);
5258 /* ======================================================= */
5259 /* Signal handler. */
5260 /* Manages SIGSEGV, SIGTERM, SIGHUP, SIGWINCH and SIGALRM. */
5261 /* ======================================================= */
5262 void
5263 sig_handler(int s)
5265 switch (s)
5267 /* Standard termination signals. */
5268 /* """"""""""""""""""""""""""""" */
5269 case SIGPIPE:
5270 got_sigpipe = 1;
5271 break;
5273 case SIGSEGV:
5274 got_sigsegv = 1;
5275 break;
5277 case SIGTERM:
5278 got_sigterm = 1;
5279 break;
5281 case SIGHUP:
5282 got_sighup = 1;
5283 break;
5285 /* Terminal resize. */
5286 /* """""""""""""""" */
5287 case SIGWINCH:
5288 got_winch = 1;
5289 break;
5291 /* Alarm triggered, This signal is used by the search mechanism to */
5292 /* forces a window refresh. */
5293 /* The help mechanism uses it to clear the message */
5294 /* It is also used to redisplay the window after the end of a terminal */
5295 /* resizing. */
5296 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5297 case SIGALRM:
5298 if (timeout.initial_value > 0)
5299 got_timeout_tick = 1;
5301 if (forgotten_timer > 0)
5302 forgotten_timer--;
5304 if (forgotten_timer == 0)
5305 got_forgotten_alrm = 1;
5307 if (help_timer > 0)
5308 help_timer--;
5310 if (help_timer == 0 && help_mode)
5311 got_help_alrm = 1;
5313 if (daccess_timer > 0)
5314 daccess_timer--;
5316 if (daccess_timer == 0)
5317 got_daccess_alrm = 1;
5319 if (winch_timer > 0)
5320 winch_timer--;
5322 if (winch_timer == 0)
5324 got_winch = 0;
5325 got_help_alrm = 0;
5326 got_winch_alrm = 1;
5329 if (search_timer > 0)
5330 search_timer--;
5332 if (search_timer == 0 && search_mode != NONE)
5333 got_search_alrm = 1;
5335 break;
5339 /* ========================================================= */
5340 /* Set new first column to display when horizontal scrolling */
5341 /* Alter win->first_column. */
5342 /* ========================================================= */
5343 void
5344 set_new_first_column(win_t *win, term_t *term)
5346 long pos;
5348 if (word_a[current].start < win->first_column)
5350 pos = current;
5352 while (win->first_column > 0 && word_a[current].start < win->first_column)
5354 win->first_column = word_a[pos].start;
5355 pos--;
5358 else if (word_a[current].end - win->first_column >= term->ncolumns - 3)
5360 pos = first_word_in_line_a[line_nb_of_word_a[current]];
5362 while (!word_a[pos].is_last
5363 && word_a[current].end - win->first_column >= term->ncolumns - 3)
5365 pos++;
5366 win->first_column = word_a[pos].start;
5371 /* ===================================================== */
5372 /* Restrict the matches to word ending with the pattern. */
5373 /* ===================================================== */
5374 void
5375 select_ending_matches(win_t *win,
5376 term_t *term,
5377 search_data_t *search_data,
5378 long *last_line)
5380 if (matches_count > 0)
5382 long i;
5383 long j = 0;
5384 long index;
5385 long nb;
5386 long *tmp;
5387 char *ptr;
5388 char *last_glyph;
5389 int utf8_len;
5391 /* Creation of an alternate array which will */
5392 /* contain only the candidates having potentially */
5393 /* an ending pattern, if this array becomes non */
5394 /* empty then it will replace the original array. */
5395 /* """""""""""""""""""""""""""""""""""""""""""""" */
5396 alt_matching_words_a = xrealloc(alt_matching_words_a,
5397 matches_count * (sizeof(long)));
5399 for (i = 0; i < matches_count; i++)
5401 index = matching_words_a[i];
5402 char *str = word_a[index].str;
5404 /* count the trailing blanks non counted in the bitmap. */
5405 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
5406 ptr = str + strlen(str);
5408 nb = 0;
5409 while ((ptr = utf8_prev(str, ptr)) != NULL && isblank(*ptr))
5410 if (ptr - str > 0)
5411 nb++;
5412 else
5413 break;
5415 if (ptr == NULL) /* str is blank or contain an invalid uft8 sequence. */
5416 ptr = str;
5418 /* NOTE: utf8_prev cannot return NULL in the previous loop */
5419 /* because str always contains at least one UTF-8 valid */
5420 /* sequence, so does ptr. */
5421 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
5423 /* Check the bit corresponding to the last non blank glyph */
5424 /* If set we add the index to an alternate array, if not we */
5425 /* clear the bitmap of the corresponding word. */
5426 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5427 if (BIT_ISSET(word_a[index].bitmap,
5428 word_a[index].mb - nb - daccess.flength - 1))
5429 alt_matching_words_a[j++] = index;
5430 else
5432 /* Look if the end of the word potentially contain an */
5433 /* ending pattern. */
5434 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
5435 if (search_mode == FUZZY)
5437 utf8_len = mblen(ptr, 4);
5438 last_glyph = search_data->buf + search_data->len - utf8_len;
5440 /* in fuzzy search mode we only look the last glyph. */
5441 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
5442 if (memcmp(ptr, last_glyph, utf8_len) == 0)
5443 alt_matching_words_a[j++] = index;
5444 else
5445 memset(word_a[index].bitmap,
5446 '\0',
5447 (word_a[index].mb - daccess.flength) / CHAR_BIT + 1);
5449 else
5451 /* in not fuzzy search mode use all the pattern. */
5452 /* """"""""""""""""""""""""""""""""""""""""""""" */
5453 for (nb = 0; nb < search_data->utf8_len - 1; nb++)
5454 ptr = utf8_prev(str, ptr);
5455 if (memcmp(ptr, search_data->buf, search_data->len) == 0)
5456 alt_matching_words_a[j++] = index;
5457 else
5458 memset(word_a[index].bitmap,
5459 '\0',
5460 (word_a[index].mb - daccess.flength) / CHAR_BIT + 1);
5465 /* Swap the normal and alt array. */
5466 /* """""""""""""""""""""""""""""" */
5467 matches_count = j;
5468 matching_words_a_size = j;
5470 tmp = matching_words_a;
5471 matching_words_a = alt_matching_words_a;
5472 alt_matching_words_a = tmp;
5474 if (j > 0)
5476 /* Adjust the bitmap to the ending version. */
5477 /* """""""""""""""""""""""""""""""""""""""" */
5478 update_bitmaps(search_mode, search_data, END_AFFINITY);
5480 current = matching_words_a[0];
5482 if (current < win->start || current > win->end)
5483 *last_line = build_metadata(term, count, win);
5485 /* Set new first column to display. */
5486 /* """""""""""""""""""""""""""""""" */
5487 set_new_first_column(win, term);
5492 /* ======================================================= */
5493 /* Restrict the matches to word starting with the pattern. */
5494 /* ======================================================= */
5495 void
5496 select_starting_matches(win_t *win,
5497 term_t *term,
5498 search_data_t *search_data,
5499 long *last_line)
5501 if (matches_count > 0)
5503 long i;
5504 long j = 0;
5505 long index;
5506 size_t nb;
5507 long *tmp;
5508 long pos;
5509 char *first_glyph;
5510 int utf8_len;
5512 alt_matching_words_a = xrealloc(alt_matching_words_a,
5513 matches_count * (sizeof(long)));
5515 first_glyph = xmalloc(5);
5517 for (i = 0; i < matches_count; i++)
5519 index = matching_words_a[i];
5521 for (nb = 0; nb < word_a[index].mb; nb++)
5522 if (!isblank(*(word_a[index].str + daccess.flength + nb)))
5523 break;
5525 if (BIT_ISSET(word_a[index].bitmap, nb))
5526 alt_matching_words_a[j++] = index;
5527 else
5530 if (search_mode == FUZZY)
5532 first_glyph = utf8_strprefix(first_glyph,
5533 word_a[index].str + nb + daccess.flength,
5535 &pos);
5536 utf8_len = pos;
5538 /* in fuzzy search mode we only look the first glyph. */
5539 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
5540 if (memcmp(search_data->buf, first_glyph, utf8_len) == 0)
5541 alt_matching_words_a[j++] = index;
5542 else
5543 memset(word_a[index].bitmap,
5544 '\0',
5545 (word_a[index].mb + nb - daccess.flength) / CHAR_BIT + 1);
5547 else
5549 /* in not fuzzy search mode use all the pattern. */
5550 /* """"""""""""""""""""""""""""""""""""""""""""" */
5551 if (memcmp(search_data->buf,
5552 word_a[index].str + nb,
5553 search_data->len - nb)
5554 == 0)
5555 alt_matching_words_a[j++] = index;
5556 else
5557 memset(word_a[index].bitmap,
5558 '\0',
5559 (word_a[index].mb + nb - daccess.flength) / CHAR_BIT + 1);
5564 free(first_glyph);
5566 matches_count = j;
5567 matching_words_a_size = j;
5569 tmp = matching_words_a;
5570 matching_words_a = alt_matching_words_a;
5571 alt_matching_words_a = tmp;
5573 if (j > 0)
5575 /* Adjust the bitmap to the ending version. */
5576 /* """""""""""""""""""""""""""""""""""""""" */
5577 update_bitmaps(search_mode, search_data, START_AFFINITY);
5579 current = matching_words_a[0];
5581 if (current < win->start || current > win->end)
5582 *last_line = build_metadata(term, count, win);
5584 /* Set new first column to display. */
5585 /* """""""""""""""""""""""""""""""" */
5586 set_new_first_column(win, term);
5591 /* ============================= */
5592 /* Moves the cursor to the left. */
5593 /* ============================= */
5594 void
5595 move_left(win_t *win,
5596 term_t *term,
5597 toggle_t *toggles,
5598 search_data_t *search_data,
5599 langinfo_t *langinfo,
5600 long *nl,
5601 long last_line,
5602 char *tmp_word)
5604 long old_current = current;
5605 long old_start = win->start;
5606 long old_first_column = win->first_column;
5607 long wi; /* Word index. */
5611 if (current > 0)
5613 /* Sets the new win->start and win->end if the cursor */
5614 /* is at the beginning of the windows. */
5615 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
5616 if (current == win->start && win->start > 0)
5618 for (wi = win->start - 1; wi >= 0 && word_a[wi].start != 0; wi--)
5621 win->start = wi;
5623 if (word_a[wi].str != NULL)
5624 win->start = wi;
5626 if (win->end < count - 1)
5628 for (wi = win->end + 2; wi < count - 1 && word_a[wi].start != 0; wi++)
5631 if (word_a[wi].str != NULL)
5632 win->end = wi;
5636 /* In column mode we need to take care of the */
5637 /* horizontal scrolling. */
5638 /* """""""""""""""""""""""""""""""""""""""""" */
5639 if (win->col_mode || win->line_mode)
5641 long pos;
5643 if (word_a[current].start == 0)
5645 long len;
5647 len = term->ncolumns - 3;
5648 pos = first_word_in_line_a[line_nb_of_word_a[current - 1]];
5650 while (word_a[current - 1].end - win->first_column >= len)
5652 win->first_column += word_a[pos].end - word_a[pos].start + 2;
5654 pos++;
5657 else if (word_a[current - 1].start < win->first_column)
5658 win->first_column = word_a[current - 1].start;
5660 current--;
5662 else
5663 break;
5664 } while (current != old_current && !word_a[current].is_selectable);
5666 /* The old settings need to be restored if the */
5667 /* new current word is not selectable. */
5668 /* """"""""""""""""""""""""""""""""""""""""""" */
5669 if (!word_a[current].is_selectable)
5671 current = old_current;
5672 win->start = old_start;
5673 if (win->col_mode || win->line_mode)
5674 win->first_column = old_first_column;
5677 if (current != old_current)
5678 *nl = disp_lines(win,
5679 toggles,
5680 current,
5681 count,
5682 search_mode,
5683 search_data,
5684 term,
5685 last_line,
5686 tmp_word,
5687 langinfo);
5690 /* ============================================ */
5691 /* Shift the content of the window to the left. */
5692 /* Stop if the cursor will stop to be visible. */
5693 /* ============================================ */
5694 void
5695 shift_left(win_t *win,
5696 term_t *term,
5697 toggle_t *toggles,
5698 search_data_t *search_data,
5699 langinfo_t *langinfo,
5700 long *nl,
5701 long last_line,
5702 char *tmp_word,
5703 long line)
5705 long pos;
5706 long len;
5708 /* No lines to shift if not in lire or column mode. */
5709 /* """""""""""""""""""""""""""""""""""""""""""""""" */
5710 if (!win->col_mode && !win->line_mode)
5711 return;
5713 /* Do nothing if we already are on the left side of if the line */
5714 /* is already fully displayed. */
5715 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5716 if (win->first_column == 0)
5717 return;
5719 /* Find the first word to be displayed in this line. */
5720 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
5721 pos = first_word_in_line_a[line];
5723 while (pos < count - 1 && word_a[pos].start < win->first_column
5724 && word_a[pos + 1].start > 0)
5725 pos++;
5727 if (word_a[pos].start >= win->first_column)
5728 pos--;
5730 /* Make sure the word under the cursor remains visible. */
5731 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
5732 len = term->ncolumns - 3;
5733 if (word_a[current].end >= len + word_a[pos].start)
5734 return;
5736 win->first_column = word_a[pos].start;
5738 *nl = disp_lines(win,
5739 toggles,
5740 current,
5741 count,
5742 search_mode,
5743 search_data,
5744 term,
5745 last_line,
5746 tmp_word,
5747 langinfo);
5750 /* ============================== */
5751 /* Moves the cursor to the right. */
5752 /* ============================== */
5753 void
5754 move_right(win_t *win,
5755 term_t *term,
5756 toggle_t *toggles,
5757 search_data_t *search_data,
5758 langinfo_t *langinfo,
5759 long *nl,
5760 long last_line,
5761 char *tmp_word)
5763 long old_current = current;
5764 long old_start = win->start;
5765 long old_first_column = win->first_column;
5766 long wi; /* word index */
5770 if (current < count - 1)
5772 /* Sets the new win->start and win->end if the cursor */
5773 /* is at the end of the windows. */
5774 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
5775 if (current == win->end && win->start < count - 1
5776 && win->end != count - 1)
5778 for (wi = win->start + 1; wi < count - 1 && word_a[wi].start != 0; wi++)
5782 if (word_a[wi].str != NULL)
5783 win->start = wi;
5785 if (win->end < count - 1)
5787 for (wi = win->end + 2; wi < count - 1 && word_a[wi].start != 0; wi++)
5790 if (word_a[wi].str != NULL)
5791 win->end = wi;
5795 /* In column mode we need to take care of the */
5796 /* horizontal scrolling. */
5797 /* """""""""""""""""""""""""""""""""""""""""" */
5798 if (win->col_mode || win->line_mode)
5800 if (word_a[current].is_last)
5801 win->first_column = 0;
5802 else
5804 long pos;
5805 long len;
5807 len = term->ncolumns - 3;
5809 if (word_a[current + 1].end >= len + win->first_column)
5811 /* Find the first word to be displayed in this line. */
5812 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
5813 pos = first_word_in_line_a[line_nb_of_word_a[current]];
5815 while (word_a[pos].start <= win->first_column)
5816 pos++;
5818 pos--;
5820 /* If the new current word cannot be displayed, search */
5821 /* the first word in the line that can be displayed by */
5822 /* iterating on pos. */
5823 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
5824 while (word_a[current + 1].end - word_a[pos].start >= len)
5825 pos++;
5827 if (word_a[pos].start > 0)
5828 win->first_column = word_a[pos].start;
5832 current++;
5834 else
5835 break;
5836 } while (current != old_current && !word_a[current].is_selectable);
5838 /* The old settings need to be restored if the */
5839 /* new current word is not selectable. */
5840 /* """"""""""""""""""""""""""""""""""""""""""" */
5841 if (!word_a[current].is_selectable)
5843 current = old_current;
5844 win->start = old_start;
5845 if (win->col_mode || win->line_mode)
5846 win->first_column = old_first_column;
5849 if (current != old_current)
5850 *nl = disp_lines(win,
5851 toggles,
5852 current,
5853 count,
5854 search_mode,
5855 search_data,
5856 term,
5857 last_line,
5858 tmp_word,
5859 langinfo);
5862 /* =========================================== */
5863 /* Shift content of the window to the right. */
5864 /* Stop if the cursor will stop to be visible. */
5865 /* ============================================*/
5866 void
5867 shift_right(win_t *win,
5868 term_t *term,
5869 toggle_t *toggles,
5870 search_data_t *search_data,
5871 langinfo_t *langinfo,
5872 long *nl,
5873 long last_line,
5874 char *tmp_word,
5875 long line)
5877 long pos;
5879 /* No lines to shift if not in lire or column mode. */
5880 /* """""""""""""""""""""""""""""""""""""""""""""""" */
5881 if (!win->col_mode && !win->line_mode)
5882 return;
5884 /* Do nothing if we already are on the right side and all or lines */
5885 /* are already fully displayed. */
5886 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5887 if (shift_right_sym_pos_a[line] == term->ncolumns)
5889 int found = 0;
5891 /* Search for at least one line not fully displayed. */
5892 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
5893 for (long l = line_nb_of_word_a[win->start];
5894 l < line_nb_of_word_a[win->start] + win->max_lines;
5895 l++)
5897 if (shift_right_sym_pos_a[l] == term->ncolumns) /* fully displayed. */
5898 continue;
5900 found = 1; /* Use this line instead of line to calculate the shifting. */
5902 break;
5905 /* Do nothing if all lines are fully displayed. */
5906 /* '''''''''''''''''''''''''''''''''''''''''''' */
5907 if (!found)
5908 return;
5911 /* Find the first word to be displayed in this line. */
5912 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
5913 pos = first_word_in_line_a[line];
5915 while (pos < count - 1 && word_a[pos].start < win->first_column
5916 && word_a[pos + 1].start > 0)
5917 pos++;
5919 if (pos < count - 1 && word_a[pos + 1].start > 0)
5920 pos++;
5922 /* Make sure the word under the cursor remains visible. */
5923 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
5924 if (word_a[pos].start <= word_a[current].start)
5925 win->first_column = word_a[pos].start;
5926 else
5927 return;
5929 *nl = disp_lines(win,
5930 toggles,
5931 current,
5932 count,
5933 search_mode,
5934 search_data,
5935 term,
5936 last_line,
5937 tmp_word,
5938 langinfo);
5941 /* ================================================================== */
5942 /* Get the last word of a line after it has been formed to fit in the */
5943 /* terminal. */
5944 /* ================================================================== */
5945 long
5946 get_line_last_word(long line, long last_line)
5948 if (line == last_line)
5949 return count - 1;
5951 return first_word_in_line_a[line + 1] - 1;
5954 /* ==================================================================== */
5955 /* Try to locate the best word in the target line when trying to move */
5956 /* the cursor upwards. */
5957 /* returns 1 if a word has been found else 0. */
5958 /* This function has the side effect to potentially change the value of */
5959 /* the variable 'current' if an adequate word is found. */
5960 /* ==================================================================== */
5962 find_best_word_upwards(long last_word, long s, long e)
5964 int found = 0;
5965 long index;
5966 long cursor;
5968 /* Look for the first word whose start position in the line is */
5969 /* less or equal to the source word starting position. */
5970 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5971 cursor = last_word;
5972 while (word_a[cursor].start > s)
5973 cursor--;
5975 /* In case no word is eligible, keep the cursor on */
5976 /* the last word. */
5977 /* """"""""""""""""""""""""""""""""""""""""""""""" */
5978 if (cursor == last_word && word_a[cursor].start > 0)
5979 cursor--;
5981 /* Try to guess the best choice if we have multiple choices. */
5982 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5983 if (word_a[cursor].end >= s
5984 && word_a[cursor].end - s >= e - word_a[cursor + 1].start)
5985 current = cursor;
5986 else
5988 if (cursor < last_word)
5989 current = cursor + 1;
5990 else
5991 current = cursor;
5994 /* If the word is not selectable, try to find a selectable word */
5995 /* in its line. */
5996 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
5997 if (!word_a[current].is_selectable)
5999 index = 0;
6000 while (word_a[current - index].start > 0
6001 && !word_a[current - index].is_selectable)
6002 index++;
6004 if (word_a[current - index].is_selectable)
6006 current -= index;
6007 found = 1;
6009 else
6011 index = 0;
6012 while (current + index < last_word
6013 && !word_a[current + index].is_selectable)
6014 index++;
6016 if (word_a[current + index].is_selectable)
6018 current += index;
6019 found = 1;
6023 else
6024 found = 1;
6026 return found;
6029 /* ==================================================================== */
6030 /* Try to locate the best word in the target line when trying to move */
6031 /* the cursor downwards. */
6032 /* returns 1 if a word has been found else 0. */
6033 /* This function has the side effect to potentially change the value of */
6034 /* the variable 'current' if an adequate word is found. */
6035 /* ==================================================================== */
6037 find_best_word_downwards(long last_word, long s, long e)
6039 int found = 0;
6040 long index;
6041 long cursor;
6043 /* Look for the first word whose start position in the line is */
6044 /* less or equal than the source word starting position. */
6045 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6046 cursor = last_word;
6047 while (word_a[cursor].start > s)
6048 cursor--;
6050 /* In case no word is eligible, keep the cursor on */
6051 /* the last word. */
6052 /* """"""""""""""""""""""""""""""""""""""""""""""" */
6053 if (cursor == last_word && word_a[cursor].start > 0)
6054 cursor--;
6056 /* Try to guess the best choice if we have multiple choices. */
6057 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6058 if (cursor < count - 1
6059 && word_a[cursor].end - s >= e - word_a[cursor + 1].start)
6060 current = cursor;
6061 else
6063 if (cursor < count - 1)
6065 if (cursor < last_word)
6066 current = cursor + 1;
6067 else
6068 current = cursor;
6070 else
6071 current = count - 1;
6074 /* If the word is not selectable, try to find a selectable word */
6075 /* in its line. */
6076 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6077 if (!word_a[current].is_selectable)
6079 index = 0;
6080 while (word_a[current - index].start > 0
6081 && !word_a[current - index].is_selectable)
6082 index++;
6084 if (word_a[current - index].is_selectable)
6086 current -= index;
6087 found = 1;
6089 else
6091 index = 0;
6092 while (current + index < last_word
6093 && !word_a[current + index].is_selectable)
6094 index++;
6096 if (word_a[current + index].is_selectable)
6098 current += index;
6099 found = 1;
6103 else
6104 found = 1;
6106 return found;
6109 /* ========================= */
6110 /* Moves the cursor upwards. */
6111 /* ========================= */
6112 void
6113 move_up(win_t *win,
6114 term_t *term,
6115 toggle_t *toggles,
6116 search_data_t *search_data,
6117 langinfo_t *langinfo,
6118 long *nl,
6119 long page,
6120 long first_selectable,
6121 long last_line,
6122 char *tmp_word)
6124 long line; /* The line being processed (target line). */
6125 long start_line; /* The first line of the window. */
6126 long cur_line; /* The line of the cursor. */
6127 long nlines; /* Number of line in the window. */
6128 long first_selectable_line; /* the line containing the first *
6129 | selectable word. */
6130 long lines_skipped; /* The number of line between the target line and the *
6131 | first line containing a selectable word in case of *
6132 | exclusions. */
6133 long last_word; /* The last word on the target line. */
6134 long s, e; /* Starting and ending terminal position of a word. */
6135 int found; /* 1 if a line could be fond else 0. */
6137 /* Store the initial starting and ending positions of */
6138 /* the word under the cursor. */
6139 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
6140 s = word_a[current].start;
6141 e = word_a[current].end;
6143 /* Identify the line number of the first window's line */
6144 /* and the line number of the current line. */
6145 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
6146 start_line = line_nb_of_word_a[win->start];
6147 cur_line = line_nb_of_word_a[current];
6148 first_selectable_line = line_nb_of_word_a[first_selectable];
6149 lines_skipped = 0;
6150 found = 0;
6151 nlines = win->max_lines < last_line + 1 ? win->max_lines : last_line + 1;
6153 /* initialise the target line. */
6154 /* """"""""""""""""""""""""""" */
6155 line = cur_line;
6157 /* Special case if the cursor is already in the line containing the */
6158 /* first selectable word. */
6159 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6160 if (line == first_selectable_line)
6162 /* we can't move the cursor up but we still can try to show the */
6163 /* more non selectable words as we can. */
6164 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
6165 if (line <= start_line + nlines - 1 - page)
6167 /* We are scrolling one line at a time and the cursor is not in */
6168 /* the last line of the window. */
6169 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
6170 if (start_line - page > 0)
6171 /* There is enough remaining line to fill a window. */
6172 /* '''''''''''''''''''''''''''''''''''''''''''''''' */
6173 start_line -= page;
6174 else
6175 /* We cannot scroll further. */
6176 /* ''''''''''''''''''''''''' */
6177 start_line = 0;
6179 else
6181 /* The cursor is already in the last line of the windows. */
6182 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */
6183 if (line >= nlines)
6184 /* There is enough remaining line to fill a window. */
6185 /* '''''''''''''''''''''''''''''''''''''''''''''''' */
6186 start_line = line - nlines + 1;
6187 else
6188 /* We cannot scroll further. */
6189 /* ''''''''''''''''''''''''' */
6190 start_line = 0;
6193 else
6195 if (line - page < 0)
6197 /* Trivial case, we are already on the first page */
6198 /* just jump to the first selectable line. */
6199 /* """""""""""""""""""""""""""""""""""""""""""""" */
6200 line = first_selectable_line;
6201 last_word = get_line_last_word(line, last_line);
6202 find_best_word_upwards(last_word, s, e);
6204 else
6206 /* Temporarily move up one page. */
6207 /* """"""""""""""""""""""""""""" */
6208 line -= page;
6210 /* The target line cannot be before the line containing the first */
6211 /* selectable word. */
6212 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6213 if (line < first_selectable_line)
6215 line = first_selectable_line;
6216 last_word = get_line_last_word(line, last_line);
6217 find_best_word_upwards(last_word, s, e);
6219 else
6221 /* If this is not the case, search upwards for the line with a */
6222 /* selectable word. This line is guaranteed to exist. */
6223 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6224 while (line >= first_selectable_line)
6226 last_word = get_line_last_word(line, last_line);
6228 if (find_best_word_upwards(last_word, s, e))
6230 found = 1;
6231 break;
6234 line--;
6235 lines_skipped++;
6241 /* Look if we need to adjust the window to follow the cursor. */
6242 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6243 if (!found && start_line - page >= 0)
6245 /* We are on the first line containing a selectable word and */
6246 /* There is enough place to scroll up a page but only scrolls */
6247 /* up if the cursor remains in the window. */
6248 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6249 if (start_line + nlines - line > page)
6250 start_line -= page;
6252 else if (line < start_line)
6254 /* The target line is above the windows. */
6255 /* """"""""""""""""""""""""""""""""""""" */
6256 if (start_line - page - lines_skipped < 0)
6257 /* There isn't enough remaining lines to scroll up */
6258 /* a page size. */
6259 /* """"""""""""""""""""""""""""""""""""""""""""""" */
6260 start_line = 0;
6261 else
6262 start_line -= page + lines_skipped;
6265 /* And set the new value of the starting word of the window. */
6266 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6267 win->start = first_word_in_line_a[start_line];
6269 /* Set the new first column to display */
6270 /* """"""""""""""""""""""""""""""""""" */
6271 set_new_first_column(win, term);
6273 /* Redisplay the window. */
6274 /* """"""""""""""""""""" */
6275 *nl = disp_lines(win,
6276 toggles,
6277 current,
6278 count,
6279 search_mode,
6280 search_data,
6281 term,
6282 last_line,
6283 tmp_word,
6284 langinfo);
6287 /* =========================== */
6288 /* Moves the cursor downwards. */
6289 /* =========================== */
6290 void
6291 move_down(win_t *win,
6292 term_t *term,
6293 toggle_t *toggles,
6294 search_data_t *search_data,
6295 langinfo_t *langinfo,
6296 long *nl,
6297 long page,
6298 long last_selectable,
6299 long last_line,
6300 char *tmp_word)
6302 long line; /* The line being processed (target line). */
6303 long start_line; /* The first line of the window. */
6304 long cur_line; /* The line of the cursor. */
6305 long nlines; /* Number of line in the window. */
6306 long last_selectable_line; /* the line containing the last *
6307 | selectable word. */
6308 long lines_skipped; /* The number of line between the target line and the *
6309 | first line containing a selectable word in case of *
6310 | exclusions. */
6311 long last_word; /* The last word on the target line. */
6312 long s, e; /* Starting and ending terminal position of a word. */
6313 int found; /* 1 if a line could be fond in the next page else 0. */
6315 /* Store the initial starting and ending positions of */
6316 /* the word under the cursor. */
6317 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
6318 s = word_a[current].start;
6319 e = word_a[current].end;
6321 /* Identify the line number of the first window's line */
6322 /* and the line number of the current line. */
6323 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
6324 start_line = line_nb_of_word_a[win->start];
6325 cur_line = line_nb_of_word_a[current];
6326 last_selectable_line = line_nb_of_word_a[last_selectable];
6327 lines_skipped = 0;
6328 found = 0;
6329 nlines = win->max_lines < last_line + 1 ? win->max_lines : last_line + 1;
6331 /* initialise the target line. */
6332 /* """"""""""""""""""""""""""" */
6333 line = cur_line;
6335 /* Special case if the cursor is already in the line containing the */
6336 /* last selectable word. */
6337 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6338 if (line == last_selectable_line)
6340 /* we can't move the cursor down but we still can try to show the */
6341 /* more non selectable words as we can. */
6342 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
6343 if (line >= start_line + page)
6345 /* We are scrolling one line at a time and the cursor is not in */
6346 /* the first line of the window. */
6347 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
6348 if (start_line + page + nlines - 1 <= last_line)
6349 /* There is enough remaining line to fill a window. */
6350 /* '''''''''''''''''''''''''''''''''''''''''''''''' */
6351 start_line += page;
6352 else
6353 /* We cannot scroll further. */
6354 /* ''''''''''''''''''''''''' */
6355 start_line = last_line - nlines + 1;
6357 else
6359 /* The cursor is already in the first line of the windows. */
6360 /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
6361 if (last_line - line + 1 > nlines)
6362 /* There is enough remaining line to fill a window. */
6363 /* '''''''''''''''''''''''''''''''''''''''''''''''' */
6364 start_line = line;
6365 else
6366 /* We cannot scroll further. */
6367 /* ''''''''''''''''''''''''' */
6368 start_line = last_line - nlines + 1;
6371 else
6373 /* The cursor is above the line containing the last selectable word. */
6374 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6375 if (last_line - line - page < 0)
6377 /* Trivial case, we are already on the last page */
6378 /* just jump to the last selectable line. */
6379 /* """"""""""""""""""""""""""""""""""""""""""""" */
6380 line = last_selectable_line;
6381 last_word = get_line_last_word(line, last_line);
6382 find_best_word_downwards(last_word, s, e);
6384 else
6386 /* Temporarily move down one page. */
6387 /* """"""""""""""""""""""""""""""" */
6388 line += page;
6390 /* The target line cannot be after the line containing the last */
6391 /* selectable word. */
6392 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6393 if (line > last_selectable_line)
6395 line = last_selectable_line;
6396 last_word = get_line_last_word(line, last_line);
6397 find_best_word_downwards(last_word, s, e);
6399 else
6401 /* If this is not the case, search downwards for the line with a */
6402 /* selectable word. This line is guaranteed to exist. */
6403 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6404 while (line <= last_selectable_line)
6406 last_word = get_line_last_word(line, last_line);
6408 if (find_best_word_downwards(last_word, s, e))
6410 found = 1;
6411 break;
6414 line++;
6415 lines_skipped++;
6420 /* Look if we need to adjust the window to follow the cursor. */
6421 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6422 if (!found && start_line + nlines - 1 + page <= last_line)
6424 /* We are on the last line containing a selectable word and */
6425 /* There is enough place to scroll down a page but only scrolls */
6426 /* down if the cursor remains in the window. */
6427 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6428 if (line - start_line >= page)
6429 start_line += page;
6431 else if (line > start_line + nlines - 1)
6433 /* The target line is below the windows. */
6434 /* """"""""""""""""""""""""""""""""""""" */
6435 if (start_line + nlines + page + lines_skipped - 1 > last_line)
6436 /* There isn't enough remaining lines to scroll down */
6437 /* a page size. */
6438 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
6439 start_line = last_line - nlines + 1;
6440 else
6441 start_line += page + lines_skipped;
6445 /* And set the new value of the starting word of the window. */
6446 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6447 win->start = first_word_in_line_a[start_line];
6449 /* Set the new first column to display. */
6450 /* """""""""""""""""""""""""""""""""""" */
6451 set_new_first_column(win, term);
6453 /* Redisplay the window. */
6454 /* """"""""""""""""""""" */
6455 *nl = disp_lines(win,
6456 toggles,
6457 current,
6458 count,
6459 search_mode,
6460 search_data,
6461 term,
6462 last_line,
6463 tmp_word,
6464 langinfo);
6467 /* ========================================= */
6468 /* Initialize some internal data structures. */
6469 /* ========================================= */
6470 void
6471 init_main_ds(attrib_t *init_attr,
6472 win_t *win,
6473 limit_t *limits,
6474 ticker_t *timers,
6475 toggle_t *toggles,
6476 misc_t *misc,
6477 mouse_t *mouse,
6478 timeout_t *timeout,
6479 daccess_t *daccess)
6481 int i;
6483 /* Initial attribute settings. */
6484 /* """"""""""""""""""""""""""" */
6485 init_attr->is_set = UNSET;
6486 init_attr->fg = -1;
6487 init_attr->bg = -1;
6488 init_attr->bold = (signed char)-1;
6489 init_attr->dim = (signed char)-1;
6490 init_attr->reverse = (signed char)-1;
6491 init_attr->standout = (signed char)-1;
6492 init_attr->underline = (signed char)-1;
6493 init_attr->italic = (signed char)-1;
6494 init_attr->invis = (signed char)-1;
6495 init_attr->blink = (signed char)-1;
6497 /* Win fields initialization. */
6498 /* """""""""""""""""""""""""" */
6499 win->max_lines = 5;
6500 win->message_lines = 0;
6501 win->asked_max_lines = -1;
6502 win->center = 0;
6503 win->max_cols = 0;
6504 win->col_sep = 0;
6505 win->wide = 0;
6506 win->tab_mode = 0;
6507 win->col_mode = 0;
6508 win->line_mode = 0;
6509 win->first_column = 0;
6510 win->real_max_width = 0;
6511 win->sb_column = -1;
6512 win->hbar_displayed = 0;
6514 win->cursor_attr = *init_attr;
6515 win->cursor_marked_attr = *init_attr;
6516 win->cursor_on_marked_attr = *init_attr;
6517 win->cursor_on_tag_attr = *init_attr;
6518 win->cursor_on_tag_marked_attr = *init_attr;
6519 win->marked_attr = *init_attr;
6520 win->bar_attr = *init_attr;
6521 win->shift_attr = *init_attr;
6522 win->message_attr = *init_attr;
6523 win->search_field_attr = *init_attr;
6524 win->search_text_attr = *init_attr;
6525 win->search_err_field_attr = *init_attr;
6526 win->search_err_text_attr = *init_attr;
6527 win->match_field_attr = *init_attr;
6528 win->match_text_attr = *init_attr;
6529 win->match_err_field_attr = *init_attr;
6530 win->match_err_text_attr = *init_attr;
6531 win->include_attr = *init_attr;
6532 win->exclude_attr = *init_attr;
6533 win->tag_attr = *init_attr;
6534 win->daccess_attr = *init_attr;
6536 win->next_tag_id = 1; /* No word is currently tagged. */
6537 win->sel_sep = NULL;
6539 for (i = 0; i < 9; i++)
6540 win->special_attr[i] = *init_attr;
6542 /* Default limits initialization. */
6543 /* """""""""""""""""""""""""""""" */
6544 limits->words = 32767;
6545 limits->cols = 256;
6546 limits->word_length = 512;
6548 /* Default timers in 1/10 s. */
6549 /* """"""""""""""""""""""""" */
6550 timers->search = 100 * FREQ / 10;
6551 timers->forgotten = -1; /* Disabled by default. */
6552 timers->help = 300 * FREQ / 10;
6553 timers->winch = 20 * FREQ / 10;
6554 timers->direct_access = 6 * FREQ / 10;
6556 /* Toggles initialization. */
6557 /* """"""""""""""""""""""" */
6558 toggles->del_line = 0;
6559 toggles->enter_val_in_search = 0;
6560 toggles->no_scrollbar = 0;
6561 toggles->blank_nonprintable = 0;
6562 toggles->keep_spaces = 0;
6563 toggles->taggable = 0;
6564 toggles->autotag = 0;
6565 toggles->noautotag = 0;
6566 toggles->pinable = 0;
6567 toggles->visual_bell = 0;
6568 toggles->incremental_search = 0;
6569 toggles->no_mouse = 0;
6570 toggles->show_blank_words = 0;
6571 toggles->cols_first = 0;
6572 toggles->rows_first = 0;
6574 /* Misc default values. */
6575 /* """""""""""""""""""" */
6576 misc->default_search_method = NONE;
6577 misc->ignore_quotes = 0;
6578 misc->invalid_char_substitute = '.';
6579 misc->blank_char_substitute = '_';
6581 /* Mouse values. */
6582 /* """"""""""""" */
6583 mouse->button[0] = 1;
6584 mouse->button[1] = 2;
6585 mouse->button[2] = 3;
6587 mouse->double_click_delay = 150;
6589 /* Set the default timeout to 0 (no expiration). */
6590 /* """"""""""""""""""""""""""""""""""""""""""""" */
6591 timeout->initial_value = 0;
6592 timeout->remain = 0;
6593 timeout->reached = 0;
6595 /* Initialize Direct Access settings. */
6596 /* """""""""""""""""""""""""""""""""" */
6597 daccess->mode = DA_TYPE_NONE;
6598 daccess->left = xstrdup(" ");
6599 daccess->right = xstrdup(")");
6600 daccess->alignment = 'r';
6601 daccess->padding = 'a';
6602 daccess->head = 'k'; /* Keep by default. */
6603 daccess->length = -2;
6604 daccess->flength = 0;
6605 daccess->offset = 0;
6606 daccess->plus = 0;
6607 daccess->size = 0;
6608 daccess->ignore = 0;
6609 daccess->follow = 'y';
6610 daccess->missing = 'y';
6611 daccess->num_sep = NULL;
6612 daccess->def_number = -1;
6615 /* *********************************** */
6616 /* ctxopt contexts callback functions. */
6617 /* *********************************** */
6619 /* ******************************** */
6620 /* ctxopt option callback function. */
6621 /* ******************************** */
6623 void
6624 help_action(char *ctx_name,
6625 char *opt_name,
6626 char *param,
6627 int nb_values,
6628 char **values,
6629 int nb_opt_data,
6630 void **opt_data,
6631 int nb_ctx_data,
6632 void **ctx_data)
6634 if (strcmp(ctx_name, "Columns") == 0)
6635 columns_help();
6636 else if (strcmp(ctx_name, "Lines") == 0)
6637 lines_help();
6638 else if (strcmp(ctx_name, "Tabulations") == 0)
6639 tabulations_help();
6640 else if (strcmp(ctx_name, "Tagging") == 0)
6641 tagging_help();
6642 else
6643 main_help();
6645 exit(EXIT_FAILURE);
6648 void
6649 long_help_action(char *ctx_name,
6650 char *opt_name,
6651 char *param,
6652 int nb_values,
6653 char **values,
6654 int nb_opt_data,
6655 void **opt_data,
6656 int nb_ctx_data,
6657 void **ctx_data)
6659 ctxopt_disp_usage(continue_after);
6661 printf("\nRead the manual for more information.\n");
6663 exit(EXIT_FAILURE);
6666 void
6667 usage_action(char *ctx_name,
6668 char *opt_name,
6669 char *param,
6670 int nb_values,
6671 char **values,
6672 int nb_opt_data,
6673 void **opt_data,
6674 int nb_ctx_data,
6675 void **ctx_data)
6677 ctxopt_ctx_disp_usage(ctx_name, exit_after);
6680 void
6681 lines_action(char *ctx_name,
6682 char *opt_name,
6683 char *param,
6684 int nb_values,
6685 char **values,
6686 int nb_opt_data,
6687 void **opt_data,
6688 int nb_ctx_data,
6689 void **ctx_data)
6691 win_t *win = opt_data[0];
6693 if (nb_values == 1)
6694 /* No need to validate if values are numeric here, they have */
6695 /* already been validated by the check_integer_constraint filter. */
6696 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6697 sscanf(values[0], "%d", &(win->asked_max_lines));
6698 else
6699 win->asked_max_lines = 0;
6702 void
6703 tab_mode_action(char *ctx_name,
6704 char *opt_name,
6705 char *param,
6706 int nb_values,
6707 char **values,
6708 int nb_opt_data,
6709 void **opt_data,
6710 int nb_ctx_data,
6711 void **ctx_data)
6713 win_t *win = opt_data[0];
6715 long max_cols;
6717 if (nb_values == 1)
6719 /* No need to validate if values are numeric or out of range here, */
6720 /* they have already been validated by the check_integer_constraint */
6721 /* and ctxopt_range_constraint filters. */
6722 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
6723 sscanf(values[0], "%ld", &max_cols);
6724 win->max_cols = max_cols;
6727 win->tab_mode = 1;
6728 win->col_mode = 0;
6729 win->line_mode = 0;
6732 void
6733 set_pattern_action(char *ctx_name,
6734 char *opt_name,
6735 char *param,
6736 int nb_values,
6737 char **values,
6738 int nb_opt_data,
6739 void **opt_data,
6740 int nb_ctx_data,
6741 void **ctx_data)
6743 char **pattern = opt_data[0];
6744 misc_t *misc = opt_data[1];
6746 *pattern = xstrdup(values[0]);
6747 utf8_interpret(*pattern, misc->invalid_char_substitute);
6750 void
6751 int_action(char *ctx_name,
6752 char *opt_name,
6753 char *param,
6754 int nb_values,
6755 char **values,
6756 int nb_opt_data,
6757 void **opt_data,
6758 int nb_ctx_data,
6759 void **ctx_data)
6761 char **string = opt_data[0];
6762 int *shell_like = opt_data[1];
6763 langinfo_t *langinfo = opt_data[2];
6764 misc_t *misc = opt_data[3];
6766 if (nb_values == 1)
6768 *string = xstrdup(values[0]);
6769 if (!langinfo->utf8)
6770 utf8_sanitize(*string, misc->invalid_char_substitute);
6771 utf8_interpret(*string, misc->invalid_char_substitute);
6774 *shell_like = 0;
6777 void
6778 set_string_action(char *ctx_name,
6779 char *opt_name,
6780 char *param,
6781 int nb_values,
6782 char **values,
6783 int nb_opt_data,
6784 void **opt_data,
6785 int nb_ctx_data,
6786 void **ctx_data)
6788 char **string = opt_data[0];
6789 langinfo_t *langinfo = opt_data[1];
6790 misc_t *misc = opt_data[2];
6792 *string = xstrdup(values[0]);
6793 if (!langinfo->utf8)
6794 utf8_sanitize(*string, misc->invalid_char_substitute);
6795 utf8_interpret(*string, misc->invalid_char_substitute);
6798 void
6799 wide_mode_action(char *ctx_name,
6800 char *opt_name,
6801 char *param,
6802 int nb_values,
6803 char **values,
6804 int nb_opt_data,
6805 void **opt_data,
6806 int nb_ctx_data,
6807 void **ctx_data)
6809 win_t *win = opt_data[0];
6811 win->wide = 1;
6814 void
6815 center_mode_action(char *ctx_name,
6816 char *opt_name,
6817 char *param,
6818 int nb_values,
6819 char **values,
6820 int nb_opt_data,
6821 void **opt_data,
6822 int nb_ctx_data,
6823 void **ctx_data)
6825 win_t *win = opt_data[0];
6827 win->center = 1;
6830 void
6831 columns_select_action(char *ctx_name,
6832 char *opt_name,
6833 char *param,
6834 int nb_values,
6835 char **values,
6836 int nb_opt_data,
6837 void **opt_data,
6838 int nb_ctx_data,
6839 void **ctx_data)
6841 int v;
6842 ll_t **cols_selector_list = opt_data[0];
6843 toggle_t *toggles = opt_data[1];
6845 if (*cols_selector_list == NULL)
6846 *cols_selector_list = ll_new();
6848 if (toggles->cols_first == 0 && toggles->rows_first == 0)
6849 toggles->cols_first = 1;
6851 for (v = 0; v < nb_values; v++)
6852 ll_append(*cols_selector_list, xstrdup(values[v]));
6855 void
6856 rows_select_action(char *ctx_name,
6857 char *opt_name,
6858 char *param,
6859 int nb_values,
6860 char **values,
6861 int nb_opt_data,
6862 void **opt_data,
6863 int nb_ctx_data,
6864 void **ctx_data)
6866 int v;
6867 ll_t **rows_selector_list = opt_data[0];
6868 win_t *win = opt_data[1];
6869 toggle_t *toggles = opt_data[2];
6871 if (*rows_selector_list == NULL)
6872 *rows_selector_list = ll_new();
6874 if (toggles->cols_first == 0 && toggles->rows_first == 0)
6875 toggles->rows_first = 1;
6877 for (v = 0; v < nb_values; v++)
6878 ll_append(*rows_selector_list, xstrdup(values[v]));
6880 win->max_cols = 0; /* Disable the window column restriction. */
6883 void
6884 aligns_select_action(char *ctx_name,
6885 char *opt_name,
6886 char *param,
6887 int nb_values,
6888 char **values,
6889 int nb_opt_data,
6890 void **opt_data,
6891 int nb_ctx_data,
6892 void **ctx_data)
6894 int v;
6895 ll_t **aligns_selector_list = opt_data[0];
6897 if (*aligns_selector_list == NULL)
6898 *aligns_selector_list = ll_new();
6900 for (v = 0; v < nb_values; v++)
6901 ll_append(*aligns_selector_list, xstrdup(values[v]));
6904 void
6905 toggle_action(char *ctx_name,
6906 char *opt_name,
6907 char *param,
6908 int nb_values,
6909 char **values,
6910 int nb_opt_data,
6911 void **opt_data,
6912 int nb_ctx_data,
6913 void **ctx_data)
6915 toggle_t *toggles = opt_data[0];
6917 if (strcmp(opt_name, "clean") == 0)
6918 toggles->del_line = 1;
6919 else if (strcmp(opt_name, "keep_spaces") == 0)
6920 toggles->keep_spaces = 1;
6921 else if (strcmp(opt_name, "visual_bell") == 0)
6922 toggles->visual_bell = 1;
6923 else if (strcmp(opt_name, "validate_in_search_mode") == 0)
6924 toggles->enter_val_in_search = 1;
6925 else if (strcmp(opt_name, "blank_nonprintable") == 0)
6926 toggles->blank_nonprintable = 1;
6927 else if (strcmp(opt_name, "no_scroll_bar") == 0)
6929 toggles->no_scrollbar = 1;
6930 toggles->no_hor_scrollbar = 1;
6932 else if (strcmp(opt_name, "no_hor_scroll_bar") == 0)
6934 if (!toggles->no_scrollbar)
6935 toggles->no_hor_scrollbar = 1;
6937 else if (strcmp(opt_name, "auto_tag") == 0)
6938 toggles->autotag = 1;
6939 else if (strcmp(opt_name, "no_auto_tag") == 0)
6940 toggles->noautotag = 1;
6941 else if (strcmp(opt_name, "incremental_search") == 0)
6942 toggles->incremental_search = 1;
6943 else if (strcmp(opt_name, "no_mouse") == 0)
6944 toggles->no_mouse = 1;
6947 void
6948 invalid_char_action(char *ctx_name,
6949 char *opt_name,
6950 char *param,
6951 int nb_values,
6952 char **values,
6953 int nb_opt_data,
6954 void **opt_data,
6955 int nb_ctx_data,
6956 void **ctx_data)
6958 misc_t *misc = opt_data[0];
6960 char ic = *values[0];
6962 if (isprint(ic))
6963 misc->invalid_char_substitute = ic;
6964 else
6965 misc->invalid_char_substitute = '.';
6968 void
6969 show_blank_action(char *ctx_name,
6970 char *opt_name,
6971 char *param,
6972 int nb_values,
6973 char **values,
6974 int nb_opt_data,
6975 void **opt_data,
6976 int nb_ctx_data,
6977 void **ctx_data)
6979 toggle_t *toggles = opt_data[0];
6980 misc_t *misc = opt_data[1];
6982 unsigned char bc;
6984 toggles->show_blank_words = 1;
6986 if (nb_values == 1)
6988 bc = (unsigned char)*values[0];
6990 if (isprint(bc))
6991 misc->blank_char_substitute = bc;
6992 else
6993 misc->blank_char_substitute = '_';
6997 void
6998 gutter_action(char *ctx_name,
6999 char *opt_name,
7000 char *param,
7001 int nb_values,
7002 char **values,
7003 int nb_opt_data,
7004 void **opt_data,
7005 int nb_ctx_data,
7006 void **ctx_data)
7008 win_t *win = opt_data[0];
7009 langinfo_t *langinfo = opt_data[1];
7010 misc_t *misc = opt_data[2];
7012 if (nb_values == 0)
7014 /* As there is no argument, the gutter array will only contain */
7015 /* a vertical bar. */
7016 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7017 win->gutter_a = xmalloc(1 * sizeof(char *));
7019 if (langinfo->utf8)
7020 win->gutter_a[0] = xstrdup(vertical_bar);
7021 else
7022 win->gutter_a[0] = xstrdup("|");
7024 win->gutter_nb = 1;
7026 else
7028 /* The argument is used to feed the gutter array, each of its character */
7029 /* Will serve as gutter in a round-robin way. */
7030 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7031 long n;
7032 wchar_t *w;
7033 long i, offset;
7034 char *gutter;
7036 gutter = xstrdup(values[0]);
7038 utf8_interpret(gutter,
7039 misc->invalid_char_substitute); /* Guarantees a well *
7040 | formed UTF-8 string. */
7042 win->gutter_nb = utf8_strlen(gutter);
7043 win->gutter_a = xmalloc(win->gutter_nb * sizeof(char *));
7045 offset = 0;
7047 for (i = 0; i < win->gutter_nb; i++)
7049 int mblength;
7051 mblength = utf8_get_length(*(gutter + offset));
7052 win->gutter_a[i] = xcalloc(1, mblength + 1);
7053 memcpy(win->gutter_a[i], gutter + offset, mblength);
7055 n = wcswidth((w = utf8_strtowcs(win->gutter_a[i])), 1);
7056 free(w);
7058 if (n > 1)
7060 fprintf(stderr, "%s: A multi columns gutter is not allowed.\n", param);
7061 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7063 offset += mblength;
7065 free(gutter);
7067 win->col_sep = 1; /* Activate the gutter. */
7070 void
7071 column_mode_action(char *ctx_name,
7072 char *opt_name,
7073 char *param,
7074 int nb_values,
7075 char **values,
7076 int nb_opt_data,
7077 void **opt_data,
7078 int nb_ctx_data,
7079 void **ctx_data)
7081 win_t *win = opt_data[0];
7083 win->tab_mode = 0;
7084 win->col_mode = 1;
7085 win->line_mode = 0;
7086 win->max_cols = 0;
7089 void
7090 line_mode_action(char *ctx_name,
7091 char *opt_name,
7092 char *param,
7093 int nb_values,
7094 char **values,
7095 int nb_opt_data,
7096 void **opt_data,
7097 int nb_ctx_data,
7098 void **ctx_data)
7100 win_t *win = opt_data[0];
7102 win->line_mode = 1;
7103 win->tab_mode = 0;
7104 win->col_mode = 0;
7105 win->max_cols = 0;
7108 void
7109 include_re_action(char *ctx_name,
7110 char *opt_name,
7111 char *param,
7112 int nb_values,
7113 char **values,
7114 int nb_opt_data,
7115 void **opt_data,
7116 int nb_ctx_data,
7117 void **ctx_data)
7119 int *pattern_def_include = opt_data[0];
7120 char **include_pattern = opt_data[1];
7121 misc_t *misc = opt_data[2];
7123 /* Set the default behaviour if not already set. */
7124 /* """"""""""""""""""""""""""""""""""""""""""""" */
7125 if (*pattern_def_include == -1)
7126 *pattern_def_include = 0;
7128 if (*include_pattern == NULL)
7129 *include_pattern = concat("(", values[0], ")", (char *)0);
7130 else
7131 *include_pattern = concat(*include_pattern,
7132 "|(",
7133 values[0],
7134 ")",
7135 (char *)0);
7137 /* Replace the UTF-8 ASCII representations by their binary values in */
7138 /* inclusion patterns. */
7139 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7140 utf8_interpret(*include_pattern, misc->invalid_char_substitute);
7143 void
7144 exclude_re_action(char *ctx_name,
7145 char *opt_name,
7146 char *param,
7147 int nb_values,
7148 char **values,
7149 int nb_opt_data,
7150 void **opt_data,
7151 int nb_ctx_data,
7152 void **ctx_data)
7154 int *pattern_def_include = opt_data[0];
7155 char **exclude_pattern = opt_data[1];
7156 misc_t *misc = opt_data[2];
7158 /* Set the default behaviour if not already set. */
7159 /* """"""""""""""""""""""""""""""""""""""""""""" */
7160 if (*pattern_def_include == -1)
7161 *pattern_def_include = 1;
7163 if (*exclude_pattern == NULL)
7164 *exclude_pattern = concat("(", values[0], ")", (char *)0);
7165 else
7166 *exclude_pattern = concat(*exclude_pattern,
7167 "|(",
7168 values[0],
7169 ")",
7170 (char *)0);
7172 /* Replace the UTF-8 ASCII representations by their binary values in */
7173 /* exclusion patterns. */
7174 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7175 utf8_interpret(*exclude_pattern, misc->invalid_char_substitute);
7178 void
7179 post_subst_action(char *ctx_name,
7180 char *opt_name,
7181 char *param,
7182 int nb_values,
7183 char **values,
7184 int nb_opt_data,
7185 void **opt_data,
7186 int nb_ctx_data,
7187 void **ctx_data)
7189 ll_t **list = opt_data[0];
7190 misc_t *misc = opt_data[1];
7192 int i;
7194 if (*list == NULL)
7195 *list = ll_new();
7197 for (i = 0; i < nb_values; i++)
7199 sed_t *sed_node;
7201 sed_node = xmalloc(sizeof(sed_t));
7202 sed_node->pattern = xstrdup(values[i]);
7203 utf8_interpret(sed_node->pattern, misc->invalid_char_substitute);
7204 sed_node->stop = 0;
7205 ll_append(*list, sed_node);
7209 void
7210 special_level_action(char *ctx_name,
7211 char *opt_name,
7212 char *param,
7213 int nb_values,
7214 char **values,
7215 int nb_opt_data,
7216 void **opt_data,
7217 int nb_ctx_data,
7218 void **ctx_data)
7220 char **special_pattern = opt_data[0];
7221 win_t *win = opt_data[1];
7222 term_t *term = opt_data[2];
7223 attrib_t *init_attr = opt_data[3];
7224 misc_t *misc = opt_data[4];
7226 attrib_t attr = *init_attr;
7227 char opt = param[strlen(param) - 1]; /* last character of param. */
7228 int i;
7230 special_pattern[opt - '1'] = xstrdup(values[0]);
7231 utf8_interpret(special_pattern[opt - '1'], misc->invalid_char_substitute);
7233 /* Parse optional additional arguments. */
7234 /* """""""""""""""""""""""""""""""""""" */
7235 for (i = 1; i < nb_values; i++)
7237 /* Colors must respect the format: <fg color>/<bg color>. */
7238 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
7239 if (parse_attr(values[i], &attr, term->colors))
7241 win->special_attr[opt - '1'].is_set = FORCED;
7242 win->special_attr[opt - '1'].fg = attr.fg;
7243 win->special_attr[opt - '1'].bg = attr.bg;
7244 win->special_attr[opt - '1'].bold = attr.bold;
7245 win->special_attr[opt - '1'].dim = attr.dim;
7246 win->special_attr[opt - '1'].reverse = attr.reverse;
7247 win->special_attr[opt - '1'].standout = attr.standout;
7248 win->special_attr[opt - '1'].underline = attr.underline;
7249 win->special_attr[opt - '1'].italic = attr.italic;
7250 win->special_attr[opt - '1'].invis = attr.invis;
7251 win->special_attr[opt - '1'].blink = attr.blink;
7256 void
7257 attributes_action(char *ctx_name,
7258 char *opt_name,
7259 char *param,
7260 int nb_values,
7261 char **values,
7262 int nb_opt_data,
7263 void **opt_data,
7264 int nb_ctx_data,
7265 void **ctx_data)
7267 win_t *win = opt_data[0];
7268 term_t *term = opt_data[1];
7269 attrib_t *init_attr = opt_data[2];
7271 long i, a; /* loop index. */
7272 long offset = 0; /* nb of chars to ship to find the attribute *
7273 | representation (prefix size). */
7275 attrib_t attr;
7276 attrib_t *attr_to_set = NULL;
7278 /* Flags to check if an attribute is already set */
7279 /* """"""""""""""""""""""""""""""""""""""""""""" */
7280 int inc_attr_set = 0; /* included words. */
7281 int exc_attr_set = 0; /* excluded words. */
7282 int cur_attr_set = 0; /* highlighted word (cursor). */
7283 int bar_attr_set = 0; /* scroll bar. */
7284 int shift_attr_set = 0; /* horizontal scrolling arrows. */
7285 int message_attr_set = 0; /* message (title). */
7286 int tag_attr_set = 0; /* selected (tagged) words. */
7287 int cursor_on_tag_attr_set = 0; /* selected words under the cursor. */
7288 int sf_attr_set = 0; /* currently searched field color. */
7289 int st_attr_set = 0; /* currently searched text color. */
7290 int mf_attr_set = 0; /* matching word field color. */
7291 int mt_attr_set = 0; /* matching word text color. */
7292 int mfe_attr_set = 0; /* matching word with error field color. */
7293 int mte_attr_set = 0; /* matching word with error text color. */
7294 int daccess_attr_set = 0; /* Direct access text color. */
7296 /* Information relatives to the attributes to be searched and set. */
7297 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7298 struct
7300 attrib_t *attr;
7301 char *msg;
7302 int *flag;
7303 char *prefix;
7304 int prefix_len;
7305 } attr_infos[] = {
7306 { &win->exclude_attr,
7307 "The exclude attribute is already set.",
7308 &exc_attr_set,
7309 "e:",
7310 2 },
7311 { &win->include_attr,
7312 "The include attribute is already set.",
7313 &inc_attr_set,
7314 "i:",
7315 2 },
7316 { &win->cursor_attr,
7317 "The cursor attribute is already set.",
7318 &cur_attr_set,
7319 "c:",
7320 2 },
7321 { &win->bar_attr,
7322 "The scroll bar attribute is already set.",
7323 &bar_attr_set,
7324 "b:",
7325 2 },
7326 { &win->shift_attr,
7327 "The shift attribute is already set.",
7328 &shift_attr_set,
7329 "s:",
7330 2 },
7331 { &win->message_attr,
7332 "The message attribute is already set.",
7333 &message_attr_set,
7334 "m:",
7335 2 },
7336 { &win->tag_attr,
7337 "The tag attribute is already set.",
7338 &tag_attr_set,
7339 "t:",
7340 2 },
7341 { &win->cursor_on_tag_attr,
7342 "The cursor on tagged word attribute is already set.",
7343 &cursor_on_tag_attr_set,
7344 "ct:",
7345 3 },
7346 { &win->search_field_attr,
7347 "The search field attribute is already set.",
7348 &sf_attr_set,
7349 "sf:",
7350 3 },
7351 { &win->search_text_attr,
7352 "The search text attribute is already set.",
7353 &st_attr_set,
7354 "st:",
7355 3 },
7356 { &win->search_err_field_attr,
7357 "The search with error field attribute is already set.",
7358 &sf_attr_set,
7359 "sfe:",
7360 4 },
7361 { &win->search_err_text_attr,
7362 "The search text with error attribute is already set.",
7363 &st_attr_set,
7364 "ste:",
7365 4 },
7366 { &win->match_field_attr,
7367 "The matching word field attribute is already set.",
7368 &mf_attr_set,
7369 "mf:",
7370 3 },
7371 { &win->match_text_attr,
7372 "The matching word text attribute is already set.",
7373 &mt_attr_set,
7374 "mt:",
7375 3 },
7376 { &win->match_err_field_attr,
7377 "The matching word with error field attribute is already set.",
7378 &mfe_attr_set,
7379 "mfe:",
7380 4 },
7381 { &win->match_err_text_attr,
7382 "The matching word with error text attribute is already set.",
7383 &mte_attr_set,
7384 "mte:",
7385 4 },
7386 { &win->daccess_attr,
7387 "The direct access tag attribute is already set.",
7388 &daccess_attr_set,
7389 "da:",
7390 3 },
7391 { NULL, NULL, NULL, NULL, 0 }
7394 /* Parse the arguments. */
7395 /* The syntax of the attributes has already been pre validated by the */
7396 /* ctxopt_re_constraint filter. */
7397 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7398 for (a = 0; a < nb_values; a++)
7400 attr = *init_attr;
7402 i = 0;
7403 while (attr_infos[i].flag != NULL)
7405 if (strncmp(values[a], attr_infos[i].prefix, attr_infos[i].prefix_len)
7406 == 0)
7408 if (*attr_infos[i].flag)
7410 fprintf(stderr, "%s: ", param);
7411 fputs_safe(attr_infos[i].msg, stderr);
7412 fputs_safe("\n", stderr);
7413 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7416 attr_to_set = attr_infos[i].attr;
7417 *attr_infos[i].flag = 1;
7418 offset = attr_infos[i].prefix_len;
7419 break; /* We have found a matching prefix, *
7420 | no need to continue. */
7422 i++;
7424 if (attr_infos[i].flag == NULL)
7426 fprintf(stderr, "%s: Bad attribute prefix in %s\n", param, values[a]);
7427 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7430 /* Attributes must respect the format: */
7431 /* <fg color>/<bg color>,<styles>. */
7432 /* """"""""""""""""""""""""""""""""""" */
7433 if (parse_attr(values[a] + offset, &attr, term->colors))
7435 attr_to_set->is_set = FORCED;
7436 attr_to_set->fg = attr.fg;
7437 attr_to_set->bg = attr.bg;
7438 attr_to_set->bold = attr.bold;
7439 attr_to_set->dim = attr.dim;
7440 attr_to_set->reverse = attr.reverse;
7441 attr_to_set->standout = attr.standout;
7442 attr_to_set->underline = attr.underline;
7443 attr_to_set->italic = attr.italic;
7444 attr_to_set->invis = attr.invis;
7445 attr_to_set->blink = attr.blink;
7447 else
7449 fprintf(stderr, "%s: Bad attribute settings %s\n", param, values[a]);
7450 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7455 void
7456 limits_action(char *ctx_name,
7457 char *opt_name,
7458 char *param,
7459 int nb_values,
7460 char **values,
7461 int nb_opt_data,
7462 void **opt_data,
7463 int nb_ctx_data,
7464 void **ctx_data)
7466 limit_t *limits = opt_data[0];
7468 long l;
7469 long val;
7471 char *lim;
7472 char *endptr;
7473 char *p;
7475 /* Parse the arguments. */
7476 /* """""""""""""""""""" */
7477 for (l = 0; l < nb_values; l++)
7479 errno = 0;
7480 lim = values[l];
7481 p = lim + 2;
7483 switch (*lim)
7485 case 'l': /* word length. */
7486 val = strtol(p, &endptr, 0);
7487 if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
7488 || *endptr != '\0')
7490 fprintf(stderr, "%s: Invalid word length limit. ", p);
7491 fprintf(stderr,
7492 "Using the default value: %ld\n",
7493 limits->word_length);
7495 else
7496 limits->word_length = val;
7497 break;
7499 case 'w': /* max number of words. */
7500 val = strtol(p, &endptr, 0);
7501 if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
7502 || *endptr != '\0')
7504 fprintf(stderr, "%s: Invalid words number limit. ", p);
7505 fprintf(stderr, "Using the default value: %ld\n", limits->words);
7507 else
7508 limits->words = val;
7509 break;
7511 case 'c': /* max number of words. */
7512 val = strtol(p, &endptr, 0);
7513 if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
7514 || *endptr != '\0')
7516 fprintf(stderr, "%s: Invalid columns number limit. ", p);
7517 fprintf(stderr, "Using the default value: %ld\n", limits->cols);
7519 else
7520 limits->cols = val;
7521 break;
7523 default:
7524 fprintf(stderr, "%s: Invalid limit keyword, should be l, w or c.\n", p);
7525 break;
7530 void
7531 copyright_action(char *ctx_name,
7532 char *opt_name,
7533 char *param,
7534 int nb_values,
7535 char **values,
7536 int nb_opt_data,
7537 void **opt_data,
7538 int nb_ctx_data,
7539 void **ctx_data)
7541 fputs_safe("Copyright 2015 - Pierre Gentile <p.gen.progs@gmail.com> - "
7542 "MPL-2.0\n",
7543 stdout);
7545 exit(EXIT_SUCCESS);
7548 void
7549 version_action(char *ctx_name,
7550 char *opt_name,
7551 char *param,
7552 int nb_values,
7553 char **values,
7554 int nb_opt_data,
7555 void **opt_data,
7556 int nb_ctx_data,
7557 void **ctx_data)
7559 fputs_safe("Version: " VERSION "\n", stdout);
7561 exit(EXIT_SUCCESS);
7564 void
7565 timeout_action(char *ctx_name,
7566 char *opt_name,
7567 char *param,
7568 int nb_values,
7569 char **values,
7570 int nb_opt_data,
7571 void **opt_data,
7572 int nb_ctx_data,
7573 void **ctx_data)
7575 misc_t *misc = opt_data[0];
7577 if (strcmp(opt_name, "hidden_timeout") == 0)
7578 quiet_timeout = 1;
7580 if (strprefix("current", values[0]))
7581 timeout.mode = CURRENT;
7582 else if (strprefix("quit", values[0]))
7583 timeout.mode = QUIT;
7584 else if (strprefix("word", values[0]))
7586 if (nb_values == 3)
7588 timeout.mode = WORD;
7589 timeout_word = xstrdup(values[1]);
7590 utf8_interpret(timeout_word, misc->invalid_char_substitute);
7592 else
7594 fprintf(stderr, "%s: Missing timeout selected word or delay.\n", param);
7595 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7599 if (sscanf(values[nb_values - 1], "%5u", &timeout.initial_value) == 1)
7601 timeout.initial_value *= FREQ;
7602 timeout.remain = timeout.initial_value;
7604 else
7606 fprintf(stderr, "%s: Invalid timeout delay.\n", param);
7607 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7611 void
7612 tag_mode_action(char *ctx_name,
7613 char *opt_name,
7614 char *param,
7615 int nb_values,
7616 char **values,
7617 int nb_opt_data,
7618 void **opt_data,
7619 int nb_ctx_data,
7620 void **ctx_data)
7622 toggle_t *toggles = opt_data[0];
7623 win_t *win = opt_data[1];
7624 misc_t *misc = opt_data[2];
7626 toggles->taggable = 1;
7628 if (nb_values == 1)
7630 win->sel_sep = xstrdup(values[0]);
7631 utf8_interpret(win->sel_sep, misc->invalid_char_substitute);
7635 void
7636 pin_mode_action(char *ctx_name,
7637 char *opt_name,
7638 char *param,
7639 int nb_values,
7640 char **values,
7641 int nb_opt_data,
7642 void **opt_data,
7643 int nb_ctx_data,
7644 void **ctx_data)
7646 toggle_t *toggles = opt_data[0];
7647 win_t *win = opt_data[1];
7648 misc_t *misc = opt_data[2];
7650 toggles->taggable = 1;
7651 toggles->pinable = 1;
7653 if (nb_values == 1)
7655 win->sel_sep = xstrdup(values[0]);
7656 utf8_interpret(win->sel_sep, misc->invalid_char_substitute);
7660 void
7661 search_method_action(char *ctx_name,
7662 char *opt_name,
7663 char *param,
7664 int nb_values,
7665 char **values,
7666 int nb_opt_data,
7667 void **opt_data,
7668 int nb_ctx_data,
7669 void **ctx_data)
7671 misc_t *misc = opt_data[0];
7673 if (strprefix("prefix", values[0]))
7674 misc->default_search_method = PREFIX;
7675 else if (strprefix("fuzzy", values[0]))
7676 misc->default_search_method = FUZZY;
7677 else if (strprefix("substring", values[0]))
7678 misc->default_search_method = SUBSTRING;
7679 else
7681 fprintf(stderr, "%s: Bad search method: %s\n", param, values[0]);
7682 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7686 void
7687 auto_da_action(char *ctx_name,
7688 char *opt_name,
7689 char *param,
7690 int nb_values,
7691 char **values,
7692 int nb_opt_data,
7693 void **opt_data,
7694 int nb_ctx_data,
7695 void **ctx_data)
7697 char **daccess_pattern = opt_data[0];
7698 int i;
7700 if (nb_values == 0)
7702 if (daccess.missing == 'y' || ((daccess.mode & DA_TYPE_POS) == 0))
7704 if (*daccess_pattern == NULL)
7706 *daccess_pattern = xstrdup("(.)");
7707 daccess.mode |= DA_TYPE_AUTO; /* Auto. */
7709 else
7710 *daccess_pattern = concat(*daccess_pattern, "|(.)", (char *)0);
7713 else
7714 for (i = 0; i < nb_values; i++)
7716 char *value;
7718 if (*values[i] == '\0'
7719 && (daccess.missing == 'y' || ((daccess.mode & DA_TYPE_POS) == 0)))
7720 value = ".";
7721 else
7722 value = values[i];
7724 if (*daccess_pattern == NULL)
7726 *daccess_pattern = concat("(", value, ")", (char *)0);
7727 daccess.mode |= DA_TYPE_AUTO; /* Auto. */
7729 else
7730 *daccess_pattern = concat(*daccess_pattern,
7731 "|(",
7732 value,
7733 ")",
7734 (char *)0);
7737 if (daccess.def_number < 0)
7739 if (strcmp(param, "-N") == 0)
7740 daccess.def_number = 0; /* Words are unnumbered by default. */
7741 else
7742 daccess.def_number = 1; /* Words are numbered by default. */
7746 void
7747 field_da_number_action(char *ctx_name,
7748 char *opt_name,
7749 char *param,
7750 int nb_values,
7751 char **values,
7752 int nb_opt_data,
7753 void **opt_data,
7754 int nb_ctx_data,
7755 void **ctx_data)
7757 daccess.mode |= DA_TYPE_POS;
7760 void
7761 da_options_action(char *ctx_name,
7762 char *opt_name,
7763 char *param,
7764 int nb_values,
7765 char **values,
7766 int nb_opt_data,
7767 void **opt_data,
7768 int nb_ctx_data,
7769 void **ctx_data)
7771 long *daccess_index = opt_data[0];
7772 misc_t *misc = opt_data[1];
7774 int pos;
7775 wchar_t *w;
7776 int n;
7777 int i;
7779 /* Parse optional additional arguments. */
7780 /* The syntax of the arguments has already been pre validated by the */
7781 /* ctxopt_re_constraint filter. */
7782 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
7783 for (i = 0; i < nb_values; i++)
7785 char *value = values[i];
7787 switch (*value)
7789 case 'l': /* Left char .*/
7790 free(daccess.left);
7792 daccess.left = xstrdup(value + 2);
7793 utf8_interpret(daccess.left, misc->invalid_char_substitute);
7795 if (utf8_strlen(daccess.left) != 1)
7797 fprintf(stderr, "%s: Too many characters after l:\n", param);
7798 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7801 n = wcswidth((w = utf8_strtowcs(daccess.left)), 1);
7802 free(w);
7804 if (n > 1)
7806 fprintf(stderr,
7807 "%s: A multi columns character is not allowed "
7808 "after l:\n",
7809 param);
7810 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7812 break;
7814 case 'r': /* Right char. */
7815 free(daccess.right);
7817 daccess.right = xstrdup(value + 2);
7818 utf8_interpret(daccess.right, misc->invalid_char_substitute);
7820 if (utf8_strlen(daccess.right) != 1)
7822 fprintf(stderr, "%s: Too many characters after r:\n", param);
7823 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7826 n = wcswidth((w = utf8_strtowcs(daccess.right)), 1);
7827 free(w);
7829 if (n > 1)
7831 fprintf(stderr,
7832 "%s: A multi columns character is not allowed "
7833 "after r:\n",
7834 param);
7835 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7837 break;
7839 case 'a': /* Alignment. */
7840 if (strprefix("left", value + 2))
7841 daccess.alignment = 'l';
7842 else if (strprefix("right", value + 2))
7843 daccess.alignment = 'r';
7844 else
7846 fprintf(stderr,
7847 "%s: The value after a: must be "
7848 "l(eft) or r(ight)\n",
7849 param);
7850 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7852 break;
7854 case 'p': /* Padding. */
7855 if (strprefix("all", value + 2))
7856 daccess.padding = 'a';
7857 else if (strprefix("included", value + 2))
7858 daccess.padding = 'i';
7859 else
7861 fprintf(stderr, "%s: Bad value after p:\n", param);
7862 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7864 break;
7866 case 'w': /* Width. */
7867 if (sscanf(value + 2, "%d%n", &daccess.length, &pos) != 1)
7869 fprintf(stderr, "%s: Bad value after w:\n", param);
7870 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7872 if (value[pos + 2] != '\0')
7874 fprintf(stderr, "%s: Bad value after w:\n", param);
7875 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7877 if (daccess.length <= 0 || daccess.length > 5)
7879 fprintf(stderr, "%s: w sub-option must be between 1 and 5\n", param);
7880 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7882 break;
7884 case 'o': /* Start offset. */
7885 if (sscanf(value + 2, "%zu%n+", &daccess.offset, &pos) == 1)
7887 if (value[pos + 2] == '+')
7889 daccess.plus = 1;
7891 if (value[pos + 3] != '\0')
7893 fprintf(stderr, "%s: Bad value after o:\n", param);
7894 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7897 else if (value[pos + 2] != '\0')
7899 fprintf(stderr, "%s: Bad value after o:\n", param);
7900 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7903 else
7905 fprintf(stderr, "%s: Bad value after o:\n", param);
7906 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7909 break;
7911 case 'n': /* Number of digits to extract. */
7912 if (sscanf(value + 2, "%d%n", &daccess.size, &pos) != 1)
7914 fprintf(stderr, "%s: Bad value after n:\n", param);
7915 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7917 if (value[pos + 2] != '\0')
7919 fprintf(stderr, "%s: Bad value after n:\n", param);
7920 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7922 if (daccess.size <= 0 || daccess.size > 5)
7924 fprintf(stderr, "n sub-option must have a value between 1 and 5.\n");
7925 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7927 break;
7929 case 'i': /* Number of UTF-8 glyphs to ignore after the *
7930 | selector to extract. */
7931 if (sscanf(value + 2, "%zu%n", &daccess.ignore, &pos) != 1)
7933 fprintf(stderr, "%s: Bad value after i:\n", param);
7934 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7936 if (value[pos + 2] != '\0')
7938 fprintf(stderr, "%s: Bad value after i:\n", param);
7939 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7941 break;
7943 case 'f': /* Follow. */
7944 if (strprefix("yes", value + 2))
7945 daccess.follow = 'y';
7946 else if (strprefix("no", value + 2))
7947 daccess.follow = 'n';
7948 else
7950 fprintf(stderr, "%s: Bad value after f:\n", param);
7951 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7953 break;
7955 case 'm': /* Possibly number missing embedded numbers. */
7956 if (strprefix("yes", value + 2))
7957 daccess.missing = 'y';
7958 else if (strprefix("no", value + 2))
7959 daccess.missing = 'n';
7960 else
7962 fprintf(stderr, "%s: Bad value after m:\n", param);
7963 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7965 break;
7967 case 'd': /* Decorate. */
7968 free(daccess.num_sep);
7970 daccess.num_sep = xstrdup(value + 2);
7971 utf8_interpret(daccess.num_sep, misc->invalid_char_substitute);
7973 if (utf8_strlen(daccess.num_sep) != 1)
7975 fprintf(stderr, "%s: Too many characters after d:\n", param);
7976 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7979 n = wcswidth((w = utf8_strtowcs(daccess.num_sep)), 1);
7980 free(w);
7982 if (n > 1)
7984 fprintf(stderr,
7985 "%s: A multi columns separator is not allowed "
7986 "after d:\n",
7987 param);
7988 ctxopt_ctx_disp_usage(ctx_name, exit_after);
7990 break;
7992 case 's': /* Start index. */
7994 long index;
7996 if (sscanf(value + 2, "%ld%ln", daccess_index, &index) == 1)
7998 if (*daccess_index < 0 || *(value + 2 + index) != '\0')
7999 *daccess_index = 1;
8001 else
8003 fprintf(stderr, "%s: Invalid first index after s:\n", param);
8004 ctxopt_ctx_disp_usage(ctx_name, exit_after);
8007 break;
8009 case 'h': /* Head. */
8010 if (strprefix("trim", value + 2))
8011 daccess.head = 't';
8012 else if (strprefix("cut", value + 2))
8013 daccess.head = 'c';
8014 else if (strprefix("keep", value + 2))
8015 daccess.head = 'k';
8016 else
8018 fprintf(stderr, "%s: Bad value after :h\n", param);
8019 ctxopt_ctx_disp_usage(ctx_name, exit_after);
8021 break;
8023 default:
8025 fprintf(stderr, "%s: Bad sub-command: %s\n", param, value);
8026 ctxopt_ctx_disp_usage(ctx_name, exit_after);
8030 if (daccess.length <= 0 || daccess.length > 5)
8031 daccess.length = -2; /* special value -> auto. */
8035 void
8036 ignore_quotes_action(char *ctx_name,
8037 char *opt_name,
8038 char *param,
8039 int nb_values,
8040 char **values,
8041 int nb_opt_data,
8042 void **opt_data,
8043 int nb_ctx_data,
8044 void **ctx_data)
8046 misc_t *misc = opt_data[0];
8048 misc->ignore_quotes = 1;
8051 void
8052 forgotten_action(char *ctx_name,
8053 char *opt_name,
8054 char *param,
8055 int nb_values,
8056 char **values,
8057 int nb_opt_data,
8058 void **opt_data,
8059 int nb_ctx_data,
8060 void **ctx_data)
8062 ticker_t *timers = opt_data[0];
8064 long val;
8065 char *endptr;
8066 char *p;
8067 size_t l = strlen(values[0]);
8069 /* Add a 0 at the end of the parameter to multiply it by 10 */
8070 /* as the timer values must be in tenths of seconds. */
8071 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8072 p = xmalloc(l + 2);
8073 p = strcpy(p, values[0]);
8074 p[l] = '0';
8076 errno = 0;
8077 val = strtol(p, &endptr, 0);
8078 if (errno == ERANGE || (val == 0 && errno != 0) || endptr == p
8079 || *endptr != '\0')
8081 fprintf(stderr, "%s: Invalid timeout delay.\n", values[0]);
8082 ctxopt_ctx_disp_usage(ctx_name, exit_after);
8084 else if (val == 0)
8085 timers->forgotten = -1; /* Explicitly disable the timers. */
8086 else
8087 timers->forgotten = (int)val;
8089 free(p);
8092 void
8093 button_action(char *ctx_name,
8094 char *opt_name,
8095 char *param,
8096 int nb_values,
8097 char **values,
8098 int nb_opt_data,
8099 void **opt_data,
8100 int nb_ctx_data,
8101 void **ctx_data)
8103 mouse_t *mouse = opt_data[0];
8105 char *endptr;
8106 char *p;
8107 long val;
8109 if (nb_values != 2)
8111 fprintf(stderr, "Remapping of buttons 1 and 3 expected.\n");
8112 ctxopt_ctx_disp_usage(ctx_name, exit_after);
8115 int const index[2] = { 0, 2 };
8116 int ind;
8118 mouse->button[0] = 0;
8119 mouse->button[1] = 0;
8120 mouse->button[2] = 0;
8122 for (int i = 0; i < nb_values; i++)
8124 ind = index[i];
8125 errno = 0;
8126 p = values[i];
8127 val = strtol(p, &endptr, 0);
8128 if (errno == ERANGE || endptr == p || *endptr != '\0' || val < 1 || val > 3)
8130 fprintf(stderr, "%s: Invalid button number.\n", values[0]);
8131 ctxopt_ctx_disp_usage(ctx_name, exit_after);
8133 mouse->button[(int)val - 1] = ind + 1;
8137 void
8138 double_click_action(char *ctx_name,
8139 char *opt_name,
8140 char *param,
8141 int nb_values,
8142 char **values,
8143 int nb_opt_data,
8144 void **opt_data,
8145 int nb_ctx_data,
8146 void **ctx_data)
8148 mouse_t *mouse = opt_data[0];
8149 int *ddc = opt_data[1];
8151 char *endptr;
8152 char *p = values[0];
8153 long val;
8155 errno = 0;
8156 val = strtol(p, &endptr, 0);
8157 if (errno == ERANGE || endptr == p || *endptr != '\0')
8158 return; /* default value (150 ~ 1/6.66th second) is set in main(). */
8159 if (val == 0)
8160 *ddc = 1; /* disable double_click; */
8161 else if (val >= 100 && val <= 500)
8162 mouse->double_click_delay = val;
8165 /* =================================================================== */
8166 /* Cancels a search. Called when ESC is hit or a new search session is */
8167 /* initiated and the incremental_search option is not used. */
8168 /* =================================================================== */
8169 void
8170 reset_search_buffer(win_t *win,
8171 search_data_t *search_data,
8172 ticker_t *timers,
8173 toggle_t *toggles,
8174 term_t *term,
8175 daccess_t *daccess,
8176 langinfo_t *langinfo,
8177 long last_line,
8178 char *tmp_word,
8179 long word_real_max_size)
8181 /* ESC key has been pressed. */
8182 /* """"""""""""""""""""""""" */
8183 search_mode_t saved_search_mode = search_mode;
8185 /* Cancel the search timer. */
8186 /* """""""""""""""""""""""" */
8187 search_timer = 0;
8189 search_data->err = 0;
8190 search_data->only_starting = 0;
8191 search_data->only_ending = 0;
8193 if (help_mode)
8194 disp_lines(win,
8195 toggles,
8196 current,
8197 count,
8198 search_mode,
8199 search_data,
8200 term,
8201 last_line,
8202 tmp_word,
8203 langinfo);
8205 /* Reset the direct access selector stack. */
8206 /* """"""""""""""""""""""""""""""""""""""" */
8207 memset(daccess_stack, '\0', 6);
8208 daccess_stack_head = 0;
8209 daccess_timer = timers->direct_access;
8211 /* Clean the potential matching words non empty list. */
8212 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
8213 search_mode = NONE;
8215 if (matches_count > 0 || saved_search_mode != search_mode)
8217 clean_matches(search_data, word_real_max_size);
8219 disp_lines(win,
8220 toggles,
8221 current,
8222 count,
8223 search_mode,
8224 search_data,
8225 term,
8226 last_line,
8227 tmp_word,
8228 langinfo);
8232 /* =============================================================== */
8233 /* Detect if the current terminal belongs to the foreground group. */
8234 /* returns 1 if yes else returns 0. */
8235 /* =============================================================== */
8237 is_in_foreground_process_group(void)
8239 int fd, fg;
8241 fd = open("/dev/tty", O_RDONLY);
8242 if (fd < 0)
8243 return 0;
8245 fg = (tcgetpgrp(fd) == getpgid(0));
8247 close(fd);
8249 return fg;
8252 /* ===================================================================== */
8253 /* Get the new index of a word based on the mouse click position. */
8254 /* Returns a new index if a word could be selected or the original index */
8255 /* The error flag is set if the word was not selectable or if the user */
8256 /* did not click on a word. */
8257 /* ===================================================================== */
8258 long
8259 get_clicked_index(win_t *win,
8260 term_t *term,
8261 int line_click,
8262 int column_click,
8263 int *error)
8265 long new_current; /* Future new current word on success. */
8266 long clicked_line; /* Number of the clicked line in smenu internal *
8267 | representation on the screen. */
8268 int delta; /* Compensation due to the alignment of the first *
8269 | visible word of the selected line in the window. */
8271 /* Make sure the error indicator is initialized. */
8272 /* """"""""""""""""""""""""""""""""""""""""""""" */
8273 *error = 0;
8275 /* Get the internal line number of the click. */
8276 /* """""""""""""""""""""""""""""""""""""""""" */
8277 clicked_line = line_nb_of_word_a[win->start] + line_click - term->curs_line;
8279 /* Ignore clicks after the last word on a line. */
8280 /* """""""""""""""""""""""""""""""""""""""""""" */
8281 if (column_click >= shift_right_sym_pos_a[clicked_line])
8282 return current;
8284 new_current = first_word_in_line_a[clicked_line];
8286 if (new_current == count - 1)
8287 return new_current; /* The users clicked on the last word; */
8288 else
8290 int offset; /* in column or line mode lines can be larger than the *
8291 | terminal size, in this case a space is added at the *
8292 | start of the lines to possibly put the shift arrow *
8293 | indicator, this space must be taken into account. */
8294 int found; /* Flag to indicate if a word could have been clicked. */
8296 /* Take into account the presence of shift arrows if any. */
8297 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
8298 if (win->has_truncated_lines)
8299 offset = 1;
8300 else
8301 offset = 0;
8303 /* Compensates for the offset of the window in the case of a centred */
8304 /* window and the additional space added by the display of truncated */
8305 /* lines. */
8306 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8307 column_click -= win->offset;
8309 /* Forbid click on column 1 if the windows has truncated lines. */
8310 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
8311 if (column_click == 1 && offset == 1)
8312 return current;
8314 /* Normalize column_click. */
8315 /* """"""""""""""""""""""" */
8316 column_click -= offset;
8318 /* Determine the offset of the first character of the */
8319 /* first visible word in the clicked line. */
8320 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
8321 while (word_a[new_current].start < win->first_column)
8323 new_current++;
8325 if (new_current == count || word_a[new_current].start == 0)
8326 return current;
8329 delta = word_a[new_current].start - win->first_column;
8331 /* Find the right clicked word if any. */
8332 /* """"""""""""""""""""""""""""""""""" */
8333 found = 0;
8335 while (1)
8337 if ((column_click - 1
8338 >= word_a[new_current].start - win->first_column - delta)
8339 && (column_click - 1
8340 <= word_a[new_current].end - win->first_column - delta))
8342 found = 1;
8343 break;
8346 new_current++;
8348 if (new_current == count || word_a[new_current].start == 0)
8349 break;
8352 if (!found)
8354 *error = 1;
8355 new_current = current;
8359 if (!word_a[new_current].is_selectable)
8361 *error = 1;
8362 return current;
8365 return new_current;
8368 /* ==================================================================== */
8369 /* Get the number of the clicked shift arrow if any. */
8370 /* arrow will contain 0 if the left arrow as clicked and 1 if the right */
8371 /* arrow was clicked. */
8372 /* Returns 0 is no arrow wa clicked else 1. */
8373 /* ==================================================================== */
8375 shift_arrow_clicked(win_t *win,
8376 term_t *term,
8377 int line_click,
8378 int column_click,
8379 long *clicked_line,
8380 int *arrow)
8382 /* Get the internal line number of the click. */
8383 /* """""""""""""""""""""""""""""""""""""""""" */
8384 *clicked_line = line_nb_of_word_a[win->start] + line_click - term->curs_line;
8386 if (column_click > shift_right_sym_pos_a[*clicked_line])
8387 return 0;
8389 /* Compensates for the offset of the window */
8390 /* in the case of a centred window. */
8391 /* """""""""""""""""""""""""""""""""""""""" */
8392 if (column_click == 1)
8393 *arrow = 0; /* Left arrow. */
8394 else if (shift_right_sym_pos_a[*clicked_line] < term->ncolumns
8395 && column_click == shift_right_sym_pos_a[*clicked_line])
8396 *arrow = 1; /* Right arrow. */
8397 else
8398 return 0;
8400 return 1;
8403 /* ============================================================== */
8404 /* Takes a string with leading and/or trailing spaces and try to */
8405 /* left-align, right-align or center them by re-arranging blanks. */
8406 /* word IN/OUT string to work with. */
8407 /* alignment IN kind of alignments. */
8408 /* ============================================================== */
8409 void
8410 align_word(word_t *word, alignment_t alignment, size_t prefix, char sp)
8412 switch (alignment)
8414 char *str;
8415 size_t n;
8416 size_t m;
8417 size_t wl;
8418 size_t l;
8420 case AL_LEFT:
8421 /* Left align the word. */
8422 /* """""""""""""""""""" */
8423 str = xstrdup(word->str + prefix);
8424 n = 0;
8426 while (str[n] == sp)
8427 n++;
8429 swap_string_parts(&str, n);
8431 strcpy(word->str + prefix, str);
8432 free(str);
8434 break;
8436 case AL_RIGHT:
8437 /* Right align the word. */
8438 /* """"""""""""""""""""" */
8439 str = xstrdup(word->str + prefix);
8440 n = strlen(str) - 1;
8442 while (n && str[n] == sp)
8443 n--;
8445 swap_string_parts(&str, n + 1);
8447 strcpy(word->str + prefix, str);
8448 free(str);
8450 break;
8452 case AL_CENTERED:
8453 /* Center the word. */
8454 /* """""""""""""""" */
8455 str = xstrdup(word->str + prefix);
8456 n = 0;
8457 l = strlen(str);
8458 m = l - 1;
8460 while (str[n] == sp)
8461 n++;
8463 while (m && str[m] == sp)
8464 m--;
8466 if (n > m)
8468 free(str);
8469 break;
8472 wl = m - n + 1;
8473 memset(word->str + prefix, sp, l);
8475 strncpy(word->str + prefix + (l - wl) / 2, str + n, wl);
8476 free(str);
8478 break;
8480 default:
8481 break;
8485 /* ================= */
8486 /* Main entry point. */
8487 /* ================= */
8489 main(int argc, char *argv[])
8491 /* Mapping of supported charsets and the number of bits used in them. */
8492 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8493 charsetinfo_t all_supported_charsets[] = {
8494 { "UTF-8", 8 },
8496 { "ANSI_X3.4-1968", 7 },
8497 { "ANSI_X3.4-1986", 7 },
8498 { "646", 7 },
8499 { "ASCII", 7 },
8500 { "CP367", 7 },
8501 { "IBM367", 7 },
8502 { "ISO_646.BASIC", 7 },
8503 { "ISO_646.IRV:1991", 7 },
8504 { "ISO_646.IRV", 7 },
8505 { "ISO646-US", 7 },
8506 { "ISO-IR-6", 7 },
8507 { "US", 7 },
8508 { "US-ASCII", 7 },
8510 { "hp-roman8", 8 },
8511 { "roman8", 8 },
8512 { "r8", 8 },
8514 { "ISO8859-1", 8 },
8515 { "ISO-8859-1", 8 },
8516 { "ISO-IR-100", 8 },
8517 { "ISO_8859-1:1987", 8 },
8518 { "ISO_8859-1", 8 },
8519 { "LATIN1", 8 },
8520 { "L1", 8 },
8521 { "IBM819", 8 },
8522 { "CP819", 8 },
8524 { "ISO8859-2", 8 },
8525 { "ISO-8859-2", 8 },
8526 { "ISO-IR-101", 8 },
8527 { "ISO_8859-2:1987", 8 },
8528 { "ISO_8859-2", 8 },
8529 { "LATIN2", 8 },
8530 { "L2", 8 },
8531 { "CP28592", 8 },
8533 { "ISO8859-3", 8 },
8534 { "ISO-8859-3", 8 },
8535 { "ISO-IR-109", 8 },
8536 { "ISO_8859-3:1988", 8 },
8537 { "ISO_8859-3", 8 },
8538 { "LATIN3", 8 },
8539 { "L3", 8 },
8540 { "CP28593", 8 },
8542 { "ISO8859-4", 8 },
8543 { "ISO-8859-4", 8 },
8544 { "ISO-IR-110", 8 },
8545 { "ISO_8859-4:1988", 8 },
8546 { "LATIN4", 8 },
8547 { "L4", 8 },
8548 { "CP28594", 8 },
8550 { "ISO8859-5", 8 },
8551 { "ISO-8859-5", 8 },
8552 { "ISO-IR-144", 8 },
8553 { "ISO_8859-5:1988", 8 },
8554 { "CYRILLIC", 8 },
8555 { "CP28595", 8 },
8557 { "KOI8-R", 8 },
8558 { "KOI8-RU", 8 },
8559 { "KOI8-U", 8 },
8561 { "ISO8859-6", 8 },
8562 { "ISO-8859-6", 8 },
8563 { "ISO-IR-127", 8 },
8564 { "ISO_8859-6:1987", 8 },
8565 { "ECMA-114", 8 },
8566 { "ASMO-708", 8 },
8567 { "ARABIC", 8 },
8568 { "CP28596", 8 },
8570 { "ISO8859-7", 8 },
8571 { "ISO-8859-7", 8 },
8572 { "ISO-IR-126", 8 },
8573 { "ISO_8859-7:2003", 8 },
8574 { "ISO_8859-7:1987", 8 },
8575 { "ELOT_928", 8 },
8576 { "ECMA-118", 8 },
8577 { "GREEK", 8 },
8578 { "GREEK8", 8 },
8579 { "CP28597", 8 },
8581 { "ISO8859-8", 8 },
8582 { "ISO-8859-8", 8 },
8583 { "ISO-IR-138", 8 },
8584 { "ISO_8859-8:1988", 8 },
8585 { "HEBREW", 8 },
8586 { "CP28598", 8 },
8588 { "ISO8859-9", 8 },
8589 { "ISO-8859-9", 8 },
8590 { "ISO-IR-148", 8 },
8591 { "ISO_8859-9:1989", 8 },
8592 { "LATIN5", 8 },
8593 { "L5", 8 },
8594 { "CP28599", 8 },
8596 { "ISO8859-10", 8 },
8597 { "ISO-8859-10", 8 },
8598 { "ISO-IR-157", 8 },
8599 { "ISO_8859-10:1992", 8 },
8600 { "LATIN6", 8 },
8601 { "L6", 8 },
8602 { "CP28600", 8 },
8604 { "ISO8859-11", 8 },
8605 { "ISO-8859-11", 8 },
8606 { "ISO-8859-11:2001", 8 },
8607 { "ISO-IR-166", 8 },
8608 { "CP474", 8 },
8610 { "TIS-620", 8 },
8611 { "TIS620", 8 },
8612 { "TIS620-0", 8 },
8613 { "TIS620.2529-1", 8 },
8614 { "TIS620.2533-0", 8 },
8616 /* ISO-8859-12 was abandoned in 1997. */
8617 /* """""""""""""""""""""""""""""""""" */
8619 { "ISO8859-13", 8 },
8620 { "ISO-8859-13", 8 },
8621 { "ISO-IR-179", 8 },
8622 { "LATIN7", 8 },
8623 { "L7", 8 },
8624 { "CP28603", 8 },
8626 { "ISO8859-14", 8 },
8627 { "ISO-8859-14", 8 },
8628 { "LATIN8", 8 },
8629 { "L8", 8 },
8631 { "ISO8859-15", 8 },
8632 { "ISO-8859-15", 8 },
8633 { "LATIN-9", 8 },
8634 { "CP28605", 8 },
8636 { "ISO8859-16", 8 },
8637 { "ISO-8859-16", 8 },
8638 { "ISO-IR-226", 8 },
8639 { "ISO_8859-16:2001", 8 },
8640 { "LATIN10", 8 },
8641 { "L10", 8 },
8643 { "CP1250", 8 },
8644 { "CP1251", 8 },
8646 { "CP1252", 8 },
8647 { "MS-ANSI", 8 },
8648 { NULL, 0 }
8651 /* Terminal setting variables. */
8652 /* """"""""""""""""""""""""""" */
8653 struct termios new_in_attrs;
8654 struct termios old_in_attrs;
8656 /* Interval timers used. */
8657 /* """"""""""""""""""""" */
8658 struct itimerval periodic_itv; /* refresh rate for the timeout counter. */
8660 int nb_rem_args = 0; /* Remaining non analyzed command line arguments. */
8661 char **rem_args = NULL; /* Remaining non analyzed command line *
8662 | arguments array. */
8664 char *message = NULL; /* message to be displayed above the selection *
8665 | window. */
8666 ll_t *message_lines_list = NULL; /* list of the lines in the message to *
8667 | be displayed. */
8668 long message_max_width = 0; /* total width of the message (longest line). */
8669 long message_max_len = 0; /* max number of bytes taken by a message *
8670 | line. */
8672 char *int_string = NULL; /* String to be output when typing ^C. */
8673 int int_as_in_shell = 1; /* CTRL-C mimics the shell behaviour. */
8675 FILE *input_file; /* The name of the file passed as argument if any. */
8677 long index; /* generic counter. */
8679 long daccess_index = 1; /* First index of the numbered words. */
8681 char *daccess_np = NULL; /* direct access numbered pattern. */
8682 regex_t daccess_np_re; /* variable to store the compiled direct access *
8683 | pattern (-N) RE. */
8685 char *daccess_up = NULL; /* direct access not numbered pattern. */
8686 regex_t daccess_up_re; /* variable to store the compiled direct access *
8687 | pattern (-U) RE. */
8689 char *include_pattern = NULL; /* ASCII version of the include RE */
8690 char *exclude_pattern = NULL; /* ASCII version of the exclude RE */
8692 int pattern_def_include = -1; /* Set to -1 until an -i or -e option is *
8693 | specified, This variable remembers if *
8694 | the words not matched will be included *
8695 | (value 1) or excluded (value 0) by *
8696 | default. */
8698 regex_t include_re; /* variable to store the compiled include (-i) REs. */
8699 regex_t exclude_re; /* variable to store the compiled exclude (-e) REs. */
8701 ll_t *early_sed_list = NULL; /* List of sed like string representation *
8702 | of regex given after (-ES). */
8703 ll_t *sed_list = NULL; /* List of sed like string representation *
8704 | of regex given after (-S). */
8705 ll_t *include_sed_list = NULL; /* idem for -I. */
8706 ll_t *exclude_sed_list = NULL; /* idem for -E. */
8708 ll_t *inc_col_interval_list = NULL; /* Lists of included or */
8709 ll_t *exc_col_interval_list = NULL; /* excluded numerical intervals */
8710 ll_t *inc_row_interval_list = NULL; /* for lines and columns. */
8711 ll_t *exc_row_interval_list = NULL;
8713 ll_t *inc_col_regex_list = NULL; /* Same for lines and columns specified. */
8714 ll_t *exc_col_regex_list = NULL; /* by regular expressions. */
8715 ll_t *inc_row_regex_list = NULL;
8716 ll_t *exc_row_regex_list = NULL;
8718 ll_t *al_col_interval_list = NULL; /* Lists of left aligned rows/columns, */
8719 ll_t *ar_col_interval_list = NULL; /* right aligned rows/columns, */
8720 ll_t *ac_col_interval_list = NULL; /* centered rows/columns */
8721 ll_t *al_row_interval_list = NULL;
8722 ll_t *ar_row_interval_list = NULL;
8723 ll_t *ac_row_interval_list = NULL;
8725 ll_t *at_col_interval_list = NULL; /* list of attributes for rows and */
8726 ll_t *at_row_interval_list = NULL; /* columns. */
8728 ll_t *al_col_regex_list = NULL; /* Same for regular expression based */
8729 ll_t *ar_col_regex_list = NULL; /* alignments. */
8730 ll_t *ac_col_regex_list = NULL;
8731 ll_t *al_row_regex_list = NULL;
8732 ll_t *ar_row_regex_list = NULL;
8733 ll_t *ac_row_regex_list = NULL;
8735 ll_t *at_col_regex_list = NULL; /* list of attributes for rows and */
8736 ll_t *at_row_regex_list = NULL; /* columns. */
8738 ll_t *al_word_regex_list = NULL;
8739 ll_t *ar_word_regex_list = NULL;
8740 ll_t *ac_word_regex_list = NULL;
8742 alignment_t default_alignment = AL_NONE;
8744 filters_t rows_filter_type = UNKNOWN_FILTER;
8746 char *first_word_pattern = NULL; /* used by -A/-Z. */
8747 char *last_word_pattern = NULL;
8748 regex_t first_word_re; /* to hold the compiled first words RE. */
8749 regex_t last_word_re; /* to hold the compiled last words RE. */
8751 char *special_pattern[9] = { NULL, NULL, NULL, NULL, NULL,
8752 NULL, NULL, NULL, NULL }; /* -1 .. -9 */
8753 regex_t special_re[9]; /* array to hold the compiled special patterns RE. */
8755 int include_visual_only = 0; /* If set to 1, the original word which is *
8756 | read from stdin will be output even if its */
8757 int exclude_visual_only = 0; /* visual representation was modified via *
8758 | -S/-I/-E. */
8760 ll_t *cols_selector_list = NULL; /* to store ASCII representations of */
8761 char *cols_selector = NULL; /* ranges add RE to select some columns *
8762 | with -C. */
8764 ll_t *rows_selector_list = NULL; /* to store ASCII representations of */
8765 char *rows_selector = NULL; /* ranges and RE to select some rows *
8766 | with -R. */
8768 ll_t *aligns_selector_list = NULL; /* to store ASCII representations of */
8769 char *aligns_selector = NULL; /* RE to select some rows with -al. */
8771 long wi; /* word index. */
8773 term_t term; /* Terminal structure. */
8775 tst_node_t *tst_word = NULL; /* TST used by the search function. */
8776 tst_node_t *tst_daccess = NULL; /* TST used by the direct access system. */
8778 long page; /* Step for the vertical cursor moves. */
8779 char *word; /* Temporary variable to work on words. */
8780 char *tmp_word; /* Temporary variable able to contain the beginning of *
8781 | the word to be displayed. */
8783 long last_line = 0; /* last logical line number (from 0). */
8784 win_t win;
8785 limit_t limits; /* set of various limitations. */
8786 ticker_t timers; /* timers contents. */
8787 misc_t misc; /* misc contents. */
8788 mouse_t mouse; /* mouse default values. */
8789 toggle_t toggles; /* set of binary indicators. */
8791 int old_fd0; /* backups of the old stdin file descriptor. */
8792 int old_fd1; /* backups of the old stdout file descriptor. */
8793 FILE *old_stdin;
8794 FILE *old_stdout; /* The selected word will go there. */
8796 long nl; /* Number of lines displayed in the window. */
8797 long line_offset; /* Used to correctly put the cursor at the start of the *
8798 | selection window, even after a terminal vertical *
8799 | scroll. */
8801 long first_selectable; /* Index of the first selectable word in the input *
8802 | stream. */
8803 long last_selectable; /* Index of the last selectable word in the input *
8804 | stream. */
8806 long min_size; /* Minimum screen width of a column in tabular mode. */
8808 long tab_max_size; /* Maximum screen width of a column in tabular *
8809 | mode. */
8810 long tab_real_max_size; /* Maximum size in bytes of a column in tabular *
8811 | mode. */
8813 long *col_real_max_size = NULL; /* Array of maximum sizes (bytes) of each */
8814 /* column in column mode. */
8816 long *col_max_size = NULL; /* Array of maximum sizes (in display cells) *
8817 | of each column in column mode. */
8819 attrib_t **col_attrs = NULL; /* attributes for each column in column mode. */
8821 long word_real_max_size = 0; /* size of the longer word after expansion. */
8822 long cols_real_max_size = 0; /* Max real width of all columns used when *
8823 | -w and -c are both set. */
8825 long cols_max_size = 0; /* Same as above for the columns widths */
8827 long col_index = 0; /* Index of the current column when reading words, *
8828 | used in column mode. */
8830 long cols_number = 0; /* Number of columns in column mode. */
8832 char *pre_selection_index = NULL; /* pattern used to set the initial *
8833 | cursor position. */
8835 unsigned char buffer[64]; /* Input buffer which will contain the scancode. */
8837 search_data_t search_data;
8838 search_data.buf = NULL; /* Search buffer */
8839 search_data.len = 0; /* Current position in the search buffer */
8840 search_data.utf8_len = 0; /* Current position in the search buffer in *
8841 | UTF-8 units. */
8842 search_data.err = 0; /* reset the error indicator. */
8843 search_data.fuzzy_err_pos = -1; /* no last error position in search *
8844 buffer. */
8846 long matching_word_cur_index = -1; /* cache for the next/previous moves *
8847 | in the matching words array. */
8849 struct sigaction sa; /* Signal structure. */
8851 char *iws = NULL, *ils = NULL, *zg = NULL; /* words/lines delimiters and *
8852 | zapped glyphs strings. */
8854 ll_t *word_delims_list = NULL; /* and associated linked lists. */
8855 ll_t *line_delims_list = NULL;
8856 ll_t *zapped_glyphs_list = NULL;
8858 char utf8_buffer[5]; /* buffer to store the bytes of a UTF-8 glyph *
8859 | (4 chars max). */
8860 unsigned char is_last;
8861 char *charset;
8863 char *home_ini_file; /* init file full path. */
8864 char *local_ini_file; /* init file full path. */
8866 charsetinfo_t *charset_ptr;
8867 langinfo_t langinfo;
8868 int is_supported_charset;
8870 long line_count = 0; /* Only used when -R is selected. */
8872 attrib_t init_attr;
8874 ll_node_t *inc_interval_node = NULL; /* one node of this list. */
8875 ll_node_t *exc_interval_node = NULL; /* one node of this list. */
8877 interval_t *inc_interval; /* the data in each node. */
8878 interval_t *exc_interval; /* the data in each node. */
8879 int row_def_selectable; /* default selectable value. */
8881 int line_selected_by_regex = 0;
8882 int line_excluded = 0;
8884 char *timeout_message;
8886 char *common_options;
8887 char *main_options, *main_spec_options;
8888 char *col_options, *col_spec_options;
8889 char *line_options, *line_spec_options;
8890 char *tab_options, *tab_spec_options;
8891 char *tag_options, *tag_spec_options;
8893 /* Used to check if we can get the cursor position in the terminal. */
8894 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8895 int row; /* absolute line position in terminal (1...) */
8896 int col; /* absolute column position in terminal (1...) */
8898 int mouse_proto = -1;
8900 /* Initialize some internal data structures. */
8901 /* """"""""""""""""""""""""""""""""""""""""" */
8902 init_main_ds(&init_attr,
8903 &win,
8904 &limits,
8905 &timers,
8906 &toggles,
8907 &misc,
8908 &mouse,
8909 &timeout,
8910 &daccess);
8912 /* direct access variable initialization. */
8913 /* """""""""""""""""""""""""""""""""""""" */
8914 daccess_stack = xcalloc(6, 1);
8915 daccess_stack_head = 0;
8917 /* fuzzy variables initialization. */
8918 /* """"""""""""""""""""""""""""""" */
8919 tst_search_list = ll_new();
8920 ll_append(tst_search_list, sub_tst_new());
8922 matching_words_a_size = 64;
8923 matching_words_a = xmalloc(matching_words_a_size * sizeof(long));
8924 matches_count = 0;
8926 best_matching_words_a_size = 16;
8927 best_matching_words_a = xmalloc(best_matching_words_a_size * sizeof(long));
8928 best_matches_count = 0;
8930 /* Initialize the tag hit number which will permit to sort the */
8931 /* pinned words when displayed. */
8932 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8933 long tag_nb = 0;
8935 /* Columns selection variables. */
8936 /* """""""""""""""""""""""""""" */
8937 char *cols_filter = NULL;
8939 /* Initialize the count of tagged words. */
8940 /* """"""""""""""""""""""""""""""""""""" */
8941 long tagged_words = 0;
8943 /* Double-click related variables. */
8944 /* """"""""""""""""""""""""""""""" */
8945 int disable_double_click = 0;
8946 int click_nr = 0;
8947 struct timespec last_click_ts;
8949 /* Get the current locale. */
8950 /* """"""""""""""""""""""" */
8951 setlocale(LC_ALL, "");
8952 charset = nl_langinfo(CODESET);
8954 /* Check if the local charset is supported. */
8955 /* """""""""""""""""""""""""""""""""""""""" */
8956 is_supported_charset = 0;
8957 charset_ptr = all_supported_charsets;
8959 while (charset_ptr->name != NULL)
8961 if (my_strcasecmp(charset, charset_ptr->name) == 0)
8963 is_supported_charset = 1;
8964 langinfo.bits = charset_ptr->bits;
8965 break;
8967 charset_ptr++;
8970 if (!is_supported_charset)
8972 fprintf(stderr, "%s is not a supported charset.", charset);
8974 exit(EXIT_FAILURE);
8977 /* Remember the fact that the charset is UTF-8. */
8978 /* """""""""""""""""""""""""""""""""""""""""""" */
8979 if (strcmp(charset, "UTF-8") == 0)
8980 langinfo.utf8 = 1;
8981 else
8982 langinfo.utf8 = 0;
8984 /* my_isprint is a function pointers that points to */
8985 /* the 7 or 8-bit version of isprint according to the */
8986 /* current locale. */
8987 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
8988 if (langinfo.utf8 || langinfo.bits == 8)
8989 my_isprint = isprint8;
8990 else
8991 my_isprint = isprint7;
8993 /* my_isempty is a function pointers that points to */
8994 /* the utf8 or non_utf8 version of isprint according to the */
8995 /* current locale. */
8996 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
8997 if (langinfo.utf8)
8998 my_isempty = isempty_utf8;
8999 else
9000 my_isempty = isempty_non_utf8;
9002 /* Set terminal in noncanonical, noecho mode and */
9003 /* if TERM is unset or unknown, vt100 is assumed. */
9004 /* """""""""""""""""""""""""""""""""""""""""""""" */
9005 if (getenv("TERM") == NULL)
9006 setupterm("vt100", 1, (int *)0);
9007 else
9008 setupterm((char *)0, 1, (int *)0);
9010 /* Get the number of colors if the use of colors is available */
9011 /* and authorized. */
9012 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9013 if (getenv("NO_COLOR") != NULL)
9014 term.colors = 0;
9015 else
9017 term.colors = tigetnum("colors");
9018 if (term.colors < 0)
9019 term.colors = 0;
9022 /* Ignore SIGTTIN. */
9023 /* """"""""""""""" */
9024 sigset_t sigs, oldsigs;
9026 sigemptyset(&sigs);
9027 sigaddset(&sigs, SIGTTIN);
9028 sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
9030 /* Temporarily set /dev/tty as stdin/stdout to get its size */
9031 /* even in a pipe. */
9032 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9033 old_fd0 = dup(0);
9034 if (old_fd0 == -1)
9036 fprintf(stderr, "Cannot save the standard input file descriptor.\n");
9037 exit(EXIT_FAILURE);
9040 old_stdin = freopen("/dev/tty", "r", stdin);
9042 old_fd1 = dup(1);
9043 if (old_fd1 == -1)
9045 fprintf(stderr, "Cannot save the standard output file descriptor.\n");
9046 exit(EXIT_FAILURE);
9049 old_stdout = freopen("/dev/tty", "w", stdout);
9051 if (old_stdin == NULL || old_stdout == NULL)
9053 fprintf(stderr, "A terminal is required to use this program.\n");
9054 exit(EXIT_FAILURE);
9057 /* Get the number of lines/columns of the terminal. */
9058 /* """""""""""""""""""""""""""""""""""""""""""""""" */
9059 get_terminal_size(&term.nlines, &term.ncolumns, &term);
9061 /* Restore the old stdin and stdout. */
9062 /* """"""""""""""""""""""""""""""""" */
9063 if (dup2(old_fd0, 0) == -1)
9065 fprintf(stderr, "Cannot restore the standard input file descriptor.\n");
9066 exit(EXIT_FAILURE);
9069 if (dup2(old_fd1, 1) == -1)
9071 fprintf(stderr, "Cannot restore the standard output file descriptor.\n");
9072 exit(EXIT_FAILURE);
9075 close(old_fd0);
9076 close(old_fd1);
9078 /* Build the full path of the .ini file. */
9079 /* """"""""""""""""""""""""""""""""""""" */
9080 home_ini_file = make_ini_path(argv[0], "HOME");
9081 local_ini_file = make_ini_path(argv[0], "PWD");
9083 /* Set the attributes from the configuration file if possible. */
9084 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9085 if (ini_load(home_ini_file,
9086 &win,
9087 &term,
9088 &limits,
9089 &timers,
9090 &misc,
9091 &mouse,
9092 ini_cb))
9093 exit(EXIT_FAILURE);
9095 if (ini_load(local_ini_file,
9096 &win,
9097 &term,
9098 &limits,
9099 &timers,
9100 &misc,
9101 &mouse,
9102 ini_cb))
9103 exit(EXIT_FAILURE);
9105 free(home_ini_file);
9106 free(local_ini_file);
9108 /* Command line option settings using ctxopt. */
9109 /* """""""""""""""""""""""""""""""""""""""""" */
9110 ctxopt_init(argv[0],
9111 "stop_if_non_option=No "
9112 "allow_abbreviations=No "
9113 "display_usage_on_error=Yes ");
9115 common_options = "[*help] "
9116 "[*usage] "
9117 "[include_re... #regex] "
9118 "[exclude_re... #regex] "
9119 "[title #message] "
9120 "[int [#string]] "
9121 "[attributes #prefix:attr...] "
9122 "[special_level_1 #...<3] "
9123 "[special_level_2 #...<3] "
9124 "[special_level_3 #...<3] "
9125 "[special_level_4 #...<3] "
9126 "[special_level_5 #...<3] "
9127 "[special_level_6 #...<3] "
9128 "[special_level_7 #...<3] "
9129 "[special_level_8 #...<3] "
9130 "[special_level_9 #...<3] "
9131 "[zapped_glyphs #bytes] "
9132 "[lines [#height]] "
9133 "[blank_nonprintable] "
9134 "[*invalid_character #invalid_char_subst] "
9135 "[center_mode] "
9136 "[clean] "
9137 "[keep_spaces] "
9138 "[word_separators #bytes] "
9139 "[no_scroll_bar] "
9140 "[no_hor_scroll_bar] "
9141 "[early_subst_all... #/regex/repl/opts] "
9142 "[post_subst_all... #/regex/repl/opts] "
9143 "[post_subst_included... #/regex/repl/opts] "
9144 "[post_subst_excluded... #/regex/repl/opts] "
9145 "[search_method #prefix|substring|fuzzy] "
9146 "[start_pattern #pattern] "
9147 "[timeout #...] "
9148 "[hidden_timeout #...] "
9149 "[validate_in_search_mode] "
9150 "[visual_bell] "
9151 "[ignore_quotes] "
9152 "[incremental_search] "
9153 "[limits... #limit:value...] "
9154 "[forgotten_timeout #timeout] "
9155 "[double_click_delay #delay] "
9156 "[button_remapping #mapping...] "
9157 "[no_mouse] "
9158 "[show_blank_words [#blank_char]] "; /* <- don't remove *
9159 | this space! */
9161 main_spec_options = "[*copyright] "
9162 "[*version] "
9163 "[*long_help] "
9164 "[da_options #prefix:attr...] "
9165 "[auto_da_number... [#regex...]] "
9166 "[auto_da_unnumber... [#regex...]] "
9167 "[field_da_number] "
9168 "[column_mode>Columns] "
9169 "[line_mode>Lines] "
9170 "[tab_mode>Tabulations [#cols]] "
9171 "[tag_mode>Tagging [#delim]] "
9172 "[pin_mode>Tagging [#delim]]";
9174 col_spec_options = "[wide_mode] "
9175 "[columns_select... #selector...] "
9176 "[rows_select... #selector...] "
9177 "[aligns_select... #re_selector...] "
9178 "[gutter [#string]] "
9179 "[line_separators #bytes] "
9180 "[da_options #prefix:attr...] "
9181 "[auto_da_number... [#regex...]] "
9182 "[auto_da_unnumber... [#regex...]] "
9183 "[field_da_number] "
9184 "[tag_mode>Tagging [#delim]] "
9185 "[pin_mode>Tagging [#delim]] "
9186 "[force_first_column #regex] "
9187 "[force_last_column #regex]";
9189 line_spec_options = "[rows_select... #selector...] "
9190 "[line_separators #bytes] "
9191 "[da_options #prefix:attr...] "
9192 "[auto_da_number... [#regex...]] "
9193 "[auto_da_unnumber... [#regex...]] "
9194 "[field_da_number] "
9195 "[tag_mode>Tagging [#delim]] "
9196 "[pin_mode>Tagging [#delim]] "
9197 "[force_first_column #regex] "
9198 "[force_last_column #regex]";
9200 tab_spec_options = "[wide_mode] "
9201 "[gutter [#string]] "
9202 "[line_separators #bytes] "
9203 "[da_options #prefix:attr...] "
9204 "[auto_da_number... [#regex...]] "
9205 "[auto_da_unnumber... [#regex...]] "
9206 "[field_da_number] "
9207 "[tag_mode>Tagging [#delim]] "
9208 "[pin_mode>Tagging [#delim]] "
9209 "[force_first_column #regex] "
9210 "[force_last_column #regex]";
9212 tag_spec_options = "[auto_tag] "
9213 "[no_auto_tag] "
9214 "[column_mode>Columns] "
9215 "[line_mode>Lines] "
9216 "[tab_mode>Tabulations [#cols]]";
9218 main_options = concat(common_options, main_spec_options, (char *)0);
9219 col_options = concat(common_options, col_spec_options, (char *)0);
9220 line_options = concat(common_options, line_spec_options, (char *)0);
9221 tab_options = concat(common_options, tab_spec_options, (char *)0);
9222 tag_options = concat(common_options, tag_spec_options, (char *)0);
9224 ctxopt_new_ctx("Main", main_options);
9225 ctxopt_new_ctx("Columns", col_options);
9226 ctxopt_new_ctx("Lines", line_options);
9227 ctxopt_new_ctx("Tabulations", tab_options);
9228 ctxopt_new_ctx("Tagging", tag_options);
9230 free(main_options);
9231 free(col_options);
9232 free(line_options);
9233 free(tab_options);
9234 free(tag_options);
9236 /* ctxopt parameters. */
9237 /* """""""""""""""""" */
9239 ctxopt_add_opt_settings(parameters, "help", "-h -help");
9240 ctxopt_add_opt_settings(parameters, "long_help", "-H -long-help");
9241 ctxopt_add_opt_settings(parameters, "usage", "-? -u -usage");
9242 ctxopt_add_opt_settings(parameters, "copyright", "-copyright");
9243 ctxopt_add_opt_settings(parameters, "version", "-V -version");
9244 ctxopt_add_opt_settings(parameters,
9245 "include_re",
9246 "-i -in -inc -incl -include");
9247 ctxopt_add_opt_settings(parameters,
9248 "exclude_re",
9249 "-e -ex -exc -excl -exclude");
9250 ctxopt_add_opt_settings(parameters, "lines", "-n -lines -height");
9251 ctxopt_add_opt_settings(parameters, "title", "-m -msg -message -title");
9252 ctxopt_add_opt_settings(parameters, "int", "-! -int -int_string");
9253 ctxopt_add_opt_settings(parameters, "attributes", "-a -attr -attributes");
9254 ctxopt_add_opt_settings(parameters, "special_level_1", "-1 -l1 -level1");
9255 ctxopt_add_opt_settings(parameters, "special_level_2", "-2 -l2 -level2");
9256 ctxopt_add_opt_settings(parameters, "special_level_3", "-3 -l3 -level3");
9257 ctxopt_add_opt_settings(parameters, "special_level_4", "-4 -l4 -level4");
9258 ctxopt_add_opt_settings(parameters, "special_level_5", "-5 -l5 -level5");
9259 ctxopt_add_opt_settings(parameters, "special_level_6", "-6 -l6 -level6");
9260 ctxopt_add_opt_settings(parameters, "special_level_7", "-7 -l7 -level7");
9261 ctxopt_add_opt_settings(parameters, "special_level_8", "-8 -l8 -level8");
9262 ctxopt_add_opt_settings(parameters, "special_level_9", "-9 -l9 -level9");
9263 ctxopt_add_opt_settings(parameters, "tag_mode", "-T -tm -tag -tag_mode");
9264 ctxopt_add_opt_settings(parameters, "pin_mode", "-P -pm -pin -pin_mode");
9265 ctxopt_add_opt_settings(parameters, "auto_tag", "-p -at -auto_tag");
9266 ctxopt_add_opt_settings(parameters, "no_auto_tag", "-0 -noat -no_auto_tag");
9267 ctxopt_add_opt_settings(parameters, "auto_da_number", "-N -number");
9268 ctxopt_add_opt_settings(parameters, "auto_da_unnumber", "-U -unnumber");
9269 ctxopt_add_opt_settings(parameters,
9270 "field_da_number",
9271 "-F -en -embedded_number");
9272 ctxopt_add_opt_settings(parameters, "da_options", "-D -data -options");
9273 ctxopt_add_opt_settings(parameters, "invalid_character", "-. -dot -invalid");
9274 ctxopt_add_opt_settings(parameters, "blank_nonprintable", "-b -blank");
9275 ctxopt_add_opt_settings(parameters, "center_mode", "-M -middle -center");
9276 ctxopt_add_opt_settings(parameters,
9277 "clean",
9278 "-d -restore -delete -clean "
9279 "-delete_window -clean_window");
9280 ctxopt_add_opt_settings(parameters,
9281 "column_mode",
9282 "-c -col -col_mode -column");
9283 ctxopt_add_opt_settings(parameters, "line_mode", "-l -line -line_mode");
9284 ctxopt_add_opt_settings(parameters,
9285 "tab_mode",
9286 "-t -tab -tab_mode -tabulate_mode");
9287 ctxopt_add_opt_settings(parameters, "wide_mode", "-w -wide -wide_mode");
9288 ctxopt_add_opt_settings(parameters,
9289 "columns_select",
9290 "-C -cs -cols -cols_select");
9291 ctxopt_add_opt_settings(parameters,
9292 "rows_select",
9293 "-R -rs -rows -rows_select");
9294 ctxopt_add_opt_settings(parameters, "aligns_select", "-al -align");
9295 ctxopt_add_opt_settings(parameters,
9296 "force_first_column",
9297 "-A -fc -first_column");
9298 ctxopt_add_opt_settings(parameters,
9299 "force_last_column",
9300 "-Z -lc -last_column");
9301 ctxopt_add_opt_settings(parameters, "gutter", "-g -gutter");
9302 ctxopt_add_opt_settings(parameters, "keep_spaces", "-k -ks -keep_spaces");
9303 ctxopt_add_opt_settings(parameters,
9304 "word_separators",
9305 "-W -ws -wd -word_delimiters -word_separators");
9306 ctxopt_add_opt_settings(parameters,
9307 "line_separators",
9308 "-L -ls -ld -line-delimiters -line_separators");
9309 ctxopt_add_opt_settings(parameters, "zapped_glyphs", "-z -zap -zap-glyphs");
9310 ctxopt_add_opt_settings(parameters,
9311 "no_scroll_bar",
9312 "-q -no_bar -no_scroll_bar");
9313 ctxopt_add_opt_settings(parameters,
9314 "no_hor_scroll_bar",
9315 "-no_hbar -no_hor_scroll_bar");
9316 ctxopt_add_opt_settings(parameters, "early_subst_all", "-ES -early_subst");
9317 ctxopt_add_opt_settings(parameters, "post_subst_all", "-S -subst");
9318 ctxopt_add_opt_settings(parameters,
9319 "post_subst_included",
9320 "-I -si -subst_included");
9321 ctxopt_add_opt_settings(parameters,
9322 "post_subst_excluded",
9323 "-E -se -subst_excluded");
9324 ctxopt_add_opt_settings(parameters, "search_method", "-/ -search_method");
9325 ctxopt_add_opt_settings(parameters,
9326 "start_pattern",
9327 "-s -sp -start -start_pattern");
9328 ctxopt_add_opt_settings(parameters, "timeout", "-x -tmout -timeout");
9329 ctxopt_add_opt_settings(parameters,
9330 "hidden_timeout",
9331 "-X -htmout -hidden_timeout");
9332 ctxopt_add_opt_settings(parameters,
9333 "validate_in_search_mode",
9334 "-r -auto_validate");
9335 ctxopt_add_opt_settings(parameters, "visual_bell", "-v -vb -visual_bell");
9336 ctxopt_add_opt_settings(parameters, "ignore_quotes", "-Q -ignore_quotes");
9337 ctxopt_add_opt_settings(parameters,
9338 "incremental_search",
9339 "-is -incremental_search");
9340 ctxopt_add_opt_settings(parameters, "limits", "-lim -limits");
9341 ctxopt_add_opt_settings(parameters,
9342 "forgotten_timeout",
9343 "-f -forgotten_timeout -global_timeout");
9344 ctxopt_add_opt_settings(parameters, "no_mouse", "-nm -no_mouse");
9345 ctxopt_add_opt_settings(parameters,
9346 "button_remapping",
9347 "-br -buttons -button_remapping");
9348 ctxopt_add_opt_settings(parameters,
9349 "double_click_delay",
9350 "-dc -dcd -double_click -double_click_delay");
9351 ctxopt_add_opt_settings(parameters,
9352 "show_blank_words",
9353 "-sb -sbw -show_blank_words");
9355 /* ctxopt options incompatibilities. */
9356 /* """"""""""""""""""""""""""""""""" */
9358 ctxopt_add_ctx_settings(incompatibilities,
9359 "Main",
9360 "column_mode line_mode tab_mode");
9361 ctxopt_add_ctx_settings(incompatibilities, "Main", "tag_mode pin_mode");
9362 ctxopt_add_ctx_settings(incompatibilities, "Main", "help usage");
9363 ctxopt_add_ctx_settings(incompatibilities, "Main", "timeout hidden_timeout");
9364 ctxopt_add_ctx_settings(incompatibilities,
9365 "Main",
9366 "no_mouse button_remapping");
9367 ctxopt_add_ctx_settings(incompatibilities,
9368 "Main",
9369 "no_mouse double_click_delay");
9371 /* ctxopt options requirements. */
9372 /* """""""""""""""""""""""""""" */
9374 ctxopt_add_ctx_settings(requirements,
9375 "Main",
9376 "da_options "
9377 "field_da_number auto_da_number auto_da_unnumber");
9378 ctxopt_add_ctx_settings(requirements,
9379 "Columns",
9380 "da_options "
9381 "field_da_number auto_da_number auto_da_unnumber");
9382 ctxopt_add_ctx_settings(requirements,
9383 "Lines",
9384 "da_options "
9385 "field_da_number auto_da_number auto_da_unnumber");
9386 ctxopt_add_ctx_settings(requirements,
9387 "Tabulations",
9388 "da_options "
9389 "field_da_number auto_da_number auto_da_unnumber");
9391 /* ctxopt actions. */
9392 /* """"""""""""""" */
9394 ctxopt_add_opt_settings(actions,
9395 "auto_tag",
9396 toggle_action,
9397 &toggles,
9398 (char *)0);
9399 ctxopt_add_opt_settings(actions,
9400 "no_auto_tag",
9401 toggle_action,
9402 &toggles,
9403 (char *)0);
9404 ctxopt_add_opt_settings(actions,
9405 "invalid_character",
9406 invalid_char_action,
9407 &misc,
9408 (char *)0);
9409 ctxopt_add_opt_settings(actions,
9410 "blank_nonprintable",
9411 toggle_action,
9412 &toggles,
9413 (char *)0);
9414 ctxopt_add_opt_settings(actions,
9415 "center_mode",
9416 center_mode_action,
9417 &win,
9418 (char *)0);
9419 ctxopt_add_opt_settings(actions, "clean", toggle_action, &toggles, (char *)0);
9420 ctxopt_add_opt_settings(actions,
9421 "column_mode",
9422 column_mode_action,
9423 &win,
9424 (char *)0);
9425 ctxopt_add_opt_settings(actions,
9426 "line_mode",
9427 line_mode_action,
9428 &win,
9429 (char *)0);
9430 ctxopt_add_opt_settings(actions,
9431 "tab_mode",
9432 tab_mode_action,
9433 &win,
9434 (char *)0);
9435 ctxopt_add_opt_settings(actions,
9436 "columns_select",
9437 columns_select_action,
9438 &cols_selector_list,
9439 &toggles,
9440 (char *)0);
9441 ctxopt_add_opt_settings(actions,
9442 "rows_select",
9443 rows_select_action,
9444 &rows_selector_list,
9445 &win,
9446 &toggles,
9447 (char *)0);
9448 ctxopt_add_opt_settings(actions,
9449 "aligns_select",
9450 aligns_select_action,
9451 &aligns_selector_list,
9452 (char *)0);
9453 ctxopt_add_opt_settings(actions,
9454 "include_re",
9455 include_re_action,
9456 &pattern_def_include,
9457 &include_pattern,
9458 &misc,
9459 (char *)0);
9460 ctxopt_add_opt_settings(actions,
9461 "exclude_re",
9462 exclude_re_action,
9463 &pattern_def_include,
9464 &exclude_pattern,
9465 &misc,
9466 (char *)0);
9467 ctxopt_add_opt_settings(actions,
9468 "gutter",
9469 gutter_action,
9470 &win,
9471 &langinfo,
9472 &misc,
9473 (char *)0);
9474 ctxopt_add_opt_settings(actions, "help", help_action, (char *)0);
9475 ctxopt_add_opt_settings(actions, "long_help", long_help_action, (char *)0);
9476 ctxopt_add_opt_settings(actions, "usage", usage_action, (char *)0);
9477 ctxopt_add_opt_settings(actions,
9478 "keep_spaces",
9479 toggle_action,
9480 &toggles,
9481 (char *)0);
9482 ctxopt_add_opt_settings(actions, "lines", lines_action, &win, (char *)0);
9483 ctxopt_add_opt_settings(actions,
9484 "no_scroll_bar",
9485 toggle_action,
9486 &toggles,
9487 (char *)0);
9488 ctxopt_add_opt_settings(actions,
9489 "no_hor_scroll_bar",
9490 toggle_action,
9491 &toggles,
9492 (char *)0);
9493 ctxopt_add_opt_settings(actions,
9494 "start_pattern",
9495 set_pattern_action,
9496 &pre_selection_index,
9497 &misc,
9498 (char *)0);
9499 ctxopt_add_opt_settings(actions,
9500 "title",
9501 set_string_action,
9502 &message,
9503 &langinfo,
9504 &misc,
9505 (char *)0);
9506 ctxopt_add_opt_settings(actions,
9507 "int",
9508 int_action,
9509 &int_string,
9510 &int_as_in_shell,
9511 &langinfo,
9512 &misc,
9513 (char *)0);
9514 ctxopt_add_opt_settings(actions,
9515 "validate_in_search_mode",
9516 toggle_action,
9517 &toggles,
9518 (char *)0);
9519 ctxopt_add_opt_settings(actions, "copyright", copyright_action, (char *)0);
9520 ctxopt_add_opt_settings(actions, "version", version_action, (char *)0);
9521 ctxopt_add_opt_settings(actions,
9522 "visual_bell",
9523 toggle_action,
9524 &toggles,
9525 (char *)0);
9526 ctxopt_add_opt_settings(actions,
9527 "incremental_search",
9528 toggle_action,
9529 &toggles,
9530 (char *)0);
9531 ctxopt_add_opt_settings(actions,
9532 "wide_mode",
9533 wide_mode_action,
9534 &win,
9535 (char *)0);
9536 ctxopt_add_opt_settings(actions,
9537 "early_subst_all",
9538 post_subst_action,
9539 &early_sed_list,
9540 &misc,
9541 (char *)0);
9542 ctxopt_add_opt_settings(actions,
9543 "post_subst_all",
9544 post_subst_action,
9545 &sed_list,
9546 &misc,
9547 (char *)0);
9548 ctxopt_add_opt_settings(actions,
9549 "post_subst_included",
9550 post_subst_action,
9551 &include_sed_list,
9552 &misc,
9553 (char *)0);
9554 ctxopt_add_opt_settings(actions,
9555 "post_subst_excluded",
9556 post_subst_action,
9557 &exclude_sed_list,
9558 &misc,
9559 (char *)0);
9560 ctxopt_add_opt_settings(actions,
9561 "special_level_1",
9562 special_level_action,
9563 special_pattern,
9564 &win,
9565 &term,
9566 &init_attr,
9567 &misc,
9568 (char *)0);
9569 ctxopt_add_opt_settings(actions,
9570 "special_level_2",
9571 special_level_action,
9572 special_pattern,
9573 &win,
9574 &term,
9575 &init_attr,
9576 &misc,
9577 (char *)0);
9578 ctxopt_add_opt_settings(actions,
9579 "special_level_3",
9580 special_level_action,
9581 special_pattern,
9582 &win,
9583 &term,
9584 &init_attr,
9585 &misc,
9586 (char *)0);
9587 ctxopt_add_opt_settings(actions,
9588 "special_level_4",
9589 special_level_action,
9590 special_pattern,
9591 &win,
9592 &term,
9593 &init_attr,
9594 &misc,
9595 (char *)0);
9596 ctxopt_add_opt_settings(actions,
9597 "special_level_5",
9598 special_level_action,
9599 special_pattern,
9600 &win,
9601 &term,
9602 &init_attr,
9603 &misc,
9604 (char *)0);
9605 ctxopt_add_opt_settings(actions,
9606 "special_level_6",
9607 special_level_action,
9608 special_pattern,
9609 &win,
9610 &term,
9611 &init_attr,
9612 &misc,
9613 (char *)0);
9614 ctxopt_add_opt_settings(actions,
9615 "special_level_7",
9616 special_level_action,
9617 special_pattern,
9618 &win,
9619 &term,
9620 &init_attr,
9621 &misc,
9622 (char *)0);
9623 ctxopt_add_opt_settings(actions,
9624 "special_level_8",
9625 special_level_action,
9626 special_pattern,
9627 &win,
9628 &term,
9629 &init_attr,
9630 &misc,
9631 (char *)0);
9632 ctxopt_add_opt_settings(actions,
9633 "special_level_9",
9634 special_level_action,
9635 special_pattern,
9636 &win,
9637 &term,
9638 &init_attr,
9639 &misc,
9640 (char *)0);
9641 ctxopt_add_opt_settings(actions,
9642 "attributes",
9643 attributes_action,
9644 &win,
9645 &term,
9646 &init_attr,
9647 (char *)0);
9648 ctxopt_add_opt_settings(actions, "timeout", timeout_action, &misc, (char *)0);
9649 ctxopt_add_opt_settings(actions,
9650 "hidden_timeout",
9651 timeout_action,
9652 &misc,
9653 (char *)0);
9654 ctxopt_add_opt_settings(actions,
9655 "force_first_column",
9656 set_pattern_action,
9657 &first_word_pattern,
9658 &misc,
9659 (char *)0);
9660 ctxopt_add_opt_settings(actions,
9661 "force_last_column",
9662 set_pattern_action,
9663 &last_word_pattern,
9664 &misc,
9665 (char *)0);
9666 ctxopt_add_opt_settings(actions,
9667 "word_separators",
9668 set_pattern_action,
9669 &iws,
9670 &misc,
9671 (char *)0);
9672 ctxopt_add_opt_settings(actions,
9673 "line_separators",
9674 set_pattern_action,
9675 &ils,
9676 &misc,
9677 (char *)0);
9678 ctxopt_add_opt_settings(actions,
9679 "zapped_glyphs",
9680 set_pattern_action,
9681 &zg,
9682 &misc,
9683 (char *)0);
9684 ctxopt_add_opt_settings(actions,
9685 "tag_mode",
9686 tag_mode_action,
9687 &toggles,
9688 &win,
9689 &misc,
9690 (char *)0);
9691 ctxopt_add_opt_settings(actions,
9692 "pin_mode",
9693 pin_mode_action,
9694 &toggles,
9695 &win,
9696 &misc,
9697 (char *)0);
9698 ctxopt_add_opt_settings(actions,
9699 "search_method",
9700 search_method_action,
9701 &misc,
9702 (char *)0);
9703 ctxopt_add_opt_settings(actions,
9704 "auto_da_number",
9705 auto_da_action,
9706 &daccess_np,
9707 (char *)0);
9708 ctxopt_add_opt_settings(actions,
9709 "auto_da_unnumber",
9710 auto_da_action,
9711 &daccess_up,
9712 (char *)0);
9713 ctxopt_add_opt_settings(actions,
9714 "field_da_number",
9715 field_da_number_action,
9716 (char *)0);
9717 ctxopt_add_opt_settings(actions,
9718 "da_options",
9719 da_options_action,
9720 &daccess_index,
9721 &misc,
9722 (char *)0);
9723 ctxopt_add_opt_settings(actions,
9724 "ignore_quotes",
9725 ignore_quotes_action,
9726 &misc,
9727 (char *)0);
9728 ctxopt_add_opt_settings(actions, "limits", limits_action, &limits, (char *)0);
9729 ctxopt_add_opt_settings(actions,
9730 "forgotten_timeout",
9731 forgotten_action,
9732 &timers,
9733 (char *)0);
9734 ctxopt_add_opt_settings(actions,
9735 "button_remapping",
9736 button_action,
9737 &mouse,
9738 (char *)0);
9739 ctxopt_add_opt_settings(actions,
9740 "double_click_delay",
9741 double_click_action,
9742 &mouse,
9743 &disable_double_click,
9744 (char *)0);
9745 ctxopt_add_opt_settings(actions,
9746 "no_mouse",
9747 toggle_action,
9748 &toggles,
9749 (char *)0);
9750 ctxopt_add_opt_settings(actions,
9751 "show_blank_words",
9752 show_blank_action,
9753 &toggles,
9754 &misc,
9755 (char *)0);
9757 /* ctxopt constraints. */
9758 /* """"""""""""""""""" */
9760 ctxopt_add_opt_settings(constraints,
9761 "attributes",
9762 ctxopt_re_constraint,
9763 "[^:]+:.+");
9764 ctxopt_add_opt_settings(constraints,
9765 "da_options",
9766 ctxopt_re_constraint,
9767 "[^:]+:.+");
9768 ctxopt_add_opt_settings(constraints, "lines", check_integer_constraint, "");
9770 ctxopt_add_opt_settings(constraints,
9771 "tab_mode",
9772 check_integer_constraint,
9773 "");
9774 ctxopt_add_opt_settings(constraints,
9775 "tab_mode",
9776 ctxopt_range_constraint,
9777 "1 .");
9778 ctxopt_add_opt_settings(constraints,
9779 "double_click_delay",
9780 check_integer_constraint,
9781 "");
9783 /* Evaluation order. */
9784 /* """"""""""""""""" */
9786 ctxopt_add_opt_settings(after,
9787 "field_da_number",
9788 "auto_da_number auto_da_unnumber");
9790 ctxopt_add_opt_settings(after,
9791 "da_options",
9792 "field_da_number auto_da_number auto_da_unnumber");
9794 /* In help settings. */
9795 /* """"""""""""""""" */
9797 ctxopt_add_opt_settings(visible_in_help, "copyright", "no");
9799 /* Command line options analysis. */
9800 /* """""""""""""""""""""""""""""" */
9801 ctxopt_analyze(argc - 1, argv + 1, &nb_rem_args, &rem_args);
9803 /* Command line options evaluation. */
9804 /* """""""""""""""""""""""""""""""" */
9805 ctxopt_evaluate();
9807 /* Check remaining non analyzed command line arguments. */
9808 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
9809 if (nb_rem_args == 1)
9811 input_file = fopen_safe(rem_args[0], "r");
9812 if (input_file == NULL)
9814 fprintf(stderr,
9815 "The file \"%s\" does not exist or cannot be read.\n",
9816 rem_args[0]);
9817 ctxopt_ctx_disp_usage(NULL, exit_after);
9818 exit(EXIT_FAILURE); /* Avoid a compiler warning. */
9821 else if (nb_rem_args == 0)
9822 input_file = stdin;
9823 else
9825 fprintf(stderr, "Extra arguments detected:\n");
9827 fprintf(stderr, "%s", rem_args[1]);
9828 for (int i = 2; i < nb_rem_args; i++)
9829 fprintf(stderr, ", %s", rem_args[i]);
9830 fprintf(stderr, ".\n");
9832 ctxopt_ctx_disp_usage(NULL, exit_after);
9833 exit(EXIT_FAILURE); /* Avoid a compiler warning. */
9836 /* Free the memory used internally by ctxopt. */
9837 /* """""""""""""""""""""""""""""""""""""""""" */
9838 ctxopt_free_memory();
9840 /* If we did not impose the number of columns, use the whole */
9841 /* terminal width. */
9842 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9843 if (win.tab_mode && !win.max_cols)
9844 win.wide = 1;
9846 /* Force the padding mode to all when words are numbered and not in */
9847 /* col/line/tab_mode. */
9848 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9849 if (daccess.padding != 'a' && (win.col_mode || win.line_mode || win.tab_mode))
9850 daccess.padding = 'a';
9852 win.start = 0;
9854 term.color_method = ANSI; /* We default to setaf/setbf to set colors. */
9855 term.curs_line = term.curs_column = 0;
9858 char *str;
9860 str = tigetstr("cuu1");
9861 term.has_cursor_up = (str == (char *)-1 || str == NULL) ? 0 : 1;
9862 str = tigetstr("cud1");
9863 term.has_cursor_down = (str == (char *)-1 || str == NULL) ? 0 : 1;
9864 str = tigetstr("cub1");
9865 term.has_cursor_left = (str == (char *)-1 || str == NULL) ? 0 : 1;
9866 str = tigetstr("cuf1");
9867 term.has_cursor_right = (str == (char *)-1 || str == NULL) ? 0 : 1;
9868 str = tigetstr("cup");
9869 term.has_cursor_address = (str == (char *)-1 || str == NULL) ? 0 : 1;
9870 str = tigetstr("sc");
9871 term.has_save_cursor = (str == (char *)-1 || str == NULL) ? 0 : 1;
9872 str = tigetstr("rc");
9873 term.has_restore_cursor = (str == (char *)-1 || str == NULL) ? 0 : 1;
9874 str = tigetstr("setf");
9875 term.has_setf = (str == (char *)-1 || str == NULL) ? 0 : 1;
9876 str = tigetstr("setb");
9877 term.has_setb = (str == (char *)-1 || str == NULL) ? 0 : 1;
9878 str = tigetstr("setaf");
9879 term.has_setaf = (str == (char *)-1 || str == NULL) ? 0 : 1;
9880 str = tigetstr("setab");
9881 term.has_setab = (str == (char *)-1 || str == NULL) ? 0 : 1;
9882 str = tigetstr("hpa");
9883 term.has_hpa = (str == (char *)-1 || str == NULL) ? 0 : 1;
9884 str = tigetstr("cuf");
9885 term.has_parm_right_cursor = (str == (char *)-1 || str == NULL) ? 0 : 1;
9886 str = tigetstr("bold");
9887 term.has_bold = (str == (char *)-1 || str == NULL) ? 0 : 1;
9888 str = tigetstr("dim");
9889 term.has_dim = (str == (char *)-1 || str == NULL) ? 0 : 1;
9890 str = tigetstr("rev");
9891 term.has_reverse = (str == (char *)-1 || str == NULL) ? 0 : 1;
9892 str = tigetstr("smul");
9893 term.has_underline = (str == (char *)-1 || str == NULL) ? 0 : 1;
9894 str = tigetstr("smso");
9895 term.has_standout = (str == (char *)-1 || str == NULL) ? 0 : 1;
9896 str = tigetstr("sitm");
9897 term.has_italic = (str == (char *)-1 || str == NULL) ? 0 : 1;
9898 str = tigetstr("invis");
9899 term.has_invis = (str == (char *)-1 || str == NULL) ? 0 : 1;
9900 str = tigetstr("blink");
9901 term.has_blink = (str == (char *)-1 || str == NULL) ? 0 : 1;
9902 str = tigetstr("kmous");
9903 term.has_kmous = (str == (char *)-1 || str == NULL) ? 0 : 1;
9904 str = tigetstr("rep");
9905 term.has_rep = (str == (char *)-1 || str == NULL) ? 0 : 1;
9908 if (!term.has_cursor_up || !term.has_cursor_down || !term.has_cursor_left
9909 || !term.has_cursor_right || !term.has_save_cursor
9910 || !term.has_restore_cursor)
9912 fprintf(stderr,
9913 "The terminal does not have the required cursor "
9914 "management capabilities.\n");
9916 exit(EXIT_FAILURE);
9919 word_buffer = xcalloc(1, daccess.flength + limits.word_length + 1);
9921 /* default_search_method is not set in the command line nor in a config */
9922 /* file, set it to fuzzy. */
9923 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9924 if (misc.default_search_method == NONE)
9925 misc.default_search_method = FUZZY;
9927 /* If some attributes were not set, set their default values. */
9928 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
9929 if (term.colors > 7)
9931 int const special_def_attr[9] = { 1, 2, 3, 5, 6, 7, 7, 7, 7 };
9933 if (!win.cursor_attr.is_set)
9935 if (term.has_reverse)
9936 win.cursor_attr.reverse = 1;
9937 else if (term.has_standout)
9938 win.cursor_attr.standout = 1;
9940 win.cursor_attr.is_set = SET;
9943 if (!win.cursor_marked_attr.is_set)
9945 if (term.has_dim)
9946 win.cursor_marked_attr.dim = 1;
9947 else if (term.has_bold)
9948 win.cursor_marked_attr.bold = 1;
9950 if (term.has_reverse)
9951 win.cursor_marked_attr.reverse = 1;
9952 else if (term.has_standout)
9953 win.cursor_marked_attr.standout = 1;
9955 win.cursor_marked_attr.is_set = SET;
9958 if (!win.cursor_on_marked_attr.is_set)
9960 if (term.has_bold)
9961 win.cursor_on_marked_attr.bold = 1;
9962 if (term.has_italic)
9963 win.cursor_on_marked_attr.italic = 1;
9965 if (term.has_reverse)
9966 win.cursor_on_marked_attr.reverse = 1;
9967 else if (term.has_standout)
9968 win.cursor_on_marked_attr.standout = 1;
9970 win.cursor_on_marked_attr.is_set = SET;
9973 if (!win.cursor_on_tag_attr.is_set)
9975 if (term.has_reverse)
9976 win.cursor_on_tag_attr.reverse = 1;
9978 if (term.has_underline)
9979 win.cursor_on_tag_attr.underline = 1;
9980 else
9981 win.cursor_on_tag_attr.fg = 2;
9983 win.cursor_on_tag_attr.is_set = SET;
9986 if (!win.cursor_on_tag_marked_attr.is_set)
9988 if (term.has_dim)
9989 win.cursor_on_tag_marked_attr.dim = 1;
9991 if (term.has_reverse)
9992 win.cursor_on_tag_marked_attr.reverse = 1;
9994 if (term.has_underline)
9995 win.cursor_on_tag_marked_attr.underline = 1;
9997 win.cursor_on_tag_marked_attr.is_set = SET;
10000 if (!win.marked_attr.is_set)
10002 if (term.has_standout)
10003 win.marked_attr.standout = 1;
10005 if (term.has_bold)
10006 win.marked_attr.bold = 1;
10008 win.marked_attr.fg = 2;
10009 win.marked_attr.bg = 0;
10011 win.marked_attr.is_set = SET;
10014 if (!win.bar_attr.is_set)
10016 win.bar_attr.fg = 2;
10017 win.bar_attr.is_set = SET;
10020 if (!win.shift_attr.is_set)
10022 win.shift_attr.fg = 2;
10023 win.shift_attr.is_set = SET;
10026 if (!win.message_attr.is_set)
10028 if (term.has_bold)
10029 win.message_attr.bold = 1;
10030 else if (term.has_reverse)
10031 win.message_attr.reverse = 1;
10032 else
10034 win.message_attr.fg = 0;
10035 win.message_attr.bg = 7;
10038 win.message_attr.is_set = SET;
10041 if (!win.search_field_attr.is_set)
10043 win.search_field_attr.bg = 5;
10044 win.search_field_attr.is_set = SET;
10047 if (!win.search_text_attr.is_set)
10049 win.search_text_attr.fg = 0;
10050 win.search_text_attr.bg = 6;
10052 win.search_text_attr.is_set = SET;
10055 if (!win.search_err_field_attr.is_set)
10057 win.search_err_field_attr.bg = 1;
10058 win.search_err_field_attr.is_set = SET;
10061 if (!win.search_err_text_attr.is_set)
10063 if (term.has_reverse)
10064 win.search_err_text_attr.reverse = 1;
10066 win.search_err_text_attr.fg = 1;
10067 win.search_err_text_attr.is_set = SET;
10070 if (!win.match_field_attr.is_set)
10072 win.match_field_attr.is_set = SET;
10075 if (!win.match_text_attr.is_set)
10077 win.match_text_attr.fg = 5;
10078 win.match_text_attr.is_set = SET;
10081 if (!win.match_err_field_attr.is_set)
10083 win.match_err_field_attr.is_set = SET;
10086 if (!win.match_err_text_attr.is_set)
10088 win.match_err_text_attr.fg = 1;
10089 win.match_err_text_attr.is_set = SET;
10092 if (!win.exclude_attr.is_set)
10094 win.exclude_attr.fg = 6;
10096 win.exclude_attr.is_set = SET;
10099 /* This attribute should complete the attributes already set. */
10100 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10101 if (!win.tag_attr.is_set)
10103 if (term.has_underline)
10104 win.tag_attr.underline = 1;
10105 else if (term.has_bold)
10106 win.tag_attr.bold = 1;
10107 else
10108 win.tag_attr.fg = 2;
10110 win.tag_attr.is_set = SET;
10113 if (!win.daccess_attr.is_set)
10115 if (term.has_bold)
10116 win.daccess_attr.bold = 1;
10118 win.daccess_attr.is_set = SET;
10121 for (index = 0; index < 9; index++)
10123 if (!win.special_attr[index].is_set)
10125 win.special_attr[index].fg = special_def_attr[index];
10126 win.special_attr[index].is_set = SET;
10130 else
10132 if (!win.cursor_attr.is_set)
10134 if (term.has_reverse)
10135 win.cursor_attr.reverse = 1;
10137 win.cursor_attr.is_set = SET;
10140 if (!win.cursor_on_tag_attr.is_set)
10142 if (term.has_reverse)
10143 win.cursor_on_tag_attr.reverse = 1;
10145 if (term.has_underline)
10146 win.cursor_on_tag_attr.underline = 1;
10147 else if (term.has_bold)
10148 win.cursor_on_tag_attr.bold = 1;
10150 win.cursor_on_tag_attr.is_set = SET;
10153 if (!win.bar_attr.is_set)
10155 if (term.has_bold)
10156 win.bar_attr.bold = 1;
10158 win.bar_attr.is_set = SET;
10161 if (!win.shift_attr.is_set)
10163 if (term.has_reverse)
10164 win.shift_attr.reverse = 1;
10166 win.shift_attr.is_set = SET;
10169 if (!win.message_attr.is_set)
10171 if (term.has_bold)
10172 win.message_attr.bold = 1;
10173 else if (term.has_reverse)
10174 win.message_attr.reverse = 1;
10176 win.message_attr.is_set = SET;
10179 if (!win.search_field_attr.is_set)
10181 if (term.has_reverse)
10182 win.search_field_attr.reverse = 1;
10184 win.search_field_attr.is_set = SET;
10187 if (!win.search_text_attr.is_set)
10189 if (term.has_bold)
10190 win.search_text_attr.bold = 1;
10192 win.search_text_attr.is_set = SET;
10195 if (!win.search_err_field_attr.is_set)
10197 if (term.has_bold)
10198 win.search_err_field_attr.bold = 1;
10200 win.search_err_field_attr.is_set = SET;
10203 if (!win.search_err_text_attr.is_set)
10205 if (term.has_reverse)
10206 win.search_err_text_attr.reverse = 1;
10208 win.search_err_text_attr.is_set = SET;
10211 if (!win.match_field_attr.is_set)
10213 if (term.has_bold)
10214 win.match_field_attr.bold = 1;
10215 else if (term.has_reverse)
10216 win.match_field_attr.reverse = 1;
10218 win.match_field_attr.is_set = SET;
10221 if (!win.match_text_attr.is_set)
10223 if (term.has_reverse)
10224 win.match_text_attr.reverse = 1;
10225 else if (term.has_bold)
10226 win.match_text_attr.bold = 1;
10228 win.match_text_attr.is_set = SET;
10231 if (!win.exclude_attr.is_set)
10233 if (term.has_dim)
10234 win.exclude_attr.dim = 1;
10235 else if (term.has_italic)
10236 win.exclude_attr.italic = 1;
10237 else if (term.has_bold)
10238 win.exclude_attr.bold = 1;
10240 win.exclude_attr.is_set = SET;
10243 if (!win.tag_attr.is_set)
10245 if (term.has_underline)
10246 win.tag_attr.underline = 1;
10247 else if (term.has_standout)
10248 win.tag_attr.standout = 1;
10249 else if (term.has_reverse)
10250 win.tag_attr.reverse = 1;
10252 win.tag_attr.is_set = SET;
10255 if (!win.daccess_attr.is_set)
10257 if (term.has_bold)
10258 win.daccess_attr.bold = 1;
10260 win.daccess_attr.is_set = SET;
10263 for (index = 0; index < 9; index++)
10265 if (!win.special_attr[index].is_set)
10267 if (term.has_bold)
10268 win.special_attr[index].bold = 1;
10269 else if (term.has_standout)
10270 win.special_attr[index].standout = 1;
10272 win.special_attr[index].is_set = SET;
10277 /* Initialize and arm the global (forgotten) timer. */
10278 /* """""""""""""""""""""""""""""""""""""""""""""""" */
10279 forgotten_timer = timers.forgotten;
10281 /* Initialize the timeout message when the x/X option is set. */
10282 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10283 if (!quiet_timeout && timeout.initial_value > 0)
10285 switch (timeout.mode)
10287 case QUIT:
10288 timeout_message = xstrdup(
10289 "[ s before quitting without selecting anything]");
10290 break;
10292 case CURRENT:
10293 timeout_message = xstrdup(
10294 "[ s before selecting the current highlighted word]");
10295 break;
10297 case WORD:
10299 char *s = "[ s before selecting the word \"";
10301 timeout_message = xcalloc(1, 4 + strlen(s) + strlen(timeout_word));
10303 strcpy(timeout_message, s);
10304 strcat(timeout_message, timeout_word);
10305 strcat(timeout_message, "\"]");
10307 break;
10310 default:
10311 /* The other cases are impossible due to options analysis. */
10312 /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
10313 timeout_message = xstrdup(" "); /* Just in case. */
10316 timeout_seconds = xcalloc(1, 6);
10317 sprintf(timeout_seconds, "%5u", timeout.initial_value / FREQ);
10318 memcpy(timeout_message + 1, timeout_seconds, 5);
10320 message_lines_list = ll_new();
10322 if (message)
10324 long len;
10326 get_message_lines(message,
10327 message_lines_list,
10328 &message_max_width,
10329 &message_max_len);
10330 ll_append(message_lines_list, timeout_message);
10332 if ((len = strlen(timeout_message)) > message_max_len)
10333 message_max_len = message_max_width = len;
10335 else
10337 ll_append(message_lines_list, timeout_message);
10338 message_max_len = message_max_width = strlen(timeout_message);
10341 else if (message)
10343 message_lines_list = ll_new();
10344 get_message_lines(message,
10345 message_lines_list,
10346 &message_max_width,
10347 &message_max_len);
10350 /* Force the maximum number of window's line if -n is used. */
10351 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10352 if (term.nlines <= win.message_lines)
10354 win.message_lines = term.nlines - 1;
10355 win.max_lines = 1;
10357 else if (win.asked_max_lines >= 0)
10359 if (win.asked_max_lines == 0)
10360 win.max_lines = term.nlines - win.message_lines - 1;
10361 else
10363 if (win.asked_max_lines > term.nlines - win.message_lines)
10364 win.max_lines = term.nlines - win.message_lines - 1;
10365 else
10366 win.max_lines = win.asked_max_lines;
10369 else /* -n was not used. Set win.asked_max_lines to its default value. */
10370 win.asked_max_lines = win.max_lines;
10372 /* Allocate the memory for our words structures. */
10373 /* """"""""""""""""""""""""""""""""""""""""""""" */
10374 word_a = xmalloc(WORDSCHUNK * sizeof(word_t));
10376 /* Fill an array of word_t elements obtained from stdin. */
10377 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
10378 tab_real_max_size = 0;
10379 tab_max_size = 0;
10380 min_size = 0;
10382 /* Parse the list of glyphs to be zapped (option -z). */
10383 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
10384 zapped_glyphs_list = ll_new();
10385 if (zg != NULL)
10387 int utf8_len;
10388 char *zg_ptr = zg;
10389 char *tmp;
10391 utf8_len = mblen(zg_ptr, 4);
10393 while (utf8_len != 0)
10395 tmp = xmalloc(utf8_len + 1);
10396 memcpy(tmp, zg_ptr, utf8_len);
10397 tmp[utf8_len] = '\0';
10398 ll_append(zapped_glyphs_list, tmp);
10400 zg_ptr += utf8_len;
10401 utf8_len = mblen(zg_ptr, 4);
10405 /* Parse the word separators string (option -W). If it is empty then */
10406 /* the standard delimiters (space, tab and EOL) are used. Each of its */
10407 /* UTF-8 sequences are stored in a linked list. */
10408 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10409 word_delims_list = ll_new();
10411 if (iws == NULL)
10413 ll_append(word_delims_list, " ");
10414 ll_append(word_delims_list, "\t");
10415 ll_append(word_delims_list, "\n");
10417 else
10419 int utf8_len;
10420 char *iws_ptr = iws;
10421 char *tmp;
10423 utf8_len = mblen(iws_ptr, 4);
10425 while (utf8_len != 0)
10427 tmp = xmalloc(utf8_len + 1);
10428 memcpy(tmp, iws_ptr, utf8_len);
10429 tmp[utf8_len] = '\0';
10430 ll_append(word_delims_list, tmp);
10432 iws_ptr += utf8_len;
10433 utf8_len = mblen(iws_ptr, 4);
10437 /* Parse the line separators string (option -L). If it is empty then */
10438 /* the standard delimiter (newline) is used. Each of its UTF-8 */
10439 /* sequences are stored in a linked list. */
10440 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10441 line_delims_list = ll_new();
10443 /* A default line separator is set to '\n' except in tab_mode */
10444 /* where it should be explicitly set. */
10445 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10446 if (ils == NULL && !win.tab_mode)
10447 ll_append(line_delims_list, "\n");
10448 else
10450 int utf8_len;
10451 char *ils_ptr = ils;
10452 char *tmp;
10454 utf8_len = mblen(ils_ptr, 4);
10456 while (utf8_len != 0)
10458 tmp = xmalloc(utf8_len + 1);
10459 memcpy(tmp, ils_ptr, utf8_len);
10460 tmp[utf8_len] = '\0';
10461 ll_append(line_delims_list, tmp);
10463 /* Add this record delimiter as a word delimiter. */
10464 /* """""""""""""""""""""""""""""""""""""""""""""" */
10465 if (ll_find(word_delims_list, tmp, buffer_cmp) == NULL)
10466 ll_append(word_delims_list, tmp);
10468 ils_ptr += utf8_len;
10469 utf8_len = mblen(ils_ptr, 4);
10473 /* Initialize the first chunks of the arrays which will contain the */
10474 /* maximum length of each column in column mode. */
10475 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10476 if (win.col_mode)
10478 long ci; /* Column index. */
10480 col_real_max_size = xmalloc(COLSCHUNK * sizeof(long));
10481 col_max_size = xmalloc(COLSCHUNK * sizeof(long));
10483 for (ci = 0; ci < COLSCHUNK; ci++)
10484 col_real_max_size[ci] = col_max_size[ci] = 0;
10486 col_index = cols_number = 0;
10489 /* Compile the regular expression patterns. */
10490 /* """""""""""""""""""""""""""""""""""""""" */
10491 if (daccess_np
10492 && regcomp(&daccess_np_re, daccess_np, REG_EXTENDED | REG_NOSUB) != 0)
10494 fprintf(stderr, "%s: Bad regular expression.\n", daccess_np);
10496 exit(EXIT_FAILURE);
10499 if (daccess_up
10500 && regcomp(&daccess_up_re, daccess_up, REG_EXTENDED | REG_NOSUB) != 0)
10502 fprintf(stderr, "%s: Bad regular expression.\n", daccess_up);
10504 exit(EXIT_FAILURE);
10507 if (include_pattern != NULL
10508 && regcomp(&include_re, include_pattern, REG_EXTENDED | REG_NOSUB) != 0)
10510 fprintf(stderr, "%s: Bad regular expression.\n", include_pattern);
10512 exit(EXIT_FAILURE);
10515 if (exclude_pattern != NULL
10516 && regcomp(&exclude_re, exclude_pattern, REG_EXTENDED | REG_NOSUB) != 0)
10518 fprintf(stderr, "%s: Bad regular expression.\n", exclude_pattern);
10520 exit(EXIT_FAILURE);
10523 if (first_word_pattern != NULL
10524 && regcomp(&first_word_re, first_word_pattern, REG_EXTENDED | REG_NOSUB)
10525 != 0)
10527 fprintf(stderr, "%s: Bad regular expression.\n", first_word_pattern);
10529 exit(EXIT_FAILURE);
10532 if (last_word_pattern != NULL
10533 && regcomp(&last_word_re, last_word_pattern, REG_EXTENDED | REG_NOSUB)
10534 != 0)
10536 fprintf(stderr, "%s: Bad regular expression.\n", last_word_pattern);
10538 exit(EXIT_FAILURE);
10541 for (index = 0; index < 9; index++)
10543 if (special_pattern[index] != NULL
10544 && regcomp(&special_re[index],
10545 special_pattern[index],
10546 REG_EXTENDED | REG_NOSUB)
10547 != 0)
10549 fprintf(stderr, "%s: Bad regular expression.\n", special_pattern[index]);
10551 exit(EXIT_FAILURE);
10555 /* Parse the post-processing patterns and extract its values. */
10556 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10557 if (early_sed_list != NULL)
10559 ll_node_t *node = early_sed_list->head;
10561 while (node != NULL)
10563 if (!parse_sed_like_string((sed_t *)(node->data)))
10565 fprintf(stderr,
10566 "Bad -ES argument. Must be something like: "
10567 "/regex/repl_string/[g][v][s][i].\n");
10569 exit(EXIT_FAILURE);
10572 node = node->next;
10576 if (sed_list != NULL)
10578 ll_node_t *node = sed_list->head;
10580 while (node != NULL)
10582 if (!parse_sed_like_string((sed_t *)(node->data)))
10584 fprintf(stderr,
10585 "Bad -S argument. Must be something like: "
10586 "/regex/repl_string/[g][v][s][i].\n");
10588 exit(EXIT_FAILURE);
10590 if ((!include_visual_only || !exclude_visual_only)
10591 && ((sed_t *)(node->data))->visual)
10593 include_visual_only = 1;
10594 exclude_visual_only = 1;
10597 node = node->next;
10601 if (include_sed_list != NULL)
10603 ll_node_t *node = include_sed_list->head;
10605 while (node != NULL)
10607 if (!parse_sed_like_string((sed_t *)(node->data)))
10609 fprintf(stderr,
10610 "Bad -I argument. Must be something like: "
10611 "/regex/repl_string/[g][v][s][i].\n");
10613 exit(EXIT_FAILURE);
10615 if (!include_visual_only && ((sed_t *)(node->data))->visual)
10616 include_visual_only = 1;
10618 node = node->next;
10622 if (exclude_sed_list != NULL)
10624 ll_node_t *node = exclude_sed_list->head;
10626 while (node != NULL)
10628 if (!parse_sed_like_string((sed_t *)(node->data)))
10630 fprintf(stderr,
10631 "Bad -E argument. Must be something like: "
10632 "/regex/repl_string/[g][v][s][i].\n");
10634 exit(EXIT_FAILURE);
10636 if (!exclude_visual_only && ((sed_t *)(node->data))->visual)
10637 exclude_visual_only = 1;
10639 node = node->next;
10643 /* Parse the row selection string if any. */
10644 /* """""""""""""""""""""""""""""""""""""" */
10645 if (rows_selector_list != NULL)
10647 ll_node_t *node_selector = rows_selector_list->head;
10648 ll_node_t *node;
10649 filters_t filter_type;
10650 attr_elem_t *elem;
10652 rows_filter_type = UNKNOWN_FILTER;
10653 while (node_selector != NULL)
10655 char *unparsed;
10657 rows_selector = node_selector->data;
10659 parse_selectors(rows_selector,
10660 &filter_type,
10661 &unparsed,
10662 &inc_row_interval_list,
10663 &inc_row_regex_list,
10664 &exc_row_interval_list,
10665 &exc_row_regex_list,
10666 &al_row_interval_list,
10667 &al_row_regex_list,
10668 &ar_row_interval_list,
10669 &ar_row_regex_list,
10670 &ac_row_interval_list,
10671 &ac_row_regex_list,
10672 &at_row_interval_list,
10673 &at_row_regex_list,
10674 &default_alignment,
10675 &win,
10676 &misc,
10677 &term);
10679 if (*unparsed != '\0')
10681 fprintf(stderr,
10682 "Bad or not allowed row selection argument. "
10683 "Unparsed part: %s\n",
10684 unparsed);
10686 exit(EXIT_FAILURE);
10689 if (rows_filter_type == UNKNOWN_FILTER)
10690 rows_filter_type = filter_type;
10692 node_selector = node_selector->next;
10694 free(unparsed);
10696 optimize_an_interval_list(inc_row_interval_list);
10697 optimize_an_interval_list(exc_row_interval_list);
10698 optimize_an_interval_list(al_row_interval_list);
10699 optimize_an_interval_list(ar_row_interval_list);
10700 optimize_an_interval_list(ac_row_interval_list);
10702 if (at_row_interval_list != NULL)
10704 node = at_row_interval_list->head;
10705 while (node)
10707 elem = node->data;
10708 optimize_an_interval_list(elem->list);
10709 node = node->next;
10714 /* Parse the column selection string if any. */
10715 /* """"""""""""""""""""""""""""""""""""""""" */
10716 if (cols_selector_list != NULL)
10718 filters_t filter_type, cols_filter_type;
10719 interval_t *data;
10720 ll_node_t *node;
10721 ll_node_t *node_selector = cols_selector_list->head;
10722 attr_elem_t *elem;
10724 cols_filter = xmalloc(limits.cols);
10726 cols_filter_type = UNKNOWN_FILTER;
10727 while (node_selector != NULL)
10729 char *unparsed;
10731 cols_selector = node_selector->data;
10733 parse_selectors(cols_selector,
10734 &filter_type,
10735 &unparsed,
10736 &inc_col_interval_list,
10737 &inc_col_regex_list,
10738 &exc_col_interval_list,
10739 &exc_col_regex_list,
10740 &al_col_interval_list,
10741 &al_col_regex_list,
10742 &ar_col_interval_list,
10743 &ar_col_regex_list,
10744 &ac_col_interval_list,
10745 &ac_col_regex_list,
10746 &at_col_interval_list,
10747 &at_col_regex_list,
10748 &default_alignment,
10749 &win,
10750 &misc,
10751 &term);
10753 if (*unparsed != '\0')
10755 fprintf(stderr,
10756 "Bad or not allowed column selection argument. "
10757 "Unparsed part: %s\n",
10758 unparsed);
10760 exit(EXIT_FAILURE);
10763 optimize_an_interval_list(inc_col_interval_list);
10764 optimize_an_interval_list(exc_col_interval_list);
10766 if (cols_filter_type == UNKNOWN_FILTER)
10767 cols_filter_type = filter_type;
10769 /* Only initialize the whole set when -C is encountered for the */
10770 /* first time. */
10771 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10772 if (cols_filter_type == INCLUDE_FILTER)
10773 memset(cols_filter, SOFT_EXCLUDE_MARK, limits.cols);
10774 else
10775 memset(cols_filter, SOFT_INCLUDE_MARK, limits.cols);
10777 /* Process the explicitly included columns intervals. */
10778 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
10779 if (inc_col_interval_list != NULL)
10780 for (node = inc_col_interval_list->head; node; node = node->next)
10782 data = node->data;
10784 if (data->low >= limits.cols)
10785 break;
10787 if (data->high >= limits.cols)
10788 data->high = limits.cols - 1;
10790 memset(cols_filter + data->low,
10791 INCLUDE_MARK,
10792 data->high - data->low + 1);
10795 /* Process the explicitly excluded column intervals. */
10796 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
10797 if (exc_col_interval_list != NULL)
10798 for (node = exc_col_interval_list->head; node; node = node->next)
10800 data = node->data;
10802 if (data->low >= limits.cols)
10803 break;
10805 if (data->high >= limits.cols)
10806 data->high = limits.cols - 1;
10808 memset(cols_filter + data->low,
10809 EXCLUDE_MARK,
10810 data->high - data->low + 1);
10813 node_selector = node_selector->next;
10815 free(unparsed);
10818 optimize_an_interval_list(al_col_interval_list);
10819 optimize_an_interval_list(ar_col_interval_list);
10820 optimize_an_interval_list(ac_col_interval_list);
10822 if (at_col_interval_list != NULL)
10824 node = at_col_interval_list->head;
10825 while (node)
10827 elem = node->data;
10828 optimize_an_interval_list(elem->list);
10829 node = node->next;
10834 /* parse the alignment selector list if any. */
10835 /* """"""""""""""""""""""""""""""""""""""""" */
10836 if (aligns_selector_list != NULL)
10838 ll_node_t *node_selector = aligns_selector_list->head;
10840 while (node_selector != NULL)
10842 char *unparsed;
10844 aligns_selector = node_selector->data;
10846 parse_al_selectors(aligns_selector,
10847 &unparsed,
10848 &al_word_regex_list,
10849 &ar_word_regex_list,
10850 &ac_word_regex_list,
10851 &default_alignment,
10852 &misc);
10854 if (*unparsed != '\0')
10856 fprintf(stderr,
10857 "Bad alignment selection argument. Unparsed part: %s\n",
10858 unparsed);
10860 exit(EXIT_FAILURE);
10863 free(unparsed);
10865 node_selector = node_selector->next;
10869 /* Initialize the useful values needed to walk through */
10870 /* the rows intervals. */
10871 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
10872 if (rows_filter_type == INCLUDE_FILTER)
10873 row_def_selectable = SOFT_EXCLUDE_MARK;
10874 else if (rows_filter_type == EXCLUDE_FILTER)
10875 row_def_selectable = SOFT_INCLUDE_MARK;
10876 else
10878 if (pattern_def_include == 0)
10879 row_def_selectable = SOFT_EXCLUDE_MARK;
10880 else
10881 row_def_selectable = SOFT_INCLUDE_MARK;
10884 /* Set the head of the interval list. */
10885 /* """""""""""""""""""""""""""""""""" */
10886 if (inc_row_interval_list)
10887 inc_interval_node = inc_row_interval_list->head;
10888 else
10889 inc_interval_node = NULL;
10891 if (exc_row_interval_list)
10892 exc_interval_node = exc_row_interval_list->head;
10893 else
10894 exc_interval_node = NULL;
10896 /* And get the first interval.*/
10897 /* """""""""""""""""""""""""" */
10898 if (inc_interval_node)
10899 inc_interval = (interval_t *)inc_interval_node->data;
10900 else
10901 inc_interval = NULL;
10903 if (exc_interval_node)
10904 exc_interval = (interval_t *)exc_interval_node->data;
10905 else
10906 exc_interval = NULL;
10908 /* First pass: */
10909 /* Get and process the input stream words. */
10910 /* In this pass, the different actions will occur: */
10911 /* - A new word is read from stdin */
10912 /* - A new SOL and or EOL is possibly set */
10913 /* - A special level is possibly affected to the word just read */
10914 /* - The -R is taken into account */
10915 /* - The first part of the -C option is done */
10916 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10917 while ((word = get_word(input_file,
10918 word_delims_list,
10919 line_delims_list,
10920 zapped_glyphs_list,
10921 utf8_buffer,
10922 &is_last,
10923 &toggles,
10924 &langinfo,
10925 &win,
10926 &limits,
10927 &misc))
10928 != NULL)
10930 int selectable;
10931 int is_first = 0;
10932 unsigned char special_level;
10933 int row_inc_matched = 0;
10934 ll_node_t *node;
10936 if (*word == '\0')
10937 continue;
10939 /* Early substitution. */
10940 /* """"""""""""""""""" */
10941 if (early_sed_list != NULL)
10943 char *tmp;
10945 node = early_sed_list->head;
10947 while (node != NULL)
10949 tmp = xstrdup(word);
10950 if (replace(word, (sed_t *)(node->data)))
10953 free(word);
10954 word = xstrdup(word_buffer);
10956 if (((sed_t *)(node->data))->stop)
10957 break;
10960 *word_buffer = '\0';
10961 node = node->next;
10962 free(tmp);
10966 if (*word == '\0')
10967 continue;
10969 /* Manipulates the is_last flag word indicator to make this word */
10970 /* the first or last one of the current line in column/line/tab mode. */
10971 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
10972 if (win.col_mode || win.line_mode || win.tab_mode)
10974 if (first_word_pattern != NULL
10975 && regexec(&first_word_re, word, (int)0, NULL, 0) == 0)
10976 is_first = 1;
10978 if (last_word_pattern != NULL && !is_last
10979 && regexec(&last_word_re, word, (int)0, NULL, 0) == 0)
10980 is_last = 1;
10983 /* Check if the word is special. */
10984 /* """"""""""""""""""""""""""""" */
10985 special_level = 0;
10986 for (index = 0; index < 9; index++)
10988 if (special_pattern[index] != NULL
10989 && regexec(&special_re[index], word, (int)0, NULL, 0) == 0)
10991 special_level = index + 1;
10992 break;
10996 /* Default selectable state. */
10997 /* """"""""""""""""""""""""" */
10998 selectable = SOFT_INCLUDE_MARK;
11000 /* For each new line check if the line is in the current */
11001 /* interval or if we need to get the next interval if any .*/
11002 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
11003 if (rows_selector)
11005 if (count > 0 && word_a[count - 1].is_last)
11007 /* We are in a new line, reset the flag indicating that we are on */
11008 /* a line selected by a regular expression and the flag saying */
11009 /* that the whole line has been excluded. */
11010 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11011 line_selected_by_regex = 0;
11012 line_excluded = 0;
11014 /* And also reset the flag telling that the row has been explicitly */
11015 /* removed from the selectable list of words. */
11016 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11017 row_inc_matched = 0;
11019 /* Increment the line counter used to see if we are an include or */
11020 /* exclude set of lines. */
11021 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11022 line_count++;
11024 /* Look if we need to use the next interval of the list. */
11025 /* ''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11026 if (inc_interval_node && line_count > inc_interval->high)
11028 inc_interval_node = inc_interval_node->next;
11029 if (inc_interval_node)
11030 inc_interval = (interval_t *)inc_interval_node->data;
11033 if (exc_interval_node && line_count > exc_interval->high)
11035 exc_interval_node = exc_interval_node->next;
11036 if (exc_interval_node)
11037 exc_interval = (interval_t *)exc_interval_node->data;
11041 /* Look if the line is in an excluded or included line. */
11042 /* The included line intervals are only checked if the word didn't */
11043 /* belong to an excluded line interval before. */
11044 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11045 if (exc_interval && line_count >= exc_interval->low
11046 && line_count <= exc_interval->high)
11047 selectable = EXCLUDE_MARK;
11049 if (selectable != EXCLUDE_MARK && inc_interval
11050 && line_count >= inc_interval->low
11051 && line_count <= inc_interval->high)
11053 selectable = INCLUDE_MARK;
11055 /* As the raw has been explicitly selected, record that so than */
11056 /* we can distinguish that from the implicit selection. */
11057 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11058 row_inc_matched = 1;
11062 /* Check if the all the words in the current row must be included or */
11063 /* excluded from the selectable set of words. */
11064 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11065 if (selectable != EXCLUDE_MARK)
11067 /* Look in the excluded list of regular expressions. */
11068 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
11069 if (exc_row_regex_list != NULL)
11071 regex_t *row_re;
11073 ll_node_t *row_regex_node = exc_row_regex_list->head;
11075 while (row_regex_node != NULL)
11077 row_re = row_regex_node->data;
11078 if (regexec(row_re, word, (int)0, NULL, 0) == 0)
11080 long c = count - 1;
11082 /* Mark all the next words of the line as excluded. */
11083 /* '''''''''''''''''''''''''''''''''''''''''''''''' */
11084 line_selected_by_regex = 1;
11085 line_excluded = 1;
11087 /* Mark all the previous words of the line as excluded. */
11088 /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
11089 while (c >= 0 && !word_a[c].is_last)
11091 word_a[c].is_selectable = EXCLUDE_MARK;
11092 c--;
11095 /* Mark the current word as not excluded. */
11096 /* '''''''''''''''''''''''''''''''''''''' */
11097 selectable = EXCLUDE_MARK;
11099 /* No need to continue as the line is already marked as */
11100 /* excluded. */
11101 /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
11102 break;
11105 row_regex_node = row_regex_node->next;
11109 /* If the line has not yet been excluded and the list of explicitly */
11110 /* include regular expressions is not empty then give them a chance. */
11111 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11112 if (selectable != EXCLUDE_MARK && inc_row_regex_list != NULL)
11114 regex_t *row_re;
11116 ll_node_t *row_regex_node = inc_row_regex_list->head;
11118 while (row_regex_node != NULL)
11120 row_re = row_regex_node->data;
11121 if (regexec(row_re, word, (int)0, NULL, 0) == 0)
11123 long c = count - 1;
11125 while (c >= 0 && !word_a[c].is_last)
11127 /* Do not include an already excluded word. */
11128 /* """""""""""""""""""""""""""""""""""""""" */
11129 if (word_a[c].is_selectable)
11130 word_a[c].is_selectable = INCLUDE_MARK;
11132 c--;
11135 /* Mark all the next words of the line as included. */
11136 /* '''''''''''''''''''''''''''''''''''''''''''''''' */
11137 line_selected_by_regex = 1;
11139 /* Mark all the previous words of the line as included. */
11140 /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
11141 selectable = INCLUDE_MARK;
11144 row_regex_node = row_regex_node->next;
11149 /* If the line contains a word that matched a regex which determines */
11150 /* the inclusion of exclusion of this line, then use the regex */
11151 /* selection flag to determine the inclusion/exclusion of the future */
11152 /* words in the line. */
11153 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11154 if (line_excluded)
11155 selectable = EXCLUDE_MARK;
11156 else
11158 if (line_selected_by_regex)
11159 selectable = (row_def_selectable == EXCLUDE_MARK) ? SOFT_EXCLUDE_MARK
11160 : INCLUDE_MARK;
11162 /* Check if the current word is matching an include or exclude */
11163 /* pattern */
11164 /* Only do it if if hasn't be explicitly deselected before. */
11165 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11166 if (selectable != EXCLUDE_MARK)
11168 /* Check if the word will be excluded in the list of selectable */
11169 /* words or not. */
11170 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11171 if (exclude_pattern != NULL
11172 && regexec(&exclude_re, word, (int)0, NULL, 0) == 0)
11173 selectable = EXCLUDE_MARK;
11175 if (selectable != 0 && !line_selected_by_regex)
11177 /* Check if the word will be included in the list of selectable */
11178 /* words or not. */
11179 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11180 if (include_pattern != NULL)
11182 if (regexec(&include_re, word, (int)0, NULL, 0) == 0)
11183 selectable = INCLUDE_MARK;
11184 else if (!row_inc_matched)
11185 selectable = row_def_selectable;
11187 else if (rows_selector && !row_inc_matched)
11188 selectable = row_def_selectable;
11193 if (win.col_mode)
11195 /* In column mode we must manage the allocation space for some */
11196 /* column's related data structures and check if some limits ave not */
11197 /* been reached. */
11198 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11200 if (is_first)
11201 col_index = 1;
11202 else
11204 col_index++;
11206 if (col_index > cols_number)
11208 /* Check the limits. */
11209 /* ''''''''''''''''' */
11210 if (col_index == limits.cols)
11212 fprintf(stderr,
11213 "The number of columns has reached the limit of %ld.\n",
11214 limits.cols);
11216 exit(EXIT_FAILURE);
11219 cols_number++;
11221 /* Look if we need to enlarge the arrays indexed by the */
11222 /* number of columns. */
11223 /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
11224 if (cols_number % COLSCHUNK == 0)
11226 long ci; /* column index */
11228 col_real_max_size = xrealloc(col_real_max_size,
11229 (cols_number + COLSCHUNK)
11230 * sizeof(long));
11232 col_max_size = xrealloc(col_max_size,
11233 (cols_number + COLSCHUNK) * sizeof(long));
11235 /* Initialize the max size for the new columns. */
11236 /* '''''''''''''''''''''''''''''''''''''''''''' */
11237 for (ci = 0; ci < COLSCHUNK; ci++)
11239 col_real_max_size[cols_number + ci] = 0;
11240 col_max_size[cols_number + ci] = 0;
11246 /* We must now check if the word matches a RE that */
11247 /* exclude the whole column. */
11248 /* """"""""""""""""""""""""""""""""""""""""""""""" */
11249 if (cols_selector != NULL)
11251 long ci; /* column index. */
11253 regex_t *col_re;
11255 if (cols_filter[col_index - 1] == EXCLUDE_MARK)
11256 selectable = EXCLUDE_MARK;
11257 else
11259 if (exc_col_regex_list != NULL)
11261 /* Some columns must be excluded by regex. */
11262 /* ''''''''''''''''''''''''''''''''''''''' */
11263 ll_node_t *col_regex_node = exc_col_regex_list->head;
11265 while (col_regex_node != NULL)
11267 col_re = col_regex_node->data;
11269 if (regexec(col_re, word, (int)0, NULL, 0) == 0)
11271 cols_filter[col_index - 1] = EXCLUDE_MARK;
11272 selectable = EXCLUDE_MARK;
11274 /* Mark non selectable the items above in the column. */
11275 /* '''''''''''''''''''''''''''''''''''''''''''''''''' */
11276 ci = 0;
11277 for (wi = 0; wi < count; wi++)
11279 if (ci == col_index - 1)
11280 word_a[wi].is_selectable = EXCLUDE_MARK;
11282 if (word_a[wi].is_last)
11283 ci = 0;
11284 else
11285 ci++;
11287 break;
11290 col_regex_node = col_regex_node->next;
11294 if (inc_col_regex_list != NULL)
11296 /* Some columns must be included by regex. */
11297 /* ''''''''''''''''''''''''''''''''''''''' */
11298 ll_node_t *col_regex_node = inc_col_regex_list->head;
11300 while (col_regex_node != NULL)
11302 col_re = col_regex_node->data;
11304 if (regexec(col_re, word, (int)0, NULL, 0) == 0)
11306 cols_filter[col_index - 1] = INCLUDE_MARK;
11307 break;
11310 col_regex_node = col_regex_node->next;
11317 /* Initialize the alignment information of each column to be 'left'. */
11318 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11319 col_attrs = xmalloc(cols_number * sizeof(attrib_t *));
11320 for (long ci = 0; ci < cols_number; ci++)
11321 col_attrs[ci] = NULL;
11323 /* Store some known values in the current word's structure. */
11324 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11325 word_a[count].start = word_a[count].end = 0;
11327 word_a[count].str = word;
11328 word_a[count].is_selectable = selectable;
11330 word_a[count].special_level = special_level;
11331 word_a[count].is_matching = 0;
11332 word_a[count].is_numbered = 0;
11333 word_a[count].tag_order = 0;
11334 word_a[count].tag_id = 0;
11335 word_a[count].iattr = NULL;
11337 if (win.col_mode || win.line_mode || win.tab_mode)
11339 /* Set the last word in line indicator when in */
11340 /* column/line/tab mode. */
11341 /* ''''''''''''''''''''''''''''''''''''''''''' */
11342 if (is_first && count > 0)
11343 word_a[count - 1].is_last = 1;
11344 word_a[count].is_last = is_last;
11345 if (is_last)
11346 col_index = 0;
11348 else
11349 word_a[count].is_last = 0;
11351 /* One more word... */
11352 /* """""""""""""""" */
11353 if (count + 1 >= limits.words)
11355 fprintf(stderr,
11356 "The number of read words has reached the limit of %ld.\n",
11357 limits.words);
11359 exit(EXIT_FAILURE);
11362 count++;
11364 if (count % WORDSCHUNK == 0)
11365 word_a = xrealloc(word_a, (count + WORDSCHUNK) * sizeof(word_t));
11368 /* Early exit if there is no input or if no word is selected. */
11369 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11370 if (count == 0)
11371 exit(EXIT_FAILURE);
11373 /* Ignore SIGINT */
11374 /* """"""""""""" */
11375 sigaddset(&sigs, SIGINT);
11376 sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
11378 /* The last word is always the last of its line. */
11379 /* """"""""""""""""""""""""""""""""""""""""""""" */
11380 if (win.col_mode || win.line_mode || win.tab_mode)
11381 word_a[count - 1].is_last = 1;
11383 /* Second pass to modify the word according to all/include/exclude */
11384 /* regular expressions and the columns settings set in the previous pass. */
11385 /* This must be done separately because in the first pass, some word */
11386 /* could have been marked as excluded before the currently processed word */
11387 /* (second part of the -C option) */
11388 /* In this pass the following actions will also be done: */
11389 /* - Finish the work on columns. */
11390 /* - Possibly modify the word according to -S/-I/-E arguments */
11391 /* - Replace unprintable characters in the word by mnemonics */
11392 /* - Remember the max size of the words/columns/tabs */
11393 /* - Insert the word in a TST (Ternary Search Tree) index to facilitate */
11394 /* word search (each node pf the TST will contain an UTF-8 glyph). */
11395 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11396 col_index = 0;
11397 for (wi = 0; wi < count; wi++)
11399 char *unaltered_word;
11400 long size;
11401 long word_len;
11402 wchar_t *tmpw;
11403 word_t *word;
11404 long s;
11405 long len;
11406 char *expanded_word;
11407 long i;
11409 /* If the column section argument is set, then adjust the final */
11410 /* selectable attribute according to the already set words and column */
11411 /* selectable flag contents. */
11412 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11413 if (cols_selector_list != NULL)
11415 if (cols_filter[col_index] == EXCLUDE_MARK)
11416 word_a[wi].is_selectable = EXCLUDE_MARK;
11417 else if (word_a[wi].is_selectable != EXCLUDE_MARK)
11419 switch (cols_filter[col_index])
11421 case INCLUDE_MARK:
11422 word_a[wi].is_selectable = INCLUDE_MARK;
11423 break;
11425 case SOFT_EXCLUDE_MARK:
11426 if (word_a[wi].is_selectable == SOFT_EXCLUDE_MARK
11427 || word_a[wi].is_selectable == SOFT_INCLUDE_MARK)
11428 word_a[wi].is_selectable = EXCLUDE_MARK;
11429 else
11430 word_a[wi].is_selectable = INCLUDE_MARK;
11431 break;
11433 case SOFT_INCLUDE_MARK:
11434 if (word_a[wi].is_selectable == SOFT_EXCLUDE_MARK)
11435 word_a[wi].is_selectable = EXCLUDE_MARK;
11436 else
11437 word_a[wi].is_selectable = INCLUDE_MARK;
11438 break;
11443 word = &word_a[wi];
11445 /* Make sure that daccess.length >= daccess.size */
11446 /* with DA_TYPE_POS. */
11447 /* """"""""""""""""""""""""""""""""""""""""""""" */
11448 if (daccess.mode != DA_TYPE_NONE)
11450 if ((daccess.mode & DA_TYPE_POS) && daccess.size > 0
11451 && daccess.size > daccess.length)
11452 daccess.length = daccess.size;
11454 /* Auto determination of the length of the selector */
11455 /* with DA_TYPE_AUTO. */
11456 /* """""""""""""""""""""""""""""""""""""""""""""""" */
11457 if ((daccess.mode & DA_TYPE_AUTO) && daccess.length == -2)
11459 long n = count + daccess_index - 1;
11461 daccess.length = 0;
11463 while (n)
11465 n /= 10;
11466 daccess.length++;
11470 /* Set the full length of the prefix in case of numbering. */
11471 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
11472 if (daccess.length > 0)
11473 daccess.flength = 3 + daccess.length;
11475 if (word->is_selectable != EXCLUDE_MARK
11476 && word->is_selectable != SOFT_EXCLUDE_MARK)
11478 char *selector;
11479 char *tmp = xmalloc(strlen(word->str) + 4 + daccess.length);
11480 long *word_pos = xmalloc(sizeof(long));
11481 int may_number;
11483 if (!my_isempty(word->str))
11485 *word_pos = wi;
11487 tmp[0] = ' ';
11489 /* Check if the word is eligible to the numbering process. */
11490 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
11491 if (daccess_up == NULL && daccess_np == NULL)
11493 if (daccess.mode & DA_TYPE_POS)
11494 may_number = 1;
11495 else
11496 may_number = 0;
11498 else
11500 if (daccess_up != NULL
11501 && !!regexec(&daccess_up_re, word->str, (int)0, NULL, 0) == 0)
11502 may_number = 0;
11503 else
11505 if (daccess_np != NULL
11506 && !!regexec(&daccess_np_re, word->str, (int)0, NULL, 0) == 0)
11507 may_number = 1;
11508 else
11509 may_number = daccess.def_number;
11513 /* It is... */
11514 /* """""""" */
11515 if (may_number)
11517 if ((daccess.mode & DA_TYPE_POS) && !word->is_numbered
11518 && daccess.size > 0
11519 && (daccess.offset + daccess.size + daccess.ignore)
11520 <= utf8_strlen(word->str))
11522 unsigned selector_value; /* numerical value of the *
11523 | extracted selector. */
11524 long selector_offset; /* offset in byte to the selector *
11525 | to extract. */
11526 char *ptr; /* points just after the selector *
11527 | to extract. */
11528 long plus_offset; /* points to the first occurrence *
11529 | of a number in word->str after *
11530 | the offset given. */
11532 selector_offset = utf8_offset(word->str, daccess.offset);
11534 if (daccess.plus)
11536 plus_offset = strcspn(word->str + selector_offset,
11537 "0123456789");
11539 if (plus_offset + daccess.size + daccess.ignore
11540 <= strlen(word->str))
11541 selector_offset += plus_offset;
11544 ptr = word->str + selector_offset;
11545 selector = xstrndup(ptr, daccess.size);
11547 /* read the embedded number and, if correct, format */
11548 /* it according to daccess.alignment. */
11549 /* """""""""""""""""""""""""""""""""""""""""""""""" */
11550 if (sscanf(selector, "%u", &selector_value) == 1)
11552 sprintf(selector, "%u", selector_value);
11554 sprintf(tmp + 1,
11555 "%*u",
11556 daccess.alignment == 'l' ? -daccess.length
11557 : daccess.length,
11558 selector_value);
11560 /* Overwrite the end of the word to erase */
11561 /* the selector. */
11562 /* """""""""""""""""""""""""""""""""""""" */
11563 my_strcpy(ptr,
11564 ptr + daccess.size
11565 + utf8_offset(ptr + daccess.size, daccess.ignore));
11567 /* Modify the word according to the 'h' directive */
11568 /* of -D. */
11569 /* """""""""""""""""""""""""""""""""""""""""""""" */
11570 if (daccess.head == 'c')
11571 /* h:c is present cut the leading characters */
11572 /* before the selector. */
11573 /* ''''''''''''''''''''''''''''''''''''''''' */
11574 memmove(word->str, ptr, strlen(ptr) + 1);
11575 else if (daccess.head == 't')
11577 /* h:t is present trim the leading characters */
11578 /* before the selector if they are ' ' or '\t'. */
11579 /* '''''''''''''''''''''''''''''''''''''''''''' */
11580 char *p = word->str;
11582 while (p != ptr && (*p == ' ' || *p == '\t'))
11583 p++;
11585 if (p == ptr)
11586 memmove(word->str, ptr, strlen(ptr) + 1);
11589 ltrim(selector, " ");
11590 rtrim(selector, " ", 0);
11592 tst_daccess = tst_insert(tst_daccess,
11593 utf8_strtowcs(selector),
11594 word_pos);
11596 if (daccess.follow == 'y')
11597 daccess_index = selector_value + 1;
11599 word->is_numbered = 1;
11601 free(selector);
11604 /* Try to number this word if it is still non numbered and */
11605 /* the -N/-U option is given. */
11606 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
11607 if (!word->is_numbered && (daccess.mode & DA_TYPE_AUTO))
11609 sprintf(tmp + 1,
11610 "%*ld",
11611 daccess.alignment == 'l' ? -daccess.length
11612 : daccess.length,
11613 daccess_index);
11615 selector = xstrdup(tmp + 1);
11616 ltrim(selector, " ");
11617 rtrim(selector, " ", 0);
11619 /* Insert it in the tst tree containing the selector's */
11620 /* digits. */
11621 /* ''''''''''''''''''''''''''''''''''''''''''''''''''' */
11622 tst_daccess = tst_insert(tst_daccess,
11623 utf8_strtowcs(selector),
11624 word_pos);
11625 daccess_index++;
11627 free(selector);
11629 word->is_numbered = 1;
11633 /* Fill the space taken by the numbering by space if the word */
11634 /* is not numbered. */
11635 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11636 if (daccess.length > 0 && !word->is_numbered)
11638 for (i = 0; i < daccess.flength; i++)
11639 tmp[i] = ' ';
11642 /* Make sure that the 2 character after this placeholder */
11643 /* are initialized. */
11644 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
11645 if (daccess.length > 0)
11647 tmp[1 + daccess.length] = ' ';
11648 tmp[2 + daccess.length] = ' ';
11651 else if (daccess.length > 0)
11653 /* Make sure that the prefix of empty word is blank */
11654 /* as they may be display in column mode. */
11655 /* """""""""""""""""""""""""""""""""""""""""""""""" */
11656 for (i = 0; i < daccess.flength; i++)
11657 tmp[i] = ' ';
11660 if (daccess.length > 0)
11662 my_strcpy(tmp + daccess.flength, word->str);
11663 free(word->str);
11664 word->str = tmp;
11666 else
11667 free(tmp);
11669 else
11671 /* Should we also add space at the beginning of excluded words? */
11672 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11673 if (daccess.padding == 'a')
11675 char *tmp = xmalloc(strlen(word->str) + 4 + daccess.length);
11676 for (i = 0; i < daccess.flength; i++)
11677 tmp[i] = ' ';
11678 my_strcpy(tmp + daccess.flength, word->str);
11679 free(word->str);
11680 word->str = tmp;
11684 else
11686 daccess.size = 0;
11687 daccess.length = 0;
11690 /* Save the original word. */
11691 /* """"""""""""""""""""""" */
11692 unaltered_word = xstrdup(word->str);
11694 /* Possibly modify the word according to -S/-I/-E arguments. */
11695 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11697 ll_node_t *node = NULL;
11698 char *tmp;
11700 /* Manage the -S case. */
11701 /* """"""""""""""""""" */
11702 if (sed_list != NULL)
11704 node = sed_list->head;
11706 while (node != NULL)
11708 tmp = xstrndup(word->str, daccess.flength);
11709 if (replace(word->str + daccess.flength, (sed_t *)(node->data)))
11712 free(word->str);
11713 memmove(word_buffer + daccess.flength,
11714 word_buffer,
11715 strlen(word_buffer) + 1);
11716 memmove(word_buffer, tmp, daccess.flength);
11718 word->str = xstrdup(word_buffer);
11720 if (((sed_t *)(node->data))->stop)
11721 break;
11724 *word_buffer = '\0';
11725 node = node->next;
11726 free(tmp);
11729 else
11731 /* Manage the -I/-E case. */
11732 /* """""""""""""""""""""" */
11733 if ((word->is_selectable == INCLUDE_MARK
11734 || word->is_selectable == SOFT_INCLUDE_MARK)
11735 && include_sed_list != NULL)
11736 node = include_sed_list->head;
11737 else if ((word->is_selectable == EXCLUDE_MARK
11738 || word->is_selectable == SOFT_EXCLUDE_MARK)
11739 && exclude_sed_list != NULL)
11740 node = exclude_sed_list->head;
11741 else
11742 node = NULL;
11744 *word_buffer = '\0';
11746 while (node != NULL)
11748 tmp = xstrndup(word->str, daccess.flength);
11749 if (replace(word->str + daccess.flength, (sed_t *)(node->data)))
11752 free(word->str);
11753 memmove(word_buffer + daccess.flength,
11754 word_buffer,
11755 strlen(word_buffer) + 1);
11756 memmove(word_buffer, tmp, daccess.flength);
11758 word->str = xstrdup(word_buffer);
11760 if (((sed_t *)(node->data))->stop)
11761 break;
11763 *word_buffer = '\0';
11764 node = node->next;
11765 free(tmp);
11770 /* A substitution leading to an empty word is invalid in column mode. */
11771 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11772 if (win.col_mode)
11774 long len;
11776 if (daccess.padding == 'a')
11777 len = daccess.flength;
11778 else
11779 len = 0;
11781 if (*(word->str + len) == '\0')
11782 exit(EXIT_FAILURE);
11785 /* Alter the word just read be replacing special chars by their */
11786 /* escaped equivalents. */
11787 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11788 word_len = strlen(word->str);
11790 expanded_word = xmalloc(5 * word_len + 1);
11791 len = expand(word->str, expanded_word, &langinfo, &toggles, &misc);
11793 /* Update it if needed. */
11794 /* '''''''''''''''''''' */
11795 if (strcmp(expanded_word, word->str) != 0)
11797 word_len = len;
11798 free(word->str);
11799 word->str = xstrdup(expanded_word);
11802 free(expanded_word);
11804 if (win.col_mode)
11806 /* Update the max values of col_real_max_size[col_index] */
11807 /* and col_max_size[col_index]. */
11808 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
11809 if ((s = (long)word_len) > col_real_max_size[col_index])
11811 col_real_max_size[col_index] = s;
11813 /* Also update the real max size of all columns seen. */
11814 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
11815 if (s > cols_real_max_size)
11816 cols_real_max_size = s;
11819 s = (long)mbstowcs(NULL, word->str, 0);
11820 s = wcswidth((tmpw = utf8_strtowcs(word->str)), s);
11821 free(tmpw);
11823 if (s > col_max_size[col_index])
11825 col_max_size[col_index] = s;
11827 /* Also update the max size of all columns seen. */
11828 /* """"""""""""""""""""""""""""""""""""""""""""" */
11829 if (s > cols_max_size)
11830 cols_max_size = s;
11832 /* Update the size of the longest expanded word. */
11833 /* """"""""""""""""""""""""""""""""""""""""""""" */
11834 word_real_max_size = cols_real_max_size + 1;
11836 else if (win.tab_mode)
11838 /* Store the new max number of bytes in a word */
11839 /* and update the size of the longest expanded word. */
11840 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
11841 if ((long)word_len > tab_real_max_size)
11842 word_real_max_size = tab_real_max_size = (long)word_len;
11844 /* Store the new max word width. */
11845 /* """"""""""""""""""""""""""""" */
11846 size = (long)mbstowcs(NULL, word->str, 0);
11848 if ((size = wcswidth((tmpw = utf8_strtowcs(word->str)), size))
11849 > tab_max_size)
11850 tab_max_size = size;
11852 free(tmpw);
11854 else if (word_real_max_size < word_len)
11855 /* Update the size of the longest expanded word. */
11856 /* """"""""""""""""""""""""""""""""""""""""""""" */
11857 word_real_max_size = word_len;
11859 /* When the visual only flag is set, we keep the unaltered word so */
11860 /* that it can be restored even if its visual and searchable */
11861 /* representation may have been altered by the previous code. */
11862 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11864 /* Record the length of the word in bytes. This information will be */
11865 /* used if the -k option (keep spaces ) is not set. */
11866 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11867 word->len = strlen(word->str);
11869 /* Save the non modified word in .orig if it has been altered. */
11870 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11871 if ((strcmp(word->str, unaltered_word) != 0)
11872 && ((word->is_selectable && include_visual_only)
11873 || (!word->is_selectable && exclude_visual_only)))
11875 word->orig = unaltered_word;
11877 else
11879 word->orig = NULL;
11880 free(unaltered_word);
11883 if (win.col_mode)
11885 if (word_a[wi].is_last)
11886 col_index = 0;
11887 else
11888 col_index++;
11892 /* Set the minimum width of a column (-w and -t or -c option). */
11893 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11894 if (win.wide)
11896 if (win.tab_mode)
11898 if (win.max_cols > 0)
11899 min_size = (term.ncolumns - 2) / win.max_cols - 1;
11901 if (min_size < tab_max_size)
11902 min_size = tab_max_size;
11904 word_real_max_size = min_size + tab_real_max_size - tab_max_size;
11906 else /* Column mode. */
11908 min_size = (term.ncolumns - 2) / cols_number;
11909 if (min_size < cols_max_size)
11910 min_size = cols_max_size;
11912 word_real_max_size = cols_real_max_size;
11916 /* Third (compress) pass: remove all empty word and words containing */
11917 /* only spaces when not in column mode. */
11918 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11919 if (!win.col_mode)
11921 long offset;
11923 offset = 0;
11924 for (wi = 0; wi < count - offset; wi++)
11926 long len;
11928 while (wi + offset < count)
11930 if (daccess.padding == 'a' || word_a[wi + offset].is_numbered)
11931 len = daccess.flength;
11932 else
11933 len = 0;
11935 if (!my_isempty(word_a[wi + offset].str + len))
11936 break;
11938 /* Keep non selectable empty words to allow special effects. */
11939 /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
11940 if (word_a[wi + offset].is_selectable == SOFT_EXCLUDE_MARK
11941 || word_a[wi + offset].is_selectable == EXCLUDE_MARK)
11942 break;
11944 offset++;
11947 if (offset > 0)
11948 word_a[wi] = word_a[wi + offset];
11950 count -= offset;
11953 if (count == 0)
11954 exit(EXIT_FAILURE);
11956 /* Allocate the space for the satellites arrays. */
11957 /* """"""""""""""""""""""""""""""""""""""""""""" */
11958 line_nb_of_word_a = xmalloc(count * sizeof(long));
11959 first_word_in_line_a = xmalloc(count * sizeof(long));
11960 shift_right_sym_pos_a = xmalloc(count * sizeof(long));
11962 /* Fourth pass: */
11963 /* When in column or tabulating mode, we need to adjust the length of */
11964 /* all the words by adding the right number of spaces so that they will */
11965 /* be aligned correctly. In column mode the size of each column is */
11966 /* variable; in tabulate mode it is constant. */
11967 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11968 if (win.col_mode)
11970 char *temp;
11972 /* Sets all columns to the same size when -w and -c are both set. */
11973 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
11974 if (win.wide)
11975 for (col_index = 0; col_index < cols_number; col_index++)
11977 col_max_size[col_index] = cols_max_size;
11978 col_real_max_size[col_index] = cols_real_max_size;
11981 /* Total space taken by all the columns plus the gutter. */
11982 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
11983 win.max_width = 0;
11984 win.real_max_width = 0;
11985 for (col_index = 0; col_index < cols_number; col_index++)
11987 if (win.max_width + col_max_size[col_index] + 1 <= term.ncolumns - 2)
11988 win.max_width += col_max_size[col_index] + 1;
11990 win.real_max_width += col_max_size[col_index] + 1;
11993 col_index = 0;
11994 for (wi = 0; wi < count; wi++)
11996 long s1, s2;
11997 long word_width;
11998 wchar_t *w;
11999 regex_t re;
12000 ll_node_t *node;
12001 interval_t *interval;
12003 /* Does this word matched by one of the alignment regex? */
12004 /* If yes, then add the current column number to the list of the */
12005 /* corresponding column_alignment list */
12006 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12007 ll_t *regex_list_a[3] = { al_col_regex_list,
12008 ar_col_regex_list,
12009 ac_col_regex_list };
12010 ll_t **interval_list_a[3] = { &al_col_interval_list,
12011 &ar_col_interval_list,
12012 &ac_col_interval_list };
12014 for (int i = 0; i < 3; i++) /* For each regex list. */
12016 if (regex_list_a[i] == NULL)
12017 continue;
12019 node = regex_list_a[i]->head;
12020 while (node) /* For each RE in the list. */
12022 re = *(regex_t *)(node->data);
12023 if (regexec(&re, word_a[wi].str + daccess.flength, (int)0, NULL, 0)
12024 == 0)
12026 int already_aligned = 0;
12028 /* We have a match. */
12029 /* '''''''''''''''' */
12030 interval = xmalloc(sizeof(interval_t));
12031 interval->low = interval->high = col_index;
12033 /* Look if the column has already been inserted in another */
12034 /* interval list. */
12035 /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
12036 for (int j = 0; j < 3; j++)
12038 interval_t inter;
12039 ll_node_t *n;
12041 /* Quick continuation. */
12042 /* ''''''''''''''''''' */
12043 if (i == j || *interval_list_a[j] == NULL)
12044 continue;
12046 n = (*interval_list_a[j])->head;
12047 while (n)
12049 inter = *(interval_t *)(n->data);
12050 if (col_index >= inter.low && col_index <= inter.high)
12052 already_aligned = 1; /* This column is already aligned. */
12053 break;
12055 n = n->next;
12059 if (!already_aligned)
12061 /* Append a new interval containing the current column number */
12062 /* in the interval list matching the regex list. */
12063 /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */
12064 if (*interval_list_a[i] == NULL)
12065 *interval_list_a[i] = ll_new();
12067 ll_append(*interval_list_a[i], interval);
12070 node = node->next;
12074 /* Process regex based attributes and fill the attribute columns */
12075 /* list accordingly. */
12076 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12077 if (at_col_regex_list != NULL)
12079 attrib_t *attr;
12080 attr_elem_t *elem;
12081 ll_node_t *regex_node;
12082 ll_node_t *interval_node;
12084 node = at_col_regex_list->head;
12085 while (node)
12087 elem = node->data;
12088 attr = elem->attr;
12090 regex_node = elem->list->head;
12091 while (regex_node)
12093 re = *(regex_t *)(regex_node->data);
12094 if (regexec(&re, word_a[wi].str + daccess.flength, (int)0, NULL, 0)
12095 == 0)
12097 /* We have a match. */
12098 /* '''''''''''''''' */
12099 attr_elem_t *new_elem;
12101 if (col_attrs[col_index] != NULL)
12102 break;
12104 new_elem = xmalloc(sizeof(attr_elem_t));
12105 new_elem->attr = attr;
12107 interval = xmalloc(sizeof(interval_t));
12108 interval->low = interval->high = col_index;
12110 col_attrs[col_index] = attr;
12112 if (at_col_interval_list == NULL)
12113 at_col_interval_list = ll_new();
12115 if ((interval_node = ll_find(at_col_interval_list,
12116 elem,
12117 attr_elem_cmp))
12118 != NULL)
12120 ll_append(((attr_elem_t *)(interval_node->data))->list,
12121 interval);
12123 else
12125 new_elem->list = ll_new();
12126 ll_append(new_elem->list, interval);
12127 ll_append(at_col_interval_list, new_elem);
12130 regex_node = regex_node->next;
12132 node = node->next;
12136 s1 = (long)strlen(word_a[wi].str);
12137 word_width = mbstowcs(NULL, word_a[wi].str, 0);
12138 s2 = wcswidth((w = utf8_strtowcs(word_a[wi].str)), word_width);
12139 free(w);
12141 /* Use the 0x05 character as a placeholder to preserve the internal */
12142 /* spaces of the word if there are any. This value has been chosen */
12143 /* because it cannot be part of a UTF-8 sequence and is very unlikely */
12144 /* to be part of a normal word from the input stream. */
12145 /* This placeholder will be removed during the alignment phase. */
12146 /*"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12147 temp = xcalloc(1, col_real_max_size[col_index] + s1 - s2 + 1);
12148 memset(temp, 0x05, col_max_size[col_index] + s1 - s2);
12149 memcpy(temp, word_a[wi].str, s1);
12150 temp[col_real_max_size[col_index] + s1 - s2] = '\0';
12151 free(word_a[wi].str);
12152 word_a[wi].str = temp;
12154 if (word_a[wi].is_last)
12155 col_index = 0;
12156 else
12157 col_index++;
12159 optimize_an_interval_list(al_col_interval_list);
12160 optimize_an_interval_list(ar_col_interval_list);
12161 optimize_an_interval_list(ac_col_interval_list);
12163 else if (win.tab_mode)
12165 char *temp;
12167 if (tab_max_size < min_size)
12169 tab_max_size = min_size;
12170 if (tab_max_size > tab_real_max_size)
12171 tab_real_max_size = tab_max_size;
12174 for (wi = 0; wi < count; wi++)
12176 long s1, s2;
12177 long word_width;
12178 wchar_t *w;
12180 s1 = (long)strlen(word_a[wi].str);
12181 word_width = mbstowcs(NULL, word_a[wi].str, 0);
12182 s2 = wcswidth((w = utf8_strtowcs(word_a[wi].str)), word_width);
12183 free(w);
12184 temp = xcalloc(1, tab_real_max_size + s1 - s2 + 1);
12185 memset(temp, ' ', tab_max_size + s1 - s2);
12186 memcpy(temp, word_a[wi].str, s1);
12187 temp[tab_real_max_size + s1 - s2] = '\0';
12188 free(word_a[wi].str);
12189 word_a[wi].str = temp;
12193 /* Fifth pass: transforms the remaining SOFT_EXCLUDE_MARKs with */
12194 /* EXCLUDE_MARKs. */
12195 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12196 for (wi = 0; wi < count; wi++)
12198 long *data;
12199 wchar_t *w;
12200 ll_t *list;
12202 if (word_a[wi].is_selectable == SOFT_EXCLUDE_MARK)
12203 word_a[wi].is_selectable = EXCLUDE_MARK;
12205 /* If the word is selectable insert it in the TST tree */
12206 /* with its associated index in the input stream. */
12207 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
12208 if (word_a[wi].is_selectable)
12210 data = xmalloc(sizeof(long));
12211 *data = wi;
12213 /* Create a wide characters string from the word screen */
12214 /* representation to be able to store in in the TST. */
12215 /* Note that the direct access selector,if any, is not */
12216 /* stored. */
12217 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
12218 if (word_a[wi].is_numbered)
12219 w = utf8_strtowcs(word_a[wi].str + daccess.flength);
12220 else
12221 w = utf8_strtowcs(word_a[wi].str);
12223 /* If we didn't already encounter this word, then create a new */
12224 /* entry in the TST for it and store its index in its list. */
12225 /* Otherwise, add its index in its index list. */
12226 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12227 if (tst_word && (list = tst_search(tst_word, w)) != NULL)
12228 ll_append(list, data);
12229 else
12231 list = ll_new();
12232 ll_append(list, data);
12233 tst_word = tst_insert(tst_word, w, list);
12235 free(w);
12239 /* Sixth pass: Apply alignment rules in column modes. */
12240 /* The column alignments, based on regular expressions, have already */
12241 /* been processed in the fourth pass which converted this information */
12242 /* by adding new ranges to the three lists of column ranges. */
12243 /* */
12244 /* It remains to interpret these lists of column intervals and the lists */
12245 /* of intervals and regular expressions for the rows. */
12246 /* */
12247 /* To do this, a working table (aligned_a) is created and reset for */
12248 /* each row to store the statistics of alignments already processed, */
12249 /* taking into account the previous result and the order in which the */
12250 /* row and column alignment requests were made. */
12251 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12252 if (win.col_mode)
12254 long row_index = 0;
12255 interval_t interval;
12257 alignment_t alignment; /* Future value of the word alignment. */
12258 alignment_t word_alignment; /* Specific word alignment. */
12259 alignment_t row_alignment; /* current row word alignments. */
12261 char *str, *tstr;
12263 col_index = 0;
12265 ll_node_t *cur_word_node_a[3];
12267 ll_t *col_interval_list_a[3] = { al_col_interval_list,
12268 ar_col_interval_list,
12269 ac_col_interval_list };
12271 ll_node_t *cur_col_node_a[3] = {
12272 al_col_interval_list != NULL ? al_col_interval_list->head : NULL,
12273 ar_col_interval_list != NULL ? ar_col_interval_list->head : NULL,
12274 ac_col_interval_list != NULL ? ac_col_interval_list->head : NULL
12277 ll_node_t *cur_row_interval_node_a[3] = {
12278 al_row_interval_list != NULL ? al_row_interval_list->head : NULL,
12279 ar_row_interval_list != NULL ? ar_row_interval_list->head : NULL,
12280 ac_row_interval_list != NULL ? ac_row_interval_list->head : NULL
12283 ll_node_t *cur_row_regex_node_a[3];
12285 alignment_t const alignment_a[3] = { AL_LEFT, AL_RIGHT, AL_CENTERED };
12287 char *aligned_a; /* Array of indicators used to remember that a word *
12288 | has been aligned with -al in a row. */
12290 /* Initialize each chars of aligned_a with No ('N'). */
12291 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
12292 aligned_a = xmalloc(cols_number);
12294 for (int i = 0; i < cols_number; i++)
12295 aligned_a[i] = 'N';
12297 row_alignment = AL_NONE;
12299 for (wi = 0; wi < count; wi++)
12301 word_alignment = AL_NONE;
12303 if (row_alignment != AL_NONE)
12304 alignment = row_alignment;
12305 else
12306 alignment = default_alignment;
12308 str = xstrdup(word_a[wi].str + daccess.flength);
12309 tstr = xstrdup(str);
12311 rtrim(tstr, "\x05", 0);
12312 ltrim(tstr, "\x05");
12314 /* First check if the current word is matched by a word specified */
12315 /* regular expression set by the * alignment option. */
12316 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12318 cur_word_node_a[0] = al_word_regex_list != NULL ? al_word_regex_list->head
12319 : NULL;
12321 cur_word_node_a[1] = ar_word_regex_list != NULL ? ar_word_regex_list->head
12322 : NULL;
12324 cur_word_node_a[2] = ac_word_regex_list != NULL ? ac_word_regex_list->head
12325 : NULL;
12327 cur_row_regex_node_a[0] = al_row_regex_list != NULL
12328 ? al_row_regex_list->head
12329 : NULL;
12331 cur_row_regex_node_a[1] = ar_row_regex_list != NULL
12332 ? ar_row_regex_list->head
12333 : NULL;
12335 cur_row_regex_node_a[2] = ac_row_regex_list != NULL
12336 ? ac_row_regex_list->head
12337 : NULL;
12339 /* Process the word alignment regex lists. */
12340 /* """"""""""""""""""""""""""""""""""""""" */
12341 for (int i = 0; i < 3; i++)
12343 while (word_alignment == AL_NONE && cur_word_node_a[i] != NULL)
12345 regex_t *re;
12347 re = (regex_t *)(cur_word_node_a[i]->data);
12349 if (regexec(re, tstr, (int)0, NULL, 0) == 0)
12351 word_alignment = alignment_a[i];
12353 /* Mark this word as aligned in this row. */
12354 /* """""""""""""""""""""""""""""""""""""" */
12355 aligned_a[col_index] = 'Y';
12357 break; /* Early exit of the while loop. */
12360 cur_word_node_a[i] = cur_word_node_a[i]->next;
12364 /* Process the alignment lists for columns and increment */
12365 /* their current pointers when needed. */
12366 /* The current interval pointer cur_col_node_a[i] is only */
12367 /* modified to point to the next one of the list is the */
12368 /* current column is greater than the max column in the */
12369 /* pointed interval. */
12370 /* An already aligned column will not be realigned. */
12371 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12372 for (int i = 0; i < 3; i++)
12374 if (cur_col_node_a[i] != NULL)
12376 interval = *(interval_t *)(cur_col_node_a[i]->data);
12377 if (aligned_a[col_index] == 'N' && col_index >= interval.low
12378 && col_index <= interval.high)
12380 /* Tell that the word is already aligned when column */
12381 /* alignments have precedence over row alignments. */
12382 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
12383 if (toggles.cols_first)
12384 aligned_a[col_index] = 'Y';
12386 alignment = alignment_a[i];
12388 else if (col_index > interval.high)
12389 cur_col_node_a[i] = cur_col_node_a[i]->next;
12391 if (word_a[wi].is_last && col_interval_list_a[i] != NULL)
12392 cur_col_node_a[i] = col_interval_list_a[i]->head;
12395 /* Process row interval and regex alignment lists. */
12396 /* """"""""""""""""""""""""""""""""""""""""""""""" */
12397 for (int i = 0; i < 3; i++)
12399 /* Lists of intervals. */
12400 /* ''''''''''''''''''' */
12401 if (cur_row_interval_node_a[i] != NULL)
12403 interval = *(interval_t *)(cur_row_interval_node_a[i]->data);
12404 if (row_alignment == AL_NONE && row_index >= interval.low
12405 && row_index <= interval.high)
12407 row_alignment = alignment_a[i];
12408 if (aligned_a[col_index] == 'N')
12409 alignment = alignment_a[i];
12411 else if (row_index > interval.high)
12412 cur_row_interval_node_a[i] = cur_row_interval_node_a[i]->next;
12415 /* Lists of regular expression. */
12416 /* '''''''''''''''''''''''''''' */
12417 if (cur_row_regex_node_a[i] != NULL)
12419 while (cur_row_regex_node_a[i] != NULL)
12421 regex_t *re;
12423 re = (regex_t *)(cur_row_regex_node_a[i]->data);
12425 if (row_alignment == AL_NONE
12426 && regexec(re, tstr, (int)0, NULL, 0) == 0)
12428 row_alignment = alignment_a[i];
12429 if (aligned_a[col_index] == 'N')
12430 alignment = alignment_a[i];
12432 /* Also aligns the previous words in the line to the */
12433 /* right, left or centre. */
12434 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
12435 long j = wi;
12436 while (j > 0 && !word_a[j - 1].is_last)
12438 j--;
12440 /* Do not realign words already aligned with -al */
12441 /* of if already aligned using a column RE when */
12442 /* column alignments have precedence. */
12443 /* ''''''''''''''''''''''''''''''''''''''''''''' */
12444 if (aligned_a[col_index - (wi - j)] == 'N')
12445 align_word(&word_a[j], alignment_a[i], daccess.flength, 0x05);
12448 break; /* Early exit of the while loop. */
12450 else if (toggles.rows_first && row_alignment != AL_NONE)
12451 alignment = row_alignment;
12453 cur_row_regex_node_a[i] = cur_row_regex_node_a[i]->next;
12458 /* Force a word alignment if it is set for this word. */
12459 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
12460 if (word_alignment != AL_NONE)
12461 alignment = word_alignment;
12463 /* Do the alignment. */
12464 /* """"""""""""""""" */
12465 align_word(&word_a[wi], alignment, daccess.flength, 0x05);
12467 /* Adjusts things before a row change. */
12468 /* """"""""""""""""""""""""""""""""""" */
12469 if (word_a[wi].is_last || wi == count - 1) /* About to start a new row? */
12471 row_index++;
12473 /* Re-initialize the array with No ('N'). */
12474 /* as this is a new row. */
12475 /* """""""""""""""""""""""""""""""""""""" */
12476 for (int i = 0; i < cols_number; i++)
12478 aligned_a[i] = 'N';
12480 /* We can restore the spaces which are not part of the word */
12481 /* now that the row is fully processed. */
12482 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12483 if (wi - i >= 0)
12484 strrep(word_a[wi - i].str + daccess.flength, 0x05, ' ');
12487 col_index = 0; /* Restart the columns counter. */
12488 row_alignment = AL_NONE;
12490 else
12491 col_index++;
12493 free(str);
12494 free(tstr);
12498 /* Seventh pass: sets default attributes. */
12499 /* """""""""""""""""""""""""""""""""""""" */
12500 if (win.col_mode)
12502 if (at_row_interval_list != NULL || at_col_interval_list != NULL
12503 || at_row_regex_list != NULL || at_col_regex_list != NULL)
12505 long row_index = 0;
12506 interval_t *interval;
12507 attrib_t *attr;
12508 ll_t *list;
12509 attr_elem_t *attr_elem;
12510 ll_node_t *attr_elem_node;
12511 ll_node_t *node;
12512 regex_t re;
12514 col_index = 0;
12516 for (wi = 0; wi < count; wi++)
12518 if (word_a[wi].iattr != NULL)
12519 continue;
12521 if (at_row_interval_list != NULL)
12523 attr_elem_node = at_row_interval_list->head;
12524 while (attr_elem_node != NULL)
12526 attr_elem = attr_elem_node->data;
12527 attr = attr_elem->attr;
12528 list = attr_elem->list; /* Cannot be null by construction. */
12529 node = list->head;
12531 while (node)
12533 interval = node->data;
12534 if (row_index >= interval->low && row_index <= interval->high)
12536 word_a[wi].iattr = attr;
12537 goto early_row_exit;
12539 node = node->next;
12541 attr_elem_node = attr_elem_node->next;
12544 early_row_exit:
12546 if (at_col_interval_list != NULL)
12548 attr_elem_node = at_col_interval_list->head;
12549 while (attr_elem_node != NULL)
12551 attr_elem = attr_elem_node->data;
12552 attr = attr_elem->attr;
12553 list = attr_elem->list; /* Cannot be null by construction. */
12554 node = list->head;
12555 while (node)
12557 interval = node->data;
12558 if (col_index >= interval->low && col_index <= interval->high)
12560 if (word_a[wi].iattr == NULL || toggles.cols_first)
12562 if (col_attrs[col_index] == NULL)
12564 word_a[wi].iattr = attr;
12565 col_attrs[col_index] = attr;
12567 else
12568 word_a[wi].iattr = col_attrs[col_index];
12570 goto early_col_exit;
12573 node = node->next;
12575 attr_elem_node = attr_elem_node->next;
12578 early_col_exit:
12580 if (at_row_regex_list != NULL)
12582 attr_elem_node = at_row_regex_list->head;
12583 while (attr_elem_node != NULL)
12585 attr_elem = attr_elem_node->data;
12586 attr = attr_elem->attr;
12587 list = attr_elem->list; /* Cannot be null by construction. */
12588 node = list->head;
12589 while (node)
12591 re = *(regex_t *)(node->data);
12592 if (regexec(&re,
12593 word_a[wi].str + daccess.flength,
12594 (int)0,
12595 NULL,
12597 == 0)
12599 col_index = 0;
12600 while (wi > 0 && !word_a[wi - 1].is_last)
12601 wi--;
12603 while (wi < count)
12605 if (toggles.cols_first && col_attrs[col_index] != NULL)
12606 word_a[wi].iattr = col_attrs[col_index];
12607 else
12608 word_a[wi].iattr = attr;
12610 col_index++;
12612 if (word_a[wi].is_last)
12613 break;
12614 wi++;
12617 node = node->next;
12619 attr_elem_node = attr_elem_node->next;
12623 /* Adjusts things before a row change. */
12624 /* """"""""""""""""""""""""""""""""""" */
12625 if (word_a[wi].is_last
12626 || wi == count - 1) /* About to start a new row? */
12628 row_index++;
12630 col_index = 0; /* Restart the columns counter. */
12632 else
12633 col_index++;
12638 /* The word after the last one is set to NULL. */
12639 /* """"""""""""""""""""""""""""""""""""""""""" */
12640 word_a[count].str = NULL;
12642 /* We can now allocate the space for our tmp_word work variable */
12643 /* augmented by the number of tabulation columns. This is not */
12644 /* optimal but the loss is tiny and we have the guarantee that */
12645 /* enough place will be allocated. */
12646 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12647 tmp_word = xcalloc(1, word_real_max_size + tab_max_size + 1);
12649 search_data.utf8_off_a = xmalloc(word_real_max_size * sizeof(long));
12650 search_data.utf8_len_a = xmalloc(word_real_max_size * sizeof(long));
12652 win.start = 0; /* index of the first element in the *
12653 | words array to be displayed. */
12655 /* We can now build the first metadata. */
12656 /* """""""""""""""""""""""""""""""""""" */
12657 last_line = build_metadata(&term, count, &win);
12659 /* Adjust the max number of lines in the windows */
12660 /* if it has not be explicitly set. */
12661 /* """"""""""""""""""""""""""""""""""""""""""""" */
12662 if (line_nb_of_word_a[count - 1] < win.max_lines)
12663 win.max_lines = win.asked_max_lines = line_nb_of_word_a[count - 1] + 1;
12665 /* Index of the selected element in the array words */
12666 /* The string can be: */
12667 /* "last" The string "last" put the cursor on the last word */
12668 /* n a number put the cursor on the word n */
12669 /* /pref /+a regexp put the cursor on the first */
12670 /* word matching the prefix "pref" */
12671 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12673 for (wi = 0; wi < count; wi++)
12675 long len;
12677 if (daccess.padding == 'a' || word_a[wi].is_numbered)
12678 len = daccess.flength;
12679 else
12680 len = 0;
12682 word_a[wi].bitmap = xcalloc(1, (word_a[wi].mb - len) / CHAR_BIT + 1);
12685 /* Find the first selectable word (if any) in the input stream. */
12686 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12687 first_selectable = 0;
12688 while (first_selectable < count && !word_a[first_selectable].is_selectable)
12689 first_selectable++;
12691 /* If not found, abort. */
12692 /* """""""""""""""""""" */
12693 if (first_selectable == count)
12695 fprintf(stderr, "No selectable word found.\n");
12697 exit(EXIT_FAILURE);
12700 /* Else find the last selectable word in the input stream. */
12701 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
12702 last_selectable = count - 1;
12703 while (last_selectable > 0 && !word_a[last_selectable].is_selectable)
12704 last_selectable--;
12706 if (pre_selection_index == NULL)
12707 /* Option -s was not used. */
12708 /* """"""""""""""""""""""" */
12709 current = first_selectable;
12710 else if (*pre_selection_index == '/')
12712 /* A regular expression is expected. */
12713 /* """"""""""""""""""""""""""""""""" */
12714 regex_t re;
12716 if (regcomp(&re, pre_selection_index + 1, REG_EXTENDED | REG_NOSUB) != 0)
12718 fprintf(stderr, "%s: Invalid regular expression.\n", pre_selection_index);
12720 exit(EXIT_FAILURE);
12722 else
12724 int found = 0;
12725 char *word;
12727 for (index = first_selectable; index <= last_selectable; index++)
12729 if (!word_a[index].is_selectable)
12730 continue;
12732 if (word_a[index].orig != NULL)
12733 word = word_a[index].orig;
12734 else
12735 word = word_a[index].str;
12737 if (regexec(&re, word, (int)0, NULL, 0) == 0)
12739 if (!found)
12741 found = 1;
12742 current = index;
12745 /* Insert the index in the search array. */
12746 /* """"""""""""""""""""""""""""""""""""" */
12747 insert_sorted_index(&matching_words_a,
12748 &matching_words_a_size,
12749 &matches_count,
12750 index);
12754 if (!found)
12755 current = first_selectable;
12758 else if (*pre_selection_index == '=') /* exact search. */
12760 /* An exact match is expected. */
12761 /* """"""""""""""""""""""""""" */
12762 wchar_t *w;
12764 ll_t *list;
12765 ll_node_t *node;
12767 list = tst_search(tst_word, w = utf8_strtowcs(pre_selection_index + 1));
12768 if (list != NULL)
12770 node = list->head;
12771 current = *(long *)(node->data);
12773 while (node)
12775 /* Insert the index in the search array. */
12776 /* """"""""""""""""""""""""""""""""""""" */
12777 insert_sorted_index(&matching_words_a,
12778 &matching_words_a_size,
12779 &matches_count,
12780 *(long *)(node->data));
12781 node = node->next;
12784 else
12785 current = first_selectable;
12787 free(w);
12789 else if (*pre_selection_index != '\0')
12791 /* A prefix string or an index is expected. */
12792 /* """""""""""""""""""""""""""""""""""""""" */
12793 int len;
12794 char *ptr = pre_selection_index;
12796 if (*ptr == '#')
12798 /* An index is expected. */
12799 /* """"""""""""""""""""" */
12800 ptr++;
12802 if (sscanf(ptr, "%ld%n", &current, &len) == 1 && len == (int)strlen(ptr))
12804 /* We got an index (numeric value). */
12805 /* """""""""""""""""""""""""""""""" */
12806 if (current < 0)
12807 current = first_selectable;
12809 if (current >= count)
12810 current = count - 1;
12812 if (!word_a[current].is_selectable)
12814 if (current > last_selectable)
12815 current = last_selectable;
12816 else if (current < first_selectable)
12817 current = first_selectable;
12818 else
12819 while (current > first_selectable && !word_a[current].is_selectable)
12820 current--;
12823 else if (*ptr == '\0' || strcmp(ptr, "last") == 0)
12824 /* We got a special index (empty or last). */
12825 /* """"""""""""""""""""""""""""""""""""""" */
12826 current = last_selectable;
12827 else
12829 fprintf(stderr, "%s: Invalid index.\n", ptr);
12831 exit(EXIT_FAILURE);
12834 else
12836 int found = 0;
12838 /* A prefix is expected. */
12839 /* """"""""""""""""""""" */
12840 for (new_current = first_selectable; new_current < count; new_current++)
12843 if (strprefix(word_a[new_current].str, ptr)
12844 && word_a[new_current].is_selectable)
12846 if (!found)
12848 current = new_current;
12849 found = 1;
12852 /* Insert the index in the search array. */
12853 /* """"""""""""""""""""""""""""""""""""" */
12854 insert_sorted_index(&matching_words_a,
12855 &matching_words_a_size,
12856 &matches_count,
12857 new_current);
12861 if (!found)
12862 current = first_selectable;
12865 else
12866 current = first_selectable;
12868 /* We now need to adjust the 'start'/'end' fields of the */
12869 /* structure 'win'. */
12870 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
12871 set_win_start_end(&win, current, last_line);
12873 /* Re-associates /dev/tty with stdin and stdout. */
12874 /* """"""""""""""""""""""""""""""""""""""""""""" */
12875 if (freopen("/dev/tty", "r", stdin) == NULL)
12877 fprintf(stderr, "Unable to associate /dev/tty with stdin.\n");
12878 exit(EXIT_FAILURE);
12881 old_fd1 = dup(1);
12882 old_stdout = fdopen(old_fd1, "w");
12884 setbuf(old_stdout, NULL);
12886 if (freopen("/dev/tty", "w", stdout) == NULL)
12888 fprintf(stderr, "Unable to associate /dev/tty with stdout.\n");
12889 exit(EXIT_FAILURE);
12892 setvbuf(stdout, NULL, _IONBF, 0);
12894 /* Make sure smenu runs in foreground. */
12895 /* """"""""""""""""""""""""""""""""""" */
12896 if (!is_in_foreground_process_group())
12898 fprintf(stderr, "smenu cannot be launched in background.\n");
12899 exit(EXIT_FAILURE);
12902 /* Set the characteristics of the terminal. */
12903 /* """""""""""""""""""""""""""""""""""""""" */
12904 setup_term(fileno(stdin), &old_in_attrs, &new_in_attrs);
12906 if (!get_cursor_position(&row, &col))
12908 fprintf(stderr,
12909 "The terminal does not have the capability to report "
12910 "the cursor position.\n");
12911 restore_term(fileno(stdin), &old_in_attrs);
12913 exit(EXIT_FAILURE);
12916 /* Initialize the search buffer with tab_real_max_size+1 NULs */
12917 /* It will never be reallocated, only cleared. */
12918 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12919 search_data.buf = xcalloc(1, word_real_max_size + 1 - daccess.flength);
12921 /* Hide the cursor. */
12922 /* """""""""""""""" */
12923 (void)tputs(TPARM1(cursor_invisible), 1, outch);
12925 /* Force the display to start at a beginning of line. */
12926 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
12927 get_cursor_position(&term.curs_line, &term.curs_column);
12928 if (term.curs_column > 1)
12929 puts("");
12931 /* Display the words window and its title for the first time. */
12932 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12933 disp_message(message_lines_list,
12934 message_max_width,
12935 message_max_len,
12936 &term,
12937 &win,
12938 &langinfo);
12940 /* Before displaying the word windows for the first time when in */
12941 /* column or line mode, we need to ensure that the word under the */
12942 /* cursor will be visible by setting the number of the first column */
12943 /* to be displayed. */
12944 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12945 if (win.col_mode || win.line_mode)
12947 long pos;
12948 long len;
12950 len = term.ncolumns - 3;
12952 /* Adjust win.first_column if the cursor is not visible. */
12953 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
12954 pos = first_word_in_line_a[line_nb_of_word_a[current]];
12956 while (word_a[current].end - word_a[pos].start >= len)
12957 pos++;
12959 win.first_column = word_a[pos].start;
12962 /* Save the initial cursor line and column, here only the line is */
12963 /* interesting us. This will tell us if we are in need to compensate */
12964 /* a terminal automatic scrolling. */
12965 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12966 get_cursor_position(&term.curs_line, &term.curs_column);
12968 nl = disp_lines(&win,
12969 &toggles,
12970 current,
12971 count,
12972 search_mode,
12973 &search_data,
12974 &term,
12975 last_line,
12976 tmp_word,
12977 &langinfo);
12979 /* Assert the presence of an early display of the horizontal bar. */
12980 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12981 if (win.has_hbar)
12982 win.hbar_displayed = 1;
12984 /* Determine the number of lines to move the cursor up if the window */
12985 /* display needed a terminal scrolling. */
12986 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
12987 if (nl + term.curs_line - 1 > term.nlines)
12988 line_offset = term.curs_line + nl - term.nlines;
12989 else
12990 line_offset = 0;
12992 /* Set the cursor to the first line of the window. */
12993 /* """"""""""""""""""""""""""""""""""""""""""""""" */
12995 long i; /* generic index in this block. */
12997 for (i = 1; i < line_offset; i++)
12998 (void)tputs(TPARM1(cursor_up), 1, outch);
13001 /* Enable the reporting of the mouse events. */
13002 /* """"""""""""""""""""""""""""""""""""""""" */
13003 if (!toggles.no_mouse)
13005 if (term.has_kmous && strncmp(tigetstr("kmous"), "\x1b[<", 3) == 0)
13006 mouse_trk_on = "\x1b[?1005l\x1b[?1000;1006h\x1b[?2004h";
13007 else
13008 mouse_trk_on = "\x1b[?1005l\x1b[?1000;1015;1006h\x1b[?2004h";
13010 mouse_trk_off = "\x1b[?1000;1015;1006l\x1b[?2004l";
13012 printf("%s", mouse_trk_on);
13015 /* Save again the cursor current line and column positions so that we */
13016 /* will be able to put the terminal cursor back here. */
13017 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13018 get_cursor_position(&term.curs_line, &term.curs_column);
13020 /* Arm the periodic timer. */
13021 /* """"""""""""""""""""""" */
13022 periodic_itv.it_value.tv_sec = 0;
13023 periodic_itv.it_value.tv_usec = TCK;
13024 periodic_itv.it_interval.tv_sec = 0;
13025 periodic_itv.it_interval.tv_usec = TCK;
13026 setitimer(ITIMER_REAL, &periodic_itv, NULL);
13028 /* Signal management. */
13029 /* """""""""""""""""" */
13030 void sig_handler(int s);
13032 sa.sa_handler = sig_handler;
13033 sa.sa_flags = 0;
13034 sigemptyset(&sa.sa_mask);
13035 sigaction(SIGWINCH, &sa, NULL);
13036 sigaction(SIGALRM, &sa, NULL);
13037 sigaction(SIGTERM, &sa, NULL);
13038 sigaction(SIGHUP, &sa, NULL);
13039 sigaction(SIGSEGV, &sa, NULL);
13040 sigaction(SIGPIPE, &sa, NULL);
13042 /* Main loop. */
13043 /* """""""""" */
13044 while (1)
13046 int sc = 0; /* scancode */
13048 /* Manage the case of a broken pipe by exiting failure and restoring */
13049 /* the terminal and the cursor. */
13050 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13051 if (got_sigpipe)
13053 (void)tputs(TPARM1(carriage_return), 1, outch);
13054 (void)tputs(TPARM1(cursor_normal), 1, outch);
13055 restore_term(fileno(stdin), &old_in_attrs);
13057 exit(128 + SIGPIPE);
13060 /* Manage a segmentation fault by exiting with failure and restoring */
13061 /* the terminal and the cursor. */
13062 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13063 if (got_sigsegv)
13065 fputs_safe("SIGSEGV received!\n", stderr);
13066 (void)tputs(TPARM1(carriage_return), 1, outch);
13067 (void)tputs(TPARM1(cursor_normal), 1, outch);
13068 restore_term(fileno(stdin), &old_in_attrs);
13070 exit(128 + SIGSEGV);
13073 /* Manage the hangup and termination signal by exiting with failure */
13074 /* and restoring the terminal and the cursor. */
13075 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13076 if (got_sigterm || got_sighup)
13078 fputs_safe("Interrupted!\n", stderr);
13079 (void)tputs(TPARM1(carriage_return), 1, outch);
13080 (void)tputs(TPARM1(cursor_normal), 1, outch);
13081 restore_term(fileno(stdin), &old_in_attrs);
13083 if (got_sigterm)
13084 exit(128 + SIGTERM);
13085 else
13086 exit(128 + SIGHUP);
13089 /* If this alarm is triggered, then gracefully exit. */
13090 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
13091 if (got_forgotten_alrm)
13093 (void)tputs(TPARM1(carriage_return), 1, outch);
13094 (void)tputs(TPARM1(cursor_normal), 1, outch);
13095 restore_term(fileno(stdin), &old_in_attrs);
13097 exit(EXIT_SUCCESS);
13100 /* If this alarm is triggered, then redisplay the window */
13101 /* to remove the help message and disable this timer. */
13102 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
13103 if (got_help_alrm)
13105 got_help_alrm = 0;
13107 /* Calculate the new metadata and draw the window again. */
13108 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
13109 last_line = build_metadata(&term, count, &win);
13111 help_mode = 0;
13112 nl = disp_lines(&win,
13113 &toggles,
13114 current,
13115 count,
13116 search_mode,
13117 &search_data,
13118 &term,
13119 last_line,
13120 tmp_word,
13121 &langinfo);
13124 /* Reset the direct access selector if the direct access alarm rang. */
13125 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13126 if (got_daccess_alrm)
13128 got_daccess_alrm = 0;
13129 memset(daccess_stack, '\0', 6);
13130 daccess_stack_head = 0;
13132 daccess_timer = timers.direct_access;
13135 if (got_search_alrm)
13137 got_search_alrm = 0;
13139 /* Calculate the new metadata and draw the window again. */
13140 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
13141 last_line = build_metadata(&term, count, &win);
13143 search_mode = NONE;
13145 nl = disp_lines(&win,
13146 &toggles,
13147 current,
13148 count,
13149 search_mode,
13150 &search_data,
13151 &term,
13152 last_line,
13153 tmp_word,
13154 &langinfo);
13157 if (got_winch)
13159 got_winch = 0;
13160 got_winch_alrm = 0;
13161 winch_timer = timers.winch; /* Rearm the refresh timer. */
13164 /* If the timeout is set then decrement its remaining value */
13165 /* Upon expiration of this alarm, we trigger a content update */
13166 /* of the window. */
13167 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13168 if (got_winch_alrm)
13170 long i; /* generic index in this block */
13171 int nlines, ncolumns;
13172 int line, column;
13173 int original_message_lines;
13175 got_winch_alrm = 0; /* Reset the flag signaling the need for a refresh. */
13176 winch_timer = -1; /* Disarm the timer used for this refresh. */
13178 if (message_lines_list != NULL && message_lines_list->len > 0)
13179 original_message_lines = message_lines_list->len + 1;
13180 else
13181 original_message_lines = 0;
13183 get_terminal_size(&nlines, &ncolumns, &term);
13185 /* Update term with the new number of lines and columns */
13186 /* of the real terminal. */
13187 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
13188 term.nlines = nlines;
13189 term.ncolumns = ncolumns;
13191 /* Reset the number of lines if the terminal has enough lines. */
13192 /* message_lines_list->len+1 is used here instead of */
13193 /* win.message_lines because win.message_lines may have been */
13194 /* altered by a previous scrolling and not yet recalculated. */
13195 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13196 if (term.nlines <= original_message_lines)
13198 win.message_lines = term.nlines - 1;
13199 win.max_lines = 1;
13201 else
13203 win.message_lines = original_message_lines;
13205 if (win.max_lines < term.nlines - win.message_lines)
13207 if (win.asked_max_lines == 0)
13208 win.max_lines = term.nlines - win.message_lines;
13209 else
13211 if (win.asked_max_lines > term.nlines - win.message_lines)
13212 win.max_lines = term.nlines - win.message_lines;
13213 else
13214 win.max_lines = win.asked_max_lines;
13217 else
13218 win.max_lines = term.nlines - win.message_lines;
13221 /* Erase the visible part of the displayed window. */
13222 /* """"""""""""""""""""""""""""""""""""""""""""""" */
13223 for (i = 0; i < win.message_lines; i++)
13225 (void)tputs(TPARM1(clr_bol), 1, outch);
13226 (void)tputs(TPARM1(clr_eol), 1, outch);
13227 (void)tputs(TPARM1(cursor_up), 1, outch);
13230 (void)tputs(TPARM1(clr_bol), 1, outch);
13231 (void)tputs(TPARM1(clr_eol), 1, outch);
13232 (void)tputs(TPARM1(save_cursor), 1, outch);
13234 get_cursor_position(&line, &column);
13236 for (i = 1; i < nl + win.message_lines; i++)
13238 if (line + i >= nlines)
13239 break; /* We have reached the last terminal line. */
13241 (void)tputs(TPARM1(cursor_down), 1, outch);
13242 (void)tputs(TPARM1(clr_bol), 1, outch);
13243 (void)tputs(TPARM1(clr_eol), 1, outch);
13245 (void)tputs(TPARM1(restore_cursor), 1, outch);
13247 /* Get new cursor position. */
13248 /* """""""""""""""""""""""" */
13249 get_cursor_position(&term.curs_line, &term.curs_column);
13251 /* Calculate the new metadata and draw the window again. */
13252 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
13253 last_line = build_metadata(&term, count, &win);
13255 if (win.col_mode || win.line_mode)
13257 long pos;
13258 long len;
13260 len = term.ncolumns - 3;
13262 /* Adjust win.first_column if the cursor is no more visible. */
13263 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13264 pos = first_word_in_line_a[line_nb_of_word_a[current]];
13266 while (word_a[current].end - word_a[pos].start >= len)
13267 pos++;
13269 win.first_column = word_a[pos].start;
13272 /* Keep a line available for an eventual horizontal scroll bar. */
13273 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13274 if (win.col_mode || win.line_mode)
13276 if (win.asked_max_lines == 0
13277 || win.max_lines > term.nlines - win.message_lines)
13279 win.max_lines = term.nlines - win.message_lines - 1;
13281 (void)tputs(TPARM3(cursor_address, win.max_lines, 0), 1, outch);
13282 (void)tputs(TPARM1(clr_eol), 1, outch);
13283 (void)tputs(TPARM3(cursor_address, 0, 0), 1, outch);
13286 else if (win.asked_max_lines > term.nlines - win.message_lines)
13287 win.max_lines = term.nlines - win.message_lines - 1;
13288 else
13289 win.max_lines = win.asked_max_lines;
13292 disp_message(message_lines_list,
13293 message_max_width,
13294 message_max_len,
13295 &term,
13296 &win,
13297 &langinfo);
13299 nl = disp_lines(&win,
13300 &toggles,
13301 current,
13302 count,
13303 search_mode,
13304 &search_data,
13305 &term,
13306 last_line,
13307 tmp_word,
13308 &langinfo);
13310 /* Determine the number of lines to move the cursor up if the window */
13311 /* display needed a terminal scrolling. */
13312 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13313 if (nl + win.message_lines + term.curs_line > term.nlines)
13314 line_offset = term.curs_line + nl + win.message_lines - term.nlines;
13315 else
13316 line_offset = 0;
13318 /* Set the cursor to the first line of the window. */
13319 /* """"""""""""""""""""""""""""""""""""""""""""""" */
13320 for (i = 1; i < line_offset; i++)
13321 (void)tputs(TPARM1(cursor_up), 1, outch);
13323 /* Get new cursor position. */
13324 /* """""""""""""""""""""""" */
13325 get_cursor_position(&term.curs_line, &term.curs_column);
13327 /* Short-circuit the loop. */
13328 /* """"""""""""""""""""""" */
13329 continue;
13332 /* and possibly set its reached value. */
13333 /* The counter is frozen in search and help mode. */
13334 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13335 if (timeout.initial_value && search_mode == NONE && !help_mode
13336 && got_timeout_tick)
13338 long i;
13339 char *timeout_string;
13341 got_timeout_tick = 0;
13343 timeout.remain--;
13345 if (!quiet_timeout && timeout.remain % FREQ == 0)
13347 sprintf(timeout_seconds, "%5u", timeout.remain / FREQ);
13348 timeout_string =
13349 (char *)(((ll_node_t *)(message_lines_list->tail))->data);
13350 memcpy(timeout_string + 1, timeout_seconds, 5);
13352 /* Erase the current window. */
13353 /* """"""""""""""""""""""""" */
13354 for (i = 0; i < win.message_lines; i++)
13356 (void)tputs(TPARM1(cursor_up), 1, outch);
13357 (void)tputs(TPARM1(clr_bol), 1, outch);
13358 (void)tputs(TPARM1(clr_eol), 1, outch);
13361 (void)tputs(TPARM1(clr_bol), 1, outch);
13362 (void)tputs(TPARM1(clr_eol), 1, outch);
13364 /* Display the words window and its title for the first time. */
13365 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13366 disp_message(message_lines_list,
13367 message_max_width,
13368 message_max_len,
13369 &term,
13370 &win,
13371 &langinfo);
13373 /* The timeout has expired. */
13374 /* """""""""""""""""""""""" */
13375 if (timeout.remain == 0)
13376 timeout.reached = 1;
13379 if (timeout.reached)
13381 if (timeout.mode == QUIT)
13382 goto quit;
13383 else if (timeout.mode == CURRENT || timeout.mode == WORD)
13384 goto enter;
13387 /* Pressed keys scancodes processing. */
13388 /* """""""""""""""""""""""""""""""""" */
13389 page = 1; /* Default number of lines to do down/up *
13390 | with PgDn/PgUp. */
13392 sc = get_scancode(buffer, 63);
13394 if (sc && winch_timer < 0) /* Do not allow input when a window *
13395 | refresh is scheduled. */
13397 /* Rearm the timer named "forgotten". */
13398 /* """""""""""""""""""""""""""""""""" */
13399 forgotten_timer = timers.forgotten;
13401 if (timeout.initial_value && buffer[0] != 0x0d && buffer[0] != 'q'
13402 && buffer[0] != 'Q' && buffer[0] != 3)
13404 char *timeout_string;
13406 /* Reset the timeout to its initial value. */
13407 /* """"""""""""""""""""""""""""""""""""""" */
13408 timeout.remain = timeout.initial_value;
13410 if (!quiet_timeout)
13412 sprintf(timeout_seconds, "%5u", timeout.initial_value / FREQ);
13413 timeout_string =
13414 (char *)(((ll_node_t *)(message_lines_list->tail))->data);
13415 memcpy(timeout_string + 1, timeout_seconds, 5);
13417 /* Clear the message. */
13418 /* """""""""""""""""" */
13419 for (long i = 0; i < win.message_lines; i++)
13421 (void)tputs(TPARM1(cursor_up), 1, outch);
13422 (void)tputs(TPARM1(clr_bol), 1, outch);
13423 (void)tputs(TPARM1(clr_eol), 1, outch);
13426 (void)tputs(TPARM1(clr_bol), 1, outch);
13427 (void)tputs(TPARM1(clr_eol), 1, outch);
13429 /* Display the words window and its title for the first time. */
13430 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13431 disp_message(message_lines_list,
13432 message_max_width,
13433 message_max_len,
13434 &term,
13435 &win,
13436 &langinfo);
13439 setitimer(ITIMER_REAL, &periodic_itv, NULL);
13442 if (search_mode == NONE && help_mode && buffer[0] != '?')
13444 got_help_alrm = 1;
13445 continue;
13448 switch (buffer[0])
13450 case 0x01: /* ^A */
13451 if (search_mode != NONE)
13452 goto khome;
13454 break;
13456 case 0x1a: /* ^Z */
13457 if (search_mode != NONE)
13458 goto kend;
13460 break;
13462 case 0x1b: /* ESC */
13464 /* Ignore mouse pastes when bracketed pastes is enabled. */
13465 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
13466 if (memcmp("\x1b[200~", buffer, 6) == 0)
13468 int c;
13469 char eb[6] = { 0 };
13471 /* Consume stdin until a closing bracket is found. */
13472 /* ''''''''''''''''''''''''''''''''''''''''''''''' */
13473 while (1)
13475 /* Fast reading until an ESC or the end of input is found. */
13476 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
13477 while ((c = my_fgetc(stdin)) != EOF && c != 0x1b)
13478 ; /* Null action. */
13480 /* Exits the loop early if the first ESC character starting */
13481 /* the ending bracket has already be consumed while reading */
13482 /* the content of buffer. */
13483 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13484 if (c == EOF)
13485 break;
13487 /* Read the 5 next characters to look for the */
13488 /* ending bracket "[201~". */
13489 /* """""""""""""""""""""""""""""""""""""""""" */
13490 scanf("%5c", eb);
13491 if (memcmp("[201~", eb, 5) == 0)
13492 break;
13496 /* An escape sequence or a UTF-8 sequence has been pressed. */
13497 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13498 if (memcmp("\x1bOH", buffer, 3) == 0
13499 || memcmp("\x1bk", buffer, 2) == 0
13500 || memcmp("\x1b[H", buffer, 3) == 0
13501 || memcmp("\x1b[1~", buffer, 4) == 0
13502 || memcmp("\x1b[7~", buffer, 4) == 0)
13504 /* HOME key has been pressed. */
13505 /* """""""""""""""""""""""""" */
13506 if (search_mode != NONE)
13508 khome:
13509 search_data.only_starting = 1;
13510 search_data.only_ending = 0;
13511 select_starting_matches(&win, &term, &search_data, &last_line);
13513 else
13515 /* Find the first selectable word. */
13516 /* """"""""""""""""""""""""""""""" */
13517 current = win.start;
13519 while (current < win.end && !word_a[current].is_selectable)
13520 current++;
13522 /* In column mode we need to take care of the */
13523 /* horizontal scrolling. */
13524 /* """""""""""""""""""""""""""""""""""""""""" */
13525 if ((win.col_mode || win.line_mode)
13526 && word_a[current].end < win.first_column)
13527 win.first_column = word_a[current].start;
13530 nl = disp_lines(&win,
13531 &toggles,
13532 current,
13533 count,
13534 search_mode,
13535 &search_data,
13536 &term,
13537 last_line,
13538 tmp_word,
13539 &langinfo);
13540 break;
13543 if (memcmp("\x1b[1;5H", buffer, 6) == 0
13544 || memcmp("\x1b[7^", buffer, 4) == 0)
13545 /* CTRL HOME key has been pressed. */
13546 /* """"""""""""""""""""""""""""""" */
13547 goto kchome;
13549 if (memcmp("\x1bOF", buffer, 3) == 0
13550 || memcmp("\x1bj", buffer, 2) == 0
13551 || memcmp("\x1b[F", buffer, 3) == 0
13552 || memcmp("\x1b[4~", buffer, 4) == 0
13553 || memcmp("\x1b[8~", buffer, 4) == 0)
13555 /* END key has been pressed. */
13556 /* """"""""""""""""""""""""" */
13557 if (search_mode != NONE)
13559 kend:
13561 if (matches_count > 0 && search_mode != PREFIX)
13563 search_data.only_starting = 0;
13564 search_data.only_ending = 1;
13565 select_ending_matches(&win, &term, &search_data, &last_line);
13568 else
13570 /* Find the last selectable word. */
13571 /* """""""""""""""""""""""""""""" */
13572 current = win.end;
13574 while (current > win.start && !word_a[current].is_selectable)
13575 current--;
13577 /* In column mode we need to take care of the */
13578 /* horizontal scrolling. */
13579 /* """""""""""""""""""""""""""""""""""""""""" */
13580 if (win.col_mode || win.line_mode)
13581 set_new_first_column(&win, &term);
13584 nl = disp_lines(&win,
13585 &toggles,
13586 current,
13587 count,
13588 search_mode,
13589 &search_data,
13590 &term,
13591 last_line,
13592 tmp_word,
13593 &langinfo);
13594 break;
13597 if (memcmp("\x1b[1;5F", buffer, 6) == 0
13598 || memcmp("\x1b[8^", buffer, 4) == 0)
13599 /* CTRL END key has been pressed. */
13600 /* """""""""""""""""""""""""""""" */
13601 goto kcend;
13603 if (memcmp("\x1bOD", buffer, 3) == 0
13604 || memcmp("\x1b[D", buffer, 3) == 0)
13605 /* Left arrow key has been pressed. */
13606 /* """""""""""""""""""""""""""""""" */
13607 goto kl;
13609 if (memcmp("\x1bOC", buffer, 3) == 0
13610 || memcmp("\x1b[C", buffer, 3) == 0)
13611 /* Right arrow key has been pressed. */
13612 /* """"""""""""""""""""""""""""""""" */
13613 goto kr;
13615 if (memcmp("\x1bOA", buffer, 3) == 0
13616 || memcmp("\x1b[A", buffer, 3) == 0)
13617 /* Up arrow key has been pressed. */
13618 /* """""""""""""""""""""""""""""" */
13619 goto ku;
13621 if (memcmp("\x1bOB", buffer, 3) == 0
13622 || memcmp("\x1b[B", buffer, 3) == 0)
13623 /* Down arrow key has been pressed. */
13624 /* """""""""""""""""""""""""""""""" */
13625 goto kd;
13627 if (memcmp("\x1b[I", buffer, 3) == 0
13628 || memcmp("\x1b[5~", buffer, 4) == 0)
13629 /* PgUp key has been pressed. */
13630 /* """""""""""""""""""""""""" */
13631 goto kpp;
13633 if (memcmp("\x1b[G", buffer, 3) == 0
13634 || memcmp("\x1b[6~", buffer, 4) == 0)
13635 /* PgDn key has been pressed. */
13636 /* """""""""""""""""""""""""" */
13637 goto knp;
13639 if (memcmp("\x1b[L", buffer, 3) == 0
13640 || memcmp("\x1b[2~", buffer, 4) == 0)
13641 /* Ins key has been pressed. */
13642 /* """"""""""""""""""""""""" */
13643 goto kins;
13645 if (memcmp("\x1b[3~", buffer, 4) == 0)
13646 /* Del key has been pressed. */
13647 /* """"""""""""""""""""""""" */
13648 goto kdel;
13650 if (memcmp("\x1b[1;2F", buffer, 6) == 0
13651 || memcmp("\x1b[1;5C", buffer, 6) == 0
13652 || memcmp("\x1b[8$", buffer, 4) == 0
13653 || memcmp("\x1bOc", buffer, 3) == 0)
13654 /* SHIFT END or CTRL -> has been pressed. */
13655 /* """""""""""""""""""""""""""""""""""""" */
13656 goto keol;
13658 if (memcmp("\x1b[1;2H", buffer, 6) == 0
13659 || memcmp("\x1b[1;5D", buffer, 6) == 0
13660 || memcmp("\x1b[7$", buffer, 4) == 0
13661 || memcmp("\x1bOd", buffer, 3) == 0)
13662 /* SHIFT HOME or CTRL <- key has been pressed. */
13663 /* """"""""""""""""""""""""""""""""""""""""""" */
13664 goto ksol;
13666 if (!toggles.no_mouse)
13668 double res = 5000; /* 5 s, arbitrary value. */
13669 struct timespec res_ts;
13671 /* The minimal resolution for double-click must be 1/10 s. */
13672 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
13673 if (clock_getres(CLOCK_MONOTONIC, &res_ts) == -1)
13674 disable_double_click = 1;
13675 else
13676 res = 1000.0 * res_ts.tv_sec + 1e-6 * res_ts.tv_nsec;
13678 if (res > 100)
13679 disable_double_click = 1;
13681 /* Detect the mouse protocol. */
13682 /* """""""""""""""""""""""""" */
13683 if (memcmp("\x1b[<", buffer, 3) == 0)
13685 mouse_proto = MOUSE1006;
13686 goto kmouse;
13689 if (memcmp("\x1b[M", buffer, 3) == 0)
13691 mouse_proto = MOUSE1000;
13692 goto kmouse;
13695 if (memcmp("\x1b[32;", buffer, 5) == 0
13696 || memcmp("\x1b[33;", buffer, 5) == 0
13697 || memcmp("\x1b[34;", buffer, 5) == 0
13698 || memcmp("\x1b[96;", buffer, 5) == 0
13699 || memcmp("\x1b[97;", buffer, 5) == 0)
13701 mouse_proto = MOUSE1015;
13702 goto kmouse;
13706 if (buffer[0] == 0x1b && buffer[1] == '\0')
13708 /* ESC key has been pressed. */
13709 /* """"""""""""""""""""""""" */
13710 reset_search_buffer(&win,
13711 &search_data,
13712 &timers,
13713 &toggles,
13714 &term,
13715 &daccess,
13716 &langinfo,
13717 last_line,
13718 tmp_word,
13719 word_real_max_size);
13721 /* Unmark the marked word. */
13722 /* """"""""""""""""""""""" */
13723 if (toggles.taggable && marked >= 0)
13724 goto unmark_word;
13726 break;
13729 /* Else ignore key. */
13730 break;
13732 quit:
13733 case 'q':
13734 case 'Q':
13735 case 3: /* ^C */
13736 /* q or Q of ^C has been pressed. */
13737 /* """""""""""""""""""""""""""""" */
13739 if (search_mode != NONE && buffer[0] != 3)
13740 goto special_cmds_when_searching;
13743 long i; /* Generic index in this block. */
13745 for (i = 0; i < win.message_lines; i++)
13746 (void)tputs(TPARM1(cursor_up), 1, outch);
13748 if (toggles.del_line)
13750 (void)tputs(TPARM1(clr_eol), 1, outch);
13751 (void)tputs(TPARM1(clr_bol), 1, outch);
13752 (void)tputs(TPARM1(save_cursor), 1, outch);
13754 for (i = 1; i < nl + win.message_lines; i++)
13756 (void)tputs(TPARM1(cursor_down), 1, outch);
13757 (void)tputs(TPARM1(clr_eol), 1, outch);
13758 (void)tputs(TPARM1(clr_bol), 1, outch);
13760 (void)tputs(TPARM1(restore_cursor), 1, outch);
13762 else
13764 for (i = 1; i < nl + win.message_lines; i++)
13765 (void)tputs(TPARM1(cursor_down), 1, outch);
13766 puts("");
13770 /* Disable the reporting of the mouse events. */
13771 /* """""""""""""""""""""""""""""""""""""""""" */
13772 if (!toggles.no_mouse)
13773 printf("%s", mouse_trk_off);
13775 /* Restore the visibility of the cursor. */
13776 /* """"""""""""""""""""""""""""""""""""" */
13777 (void)tputs(TPARM1(cursor_normal), 1, outch);
13779 if (buffer[0] == 3) /* ^C */
13781 if (int_string != NULL)
13782 fprintf(old_stdout, "%s", int_string);
13784 /* Set the cursor at the start on the line an restore the */
13785 /* original terminal state before exiting. */
13786 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
13787 (void)tputs(TPARM1(carriage_return), 1, outch);
13788 restore_term(fileno(stdin), &old_in_attrs);
13790 if (int_as_in_shell)
13791 exit(128 + SIGINT);
13793 else
13794 restore_term(fileno(stdin), &old_in_attrs);
13796 exit(EXIT_SUCCESS);
13798 case 0x0c:
13799 /* Form feed (^L) is a traditional method to redraw a screen. */
13800 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
13801 if (current < win.start || current > win.end)
13802 last_line = build_metadata(&term, count, &win);
13804 nl = disp_lines(&win,
13805 &toggles,
13806 current,
13807 count,
13808 search_mode,
13809 &search_data,
13810 &term,
13811 last_line,
13812 tmp_word,
13813 &langinfo);
13814 break;
13816 case 'n':
13817 case ' ':
13818 /* n or the space bar has been pressed. */
13819 /* """""""""""""""""""""""""""""""""""" */
13820 if (search_mode != NONE)
13821 goto special_cmds_when_searching;
13823 if (matches_count > 0)
13825 long pos = find_next_matching_word(matching_words_a,
13826 matches_count,
13827 current,
13828 &matching_word_cur_index);
13829 if (pos >= 0)
13830 current = pos;
13832 if (current < win.start || current > win.end)
13833 last_line = build_metadata(&term, count, &win);
13835 /* Set new first column to display. */
13836 /* """""""""""""""""""""""""""""""" */
13837 set_new_first_column(&win, &term);
13839 nl = disp_lines(&win,
13840 &toggles,
13841 current,
13842 count,
13843 search_mode,
13844 &search_data,
13845 &term,
13846 last_line,
13847 tmp_word,
13848 &langinfo);
13850 break;
13852 case 'N':
13853 /* N has been pressed.*/
13854 /* """"""""""""""""""" */
13855 if (search_mode != NONE)
13856 goto special_cmds_when_searching;
13858 if (matches_count > 0)
13860 long pos = find_prev_matching_word(matching_words_a,
13861 matches_count,
13862 current,
13863 &matching_word_cur_index);
13864 if (pos >= 0)
13865 current = pos;
13868 if (current < win.start || current > win.end)
13869 last_line = build_metadata(&term, count, &win);
13871 /* Set new first column to display. */
13872 /* """""""""""""""""""""""""""""""" */
13873 set_new_first_column(&win, &term);
13875 nl = disp_lines(&win,
13876 &toggles,
13877 current,
13878 count,
13879 search_mode,
13880 &search_data,
13881 &term,
13882 last_line,
13883 tmp_word,
13884 &langinfo);
13885 break;
13887 case 's':
13888 /* s has been pressed. */
13889 /* """"""""""""""""""" */
13890 if (search_mode != NONE)
13891 goto special_cmds_when_searching;
13893 if (matches_count > 0)
13895 long pos;
13897 if (best_matches_count > 0)
13898 pos = find_next_matching_word(best_matching_words_a,
13899 best_matches_count,
13900 current,
13901 &matching_word_cur_index);
13902 else
13903 pos = find_next_matching_word(matching_words_a,
13904 matches_count,
13905 current,
13906 &matching_word_cur_index);
13908 if (pos >= 0)
13909 current = pos;
13911 if (current < win.start || current > win.end)
13912 last_line = build_metadata(&term, count, &win);
13914 /* Set new first column to display. */
13915 /* """""""""""""""""""""""""""""""" */
13916 set_new_first_column(&win, &term);
13918 nl = disp_lines(&win,
13919 &toggles,
13920 current,
13921 count,
13922 search_mode,
13923 &search_data,
13924 &term,
13925 last_line,
13926 tmp_word,
13927 &langinfo);
13929 break;
13931 case 'S':
13932 /* S has been pressed. */
13933 /* """"""""""""""""""" */
13934 if (search_mode != NONE)
13935 goto special_cmds_when_searching;
13937 if (matches_count > 0)
13939 long pos;
13941 if (best_matches_count > 0)
13942 pos = find_prev_matching_word(best_matching_words_a,
13943 best_matches_count,
13944 current,
13945 &matching_word_cur_index);
13946 else
13947 pos = find_prev_matching_word(matching_words_a,
13948 matches_count,
13949 current,
13950 &matching_word_cur_index);
13952 if (pos >= 0)
13953 current = pos;
13956 if (current < win.start || current > win.end)
13957 last_line = build_metadata(&term, count, &win);
13959 /* Set new first column to display. */
13960 /* """""""""""""""""""""""""""""""" */
13961 set_new_first_column(&win, &term);
13963 nl = disp_lines(&win,
13964 &toggles,
13965 current,
13966 count,
13967 search_mode,
13968 &search_data,
13969 &term,
13970 last_line,
13971 tmp_word,
13972 &langinfo);
13973 break;
13975 enter:
13976 case 0x0d: /* CR */
13978 /* <Enter> has been pressed. */
13979 /* """"""""""""""""""""""""" */
13981 int extra_lines;
13982 char *output_str;
13983 output_t *output_node;
13985 int width = 0;
13987 wchar_t *w;
13988 long i; /* Generic index in this block. */
13990 if (help_mode || marked >= 0)
13992 marked = -1; /* Disable the marked mode unconditionally. */
13993 nl = disp_lines(&win,
13994 &toggles,
13995 current,
13996 count,
13997 search_mode,
13998 &search_data,
13999 &term,
14000 last_line,
14001 tmp_word,
14002 &langinfo);
14005 if (search_mode != NONE)
14007 /* Cancel the search timer. */
14008 /* """""""""""""""""""""""" */
14009 search_timer = 0;
14011 search_mode = NONE;
14012 search_data.only_starting = 0;
14013 search_data.only_ending = 0;
14015 nl = disp_lines(&win,
14016 &toggles,
14017 current,
14018 count,
14019 search_mode,
14020 &search_data,
14021 &term,
14022 last_line,
14023 tmp_word,
14024 &langinfo);
14026 if (!toggles.enter_val_in_search)
14027 break;
14030 if (toggles.del_line)
14032 for (i = 0; i < win.message_lines; i++)
14033 (void)tputs(TPARM1(cursor_up), 1, outch);
14035 (void)tputs(TPARM1(clr_eol), 1, outch);
14036 (void)tputs(TPARM1(clr_bol), 1, outch);
14037 (void)tputs(TPARM1(save_cursor), 1, outch);
14039 for (i = 1; i < nl + win.message_lines; i++)
14041 (void)tputs(TPARM1(cursor_down), 1, outch);
14042 (void)tputs(TPARM1(clr_eol), 1, outch);
14043 (void)tputs(TPARM1(clr_bol), 1, outch);
14046 (void)tputs(TPARM1(restore_cursor), 1, outch);
14048 else
14050 for (i = 1; i < nl; i++)
14051 (void)tputs(TPARM1(cursor_down), 1, outch);
14052 puts("");
14055 /* When a timeout of type WORD is set, prints the specified word */
14056 /* else prints the current selected entries. */
14057 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
14058 if (timeout.initial_value > 0 && timeout.remain == 0
14059 && timeout.mode == WORD)
14060 fprintf(old_stdout, "%s", timeout_word);
14061 else
14063 char *num_str;
14064 char *str;
14066 if (toggles.taggable)
14068 ll_t *output_list = ll_new();
14069 ll_node_t *node;
14071 /* When using -P, updates the tagging order of this word to */
14072 /* make sure that the output will be correctly sorted. */
14073 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
14074 if (word_a[current].tag_id == 0 && toggles.pinable)
14075 word_a[current].tag_order = tag_nb++;
14077 for (wi = 0; wi < count; wi++)
14079 if (word_a[wi].tag_id > 0 || wi == current)
14081 /* If the -p option is not used we do not take into */
14082 /* account an untagged word under the cursor if at least */
14083 /* on word is tagged. */
14084 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
14085 if (wi == current && tagged_words > 0 && !toggles.autotag
14086 && word_a[wi].tag_id == 0)
14087 continue;
14089 /* In tagged mode, do not automatically tag the word */
14090 /* under the cursor if toggles.noautotag is set and no */
14091 /* word are tagged. */
14092 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
14093 if (tagged_words == 0 && toggles.taggable
14094 && toggles.noautotag)
14095 continue;
14097 /* Chose the original string to print if the current one */
14098 /* has been altered by a possible expansion. */
14099 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
14100 output_node = xmalloc(sizeof(output_t));
14102 if (word_a[wi].orig != NULL)
14103 str = word_a[wi].orig;
14104 else
14105 str = word_a[wi].str;
14107 if (word_a[wi].is_numbered && daccess.num_sep)
14109 num_str = xstrndup(str + 1, daccess.length);
14111 ltrim(num_str, " ");
14112 rtrim(num_str, " ", 0);
14114 output_node->output_str = concat(num_str,
14115 daccess.num_sep,
14116 str + daccess.flength,
14117 (char *)0);
14119 free(num_str);
14121 else
14122 output_node->output_str = xstrdup(str + daccess.flength);
14124 output_node->order = word_a[wi].tag_order;
14126 /* Trim the spaces if -k is not given. */
14127 /* """"""""""""""""""""""""""""""""""" */
14128 if (!toggles.keep_spaces)
14130 ltrim(output_node->output_str, " \t");
14131 rtrim(output_node->output_str, " \t", 0);
14134 ll_append(output_list, output_node);
14138 /* If nothing is selected, exist without printing anything. */
14139 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
14140 if (output_list->head == NULL)
14141 goto exit;
14143 /* If -P is in use, then sort the output list. */
14144 /* """"""""""""""""""""""""""""""""""""""""""" */
14145 if (toggles.pinable)
14146 ll_sort(output_list, tag_comp, tag_swap);
14148 /* And print them. */
14149 /* """"""""""""""" */
14150 node = output_list->head;
14151 while (node->next != NULL)
14153 str = ((output_t *)(node->data))->output_str;
14155 fprintf(old_stdout, "%s", str);
14156 width += wcswidth((w = utf8_strtowcs(str)), 65535);
14157 free(w);
14158 free(str);
14159 free(node->data);
14161 if (win.sel_sep != NULL)
14163 fprintf(old_stdout, "%s", win.sel_sep);
14164 width += wcswidth((w = utf8_strtowcs(win.sel_sep)), 65535);
14165 free(w);
14167 else
14169 fprintf(old_stdout, " ");
14170 width++;
14173 node = node->next;
14176 str = ((output_t *)(node->data))->output_str;
14177 fprintf(old_stdout, "%s", str);
14178 width += wcswidth((w = utf8_strtowcs(str)), 65535);
14179 free(w);
14180 free(str);
14181 free(node->data);
14183 else
14185 /* Chose the original string to print if the current one has */
14186 /* been altered by a possible expansion. */
14187 /* Once this made, print it. */
14188 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
14189 if (word_a[current].orig != NULL)
14190 str = word_a[current].orig;
14191 else
14192 str = word_a[current].str;
14194 if (word_a[current].is_numbered && daccess.num_sep)
14196 num_str = xstrndup(str + 1, daccess.length);
14198 ltrim(num_str, " ");
14199 rtrim(num_str, " ", 0);
14201 output_str = concat(num_str,
14202 daccess.num_sep,
14203 str + daccess.flength,
14204 (char *)0);
14206 free(num_str);
14208 else
14209 output_str = str + daccess.flength;
14211 /* Trim the spaces if -k is not given. */
14212 /* """"""""""""""""""""""""""""""""""" */
14213 if (!toggles.keep_spaces)
14215 ltrim(output_str, " \t");
14216 rtrim(output_str, " \t", 0);
14219 width = wcswidth((w = utf8_strtowcs(output_str)), 65535);
14220 free(w);
14222 /* And print it. */
14223 /* """"""""""""" */
14224 fprintf(old_stdout, "%s", output_str);
14227 /* If the output stream is a terminal. */
14228 /* """"""""""""""""""""""""""""""""""" */
14229 if (isatty(old_fd1))
14231 /* width is (in term of terminal columns) the size of the */
14232 /* string to be displayed. */
14233 /* */
14234 /* With that information, count the number of terminal lines */
14235 /* printed. */
14236 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
14237 extra_lines = width / term.ncolumns;
14239 /* Clean the printed line and all the extra lines used. */
14240 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
14241 (void)tputs(TPARM1(delete_line), 1, outch);
14243 for (i = 0; i < extra_lines; i++)
14245 (void)tputs(TPARM1(cursor_up), 1, outch);
14246 (void)tputs(TPARM1(clr_eol), 1, outch);
14247 (void)tputs(TPARM1(clr_bol), 1, outch);
14252 exit:
14254 /* Disable mouse reporting. */
14255 /* '''''''''''''''''''''''' */
14256 if (!toggles.no_mouse)
14257 printf("%s", mouse_trk_off);
14259 /* Restore the visibility of the cursor. */
14260 /* """"""""""""""""""""""""""""""""""""" */
14261 (void)tputs(TPARM1(cursor_normal), 1, outch);
14263 /* Set the cursor at the start on the line an restore the */
14264 /* original terminal state before exiting. */
14265 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
14266 (void)tputs(TPARM1(carriage_return), 1, outch);
14267 restore_term(fileno(stdin), &old_in_attrs);
14269 exit(EXIT_SUCCESS);
14272 ksol:
14273 /* Go to the start of the line. */
14274 /* """""""""""""""""""""""""""" */
14275 search_mode = NONE;
14277 /* Fall through. */
14278 /* """"""""""""" */
14280 case 'H':
14281 if (search_mode == NONE)
14283 if (win.col_mode || win.line_mode)
14285 long pos;
14287 pos = first_word_in_line_a[line_nb_of_word_a[current]];
14289 search_mode = NONE;
14291 /* Find the first selectable word. */
14292 /* """"""""""""""""""""""""""""""" */
14293 while (!word_a[pos].is_last)
14295 if (word_a[pos].is_selectable)
14297 current = pos;
14298 break;
14301 pos++;
14303 if (pos > current)
14304 break;
14307 if (word_a[pos].is_last && word_a[pos].is_selectable)
14308 current = pos;
14310 set_new_first_column(&win, &term);
14312 nl = disp_lines(&win,
14313 &toggles,
14314 current,
14315 count,
14316 search_mode,
14317 &search_data,
14318 &term,
14319 last_line,
14320 tmp_word,
14321 &langinfo);
14324 else
14325 goto special_cmds_when_searching;
14327 break;
14330 /* Cursor Left key has been pressed. */
14331 /* """"""""""""""""""""""""""""""""" */
14332 search_mode = NONE;
14334 /* Fall through. */
14335 /* """"""""""""" */
14337 case 'h':
14338 if (search_mode == NONE)
14339 move_left(&win,
14340 &term,
14341 &toggles,
14342 &search_data,
14343 &langinfo,
14344 &nl,
14345 last_line,
14346 tmp_word);
14347 else
14348 goto special_cmds_when_searching;
14350 break;
14352 /* shift the window to the left if possible. */
14353 /* """"""""""""""""""""""""""""""""""""""""" */
14354 case '<':
14355 if (search_mode == NONE)
14356 shift_left(&win,
14357 &term,
14358 &toggles,
14359 &search_data,
14360 &langinfo,
14361 &nl,
14362 last_line,
14363 tmp_word,
14364 line_nb_of_word_a[current]);
14365 else
14366 goto special_cmds_when_searching;
14368 break;
14370 keol:
14371 /* Go to the end of the line. */
14372 /* """""""""""""""""""""""""" */
14373 search_mode = NONE;
14375 /* Fall through. */
14376 /* """"""""""""" */
14378 case 'L':
14379 if (search_mode == NONE)
14381 if (win.col_mode || win.line_mode)
14383 long pos;
14385 pos = current;
14387 search_mode = NONE;
14389 /* Find the first selectable word. */
14390 /* """"""""""""""""""""""""""""""" */
14391 while (!word_a[pos].is_last)
14393 if (word_a[pos].is_selectable)
14394 current = pos;
14396 pos++;
14399 if (word_a[pos].is_selectable)
14400 current = pos;
14402 set_new_first_column(&win, &term);
14404 nl = disp_lines(&win,
14405 &toggles,
14406 current,
14407 count,
14408 search_mode,
14409 &search_data,
14410 &term,
14411 last_line,
14412 tmp_word,
14413 &langinfo);
14416 else
14417 goto special_cmds_when_searching;
14419 break;
14422 /* Right key has been pressed. */
14423 /* """"""""""""""""""""""""""" */
14424 search_mode = NONE;
14426 /* Fall through. */
14427 /* """"""""""""" */
14429 case 'l':
14430 if (search_mode == NONE)
14431 move_right(&win,
14432 &term,
14433 &toggles,
14434 &search_data,
14435 &langinfo,
14436 &nl,
14437 last_line,
14438 tmp_word);
14439 else
14440 goto special_cmds_when_searching;
14442 break;
14444 /* shift the window to the left if possible. */
14445 /* """"""""""""""""""""""""""""""""""""""""" */
14446 case '>':
14447 if (search_mode == NONE)
14448 shift_right(&win,
14449 &term,
14450 &toggles,
14451 &search_data,
14452 &langinfo,
14453 &nl,
14454 last_line,
14455 tmp_word,
14456 line_nb_of_word_a[current]);
14457 else
14458 goto special_cmds_when_searching;
14460 break;
14462 case 0x0b:
14463 /* ^K key has been pressed. */
14464 /* """""""""""""""""""""""" */
14465 goto kchome;
14467 case 'K':
14468 if (search_mode != NONE)
14469 goto special_cmds_when_searching;
14471 kpp:
14472 /* PgUp key has been pressed. */
14473 /* """""""""""""""""""""""""" */
14474 page = win.max_lines;
14477 /* Cursor Up key has been pressed. */
14478 /* """"""""""""""""""""""""""""""" */
14479 search_mode = NONE;
14481 /* Fall through. */
14482 /* """"""""""""" */
14484 case 'k':
14485 if (search_mode == NONE)
14486 move_up(&win,
14487 &term,
14488 &toggles,
14489 &search_data,
14490 &langinfo,
14491 &nl,
14492 page,
14493 first_selectable,
14494 last_line,
14495 tmp_word);
14496 else
14497 goto special_cmds_when_searching;
14499 break;
14501 kchome:
14502 /* Go to the first selectable word. */
14503 /* """""""""""""""""""""""""""""""" */
14504 current = 0;
14506 search_mode = NONE;
14508 /* Find the first selectable word. */
14509 /* """"""""""""""""""""""""""""""" */
14510 while (!word_a[current].is_selectable)
14511 current++;
14513 if (current < win.start || current > win.end)
14514 last_line = build_metadata(&term, count, &win);
14516 /* In column mode we need to take care of the */
14517 /* horizontal scrolling. */
14518 /* """""""""""""""""""""""""""""""""""""""""" */
14519 if (win.col_mode || win.line_mode)
14521 long pos;
14523 /* Adjust win.first_column if the cursor is */
14524 /* no more visible. */
14525 /* """""""""""""""""""""""""""""""""""""""" */
14526 pos = first_word_in_line_a[line_nb_of_word_a[current]];
14528 while (word_a[current].end - word_a[pos].start >= term.ncolumns - 3)
14529 pos++;
14531 win.first_column = word_a[pos].start;
14534 nl = disp_lines(&win,
14535 &toggles,
14536 current,
14537 count,
14538 search_mode,
14539 &search_data,
14540 &term,
14541 last_line,
14542 tmp_word,
14543 &langinfo);
14544 break;
14546 case 0x0a:
14547 /* ^J key has been pressed. */
14548 /* """""""""""""""""""""""" */
14549 goto kcend;
14551 case 'J':
14552 if (search_mode != NONE)
14553 goto special_cmds_when_searching;
14555 knp:
14556 /* PgDn key has been pressed. */
14557 /* """""""""""""""""""""""""" */
14558 page = win.max_lines;
14561 /* Cursor Down key has been pressed. */
14562 /* """"""""""""""""""""""""""""""""" */
14563 search_mode = NONE;
14565 /* Fall through. */
14566 /* """"""""""""" */
14568 case 'j':
14569 if (search_mode == NONE)
14570 move_down(&win,
14571 &term,
14572 &toggles,
14573 &search_data,
14574 &langinfo,
14575 &nl,
14576 page,
14577 last_selectable,
14578 last_line,
14579 tmp_word);
14580 else
14581 goto special_cmds_when_searching;
14583 break;
14585 kcend:
14586 /* Go to the last selectable word. */
14587 /* """"""""""""""""""""""""""""""" */
14588 current = count - 1;
14590 search_mode = NONE;
14592 /* Find the first selectable word. */
14593 /* """"""""""""""""""""""""""""""" */
14594 while (!word_a[current].is_selectable)
14595 current--;
14597 if (current < win.start || current > win.end)
14598 last_line = build_metadata(&term, count, &win);
14600 /* In column mode we need to take care of the */
14601 /* horizontal scrolling. */
14602 /* """""""""""""""""""""""""""""""""""""""""" */
14603 if (win.col_mode || win.line_mode)
14605 long pos;
14607 /* Adjust win.first_column if the cursor is no more visible. */
14608 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
14609 pos = first_word_in_line_a[line_nb_of_word_a[current]];
14611 while (word_a[current].end - word_a[pos].start >= term.ncolumns - 3)
14612 pos++;
14614 win.first_column = word_a[pos].start;
14617 nl = disp_lines(&win,
14618 &toggles,
14619 current,
14620 count,
14621 search_mode,
14622 &search_data,
14623 &term,
14624 last_line,
14625 tmp_word,
14626 &langinfo);
14627 break;
14629 case '/':
14630 /* Generic search method according the misc settings. */
14631 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
14632 if (misc.default_search_method == PREFIX)
14633 goto prefix_method;
14634 else if (misc.default_search_method == SUBSTRING)
14635 goto substring_method;
14636 else if (misc.default_search_method == FUZZY)
14637 goto fuzzy_method;
14639 break;
14641 case '~':
14642 case '*':
14643 /* ~ or * key has been pressed */
14644 /* (start of a fuzzy search session). */
14645 /* """""""""""""""""""""""""""""""""" */
14646 fuzzy_method:
14648 if (search_mode == NONE)
14650 if (!toggles.incremental_search)
14651 reset_search_buffer(&win,
14652 &search_data,
14653 &timers,
14654 &toggles,
14655 &term,
14656 &daccess,
14657 &langinfo,
14658 last_line,
14659 tmp_word,
14660 word_real_max_size);
14662 /* Set the search timer. */
14663 /* """"""""""""""""""""" */
14664 search_timer = timers.search; /* default 10 s. */
14666 search_mode = FUZZY;
14668 if (old_search_mode != FUZZY)
14670 old_search_mode = FUZZY;
14671 clean_matches(&search_data, word_real_max_size);
14674 nl = disp_lines(&win,
14675 &toggles,
14676 current,
14677 count,
14678 search_mode,
14679 &search_data,
14680 &term,
14681 last_line,
14682 tmp_word,
14683 &langinfo);
14685 else
14686 goto special_cmds_when_searching;
14688 break;
14690 case '\'':
14691 case '\"':
14692 /* ' or " key has been pressed */
14693 /* (start of a substring search session). */
14694 /* """""""""""""""""""""""""""""""""""""" */
14695 substring_method:
14697 if (search_mode == NONE)
14699 if (!toggles.incremental_search)
14700 reset_search_buffer(&win,
14701 &search_data,
14702 &timers,
14703 &toggles,
14704 &term,
14705 &daccess,
14706 &langinfo,
14707 last_line,
14708 tmp_word,
14709 word_real_max_size);
14711 /* Set the search timer. */
14712 /* """"""""""""""""""""" */
14713 search_timer = timers.search; /* default 10 s. */
14715 search_mode = SUBSTRING;
14717 if (old_search_mode != SUBSTRING)
14719 old_search_mode = SUBSTRING;
14720 clean_matches(&search_data, word_real_max_size);
14723 nl = disp_lines(&win,
14724 &toggles,
14725 current,
14726 count,
14727 search_mode,
14728 &search_data,
14729 &term,
14730 last_line,
14731 tmp_word,
14732 &langinfo);
14734 else
14735 goto special_cmds_when_searching;
14737 break;
14739 case '=':
14740 case '^':
14741 /* ^ or = key has been pressed */
14742 /* (start of a prefix search session). */
14743 /* """"""""""""""""""""""""""""""""""" */
14744 prefix_method:
14746 if (search_mode == NONE)
14748 if (!toggles.incremental_search)
14749 reset_search_buffer(&win,
14750 &search_data,
14751 &timers,
14752 &toggles,
14753 &term,
14754 &daccess,
14755 &langinfo,
14756 last_line,
14757 tmp_word,
14758 word_real_max_size);
14760 /* Set the search timer. */
14761 /* """"""""""""""""""""" */
14762 search_timer = timers.search; /* default 10 s. */
14764 search_mode = PREFIX;
14766 if (old_search_mode != PREFIX)
14768 old_search_mode = PREFIX;
14769 clean_matches(&search_data, word_real_max_size);
14772 nl = disp_lines(&win,
14773 &toggles,
14774 current,
14775 count,
14776 search_mode,
14777 &search_data,
14778 &term,
14779 last_line,
14780 tmp_word,
14781 &langinfo);
14782 break;
14784 else
14785 goto special_cmds_when_searching;
14787 break;
14789 kins:
14790 /* The INS key has been pressed to tag a word if */
14791 /* tagging is enabled. */
14792 /* """"""""""""""""""""""""""""""""""""""""""""" */
14793 if (toggles.taggable && word_a[current].tag_id == 0)
14795 word_a[current].tag_id = win.next_tag_id++;
14796 tagged_words++;
14798 if (toggles.pinable)
14799 word_a[current].tag_order = tag_nb++;
14801 nl = disp_lines(&win,
14802 &toggles,
14803 current,
14804 count,
14805 search_mode,
14806 &search_data,
14807 &term,
14808 last_line,
14809 tmp_word,
14810 &langinfo);
14812 break;
14814 kdel:
14815 /* The DEL key has been pressed to untag a word if */
14816 /* tagging is enabled. */
14817 /* """"""""""""""""""""""""""""""""""""""""""""""" */
14818 if (toggles.taggable && word_a[current].tag_id > 0)
14820 word_a[current].tag_id = 0;
14821 tagged_words--;
14823 /* We do not try to change tag_nb here to guaranty that */
14824 /* tag_nb will be greater than all those already stored */
14825 /* in all word_a[*].tag_order. */
14826 /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
14828 nl = disp_lines(&win,
14829 &toggles,
14830 current,
14831 count,
14832 search_mode,
14833 &search_data,
14834 &term,
14835 last_line,
14836 tmp_word,
14837 &langinfo);
14839 break;
14841 case 't':
14842 /* t has been pressed to tag/untag a word if */
14843 /* tagging is enabled. */
14844 /* """"""""""""""""""""""""""""""""""""""""" */
14845 if (search_mode == NONE)
14847 if (toggles.taggable)
14849 if (word_a[current].tag_id > 0)
14851 word_a[current].tag_id = 0;
14852 tagged_words--;
14854 else
14856 word_a[current].tag_id = win.next_tag_id++;
14857 tagged_words++;
14859 if (toggles.pinable)
14860 word_a[current].tag_order = tag_nb++;
14863 nl = disp_lines(&win,
14864 &toggles,
14865 current,
14866 count,
14867 search_mode,
14868 &search_data,
14869 &term,
14870 last_line,
14871 tmp_word,
14872 &langinfo);
14875 else
14876 goto special_cmds_when_searching;
14877 break;
14879 case 'u':
14880 /* u has been pressed to untag a word if */
14881 /* tagging is enabled. */
14882 /* """"""""""""""""""""""""""""""""""""" */
14883 if (search_mode == NONE)
14885 if (toggles.taggable)
14887 if (word_a[current].tag_id > 0)
14889 word_a[current].tag_id = 0;
14890 tagged_words--;
14892 nl = disp_lines(&win,
14893 &toggles,
14894 current,
14895 count,
14896 search_mode,
14897 &search_data,
14898 &term,
14899 last_line,
14900 tmp_word,
14901 &langinfo);
14905 else
14906 goto special_cmds_when_searching;
14907 break;
14909 case 20:
14910 /* (CTRL-t) Remove all tags. */
14911 /* """"""""""""""""""""""""" */
14912 if (search_mode == NONE)
14914 if (toggles.taggable && (win.next_tag_id > 1 || marked >= 0))
14916 tagged_words = 0;
14917 win.next_tag_id = 1;
14918 marked = -1;
14920 for (wi = 0; wi < count; wi++)
14922 word_a[wi].tag_id = 0;
14923 word_a[wi].tag_order = 0;
14926 nl = disp_lines(&win,
14927 &toggles,
14928 current,
14929 count,
14930 search_mode,
14931 &search_data,
14932 &term,
14933 last_line,
14934 tmp_word,
14935 &langinfo);
14938 else
14939 goto special_cmds_when_searching;
14940 break;
14942 tag_column:
14943 case 'c':
14944 /* Tag/untag all the words in the current column. */
14945 /* """""""""""""""""""""""""""""""""""""""""""""" */
14946 if (search_mode == NONE)
14948 if (toggles.taggable || toggles.pinable)
14950 long col, cur_col, marked_col;
14951 long first, last;
14953 int tagged;
14955 if (!win.col_mode)
14956 break;
14958 /* Get the current column number. */
14959 /* """""""""""""""""""""""""""""" */
14960 cur_col = current
14961 - first_word_in_line_a[line_nb_of_word_a[current]] + 1;
14963 /* Determine the loop values according to the existence of */
14964 /* a marked word and is value. */
14965 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
14966 if (marked == -1)
14968 first = 0;
14969 last = count - 1;
14971 else
14973 marked_col = marked
14974 - first_word_in_line_a[line_nb_of_word_a[marked]]
14975 + 1;
14977 /* Ignore the marked word is its is not on the same column */
14978 /* as the current word. */
14979 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
14980 if (cur_col == marked_col)
14982 if (marked <= current)
14984 first = first_word_in_line_a[line_nb_of_word_a[marked]];
14985 last = current;
14987 else
14989 first = first_word_in_line_a[line_nb_of_word_a[current]];
14990 last = marked;
14992 /* Pre-increment tag_nb with is maximum mulue as */
14993 /* it will be decremented in the following loop when */
14994 /* marked > current */
14995 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
14996 tag_nb += marked - current + 1;
14999 else
15000 break;
15003 /* Tag from first to last. */
15004 /* """"""""""""""""""""""" */
15005 col = 0;
15006 tagged = 0;
15008 for (wi = first; wi <= last; wi++)
15010 col++;
15011 if (col == cur_col && word_a[wi].is_selectable
15012 && word_a[wi].tag_id == 0)
15014 word_a[wi].tag_id = win.next_tag_id;
15015 tagged_words++;
15017 if (toggles.pinable)
15019 if (marked <= current)
15020 word_a[wi].tag_order = tag_nb++;
15021 else
15022 word_a[wi].tag_order = tag_nb--;
15025 tagged = 1;
15028 /* Time to go back to column 1? */
15029 /* """""""""""""""""""""""""""" */
15030 if (word_a[wi].is_last)
15031 col = 0;
15034 if (tagged)
15035 win.next_tag_id++;
15037 if (marked > current)
15038 tag_nb += marked - current + 1;
15040 marked = -1;
15042 nl = disp_lines(&win,
15043 &toggles,
15044 current,
15045 count,
15046 search_mode,
15047 &search_data,
15048 &term,
15049 last_line,
15050 tmp_word,
15051 &langinfo);
15054 else
15055 goto special_cmds_when_searching;
15056 break;
15058 case 'C':
15059 /* Allow to tag part of a column, the first invocation marks */
15060 /* the starting word and the second marks the words in between. */
15061 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15062 if (search_mode == NONE)
15064 /* Mark the first word is not already marked. */
15065 /* """""""""""""""""""""""""""""""""""""""""" */
15066 if (!win.col_mode)
15067 break;
15069 if (!toggles.taggable && !toggles.pinable)
15070 break;
15072 if (marked == -1)
15073 goto mark_word;
15075 goto tag_column;
15077 else
15078 goto special_cmds_when_searching;
15079 break;
15081 tag_line:
15082 case 'r':
15083 /* Tag/untag all the words in the current line. */
15084 /* """""""""""""""""""""""""""""""""""""""""""" */
15085 if (search_mode == NONE)
15087 if (toggles.taggable || toggles.pinable)
15089 long first, last;
15090 long marked_line;
15091 int tagged;
15093 if (!win.col_mode && !win.line_mode)
15094 break;
15096 if (marked >= 0)
15098 marked_line = line_nb_of_word_a[marked];
15099 if (marked_line == line_nb_of_word_a[current])
15101 if (marked <= current)
15103 first = marked;
15104 last = current;
15106 else
15108 first = current;
15109 last = marked;
15111 /* Pre-increment tag_nb with is maximum mulue as */
15112 /* it will be decremented in the following loop when */
15113 /* marked > current */
15114 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
15115 tag_nb += marked - current + 1;
15118 else
15119 break;
15121 else
15123 first = first_word_in_line_a[line_nb_of_word_a[current]];
15124 if (line_nb_of_word_a[current] == line_nb_of_word_a[count - 1])
15125 last = count - 1;
15126 else
15127 last = first_word_in_line_a[line_nb_of_word_a[current] + 1]
15128 - 1;
15131 /* Tag from first to last. */
15132 /* """"""""""""""""""""""" */
15133 wi = first;
15134 tagged = 0;
15138 if (word_a[wi].is_selectable)
15140 if (word_a[wi].tag_id == 0)
15142 word_a[wi].tag_id = win.next_tag_id;
15143 tagged_words++;
15145 if (toggles.pinable)
15147 if (marked <= current)
15148 word_a[wi].tag_order = tag_nb++;
15149 else
15150 word_a[wi].tag_order = tag_nb--;
15153 tagged = 1;
15156 wi++;
15157 } while (wi <= last);
15159 if (tagged)
15160 win.next_tag_id++;
15162 if (marked > current)
15163 tag_nb += marked - current + 1;
15165 marked = -1;
15167 nl = disp_lines(&win,
15168 &toggles,
15169 current,
15170 count,
15171 search_mode,
15172 &search_data,
15173 &term,
15174 last_line,
15175 tmp_word,
15176 &langinfo);
15179 else
15180 goto special_cmds_when_searching;
15181 break;
15183 case 'R':
15184 /* Make sure that all the words in the current line */
15185 /* are not tagged. */
15186 /* """""""""""""""""""""""""""""""""""""""""""""""" */
15187 if (search_mode == NONE)
15189 if (!win.col_mode && !win.line_mode)
15190 break;
15192 if (!toggles.taggable && !toggles.pinable)
15193 break;
15195 if (marked == -1)
15196 goto mark_word;
15198 goto tag_line;
15200 else
15201 goto special_cmds_when_searching;
15202 break;
15204 mark_word:
15205 case 'm':
15206 /* Mark the current word (ESC clears the mark). */
15207 /* """""""""""""""""""""""""""""""""""""""""""" */
15208 if (search_mode == NONE)
15210 if (toggles.taggable)
15212 marked = current;
15214 nl = disp_lines(&win,
15215 &toggles,
15216 current,
15217 count,
15218 search_mode,
15219 &search_data,
15220 &term,
15221 last_line,
15222 tmp_word,
15223 &langinfo);
15226 else
15227 goto special_cmds_when_searching;
15228 break;
15230 unmark_word:
15231 case 'M':
15232 /* unmark the current word (ESC clears the mark). */
15233 /* """""""""""""""""""""""""""""""""""""""""""""" */
15234 if (search_mode == NONE)
15236 if (toggles.taggable)
15238 marked = -1;
15240 nl = disp_lines(&win,
15241 &toggles,
15242 current,
15243 count,
15244 search_mode,
15245 &search_data,
15246 &term,
15247 last_line,
15248 tmp_word,
15249 &langinfo);
15252 else
15253 goto special_cmds_when_searching;
15254 break;
15256 tag_to_mark:
15257 case 'T':
15258 /* T has been pressed to tag all matching words resulting */
15259 /* from a previous search if tagging is enabled. */
15260 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
15261 if (search_mode == NONE)
15263 if (toggles.taggable)
15265 long i;
15267 /* Is words have been matched by a recent search, tag them. */
15268 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15269 if (matches_count > 0 && marked == -1)
15271 int tagged = 0;
15273 for (i = 0; i < matches_count; i++)
15275 wi = matching_words_a[i];
15277 if (word_a[wi].tag_id == 0)
15279 word_a[wi].tag_id = win.next_tag_id;
15280 tagged_words++;
15282 if (toggles.pinable)
15283 word_a[wi].tag_order = tag_nb++;
15285 tagged = 1;
15288 if (tagged)
15289 win.next_tag_id++;
15291 else /* Tag word between the marked and current words. */
15293 if (marked == -1)
15294 goto mark_word;
15297 long first, last;
15298 int tagged = 0;
15300 if (marked <= current)
15302 first = marked;
15303 last = current;
15305 else
15307 first = current;
15308 last = marked;
15310 /* Pre-increment tag_nb with is maximum mulue as */
15311 /* it will be decremented in the following loop when */
15312 /* marked > current */
15313 /* ''''''''''''''''''''''''''''''''''''''''''''''''' */
15314 tag_nb += marked - current + 1;
15317 tagged = 0;
15319 for (wi = first; wi <= last; wi++)
15321 if (!word_a[wi].is_selectable)
15322 continue;
15324 if (word_a[wi].tag_id == 0)
15326 word_a[wi].tag_id = win.next_tag_id;
15327 tagged_words++;
15329 if (toggles.pinable)
15331 if (marked <= current)
15332 word_a[wi].tag_order = tag_nb++;
15333 else
15334 word_a[wi].tag_order = tag_nb--;
15337 tagged = 1;
15341 if (tagged)
15342 win.next_tag_id++;
15344 if (marked > current)
15345 tag_nb += marked - current + 1;
15347 marked = -1;
15351 nl = disp_lines(&win,
15352 &toggles,
15353 current,
15354 count,
15355 search_mode,
15356 &search_data,
15357 &term,
15358 last_line,
15359 tmp_word,
15360 &langinfo);
15363 else
15364 goto special_cmds_when_searching;
15365 break;
15367 adaptative_tag_to_mark:
15368 case 'Z':
15369 /* Z has been pressed to tag consecutive word in a given zone . */
15370 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15371 if (search_mode == NONE)
15373 if (toggles.taggable)
15375 if (marked == -1)
15376 marked = current;
15377 else if (marked == current)
15378 marked = -1;
15379 else if (marked >= 0 && win.col_mode)
15381 long cur_col =
15382 current - first_word_in_line_a[line_nb_of_word_a[current]]
15383 + 1;
15384 long mark_col =
15385 marked - first_word_in_line_a[line_nb_of_word_a[marked]] + 1;
15386 if (cur_col == mark_col)
15387 goto tag_column;
15388 else if (line_nb_of_word_a[current]
15389 == line_nb_of_word_a[marked])
15390 goto tag_line;
15391 else
15392 goto tag_to_mark;
15394 else if (marked >= 0)
15396 if (win.line_mode
15397 && line_nb_of_word_a[current] == line_nb_of_word_a[marked])
15398 goto tag_line;
15399 else
15400 goto tag_to_mark;
15403 nl = disp_lines(&win,
15404 &toggles,
15405 current,
15406 count,
15407 search_mode,
15408 &search_data,
15409 &term,
15410 last_line,
15411 tmp_word,
15412 &langinfo);
15414 else
15415 goto special_cmds_when_searching;
15416 break;
15418 case 'U':
15419 /* U has been pressed to undo the last tagging operation. */
15420 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
15421 if (search_mode == NONE)
15423 if (toggles.taggable)
15425 for (wi = 0; wi < count; wi++)
15427 if (!word_a[wi].is_selectable)
15428 continue;
15430 if (word_a[wi].tag_id > 0)
15432 if (word_a[wi].tag_id == win.next_tag_id - 1)
15434 word_a[wi].tag_id = 0;
15435 tagged_words--;
15440 if (win.next_tag_id > 1)
15441 win.next_tag_id--;
15443 nl = disp_lines(&win,
15444 &toggles,
15445 current,
15446 count,
15447 search_mode,
15448 &search_data,
15449 &term,
15450 last_line,
15451 tmp_word,
15452 &langinfo);
15455 else
15456 goto special_cmds_when_searching;
15457 break;
15459 case '0':
15460 case '1':
15461 case '2':
15462 case '3':
15463 case '4':
15464 case '5':
15465 case '6':
15466 case '7':
15467 case '8':
15468 case '9':
15469 /* A digit has been pressed to build a number to be used for */
15470 /* A direct access to a words if direct access is enabled. */
15471 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15473 if (search_mode == NONE && daccess.mode != DA_TYPE_NONE)
15475 wchar_t *w;
15476 long *pos;
15478 /* Set prev_current to the initial current word to be */
15479 /* able to return here if the first direct access fails. */
15480 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
15481 if (daccess_stack_head == 0)
15482 prev_current = current;
15484 if (daccess_stack_head == daccess.length)
15485 break;
15487 daccess_stack[daccess_stack_head] = buffer[0];
15488 daccess_stack_head++;
15489 w = utf8_strtowcs(daccess_stack);
15490 pos = tst_search(tst_daccess, w);
15491 free(w);
15493 if (pos != NULL)
15495 current = *pos;
15497 if (current < win.start || current > win.end)
15498 last_line = build_metadata(&term, count, &win);
15500 /* Set new first column to display. */
15501 /* """""""""""""""""""""""""""""""" */
15502 set_new_first_column(&win, &term);
15504 nl = disp_lines(&win,
15505 &toggles,
15506 current,
15507 count,
15508 search_mode,
15509 &search_data,
15510 &term,
15511 last_line,
15512 tmp_word,
15513 &langinfo);
15515 else
15517 if (current != prev_current)
15519 current = prev_current;
15521 if (current < win.start || current > win.end)
15522 last_line = build_metadata(&term, count, &win);
15524 /* Set new first column to display. */
15525 /* """""""""""""""""""""""""""""""" */
15526 set_new_first_column(&win, &term);
15528 nl = disp_lines(&win,
15529 &toggles,
15530 current,
15531 count,
15532 search_mode,
15533 &search_data,
15534 &term,
15535 last_line,
15536 tmp_word,
15537 &langinfo);
15541 daccess_timer = timers.direct_access;
15543 else
15544 goto special_cmds_when_searching;
15546 break;
15548 case 0x08: /* ^H */
15549 case 0x7f: /* BS */
15550 /* backspace/CTRL-H management. */
15551 /* """""""""""""""""""""""""""" */
15553 long i;
15555 if (daccess_stack_head > 0)
15556 daccess_stack[--daccess_stack_head] = '\0';
15558 if (search_mode != NONE)
15560 if (search_data.utf8_len > 0)
15562 char *prev;
15564 prev = utf8_prev(search_data.buf,
15565 search_data.buf + search_data.len - 1);
15567 /* Reset the error indicator if we erase the first non */
15568 /* matching element of the search buffer. */
15569 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
15570 if (search_mode != FUZZY
15571 || search_data.utf8_len == search_data.fuzzy_err_pos - 1)
15573 search_data.err = 0;
15574 search_data.fuzzy_err_pos = -1;
15576 search_data.utf8_len--;
15578 if (prev)
15580 *(utf8_next(prev)) = '\0';
15581 search_data.len = prev - search_data.buf + 1;
15583 else
15585 *search_data.buf = '\0';
15586 search_data.len = 0;
15588 for (i = 0; i < matches_count; i++)
15590 long n = matching_words_a[i];
15592 word_a[n].is_matching = 0;
15594 memset(word_a[n].bitmap,
15595 '\0',
15596 (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
15599 matches_count = 0;
15601 nl = disp_lines(&win,
15602 &toggles,
15603 current,
15604 count,
15605 search_mode,
15606 &search_data,
15607 &term,
15608 last_line,
15609 tmp_word,
15610 &langinfo);
15613 else
15615 my_beep(&toggles);
15616 if (search_data.err)
15618 search_data.err = 0;
15619 search_data.fuzzy_err_pos = -1;
15621 nl = disp_lines(&win,
15622 &toggles,
15623 current,
15624 count,
15625 search_mode,
15626 &search_data,
15627 &term,
15628 last_line,
15629 tmp_word,
15630 &langinfo);
15634 if (search_data.utf8_len > 0)
15635 goto special_cmds_when_searching;
15636 else
15638 /* When there is only one glyph in the search list in */
15639 /* FUZZY and SUBSTRING mode then all is already done except */
15640 /* the cleanup of the first level of the tst_search_list. */
15641 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15642 if (search_mode != PREFIX)
15644 sub_tst_t *sub_tst_data;
15645 ll_node_t *node;
15647 node = tst_search_list->tail;
15648 sub_tst_data = (sub_tst_t *)(node->data);
15650 search_data.err = 0;
15652 sub_tst_data->count = 0;
15658 break;
15660 case '?':
15661 /* Help mode. */
15662 /* """""""""" */
15663 if (search_mode == NONE)
15665 help(&win, &term, last_line);
15666 help_mode = 1;
15668 /* Arm the help timer. */
15669 /* """"""""""""""""""" */
15670 help_timer = timers.help; /* default 30 s. */
15672 else
15673 goto special_cmds_when_searching;
15674 break;
15676 kmouse:
15678 long new_current;
15679 unsigned int iCb, iCx, iCy;
15680 unsigned char cCb, cCx, cCy;
15681 char action;
15683 int state;
15684 int button, old_button;
15685 int line_click;
15686 int column_click;
15687 int error;
15689 long old_current = current;
15691 struct timespec actual_click_ts;
15693 switch (mouse_proto)
15695 case MOUSE1006:
15696 if (sscanf((char *)buffer + 3,
15697 "%u;%u;%u%c",
15698 &iCb,
15699 &iCx,
15700 &iCy,
15701 &action)
15702 != 4)
15703 goto ignore_mouse_event;
15705 state = iCb & ~3;
15706 button = iCb & 3;
15707 line_click = iCy;
15708 column_click = iCx;
15710 /* Only consider button click (not release) events. */
15711 /* """""""""""""""""""""""""""""""""""""""""""""""" */
15712 if (action == 'm') /* released. */
15713 goto ignore_mouse_event;
15715 break;
15717 case MOUSE1015:
15718 if (sscanf((char *)buffer + 2,
15719 "%u;%u;%u%c",
15720 &iCb,
15721 &iCx,
15722 &iCy,
15723 &action)
15724 != 4)
15725 goto ignore_mouse_event;
15727 state = (iCb - 32) & ~3;
15728 button = (iCb - 32) & 3;
15729 line_click = iCy;
15730 column_click = iCx;
15732 /* Only consider button click (not release) events. */
15733 /* """""""""""""""""""""""""""""""""""""""""""""""" */
15734 if (state == 0 && button == 3) /* released. */
15735 goto ignore_mouse_event;
15737 break;
15739 case MOUSE1000:
15740 if (sscanf((char *)buffer + 3, "%c%c%c", &cCb, &cCx, &cCy) != 3)
15741 goto ignore_mouse_event;
15743 state = (cCb - 32) & ~3;
15744 button = (cCb - 32) & 3;
15745 line_click = cCy - 32;
15746 column_click = cCx - 32;
15748 /* Only consider button click (not release) events. */
15749 /* """""""""""""""""""""""""""""""""""""""""""""""" */
15750 if (button == 3) /* released. */
15751 goto ignore_mouse_event;
15753 break;
15755 default:
15756 goto ignore_mouse_event;
15759 /* Mouse Button remapping. */
15760 /* """"""""""""""""""""""" */
15761 old_button = button;
15762 button = mouse.button[button] - 1;
15763 long clicked_line;
15764 int clicked_arrow;
15766 /* Only buttons 0,1 and 2 are considered for clicks. */
15767 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
15768 if (button < 0 || button > 2)
15769 goto ignore_mouse_event;
15771 /* Do not do anything if the user has above or below the window. */
15772 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15773 if (line_click < term.curs_line
15774 || line_click
15775 >= term.curs_line + win.max_lines + (win.has_hbar ? 1 : 0))
15776 break;
15778 /* Mouse wheel scroll down. */
15779 /* """""""""""""""""""""""" */
15780 if (state == 64 && old_button == 1)
15781 goto kd; /* down arrow. */
15783 /* Mouse wheel scroll down + CTRL. */
15784 /* """"""""""""""""""""""""""""""" */
15785 if (state == 80 && old_button == 1)
15786 goto knp; /* PgDn. */
15788 /* Mouse wheel scroll up. */
15789 /* """""""""""""""""""""" */
15790 if (state == 64 && old_button == 0)
15791 goto ku; /* up arrow. */
15793 /* Mouse wheel scroll up + CTRL. */
15794 /* """"""""""""""""""""""""""""" */
15795 if (state == 80 && old_button == 0)
15796 goto kpp; /* PgUp. */
15798 /* Manage the clicks at the ends of the vertical scroll bar. */
15799 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
15800 if (button == 0 && column_click == win.sb_column + 1)
15802 if (line_click == term.curs_line + win.max_lines - 1)
15804 if (state == 16)
15805 goto knp; /* PgDn. */
15806 if (state == 0)
15807 goto kd; /* down arrow. */
15809 else if (line_click == term.curs_line)
15811 if (state == 16)
15812 goto kpp; /* PgUp. */
15813 if (state == 0)
15814 goto ku; /* up arrow. */
15816 else if (line_click > term.curs_line
15817 && line_click < term.curs_line + win.max_lines - 1)
15819 float ratio; /* cursor ratio in the between the extremities *
15820 | of the scroll bar. */
15822 if (win.max_lines > 3)
15824 float corr; /* scaling correction. */
15825 long line; /* new selected line. */
15827 corr = (float)(win.max_lines - 2) / (win.max_lines - 3);
15828 ratio = (float)(line_click - term.curs_line - 1)
15829 / (win.max_lines - 2);
15831 /* Find the location of the new current word based on */
15832 /* the position of the cursor in the scroll bar. */
15833 /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
15834 line = ratio * corr * line_nb_of_word_a[count - 1];
15835 current = first_word_in_line_a[line];
15837 /* Make sure the new current word is selectable. */
15838 /* """"""""""""""""""""""""""""""""""""""""""""" */
15839 if (current < first_selectable)
15840 current = first_selectable;
15842 if (current > last_selectable)
15843 current = last_selectable;
15845 while (!word_a[current].is_selectable)
15846 current++;
15848 /* Display the window. */
15849 /* """"""""""""""""""" */
15850 last_line = build_metadata(&term, count, &win);
15852 set_new_first_column(&win, &term);
15854 nl = disp_lines(&win,
15855 &toggles,
15856 current,
15857 count,
15858 search_mode,
15859 &search_data,
15860 &term,
15861 last_line,
15862 tmp_word,
15863 &langinfo);
15867 else
15868 /* Manage the clicks in the horizontal scroll bar. */
15869 /* """"""""""""""""""""""""""""""""""""""""""""""" */
15870 if (win.has_hbar && button == 0
15871 && line_click == term.curs_line + win.max_lines)
15873 long wi; /* Word index. */
15874 long line = line_nb_of_word_a[current];
15875 long leftmost;
15876 long rightmost;
15877 int leftmost_start;
15878 int rightmost_end;
15880 /* Find the first selectable word in the line */
15881 /* containing the cursor. */
15882 /* """""""""""""""""""""""""""""""""""""""""" */
15883 wi = first_word_in_line_a[line];
15885 while (!word_a[wi].is_selectable)
15886 wi++;
15888 leftmost = wi;
15889 leftmost_start = word_a[leftmost].start;
15891 if (column_click == 1)
15893 /* First, manage the case where the user clicked at the */
15894 /* beginning of the scroll bar. */
15895 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
15896 if (current > leftmost && word_a[current].start > 0)
15897 goto kl;
15900 /* Else we need to calculate the rightmost selectable word */
15901 /* in the line containing the cursor. */
15902 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
15903 else if (line == last_line)
15904 wi = count - 1;
15905 else
15906 wi = first_word_in_line_a[line + 1] - 1;
15908 while (!word_a[wi].is_selectable)
15909 wi--;
15911 rightmost = wi;
15913 /* Manage the case where the users clicked at the end */
15914 /* of the scroll bar. */
15915 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
15916 if (column_click == term.ncolumns - 1)
15917 if (current < rightmost && current < count - 1
15918 && word_a[current + 1].start > 0)
15919 goto kr;
15921 /* Finally manage the core where the users clicked in */
15922 /* the crossbar. */
15923 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
15924 rightmost_end = word_a[rightmost].end;
15927 long index;
15928 int target;
15929 double ratio;
15931 if (column_click >= 2 && column_click - 2 <= term.ncolumns - 4)
15933 ratio = (1.0 * column_click - 2) / (term.ncolumns - 4);
15934 target = (int)((rightmost_end - leftmost_start + 1) * ratio)
15935 + leftmost_start;
15937 if (target > 0) /* General case. */
15939 if (word_a[current].start <= target)
15940 index = current;
15941 else
15942 index = leftmost;
15944 while (index <= rightmost && word_a[index].start <= target)
15945 current = index++;
15947 else /* Trivial case. */
15948 current = leftmost;
15950 set_new_first_column(&win, &term);
15952 nl = disp_lines(&win,
15953 &toggles,
15954 current,
15955 count,
15956 search_mode,
15957 &search_data,
15958 &term,
15959 last_line,
15960 tmp_word,
15961 &langinfo);
15965 break;
15967 else
15969 /* Manage clicks on the horizontal arrows in lines if any. */
15970 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
15971 if (win.has_truncated_lines
15972 && shift_arrow_clicked(&win,
15973 &term,
15974 line_click,
15975 column_click,
15976 &clicked_line,
15977 &clicked_arrow))
15979 if (button == 0)
15980 switch (clicked_arrow)
15982 case 0: /* left */
15983 shift_left(&win,
15984 &term,
15985 &toggles,
15986 &search_data,
15987 &langinfo,
15988 &nl,
15989 last_line,
15990 tmp_word,
15991 clicked_line);
15992 break;
15994 case 1: /* right */
15995 shift_right(&win,
15996 &term,
15997 &toggles,
15998 &search_data,
15999 &langinfo,
16000 &nl,
16001 last_line,
16002 tmp_word,
16003 clicked_line);
16004 break;
16006 nl = disp_lines(&win,
16007 &toggles,
16008 current,
16009 count,
16010 search_mode,
16011 &search_data,
16012 &term,
16013 last_line,
16014 tmp_word,
16015 &langinfo);
16017 else
16019 /* Get the new current word on click. */
16020 /* """""""""""""""""""""""""""""""""" */
16021 error = 0;
16022 new_current = get_clicked_index(&win,
16023 &term,
16024 line_click,
16025 column_click,
16026 &error);
16028 /* Update the selection index and refresh if needed. */
16029 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
16030 if ((toggles.taggable || toggles.pinable
16031 || new_current != current)
16032 && error == 0)
16034 current = new_current;
16036 /* Manage the marking of a word. */
16037 /* (button 0 + CTRL or button 2 pressed). */
16038 /* """""""""""""""""""""""""""""""""""""" */
16039 if ((button == 0 && state == 16)
16040 && (toggles.taggable || toggles.pinable))
16042 if (marked == -1)
16043 goto mark_word;
16044 else
16045 goto unmark_word;
16048 /* Manage the tagging of a word. */
16049 /* (button 2 + CTRL or button 2 pressed). */
16050 /* """""""""""""""""""""""""""""""""""""" */
16051 if ((button == 2 && state == 0)
16052 && (toggles.taggable || toggles.pinable))
16054 if (word_a[current].tag_id > 0)
16055 goto kdel;
16056 else
16057 goto kins;
16060 if ((button == 2 && state == 16)
16061 && (toggles.taggable || toggles.pinable))
16062 goto adaptative_tag_to_mark; /* Like 'Z' keyboard command. */
16064 /* Redisplay the new window if the first button was pressed */
16065 /* otherwise reset the cursor position to its previous value. */
16066 /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
16067 if (button == 0 || button == 2)
16068 nl = disp_lines(&win,
16069 &toggles,
16070 current,
16071 count,
16072 search_mode,
16073 &search_data,
16074 &term,
16075 last_line,
16076 tmp_word,
16077 &langinfo);
16078 else
16079 current = old_current;
16082 /* Manage double clicks if the clicked word is selectable. */
16083 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
16084 if (!disable_double_click && button == 0)
16086 /* More than one clicks on the same word? */
16087 /* """""""""""""""""""""""""""""""""""""" */
16088 if (click_nr > 0 && old_current == current)
16090 double delay;
16092 /* Get the delay between the actual click and */
16093 /* the previous one. */
16094 /* """""""""""""""""""""""""""""""""""""""""" */
16095 clock_gettime(CLOCK_MONOTONIC, &actual_click_ts);
16096 delay = (1000.0 * actual_click_ts.tv_sec
16097 + 1e-6 * actual_click_ts.tv_nsec)
16098 - (1000.0 * last_click_ts.tv_sec
16099 + 1e-6 * last_click_ts.tv_nsec);
16101 if (!error && delay > 0
16102 && delay <= mouse.double_click_delay)
16103 goto enter; /* Press Enter. */
16104 else
16105 clock_gettime(CLOCK_MONOTONIC, &last_click_ts);
16107 else
16109 /* The first click on a selectable was not make yet. */
16110 /* """"""""""""""""""""""""""""""""""""""""""""""""" */
16111 clock_gettime(CLOCK_MONOTONIC, &last_click_ts);
16113 if (!error)
16114 click_nr = 1;
16120 ignore_mouse_event:
16121 break;
16123 special_cmds_when_searching:
16124 default:
16126 int c; /* byte index in the scancode string .*/
16127 sub_tst_t *sub_tst_data;
16128 long i;
16130 if (search_mode != NONE)
16132 long old_len = search_data.len;
16133 long old_utf8_len = search_data.utf8_len;
16134 ll_node_t *node;
16135 wchar_t *ws;
16137 /* Copy all the bytes included in the key press to buffer. */
16138 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
16139 if (buffer[0] != 0x08 && buffer[0] != 0x7f) /* Backspace. */
16141 /* The only case where we have to manage backspace hits */
16142 /* here is if the user has entered them in fuzzy search */
16143 /* mode. As the search buffer has already been amended, */
16144 /* we do not have to update the search buffer again. */
16145 /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */
16146 for (c = 0; c < sc
16147 && search_data.utf8_len
16148 < word_real_max_size - daccess.flength;
16149 c++)
16150 search_data.buf[search_data.len++] = buffer[c];
16152 /* Update the glyph array with the content of the search */
16153 /* buffer. */
16154 /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */
16155 if (search_data.utf8_len < word_real_max_size - daccess.flength)
16157 search_data.utf8_off_a[search_data.utf8_len] = old_len;
16158 search_data.utf8_len_a[search_data.utf8_len] = search_data.len
16159 - old_len;
16160 search_data.utf8_len++;
16164 /* Restart the search timer. */
16165 /* """"""""""""""""""""""""" */
16166 search_timer = timers.search; /* default 10 s. */
16168 if (search_mode == PREFIX)
16170 ws = utf8_strtowcs(search_data.buf);
16172 /* Purge the matching words list. */
16173 /* """""""""""""""""""""""""""""" */
16174 for (i = 0; i < matches_count; i++)
16176 long n = matching_words_a[i];
16178 word_a[n].is_matching = 0;
16180 memset(word_a[n].bitmap,
16181 '\0',
16182 (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
16185 matches_count = 0;
16187 tst_prefix_search(tst_word, ws, tst_cb);
16189 /* matches_count is updated by tst_cb. */
16190 /* """"""""""""""""""""""""""""""""""" */
16191 if (matches_count > 0)
16193 if (search_data.len == old_len && matches_count == 1
16194 && buffer[0] != 0x08 && buffer[0] != 0x7f)
16195 my_beep(&toggles);
16196 else
16198 /* Adjust the bitmap to the ending version. */
16199 /* """""""""""""""""""""""""""""""""""""""" */
16200 update_bitmaps(search_mode, &search_data, NO_AFFINITY);
16202 current = matching_words_a[0];
16204 if (current < win.start || current > win.end)
16205 last_line = build_metadata(&term, count, &win);
16207 /* Set new first column to display. */
16208 /* """""""""""""""""""""""""""""""" */
16209 set_new_first_column(&win, &term);
16211 nl = disp_lines(&win,
16212 &toggles,
16213 current,
16214 count,
16215 search_mode,
16216 &search_data,
16217 &term,
16218 last_line,
16219 tmp_word,
16220 &langinfo);
16223 else
16225 my_beep(&toggles);
16227 search_data.err = 1;
16228 search_data.len = old_len;
16229 search_data.utf8_len = old_utf8_len;
16230 search_data.buf[search_data.len] = '\0';
16232 /* Set new first column to display. */
16233 /* """""""""""""""""""""""""""""""" */
16234 set_new_first_column(&win, &term);
16236 nl = disp_lines(&win,
16237 &toggles,
16238 current,
16239 count,
16240 search_mode,
16241 &search_data,
16242 &term,
16243 last_line,
16244 tmp_word,
16245 &langinfo);
16248 else if (search_mode == FUZZY)
16250 /* tst_search_list: [sub_tst_t *] -> [sub_tst_t *]... */
16251 /* ^ ^ */
16252 /* | | */
16253 /* level 1 level_2 */
16254 /* */
16255 /* Each sub_tst_t * points to a data structure including */
16256 /* a sorted array to nodes in the words tst. */
16257 /* Each of these node starts a matching candidate. */
16258 /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */
16259 wchar_t *w = utf8_strtowcs(search_data.buf + old_len);
16261 /* zero previous matching indicators. */
16262 /* """""""""""""""""""""""""""""""""" */
16263 for (i = 0; i < matches_count; i++)
16265 long n = matching_words_a[i];
16267 word_a[n].is_matching = 0;
16269 memset(word_a[n].bitmap,
16270 '\0',
16271 (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
16274 matches_count = 0;
16276 if (buffer[0] == 0x08 || buffer[0] == 0x7f) /* Backspace */
16278 node = tst_search_list->tail;
16279 sub_tst_data = (sub_tst_t *)(node->data);
16281 sub_tst_data->count = 0;
16283 if (tst_search_list->len > 0)
16285 free(sub_tst_data->array);
16286 free(sub_tst_data);
16288 ll_delete(tst_search_list, tst_search_list->tail);
16291 else
16293 if (search_data.utf8_len == 1)
16295 /* Search all the sub-tst trees having the searched */
16296 /* character as children, the resulting sub-tst are put */
16297 /* in the sub tst array attached to the currently */
16298 /* searched symbol. */
16299 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
16300 tst_fuzzy_traverse(tst_word, NULL, 0, w[0]);
16302 node = tst_search_list->tail;
16303 sub_tst_data = (sub_tst_t *)(node->data);
16304 if (sub_tst_data->count == 0)
16306 my_beep(&toggles);
16308 search_data.len = 0;
16309 search_data.utf8_len = 0;
16310 search_data.buf[0] = '\0';
16312 break;
16315 else
16317 /* Prevent the list to grow larger than the maximal */
16318 /* word's length. */
16319 /* """""""""""""""""""""""""""""""""""""""""""""""" */
16320 if (tst_search_list->len
16321 < word_real_max_size - daccess.flength)
16323 /* use the results in the level n-1 list to build the */
16324 /* level n list. */
16325 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
16326 int rc;
16328 sub_tst_t *tst_fuzzy_level_data;
16330 tst_fuzzy_level_data = sub_tst_new();
16332 ll_append(tst_search_list, tst_fuzzy_level_data);
16334 node = tst_search_list->tail->prev;
16335 sub_tst_data = (sub_tst_t *)(node->data);
16337 rc = 0;
16338 for (index = 0; index < sub_tst_data->count; index++)
16339 rc += tst_fuzzy_traverse(sub_tst_data->array[index],
16340 NULL,
16342 w[0]);
16344 if (rc == 0)
16346 free(tst_fuzzy_level_data->array);
16347 free(tst_fuzzy_level_data);
16349 ll_delete(tst_search_list, tst_search_list->tail);
16351 search_data.err = 1;
16352 if (search_data.fuzzy_err_pos == -1)
16353 search_data.fuzzy_err_pos = search_data.utf8_len;
16355 my_beep(&toggles);
16357 search_data.len = old_len;
16358 search_data.utf8_len = old_utf8_len;
16359 search_data.buf[search_data.len] = '\0';
16362 else
16363 my_beep(&toggles);
16366 free(w);
16368 /* Process this level to mark the word found as a matching */
16369 /* word if any. */
16370 /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */
16371 node = tst_search_list->tail;
16372 sub_tst_data = (sub_tst_t *)(node->data);
16374 for (index = 0; index < sub_tst_data->count; index++)
16375 tst_traverse(sub_tst_data->array[index], set_matching_flag, 0);
16377 /* Update the bitmap and re-display the window. */
16378 /* """""""""""""""""""""""""""""""""""""""""""" */
16379 if (matches_count > 0)
16381 if (search_data.only_starting)
16382 select_starting_matches(&win,
16383 &term,
16384 &search_data,
16385 &last_line);
16386 else if (search_data.only_ending)
16387 select_ending_matches(&win, &term, &search_data, &last_line);
16388 else
16389 /* Adjust the bitmap to the ending version. */
16390 /* """""""""""""""""""""""""""""""""""""""" */
16391 update_bitmaps(search_mode, &search_data, NO_AFFINITY);
16393 current = matching_words_a[0];
16395 if (current < win.start || current > win.end)
16396 last_line = build_metadata(&term, count, &win);
16398 /* Set new first column to display. */
16399 /* """""""""""""""""""""""""""""""" */
16400 set_new_first_column(&win, &term);
16402 nl = disp_lines(&win,
16403 &toggles,
16404 current,
16405 count,
16406 search_mode,
16407 &search_data,
16408 &term,
16409 last_line,
16410 tmp_word,
16411 &langinfo);
16413 else
16414 my_beep(&toggles);
16416 else /* SUBSTRING. */
16418 wchar_t *w = utf8_strtowcs(search_data.buf);
16420 /* Purge the matching words list. */
16421 /* """""""""""""""""""""""""""""" */
16422 for (i = 0; i < matches_count; i++)
16424 long n = matching_words_a[i];
16426 word_a[n].is_matching = 0;
16428 memset(word_a[n].bitmap,
16429 '\0',
16430 (word_a[n].mb - daccess.flength) / CHAR_BIT + 1);
16433 matches_count = 0;
16435 if (search_data.utf8_len == 1)
16437 /* Search all the sub-tst trees having the searched */
16438 /* character as children, the resulting sub-tst are put */
16439 /* in the level list corresponding to the letter order. */
16440 /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
16441 tst_substring_traverse(tst_word, NULL, 0, w[0]);
16443 node = tst_search_list->tail;
16444 sub_tst_data = (sub_tst_t *)(node->data);
16446 for (index = 0; index < sub_tst_data->count; index++)
16447 tst_traverse(sub_tst_data->array[index],
16448 set_matching_flag,
16451 else
16453 /* Search for the rest of the word in all the sub-tst */
16454 /* trees previously found. */
16455 /* """""""""""""""""""""""""""""""""""""""""""""""""" */
16456 node = tst_search_list->tail;
16457 sub_tst_data = (sub_tst_t *)(node->data);
16459 matches_count = 0;
16461 for (index = 0; index < sub_tst_data->count; index++)
16462 tst_prefix_search(sub_tst_data->array[index], w + 1, tst_cb);
16465 if (matches_count > 0)
16467 if (search_data.len == old_len && matches_count == 1
16468 && buffer[0] != 0x08 && buffer[0] != 0x7f)
16469 my_beep(&toggles);
16470 else
16472 if (search_data.only_starting)
16473 select_starting_matches(&win,
16474 &term,
16475 &search_data,
16476 &last_line);
16477 else if (search_data.only_ending)
16478 select_ending_matches(&win,
16479 &term,
16480 &search_data,
16481 &last_line);
16482 else
16483 update_bitmaps(search_mode, &search_data, NO_AFFINITY);
16485 current = matching_words_a[0];
16487 if (current < win.start || current > win.end)
16488 last_line = build_metadata(&term, count, &win);
16490 /* Set new first column to display. */
16491 /* """""""""""""""""""""""""""""""" */
16492 set_new_first_column(&win, &term);
16494 nl = disp_lines(&win,
16495 &toggles,
16496 current,
16497 count,
16498 search_mode,
16499 &search_data,
16500 &term,
16501 last_line,
16502 tmp_word,
16503 &langinfo);
16506 else
16508 my_beep(&toggles);
16510 search_data.err = 1;
16511 search_data.len = old_len;
16512 search_data.utf8_len--;
16513 search_data.buf[search_data.len] = '\0';
16515 /* Set new first column to display. */
16516 /* """""""""""""""""""""""""""""""" */
16517 set_new_first_column(&win, &term);
16519 nl = disp_lines(&win,
16520 &toggles,
16521 current,
16522 count,
16523 search_mode,
16524 &search_data,
16525 &term,
16526 last_line,
16527 tmp_word,
16528 &langinfo);