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