Home | History | Annotate | Line # | Download | only in dist
      1 /*	$NetBSD: decode.c,v 1.7 2023/10/06 07:13:13 simonb Exp $	*/
      2 
      3 /*
      4  * Copyright (C) 1984-2023  Mark Nudelman
      5  *
      6  * You may distribute under the terms of either the GNU General Public
      7  * License or the Less License, as specified in the README file.
      8  *
      9  * For more information, see the README file.
     10  */
     11 
     12 
     13 /*
     14  * Routines to decode user commands.
     15  *
     16  * This is all table driven.
     17  * A command table is a sequence of command descriptors.
     18  * Each command descriptor is a sequence of bytes with the following format:
     19  *     <c1><c2>...<cN><0><action>
     20  * The characters c1,c2,...,cN are the command string; that is,
     21  * the characters which the user must type.
     22  * It is terminated by a null <0> byte.
     23  * The byte after the null byte is the action code associated
     24  * with the command string.
     25  * If an action byte is OR-ed with A_EXTRA, this indicates
     26  * that the option byte is followed by an extra string.
     27  *
     28  * There may be many command tables.
     29  * The first (default) table is built-in.
     30  * Other tables are read in from "lesskey" files.
     31  * All the tables are linked together and are searched in order.
     32  */
     33 
     34 #include "less.h"
     35 #include "cmd.h"
     36 #include "lesskey.h"
     37 
     38 extern int erase_char, erase2_char, kill_char;
     39 extern int secure;
     40 extern int mousecap;
     41 extern int screen_trashed;
     42 extern int sc_height;
     43 
     44 #define SK(k) \
     45 	SK_SPECIAL_KEY, (k), 6, 1, 1, 1
     46 /*
     47  * Command table is ordered roughly according to expected
     48  * frequency of use, so the common commands are near the beginning.
     49  */
     50 
     51 static unsigned char cmdtable[] =
     52 {
     53 	'\r',0,                         A_F_LINE,
     54 	'\n',0,                         A_F_LINE,
     55 	'e',0,                          A_F_LINE,
     56 	'j',0,                          A_F_LINE,
     57 	SK(SK_DOWN_ARROW),0,            A_F_LINE,
     58 	CONTROL('E'),0,                 A_F_LINE,
     59 	CONTROL('N'),0,                 A_F_LINE,
     60 	'k',0,                          A_B_LINE,
     61 	'y',0,                          A_B_LINE,
     62 	CONTROL('Y'),0,                 A_B_LINE,
     63 	SK(SK_CONTROL_K),0,             A_B_LINE,
     64 	CONTROL('P'),0,                 A_B_LINE,
     65 	SK(SK_UP_ARROW),0,              A_B_LINE,
     66 	'J',0,                          A_FF_LINE,
     67 	'K',0,                          A_BF_LINE,
     68 	'Y',0,                          A_BF_LINE,
     69 	'd',0,                          A_F_SCROLL,
     70 	CONTROL('D'),0,                 A_F_SCROLL,
     71 	'u',0,                          A_B_SCROLL,
     72 	CONTROL('U'),0,                 A_B_SCROLL,
     73 	ESC,'[','M',0,                  A_X11MOUSE_IN,
     74 	ESC,'[','<',0,                  A_X116MOUSE_IN,
     75 	' ',0,                          A_F_SCREEN,
     76 	'f',0,                          A_F_SCREEN,
     77 	CONTROL('F'),0,                 A_F_SCREEN,
     78 	CONTROL('V'),0,                 A_F_SCREEN,
     79 	SK(SK_PAGE_DOWN),0,             A_F_SCREEN,
     80 	'b',0,                          A_B_SCREEN,
     81 	CONTROL('B'),0,                 A_B_SCREEN,
     82 	ESC,'v',0,                      A_B_SCREEN,
     83 	SK(SK_PAGE_UP),0,               A_B_SCREEN,
     84 	'z',0,                          A_F_WINDOW,
     85 	'w',0,                          A_B_WINDOW,
     86 	ESC,' ',0,                      A_FF_SCREEN,
     87 	'F',0,                          A_F_FOREVER,
     88 	ESC,'F',0,                      A_F_UNTIL_HILITE,
     89 	'R',0,                          A_FREPAINT,
     90 	'r',0,                          A_REPAINT,
     91 	CONTROL('R'),0,                 A_REPAINT,
     92 	CONTROL('L'),0,                 A_REPAINT,
     93 	ESC,'u',0,                      A_UNDO_SEARCH,
     94 	ESC,'U',0,                      A_CLR_SEARCH,
     95 	'g',0,                          A_GOLINE,
     96 	SK(SK_HOME),0,                  A_GOLINE,
     97 	'<',0,                          A_GOLINE,
     98 	ESC,'<',0,                      A_GOLINE,
     99 	'p',0,                          A_PERCENT,
    100 	'%',0,                          A_PERCENT,
    101 	ESC,'[',0,                      A_LSHIFT,
    102 	ESC,']',0,                      A_RSHIFT,
    103 	ESC,'(',0,                      A_LSHIFT,
    104 	ESC,')',0,                      A_RSHIFT,
    105 	ESC,'{',0,                      A_LLSHIFT,
    106 	ESC,'}',0,                      A_RRSHIFT,
    107 	SK(SK_RIGHT_ARROW),0,           A_RSHIFT,
    108 	SK(SK_LEFT_ARROW),0,            A_LSHIFT,
    109 	SK(SK_CTL_RIGHT_ARROW),0,       A_RRSHIFT,
    110 	SK(SK_CTL_LEFT_ARROW),0,        A_LLSHIFT,
    111 	'{',0,                          A_F_BRACKET|A_EXTRA,        '{','}',0,
    112 	'}',0,                          A_B_BRACKET|A_EXTRA,        '{','}',0,
    113 	'(',0,                          A_F_BRACKET|A_EXTRA,        '(',')',0,
    114 	')',0,                          A_B_BRACKET|A_EXTRA,        '(',')',0,
    115 	'[',0,                          A_F_BRACKET|A_EXTRA,        '[',']',0,
    116 	']',0,                          A_B_BRACKET|A_EXTRA,        '[',']',0,
    117 	ESC,CONTROL('F'),0,             A_F_BRACKET,
    118 	ESC,CONTROL('B'),0,             A_B_BRACKET,
    119 	'G',0,                          A_GOEND,
    120 	ESC,'G',0,                      A_GOEND_BUF,
    121 	ESC,'>',0,                      A_GOEND,
    122 	'>',0,                          A_GOEND,
    123 	SK(SK_END),0,                   A_GOEND,
    124 	'P',0,                          A_GOPOS,
    125 
    126 	'0',0,                          A_DIGIT,
    127 	'1',0,                          A_DIGIT,
    128 	'2',0,                          A_DIGIT,
    129 	'3',0,                          A_DIGIT,
    130 	'4',0,                          A_DIGIT,
    131 	'5',0,                          A_DIGIT,
    132 	'6',0,                          A_DIGIT,
    133 	'7',0,                          A_DIGIT,
    134 	'8',0,                          A_DIGIT,
    135 	'9',0,                          A_DIGIT,
    136 	'.',0,                          A_DIGIT,
    137 
    138 	'=',0,                          A_STAT,
    139 	CONTROL('G'),0,                 A_STAT,
    140 	':','f',0,                      A_STAT,
    141 	'/',0,                          A_F_SEARCH,
    142 	'?',0,                          A_B_SEARCH,
    143 	ESC,'/',0,                      A_F_SEARCH|A_EXTRA,        '*',0,
    144 	ESC,'?',0,                      A_B_SEARCH|A_EXTRA,        '*',0,
    145 	'n',0,                          A_AGAIN_SEARCH,
    146 	ESC,'n',0,                      A_T_AGAIN_SEARCH,
    147 	'N',0,                          A_REVERSE_SEARCH,
    148 	ESC,'N',0,                      A_T_REVERSE_SEARCH,
    149 	'&',0,                          A_FILTER,
    150 	'm',0,                          A_SETMARK,
    151 	'M',0,                          A_SETMARKBOT,
    152 	ESC,'m',0,                      A_CLRMARK,
    153 	'\'',0,                         A_GOMARK,
    154 	CONTROL('X'),CONTROL('X'),0,    A_GOMARK,
    155 	'E',0,                          A_EXAMINE,
    156 	':','e',0,                      A_EXAMINE,
    157 	CONTROL('X'),CONTROL('V'),0,    A_EXAMINE,
    158 	':','n',0,                      A_NEXT_FILE,
    159 	':','p',0,                      A_PREV_FILE,
    160 	't',0,                          A_NEXT_TAG,
    161 	'T',0,                          A_PREV_TAG,
    162 	':','x',0,                      A_INDEX_FILE,
    163 	':','d',0,                      A_REMOVE_FILE,
    164 	'-',0,                          A_OPT_TOGGLE,
    165 	':','t',0,                      A_OPT_TOGGLE|A_EXTRA,        't',0,
    166 	's',0,                          A_OPT_TOGGLE|A_EXTRA,        'o',0,
    167 	'_',0,                          A_DISP_OPTION,
    168 	'|',0,                          A_PIPE,
    169 	'v',0,                          A_VISUAL,
    170 	'!',0,                          A_SHELL,
    171 	'#',0,                          A_PSHELL,
    172 	'+',0,                          A_FIRSTCMD,
    173 
    174 	'H',0,                          A_HELP,
    175 	'h',0,                          A_HELP,
    176 	SK(SK_F1),0,                    A_HELP,
    177 	'V',0,                          A_VERSION,
    178 	'q',0,                          A_QUIT,
    179 	'Q',0,                          A_QUIT,
    180 	':','q',0,                      A_QUIT,
    181 	':','Q',0,                      A_QUIT,
    182 	'Z','Z',0,                      A_QUIT
    183 };
    184 
    185 static unsigned char edittable[] =
    186 {
    187 	'\t',0,                         EC_F_COMPLETE,  /* TAB */
    188 	'\17',0,                        EC_B_COMPLETE,  /* BACKTAB */
    189 	SK(SK_BACKTAB),0,               EC_B_COMPLETE,  /* BACKTAB */
    190 	ESC,'\t',0,                     EC_B_COMPLETE,  /* ESC TAB */
    191 	CONTROL('L'),0,                 EC_EXPAND,      /* CTRL-L */
    192 	CONTROL('V'),0,                 EC_LITERAL,     /* BACKSLASH */
    193 	CONTROL('A'),0,                 EC_LITERAL,     /* BACKSLASH */
    194 	ESC,'l',0,                      EC_RIGHT,       /* ESC l */
    195 	SK(SK_RIGHT_ARROW),0,           EC_RIGHT,       /* RIGHTARROW */
    196 	ESC,'h',0,                      EC_LEFT,        /* ESC h */
    197 	SK(SK_LEFT_ARROW),0,            EC_LEFT,        /* LEFTARROW */
    198 	ESC,'b',0,                      EC_W_LEFT,      /* ESC b */
    199 	ESC,SK(SK_LEFT_ARROW),0,        EC_W_LEFT,      /* ESC LEFTARROW */
    200 	SK(SK_CTL_LEFT_ARROW),0,        EC_W_LEFT,      /* CTRL-LEFTARROW */
    201 	ESC,'w',0,                      EC_W_RIGHT,     /* ESC w */
    202 	ESC,SK(SK_RIGHT_ARROW),0,       EC_W_RIGHT,     /* ESC RIGHTARROW */
    203 	SK(SK_CTL_RIGHT_ARROW),0,       EC_W_RIGHT,     /* CTRL-RIGHTARROW */
    204 	ESC,'i',0,                      EC_INSERT,      /* ESC i */
    205 	SK(SK_INSERT),0,                EC_INSERT,      /* INSERT */
    206 	ESC,'x',0,                      EC_DELETE,      /* ESC x */
    207 	SK(SK_DELETE),0,                EC_DELETE,      /* DELETE */
    208 	ESC,'X',0,                      EC_W_DELETE,    /* ESC X */
    209 	ESC,SK(SK_DELETE),0,            EC_W_DELETE,    /* ESC DELETE */
    210 	SK(SK_CTL_DELETE),0,            EC_W_DELETE,    /* CTRL-DELETE */
    211 	SK(SK_CTL_BACKSPACE),0,         EC_W_BACKSPACE, /* CTRL-BACKSPACE */
    212 	ESC,SK(SK_BACKSPACE),0,         EC_W_BACKSPACE, /* ESC BACKSPACE */
    213 	ESC,'0',0,                      EC_HOME,        /* ESC 0 */
    214 	SK(SK_HOME),0,                  EC_HOME,        /* HOME */
    215 	ESC,'$',0,                      EC_END,         /* ESC $ */
    216 	SK(SK_END),0,                   EC_END,         /* END */
    217 	ESC,'k',0,                      EC_UP,          /* ESC k */
    218 	SK(SK_UP_ARROW),0,              EC_UP,          /* UPARROW */
    219 	ESC,'j',0,                      EC_DOWN,        /* ESC j */
    220 	SK(SK_DOWN_ARROW),0,            EC_DOWN,        /* DOWNARROW */
    221 	CONTROL('G'),0,                 EC_ABORT,       /* CTRL-G */
    222 	ESC,'[','M',0,                  EC_X11MOUSE,    /* X11 mouse report */
    223 	ESC,'[','<',0,                  EC_X116MOUSE,   /* X11 1006 mouse report */
    224 };
    225 
    226 /*
    227  * Structure to support a list of command tables.
    228  */
    229 struct tablelist
    230 {
    231 	struct tablelist *t_next;
    232 	char *t_start;
    233 	char *t_end;
    234 };
    235 
    236 /*
    237  * List of command tables and list of line-edit tables.
    238  */
    239 static struct tablelist *list_fcmd_tables = NULL;
    240 static struct tablelist *list_ecmd_tables = NULL;
    241 static struct tablelist *list_var_tables = NULL;
    242 static struct tablelist *list_sysvar_tables = NULL;
    243 
    244 
    245 /*
    246  * Expand special key abbreviations in a command table.
    247  */
    248 static void expand_special_keys(char *table, int len)
    249 {
    250 	char *fm;
    251 	char *to;
    252 	int a;
    253 	char *repl;
    254 	int klen;
    255 
    256 	for (fm = table;  fm < table + len; )
    257 	{
    258 		/*
    259 		 * Rewrite each command in the table with any
    260 		 * special key abbreviations expanded.
    261 		 */
    262 		for (to = fm;  *fm != '\0'; )
    263 		{
    264 			if (*fm != SK_SPECIAL_KEY)
    265 			{
    266 				*to++ = *fm++;
    267 				continue;
    268 			}
    269 			/*
    270 			 * After SK_SPECIAL_KEY, next byte is the type
    271 			 * of special key (one of the SK_* constants),
    272 			 * and the byte after that is the number of bytes,
    273 			 * N, reserved by the abbreviation (including the
    274 			 * SK_SPECIAL_KEY and key type bytes).
    275 			 * Replace all N bytes with the actual bytes
    276 			 * output by the special key on this terminal.
    277 			 */
    278 			repl = special_key_str(fm[1]);
    279 			klen = fm[2] & 0377;
    280 			fm += klen;
    281 			if (repl == NULL || (int) strlen(repl) > klen)
    282 				repl = "\377";
    283 			while (*repl != '\0')
    284 				*to++ = *repl++;
    285 		}
    286 		*to++ = '\0';
    287 		/*
    288 		 * Fill any unused bytes between end of command and
    289 		 * the action byte with A_SKIP.
    290 		 */
    291 		while (to <= fm)
    292 			*to++ = A_SKIP;
    293 		fm++;
    294 		a = *fm++ & 0377;
    295 		if (a & A_EXTRA)
    296 		{
    297 			while (*fm++ != '\0')
    298 				continue;
    299 		}
    300 	}
    301 }
    302 
    303 /*
    304  * Expand special key abbreviations in a list of command tables.
    305  */
    306 static void expand_cmd_table(struct tablelist *tlist)
    307 {
    308 	struct tablelist *t;
    309 	for (t = tlist;  t != NULL;  t = t->t_next)
    310 	{
    311 		expand_special_keys(t->t_start, t->t_end - t->t_start);
    312 	}
    313 }
    314 
    315 /*
    316  * Expand special key abbreviations in all command tables.
    317  */
    318 public void expand_cmd_tables(void)
    319 {
    320 	expand_cmd_table(list_fcmd_tables);
    321 	expand_cmd_table(list_ecmd_tables);
    322 	expand_cmd_table(list_var_tables);
    323 	expand_cmd_table(list_sysvar_tables);
    324 }
    325 
    326 
    327 /*
    328  * Initialize the command lists.
    329  */
    330 public void init_cmds(void)
    331 {
    332 	/*
    333 	 * Add the default command tables.
    334 	 */
    335 	add_fcmd_table((char*)cmdtable, sizeof(cmdtable));
    336 	add_ecmd_table((char*)edittable, sizeof(edittable));
    337 #if USERFILE
    338 #ifdef BINDIR /* For backwards compatibility */
    339 	/* Try to add tables in the OLD system lesskey file. */
    340 	add_hometable(lesskey, NULL, BINDIR "/.sysless", 1);
    341 #endif
    342 	/*
    343 	 * Try to load lesskey source file or binary file.
    344 	 * If the source file succeeds, don't load binary file.
    345 	 * The binary file is likely to have been generated from
    346 	 * a (possibly out of date) copy of the src file,
    347 	 * so loading it is at best redundant.
    348 	 */
    349 	/*
    350 	 * Try to add tables in system lesskey src file.
    351 	 */
    352 #if HAVE_LESSKEYSRC
    353 	if (add_hometable(lesskey_src, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS, 1) != 0)
    354 #endif
    355 	{
    356 		/*
    357 		 * Try to add the tables in the system lesskey binary file.
    358 		 */
    359 		add_hometable(lesskey, "LESSKEY_SYSTEM", LESSKEYFILE_SYS, 1);
    360 	}
    361 	/*
    362 	 * Try to add tables in the lesskey src file "$HOME/.lesskey".
    363 	 */
    364 #if HAVE_LESSKEYSRC
    365 	if (add_hometable(lesskey_src, "LESSKEYIN", DEF_LESSKEYINFILE, 0) != 0)
    366 #endif
    367 	{
    368 		/*
    369 		 * Try to add the tables in the standard lesskey binary file "$HOME/.less".
    370 		 */
    371 		add_hometable(lesskey, "LESSKEY", LESSKEYFILE, 0);
    372 	}
    373 #endif
    374 }
    375 
    376 /*
    377  * Add a command table.
    378  */
    379 static int add_cmd_table(struct tablelist **tlist, char *buf, int len)
    380 {
    381 	struct tablelist *t;
    382 
    383 	if (len == 0)
    384 		return (0);
    385 	/*
    386 	 * Allocate a tablelist structure, initialize it,
    387 	 * and link it into the list of tables.
    388 	 */
    389 	if ((t = (struct tablelist *)
    390 			calloc(1, sizeof(struct tablelist))) == NULL)
    391 	{
    392 		return (-1);
    393 	}
    394 	t->t_start = buf;
    395 	t->t_end = buf + len;
    396 	t->t_next = *tlist;
    397 	*tlist = t;
    398 	return (0);
    399 }
    400 
    401 /*
    402  * Add a command table.
    403  */
    404 public void add_fcmd_table(char *buf, int len)
    405 {
    406 	if (add_cmd_table(&list_fcmd_tables, buf, len) < 0)
    407 		error("Warning: some commands disabled", NULL_PARG);
    408 }
    409 
    410 /*
    411  * Add an editing command table.
    412  */
    413 public void add_ecmd_table(char *buf, int len)
    414 {
    415 	if (add_cmd_table(&list_ecmd_tables, buf, len) < 0)
    416 		error("Warning: some edit commands disabled", NULL_PARG);
    417 }
    418 
    419 /*
    420  * Add an environment variable table.
    421  */
    422 static void add_var_table(struct tablelist **tlist, char *buf, int len)
    423 {
    424 	if (add_cmd_table(tlist, buf, len) < 0)
    425 		error("Warning: environment variables from lesskey file unavailable", NULL_PARG);
    426 }
    427 
    428 /*
    429  * Return action for a mouse wheel down event.
    430  */
    431 static int mouse_wheel_down(void)
    432 {
    433 	return ((mousecap == OPT_ONPLUS) ? A_B_MOUSE : A_F_MOUSE);
    434 }
    435 
    436 /*
    437  * Return action for a mouse wheel up event.
    438  */
    439 static int mouse_wheel_up(void)
    440 {
    441 	return ((mousecap == OPT_ONPLUS) ? A_F_MOUSE : A_B_MOUSE);
    442 }
    443 
    444 /*
    445  * Return action for a mouse button release event.
    446  */
    447 static int mouse_button_rel(int x, int y)
    448 {
    449 	/*
    450 	 * {{ It would be better to return an action and then do this
    451 	 *    in commands() but it's nontrivial to pass y to it. }}
    452 	 */
    453 	if (y < sc_height-1)
    454 	{
    455 		setmark('#', y);
    456 		screen_trashed = 1;
    457 	}
    458 	return (A_NOACTION);
    459 }
    460 
    461 /*
    462  * Read a decimal integer. Return the integer and set *pterm to the terminating char.
    463  */
    464 static int getcc_int(char *pterm)
    465 {
    466 	int num = 0;
    467 	int digits = 0;
    468 	for (;;)
    469 	{
    470 		char ch = getcc();
    471 		if (ch < '0' || ch > '9')
    472 		{
    473 			if (pterm != NULL) *pterm = ch;
    474 			if (digits == 0)
    475 				return (-1);
    476 			return (num);
    477 		}
    478 		if (ckd_mul(&num, num, 10) || ckd_add(&num, num, ch - '0'))
    479 			return -1;
    480 		++digits;
    481 	}
    482 }
    483 
    484 /*
    485  * Read suffix of mouse input and return the action to take.
    486  * The prefix ("\e[M") has already been read.
    487  */
    488 static int x11mouse_action(int skip)
    489 {
    490 	int b = getcc() - X11MOUSE_OFFSET;
    491 	int x = getcc() - X11MOUSE_OFFSET-1;
    492 	int y = getcc() - X11MOUSE_OFFSET-1;
    493 	if (skip)
    494 		return (A_NOACTION);
    495 	switch (b) {
    496 	default:
    497 		return (A_NOACTION);
    498 	case X11MOUSE_WHEEL_DOWN:
    499 		return mouse_wheel_down();
    500 	case X11MOUSE_WHEEL_UP:
    501 		return mouse_wheel_up();
    502 	case X11MOUSE_BUTTON_REL:
    503 		return mouse_button_rel(x, y);
    504 	}
    505 }
    506 
    507 /*
    508  * Read suffix of mouse input and return the action to take.
    509  * The prefix ("\e[<") has already been read.
    510  */
    511 static int x116mouse_action(int skip)
    512 {
    513 	char ch;
    514 	int x, y;
    515 	int b = getcc_int(&ch);
    516 	if (b < 0 || ch != ';') return (A_NOACTION);
    517 	x = getcc_int(&ch) - 1;
    518 	if (x < 0 || ch != ';') return (A_NOACTION);
    519 	y = getcc_int(&ch) - 1;
    520 	if (y < 0) return (A_NOACTION);
    521 	if (skip)
    522 		return (A_NOACTION);
    523 	switch (b) {
    524 	case X11MOUSE_WHEEL_DOWN:
    525 		return mouse_wheel_down();
    526 	case X11MOUSE_WHEEL_UP:
    527 		return mouse_wheel_up();
    528 	default:
    529 		if (ch != 'm') return (A_NOACTION);
    530 		return mouse_button_rel(x, y);
    531 	}
    532 }
    533 
    534 /*
    535  * Search a single command table for the command string in cmd.
    536  */
    537 static int cmd_search(char *cmd, char *table, char *endtable, char **sp)
    538 {
    539 	char *p;
    540 	char *q;
    541 	int a;
    542 
    543 	*sp = NULL;
    544 	for (p = table, q = cmd;  p < endtable;  p++, q++)
    545 	{
    546 		if (*p == *q)
    547 		{
    548 			/*
    549 			 * Current characters match.
    550 			 * If we're at the end of the string, we've found it.
    551 			 * Return the action code, which is the character
    552 			 * after the null at the end of the string
    553 			 * in the command table.
    554 			 */
    555 			if (*p == '\0')
    556 			{
    557 				a = *++p & 0377;
    558 				while (a == A_SKIP)
    559 					a = *++p & 0377;
    560 				if (a == A_END_LIST)
    561 				{
    562 					/*
    563 					 * We get here only if the original
    564 					 * cmd string passed in was empty ("").
    565 					 * I don't think that can happen,
    566 					 * but just in case ...
    567 					 */
    568 					return (A_UINVALID);
    569 				}
    570 				/*
    571 				 * Check for an "extra" string.
    572 				 */
    573 				if (a & A_EXTRA)
    574 				{
    575 					*sp = ++p;
    576 					a &= ~A_EXTRA;
    577 				}
    578 				if (a == A_X11MOUSE_IN)
    579 					a = x11mouse_action(0);
    580 				else if (a == A_X116MOUSE_IN)
    581 					a = x116mouse_action(0);
    582 				return (a);
    583 			}
    584 		} else if (*q == '\0')
    585 		{
    586 			/*
    587 			 * Hit the end of the user's command,
    588 			 * but not the end of the string in the command table.
    589 			 * The user's command is incomplete.
    590 			 */
    591 			return (A_PREFIX);
    592 		} else
    593 		{
    594 			/*
    595 			 * Not a match.
    596 			 * Skip ahead to the next command in the
    597 			 * command table, and reset the pointer
    598 			 * to the beginning of the user's command.
    599 			 */
    600 			if (*p == '\0' && p[1] == A_END_LIST)
    601 			{
    602 				/*
    603 				 * A_END_LIST is a special marker that tells
    604 				 * us to abort the cmd search.
    605 				 */
    606 				return (A_UINVALID);
    607 			}
    608 			while (*p++ != '\0')
    609 				continue;
    610 			while (*p == A_SKIP)
    611 				p++;
    612 			if (*p & A_EXTRA)
    613 				while (*++p != '\0')
    614 					continue;
    615 			q = cmd-1;
    616 		}
    617 	}
    618 	/*
    619 	 * No match found in the entire command table.
    620 	 */
    621 	return (A_INVALID);
    622 }
    623 
    624 /*
    625  * Decode a command character and return the associated action.
    626  * The "extra" string, if any, is returned in sp.
    627  */
    628 static int cmd_decode(struct tablelist *tlist, char *cmd, char **sp)
    629 {
    630 	struct tablelist *t;
    631 	int action = A_INVALID;
    632 
    633 	/*
    634 	 * Search thru all the command tables.
    635 	 * Stop when we find an action which is not A_INVALID.
    636 	 */
    637 	for (t = tlist;  t != NULL;  t = t->t_next)
    638 	{
    639 		action = cmd_search(cmd, t->t_start, t->t_end, sp);
    640 		if (action != A_INVALID)
    641 			break;
    642 	}
    643 	if (action == A_UINVALID)
    644 		action = A_INVALID;
    645 	return (action);
    646 }
    647 
    648 /*
    649  * Decode a command from the cmdtables list.
    650  */
    651 public int fcmd_decode(char *cmd, char **sp)
    652 {
    653 	return (cmd_decode(list_fcmd_tables, cmd, sp));
    654 }
    655 
    656 /*
    657  * Decode a command from the edittables list.
    658  */
    659 public int ecmd_decode(char *cmd, char **sp)
    660 {
    661 	return (cmd_decode(list_ecmd_tables, cmd, sp));
    662 }
    663 
    664 /*
    665  * Get the value of an environment variable.
    666  * Looks first in the lesskey file, then in the real environment.
    667  */
    668 public char * lgetenv(char *var)
    669 {
    670 	int a;
    671 	char *s;
    672 
    673 	a = cmd_decode(list_var_tables, var, &s);
    674 	if (a == EV_OK)
    675 		return (s);
    676 	s = getenv(var);
    677 	if (s != NULL && *s != '\0')
    678 		return (s);
    679 	a = cmd_decode(list_sysvar_tables, var, &s);
    680 	if (a == EV_OK)
    681 		return (s);
    682 	return (NULL);
    683 }
    684 
    685 /*
    686  * Is a string null or empty?
    687  */
    688 public int isnullenv(char *s)
    689 {
    690 	return (s == NULL || *s == '\0');
    691 }
    692 
    693 #if USERFILE
    694 /*
    695  * Get an "integer" from a lesskey file.
    696  * Integers are stored in a funny format:
    697  * two bytes, low order first, in radix KRADIX.
    698  */
    699 static int gint(char **sp)
    700 {
    701 	int n;
    702 
    703 	n = *(*sp)++;
    704 	n += *(*sp)++ * KRADIX;
    705 	return (n);
    706 }
    707 
    708 /*
    709  * Process an old (pre-v241) lesskey file.
    710  */
    711 static int old_lesskey(char *buf, int len)
    712 {
    713 	/*
    714 	 * Old-style lesskey file.
    715 	 * The file must end with either
    716 	 *     ...,cmd,0,action
    717 	 * or  ...,cmd,0,action|A_EXTRA,string,0
    718 	 * So the last byte or the second to last byte must be zero.
    719 	 */
    720 	if (buf[len-1] != '\0' && buf[len-2] != '\0')
    721 		return (-1);
    722 	add_fcmd_table(buf, len);
    723 	return (0);
    724 }
    725 
    726 /*
    727  * Process a new (post-v241) lesskey file.
    728  */
    729 static int new_lesskey(char *buf, int len, int sysvar)
    730 {
    731 	char *p;
    732 	char *end;
    733 	int c;
    734 	int n;
    735 
    736 	/*
    737 	 * New-style lesskey file.
    738 	 * Extract the pieces.
    739 	 */
    740 	if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
    741 	    buf[len-2] != C1_END_LESSKEY_MAGIC ||
    742 	    buf[len-1] != C2_END_LESSKEY_MAGIC)
    743 		return (-1);
    744 	p = buf + 4;
    745 	end = buf + len;
    746 	for (;;)
    747 	{
    748 		c = *p++;
    749 		switch (c)
    750 		{
    751 		case CMD_SECTION:
    752 			n = gint(&p);
    753 			if (n < 0 || p+n >= end)
    754 				return (-1);
    755 			add_fcmd_table(p, n);
    756 			p += n;
    757 			break;
    758 		case EDIT_SECTION:
    759 			n = gint(&p);
    760 			if (n < 0 || p+n >= end)
    761 				return (-1);
    762 			add_ecmd_table(p, n);
    763 			p += n;
    764 			break;
    765 		case VAR_SECTION:
    766 			n = gint(&p);
    767 			if (n < 0 || p+n >= end)
    768 				return (-1);
    769 			add_var_table((sysvar) ?
    770 				&list_sysvar_tables : &list_var_tables, p, n);
    771 			p += n;
    772 			break;
    773 		case END_SECTION:
    774 			return (0);
    775 		default:
    776 			/*
    777 			 * Unrecognized section type.
    778 			 */
    779 			return (-1);
    780 		}
    781 	}
    782 }
    783 
    784 /*
    785  * Set up a user command table, based on a "lesskey" file.
    786  */
    787 public int lesskey(char *filename, int sysvar)
    788 {
    789 	char *buf;
    790 	POSITION len;
    791 	long n;
    792 	int f;
    793 
    794 	if (secure)
    795 		return (1);
    796 	/*
    797 	 * Try to open the lesskey file.
    798 	 */
    799 	f = open(filename, OPEN_READ);
    800 	if (f < 0)
    801 		return (1);
    802 
    803 	/*
    804 	 * Read the file into a buffer.
    805 	 * We first figure out the size of the file and allocate space for it.
    806 	 * {{ Minimal error checking is done here.
    807 	 *    A garbage .less file will produce strange results.
    808 	 *    To avoid a large amount of error checking code here, we
    809 	 *    rely on the lesskey program to generate a good .less file. }}
    810 	 */
    811 	len = filesize(f);
    812 	if (len == NULL_POSITION || len < 3)
    813 	{
    814 		/*
    815 		 * Bad file (valid file must have at least 3 chars).
    816 		 */
    817 		close(f);
    818 		return (-1);
    819 	}
    820 	if ((buf = (char *) calloc((int)len, sizeof(char))) == NULL)
    821 	{
    822 		close(f);
    823 		return (-1);
    824 	}
    825 	if (lseek(f, (off_t)0, SEEK_SET) == BAD_LSEEK)
    826 	{
    827 		free(buf);
    828 		close(f);
    829 		return (-1);
    830 	}
    831 	n = read(f, buf, (unsigned int) len);
    832 	close(f);
    833 	if (n != len)
    834 	{
    835 		free(buf);
    836 		return (-1);
    837 	}
    838 
    839 	/*
    840 	 * Figure out if this is an old-style (before version 241)
    841 	 * or new-style lesskey file format.
    842 	 */
    843 	if (len < 4 ||
    844 	    buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
    845 	    buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
    846 		return (old_lesskey(buf, (int)len));
    847 	return (new_lesskey(buf, (int)len, sysvar));
    848 }
    849 
    850 #if HAVE_LESSKEYSRC
    851 public int lesskey_src(char *filename, int sysvar)
    852 {
    853 	static struct lesskey_tables tables;
    854 	int r = parse_lesskey(filename, &tables);
    855 	if (r != 0)
    856 		return (r);
    857 	add_fcmd_table(xbuf_char_data(&tables.cmdtable.buf), tables.cmdtable.buf.end);
    858 	add_ecmd_table(xbuf_char_data(&tables.edittable.buf), tables.edittable.buf.end);
    859 	add_var_table(sysvar ? &list_sysvar_tables : &list_var_tables,
    860 		xbuf_char_data(&tables.vartable.buf), tables.vartable.buf.end);
    861 	return (0);
    862 }
    863 
    864 void lesskey_parse_error(char *s)
    865 {
    866 	PARG parg;
    867 	parg.p_string = s;
    868 	error("%s", &parg);
    869 }
    870 #endif /* HAVE_LESSKEYSRC */
    871 
    872 /*
    873  * Add a lesskey file.
    874  */
    875 public int add_hometable(int (*call_lesskey)(char *, int), char *envname, char *def_filename, int sysvar)
    876 {
    877 	char *filename;
    878 	int r;
    879 
    880 	if (envname != NULL && (filename = lgetenv(envname)) != NULL)
    881 		filename = save(filename);
    882 	else if (sysvar) /* def_filename is full path */
    883 		filename = save(def_filename);
    884 	else /* def_filename is just basename */
    885 	{
    886 		/* Remove first char (normally a dot) unless stored in $HOME. */
    887 		char *xdg = lgetenv("XDG_CONFIG_HOME");
    888 		if (!isnullenv(xdg))
    889 			filename = dirfile(xdg, &def_filename[1], 1);
    890 		if (filename == NULL)
    891 		{
    892 			char *home = lgetenv("HOME");
    893 			if (!isnullenv(home))
    894 			{
    895 				char *cfg_dir = dirfile(home, ".config", 0);
    896 				filename = dirfile(cfg_dir, &def_filename[1], 1);
    897 				free(cfg_dir);
    898 			}
    899 		}
    900 		if (filename == NULL)
    901 			filename = homefile(def_filename);
    902 	}
    903 	if (filename == NULL)
    904 		return -1;
    905 	r = (*call_lesskey)(filename, sysvar);
    906 	free(filename);
    907 	return (r);
    908 }
    909 #endif
    910 
    911 /*
    912  * See if a char is a special line-editing command.
    913  */
    914 public int editchar(int c, int flags)
    915 {
    916 	int action;
    917 	int nch;
    918 	char *s;
    919 	char usercmd[MAX_CMDLEN+1];
    920 
    921 	/*
    922 	 * An editing character could actually be a sequence of characters;
    923 	 * for example, an escape sequence sent by pressing the uparrow key.
    924 	 * To match the editing string, we use the command decoder
    925 	 * but give it the edit-commands command table
    926 	 * This table is constructed to match the user's keyboard.
    927 	 */
    928 	if (c == erase_char || c == erase2_char)
    929 		return (EC_BACKSPACE);
    930 	if (c == kill_char)
    931 	{
    932 #if MSDOS_COMPILER==WIN32C
    933 		if (!win32_kbhit())
    934 #endif
    935 		return (EC_LINEKILL);
    936 	}
    937 
    938 	/*
    939 	 * Collect characters in a buffer.
    940 	 * Start with the one we have, and get more if we need them.
    941 	 */
    942 	nch = 0;
    943 	do {
    944 	        if (nch > 0)
    945 			c = getcc();
    946 		usercmd[nch] = c;
    947 		usercmd[nch+1] = '\0';
    948 		nch++;
    949 		action = ecmd_decode(usercmd, &s);
    950 	} while (action == A_PREFIX && nch < MAX_CMDLEN);
    951 
    952 	if (action == EC_X11MOUSE)
    953 		return (x11mouse_action(1));
    954 	if (action == EC_X116MOUSE)
    955 		return (x116mouse_action(1));
    956 
    957 	if (flags & ECF_NORIGHTLEFT)
    958 	{
    959 		switch (action)
    960 		{
    961 		case EC_RIGHT:
    962 		case EC_LEFT:
    963 			action = A_INVALID;
    964 			break;
    965 		}
    966 	}
    967 #if CMD_HISTORY
    968 	if (flags & ECF_NOHISTORY)
    969 	{
    970 		/*
    971 		 * The caller says there is no history list.
    972 		 * Reject any history-manipulation action.
    973 		 */
    974 		switch (action)
    975 		{
    976 		case EC_UP:
    977 		case EC_DOWN:
    978 			action = A_INVALID;
    979 			break;
    980 		}
    981 	}
    982 #endif
    983 #if TAB_COMPLETE_FILENAME
    984 	if (flags & ECF_NOCOMPLETE)
    985 	{
    986 		/*
    987 		 * The caller says we don't want any filename completion cmds.
    988 		 * Reject them.
    989 		 */
    990 		switch (action)
    991 		{
    992 		case EC_F_COMPLETE:
    993 		case EC_B_COMPLETE:
    994 		case EC_EXPAND:
    995 			action = A_INVALID;
    996 			break;
    997 		}
    998 	}
    999 #endif
   1000 	if ((flags & ECF_PEEK) || action == A_INVALID)
   1001 	{
   1002 		/*
   1003 		 * We're just peeking, or we didn't understand the command.
   1004 		 * Unget all the characters we read in the loop above.
   1005 		 * This does NOT include the original character that was
   1006 		 * passed in as a parameter.
   1007 		 */
   1008 		while (nch > 1)
   1009 		{
   1010 			ungetcc(usercmd[--nch]);
   1011 		}
   1012 	} else
   1013 	{
   1014 		if (s != NULL)
   1015 			ungetsc(s);
   1016 	}
   1017 	return action;
   1018 }
   1019 
   1020