2 * Copyright (C) 1984-2023 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.
12 * Routines to decode user commands.
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.
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.
36 extern int erase_char
, erase2_char
, kill_char
;
39 extern int screen_trashed
;
43 SK_SPECIAL_KEY, (k), 6, 1, 1, 1
45 * Command table is ordered roughly according to expected
46 * frequency of use, so the common commands are near the beginning.
49 static unsigned char cmdtable
[] =
55 SK(SK_DOWN_ARROW
),0, A_F_LINE
,
56 CONTROL('E'),0, A_F_LINE
,
57 CONTROL('N'),0, A_F_LINE
,
60 CONTROL('Y'),0, A_B_LINE
,
61 SK(SK_CONTROL_K
),0, A_B_LINE
,
62 CONTROL('P'),0, A_B_LINE
,
63 SK(SK_UP_ARROW
),0, A_B_LINE
,
68 CONTROL('D'),0, A_F_SCROLL
,
70 CONTROL('U'),0, A_B_SCROLL
,
71 ESC
,'[','M',0, A_X11MOUSE_IN
,
72 ESC
,'[','<',0, A_X116MOUSE_IN
,
75 CONTROL('F'),0, A_F_SCREEN
,
76 CONTROL('V'),0, A_F_SCREEN
,
77 SK(SK_PAGE_DOWN
),0, A_F_SCREEN
,
79 CONTROL('B'),0, A_B_SCREEN
,
80 ESC
,'v',0, A_B_SCREEN
,
81 SK(SK_PAGE_UP
),0, A_B_SCREEN
,
84 ESC
,' ',0, A_FF_SCREEN
,
86 ESC
,'F',0, A_F_UNTIL_HILITE
,
89 CONTROL('R'),0, A_REPAINT
,
90 CONTROL('L'),0, A_REPAINT
,
91 ESC
,'u',0, A_UNDO_SEARCH
,
92 ESC
,'U',0, A_CLR_SEARCH
,
94 SK(SK_HOME
),0, A_GOLINE
,
103 ESC
,'{',0, A_LLSHIFT
,
104 ESC
,'}',0, A_RRSHIFT
,
105 SK(SK_RIGHT_ARROW
),0, A_RSHIFT
,
106 SK(SK_LEFT_ARROW
),0, A_LSHIFT
,
107 SK(SK_CTL_RIGHT_ARROW
),0, A_RRSHIFT
,
108 SK(SK_CTL_LEFT_ARROW
),0, A_LLSHIFT
,
109 '{',0, A_F_BRACKET
|A_EXTRA
, '{','}',0,
110 '}',0, A_B_BRACKET
|A_EXTRA
, '{','}',0,
111 '(',0, A_F_BRACKET
|A_EXTRA
, '(',')',0,
112 ')',0, A_B_BRACKET
|A_EXTRA
, '(',')',0,
113 '[',0, A_F_BRACKET
|A_EXTRA
, '[',']',0,
114 ']',0, A_B_BRACKET
|A_EXTRA
, '[',']',0,
115 ESC
,CONTROL('F'),0, A_F_BRACKET
,
116 ESC
,CONTROL('B'),0, A_B_BRACKET
,
118 ESC
,'G',0, A_GOEND_BUF
,
121 SK(SK_END
),0, A_GOEND
,
137 CONTROL('G'),0, A_STAT
,
141 ESC
,'/',0, A_F_SEARCH
|A_EXTRA
, '*',0,
142 ESC
,'?',0, A_B_SEARCH
|A_EXTRA
, '*',0,
143 'n',0, A_AGAIN_SEARCH
,
144 ESC
,'n',0, A_T_AGAIN_SEARCH
,
145 'N',0, A_REVERSE_SEARCH
,
146 ESC
,'N',0, A_T_REVERSE_SEARCH
,
150 ESC
,'m',0, A_CLRMARK
,
152 CONTROL('X'),CONTROL('X'),0, A_GOMARK
,
154 ':','e',0, A_EXAMINE
,
155 CONTROL('X'),CONTROL('V'),0, A_EXAMINE
,
156 ':','n',0, A_NEXT_FILE
,
157 ':','p',0, A_PREV_FILE
,
160 ':','x',0, A_INDEX_FILE
,
161 ':','d',0, A_REMOVE_FILE
,
163 ':','t',0, A_OPT_TOGGLE
|A_EXTRA
, 't',0,
164 's',0, A_OPT_TOGGLE
|A_EXTRA
, 'o',0,
165 '_',0, A_DISP_OPTION
,
183 static unsigned char edittable
[] =
185 '\t',0, EC_F_COMPLETE
, /* TAB */
186 '\17',0, EC_B_COMPLETE
, /* BACKTAB */
187 SK(SK_BACKTAB
),0, EC_B_COMPLETE
, /* BACKTAB */
188 ESC
,'\t',0, EC_B_COMPLETE
, /* ESC TAB */
189 CONTROL('L'),0, EC_EXPAND
, /* CTRL-L */
190 CONTROL('V'),0, EC_LITERAL
, /* BACKSLASH */
191 CONTROL('A'),0, EC_LITERAL
, /* BACKSLASH */
192 ESC
,'l',0, EC_RIGHT
, /* ESC l */
193 SK(SK_RIGHT_ARROW
),0, EC_RIGHT
, /* RIGHTARROW */
194 ESC
,'h',0, EC_LEFT
, /* ESC h */
195 SK(SK_LEFT_ARROW
),0, EC_LEFT
, /* LEFTARROW */
196 ESC
,'b',0, EC_W_LEFT
, /* ESC b */
197 ESC
,SK(SK_LEFT_ARROW
),0, EC_W_LEFT
, /* ESC LEFTARROW */
198 SK(SK_CTL_LEFT_ARROW
),0, EC_W_LEFT
, /* CTRL-LEFTARROW */
199 ESC
,'w',0, EC_W_RIGHT
, /* ESC w */
200 ESC
,SK(SK_RIGHT_ARROW
),0, EC_W_RIGHT
, /* ESC RIGHTARROW */
201 SK(SK_CTL_RIGHT_ARROW
),0, EC_W_RIGHT
, /* CTRL-RIGHTARROW */
202 ESC
,'i',0, EC_INSERT
, /* ESC i */
203 SK(SK_INSERT
),0, EC_INSERT
, /* INSERT */
204 ESC
,'x',0, EC_DELETE
, /* ESC x */
205 SK(SK_DELETE
),0, EC_DELETE
, /* DELETE */
206 ESC
,'X',0, EC_W_DELETE
, /* ESC X */
207 ESC
,SK(SK_DELETE
),0, EC_W_DELETE
, /* ESC DELETE */
208 SK(SK_CTL_DELETE
),0, EC_W_DELETE
, /* CTRL-DELETE */
209 SK(SK_CTL_BACKSPACE
),0, EC_W_BACKSPACE
, /* CTRL-BACKSPACE */
210 ESC
,SK(SK_BACKSPACE
),0, EC_W_BACKSPACE
, /* ESC BACKSPACE */
211 ESC
,'0',0, EC_HOME
, /* ESC 0 */
212 SK(SK_HOME
),0, EC_HOME
, /* HOME */
213 ESC
,'$',0, EC_END
, /* ESC $ */
214 SK(SK_END
),0, EC_END
, /* END */
215 ESC
,'k',0, EC_UP
, /* ESC k */
216 SK(SK_UP_ARROW
),0, EC_UP
, /* UPARROW */
217 ESC
,'j',0, EC_DOWN
, /* ESC j */
218 SK(SK_DOWN_ARROW
),0, EC_DOWN
, /* DOWNARROW */
219 CONTROL('G'),0, EC_ABORT
, /* CTRL-G */
220 ESC
,'[','M',0, EC_X11MOUSE
, /* X11 mouse report */
221 ESC
,'[','<',0, EC_X116MOUSE
, /* X11 1006 mouse report */
225 * Structure to support a list of command tables.
229 struct tablelist
*t_next
;
235 * List of command tables and list of line-edit tables.
237 static struct tablelist
*list_fcmd_tables
= NULL
;
238 static struct tablelist
*list_ecmd_tables
= NULL
;
239 static struct tablelist
*list_var_tables
= NULL
;
240 static struct tablelist
*list_sysvar_tables
= NULL
;
244 * Expand special key abbreviations in a command table.
246 static void expand_special_keys(char *table
, int len
)
254 for (fm
= table
; fm
< table
+ len
; )
257 * Rewrite each command in the table with any
258 * special key abbreviations expanded.
260 for (to
= fm
; *fm
!= '\0'; )
262 if (*fm
!= SK_SPECIAL_KEY
)
268 * After SK_SPECIAL_KEY, next byte is the type
269 * of special key (one of the SK_* constants),
270 * and the byte after that is the number of bytes,
271 * N, reserved by the abbreviation (including the
272 * SK_SPECIAL_KEY and key type bytes).
273 * Replace all N bytes with the actual bytes
274 * output by the special key on this terminal.
276 repl
= special_key_str(fm
[1]);
279 if (repl
== NULL
|| (int) strlen(repl
) > klen
)
281 while (*repl
!= '\0')
286 * Fill any unused bytes between end of command and
287 * the action byte with A_SKIP.
295 while (*fm
++ != '\0')
302 * Expand special key abbreviations in a list of command tables.
304 static void expand_cmd_table(struct tablelist
*tlist
)
307 for (t
= tlist
; t
!= NULL
; t
= t
->t_next
)
309 expand_special_keys(t
->t_start
, t
->t_end
- t
->t_start
);
314 * Expand special key abbreviations in all command tables.
316 public void expand_cmd_tables(void)
318 expand_cmd_table(list_fcmd_tables
);
319 expand_cmd_table(list_ecmd_tables
);
320 expand_cmd_table(list_var_tables
);
321 expand_cmd_table(list_sysvar_tables
);
326 * Initialize the command lists.
328 public void init_cmds(void)
331 * Add the default command tables.
333 add_fcmd_table((char*)cmdtable
, sizeof(cmdtable
));
334 add_ecmd_table((char*)edittable
, sizeof(edittable
));
336 #ifdef BINDIR /* For backwards compatibility */
337 /* Try to add tables in the OLD system lesskey file. */
338 add_hometable(lesskey
, NULL
, BINDIR
"/.sysless", 1);
341 * Try to load lesskey source file or binary file.
342 * If the source file succeeds, don't load binary file.
343 * The binary file is likely to have been generated from
344 * a (possibly out of date) copy of the src file,
345 * so loading it is at best redundant.
348 * Try to add tables in system lesskey src file.
351 if (add_hometable(lesskey_src
, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS
, 1) != 0)
355 * Try to add the tables in the system lesskey binary file.
357 add_hometable(lesskey
, "LESSKEY_SYSTEM", LESSKEYFILE_SYS
, 1);
360 * Try to add tables in the lesskey src file "$HOME/.lesskey".
363 if (add_hometable(lesskey_src
, "LESSKEYIN", DEF_LESSKEYINFILE
, 0) != 0)
367 * Try to add the tables in the standard lesskey binary file "$HOME/.less".
369 add_hometable(lesskey
, "LESSKEY", LESSKEYFILE
, 0);
375 * Add a command table.
377 static int add_cmd_table(struct tablelist
**tlist
, char *buf
, int len
)
384 * Allocate a tablelist structure, initialize it,
385 * and link it into the list of tables.
387 if ((t
= (struct tablelist
*)
388 calloc(1, sizeof(struct tablelist
))) == NULL
)
393 t
->t_end
= buf
+ len
;
400 * Add a command table.
402 public void add_fcmd_table(char *buf
, int len
)
404 if (add_cmd_table(&list_fcmd_tables
, buf
, len
) < 0)
405 error("Warning: some commands disabled", NULL_PARG
);
409 * Add an editing command table.
411 public void add_ecmd_table(char *buf
, int len
)
413 if (add_cmd_table(&list_ecmd_tables
, buf
, len
) < 0)
414 error("Warning: some edit commands disabled", NULL_PARG
);
418 * Add an environment variable table.
420 static void add_var_table(struct tablelist
**tlist
, char *buf
, int len
)
422 if (add_cmd_table(tlist
, buf
, len
) < 0)
423 error("Warning: environment variables from lesskey file unavailable", NULL_PARG
);
427 * Return action for a mouse wheel down event.
429 static int mouse_wheel_down(void)
431 return ((mousecap
== OPT_ONPLUS
) ? A_B_MOUSE
: A_F_MOUSE
);
435 * Return action for a mouse wheel up event.
437 static int mouse_wheel_up(void)
439 return ((mousecap
== OPT_ONPLUS
) ? A_F_MOUSE
: A_B_MOUSE
);
443 * Return action for a mouse button release event.
445 static int mouse_button_rel(int x
, int y
)
448 * {{ It would be better to return an action and then do this
449 * in commands() but it's nontrivial to pass y to it. }}
460 * Read a decimal integer. Return the integer and set *pterm to the terminating char.
462 static int getcc_int(char *pterm
)
469 if (ch
< '0' || ch
> '9')
471 if (pterm
!= NULL
) *pterm
= ch
;
476 if (ckd_mul(&num
, num
, 10) || ckd_add(&num
, num
, ch
- '0'))
483 * Read suffix of mouse input and return the action to take.
484 * The prefix ("\e[M") has already been read.
486 static int x11mouse_action(int skip
)
488 int b
= getcc() - X11MOUSE_OFFSET
;
489 int x
= getcc() - X11MOUSE_OFFSET
-1;
490 int y
= getcc() - X11MOUSE_OFFSET
-1;
496 case X11MOUSE_WHEEL_DOWN
:
497 return mouse_wheel_down();
498 case X11MOUSE_WHEEL_UP
:
499 return mouse_wheel_up();
500 case X11MOUSE_BUTTON_REL
:
501 return mouse_button_rel(x
, y
);
506 * Read suffix of mouse input and return the action to take.
507 * The prefix ("\e[<") has already been read.
509 static int x116mouse_action(int skip
)
513 int b
= getcc_int(&ch
);
514 if (b
< 0 || ch
!= ';') return (A_NOACTION
);
515 x
= getcc_int(&ch
) - 1;
516 if (x
< 0 || ch
!= ';') return (A_NOACTION
);
517 y
= getcc_int(&ch
) - 1;
518 if (y
< 0) return (A_NOACTION
);
522 case X11MOUSE_WHEEL_DOWN
:
523 return mouse_wheel_down();
524 case X11MOUSE_WHEEL_UP
:
525 return mouse_wheel_up();
527 if (ch
!= 'm') return (A_NOACTION
);
528 return mouse_button_rel(x
, y
);
533 * Search a single command table for the command string in cmd.
535 static int cmd_search(char *cmd
, char *table
, char *endtable
, char **sp
)
542 for (p
= table
, q
= cmd
; p
< endtable
; p
++, q
++)
547 * Current characters match.
548 * If we're at the end of the string, we've found it.
549 * Return the action code, which is the character
550 * after the null at the end of the string
551 * in the command table.
561 * We get here only if the original
562 * cmd string passed in was empty ("").
563 * I don't think that can happen,
564 * but just in case ...
569 * Check for an "extra" string.
576 if (a
== A_X11MOUSE_IN
)
577 a
= x11mouse_action(0);
578 else if (a
== A_X116MOUSE_IN
)
579 a
= x116mouse_action(0);
582 } else if (*q
== '\0')
585 * Hit the end of the user's command,
586 * but not the end of the string in the command table.
587 * The user's command is incomplete.
594 * Skip ahead to the next command in the
595 * command table, and reset the pointer
596 * to the beginning of the user's command.
598 if (*p
== '\0' && p
[1] == A_END_LIST
)
601 * A_END_LIST is a special marker that tells
602 * us to abort the cmd search.
617 * No match found in the entire command table.
623 * Decode a command character and return the associated action.
624 * The "extra" string, if any, is returned in sp.
626 static int cmd_decode(struct tablelist
*tlist
, char *cmd
, char **sp
)
629 int action
= A_INVALID
;
632 * Search thru all the command tables.
633 * Stop when we find an action which is not A_INVALID.
635 for (t
= tlist
; t
!= NULL
; t
= t
->t_next
)
637 action
= cmd_search(cmd
, t
->t_start
, t
->t_end
, sp
);
638 if (action
!= A_INVALID
)
641 if (action
== A_UINVALID
)
647 * Decode a command from the cmdtables list.
649 public int fcmd_decode(char *cmd
, char **sp
)
651 return (cmd_decode(list_fcmd_tables
, cmd
, sp
));
655 * Decode a command from the edittables list.
657 public int ecmd_decode(char *cmd
, char **sp
)
659 return (cmd_decode(list_ecmd_tables
, cmd
, sp
));
663 * Get the value of an environment variable.
664 * Looks first in the lesskey file, then in the real environment.
666 public char * lgetenv(char *var
)
671 a
= cmd_decode(list_var_tables
, var
, &s
);
675 if (s
!= NULL
&& *s
!= '\0')
677 a
= cmd_decode(list_sysvar_tables
, var
, &s
);
684 * Is a string null or empty?
686 public int isnullenv(char *s
)
688 return (s
== NULL
|| *s
== '\0');
693 * Get an "integer" from a lesskey file.
694 * Integers are stored in a funny format:
695 * two bytes, low order first, in radix KRADIX.
697 static int gint(char **sp
)
702 n
+= *(*sp
)++ * KRADIX
;
707 * Process an old (pre-v241) lesskey file.
709 static int old_lesskey(char *buf
, int len
)
712 * Old-style lesskey file.
713 * The file must end with either
715 * or ...,cmd,0,action|A_EXTRA,string,0
716 * So the last byte or the second to last byte must be zero.
718 if (buf
[len
-1] != '\0' && buf
[len
-2] != '\0')
720 add_fcmd_table(buf
, len
);
725 * Process a new (post-v241) lesskey file.
727 static int new_lesskey(char *buf
, int len
, int sysvar
)
735 * New-style lesskey file.
736 * Extract the pieces.
738 if (buf
[len
-3] != C0_END_LESSKEY_MAGIC
||
739 buf
[len
-2] != C1_END_LESSKEY_MAGIC
||
740 buf
[len
-1] != C2_END_LESSKEY_MAGIC
)
751 if (n
< 0 || p
+n
>= end
)
753 add_fcmd_table(p
, n
);
758 if (n
< 0 || p
+n
>= end
)
760 add_ecmd_table(p
, n
);
765 if (n
< 0 || p
+n
>= end
)
767 add_var_table((sysvar
) ?
768 &list_sysvar_tables
: &list_var_tables
, p
, n
);
775 * Unrecognized section type.
783 * Set up a user command table, based on a "lesskey" file.
785 public int lesskey(char *filename
, int sysvar
)
795 * Try to open the lesskey file.
797 f
= open(filename
, OPEN_READ
);
802 * Read the file into a buffer.
803 * We first figure out the size of the file and allocate space for it.
804 * {{ Minimal error checking is done here.
805 * A garbage .less file will produce strange results.
806 * To avoid a large amount of error checking code here, we
807 * rely on the lesskey program to generate a good .less file. }}
810 if (len
== NULL_POSITION
|| len
< 3)
813 * Bad file (valid file must have at least 3 chars).
818 if ((buf
= (char *) calloc((int)len
, sizeof(char))) == NULL
)
823 if (lseek(f
, (off_t
)0, SEEK_SET
) == BAD_LSEEK
)
829 n
= read(f
, buf
, (unsigned int) len
);
838 * Figure out if this is an old-style (before version 241)
839 * or new-style lesskey file format.
842 buf
[0] != C0_LESSKEY_MAGIC
|| buf
[1] != C1_LESSKEY_MAGIC
||
843 buf
[2] != C2_LESSKEY_MAGIC
|| buf
[3] != C3_LESSKEY_MAGIC
)
844 return (old_lesskey(buf
, (int)len
));
845 return (new_lesskey(buf
, (int)len
, sysvar
));
849 public int lesskey_src(char *filename
, int sysvar
)
851 static struct lesskey_tables tables
;
852 int r
= parse_lesskey(filename
, &tables
);
855 add_fcmd_table(xbuf_char_data(&tables
.cmdtable
.buf
), tables
.cmdtable
.buf
.end
);
856 add_ecmd_table(xbuf_char_data(&tables
.edittable
.buf
), tables
.edittable
.buf
.end
);
857 add_var_table(sysvar
? &list_sysvar_tables
: &list_var_tables
,
858 xbuf_char_data(&tables
.vartable
.buf
), tables
.vartable
.buf
.end
);
862 void lesskey_parse_error(char *s
)
868 #endif /* HAVE_LESSKEYSRC */
871 * Add a lesskey file.
873 public int add_hometable(int (*call_lesskey
)(char *, int), char *envname
, char *def_filename
, int sysvar
)
878 if (envname
!= NULL
&& (filename
= lgetenv(envname
)) != NULL
)
879 filename
= save(filename
);
880 else if (sysvar
) /* def_filename is full path */
881 filename
= save(def_filename
);
882 else /* def_filename is just basename */
884 /* Remove first char (normally a dot) unless stored in $HOME. */
885 char *xdg
= lgetenv("XDG_CONFIG_HOME");
887 filename
= dirfile(xdg
, &def_filename
[1], 1);
888 if (filename
== NULL
)
890 char *home
= lgetenv("HOME");
891 if (!isnullenv(home
))
893 char *cfg_dir
= dirfile(home
, ".config", 0);
894 filename
= dirfile(cfg_dir
, &def_filename
[1], 1);
898 if (filename
== NULL
)
899 filename
= homefile(def_filename
);
901 if (filename
== NULL
)
903 r
= (*call_lesskey
)(filename
, sysvar
);
910 * See if a char is a special line-editing command.
912 public int editchar(int c
, int flags
)
917 char usercmd
[MAX_CMDLEN
+1];
920 * An editing character could actually be a sequence of characters;
921 * for example, an escape sequence sent by pressing the uparrow key.
922 * To match the editing string, we use the command decoder
923 * but give it the edit-commands command table
924 * This table is constructed to match the user's keyboard.
926 if (c
== erase_char
|| c
== erase2_char
)
927 return (EC_BACKSPACE
);
930 #if MSDOS_COMPILER==WIN32C
933 return (EC_LINEKILL
);
937 * Collect characters in a buffer.
938 * Start with the one we have, and get more if we need them.
945 usercmd
[nch
+1] = '\0';
947 action
= ecmd_decode(usercmd
, &s
);
948 } while (action
== A_PREFIX
&& nch
< MAX_CMDLEN
);
950 if (action
== EC_X11MOUSE
)
951 return (x11mouse_action(1));
952 if (action
== EC_X116MOUSE
)
953 return (x116mouse_action(1));
955 if (flags
& ECF_NORIGHTLEFT
)
966 if (flags
& ECF_NOHISTORY
)
969 * The caller says there is no history list.
970 * Reject any history-manipulation action.
981 #if TAB_COMPLETE_FILENAME
982 if (flags
& ECF_NOCOMPLETE
)
985 * The caller says we don't want any filename completion cmds.
998 if ((flags
& ECF_PEEK
) || action
== A_INVALID
)
1001 * We're just peeking, or we didn't understand the command.
1002 * Unget all the characters we read in the loop above.
1003 * This does NOT include the original character that was
1004 * passed in as a parameter.
1008 ungetcc(usercmd
[--nch
]);