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