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