1 /* 2 * Copyright (C) 1984-2026 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 #include "less.h" 11 #include "lesskey.h" 12 #include "cmd.h" 13 14 #if USERFILE 15 16 #define CONTROL(c) ((c)&037) 17 #define ESC CONTROL('[') 18 19 struct lesskey_cmdname 20 { 21 constant char *cn_name; 22 int cn_action; 23 }; 24 25 extern char version[]; 26 27 static int linenum; 28 static int errors; 29 static int less_version = 0; 30 static char *lesskey_file = NULL; 31 32 static constant struct lesskey_cmdname cmdnames[] = 33 { 34 { "back-bracket", A_B_BRACKET }, 35 { "back-line", A_B_LINE }, 36 { "back-line-force", A_BF_LINE }, 37 { "back-newline", A_B_NEWLINE }, 38 { "back-screen", A_B_SCREEN }, 39 { "back-screen-force", A_BF_SCREEN }, 40 { "back-scroll", A_B_SCROLL }, 41 { "back-search", A_B_SEARCH }, 42 { "back-window", A_B_WINDOW }, 43 { "clear-mark", A_CLRMARK }, 44 { "clear-search", A_CLR_SEARCH }, 45 { "debug", A_DEBUG }, 46 { "digit", A_DIGIT }, 47 { "display-flag", A_DISP_OPTION }, 48 { "display-option", A_DISP_OPTION }, 49 { "end", A_GOEND }, 50 { "end-scroll", A_RRSHIFT }, 51 { "examine", A_EXAMINE }, 52 { "filter", A_FILTER }, 53 { "first-cmd", A_FIRSTCMD }, 54 { "firstcmd", A_FIRSTCMD }, 55 { "flush-repaint", A_FREPAINT }, 56 { "forw-bell-hilite", A_F_FOREVER_BELL }, 57 { "forw-bracket", A_F_BRACKET }, 58 { "forw-forever", A_F_FOREVER }, 59 { "forw-line", A_F_LINE }, 60 { "forw-line-force", A_FF_LINE }, 61 { "forw-newline", A_F_NEWLINE }, 62 { "forw-screen", A_F_SCREEN }, 63 { "forw-screen-force", A_FF_SCREEN }, 64 { "forw-scroll", A_F_SCROLL }, 65 { "forw-search", A_F_SEARCH }, 66 { "forw-until-hilite", A_F_UNTIL_HILITE }, 67 { "forw-window", A_F_WINDOW }, 68 { "goto-end", A_GOEND }, 69 { "goto-end-buffered", A_GOEND_BUF }, 70 { "goto-line", A_GOLINE }, 71 { "goto-mark", A_GOMARK }, 72 { "goto-pos", A_GOPOS }, 73 { "help", A_HELP }, 74 { "index-file", A_INDEX_FILE }, 75 { "invalid", A_UINVALID }, 76 { "left-scroll", A_LSHIFT }, 77 { "mouse", A_X11MOUSE_IN }, 78 { "mouse6", A_X116MOUSE_IN }, 79 { "next-file", A_NEXT_FILE }, 80 { "next-tag", A_NEXT_TAG }, 81 { "no-scroll", A_LLSHIFT }, 82 { "noaction", A_NOACTION }, 83 { "osc8-forw-search", A_OSC8_F_SEARCH }, 84 { "osc8-back-search", A_OSC8_B_SEARCH }, 85 { "osc8-jump", A_OSC8_JUMP }, 86 { "osc8-open", A_OSC8_OPEN }, 87 { "percent", A_PERCENT }, 88 { "pipe", A_PIPE }, 89 { "prev-file", A_PREV_FILE }, 90 { "prev-tag", A_PREV_TAG }, 91 { "pshell", A_PSHELL }, 92 { "quit", A_QUIT }, 93 { "remove-file", A_REMOVE_FILE }, 94 { "repaint", A_REPAINT }, 95 { "repaint-flush", A_FREPAINT }, 96 { "repeat-search", A_AGAIN_SEARCH }, 97 { "repeat-search-all", A_T_AGAIN_SEARCH }, 98 { "reverse-search", A_REVERSE_SEARCH }, 99 { "reverse-search-all", A_T_REVERSE_SEARCH }, 100 { "right-scroll", A_RSHIFT }, 101 { "right-limit-scroll", A_RSHIFT_LIMIT }, 102 { "set-mark", A_SETMARK }, 103 { "set-mark-bottom", A_SETMARKBOT }, 104 { "shell", A_SHELL }, 105 { "status", A_STAT }, 106 { "toggle-flag", A_OPT_TOGGLE }, 107 { "toggle-option", A_OPT_TOGGLE }, 108 { "undo-hilite", A_UNDO_SEARCH }, 109 { "version", A_VERSION }, 110 { "visual", A_VISUAL }, 111 { NULL, 0 } 112 }; 113 114 static constant struct lesskey_cmdname editnames[] = 115 { 116 { "back-complete", EC_B_COMPLETE }, 117 { "backspace", EC_BACKSPACE }, 118 { "delete", EC_DELETE }, 119 { "down", EC_DOWN }, 120 { "end", EC_END }, 121 { "expand", EC_EXPAND }, 122 { "forw-complete", EC_F_COMPLETE }, 123 { "home", EC_HOME }, 124 { "insert", EC_INSERT }, 125 { "invalid", EC_UINVALID }, 126 { "kill-line", EC_LINEKILL }, 127 { "abort", EC_ABORT }, 128 { "left", EC_LEFT }, 129 { "literal", EC_LITERAL }, 130 { "mouse", EC_X11MOUSE }, 131 { "mouse6", EC_X116MOUSE }, 132 { "noaction", A_NOACTION }, 133 { "right", EC_RIGHT }, 134 { "up", EC_UP }, 135 { "word-backspace", EC_W_BACKSPACE }, 136 { "word-delete", EC_W_DELETE }, 137 { "word-left", EC_W_LEFT }, 138 { "word-right", EC_W_RIGHT }, 139 { NULL, 0 } 140 }; 141 142 /* 143 * Print a parse error message. 144 */ 145 static void parse_error(constant char *fmt, constant char *arg1) 146 { 147 char buf[1024]; 148 int n = SNPRINTF2(buf, sizeof(buf), "%s: line %d: ", lesskey_file, linenum); 149 if (n >= 0) 150 { 151 size_t len = (size_t) n; 152 if (len < sizeof(buf)) 153 SNPRINTF1(buf+len, sizeof(buf)-len, fmt, arg1); 154 } 155 ++errors; 156 lesskey_parse_error(buf); 157 } 158 159 /* 160 * Initialize lesskey_tables. 161 */ 162 static void init_tables(struct lesskey_tables *tables) 163 { 164 tables->currtable = &tables->cmdtable; 165 166 tables->cmdtable.names = cmdnames; 167 tables->cmdtable.is_var = 0; 168 xbuf_init(&tables->cmdtable.buf); 169 170 tables->edittable.names = editnames; 171 tables->edittable.is_var = 0; 172 xbuf_init(&tables->edittable.buf); 173 174 tables->vartable.names = NULL; 175 tables->vartable.is_var = 1; 176 xbuf_init(&tables->vartable.buf); 177 } 178 179 #define CHAR_STRING_LEN 8 180 181 static constant char * char_string(char *buf, char ch, int lit) 182 { 183 if (lit || (ch >= 0x20 && ch < 0x7f)) 184 { 185 buf[0] = ch; 186 buf[1] = '\0'; 187 } else 188 { 189 SNPRINTF1(buf, CHAR_STRING_LEN, "\\x%02x", ch); 190 } 191 return buf; 192 } 193 194 /* 195 * Increment char pointer by one up to terminating nul byte. 196 */ 197 static char * increment_pointer(char *p) 198 { 199 if (*p == '\0') 200 return p; 201 return p+1; 202 } 203 204 /* 205 * Parse one character of a string. 206 */ 207 static constant char * tstr(char **pp, int xlate) 208 { 209 char *p; 210 char ch; 211 int i; 212 static char buf[CHAR_STRING_LEN]; 213 static char tstr_control_k[] = 214 { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' }; 215 216 p = *pp; 217 switch (*p) 218 { 219 case '\\': 220 ++p; 221 switch (*p) 222 { 223 case '0': case '1': case '2': case '3': 224 case '4': case '5': case '6': case '7': 225 /* 226 * Parse an octal number. 227 */ 228 ch = 0; 229 i = 0; 230 do 231 ch = (char) (8*ch + (*p - '0')); 232 while (*++p >= '0' && *p <= '7' && ++i < 3); 233 *pp = p; 234 if (xlate && ch == CONTROL('K')) 235 return tstr_control_k; 236 return char_string(buf, ch, 1); 237 case 'b': 238 *pp = p+1; 239 return ("\b"); 240 case 'e': 241 *pp = p+1; 242 return char_string(buf, ESC, 1); 243 case 'n': 244 *pp = p+1; 245 return ("\n"); 246 case 'r': 247 *pp = p+1; 248 return ("\r"); 249 case 't': 250 *pp = p+1; 251 return ("\t"); 252 case 'k': 253 if (xlate) 254 { 255 char *seq = p-1; 256 ch = 0; 257 switch (*++p) 258 { 259 case 'b': ch = SK_BACKSPACE; break; 260 case 'B': ch = SK_CTL_BACKSPACE; break; 261 case 'd': ch = SK_DOWN_ARROW; break; 262 case 'D': ch = SK_PAGE_DOWN; break; 263 case 'e': ch = SK_END; break; 264 case 'E': ch = SK_CTL_END; break; 265 case 'F': ch = SK_SHIFT_END; break; 266 case 'h': ch = SK_HOME; break; 267 case 'H': ch = SK_CTL_HOME; break; 268 case 'I': ch = SK_SHIFT_HOME; break; 269 case 'i': ch = SK_INSERT; break; 270 case 'l': ch = SK_LEFT_ARROW; break; 271 case 'L': ch = SK_CTL_LEFT_ARROW; break; 272 case 'M': ch = SK_SHIFT_LEFT_ARROW; break; 273 case 'r': ch = SK_RIGHT_ARROW; break; 274 case 'R': ch = SK_CTL_RIGHT_ARROW; break; 275 case 'S': ch = SK_SHIFT_RIGHT_ARROW; break; 276 case 't': ch = SK_BACKTAB; break; 277 case 'u': ch = SK_UP_ARROW; break; 278 case 'U': ch = SK_PAGE_UP; break; 279 case 'x': ch = SK_DELETE; break; 280 case 'X': ch = SK_CTL_DELETE; break; 281 case '1': ch = SK_F1; break; 282 case '^': 283 switch (*++p) 284 { 285 case 'b': ch = SK_CTL_BACKSPACE; break; 286 case 'd': ch = SK_CTL_DOWN_ARROW; break; 287 case 'D': ch = SK_CTL_PAGE_DOWN; break; 288 case 'e': ch = SK_CTL_END; break; 289 case 'h': ch = SK_CTL_HOME; break; 290 case 'l': ch = SK_CTL_LEFT_ARROW; break; 291 case 'r': ch = SK_CTL_RIGHT_ARROW; break; 292 case 'u': ch = SK_CTL_UP_ARROW; break; 293 case 'U': ch = SK_CTL_PAGE_UP; break; 294 case 'x': ch = SK_CTL_DELETE; break; 295 } 296 break; 297 case '+': 298 switch (*++p) 299 { 300 case 'd': ch = SK_SHIFT_DOWN_ARROW; break; 301 case 'D': ch = SK_SHIFT_PAGE_DOWN; break; 302 case 'e': ch = SK_SHIFT_END; break; 303 case 'h': ch = SK_SHIFT_HOME; break; 304 case 'l': ch = SK_SHIFT_LEFT_ARROW; break; 305 case 'r': ch = SK_SHIFT_RIGHT_ARROW; break; 306 case 'u': ch = SK_SHIFT_UP_ARROW; break; 307 case 'U': ch = SK_SHIFT_PAGE_UP; break; 308 case 'x': ch = SK_SHIFT_DELETE; break; 309 } 310 break; 311 case 'p': 312 switch (*++p) 313 { 314 case '1': ch = SK_PAD_DL; break; 315 case '2': ch = SK_PAD_D; break; 316 case '3': ch = SK_PAD_DR; break; 317 case '4': ch = SK_PAD_L; break; 318 case '5': ch = SK_PAD_CENTER; break; 319 case '6': ch = SK_PAD_R; break; 320 case '7': ch = SK_PAD_UL; break; 321 case '8': ch = SK_PAD_U; break; 322 case '9': ch = SK_PAD_UR; break; 323 case '0': ch = SK_PAD_ZERO; break; 324 case '*': ch = SK_PAD_STAR; break; 325 case '/': ch = SK_PAD_SLASH; break; 326 case '-': ch = SK_PAD_DASH; break; 327 case '+': ch = SK_PAD_PLUS; break; 328 case '.': ch = SK_PAD_DOT; break; 329 case ',': ch = SK_PAD_COMMA; break; 330 case 'e': ch = SK_PAD_ENTER; break; 331 } 332 break; 333 } 334 if (ch == 0) 335 { 336 char seq_buf[8]; 337 size_t len = p - seq + 1; 338 strncpy(seq_buf, seq, len); 339 seq_buf[len] = '\0'; 340 parse_error(LM(invalid_escape_sequence), seq_buf); 341 *pp = increment_pointer(p); 342 return (""); 343 } 344 *pp = p+1; 345 buf[0] = SK_SPECIAL_KEY; 346 buf[1] = ch; 347 buf[2] = 6; 348 buf[3] = 1; 349 buf[4] = 1; 350 buf[5] = 1; 351 buf[6] = '\0'; 352 return (buf); 353 } 354 /* FALLTHRU */ 355 default: 356 /* 357 * Backslash followed by any other char 358 * just means that char. 359 */ 360 *pp = increment_pointer(p); 361 char_string(buf, *p, 1); 362 if (xlate && buf[0] == CONTROL('K')) 363 return tstr_control_k; 364 return (buf); 365 } 366 case '^': 367 /* 368 * Caret means CONTROL. 369 */ 370 *pp = increment_pointer(p+1); 371 char_string(buf, CONTROL(p[1]), 1); 372 if (xlate && buf[0] == CONTROL('K')) 373 return tstr_control_k; 374 return (buf); 375 } 376 *pp = increment_pointer(p); 377 char_string(buf, *p, 1); 378 if (xlate && buf[0] == CONTROL('K')) 379 return tstr_control_k; 380 return (buf); 381 } 382 383 static int issp(char ch) 384 { 385 return (ch == ' ' || ch == '\t'); 386 } 387 388 /* 389 * Skip non-space characters in a string. 390 */ 391 static char * skipnsp(char *s) 392 { 393 while (*s != '\0' && !issp(*s)) 394 s++; 395 return (s); 396 } 397 398 /* 399 * Clean up an input line: 400 * strip off the trailing newline & any trailing # comment. 401 */ 402 static char * clean_line(char *s) 403 { 404 int i; 405 406 s = skipsp(s); 407 for (i = 0; s[i] != '\0' && s[i] != '\n' && s[i] != '\r'; i++) 408 if (s[i] == '#' && (i == 0 || s[i-1] != '\\')) 409 break; 410 s[i] = '\0'; 411 return (s); 412 } 413 414 /* 415 * Add a byte to the output command table. 416 */ 417 static void add_cmd_char(unsigned char c, struct lesskey_tables *tables) 418 { 419 xbuf_add_byte(&tables->currtable->buf, c); 420 } 421 422 static void erase_cmd_char(struct lesskey_tables *tables) 423 { 424 xbuf_pop(&tables->currtable->buf); 425 } 426 427 /* 428 * Add a string to the output command table. 429 */ 430 static void add_cmd_str(constant char *s, struct lesskey_tables *tables) 431 { 432 for ( ; *s != '\0'; s++) 433 add_cmd_char((unsigned char) *s, tables); 434 } 435 436 /* 437 * Does a given version number match the running version? 438 * Operator compares the running version to the given version. 439 */ 440 static int match_version(char op, int ver) 441 { 442 switch (op) 443 { 444 case '>': return less_version > ver; 445 case '<': return less_version < ver; 446 case '+': return less_version >= ver; 447 case '-': return less_version <= ver; 448 case '=': return less_version == ver; 449 case '!': return less_version != ver; 450 default: return 0; /* cannot happen */ 451 } 452 } 453 454 /* 455 * Handle a #version line. 456 * If the version matches, return the part of the line that should be executed. 457 * Otherwise, return NULL. 458 */ 459 static char * version_line(char *s) 460 { 461 char op; 462 int ver; 463 char *e; 464 char buf[CHAR_STRING_LEN]; 465 466 s += strlen("#version"); 467 s = skipsp(s); 468 op = *s++; 469 /* Simplify 2-char op to one char. */ 470 switch (op) 471 { 472 case '<': if (*s == '=') { s++; op = '-'; } break; 473 case '>': if (*s == '=') { s++; op = '+'; } break; 474 case '=': if (*s == '=') { s++; } break; 475 case '!': if (*s == '=') { s++; } break; 476 default: 477 parse_error(LM(invalid_operator_X_in_version_line), char_string(buf, op, 0)); 478 return (NULL); 479 } 480 s = skipsp(s); 481 ver = lstrtoi(s, &e, 10); 482 if (e == s) 483 { 484 parse_error(LM(non_numeric_version_number_in_version_line), ""); 485 return (NULL); 486 } 487 if (!match_version(op, ver)) 488 return (NULL); 489 return (e); 490 } 491 492 /* 493 * See if we have a special "control" line. 494 */ 495 static char * control_line(char *s, struct lesskey_tables *tables) 496 { 497 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)) == 0) 498 499 if (PREFIX(s, "#line-edit")) 500 { 501 tables->currtable = &tables->edittable; 502 return (NULL); 503 } 504 if (PREFIX(s, "#command")) 505 { 506 tables->currtable = &tables->cmdtable; 507 return (NULL); 508 } 509 if (PREFIX(s, "#env")) 510 { 511 tables->currtable = &tables->vartable; 512 return (NULL); 513 } 514 if (PREFIX(s, "#stop")) 515 { 516 add_cmd_char('\0', tables); 517 add_cmd_char(A_END_LIST, tables); 518 return (NULL); 519 } 520 if (PREFIX(s, "#version")) 521 { 522 return (version_line(s)); 523 } 524 return (s); 525 } 526 527 /* 528 * Find an action, given the name of the action. 529 */ 530 static int findaction(char *actname, struct lesskey_tables *tables) 531 { 532 int i; 533 534 for (i = 0; tables->currtable->names[i].cn_name != NULL; i++) 535 if (strcmp(tables->currtable->names[i].cn_name, actname) == 0) 536 return (tables->currtable->names[i].cn_action); 537 parse_error(LM(unknown_action_X), actname); 538 return (A_INVALID); 539 } 540 541 /* 542 * Parse a line describing one key binding, of the form 543 * KEY ACTION [EXTRA] 544 * where KEY is the user key sequence, ACTION is the 545 * resulting less action, and EXTRA is an "extra" user 546 * key sequence injected after the action. 547 */ 548 static void parse_cmdline(char *p, struct lesskey_tables *tables) 549 { 550 char *actname; 551 int action; 552 constant char *s; 553 char c; 554 555 /* 556 * Parse the command string and store it in the current table. 557 */ 558 do 559 { 560 s = tstr(&p, 1); 561 add_cmd_str(s, tables); 562 } while (*p != '\0' && !issp(*p)); 563 /* 564 * Terminate the command string with a null byte. 565 */ 566 add_cmd_char('\0', tables); 567 568 /* 569 * Skip white space between the command string 570 * and the action name. 571 * Terminate the action name with a null byte. 572 */ 573 p = skipsp(p); 574 if (*p == '\0') 575 { 576 parse_error(LM(missing_action), ""); 577 return; 578 } 579 actname = p; 580 p = skipnsp(p); 581 c = *p; 582 *p = '\0'; 583 584 /* 585 * Parse the action name and store it in the current table. 586 */ 587 action = findaction(actname, tables); 588 589 /* 590 * See if an extra string follows the action name. 591 */ 592 *p = c; 593 p = skipsp(p); 594 if (*p == '\0') 595 { 596 add_cmd_char((unsigned char) action, tables); 597 } else 598 { 599 /* 600 * OR the special value A_EXTRA into the action byte. 601 * Put the extra string after the action byte. 602 */ 603 add_cmd_char((unsigned char) (action | A_EXTRA), tables); 604 while (*p != '\0') 605 add_cmd_str(tstr(&p, 0), tables); 606 add_cmd_char('\0', tables); 607 } 608 } 609 610 /* 611 * Parse a variable definition line, of the form 612 * NAME = VALUE 613 */ 614 static void parse_varline(char *line, struct lesskey_tables *tables) 615 { 616 constant char *s; 617 char *p = line; 618 char *eq; 619 620 eq = strchr(line, '='); 621 if (eq != NULL && eq > line && eq[-1] == '+') 622 { 623 /* 624 * Rather ugly way of handling a += line. 625 * {{ Note that we ignore the variable name and 626 * just append to the previously defined variable. }} 627 */ 628 erase_cmd_char(tables); /* backspace over the final null */ 629 p = eq+1; 630 } else 631 { 632 do 633 { 634 s = tstr(&p, 0); 635 add_cmd_str(s, tables); 636 } while (*p != '\0' && !issp(*p) && *p != '='); 637 /* 638 * Terminate the variable name with a null byte. 639 */ 640 add_cmd_char('\0', tables); 641 p = skipsp(p); 642 if (*p++ != '=') 643 { 644 parse_error(LM(missing_eq_in_variable_definition), ""); 645 return; 646 } 647 add_cmd_char(EV_OK|A_EXTRA, tables); 648 } 649 p = skipsp(p); 650 while (*p != '\0') 651 { 652 s = tstr(&p, 0); 653 add_cmd_str(s, tables); 654 } 655 add_cmd_char('\0', tables); 656 } 657 658 /* 659 * Parse a line from the lesskey file. 660 */ 661 static void parse_line(char *line, struct lesskey_tables *tables) 662 { 663 char *p; 664 665 /* 666 * See if it is a control line. 667 */ 668 p = control_line(line, tables); 669 if (p == NULL) 670 return; 671 /* 672 * Skip leading white space. 673 * Replace the final newline with a null byte. 674 * Ignore blank lines and comments. 675 */ 676 p = clean_line(p); 677 if (*p == '\0') 678 return; 679 680 if (tables->currtable->is_var) 681 parse_varline(p, tables); 682 else 683 parse_cmdline(p, tables); 684 } 685 686 /* 687 * Parse a lesskey source file and store result in tables. 688 */ 689 public int parse_lesskey(constant char *infile, struct lesskey_tables *tables) 690 { 691 FILE *desc; 692 char line[1024]; 693 694 lesskey_file = (infile != NULL) ? strdup(infile) : homefile(DEF_LESSKEYINFILE); 695 if (lesskey_file == NULL) 696 return (-1); 697 698 init_tables(tables); 699 errors = 0; 700 linenum = 0; 701 if (less_version == 0) 702 less_version = lstrtoi(version, NULL, 10); 703 704 /* 705 * Open the input file. 706 */ 707 if (strcmp(lesskey_file, "-") == 0) 708 desc = stdin; 709 else if ((desc = fopen(lesskey_file, "r")) == NULL) 710 { 711 /* No error message since we try several possible files. */ 712 errors = -1; 713 } 714 715 /* 716 * Read and parse the input file, one line at a time. 717 */ 718 if (desc != NULL) 719 { 720 while (fgets(line, sizeof(line), desc) != NULL) 721 { 722 ++linenum; 723 parse_line(line, tables); 724 } 725 if (desc != stdin) 726 fclose(desc); 727 } 728 free(lesskey_file); 729 lesskey_file = NULL; 730 return (errors); 731 } 732 733 /* 734 * Parse a lesskey source content and store result in tables. 735 */ 736 public int parse_lesskey_content(constant char *content, struct lesskey_tables *tables) 737 { 738 size_t cx = 0; 739 740 lesskey_file = "lesskey-content"; 741 init_tables(tables); 742 errors = 0; 743 linenum = 0; 744 if (less_version == 0) 745 less_version = lstrtoi(version, NULL, 10); 746 747 while (content[cx] != '\0') 748 { 749 /* Extract a line from the content buffer and parse it. */ 750 char line[1024]; 751 size_t lx = 0; 752 while (content[cx] != '\0' && content[cx] != '\n' && content[cx] != ';') 753 { 754 if (lx >= sizeof(line)-1) break; 755 if (content[cx] == '\\' && content[cx+1] == ';') 756 ++cx; /* escaped semicolon: skip the backslash */ 757 line[lx++] = content[cx++]; 758 } 759 line[lx] = '\0'; 760 ++linenum; 761 parse_line(line, tables); 762 if (content[cx] != '\0') ++cx; /* skip newline or semicolon */ 763 } 764 lesskey_file = NULL; 765 return (errors); 766 } 767 768 #endif /* USERFILE */ 769