xref: /freebsd/contrib/less/decode.c (revision d5cb458b4b58b0f0b3c058a32439f232fd5455ca)
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,mutable unsigned char * buf,size_t len)486 static void add_var_table(struct tablelist **tlist, mutable unsigned char *buf, size_t len)
487 {
488 	struct xbuffer xbuf;
489 
490 	xbuf_init(&xbuf);
491 	expand_evars((mutable 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 				if (extra != NULL)
753 					*extra = textra;
754 			} else if (match > 0 && action == A_INVALID) /* cmd is a prefix of this table entry */
755 			{
756 				action = A_PREFIX;
757 			}
758 			match_len = match;
759 		}
760 	}
761 	if (mlen != NULL)
762 		*mlen = match_len;
763 	return (action);
764 }
765 
766 /*
767  * Decode a command character and return the associated action.
768  * The "extra" string, if any, is returned in sp.
769  */
cmd_decode(struct tablelist * tlist,constant char * cmd,constant char ** sp)770 static int cmd_decode(struct tablelist *tlist, constant char *cmd, constant char **sp)
771 {
772 	struct tablelist *t;
773 	int action = A_INVALID;
774 	size_t match_len = 0;
775 
776 	/*
777 	 * Search for the cmd thru all the command tables.
778 	 * If we find it more than once, take the last one.
779 	 */
780 	*sp = NULL;
781 	for (t = tlist;  t != NULL;  t = t->t_next)
782 	{
783 		constant unsigned char *tsp;
784 		size_t mlen = match_len;
785 		int taction = cmd_search(cmd, t->t_start, t->t_end, &tsp, &mlen);
786 		if (mlen >= match_len)
787 		{
788 			match_len = mlen;
789 			if (taction != A_INVALID)
790 			{
791 				*sp = (constant char *) tsp;
792 				if (taction < 0)
793 				{
794 					action = -taction;
795 					break;
796 				}
797 				action = taction;
798 			}
799 		}
800 	}
801 	if (action == A_X11MOUSE_IN)
802 		action = x11mouse_action(FALSE);
803 	else if (action == A_X116MOUSE_IN)
804 		action = x116mouse_action(FALSE);
805 	return (action);
806 }
807 
808 /*
809  * Decode a command from the cmdtables list.
810  */
fcmd_decode(constant char * cmd,constant char ** sp)811 public int fcmd_decode(constant char *cmd, constant char **sp)
812 {
813 	return (cmd_decode(list_fcmd_tables, cmd, sp));
814 }
815 
816 /*
817  * Decode a command from the edittables list.
818  */
ecmd_decode(constant char * cmd,constant char ** sp)819 public int ecmd_decode(constant char *cmd, constant char **sp)
820 {
821 	return (cmd_decode(list_ecmd_tables, cmd, sp));
822 }
823 
824 
825 /*
826  * Get the value of an environment variable.
827  * Looks first in the lesskey file, then in the real environment.
828  */
lgetenv(constant char * var)829 public constant char * lgetenv(constant char *var)
830 {
831 	int a;
832 	constant char *s;
833 
834 	a = cmd_decode(list_var_tables, var, &s);
835 	if (a == EV_OK)
836 		return (s);
837 	s = getenv(var);
838 	if (s != NULL && *s != '\0')
839 		return (s);
840 	a = cmd_decode(list_sysvar_tables, var, &s);
841 	if (a == EV_OK)
842 		return (s);
843 	return (NULL);
844 }
845 
846 /*
847  * Like lgetenv, but also uses a buffer partially filled with an env table.
848  */
lgetenv_ext(constant char * var,unsigned char * env_buf,size_t env_buf_len)849 public constant char * lgetenv_ext(constant char *var, unsigned char *env_buf, size_t env_buf_len)
850 {
851 	constant char *r;
852 	size_t e;
853 	size_t env_end = 0;
854 
855 	for (e = 0;;)
856 	{
857 		for (; e < env_buf_len; e++)
858 			if (env_buf[e] == '\0')
859 				break;
860 		if (e >= env_buf_len) break;
861 		if (env_buf[++e] & A_EXTRA)
862 		{
863 			for (e = e+1; e < env_buf_len; e++)
864 				if (env_buf[e] == '\0')
865 					break;
866 		}
867 		e++;
868 		if (e >= env_buf_len) break;
869 		env_end = e;
870 	}
871 	/* Temporarily add env_buf to var_tables, do the lookup, then remove it. */
872 	add_uvar_table(env_buf, env_end);
873 	r = lgetenv(var);
874 	pop_cmd_table(&list_var_tables);
875 	return r;
876 }
877 
878 /*
879  * Is a string null or empty?
880  */
isnullenv(constant char * s)881 public lbool isnullenv(constant char *s)
882 {
883 	return (s == NULL || *s == '\0');
884 }
885 
886 #if USERFILE
887 /*
888  * Get an "integer" from a lesskey file.
889  * Integers are stored in a funny format:
890  * two bytes, low order first, in radix KRADIX.
891  */
gint(unsigned char ** sp)892 static size_t gint(unsigned char **sp)
893 {
894 	size_t n;
895 
896 	n = *(*sp)++;
897 	n += *(*sp)++ * KRADIX;
898 	return (n);
899 }
900 
901 /*
902  * Process an old (pre-v241) lesskey file.
903  */
old_lesskey(unsigned char * buf,size_t len)904 static int old_lesskey(unsigned char *buf, size_t len)
905 {
906 	/*
907 	 * Old-style lesskey file.
908 	 * The file must end with either
909 	 *     ...,cmd,0,action
910 	 * or  ...,cmd,0,action|A_EXTRA,string,0
911 	 * So the last byte or the second to last byte must be zero.
912 	 */
913 	if (buf[len-1] != '\0' && buf[len-2] != '\0')
914 		return (-1);
915 	add_fcmd_table(buf, len);
916 	return (0);
917 }
918 
919 /*
920  * Process a new (post-v241) lesskey file.
921  */
new_lesskey(unsigned char * buf,size_t len,lbool sysvar)922 static int new_lesskey(unsigned char *buf, size_t len, lbool sysvar)
923 {
924 	unsigned char *p;
925 	unsigned char *end;
926 	int c;
927 	size_t n;
928 
929 	/*
930 	 * New-style lesskey file.
931 	 * Extract the pieces.
932 	 */
933 	if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
934 	    buf[len-2] != C1_END_LESSKEY_MAGIC ||
935 	    buf[len-1] != C2_END_LESSKEY_MAGIC)
936 		return (-1);
937 	p = buf + 4;
938 	end = buf + len;
939 	for (;;)
940 	{
941 		c = *p++;
942 		switch (c)
943 		{
944 		case CMD_SECTION:
945 			n = gint(&p);
946 			if (p+n >= end)
947 				return (-1);
948 			add_fcmd_table(p, n);
949 			p += n;
950 			break;
951 		case EDIT_SECTION:
952 			n = gint(&p);
953 			if (p+n >= end)
954 				return (-1);
955 			add_ecmd_table(p, n);
956 			p += n;
957 			break;
958 		case VAR_SECTION:
959 			n = gint(&p);
960 			if (p+n >= end)
961 				return (-1);
962 			if (sysvar)
963 				add_sysvar_table(p, n);
964 			else
965 				add_uvar_table(p, n);
966 			p += n;
967 			break;
968 		case END_SECTION:
969 			return (0);
970 		default:
971 			/*
972 			 * Unrecognized section type.
973 			 */
974 			return (-1);
975 		}
976 	}
977 }
978 
979 /*
980  * Set up a user command table, based on a "lesskey" file.
981  */
lesskey(constant char * filename,lbool sysvar)982 public int lesskey(constant char *filename, lbool sysvar)
983 {
984 	unsigned char *buf;
985 	POSITION len;
986 	ssize_t n;
987 	int f;
988 
989 	if (!secure_allow(SF_LESSKEY))
990 		return (1);
991 	/*
992 	 * Try to open the lesskey file.
993 	 */
994 	f = open(filename, OPEN_READ);
995 	if (f < 0)
996 		return (1);
997 
998 	/*
999 	 * Read the file into a buffer.
1000 	 * We first figure out the size of the file and allocate space for it.
1001 	 * {{ Minimal error checking is done here.
1002 	 *    A garbage .less file will produce strange results.
1003 	 *    To avoid a large amount of error checking code here, we
1004 	 *    rely on the lesskey program to generate a good .less file. }}
1005 	 */
1006 	len = filesize(f);
1007 	if (len == NULL_POSITION || len < 3)
1008 	{
1009 		/*
1010 		 * Bad file (valid file must have at least 3 chars).
1011 		 */
1012 		close(f);
1013 		return (-1);
1014 	}
1015 	if ((buf = (unsigned char *) calloc((size_t)len, sizeof(char))) == NULL)
1016 	{
1017 		close(f);
1018 		return (-1);
1019 	}
1020 	if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
1021 	{
1022 		free(buf);
1023 		close(f);
1024 		return (-1);
1025 	}
1026 	n = read(f, buf, (size_t) len);
1027 	close(f);
1028 	if (n != len)
1029 	{
1030 		free(buf);
1031 		return (-1);
1032 	}
1033 
1034 	/*
1035 	 * Figure out if this is an old-style (before version 241)
1036 	 * or new-style lesskey file format.
1037 	 */
1038 	if (len < 4 ||
1039 	    buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
1040 	    buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
1041 		return (old_lesskey(buf, (size_t) len));
1042 	return (new_lesskey(buf, (size_t) len, sysvar));
1043 }
1044 
1045 #if HAVE_LESSKEYSRC
lesskey_text(constant char * filename,lbool sysvar,lbool content)1046 static int lesskey_text(constant char *filename, lbool sysvar, lbool content)
1047 {
1048 	int r;
1049 	static struct lesskey_tables tables;
1050 
1051 	if (!secure_allow(SF_LESSKEY))
1052 		return (1);
1053 	r = content ? parse_lesskey_content(filename, &tables) : parse_lesskey(filename, &tables);
1054 	if (r != 0)
1055 		return (r);
1056 	add_fcmd_table(tables.cmdtable.buf.data, tables.cmdtable.buf.end);
1057 	add_ecmd_table(tables.edittable.buf.data, tables.edittable.buf.end);
1058 	if (sysvar)
1059 		add_sysvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1060 	else
1061 		add_uvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1062 	return (0);
1063 }
1064 
lesskey_src(constant char * filename,lbool sysvar)1065 public int lesskey_src(constant char *filename, lbool sysvar)
1066 {
1067 	return lesskey_text(filename, sysvar, FALSE);
1068 }
1069 
lesskey_content(constant char * content,lbool sysvar)1070 public int lesskey_content(constant char *content, lbool sysvar)
1071 {
1072 	return lesskey_text(content, sysvar, TRUE);
1073 }
1074 
lesskey_parse_error(char * s)1075 void lesskey_parse_error(char *s)
1076 {
1077 	PARG parg;
1078 	parg.p_string = s;
1079 	error("%s", &parg);
1080 }
1081 #endif /* HAVE_LESSKEYSRC */
1082 
1083 /*
1084  * Add a lesskey file.
1085  */
add_hometable(int (* call_lesskey)(constant char *,lbool),constant char * envname,constant char * def_filename,lbool sysvar)1086 static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar)
1087 {
1088 	char *filename = NULL;
1089 	constant char *efilename;
1090 	int r;
1091 
1092 	if (envname != NULL && (efilename = lgetenv(envname)) != NULL)
1093 		filename = save(efilename);
1094 	else if (sysvar) /* def_filename is full path */
1095 		filename = save(def_filename);
1096 	else /* def_filename is just basename */
1097 	{
1098 		/* Remove first char (normally a dot) unless stored in $HOME. */
1099 		constant char *xdg = lgetenv("XDG_CONFIG_HOME");
1100 		if (!isnullenv(xdg))
1101 			filename = dirfile(xdg, &def_filename[1], 1);
1102 		if (filename == NULL)
1103 		{
1104 			constant char *home = lgetenv("HOME");
1105 			if (!isnullenv(home))
1106 			{
1107 				char *cfg_dir = dirfile(home, ".config", 0);
1108 				filename = dirfile(cfg_dir, &def_filename[1], 1);
1109 				free(cfg_dir);
1110 			}
1111 		}
1112 		if (filename == NULL)
1113 			filename = homefile(def_filename);
1114 	}
1115 	if (filename == NULL)
1116 		return -1;
1117 	r = (*call_lesskey)(filename, sysvar);
1118 	free(filename);
1119 	return (r);
1120 }
1121 
1122 /*
1123  * Add the content of a lesskey source file.
1124  */
add_content_table(int (* call_lesskey)(constant char *,lbool),constant char * envname,lbool sysvar)1125 static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar)
1126 {
1127 	constant char *content;
1128 
1129 	(void) call_lesskey; /* not used */
1130 	content = lgetenv(envname);
1131 	if (isnullenv(content))
1132 		return;
1133 	lesskey_content(content, sysvar);
1134 }
1135 #endif /* USERFILE */
1136 
1137 /*
1138  * See if a char is a special line-editing command.
1139  */
editchar(char c,int flags)1140 public int editchar(char c, int flags)
1141 {
1142 	int action;
1143 	int nch;
1144 	constant char *s;
1145 	char usercmd[MAX_CMDLEN+1];
1146 
1147 	/*
1148 	 * An editing character could actually be a sequence of characters;
1149 	 * for example, an escape sequence sent by pressing the uparrow key.
1150 	 * To match the editing string, we use the command decoder
1151 	 * but give it the edit-commands command table
1152 	 * This table is constructed to match the user's keyboard.
1153 	 */
1154 	if (c == erase_char || c == erase2_char)
1155 		return (EC_BACKSPACE);
1156 	if (c == kill_char)
1157 	{
1158 #if MSDOS_COMPILER==WIN32C
1159 		if (!win32_kbhit())
1160 #endif
1161 		return (EC_LINEKILL);
1162 	}
1163 
1164 	/*
1165 	 * Collect characters in a buffer.
1166 	 * Start with the one we have, and get more if we need them.
1167 	 */
1168 	nch = 0;
1169 	do {
1170 		if (nch > 0)
1171 			c = getcc();
1172 		usercmd[nch] = c;
1173 		usercmd[nch+1] = '\0';
1174 		nch++;
1175 		action = ecmd_decode(usercmd, &s);
1176 	} while (action == A_PREFIX && nch < MAX_CMDLEN);
1177 
1178 	if (action == EC_X11MOUSE)
1179 		return (x11mouse_action(TRUE));
1180 	if (action == EC_X116MOUSE)
1181 		return (x116mouse_action(TRUE));
1182 
1183 	if (flags & ECF_NORIGHTLEFT)
1184 	{
1185 		switch (action)
1186 		{
1187 		case EC_RIGHT:
1188 		case EC_LEFT:
1189 			action = A_INVALID;
1190 			break;
1191 		}
1192 	}
1193 #if CMD_HISTORY
1194 	if (flags & ECF_NOHISTORY)
1195 	{
1196 		/*
1197 		 * The caller says there is no history list.
1198 		 * Reject any history-manipulation action.
1199 		 */
1200 		switch (action)
1201 		{
1202 		case EC_UP:
1203 		case EC_DOWN:
1204 			action = A_INVALID;
1205 			break;
1206 		}
1207 	}
1208 #endif
1209 	if (flags & ECF_NOCOMPLETE)
1210 	{
1211 		/*
1212 		 * The caller says we don't want any filename completion cmds.
1213 		 * Reject them.
1214 		 */
1215 		switch (action)
1216 		{
1217 		case EC_F_COMPLETE:
1218 		case EC_B_COMPLETE:
1219 		case EC_EXPAND:
1220 			action = A_INVALID;
1221 			break;
1222 		}
1223 	}
1224 	if ((flags & ECF_PEEK) || action == A_INVALID)
1225 	{
1226 		/*
1227 		 * We're just peeking, or we didn't understand the command.
1228 		 * Unget all the characters we read in the loop above.
1229 		 * This does NOT include the original character that was
1230 		 * passed in as a parameter.
1231 		 */
1232 		while (nch > 1)
1233 		{
1234 			ungetcc(usercmd[--nch]);
1235 		}
1236 	} else
1237 	{
1238 		if (s != NULL)
1239 			ungetsc(s);
1240 	}
1241 	return action;
1242 }
1243 
1244