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