Merge branch 'vendor/DHCPCD'
[dragonfly.git] / contrib / less / decode.c
1 /*
2  * Copyright (C) 1984-2022  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9
10
11 /*
12  * Routines to decode user commands.
13  *
14  * This is all table driven.
15  * A command table is a sequence of command descriptors.
16  * Each command descriptor is a sequence of bytes with the following format:
17  *     <c1><c2>...<cN><0><action>
18  * The characters c1,c2,...,cN are the command string; that is,
19  * the characters which the user must type.
20  * It is terminated by a null <0> byte.
21  * The byte after the null byte is the action code associated
22  * with the command string.
23  * If an action byte is OR-ed with A_EXTRA, this indicates
24  * that the option byte is followed by an extra string.
25  *
26  * There may be many command tables.
27  * The first (default) table is built-in.
28  * Other tables are read in from "lesskey" files.
29  * All the tables are linked together and are searched in order.
30  */
31
32 #include "less.h"
33 #include "cmd.h"
34 #include "lesskey.h"
35
36 extern int erase_char, erase2_char, kill_char;
37 extern int secure;
38 extern int mousecap;
39 extern int screen_trashed;
40 extern int sc_height;
41
42 #define SK(k) \
43         SK_SPECIAL_KEY, (k), 6, 1, 1, 1
44 /*
45  * Command table is ordered roughly according to expected
46  * frequency of use, so the common commands are near the beginning.
47  */
48
49 static unsigned char cmdtable[] =
50 {
51         '\r',0,                         A_F_LINE,
52         '\n',0,                         A_F_LINE,
53         'e',0,                          A_F_LINE,
54         'j',0,                          A_F_LINE,
55         SK(SK_DOWN_ARROW),0,            A_F_LINE,
56         CONTROL('E'),0,                 A_F_LINE,
57         CONTROL('N'),0,                 A_F_LINE,
58         'k',0,                          A_B_LINE,
59         'y',0,                          A_B_LINE,
60         CONTROL('Y'),0,                 A_B_LINE,
61         SK(SK_CONTROL_K),0,             A_B_LINE,
62         CONTROL('P'),0,                 A_B_LINE,
63         SK(SK_UP_ARROW),0,              A_B_LINE,
64         'J',0,                          A_FF_LINE,
65         'K',0,                          A_BF_LINE,
66         'Y',0,                          A_BF_LINE,
67         'd',0,                          A_F_SCROLL,
68         CONTROL('D'),0,                 A_F_SCROLL,
69         'u',0,                          A_B_SCROLL,
70         CONTROL('U'),0,                 A_B_SCROLL,
71         ESC,'[','M',0,                  A_X11MOUSE_IN,
72         ESC,'[','<',0,                  A_X116MOUSE_IN,
73         ' ',0,                          A_F_SCREEN,
74         'f',0,                          A_F_SCREEN,
75         CONTROL('F'),0,                 A_F_SCREEN,
76         CONTROL('V'),0,                 A_F_SCREEN,
77         SK(SK_PAGE_DOWN),0,             A_F_SCREEN,
78         'b',0,                          A_B_SCREEN,
79         CONTROL('B'),0,                 A_B_SCREEN,
80         ESC,'v',0,                      A_B_SCREEN,
81         SK(SK_PAGE_UP),0,               A_B_SCREEN,
82         'z',0,                          A_F_WINDOW,
83         'w',0,                          A_B_WINDOW,
84         ESC,' ',0,                      A_FF_SCREEN,
85         'F',0,                          A_F_FOREVER,
86         ESC,'F',0,                      A_F_UNTIL_HILITE,
87         'R',0,                          A_FREPAINT,
88         'r',0,                          A_REPAINT,
89         CONTROL('R'),0,                 A_REPAINT,
90         CONTROL('L'),0,                 A_REPAINT,
91         ESC,'u',0,                      A_UNDO_SEARCH,
92         ESC,'U',0,                      A_CLR_SEARCH,
93         'g',0,                          A_GOLINE,
94         SK(SK_HOME),0,                  A_GOLINE,
95         '<',0,                          A_GOLINE,
96         ESC,'<',0,                      A_GOLINE,
97         'p',0,                          A_PERCENT,
98         '%',0,                          A_PERCENT,
99         ESC,'[',0,                      A_LSHIFT,
100         ESC,']',0,                      A_RSHIFT,
101         ESC,'(',0,                      A_LSHIFT,
102         ESC,')',0,                      A_RSHIFT,
103         ESC,'{',0,                      A_LLSHIFT,
104         ESC,'}',0,                      A_RRSHIFT,
105         SK(SK_RIGHT_ARROW),0,           A_RSHIFT,
106         SK(SK_LEFT_ARROW),0,            A_LSHIFT,
107         SK(SK_CTL_RIGHT_ARROW),0,       A_RRSHIFT,
108         SK(SK_CTL_LEFT_ARROW),0,        A_LLSHIFT,
109         '{',0,                          A_F_BRACKET|A_EXTRA,        '{','}',0,
110         '}',0,                          A_B_BRACKET|A_EXTRA,        '{','}',0,
111         '(',0,                          A_F_BRACKET|A_EXTRA,        '(',')',0,
112         ')',0,                          A_B_BRACKET|A_EXTRA,        '(',')',0,
113         '[',0,                          A_F_BRACKET|A_EXTRA,        '[',']',0,
114         ']',0,                          A_B_BRACKET|A_EXTRA,        '[',']',0,
115         ESC,CONTROL('F'),0,             A_F_BRACKET,
116         ESC,CONTROL('B'),0,             A_B_BRACKET,
117         'G',0,                          A_GOEND,
118         ESC,'G',0,                      A_GOEND_BUF,
119         ESC,'>',0,                      A_GOEND,
120         '>',0,                          A_GOEND,
121         SK(SK_END),0,                   A_GOEND,
122         'P',0,                          A_GOPOS,
123
124         '0',0,                          A_DIGIT,
125         '1',0,                          A_DIGIT,
126         '2',0,                          A_DIGIT,
127         '3',0,                          A_DIGIT,
128         '4',0,                          A_DIGIT,
129         '5',0,                          A_DIGIT,
130         '6',0,                          A_DIGIT,
131         '7',0,                          A_DIGIT,
132         '8',0,                          A_DIGIT,
133         '9',0,                          A_DIGIT,
134         '.',0,                          A_DIGIT,
135
136         '=',0,                          A_STAT,
137         CONTROL('G'),0,                 A_STAT,
138         ':','f',0,                      A_STAT,
139         '/',0,                          A_F_SEARCH,
140         '?',0,                          A_B_SEARCH,
141         ESC,'/',0,                      A_F_SEARCH|A_EXTRA,        '*',0,
142         ESC,'?',0,                      A_B_SEARCH|A_EXTRA,        '*',0,
143         'n',0,                          A_AGAIN_SEARCH,
144         ESC,'n',0,                      A_T_AGAIN_SEARCH,
145         'N',0,                          A_REVERSE_SEARCH,
146         ESC,'N',0,                      A_T_REVERSE_SEARCH,
147         '&',0,                          A_FILTER,
148         'm',0,                          A_SETMARK,
149         'M',0,                          A_SETMARKBOT,
150         ESC,'m',0,                      A_CLRMARK,
151         '\'',0,                         A_GOMARK,
152         CONTROL('X'),CONTROL('X'),0,    A_GOMARK,
153         'E',0,                          A_EXAMINE,
154         ':','e',0,                      A_EXAMINE,
155         CONTROL('X'),CONTROL('V'),0,    A_EXAMINE,
156         ':','n',0,                      A_NEXT_FILE,
157         ':','p',0,                      A_PREV_FILE,
158         't',0,                          A_NEXT_TAG,
159         'T',0,                          A_PREV_TAG,
160         ':','x',0,                      A_INDEX_FILE,
161         ':','d',0,                      A_REMOVE_FILE,
162         '-',0,                          A_OPT_TOGGLE,
163         ':','t',0,                      A_OPT_TOGGLE|A_EXTRA,        't',0,
164         's',0,                          A_OPT_TOGGLE|A_EXTRA,        'o',0,
165         '_',0,                          A_DISP_OPTION,
166         '|',0,                          A_PIPE,
167         'v',0,                          A_VISUAL,
168         '!',0,                          A_SHELL,
169         '+',0,                          A_FIRSTCMD,
170
171         'H',0,                          A_HELP,
172         'h',0,                          A_HELP,
173         SK(SK_F1),0,                    A_HELP,
174         'V',0,                          A_VERSION,
175         'q',0,                          A_QUIT,
176         'Q',0,                          A_QUIT,
177         ':','q',0,                      A_QUIT,
178         ':','Q',0,                      A_QUIT,
179         'Z','Z',0,                      A_QUIT
180 };
181
182 static unsigned char edittable[] =
183 {
184         '\t',0,                         EC_F_COMPLETE,  /* TAB */
185         '\17',0,                        EC_B_COMPLETE,  /* BACKTAB */
186         SK(SK_BACKTAB),0,               EC_B_COMPLETE,  /* BACKTAB */
187         ESC,'\t',0,                     EC_B_COMPLETE,  /* ESC TAB */
188         CONTROL('L'),0,                 EC_EXPAND,      /* CTRL-L */
189         CONTROL('V'),0,                 EC_LITERAL,     /* BACKSLASH */
190         CONTROL('A'),0,                 EC_LITERAL,     /* BACKSLASH */
191         ESC,'l',0,                      EC_RIGHT,       /* ESC l */
192         SK(SK_RIGHT_ARROW),0,           EC_RIGHT,       /* RIGHTARROW */
193         ESC,'h',0,                      EC_LEFT,        /* ESC h */
194         SK(SK_LEFT_ARROW),0,            EC_LEFT,        /* LEFTARROW */
195         ESC,'b',0,                      EC_W_LEFT,      /* ESC b */
196         ESC,SK(SK_LEFT_ARROW),0,        EC_W_LEFT,      /* ESC LEFTARROW */
197         SK(SK_CTL_LEFT_ARROW),0,        EC_W_LEFT,      /* CTRL-LEFTARROW */
198         ESC,'w',0,                      EC_W_RIGHT,     /* ESC w */
199         ESC,SK(SK_RIGHT_ARROW),0,       EC_W_RIGHT,     /* ESC RIGHTARROW */
200         SK(SK_CTL_RIGHT_ARROW),0,       EC_W_RIGHT,     /* CTRL-RIGHTARROW */
201         ESC,'i',0,                      EC_INSERT,      /* ESC i */
202         SK(SK_INSERT),0,                EC_INSERT,      /* INSERT */
203         ESC,'x',0,                      EC_DELETE,      /* ESC x */
204         SK(SK_DELETE),0,                EC_DELETE,      /* DELETE */
205         ESC,'X',0,                      EC_W_DELETE,    /* ESC X */
206         ESC,SK(SK_DELETE),0,            EC_W_DELETE,    /* ESC DELETE */
207         SK(SK_CTL_DELETE),0,            EC_W_DELETE,    /* CTRL-DELETE */
208         SK(SK_CTL_BACKSPACE),0,         EC_W_BACKSPACE, /* CTRL-BACKSPACE */
209         ESC,SK(SK_BACKSPACE),0,         EC_W_BACKSPACE, /* ESC BACKSPACE */
210         ESC,'0',0,                      EC_HOME,        /* ESC 0 */
211         SK(SK_HOME),0,                  EC_HOME,        /* HOME */
212         ESC,'$',0,                      EC_END,         /* ESC $ */
213         SK(SK_END),0,                   EC_END,         /* END */
214         ESC,'k',0,                      EC_UP,          /* ESC k */
215         SK(SK_UP_ARROW),0,              EC_UP,          /* UPARROW */
216         ESC,'j',0,                      EC_DOWN,        /* ESC j */
217         SK(SK_DOWN_ARROW),0,            EC_DOWN,        /* DOWNARROW */
218         CONTROL('G'),0,                 EC_ABORT,       /* CTRL-G */
219         ESC,'[','M',0,                  EC_X11MOUSE,    /* X11 mouse report */
220         ESC,'[','<',0,                  EC_X116MOUSE,   /* X11 1006 mouse report */
221 };
222
223 /*
224  * Structure to support a list of command tables.
225  */
226 struct tablelist
227 {
228         struct tablelist *t_next;
229         char *t_start;
230         char *t_end;
231 };
232
233 /*
234  * List of command tables and list of line-edit tables.
235  */
236 static struct tablelist *list_fcmd_tables = NULL;
237 static struct tablelist *list_ecmd_tables = NULL;
238 static struct tablelist *list_var_tables = NULL;
239 static struct tablelist *list_sysvar_tables = NULL;
240
241
242 /*
243  * Expand special key abbreviations in a command table.
244  */
245         static void
246 expand_special_keys(table, len)
247         char *table;
248         int len;
249 {
250         char *fm;
251         char *to;
252         int a;
253         char *repl;
254         int klen;
255
256         for (fm = table;  fm < table + len; )
257         {
258                 /*
259                  * Rewrite each command in the table with any
260                  * special key abbreviations expanded.
261                  */
262                 for (to = fm;  *fm != '\0'; )
263                 {
264                         if (*fm != SK_SPECIAL_KEY)
265                         {
266                                 *to++ = *fm++;
267                                 continue;
268                         }
269                         /*
270                          * After SK_SPECIAL_KEY, next byte is the type
271                          * of special key (one of the SK_* constants),
272                          * and the byte after that is the number of bytes,
273                          * N, reserved by the abbreviation (including the
274                          * SK_SPECIAL_KEY and key type bytes).
275                          * Replace all N bytes with the actual bytes
276                          * output by the special key on this terminal.
277                          */
278                         repl = special_key_str(fm[1]);
279                         klen = fm[2] & 0377;
280                         fm += klen;
281                         if (repl == NULL || (int) strlen(repl) > klen)
282                                 repl = "\377";
283                         while (*repl != '\0')
284                                 *to++ = *repl++;
285                 }
286                 *to++ = '\0';
287                 /*
288                  * Fill any unused bytes between end of command and 
289                  * the action byte with A_SKIP.
290                  */
291                 while (to <= fm)
292                         *to++ = A_SKIP;
293                 fm++;
294                 a = *fm++ & 0377;
295                 if (a & A_EXTRA)
296                 {
297                         while (*fm++ != '\0')
298                                 continue;
299                 }
300         }
301 }
302
303 /*
304  * Expand special key abbreviations in a list of command tables.
305  */
306         static void
307 expand_cmd_table(tlist)
308         struct tablelist *tlist;
309 {
310         struct tablelist *t;
311         for (t = tlist;  t != NULL;  t = t->t_next)
312         {
313                 expand_special_keys(t->t_start, t->t_end - t->t_start);
314         }
315 }
316
317 /*
318  * Expand special key abbreviations in all command tables.
319  */
320         public void
321 expand_cmd_tables(VOID_PARAM)
322 {
323         expand_cmd_table(list_fcmd_tables);
324         expand_cmd_table(list_ecmd_tables);
325         expand_cmd_table(list_var_tables);
326         expand_cmd_table(list_sysvar_tables);
327 }
328
329
330 /*
331  * Initialize the command lists.
332  */
333         public void
334 init_cmds(VOID_PARAM)
335 {
336         /*
337          * Add the default command tables.
338          */
339         add_fcmd_table((char*)cmdtable, sizeof(cmdtable));
340         add_ecmd_table((char*)edittable, sizeof(edittable));
341 #if USERFILE
342 #ifdef BINDIR /* For backwards compatibility */
343         /* Try to add tables in the OLD system lesskey file. */
344         add_hometable(lesskey, NULL, BINDIR "/.sysless", 1);
345 #endif
346         /*
347          * Try to load lesskey source file or binary file.
348          * If the source file succeeds, don't load binary file. 
349          * The binary file is likely to have been generated from 
350          * a (possibly out of date) copy of the src file, 
351          * so loading it is at best redundant.
352          */
353         /*
354          * Try to add tables in system lesskey src file.
355          */
356 #if HAVE_LESSKEYSRC 
357         if (add_hometable(lesskey_src, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS, 1) != 0)
358 #endif
359         {
360                 /*
361                  * Try to add the tables in the system lesskey binary file.
362                  */
363                 add_hometable(lesskey, "LESSKEY_SYSTEM", LESSKEYFILE_SYS, 1);
364         }
365         /*
366          * Try to add tables in the lesskey src file "$HOME/.lesskey".
367          */
368 #if HAVE_LESSKEYSRC 
369         if (add_hometable(lesskey_src, "LESSKEYIN", DEF_LESSKEYINFILE, 0) != 0)
370 #endif
371         {
372                 /*
373                  * Try to add the tables in the standard lesskey binary file "$HOME/.less".
374                  */
375                 add_hometable(lesskey, "LESSKEY", LESSKEYFILE, 0);
376         }
377 #endif
378 }
379
380 /*
381  * Add a command table.
382  */
383         static int
384 add_cmd_table(tlist, buf, len)
385         struct tablelist **tlist;
386         char *buf;
387         int len;
388 {
389         struct tablelist *t;
390
391         if (len == 0)
392                 return (0);
393         /*
394          * Allocate a tablelist structure, initialize it, 
395          * and link it into the list of tables.
396          */
397         if ((t = (struct tablelist *) 
398                         calloc(1, sizeof(struct tablelist))) == NULL)
399         {
400                 return (-1);
401         }
402         t->t_start = buf;
403         t->t_end = buf + len;
404         t->t_next = *tlist;
405         *tlist = t;
406         return (0);
407 }
408
409 /*
410  * Add a command table.
411  */
412         public void
413 add_fcmd_table(buf, len)
414         char *buf;
415         int len;
416 {
417         if (add_cmd_table(&list_fcmd_tables, buf, len) < 0)
418                 error("Warning: some commands disabled", NULL_PARG);
419 }
420
421 /*
422  * Add an editing command table.
423  */
424         public void
425 add_ecmd_table(buf, len)
426         char *buf;
427         int len;
428 {
429         if (add_cmd_table(&list_ecmd_tables, buf, len) < 0)
430                 error("Warning: some edit commands disabled", NULL_PARG);
431 }
432
433 /*
434  * Add an environment variable table.
435  */
436         static void
437 add_var_table(tlist, buf, len)
438         struct tablelist **tlist;
439         char *buf;
440         int len;
441 {
442         if (add_cmd_table(tlist, buf, len) < 0)
443                 error("Warning: environment variables from lesskey file unavailable", NULL_PARG);
444 }
445
446 /*
447  * Return action for a mouse wheel down event.
448  */
449         static int
450 mouse_wheel_down(VOID_PARAM)
451 {
452         return ((mousecap == OPT_ONPLUS) ? A_B_MOUSE : A_F_MOUSE);
453 }
454
455 /*
456  * Return action for a mouse wheel up event.
457  */
458         static int
459 mouse_wheel_up(VOID_PARAM)
460 {
461         return ((mousecap == OPT_ONPLUS) ? A_F_MOUSE : A_B_MOUSE);
462 }
463
464 /*
465  * Return action for a mouse button release event.
466  */
467         static int
468 mouse_button_rel(x, y)
469         int x;
470         int y;
471 {
472         /*
473          * {{ It would be better to return an action and then do this 
474          *    in commands() but it's nontrivial to pass y to it. }}
475          */
476         if (y < sc_height-1)
477         {
478                 setmark('#', y);
479                 screen_trashed = 1;
480         }
481         return (A_NOACTION);
482 }
483
484 /*
485  * Read a decimal integer. Return the integer and set *pterm to the terminating char.
486  */
487         static int
488 getcc_int(pterm)
489         char* pterm;
490 {
491         int num = 0;
492         int digits = 0;
493         for (;;)
494         {
495                 char ch = getcc();
496                 if (ch < '0' || ch > '9')
497                 {
498                         if (pterm != NULL) *pterm = ch;
499                         if (digits == 0)
500                                 return (-1);
501                         return (num);
502                 }
503                 num = (10 * num) + (ch - '0');
504                 ++digits;
505         }
506 }
507
508 /*
509  * Read suffix of mouse input and return the action to take.
510  * The prefix ("\e[M") has already been read.
511  */
512         static int
513 x11mouse_action(skip)
514         int skip;
515 {
516         int b = getcc() - X11MOUSE_OFFSET;
517         int x = getcc() - X11MOUSE_OFFSET-1;
518         int y = getcc() - X11MOUSE_OFFSET-1;
519         if (skip)
520                 return (A_NOACTION);
521         switch (b) {
522         default:
523                 return (A_NOACTION);
524         case X11MOUSE_WHEEL_DOWN:
525                 return mouse_wheel_down();
526         case X11MOUSE_WHEEL_UP:
527                 return mouse_wheel_up();
528         case X11MOUSE_BUTTON_REL:
529                 return mouse_button_rel(x, y);
530         }
531 }
532
533 /*
534  * Read suffix of mouse input and return the action to take.
535  * The prefix ("\e[<") has already been read.
536  */
537         static int
538 x116mouse_action(skip)
539         int skip;
540 {
541         char ch;
542         int x, y;
543         int b = getcc_int(&ch);
544         if (b < 0 || ch != ';') return (A_NOACTION);
545         x = getcc_int(&ch) - 1;
546         if (x < 0 || ch != ';') return (A_NOACTION);
547         y = getcc_int(&ch) - 1;
548         if (y < 0) return (A_NOACTION);
549         if (skip)
550                 return (A_NOACTION);
551         switch (b) {
552         case X11MOUSE_WHEEL_DOWN:
553                 return mouse_wheel_down();
554         case X11MOUSE_WHEEL_UP:
555                 return mouse_wheel_up();
556         default:
557                 if (ch != 'm') return (A_NOACTION);
558                 return mouse_button_rel(x, y);
559         }
560 }
561
562 /*
563  * Search a single command table for the command string in cmd.
564  */
565         static int
566 cmd_search(cmd, table, endtable, sp)
567         char *cmd;
568         char *table;
569         char *endtable;
570         char **sp;
571 {
572         char *p;
573         char *q;
574         int a;
575
576         *sp = NULL;
577         for (p = table, q = cmd;  p < endtable;  p++, q++)
578         {
579                 if (*p == *q)
580                 {
581                         /*
582                          * Current characters match.
583                          * If we're at the end of the string, we've found it.
584                          * Return the action code, which is the character
585                          * after the null at the end of the string
586                          * in the command table.
587                          */
588                         if (*p == '\0')
589                         {
590                                 a = *++p & 0377;
591                                 while (a == A_SKIP)
592                                         a = *++p & 0377;
593                                 if (a == A_END_LIST)
594                                 {
595                                         /*
596                                          * We get here only if the original
597                                          * cmd string passed in was empty ("").
598                                          * I don't think that can happen,
599                                          * but just in case ...
600                                          */
601                                         return (A_UINVALID);
602                                 }
603                                 /*
604                                  * Check for an "extra" string.
605                                  */
606                                 if (a & A_EXTRA)
607                                 {
608                                         *sp = ++p;
609                                         a &= ~A_EXTRA;
610                                 }
611                                 if (a == A_X11MOUSE_IN)
612                                         a = x11mouse_action(0);
613                                 else if (a == A_X116MOUSE_IN)
614                                         a = x116mouse_action(0);
615                                 return (a);
616                         }
617                 } else if (*q == '\0')
618                 {
619                         /*
620                          * Hit the end of the user's command,
621                          * but not the end of the string in the command table.
622                          * The user's command is incomplete.
623                          */
624                         return (A_PREFIX);
625                 } else
626                 {
627                         /*
628                          * Not a match.
629                          * Skip ahead to the next command in the
630                          * command table, and reset the pointer
631                          * to the beginning of the user's command.
632                          */
633                         if (*p == '\0' && p[1] == A_END_LIST)
634                         {
635                                 /*
636                                  * A_END_LIST is a special marker that tells 
637                                  * us to abort the cmd search.
638                                  */
639                                 return (A_UINVALID);
640                         }
641                         while (*p++ != '\0')
642                                 continue;
643                         while (*p == A_SKIP)
644                                 p++;
645                         if (*p & A_EXTRA)
646                                 while (*++p != '\0')
647                                         continue;
648                         q = cmd-1;
649                 }
650         }
651         /*
652          * No match found in the entire command table.
653          */
654         return (A_INVALID);
655 }
656
657 /*
658  * Decode a command character and return the associated action.
659  * The "extra" string, if any, is returned in sp.
660  */
661         static int
662 cmd_decode(tlist, cmd, sp)
663         struct tablelist *tlist;
664         char *cmd;
665         char **sp;
666 {
667         struct tablelist *t;
668         int action = A_INVALID;
669
670         /*
671          * Search thru all the command tables.
672          * Stop when we find an action which is not A_INVALID.
673          */
674         for (t = tlist;  t != NULL;  t = t->t_next)
675         {
676                 action = cmd_search(cmd, t->t_start, t->t_end, sp);
677                 if (action != A_INVALID)
678                         break;
679         }
680         if (action == A_UINVALID)
681                 action = A_INVALID;
682         return (action);
683 }
684
685 /*
686  * Decode a command from the cmdtables list.
687  */
688         public int
689 fcmd_decode(cmd, sp)
690         char *cmd;
691         char **sp;
692 {
693         return (cmd_decode(list_fcmd_tables, cmd, sp));
694 }
695
696 /*
697  * Decode a command from the edittables list.
698  */
699         public int
700 ecmd_decode(cmd, sp)
701         char *cmd;
702         char **sp;
703 {
704         return (cmd_decode(list_ecmd_tables, cmd, sp));
705 }
706
707 /*
708  * Get the value of an environment variable.
709  * Looks first in the lesskey file, then in the real environment.
710  */
711         public char *
712 lgetenv(var)
713         char *var;
714 {
715         int a;
716         char *s;
717
718         a = cmd_decode(list_var_tables, var, &s);
719         if (a == EV_OK)
720                 return (s);
721         s = getenv(var);
722         if (s != NULL && *s != '\0')
723                 return (s);
724         a = cmd_decode(list_sysvar_tables, var, &s);
725         if (a == EV_OK)
726                 return (s);
727         return (NULL);
728 }
729
730 /*
731  * Is a string null or empty? 
732  */
733         public int
734 isnullenv(s)
735         char* s;
736 {
737         return (s == NULL || *s == '\0');
738 }
739
740 #if USERFILE
741 /*
742  * Get an "integer" from a lesskey file.
743  * Integers are stored in a funny format: 
744  * two bytes, low order first, in radix KRADIX.
745  */
746         static int
747 gint(sp)
748         char **sp;
749 {
750         int n;
751
752         n = *(*sp)++;
753         n += *(*sp)++ * KRADIX;
754         return (n);
755 }
756
757 /*
758  * Process an old (pre-v241) lesskey file.
759  */
760         static int
761 old_lesskey(buf, len)
762         char *buf;
763         int len;
764 {
765         /*
766          * Old-style lesskey file.
767          * The file must end with either 
768          *     ...,cmd,0,action
769          * or  ...,cmd,0,action|A_EXTRA,string,0
770          * So the last byte or the second to last byte must be zero.
771          */
772         if (buf[len-1] != '\0' && buf[len-2] != '\0')
773                 return (-1);
774         add_fcmd_table(buf, len);
775         return (0);
776 }
777
778 /* 
779  * Process a new (post-v241) lesskey file.
780  */
781         static int
782 new_lesskey(buf, len, sysvar)
783         char *buf;
784         int len;
785         int sysvar;
786 {
787         char *p;
788         char *end;
789         int c;
790         int n;
791
792         /*
793          * New-style lesskey file.
794          * Extract the pieces.
795          */
796         if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
797             buf[len-2] != C1_END_LESSKEY_MAGIC ||
798             buf[len-1] != C2_END_LESSKEY_MAGIC)
799                 return (-1);
800         p = buf + 4;
801         end = buf + len;
802         for (;;)
803         {
804                 c = *p++;
805                 switch (c)
806                 {
807                 case CMD_SECTION:
808                         n = gint(&p);
809                         if (n < 0 || p+n >= end)
810                                 return (-1);
811                         add_fcmd_table(p, n);
812                         p += n;
813                         break;
814                 case EDIT_SECTION:
815                         n = gint(&p);
816                         if (n < 0 || p+n >= end)
817                                 return (-1);
818                         add_ecmd_table(p, n);
819                         p += n;
820                         break;
821                 case VAR_SECTION:
822                         n = gint(&p);
823                         if (n < 0 || p+n >= end)
824                                 return (-1);
825                         add_var_table((sysvar) ? 
826                                 &list_sysvar_tables : &list_var_tables, p, n);
827                         p += n;
828                         break;
829                 case END_SECTION:
830                         return (0);
831                 default:
832                         /*
833                          * Unrecognized section type.
834                          */
835                         return (-1);
836                 }
837         }
838 }
839
840 /*
841  * Set up a user command table, based on a "lesskey" file.
842  */
843         public int
844 lesskey(filename, sysvar)
845         char *filename;
846         int sysvar;
847 {
848         char *buf;
849         POSITION len;
850         long n;
851         int f;
852
853         if (secure)
854                 return (1);
855         /*
856          * Try to open the lesskey file.
857          */
858         f = open(filename, OPEN_READ);
859         if (f < 0)
860                 return (1);
861
862         /*
863          * Read the file into a buffer.
864          * We first figure out the size of the file and allocate space for it.
865          * {{ Minimal error checking is done here.
866          *    A garbage .less file will produce strange results.
867          *    To avoid a large amount of error checking code here, we
868          *    rely on the lesskey program to generate a good .less file. }}
869          */
870         len = filesize(f);
871         if (len == NULL_POSITION || len < 3)
872         {
873                 /*
874                  * Bad file (valid file must have at least 3 chars).
875                  */
876                 close(f);
877                 return (-1);
878         }
879         if ((buf = (char *) calloc((int)len, sizeof(char))) == NULL)
880         {
881                 close(f);
882                 return (-1);
883         }
884         if (lseek(f, (off_t)0, SEEK_SET) == BAD_LSEEK)
885         {
886                 free(buf);
887                 close(f);
888                 return (-1);
889         }
890         n = read(f, buf, (unsigned int) len);
891         close(f);
892         if (n != len)
893         {
894                 free(buf);
895                 return (-1);
896         }
897
898         /*
899          * Figure out if this is an old-style (before version 241)
900          * or new-style lesskey file format.
901          */
902         if (len < 4 || 
903             buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
904             buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
905                 return (old_lesskey(buf, (int)len));
906         return (new_lesskey(buf, (int)len, sysvar));
907 }
908
909 #if HAVE_LESSKEYSRC 
910         public int
911 lesskey_src(filename, sysvar)
912         char *filename;
913         int sysvar;
914 {
915         static struct lesskey_tables tables;
916         int r = parse_lesskey(filename, &tables);
917         if (r != 0)
918                 return (r);
919         add_fcmd_table(tables.cmdtable.buf.data, tables.cmdtable.buf.end);
920         add_ecmd_table(tables.edittable.buf.data, tables.edittable.buf.end);
921         add_var_table(sysvar ? &list_sysvar_tables : &list_var_tables,
922                 tables.vartable.buf.data, tables.vartable.buf.end);
923         return (0);
924 }
925
926         void
927 lesskey_parse_error(s)
928         char *s;
929 {
930         PARG parg;
931         parg.p_string = s;
932         error("%s", &parg);
933 }
934 #endif /* HAVE_LESSKEYSRC */
935
936 /*
937  * Add a lesskey file.
938  */
939         public int
940 add_hometable(call_lesskey, envname, def_filename, sysvar)
941         int (*call_lesskey)(char *, int);
942         char *envname;
943         char *def_filename;
944         int sysvar;
945 {
946         char *filename;
947         int r;
948
949         if (envname != NULL && (filename = lgetenv(envname)) != NULL)
950                 filename = save(filename);
951         else if (sysvar) /* def_filename is full path */
952                 filename = save(def_filename);
953         else /* def_filename is just basename */
954         {
955                 /* Remove first char (normally a dot) unless stored in $HOME. */
956                 char *xdg = lgetenv("XDG_CONFIG_HOME");
957                 if (!isnullenv(xdg))
958                         filename = dirfile(xdg, &def_filename[1], 1);
959                 if (filename == NULL)
960                 {
961                         char *home = lgetenv("HOME");
962                         if (!isnullenv(home))
963                         {
964                                 char *cfg_dir = dirfile(home, ".config", 0);
965                                 filename = dirfile(cfg_dir, &def_filename[1], 1);
966                                 free(cfg_dir);
967                         }
968                 }
969                 if (filename == NULL)
970                         filename = homefile(def_filename);
971         }
972         if (filename == NULL)
973                 return -1;
974         r = (*call_lesskey)(filename, sysvar);
975         free(filename);
976         return (r);
977 }
978 #endif
979
980 /*
981  * See if a char is a special line-editing command.
982  */
983         public int
984 editchar(c, flags)
985         int c;
986         int flags;
987 {
988         int action;
989         int nch;
990         char *s;
991         char usercmd[MAX_CMDLEN+1];
992         
993         /*
994          * An editing character could actually be a sequence of characters;
995          * for example, an escape sequence sent by pressing the uparrow key.
996          * To match the editing string, we use the command decoder
997          * but give it the edit-commands command table
998          * This table is constructed to match the user's keyboard.
999          */
1000         if (c == erase_char || c == erase2_char)
1001                 return (EC_BACKSPACE);
1002         if (c == kill_char)
1003         {
1004 #if MSDOS_COMPILER==WIN32C
1005                 if (!win32_kbhit())
1006 #endif
1007                 return (EC_LINEKILL);
1008         }
1009                 
1010         /*
1011          * Collect characters in a buffer.
1012          * Start with the one we have, and get more if we need them.
1013          */
1014         nch = 0;
1015         do {
1016                 if (nch > 0)
1017                         c = getcc();
1018                 usercmd[nch] = c;
1019                 usercmd[nch+1] = '\0';
1020                 nch++;
1021                 action = ecmd_decode(usercmd, &s);
1022         } while (action == A_PREFIX && nch < MAX_CMDLEN);
1023
1024         if (action == EC_X11MOUSE)
1025                 return (x11mouse_action(1));
1026         if (action == EC_X116MOUSE)
1027                 return (x116mouse_action(1));
1028
1029         if (flags & ECF_NORIGHTLEFT)
1030         {
1031                 switch (action)
1032                 {
1033                 case EC_RIGHT:
1034                 case EC_LEFT:
1035                         action = A_INVALID;
1036                         break;
1037                 }
1038         }
1039 #if CMD_HISTORY
1040         if (flags & ECF_NOHISTORY) 
1041         {
1042                 /*
1043                  * The caller says there is no history list.
1044                  * Reject any history-manipulation action.
1045                  */
1046                 switch (action)
1047                 {
1048                 case EC_UP:
1049                 case EC_DOWN:
1050                         action = A_INVALID;
1051                         break;
1052                 }
1053         }
1054 #endif
1055 #if TAB_COMPLETE_FILENAME
1056         if (flags & ECF_NOCOMPLETE) 
1057         {
1058                 /*
1059                  * The caller says we don't want any filename completion cmds.
1060                  * Reject them.
1061                  */
1062                 switch (action)
1063                 {
1064                 case EC_F_COMPLETE:
1065                 case EC_B_COMPLETE:
1066                 case EC_EXPAND:
1067                         action = A_INVALID;
1068                         break;
1069                 }
1070         }
1071 #endif
1072         if ((flags & ECF_PEEK) || action == A_INVALID)
1073         {
1074                 /*
1075                  * We're just peeking, or we didn't understand the command.
1076                  * Unget all the characters we read in the loop above.
1077                  * This does NOT include the original character that was 
1078                  * passed in as a parameter.
1079                  */
1080                 while (nch > 1) 
1081                 {
1082                         ungetcc(usercmd[--nch]);
1083                 }
1084         } else
1085         {
1086                 if (s != NULL)
1087                         ungetsc(s);
1088         }
1089         return action;
1090 }
1091