xref: /freebsd/contrib/less/decode.c (revision 252d6dde57d5dd0184929d1f8fb65e7713f51c6d)
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  */
expand_special_keys(unsigned char * table,size_t len)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  */
expand_cmd_table(struct tablelist * tlist)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  */
expand_cmd_tables(void)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  */
init_cmds(void)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  */
add_cmd_table(struct tablelist ** tlist,unsigned char * buf,size_t len)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  */
pop_cmd_table(struct tablelist ** tlist)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  */
add_fcmd_table(unsigned char * buf,size_t len)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  */
add_ecmd_table(unsigned char * buf,size_t len)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  */
add_var_table(struct tablelist ** tlist,unsigned char * buf,size_t len)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 
add_uvar_table(unsigned char * buf,size_t len)497 public void add_uvar_table(unsigned char *buf, size_t len)
498 {
499 	add_var_table(&list_var_tables, buf, len);
500 }
501 
add_sysvar_table(unsigned char * buf,size_t len)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  */
mouse_wheel_down(void)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  */
mouse_wheel_up(void)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  */
mouse_button_left(int x,int y,lbool down,lbool drag)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  */
mouse_button_right(int x,int y,lbool down,lbool drag)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  */
getcc_int(char * pterm)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 
x11mouse_button(int btn,int x,int y,lbool down,lbool drag)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  */
x11mouse_action(lbool skip)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  */
x116mouse_action(lbool skip)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  */
cmd_match(constant char * goal,constant char * str)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  */
cmd_next_entry(constant unsigned char * entry,mutable int * action,mutable constant unsigned char ** extra,mutable size_t * cmdlen)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  */
cmd_search(constant char * cmd,constant unsigned char * table,constant unsigned char * endtable,constant unsigned char ** extra,size_t * mlen)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) /* 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  */
cmd_decode(struct tablelist * tlist,constant char * cmd,constant char ** sp)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  */
fcmd_decode(constant char * cmd,constant char ** sp)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  */
ecmd_decode(constant char * cmd,constant char ** sp)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  */
lgetenv(constant char * var)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  */
lgetenv_ext(constant char * var,unsigned char * env_buf,size_t env_buf_len)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  */
isnullenv(constant char * s)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  */
gint(unsigned char ** sp)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  */
old_lesskey(unsigned char * buf,size_t len)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  */
new_lesskey(unsigned char * buf,size_t len,lbool sysvar)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  */
lesskey(constant char * filename,lbool sysvar)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
lesskey_text(constant char * filename,lbool sysvar,lbool content)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 
lesskey_src(constant char * filename,lbool sysvar)1066 public int lesskey_src(constant char *filename, lbool sysvar)
1067 {
1068 	return lesskey_text(filename, sysvar, FALSE);
1069 }
1070 
lesskey_content(constant char * content,lbool sysvar)1071 public int lesskey_content(constant char *content, lbool sysvar)
1072 {
1073 	return lesskey_text(content, sysvar, TRUE);
1074 }
1075 
lesskey_parse_error(char * s)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  */
add_hometable(int (* call_lesskey)(constant char *,lbool),constant char * envname,constant char * def_filename,lbool sysvar)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  */
add_content_table(int (* call_lesskey)(constant char *,lbool),constant char * envname,lbool sysvar)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  */
editchar(char c,int flags)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