2 * Copyright (C) 1984-2022 Mark Nudelman
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.
7 * For more information, see the README file.
18 #define CONTROL(c) ((c)&037)
19 #define ESC CONTROL('[')
21 extern void lesskey_parse_error(char *msg
);
22 extern char *homefile(char *filename
);
23 extern void *ecalloc(int count
, unsigned int size
);
24 extern int lstrtoi(char *str
, char **end
);
25 extern char version
[];
29 static int less_version
= 0;
30 static char *lesskey_file
;
32 static struct lesskey_cmdname cmdnames
[] =
34 { "back-bracket", A_B_BRACKET
},
35 { "back-line", A_B_LINE
},
36 { "back-line-force", A_BF_LINE
},
37 { "back-screen", A_B_SCREEN
},
38 { "back-scroll", A_B_SCROLL
},
39 { "back-search", A_B_SEARCH
},
40 { "back-window", A_B_WINDOW
},
41 { "clear-mark", A_CLRMARK
},
44 { "display-flag", A_DISP_OPTION
},
45 { "display-option", A_DISP_OPTION
},
47 { "end-scroll", A_RRSHIFT
},
48 { "examine", A_EXAMINE
},
49 { "filter", A_FILTER
},
50 { "first-cmd", A_FIRSTCMD
},
51 { "firstcmd", A_FIRSTCMD
},
52 { "flush-repaint", A_FREPAINT
},
53 { "forw-bracket", A_F_BRACKET
},
54 { "forw-forever", A_F_FOREVER
},
55 { "forw-until-hilite", A_F_UNTIL_HILITE
},
56 { "forw-line", A_F_LINE
},
57 { "forw-line-force", A_FF_LINE
},
58 { "forw-screen", A_F_SCREEN
},
59 { "forw-screen-force", A_FF_SCREEN
},
60 { "forw-scroll", A_F_SCROLL
},
61 { "forw-search", A_F_SEARCH
},
62 { "forw-window", A_F_WINDOW
},
63 { "goto-end", A_GOEND
},
64 { "goto-end-buffered", A_GOEND_BUF
},
65 { "goto-line", A_GOLINE
},
66 { "goto-mark", A_GOMARK
},
68 { "index-file", A_INDEX_FILE
},
69 { "invalid", A_UINVALID
},
70 { "left-scroll", A_LSHIFT
},
71 { "next-file", A_NEXT_FILE
},
72 { "next-tag", A_NEXT_TAG
},
73 { "noaction", A_NOACTION
},
74 { "no-scroll", A_LLSHIFT
},
75 { "percent", A_PERCENT
},
77 { "prev-file", A_PREV_FILE
},
78 { "prev-tag", A_PREV_TAG
},
80 { "remove-file", A_REMOVE_FILE
},
81 { "repaint", A_REPAINT
},
82 { "repaint-flush", A_FREPAINT
},
83 { "repeat-search", A_AGAIN_SEARCH
},
84 { "repeat-search-all", A_T_AGAIN_SEARCH
},
85 { "reverse-search", A_REVERSE_SEARCH
},
86 { "reverse-search-all", A_T_REVERSE_SEARCH
},
87 { "right-scroll", A_RSHIFT
},
88 { "set-mark", A_SETMARK
},
89 { "set-mark-bottom", A_SETMARKBOT
},
92 { "toggle-flag", A_OPT_TOGGLE
},
93 { "toggle-option", A_OPT_TOGGLE
},
94 { "undo-hilite", A_UNDO_SEARCH
},
95 { "clear-search", A_CLR_SEARCH
},
96 { "version", A_VERSION
},
97 { "visual", A_VISUAL
},
101 static struct lesskey_cmdname editnames
[] =
103 { "back-complete", EC_B_COMPLETE
},
104 { "backspace", EC_BACKSPACE
},
105 { "delete", EC_DELETE
},
108 { "expand", EC_EXPAND
},
109 { "forw-complete", EC_F_COMPLETE
},
111 { "insert", EC_INSERT
},
112 { "invalid", EC_UINVALID
},
113 { "kill-line", EC_LINEKILL
},
114 { "abort", EC_ABORT
},
116 { "literal", EC_LITERAL
},
117 { "right", EC_RIGHT
},
119 { "word-backspace", EC_W_BACKSPACE
},
120 { "word-delete", EC_W_DELETE
},
121 { "word-left", EC_W_LEFT
},
122 { "word-right", EC_W_RIGHT
},
127 * Print a parse error message.
130 parse_error(fmt
, arg1
)
135 int n
= snprintf(buf
, sizeof(buf
), "%s: line %d: ", lesskey_file
, linenum
);
136 if (n
>= 0 && n
< sizeof(buf
))
137 snprintf(buf
+n
, sizeof(buf
)-n
, fmt
, arg1
);
139 lesskey_parse_error(buf
);
143 * Initialize lesskey_tables.
147 struct lesskey_tables
*tables
;
149 tables
->currtable
= &tables
->cmdtable
;
151 tables
->cmdtable
.names
= cmdnames
;
152 tables
->cmdtable
.is_var
= 0;
153 xbuf_init(&tables
->cmdtable
.buf
);
155 tables
->edittable
.names
= editnames
;
156 tables
->edittable
.is_var
= 0;
157 xbuf_init(&tables
->edittable
.buf
);
159 tables
->vartable
.names
= NULL
;
160 tables
->vartable
.is_var
= 1;
161 xbuf_init(&tables
->vartable
.buf
);
164 #define CHAR_STRING_LEN 8
167 char_string(buf
, ch
, lit
)
172 if (lit
|| (ch
>= 0x20 && ch
< 0x7f))
178 snprintf(buf
, CHAR_STRING_LEN
, "\\x%02x", ch
);
184 * Increment char pointer by one up to terminating nul byte.
196 * Parse one character of a string.
206 static char buf
[CHAR_STRING_LEN
];
207 static char tstr_control_k
[] =
208 { SK_SPECIAL_KEY
, SK_CONTROL_K
, 6, 1, 1, 1, '\0' };
217 case '0': case '1': case '2': case '3':
218 case '4': case '5': case '6': case '7':
220 * Parse an octal number.
225 ch
= 8*ch
+ (*p
- '0');
226 while (*++p
>= '0' && *p
<= '7' && ++i
< 3);
228 if (xlate
&& ch
== CONTROL('K'))
229 return tstr_control_k
;
230 return char_string(buf
, ch
, 1);
236 return char_string(buf
, ESC
, 1);
251 case 'b': ch
= SK_BACKSPACE
; break;
252 case 'B': ch
= SK_CTL_BACKSPACE
; break;
253 case 'd': ch
= SK_DOWN_ARROW
; break;
254 case 'D': ch
= SK_PAGE_DOWN
; break;
255 case 'e': ch
= SK_END
; break;
256 case 'h': ch
= SK_HOME
; break;
257 case 'i': ch
= SK_INSERT
; break;
258 case 'l': ch
= SK_LEFT_ARROW
; break;
259 case 'L': ch
= SK_CTL_LEFT_ARROW
; break;
260 case 'r': ch
= SK_RIGHT_ARROW
; break;
261 case 'R': ch
= SK_CTL_RIGHT_ARROW
; break;
262 case 't': ch
= SK_BACKTAB
; break;
263 case 'u': ch
= SK_UP_ARROW
; break;
264 case 'U': ch
= SK_PAGE_UP
; break;
265 case 'x': ch
= SK_DELETE
; break;
266 case 'X': ch
= SK_CTL_DELETE
; break;
267 case '1': ch
= SK_F1
; break;
269 parse_error("invalid escape sequence \"\\k%s\"", char_string(buf
, *p
, 0));
270 *pp
= increment_pointer(p
);
274 buf
[0] = SK_SPECIAL_KEY
;
286 * Backslash followed by any other char
287 * just means that char.
289 *pp
= increment_pointer(p
);
290 char_string(buf
, *p
, 1);
291 if (xlate
&& buf
[0] == CONTROL('K'))
292 return tstr_control_k
;
297 * Caret means CONTROL.
299 *pp
= increment_pointer(p
+1);
300 char_string(buf
, CONTROL(p
[1]), 1);
301 if (xlate
&& buf
[0] == CONTROL('K'))
302 return tstr_control_k
;
305 *pp
= increment_pointer(p
);
306 char_string(buf
, *p
, 1);
307 if (xlate
&& buf
[0] == CONTROL('K'))
308 return tstr_control_k
;
316 return (ch
== ' ' || ch
== '\t');
320 * Skip leading spaces in a string.
332 * Skip non-space characters in a string.
338 while (*s
!= '\0' && !issp(*s
))
344 * Clean up an input line:
345 * strip off the trailing newline & any trailing # comment.
354 for (i
= 0; s
[i
] != '\0' && s
[i
] != '\n' && s
[i
] != '\r'; i
++)
355 if (s
[i
] == '#' && (i
== 0 || s
[i
-1] != '\\'))
362 * Add a byte to the output command table.
365 add_cmd_char(c
, tables
)
367 struct lesskey_tables
*tables
;
369 xbuf_add(&tables
->currtable
->buf
, c
);
373 erase_cmd_char(tables
)
374 struct lesskey_tables
*tables
;
376 xbuf_pop(&tables
->currtable
->buf
);
380 * Add a string to the output command table.
383 add_cmd_str(s
, tables
)
385 struct lesskey_tables
*tables
;
387 for ( ; *s
!= '\0'; s
++)
388 add_cmd_char(*s
, tables
);
392 * Does a given version number match the running version?
393 * Operator compares the running version to the given version.
396 match_version(op
, ver
)
402 case '>': return less_version
> ver
;
403 case '<': return less_version
< ver
;
404 case '+': return less_version
>= ver
;
405 case '-': return less_version
<= ver
;
406 case '=': return less_version
== ver
;
407 case '!': return less_version
!= ver
;
408 default: return 0; /* cannot happen */
413 * Handle a #version line.
414 * If the version matches, return the part of the line that should be executed.
415 * Otherwise, return NULL.
418 version_line(s
, tables
)
420 struct lesskey_tables
*tables
;
425 char buf
[CHAR_STRING_LEN
];
427 s
+= strlen("#version");
430 /* Simplify 2-char op to one char. */
433 case '<': if (*s
== '=') { s
++; op
= '-'; } break;
434 case '>': if (*s
== '=') { s
++; op
= '+'; } break;
435 case '=': if (*s
== '=') { s
++; } break;
436 case '!': if (*s
== '=') { s
++; } break;
438 parse_error("invalid operator '%s' in #version line", char_string(buf
, op
, 0));
442 ver
= lstrtoi(s
, &e
);
445 parse_error("non-numeric version number in #version line", "");
448 if (!match_version(op
, ver
))
454 * See if we have a special "control" line.
457 control_line(s
, tables
)
459 struct lesskey_tables
*tables
;
461 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)) == 0)
463 if (PREFIX(s
, "#line-edit"))
465 tables
->currtable
= &tables
->edittable
;
468 if (PREFIX(s
, "#command"))
470 tables
->currtable
= &tables
->cmdtable
;
473 if (PREFIX(s
, "#env"))
475 tables
->currtable
= &tables
->vartable
;
478 if (PREFIX(s
, "#stop"))
480 add_cmd_char('\0', tables
);
481 add_cmd_char(A_END_LIST
, tables
);
484 if (PREFIX(s
, "#version"))
486 return (version_line(s
, tables
));
492 * Find an action, given the name of the action.
495 findaction(actname
, tables
)
497 struct lesskey_tables
*tables
;
501 for (i
= 0; tables
->currtable
->names
[i
].cn_name
!= NULL
; i
++)
502 if (strcmp(tables
->currtable
->names
[i
].cn_name
, actname
) == 0)
503 return (tables
->currtable
->names
[i
].cn_action
);
504 parse_error("unknown action: \"%s\"", actname
);
509 * Parse a line describing one key binding, of the form
511 * where KEY is the user key sequence, ACTION is the
512 * resulting less action, and EXTRA is an "extra" user
513 * key sequence injected after the action.
516 parse_cmdline(p
, tables
)
518 struct lesskey_tables
*tables
;
526 * Parse the command string and store it in the current table.
531 add_cmd_str(s
, tables
);
532 } while (*p
!= '\0' && !issp(*p
));
534 * Terminate the command string with a null byte.
536 add_cmd_char('\0', tables
);
539 * Skip white space between the command string
540 * and the action name.
541 * Terminate the action name with a null byte.
546 parse_error("missing action", "");
555 * Parse the action name and store it in the current table.
557 action
= findaction(actname
, tables
);
560 * See if an extra string follows the action name.
566 add_cmd_char(action
, tables
);
570 * OR the special value A_EXTRA into the action byte.
571 * Put the extra string after the action byte.
573 add_cmd_char(action
| A_EXTRA
, tables
);
575 add_cmd_str(tstr(&p
, 0), tables
);
576 add_cmd_char('\0', tables
);
581 * Parse a variable definition line, of the form
585 parse_varline(line
, tables
)
587 struct lesskey_tables
*tables
;
593 eq
= strchr(line
, '=');
594 if (eq
!= NULL
&& eq
> line
&& eq
[-1] == '+')
597 * Rather ugly way of handling a += line.
598 * {{ Note that we ignore the variable name and
599 * just append to the previously defined variable. }}
601 erase_cmd_char(tables
); /* backspace over the final null */
608 add_cmd_str(s
, tables
);
609 } while (*p
!= '\0' && !issp(*p
) && *p
!= '=');
611 * Terminate the variable name with a null byte.
613 add_cmd_char('\0', tables
);
617 parse_error("missing = in variable definition", "");
620 add_cmd_char(EV_OK
|A_EXTRA
, tables
);
626 add_cmd_str(s
, tables
);
628 add_cmd_char('\0', tables
);
632 * Parse a line from the lesskey file.
635 parse_line(line
, tables
)
637 struct lesskey_tables
*tables
;
642 * See if it is a control line.
644 p
= control_line(line
, tables
);
648 * Skip leading white space.
649 * Replace the final newline with a null byte.
650 * Ignore blank lines and comments.
656 if (tables
->currtable
->is_var
)
657 parse_varline(p
, tables
);
659 parse_cmdline(p
, tables
);
663 * Parse a lesskey source file and store result in tables.
666 parse_lesskey(infile
, tables
)
668 struct lesskey_tables
*tables
;
674 infile
= homefile(DEF_LESSKEYINFILE
);
675 lesskey_file
= infile
;
680 if (less_version
== 0)
681 less_version
= lstrtoi(version
, NULL
);
684 * Open the input file.
686 if (strcmp(infile
, "-") == 0)
688 else if ((desc
= fopen(infile
, "r")) == NULL
)
690 /* parse_error("cannot open lesskey file %s", infile); */
695 * Read and parse the input file, one line at a time.
697 while (fgets(line
, sizeof(line
), desc
) != NULL
)
700 parse_line(line
, tables
);