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