xref: /freebsd/contrib/less/decode.c (revision e40817302ebdf89df2f3bcd679fb7f2a18c244dc)
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 extern char *no_config;
40 
41 static constant lbool allow_drag = TRUE;
42 
43 #if USERFILE
44 /* "content" is lesskey source, never binary. */
45 static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar);
46 static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar);
47 #endif /* USERFILE */
48 
49 #define SK(k) \
50 	SK_SPECIAL_KEY, (k), 6, 1, 1, 1
51 /*
52  * Command table is ordered roughly according to expected
53  * frequency of use, so the common commands are near the beginning.
54  */
55 
56 static unsigned char cmdtable[] =
57 {
58 	'\r',0,                         A_F_LINE,
59 	'\n',0,                         A_F_LINE,
60 	'e',0,                          A_F_LINE,
61 	'j',0,                          A_F_LINE,
62 	SK(SK_DOWN_ARROW),0,            A_F_LINE,
63 	CONTROL('E'),0,                 A_F_LINE,
64 	CONTROL('N'),0,                 A_F_LINE,
65 	'k',0,                          A_B_LINE,
66 	'y',0,                          A_B_LINE,
67 	CONTROL('Y'),0,                 A_B_LINE,
68 	SK(SK_CONTROL_K),0,             A_B_LINE,
69 	CONTROL('P'),0,                 A_B_LINE,
70 	SK(SK_UP_ARROW),0,              A_B_LINE,
71 	'J',0,                          A_FF_LINE,
72 	'K',0,                          A_BF_LINE,
73 	'Y',0,                          A_BF_LINE,
74 	'd',0,                          A_F_SCROLL,
75 	CONTROL('D'),0,                 A_F_SCROLL,
76 	'u',0,                          A_B_SCROLL,
77 	CONTROL('U'),0,                 A_B_SCROLL,
78 	ESC,'[','M',0,                  A_X11MOUSE_IN,
79 	ESC,'[','<',0,                  A_X116MOUSE_IN,
80 	' ',0,                          A_F_SCREEN,
81 	'f',0,                          A_F_SCREEN,
82 	CONTROL('F'),0,                 A_F_SCREEN,
83 	CONTROL('V'),0,                 A_F_SCREEN,
84 	SK(SK_PAGE_DOWN),0,             A_F_SCREEN,
85 	'b',0,                          A_B_SCREEN,
86 	CONTROL('B'),0,                 A_B_SCREEN,
87 	ESC,'v',0,                      A_B_SCREEN,
88 	SK(SK_PAGE_UP),0,               A_B_SCREEN,
89 	'z',0,                          A_F_WINDOW,
90 	'w',0,                          A_B_WINDOW,
91 	ESC,' ',0,                      A_FF_SCREEN,
92 	ESC,'b',0,                      A_BF_SCREEN,
93 	ESC,'j',0,                      A_F_NEWLINE,
94 	ESC,'k',0,                      A_B_NEWLINE,
95 	'F',0,                          A_F_FOREVER,
96 	ESC,'f',0,                      A_F_FOREVER_BELL,
97 	ESC,'F',0,                      A_F_UNTIL_HILITE,
98 	'R',0,                          A_FREPAINT,
99 	'r',0,                          A_REPAINT,
100 	CONTROL('R'),0,                 A_REPAINT,
101 	CONTROL('L'),0,                 A_REPAINT,
102 	ESC,'u',0,                      A_UNDO_SEARCH,
103 	ESC,'U',0,                      A_CLR_SEARCH,
104 	'g',0,                          A_GOLINE,
105 	SK(SK_HOME),0,                  A_LLSHIFT,
106 	SK(SK_SHIFT_HOME),0,            A_GOLINE|A_EXTRA,           ESC,'{',0,
107 	SK(SK_CTL_HOME),0,              A_GOLINE|A_EXTRA,           ESC,'{',0,
108 	SK(SK_END),0,                   A_RRSHIFT,
109 	SK(SK_SHIFT_END),0,             A_GOEND|A_EXTRA,            ESC,'}',0,
110 	SK(SK_CTL_END),0,               A_GOEND|A_EXTRA,            ESC,'}',0,
111 	'<',0,                          A_GOLINE,
112 	ESC,'<',0,                      A_GOLINE,
113 	'p',0,                          A_PERCENT,
114 	'%',0,                          A_PERCENT,
115 	ESC,'(',0,                      A_LSHIFT,
116 	ESC,')',0,                      A_RSHIFT,
117 	ESC,'{',0,                      A_LLSHIFT,
118 	ESC,'}',0,                      A_RRSHIFT,
119 	SK(SK_RIGHT_ARROW),0,           A_RSHIFT,
120 	SK(SK_LEFT_ARROW),0,            A_LSHIFT,
121 	SK(SK_CTL_RIGHT_ARROW),0,       A_RRSHIFT,
122 	SK(SK_CTL_LEFT_ARROW),0,        A_LLSHIFT,
123 	'{',0,                          A_F_BRACKET|A_EXTRA,        '{','}',0,
124 	'}',0,                          A_B_BRACKET|A_EXTRA,        '{','}',0,
125 	'(',0,                          A_F_BRACKET|A_EXTRA,        '(',')',0,
126 	')',0,                          A_B_BRACKET|A_EXTRA,        '(',')',0,
127 	'[',0,                          A_F_BRACKET|A_EXTRA,        '[',']',0,
128 	']',0,                          A_B_BRACKET|A_EXTRA,        '[',']',0,
129 	ESC,CONTROL('F'),0,             A_F_BRACKET,
130 	ESC,CONTROL('B'),0,             A_B_BRACKET,
131 	'G',0,                          A_GOEND,
132 	ESC,'G',0,                      A_GOEND_BUF,
133 	ESC,'>',0,                      A_GOEND,
134 	'>',0,                          A_GOEND,
135 	'P',0,                          A_GOPOS,
136 
137 	'0',0,                          A_DIGIT,
138 	'1',0,                          A_DIGIT,
139 	'2',0,                          A_DIGIT,
140 	'3',0,                          A_DIGIT,
141 	'4',0,                          A_DIGIT,
142 	'5',0,                          A_DIGIT,
143 	'6',0,                          A_DIGIT,
144 	'7',0,                          A_DIGIT,
145 	'8',0,                          A_DIGIT,
146 	'9',0,                          A_DIGIT,
147 	'.',0,                          A_DIGIT,
148 
149 	'=',0,                          A_STAT,
150 	CONTROL('G'),0,                 A_STAT,
151 	':','f',0,                      A_STAT,
152 	'/',0,                          A_F_SEARCH,
153 	'?',0,                          A_B_SEARCH,
154 	ESC,'/',0,                      A_F_SEARCH|A_EXTRA,        '*',0,
155 	ESC,'?',0,                      A_B_SEARCH|A_EXTRA,        '*',0,
156 	'n',0,                          A_AGAIN_SEARCH,
157 	ESC,'n',0,                      A_T_AGAIN_SEARCH,
158 	'N',0,                          A_REVERSE_SEARCH,
159 	ESC,'N',0,                      A_T_REVERSE_SEARCH,
160 	'&',0,                          A_FILTER,
161 	'm',0,                          A_SETMARK,
162 	'M',0,                          A_SETMARKBOT,
163 	ESC,'m',0,                      A_CLRMARK,
164 	'\'',0,                         A_GOMARK,
165 	CONTROL('X'),CONTROL('X'),0,    A_GOMARK,
166 	'E',0,                          A_EXAMINE,
167 	':','e',0,                      A_EXAMINE,
168 	CONTROL('X'),CONTROL('V'),0,    A_EXAMINE,
169 	':','n',0,                      A_NEXT_FILE,
170 	':','p',0,                      A_PREV_FILE,
171 	CONTROL('O'),CONTROL('N'),0,    A_OSC8_F_SEARCH,
172 	CONTROL('O'),'n',0,             A_OSC8_F_SEARCH,
173 	CONTROL('O'),CONTROL('P'),0,    A_OSC8_B_SEARCH,
174 	CONTROL('O'),'p',0,             A_OSC8_B_SEARCH,
175 	CONTROL('O'),CONTROL('O'),0,    A_OSC8_OPEN,
176 	CONTROL('O'),'o',0,             A_OSC8_OPEN,
177 	CONTROL('O'),CONTROL('L'),0,    A_OSC8_JUMP,
178 	CONTROL('O'),'l',0,             A_OSC8_JUMP,
179 	't',0,                          A_NEXT_TAG,
180 	'T',0,                          A_PREV_TAG,
181 	':','x',0,                      A_INDEX_FILE,
182 	':','d',0,                      A_REMOVE_FILE,
183 	'-',0,                          A_OPT_TOGGLE,
184 	':','t',0,                      A_OPT_TOGGLE|A_EXTRA,        't',0,
185 	's',0,                          A_OPT_TOGGLE|A_EXTRA,        'o',0,
186 	'_',0,                          A_DISP_OPTION,
187 	'|',0,                          A_PIPE,
188 	'v',0,                          A_VISUAL,
189 	'!',0,                          A_SHELL,
190 	'#',0,                          A_PSHELL,
191 	'+',0,                          A_FIRSTCMD,
192 
193 	SK(SK_PAD_U),0,                 A_B_LINE,
194 	SK(SK_PAD_D),0,                 A_F_LINE,
195 	SK(SK_PAD_R),0,                 A_RSHIFT,
196 	SK(SK_PAD_L),0,                 A_LSHIFT,
197 	SK(SK_PAD_UR),0,                A_B_SCREEN,
198 	SK(SK_PAD_UL),0,                A_LLSHIFT,
199 	SK(SK_PAD_DR),0,                A_RRSHIFT,
200 	SK(SK_PAD_DL),0,                A_GOEND,
201 	SK(SK_PAD_STAR),0,              A_NOACTION|A_EXTRA,   '*',0,
202 	SK(SK_PAD_SLASH),0,             A_NOACTION|A_EXTRA,   '/',0,
203 	SK(SK_PAD_DASH),0,              A_NOACTION|A_EXTRA,   '-',0,
204 	SK(SK_PAD_PLUS),0,              A_NOACTION|A_EXTRA,   '+',0,
205 	SK(SK_PAD_DOT),0,               A_NOACTION|A_EXTRA,   '.',0,
206 	SK(SK_PAD_COMMA),0,             A_NOACTION,
207 	SK(SK_PAD_ZERO),0,              A_NOACTION|A_EXTRA,   '0',0,
208 	SK(SK_PAD_CENTER),0,            A_NOACTION,
209 
210 	ESC,'[','2','0','0','~',0,      A_START_PASTE,
211 	ESC,'[','2','0','1','~',0,      A_END_PASTE,
212 
213 	'H',0,                          A_HELP,
214 	'h',0,                          A_HELP,
215 	SK(SK_F1),0,                    A_HELP,
216 	'V',0,                          A_VERSION,
217 	'q',0,                          A_QUIT,
218 	'Q',0,                          A_QUIT,
219 	':','q',0,                      A_QUIT,
220 	':','Q',0,                      A_QUIT,
221 	'Z','Z',0,                      A_QUIT
222 };
223 
224 static unsigned char edittable[] =
225 {
226 	'\t',0,                         EC_F_COMPLETE,  /* TAB */
227 	'\17',0,                        EC_B_COMPLETE,  /* BACKTAB */
228 	SK(SK_BACKTAB),0,               EC_B_COMPLETE,  /* BACKTAB */
229 	ESC,'\t',0,                     EC_B_COMPLETE,  /* ESC TAB */
230 	CONTROL('L'),0,                 EC_EXPAND,      /* CTRL-L */
231 	CONTROL('V'),0,                 EC_LITERAL,     /* BACKSLASH */
232 	CONTROL('A'),0,                 EC_LITERAL,     /* BACKSLASH */
233 	ESC,'l',0,                      EC_RIGHT,       /* ESC l */
234 	SK(SK_RIGHT_ARROW),0,           EC_RIGHT,       /* RIGHTARROW */
235 	ESC,'h',0,                      EC_LEFT,        /* ESC h */
236 	SK(SK_LEFT_ARROW),0,            EC_LEFT,        /* LEFTARROW */
237 	ESC,'b',0,                      EC_W_LEFT,      /* ESC b */
238 	ESC,SK(SK_LEFT_ARROW),0,        EC_W_LEFT,      /* ESC LEFTARROW */
239 	SK(SK_CTL_LEFT_ARROW),0,        EC_W_LEFT,      /* CTRL-LEFTARROW */
240 	ESC,'w',0,                      EC_W_RIGHT,     /* ESC w */
241 	ESC,SK(SK_RIGHT_ARROW),0,       EC_W_RIGHT,     /* ESC RIGHTARROW */
242 	SK(SK_CTL_RIGHT_ARROW),0,       EC_W_RIGHT,     /* CTRL-RIGHTARROW */
243 	ESC,'i',0,                      EC_INSERT,      /* ESC i */
244 	SK(SK_INSERT),0,                EC_INSERT,      /* INSERT */
245 	ESC,'x',0,                      EC_DELETE,      /* ESC x */
246 	SK(SK_DELETE),0,                EC_DELETE,      /* DELETE */
247 	ESC,'X',0,                      EC_W_DELETE,    /* ESC X */
248 	ESC,SK(SK_DELETE),0,            EC_W_DELETE,    /* ESC DELETE */
249 	SK(SK_CTL_DELETE),0,            EC_W_DELETE,    /* CTRL-DELETE */
250 	SK(SK_CTL_BACKSPACE),0,         EC_W_BACKSPACE, /* CTRL-BACKSPACE */
251 	ESC,SK(SK_BACKSPACE),0,         EC_W_BACKSPACE, /* ESC BACKSPACE */
252 	ESC,'0',0,                      EC_HOME,        /* ESC 0 */
253 	SK(SK_HOME),0,                  EC_HOME,        /* HOME */
254 	SK(SK_SHIFT_HOME),0,            EC_HOME,        /* SHIFT-HOME */
255 	SK(SK_CTL_HOME),0,              EC_HOME,        /* CTRL-HOME */
256 	ESC,'$',0,                      EC_END,         /* ESC $ */
257 	SK(SK_END),0,                   EC_END,         /* END */
258 	SK(SK_SHIFT_END),0,             EC_END,         /* SHIFT-END */
259 	SK(SK_CTL_END),0,               EC_END,         /* CTRL-END */
260 	ESC,'k',0,                      EC_UP,          /* ESC k */
261 	SK(SK_UP_ARROW),0,              EC_UP,          /* UPARROW */
262 	ESC,'j',0,                      EC_DOWN,        /* ESC j */
263 	SK(SK_DOWN_ARROW),0,            EC_DOWN,        /* DOWNARROW */
264 	CONTROL('G'),0,                 EC_ABORT,       /* CTRL-G */
265 	SK(SK_PAD_U),0,                 EC_UP,
266 	SK(SK_PAD_D),0,                 EC_DOWN,
267 	SK(SK_PAD_R),0,                 EC_RIGHT,
268 	SK(SK_PAD_L),0,                 EC_LEFT,
269 	SK(SK_PAD_UR),0,                A_NOACTION,
270 	SK(SK_PAD_UL),0,                EC_HOME,
271 	SK(SK_PAD_DR),0,                A_NOACTION,
272 	SK(SK_PAD_DL),0,                EC_END,
273 	SK(SK_PAD_STAR),0,              A_NOACTION|A_EXTRA,   '*',0,
274 	SK(SK_PAD_SLASH),0,             A_NOACTION|A_EXTRA,   '/',0,
275 	SK(SK_PAD_DASH),0,              A_NOACTION|A_EXTRA,   '-',0,
276 	SK(SK_PAD_PLUS),0,              A_NOACTION|A_EXTRA,   '+',0,
277 	SK(SK_PAD_DOT),0,               A_NOACTION|A_EXTRA,   '.',0,
278 	SK(SK_PAD_COMMA),0,             A_NOACTION|A_EXTRA,   ',',0,
279 	SK(SK_PAD_ZERO),0,              A_NOACTION|A_EXTRA,   '0',0,
280 	SK(SK_PAD_CENTER),0,            A_NOACTION,
281 	ESC,'[','M',0,                  EC_X11MOUSE,    /* X11 mouse report */
282 	ESC,'[','<',0,                  EC_X116MOUSE,   /* X11 1006 mouse report */
283 	ESC,'[','2','0','0','~',0,      A_START_PASTE,  /* open paste bracket */
284 	ESC,'[','2','0','1','~',0,      A_END_PASTE,    /* close paste bracket */
285 };
286 
287 static unsigned char dflt_vartable[] =
288 {
289 	'L','E','S','S','_','O','S','C','8','_','m','a','n', 0, EV_OK|A_EXTRA,
290 		/* echo '%o' | sed -e "s,^man\:\\([^(]*\\)( *\\([^)]*\\)\.*,-man '\\2' '\\1'," -e"t X" -e"s,\.*,-echo Invalid man link," -e"\: X" */
291 		'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','"',
292 		0,
293 
294 	'L','E','S','S','_','O','S','C','8','_','f','i','l','e', 0, EV_OK|A_EXTRA,
295 		/* 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 */
296 		'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',
297 		0,
298 };
299 
300 /*
301  * Structure to support a list of command tables.
302  */
303 struct tablelist
304 {
305 	struct tablelist *t_next;
306 	unsigned char *t_start;
307 	unsigned char *t_end;
308 };
309 
310 /*
311  * List of command tables and list of line-edit tables.
312  */
313 static struct tablelist *list_fcmd_tables = NULL;
314 static struct tablelist *list_ecmd_tables = NULL;
315 static struct tablelist *list_var_tables = NULL;
316 static struct tablelist *list_sysvar_tables = NULL;
317 
318 
319 /*
320  * Expand special key abbreviations in a command table.
321  */
322 static void expand_special_keys(unsigned char *table, size_t len)
323 {
324 	unsigned char *fm;
325 	unsigned char *to;
326 	int a;
327 	constant char *repl;
328 	size_t klen;
329 
330 	for (fm = table;  fm < table + len; )
331 	{
332 		/*
333 		 * Rewrite each command in the table with any
334 		 * special key abbreviations expanded.
335 		 */
336 		for (to = fm;  *fm != '\0'; )
337 		{
338 			if (*fm != SK_SPECIAL_KEY)
339 			{
340 				*to++ = *fm++;
341 				continue;
342 			}
343 			/*
344 			 * After SK_SPECIAL_KEY, next byte is the type
345 			 * of special key (one of the SK_* constants),
346 			 * and the byte after that is the number of bytes,
347 			 * N, reserved by the abbreviation (including the
348 			 * SK_SPECIAL_KEY and key type bytes).
349 			 * Replace all N bytes with the actual bytes
350 			 * output by the special key on this terminal.
351 			 */
352 			repl = special_key_str(fm[1]);
353 			klen = fm[2] & 0377;
354 			fm += klen;
355 			if (repl == NULL || strlen(repl) > klen)
356 				repl = "\377";
357 			while (*repl != '\0')
358 				*to++ = (unsigned char) *repl++; /*{{type-issue}}*/
359 		}
360 		*to++ = '\0';
361 		/*
362 		 * Fill any unused bytes between end of command and
363 		 * the action byte with A_SKIP.
364 		 */
365 		while (to <= fm)
366 			*to++ = A_SKIP;
367 		fm++;
368 		a = *fm++ & 0377;
369 		if (a & A_EXTRA)
370 		{
371 			while (*fm++ != '\0')
372 				continue;
373 		}
374 	}
375 }
376 
377 /*
378  * Expand special key abbreviations in a list of command tables.
379  */
380 static void expand_cmd_table(struct tablelist *tlist)
381 {
382 	struct tablelist *t;
383 	for (t = tlist;  t != NULL;  t = t->t_next)
384 	{
385 		expand_special_keys(t->t_start, ptr_diff(t->t_end, t->t_start));
386 	}
387 }
388 
389 /*
390  * Expand special key abbreviations in all command tables.
391  */
392 public void expand_cmd_tables(void)
393 {
394 	expand_cmd_table(list_fcmd_tables);
395 	expand_cmd_table(list_ecmd_tables);
396 	expand_cmd_table(list_var_tables);
397 	expand_cmd_table(list_sysvar_tables);
398 }
399 
400 /*
401  * Initialize the command lists.
402  */
403 public void init_cmds(void)
404 {
405 	/*
406 	 * Add the default command tables.
407 	 */
408 	add_fcmd_table(cmdtable, sizeof(cmdtable));
409 	add_ecmd_table(edittable, sizeof(edittable));
410 	add_sysvar_table(dflt_vartable, sizeof(dflt_vartable));
411 #if USERFILE
412 #ifdef BINDIR /* For backwards compatibility */
413 	/* Try to add tables in the OLD system lesskey file. */
414 	add_hometable(lesskey, NULL, BINDIR "/.sysless", TRUE);
415 #endif
416 	/*
417 	 * Try to load lesskey source file or binary file.
418 	 * If the source file succeeds, don't load binary file.
419 	 * The binary file is likely to have been generated from
420 	 * a (possibly out of date) copy of the src file,
421 	 * so loading it is at best redundant.
422 	 */
423 	/*
424 	 * Try to add tables in system lesskey src file.
425 	 */
426 #if HAVE_LESSKEYSRC
427 	if (add_hometable(lesskey_src, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS, TRUE) != 0)
428 #endif
429 	{
430 		/*
431 		 * Try to add the tables in the system lesskey binary file.
432 		 */
433 		add_hometable(lesskey, "LESSKEY_SYSTEM", LESSKEYFILE_SYS, TRUE);
434 	}
435 	/*
436 	 * Try to add tables in the lesskey src file "$HOME/.lesskey".
437 	 */
438 #if HAVE_LESSKEYSRC
439 	if (add_hometable(lesskey_src, "LESSKEYIN", DEF_LESSKEYINFILE, FALSE) != 0)
440 #endif
441 	{
442 		/*
443 		 * Try to add the tables in the standard lesskey binary file "$HOME/.less".
444 		 */
445 		add_hometable(lesskey, "LESSKEY", LESSKEYFILE, FALSE);
446 	}
447 
448 	add_content_table(lesskey_content, "LESSKEY_CONTENT_SYSTEM", TRUE);
449 	add_content_table(lesskey_content, "LESSKEY_CONTENT", FALSE);
450 #endif /* USERFILE */
451 }
452 
453 /*
454  * Add a command table.
455  */
456 static int add_cmd_table(struct tablelist **tlist, unsigned char *buf, size_t len)
457 {
458 	struct tablelist *t;
459 
460 	if (len == 0)
461 		return (0);
462 	/*
463 	 * Allocate a tablelist structure, initialize it,
464 	 * and link it into the list of tables.
465 	 */
466 	if ((t = (struct tablelist *)
467 			calloc(1, sizeof(struct tablelist))) == NULL)
468 	{
469 		return (-1);
470 	}
471 	t->t_start = buf;
472 	t->t_end = buf + len;
473 	t->t_next = NULL;
474 	if (*tlist == NULL)
475 		*tlist = t;
476 	else
477 	{
478 		struct tablelist *e;
479 		for (e = *tlist;  e->t_next != NULL;  e = e->t_next)
480 			continue;
481 		e->t_next = t;
482 	}
483 	return (0);
484 }
485 
486 /*
487  * Remove the last command table in a list.
488  */
489 static void pop_cmd_table(struct tablelist **tlist)
490 {
491 	struct tablelist *t;
492 	if (*tlist == NULL)
493 		return;
494 	if ((*tlist)->t_next == NULL)
495 	{
496 		t = *tlist;
497 		*tlist = NULL;
498 	} else
499 	{
500 		struct tablelist *e;
501 		for (e = *tlist;  e->t_next->t_next != NULL;  e = e->t_next)
502 			continue;
503 		t = e->t_next;
504 		e->t_next = NULL;
505 	}
506 	free(t);
507 }
508 
509 /*
510  * Add a command table.
511  */
512 public void add_fcmd_table(unsigned char *buf, size_t len)
513 {
514 	if (add_cmd_table(&list_fcmd_tables, buf, len) < 0)
515 		error("Warning: some commands disabled", NULL_PARG);
516 }
517 
518 /*
519  * Add an editing command table.
520  */
521 public void add_ecmd_table(unsigned char *buf, size_t len)
522 {
523 	if (add_cmd_table(&list_ecmd_tables, buf, len) < 0)
524 		error("Warning: some edit commands disabled", NULL_PARG);
525 }
526 
527 /*
528  * Add an environment variable table.
529  */
530 static void add_var_table(struct tablelist **tlist, mutable unsigned char *buf, size_t len)
531 {
532 	struct xbuffer xbuf;
533 
534 	xbuf_init(&xbuf);
535 	expand_evars((mutable char*)buf, len, &xbuf); /*{{unsigned-issue}}*/
536 	/* {{ We leak the table in buf. expand_evars scribbled in it so it's useless anyway. }} */
537 	if (add_cmd_table(tlist, xbuf.data, xbuf.end) < 0)
538 		error("Warning: environment variables from lesskey file unavailable", NULL_PARG);
539 }
540 
541 public void add_uvar_table(unsigned char *buf, size_t len)
542 {
543 	add_var_table(&list_var_tables, buf, len);
544 }
545 
546 public void add_sysvar_table(unsigned char *buf, size_t len)
547 {
548 	add_var_table(&list_sysvar_tables, buf, len);
549 }
550 
551 /*
552  * Return action for a mouse wheel down event.
553  */
554 static int mouse_wheel_down(void)
555 {
556 	return ((mousecap == OPT_ONPLUS) ? A_B_MOUSE : A_F_MOUSE);
557 }
558 
559 /*
560  * Return action for a mouse wheel up event.
561  */
562 static int mouse_wheel_up(void)
563 {
564 	return ((mousecap == OPT_ONPLUS) ? A_F_MOUSE : A_B_MOUSE);
565 }
566 
567 /*
568  * Return action for the left mouse button trigger.
569  */
570 static int mouse_button_left(int x, int y, lbool down, lbool drag)
571 {
572 	static int last_drag_y = -1;
573 	static int last_click_y = -1;
574 
575 	if (down && !drag)
576 	{
577 		last_drag_y = last_click_y = y;
578 	}
579 	if (allow_drag && drag && last_drag_y >= 0)
580 	{
581 		/* Drag text up/down */
582 		if (y > last_drag_y)
583 		{
584 			cmd_exec();
585 			backward(y - last_drag_y, FALSE, FALSE, FALSE);
586 			last_drag_y = y;
587 		} else if (y < last_drag_y)
588 		{
589 			cmd_exec();
590 			forward(last_drag_y - y, FALSE, FALSE, FALSE);
591 			last_drag_y = y;
592 		}
593 	} else if (!down)
594 	{
595 #if OSC8_LINK
596 		if (secure_allow(SF_OSC8_OPEN))
597 		{
598 			if (osc8_click(y, x))
599 				return (A_NOACTION);
600 		}
601 #else
602 		(void) x;
603 #endif /* OSC8_LINK */
604 		if (y < sc_height-1 && y == last_click_y)
605 		{
606 			setmark('#', y);
607 			screen_trashed();
608 		}
609 	}
610 	return (A_NOACTION);
611 }
612 
613 /*
614  * Return action for the right mouse button trigger.
615  */
616 static int mouse_button_right(int x, int y, lbool down, lbool drag)
617 {
618 	(void) x; (void) drag;
619 	/*
620 	 * {{ unlike mouse_button_left, we could return an action,
621 	 *    but keep it near mouse_button_left for readability. }}
622 	 */
623 	if (!down && y < sc_height-1)
624 	{
625 		gomark('#');
626 		screen_trashed();
627 	}
628 	return (A_NOACTION);
629 }
630 
631 /*
632  * Read a decimal integer. Return the integer and set *pterm to the terminating char.
633  */
634 static int getcc_int(char *pterm)
635 {
636 	int num = 0;
637 	int digits = 0;
638 	for (;;)
639 	{
640 		char ch = getcc();
641 		if (ch < '0' || ch > '9')
642 		{
643 			if (pterm != NULL) *pterm = ch;
644 			if (digits == 0)
645 				return (-1);
646 			return (num);
647 		}
648 		if (ckd_mul(&num, num, 10) || ckd_add(&num, num, ch - '0'))
649 			return -1;
650 		++digits;
651 	}
652 }
653 
654 static int x11mouse_button(int btn, int x, int y, lbool down, lbool drag)
655 {
656 	switch (btn) {
657 	case X11MOUSE_BUTTON1:
658 		return mouse_button_left(x, y, down, drag);
659 	/* is BUTTON2 the rightmost with 2-buttons mouse? */
660 	case X11MOUSE_BUTTON2:
661 	case X11MOUSE_BUTTON3:
662 		return mouse_button_right(x, y, down, drag);
663 	}
664 	return (A_NOACTION);
665 }
666 
667 /*
668  * Read suffix of mouse input and return the action to take.
669  * The prefix ("\e[M") has already been read.
670  */
671 static int x11mouse_action(lbool skip)
672 {
673 	static int prev_b = X11MOUSE_BUTTON_REL;
674 	int x, y;
675 	int b = getcc() - X11MOUSE_OFFSET;
676 	lbool drag = ((b & X11MOUSE_DRAG) != 0);
677 	b &= ~X11MOUSE_DRAG;
678 	x = getcc() - X11MOUSE_OFFSET-1;
679 	y = getcc() - X11MOUSE_OFFSET-1;
680 	if (skip)
681 		return (A_NOACTION);
682 	switch (b) {
683 	case X11MOUSE_WHEEL_DOWN:
684 		return mouse_wheel_down();
685 	case X11MOUSE_WHEEL_UP:
686 		return mouse_wheel_up();
687 	case X11MOUSE_BUTTON1:
688 	case X11MOUSE_BUTTON2:
689 	case X11MOUSE_BUTTON3:
690 		prev_b = b;
691 		return x11mouse_button(b, x, y, TRUE, drag);
692 	case X11MOUSE_BUTTON_REL: /* button up */
693 		return x11mouse_button(prev_b, x, y, FALSE, drag);
694 	}
695 	return (A_NOACTION);
696 }
697 
698 /*
699  * Read suffix of mouse input and return the action to take.
700  * The prefix ("\e[<") has already been read.
701  */
702 static int x116mouse_action(lbool skip)
703 {
704 	char ch;
705 	int x, y;
706 	int b = getcc_int(&ch);
707 	lbool drag = ((b & X11MOUSE_DRAG) != 0);
708 	b &= ~X11MOUSE_DRAG;
709 	if (b < 0 || ch != ';') return (A_NOACTION);
710 	x = getcc_int(&ch) - 1;
711 	if (x < 0 || ch != ';') return (A_NOACTION);
712 	y = getcc_int(&ch) - 1;
713 	if (y < 0) return (A_NOACTION);
714 	if (skip)
715 		return (A_NOACTION);
716 	switch (b) {
717 	case X11MOUSE_WHEEL_DOWN:
718 		return mouse_wheel_down();
719 	case X11MOUSE_WHEEL_UP:
720 		return mouse_wheel_up();
721 	case X11MOUSE_BUTTON1:
722 	case X11MOUSE_BUTTON2:
723 	case X11MOUSE_BUTTON3: {
724 		lbool down = (ch == 'M');
725 		lbool up = (ch == 'm');
726 		if (up || down)
727 			return x11mouse_button(b, x, y, down, drag);
728 		break; }
729 	}
730 	return (A_NOACTION);
731 }
732 
733 /*
734  * Return the largest N such that the first N chars of goal
735  * are equal to the last N chars of str.
736  */
737 static size_t cmd_match(constant char *goal, constant char *str)
738 {
739 	size_t slen = strlen(str);
740 	size_t len;
741 	for (len = slen;  len > 0;  len--)
742 		if (strncmp(str + slen - len, goal, len) == 0)
743 			break;
744 	return len;
745 }
746 
747 /*
748  * Return pointer to next command table entry.
749  * Also return the action and the extra string from the entry.
750  */
751 static constant unsigned char * cmd_next_entry(constant unsigned char *entry, mutable int *action, mutable constant unsigned char **extra, mutable size_t *cmdlen)
752 {
753 	int a;
754 	constant unsigned char *oentry = entry;
755 	while (*entry != '\0') /* skip cmd */
756 		++entry;
757 	if (cmdlen != NULL)
758 		*cmdlen = ptr_diff(entry, oentry);
759 	do
760 		a = *++entry; /* get action */
761 	while (a == A_SKIP);
762 	++entry; /* skip action */
763 	if (extra != NULL)
764 		*extra = (a & A_EXTRA) ? entry : NULL;
765 	if (a & A_EXTRA)
766 	{
767 		while (*entry++ != '\0') /* skip extra string */
768 			continue;
769 		a &= ~A_EXTRA;
770 	}
771 	if (action != NULL)
772 		*action = a;
773 	return entry;
774 }
775 
776 /*
777  * Search a single command table for the command string in cmd.
778  */
779 static int cmd_search(constant char *cmd, constant unsigned char *table, constant unsigned char *endtable, constant unsigned char **extra, size_t *mlen)
780 {
781 	int action = A_INVALID;
782 	size_t match_len = 0;
783 	if (extra != NULL)
784 		*extra = NULL;
785 	while (table < endtable)
786 	{
787 		int taction;
788 		constant unsigned char *textra;
789 		size_t cmdlen;
790 		size_t match = cmd_match((constant char *) table, cmd);
791 		table = cmd_next_entry(table, &taction, &textra, &cmdlen);
792 		if (taction == A_END_LIST)
793 			return (-action);
794 		if (match >= match_len)
795 		{
796 			if (match == cmdlen) /* (last chars of) cmd matches this table entry */
797 			{
798 				action = taction;
799 				if (extra != NULL)
800 					*extra = textra;
801 			} else if (match > 0 && action == A_INVALID) /* cmd is a prefix of this table entry */
802 			{
803 				action = A_PREFIX;
804 			}
805 			match_len = match;
806 		}
807 	}
808 	if (mlen != NULL)
809 		*mlen = match_len;
810 	return (action);
811 }
812 
813 /*
814  * Decode a command character and return the associated action.
815  * The "extra" string, if any, is returned in sp.
816  */
817 static int cmd_decode(struct tablelist *tlist, constant char *cmd, constant char **sp)
818 {
819 	struct tablelist *t;
820 	int action = A_INVALID;
821 	size_t match_len = 0;
822 
823 	/*
824 	 * Search for the cmd thru all the command tables.
825 	 * If we find it more than once, take the last one.
826 	 */
827 	*sp = NULL;
828 	for (t = tlist;  t != NULL;  t = t->t_next)
829 	{
830 		constant unsigned char *tsp;
831 		size_t mlen = match_len;
832 		int taction = cmd_search(cmd, t->t_start, t->t_end, &tsp, &mlen);
833 		if (mlen >= match_len)
834 		{
835 			match_len = mlen;
836 			if (taction != A_INVALID)
837 			{
838 				*sp = (constant char *) tsp;
839 				if (taction < 0)
840 				{
841 					action = -taction;
842 					break;
843 				}
844 				action = taction;
845 			}
846 		}
847 	}
848 	if (action == A_X11MOUSE_IN)
849 		action = x11mouse_action(FALSE);
850 	else if (action == A_X116MOUSE_IN)
851 		action = x116mouse_action(FALSE);
852 	return (action);
853 }
854 
855 /*
856  * Decode a command from the cmdtables list.
857  */
858 public int fcmd_decode(constant char *cmd, constant char **sp)
859 {
860 	return (cmd_decode(list_fcmd_tables, cmd, sp));
861 }
862 
863 /*
864  * Decode a command from the edittables list.
865  */
866 public int ecmd_decode(constant char *cmd, constant char **sp)
867 {
868 	return (cmd_decode(list_ecmd_tables, cmd, sp));
869 }
870 
871 /*
872  * Parse a comma-separated list.
873  * Call func repeatedly, passing each item in the list.
874  * Stop and return FALSE if func ever returns FALSE,
875  * otherwise parse the entire list and return TRUE.
876  */
877 public lbool parse_csl(lbool (*func)(constant char *word, size_t wlen, void *arg), constant char *str, void *arg)
878 {
879 	for (;;)
880 	{
881 		constant char *estr;
882 		while (*str == ' ' || *str == ',') ++str; /* skip leading spaces/commas */
883 		if (*str == '\0') break;
884 		estr = strchr(str, ',');
885 		if (estr == NULL) estr = str + strlen(str);
886 		while (estr > str && estr[-1] == ' ') --estr; /* trim trailing spaces */
887 		if (!(*func)(str, ptr_diff(estr, str), arg))
888 			return FALSE;
889 		str = estr;
890 	}
891 	return TRUE;
892 }
893 
894 /*
895  * Should we ignore the setting of an environment variable?
896  */
897 static lbool word_no_match(constant char *word, size_t wlen, void *arg)
898 {
899 	constant char *var = (constant char *) arg;
900 	return !(wlen == strlen(var) && strncmp(var, word, wlen) == 0);
901 }
902 static lbool ignore_env(constant char *var)
903 {
904 	if (isnullenv(no_config))
905 		return FALSE; /* no_config is not set; don't ignore anything */
906 	/* no_config is set; ignore any var that does not appear in no_config */
907 	return parse_csl(word_no_match, no_config, (void*) var);
908 }
909 
910 /*
911  * Get the value of an environment variable.
912  * Looks first in the lesskey file, then in the real environment.
913  */
914 public constant char * lgetenv(constant char *var)
915 {
916 	int a;
917 	constant char *s;
918 
919 	if (ignore_env(var))
920 		return (NULL);
921 	a = cmd_decode(list_var_tables, var, &s);
922 	if (a == EV_OK)
923 		return (s);
924 	s = getenv(var);
925 	if (s != NULL && *s != '\0')
926 		return (s);
927 	a = cmd_decode(list_sysvar_tables, var, &s);
928 	if (a == EV_OK)
929 		return (s);
930 	return (NULL);
931 }
932 
933 /*
934  * Like lgetenv, but also uses a buffer partially filled with an env table.
935  */
936 public constant char * lgetenv_ext(constant char *var, unsigned char *env_buf, size_t env_buf_len)
937 {
938 	constant char *r;
939 	size_t e;
940 	size_t env_end = 0;
941 
942 	for (e = 0;;)
943 	{
944 		for (; e < env_buf_len; e++)
945 			if (env_buf[e] == '\0')
946 				break;
947 		if (e >= env_buf_len) break;
948 		if (env_buf[++e] & A_EXTRA)
949 		{
950 			for (e = e+1; e < env_buf_len; e++)
951 				if (env_buf[e] == '\0')
952 					break;
953 		}
954 		e++;
955 		if (e >= env_buf_len) break;
956 		env_end = e;
957 	}
958 	/* Temporarily add env_buf to var_tables, do the lookup, then remove it. */
959 	add_uvar_table(env_buf, env_end);
960 	r = lgetenv(var);
961 	pop_cmd_table(&list_var_tables);
962 	return r;
963 }
964 
965 /*
966  * Is a string null or empty?
967  */
968 public lbool isnullenv(constant char *s)
969 {
970 	return (s == NULL || *s == '\0');
971 }
972 
973 #if USERFILE
974 /*
975  * Get an "integer" from a lesskey file.
976  * Integers are stored in a funny format:
977  * two bytes, low order first, in radix KRADIX.
978  */
979 static size_t gint(unsigned char **sp)
980 {
981 	size_t n;
982 
983 	n = *(*sp)++;
984 	n += *(*sp)++ * KRADIX;
985 	return (n);
986 }
987 
988 /*
989  * Process an old (pre-v241) lesskey file.
990  */
991 static int old_lesskey(unsigned char *buf, size_t len)
992 {
993 	/*
994 	 * Old-style lesskey file.
995 	 * The file must end with either
996 	 *     ...,cmd,0,action
997 	 * or  ...,cmd,0,action|A_EXTRA,string,0
998 	 * So the last byte or the second to last byte must be zero.
999 	 */
1000 	if (buf[len-1] != '\0' && buf[len-2] != '\0')
1001 		return (-1);
1002 	add_fcmd_table(buf, len);
1003 	return (0);
1004 }
1005 
1006 /*
1007  * Process a new (post-v241) lesskey file.
1008  */
1009 static int new_lesskey(unsigned char *buf, size_t len, lbool sysvar)
1010 {
1011 	unsigned char *p;
1012 	unsigned char *end;
1013 	int c;
1014 	size_t n;
1015 
1016 	/*
1017 	 * New-style lesskey file.
1018 	 * Extract the pieces.
1019 	 */
1020 	if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
1021 	    buf[len-2] != C1_END_LESSKEY_MAGIC ||
1022 	    buf[len-1] != C2_END_LESSKEY_MAGIC)
1023 		return (-1);
1024 	p = buf + 4;
1025 	end = buf + len;
1026 	for (;;)
1027 	{
1028 		c = *p++;
1029 		switch (c)
1030 		{
1031 		case CMD_SECTION:
1032 			n = gint(&p);
1033 			if (p+n >= end)
1034 				return (-1);
1035 			add_fcmd_table(p, n);
1036 			p += n;
1037 			break;
1038 		case EDIT_SECTION:
1039 			n = gint(&p);
1040 			if (p+n >= end)
1041 				return (-1);
1042 			add_ecmd_table(p, n);
1043 			p += n;
1044 			break;
1045 		case VAR_SECTION:
1046 			n = gint(&p);
1047 			if (p+n >= end)
1048 				return (-1);
1049 			if (sysvar)
1050 				add_sysvar_table(p, n);
1051 			else
1052 				add_uvar_table(p, n);
1053 			p += n;
1054 			break;
1055 		case END_SECTION:
1056 			return (0);
1057 		default:
1058 			/*
1059 			 * Unrecognized section type.
1060 			 */
1061 			return (-1);
1062 		}
1063 	}
1064 }
1065 
1066 /*
1067  * Set up a user command table, based on a "lesskey" file.
1068  */
1069 public int lesskey(constant char *filename, lbool sysvar)
1070 {
1071 	unsigned char *buf;
1072 	POSITION len;
1073 	ssize_t n;
1074 	int f;
1075 
1076 	if (!secure_allow(SF_LESSKEY) || !isnullenv(no_config))
1077 		return (1);
1078 	/*
1079 	 * Try to open the lesskey file.
1080 	 */
1081 	f = open(filename, OPEN_READ);
1082 	if (f < 0)
1083 		return (1);
1084 
1085 	/*
1086 	 * Read the file into a buffer.
1087 	 * We first figure out the size of the file and allocate space for it.
1088 	 * {{ Minimal error checking is done here.
1089 	 *    A garbage .less file will produce strange results.
1090 	 *    To avoid a large amount of error checking code here, we
1091 	 *    rely on the lesskey program to generate a good .less file. }}
1092 	 */
1093 	len = filesize(f);
1094 	if (len == NULL_POSITION || len < 3)
1095 	{
1096 		/*
1097 		 * Bad file (valid file must have at least 3 chars).
1098 		 */
1099 		close(f);
1100 		return (-1);
1101 	}
1102 	if ((buf = (unsigned char *) calloc((size_t)len, sizeof(char))) == NULL)
1103 	{
1104 		close(f);
1105 		return (-1);
1106 	}
1107 	if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
1108 	{
1109 		free(buf);
1110 		close(f);
1111 		return (-1);
1112 	}
1113 	n = read(f, buf, (size_t) len);
1114 	close(f);
1115 	if (n != len)
1116 	{
1117 		free(buf);
1118 		return (-1);
1119 	}
1120 
1121 	/*
1122 	 * Figure out if this is an old-style (before version 241)
1123 	 * or new-style lesskey file format.
1124 	 */
1125 	if (len < 4 ||
1126 	    buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
1127 	    buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
1128 		return (old_lesskey(buf, (size_t) len));
1129 	return (new_lesskey(buf, (size_t) len, sysvar));
1130 }
1131 
1132 #if HAVE_LESSKEYSRC
1133 static int lesskey_text(constant char *filename, lbool sysvar, lbool content)
1134 {
1135 	int r;
1136 	static struct lesskey_tables tables;
1137 
1138 	if (!secure_allow(SF_LESSKEY) || !isnullenv(no_config))
1139 		return (1);
1140 	r = content ? parse_lesskey_content(filename, &tables) : parse_lesskey(filename, &tables);
1141 	if (r != 0)
1142 		return (r);
1143 	add_fcmd_table(tables.cmdtable.buf.data, tables.cmdtable.buf.end);
1144 	add_ecmd_table(tables.edittable.buf.data, tables.edittable.buf.end);
1145 	if (sysvar)
1146 		add_sysvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1147 	else
1148 		add_uvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1149 	return (0);
1150 }
1151 
1152 public int lesskey_src(constant char *filename, lbool sysvar)
1153 {
1154 	return lesskey_text(filename, sysvar, FALSE);
1155 }
1156 
1157 public int lesskey_content(constant char *content, lbool sysvar)
1158 {
1159 	return lesskey_text(content, sysvar, TRUE);
1160 }
1161 
1162 void lesskey_parse_error(char *s)
1163 {
1164 	PARG parg;
1165 	parg.p_string = s;
1166 	error("%s", &parg);
1167 }
1168 #endif /* HAVE_LESSKEYSRC */
1169 
1170 /*
1171  * Add a lesskey file.
1172  */
1173 static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar)
1174 {
1175 	char *filename = NULL;
1176 	constant char *efilename;
1177 	int r;
1178 
1179 	if (envname != NULL && (efilename = lgetenv(envname)) != NULL)
1180 		filename = save(efilename);
1181 	else if (sysvar) /* def_filename is full path */
1182 		filename = save(def_filename);
1183 	else /* def_filename is just basename */
1184 	{
1185 		/* Remove first char (normally a dot) unless stored in $HOME. */
1186 		constant char *xdg = lgetenv("XDG_CONFIG_HOME");
1187 		if (!isnullenv(xdg))
1188 			filename = dirfile(xdg, &def_filename[1], 1);
1189 		if (filename == NULL)
1190 		{
1191 			constant char *home = lgetenv("HOME");
1192 			if (!isnullenv(home))
1193 			{
1194 				char *cfg_dir = dirfile(home, ".config", 0);
1195 				filename = dirfile(cfg_dir, &def_filename[1], 1);
1196 				free(cfg_dir);
1197 			}
1198 		}
1199 		if (filename == NULL)
1200 			filename = homefile(def_filename);
1201 	}
1202 	if (filename == NULL)
1203 		return -1;
1204 	r = (*call_lesskey)(filename, sysvar);
1205 	free(filename);
1206 	return (r);
1207 }
1208 
1209 /*
1210  * Add the content of a lesskey source file.
1211  */
1212 static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar)
1213 {
1214 	constant char *content;
1215 
1216 	(void) call_lesskey; /* not used */
1217 	content = lgetenv(envname);
1218 	if (isnullenv(content))
1219 		return;
1220 	lesskey_content(content, sysvar);
1221 }
1222 #endif /* USERFILE */
1223 
1224 /*
1225  * See if a char is a special line-editing command.
1226  */
1227 public int editchar(char c, int flags)
1228 {
1229 	int action;
1230 	int nch;
1231 	constant char *s;
1232 	char usercmd[MAX_CMDLEN+1];
1233 
1234 	/*
1235 	 * An editing character could actually be a sequence of characters;
1236 	 * for example, an escape sequence sent by pressing the uparrow key.
1237 	 * To match the editing string, we use the command decoder
1238 	 * but give it the edit-commands command table
1239 	 * This table is constructed to match the user's keyboard.
1240 	 */
1241 	if (c == erase_char || c == erase2_char)
1242 		return (EC_BACKSPACE);
1243 	if (c == kill_char)
1244 	{
1245 #if MSDOS_COMPILER==WIN32C
1246 		if (!win32_kbhit())
1247 #endif
1248 		return (EC_LINEKILL);
1249 	}
1250 
1251 	/*
1252 	 * Collect characters in a buffer.
1253 	 * Start with the one we have, and get more if we need them.
1254 	 */
1255 	nch = 0;
1256 	do {
1257 		if (nch > 0)
1258 			c = getcc();
1259 		usercmd[nch] = c;
1260 		usercmd[nch+1] = '\0';
1261 		nch++;
1262 		action = ecmd_decode(usercmd, &s);
1263 	} while (action == A_PREFIX && nch < MAX_CMDLEN);
1264 
1265 	if (action == EC_X11MOUSE)
1266 		return (x11mouse_action(TRUE));
1267 	if (action == EC_X116MOUSE)
1268 		return (x116mouse_action(TRUE));
1269 
1270 	if (flags & ECF_NORIGHTLEFT)
1271 	{
1272 		switch (action)
1273 		{
1274 		case EC_RIGHT:
1275 		case EC_LEFT:
1276 			action = A_INVALID;
1277 			break;
1278 		}
1279 	}
1280 #if CMD_HISTORY
1281 	if (flags & ECF_NOHISTORY)
1282 	{
1283 		/*
1284 		 * The caller says there is no history list.
1285 		 * Reject any history-manipulation action.
1286 		 */
1287 		switch (action)
1288 		{
1289 		case EC_UP:
1290 		case EC_DOWN:
1291 			action = A_INVALID;
1292 			break;
1293 		}
1294 	}
1295 #endif
1296 	if (flags & ECF_NOCOMPLETE)
1297 	{
1298 		/*
1299 		 * The caller says we don't want any filename completion cmds.
1300 		 * Reject them.
1301 		 */
1302 		switch (action)
1303 		{
1304 		case EC_F_COMPLETE:
1305 		case EC_B_COMPLETE:
1306 		case EC_EXPAND:
1307 			action = A_INVALID;
1308 			break;
1309 		}
1310 	}
1311 	if ((flags & ECF_PEEK) || action == A_INVALID)
1312 	{
1313 		/*
1314 		 * We're just peeking, or we didn't understand the command.
1315 		 * Unget all the characters we read in the loop above.
1316 		 * This does NOT include the original character that was
1317 		 * passed in as a parameter.
1318 		 */
1319 		while (nch > 1)
1320 		{
1321 			ungetcc(usercmd[--nch]);
1322 		}
1323 	} else
1324 	{
1325 		if (s != NULL)
1326 			ungetsc(s);
1327 	}
1328 	return action;
1329 }
1330 
1331