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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 497 public void add_uvar_table(unsigned char *buf, size_t len) 498 { 499 add_var_table(&list_var_tables, buf, len); 500 } 501 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 && action == A_INVALID) /* 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 1066 public int lesskey_src(constant char *filename, lbool sysvar) 1067 { 1068 return lesskey_text(filename, sysvar, FALSE); 1069 } 1070 1071 public int lesskey_content(constant char *content, lbool sysvar) 1072 { 1073 return lesskey_text(content, sysvar, TRUE); 1074 } 1075 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 */ 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 */ 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 */ 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