Annotation of src/usr.bin/less/decode.c, Revision 1.3
1.3 ! mpech 1: /* $OpenBSD: decode.c,v 1.2 2001/01/29 01:58:01 niklas Exp $ */
1.2 niklas 2:
1.1 etheisen 3: /*
4: * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman
5: * All rights reserved.
6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice in the documentation and/or other materials provided with
14: * the distribution.
15: *
16: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
17: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
20: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27: */
28:
29:
30: /*
31: * Routines to decode user commands.
32: *
33: * This is all table driven.
34: * A command table is a sequence of command descriptors.
35: * Each command descriptor is a sequence of bytes with the following format:
36: * <c1><c2>...<cN><0><action>
37: * The characters c1,c2,...,cN are the command string; that is,
38: * the characters which the user must type.
39: * It is terminated by a null <0> byte.
40: * The byte after the null byte is the action code associated
41: * with the command string.
42: * If an action byte is OR-ed with A_EXTRA, this indicates
43: * that the option byte is followed by an extra string.
44: *
45: * There may be many command tables.
46: * The first (default) table is built-in.
47: * Other tables are read in from "lesskey" files.
48: * All the tables are linked together and are searched in order.
49: */
50:
51: #include "less.h"
52: #include "cmd.h"
53: #include "lesskey.h"
54:
55: extern int erase_char, kill_char;
56:
57: /*
58: * Command table is ordered roughly according to expected
59: * frequency of use, so the common commands are near the beginning.
60: */
61: static unsigned char cmdtable[] =
62: {
63: '\r',0, A_F_LINE,
64: '\n',0, A_F_LINE,
65: 'e',0, A_F_LINE,
66: 'j',0, A_F_LINE,
67: CONTROL('E'),0, A_F_LINE,
68: CONTROL('N'),0, A_F_LINE,
69: 'k',0, A_B_LINE,
70: 'y',0, A_B_LINE,
71: CONTROL('Y'),0, A_B_LINE,
72: CONTROL('K'),0, A_B_LINE,
73: CONTROL('P'),0, A_B_LINE,
74: 'J',0, A_FF_LINE,
75: 'K',0, A_BF_LINE,
76: 'Y',0, A_BF_LINE,
77: 'd',0, A_F_SCROLL,
78: CONTROL('D'),0, A_F_SCROLL,
79: 'u',0, A_B_SCROLL,
80: CONTROL('U'),0, A_B_SCROLL,
81: ' ',0, A_F_SCREEN,
82: 'f',0, A_F_SCREEN,
83: CONTROL('F'),0, A_F_SCREEN,
84: CONTROL('V'),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: 'z',0, A_F_WINDOW,
89: 'w',0, A_B_WINDOW,
90: 'F',0, A_F_FOREVER,
91: 'R',0, A_FREPAINT,
92: 'r',0, A_REPAINT,
93: CONTROL('R'),0, A_REPAINT,
94: CONTROL('L'),0, A_REPAINT,
95: ESC,'u',0, A_UNDO_SEARCH,
96: 'g',0, A_GOLINE,
97: '<',0, A_GOLINE,
98: ESC,'<',0, A_GOLINE,
99: 'p',0, A_PERCENT,
100: '%',0, A_PERCENT,
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: '[',0, A_F_BRACKET|A_EXTRA, '[',']',0,
106: ']',0, A_B_BRACKET|A_EXTRA, '[',']',0,
107: ESC,CONTROL('F'),0, A_F_BRACKET,
108: ESC,CONTROL('B'),0, A_B_BRACKET,
109: 'G',0, A_GOEND,
110: ESC,'>',0, A_GOEND,
111: '>',0, A_GOEND,
112: 'P',0, A_GOPOS,
113:
114: '0',0, A_DIGIT,
115: '1',0, A_DIGIT,
116: '2',0, A_DIGIT,
117: '3',0, A_DIGIT,
118: '4',0, A_DIGIT,
119: '5',0, A_DIGIT,
120: '6',0, A_DIGIT,
121: '7',0, A_DIGIT,
122: '8',0, A_DIGIT,
123: '9',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: ':','x',0, A_INDEX_FILE,
145: '-',0, A_OPT_TOGGLE,
146: ':','t',0, A_OPT_TOGGLE|A_EXTRA, 't',0,
147: 's',0, A_OPT_TOGGLE|A_EXTRA, 'o',0,
148: '_',0, A_DISP_OPTION,
149: '|',0, A_PIPE,
150: 'v',0, A_VISUAL,
151: '!',0, A_SHELL,
152: '+',0, A_FIRSTCMD,
153:
154: 'H',0, A_HELP,
155: 'h',0, A_HELP,
156: 'V',0, A_VERSION,
157: 'q',0, A_QUIT,
158: ':','q',0, A_QUIT,
159: ':','Q',0, A_QUIT,
160: 'Z','Z',0, A_QUIT
161: };
162:
163: static unsigned char edittable[] =
164: {
165: '\t',0, EC_F_COMPLETE, /* TAB */
166: '\17',0, EC_B_COMPLETE, /* BACKTAB */
167: '\14',0, EC_EXPAND, /* CTRL-L */
168: CONTROL('V'),0, EC_LITERAL, /* BACKSLASH */
169: CONTROL('A'),0, EC_LITERAL, /* BACKSLASH */
170: ESC,'l',0, EC_RIGHT, /* ESC l */
171: ESC,'h',0, EC_LEFT, /* ESC h */
172: ESC,'b',0, EC_W_LEFT, /* ESC b */
173: ESC,'w',0, EC_W_RIGHT, /* ESC w */
174: ESC,'i',0, EC_INSERT, /* ESC i */
175: ESC,'x',0, EC_DELETE, /* ESC x */
176: ESC,'X',0, EC_W_DELETE, /* ESC X */
177: ESC,'\b',0, EC_W_BACKSPACE, /* ESC BACKSPACE */
178: ESC,'0',0, EC_HOME, /* ESC 0 */
179: ESC,'$',0, EC_END, /* ESC $ */
180: ESC,'k',0, EC_UP, /* ESC k */
181: ESC,'j',0, EC_DOWN, /* ESC j */
182: ESC,'\t',0, EC_B_COMPLETE /* ESC TAB */
183: };
184:
185: /*
186: * Structure to support a list of command tables.
187: */
188: struct tablelist
189: {
190: struct tablelist *t_next;
191: char *t_start;
192: char *t_end;
193: };
194:
195: /*
196: * List of command tables and list of line-edit tables.
197: */
198: static struct tablelist *list_fcmd_tables = NULL;
199: static struct tablelist *list_ecmd_tables = NULL;
200:
201:
202: /*
203: * Initialize the command lists.
204: */
205: public void
206: init_cmds()
207: {
208: /*
209: * Add the default command tables.
210: */
211: add_fcmd_table((char*)cmdtable, sizeof(cmdtable));
212: add_ecmd_table((char*)edittable, sizeof(edittable));
213: get_editkeys();
214: #if USERFILE
215: /*
216: * Try to add the tables in the standard lesskey file "$HOME/.less".
217: */
218: add_hometable();
219: #endif
220: }
221:
222: /*
223: *
224: */
225: static int
226: add_cmd_table(tlist, buf, len)
227: struct tablelist **tlist;
228: char *buf;
229: int len;
230: {
1.3 ! mpech 231: struct tablelist *t;
1.1 etheisen 232:
233: if (len == 0)
234: return (0);
235: /*
236: * Allocate a tablelist structure, initialize it,
237: * and link it into the list of tables.
238: */
239: if ((t = (struct tablelist *)
240: calloc(1, sizeof(struct tablelist))) == NULL)
241: {
242: return (-1);
243: }
244: t->t_start = buf;
245: t->t_end = buf + len;
246: t->t_next = *tlist;
247: *tlist = t;
248: return (0);
249: }
250:
251: /*
252: * Add a command table.
253: */
254: public void
255: add_fcmd_table(buf, len)
256: char *buf;
257: int len;
258: {
259: if (add_cmd_table(&list_fcmd_tables, buf, len) < 0)
260: error("Warning: some commands disabled", NULL_PARG);
261: }
262:
263: /*
264: * Add an editing command table.
265: */
266: public void
267: add_ecmd_table(buf, len)
268: char *buf;
269: int len;
270: {
271: if (add_cmd_table(&list_ecmd_tables, buf, len) < 0)
272: error("Warning: some edit commands disabled", NULL_PARG);
273: }
274:
275: /*
276: * Search a single command table for the command string in cmd.
277: */
278: public int
279: cmd_search(cmd, table, endtable, sp)
280: char *cmd;
281: char *table;
282: char *endtable;
283: char **sp;
284: {
1.3 ! mpech 285: char *p;
! 286: char *q;
! 287: int a;
1.1 etheisen 288:
289: for (p = table, q = cmd; p < endtable; p++, q++)
290: {
291: if (*p == *q)
292: {
293: /*
294: * Current characters match.
295: * If we're at the end of the string, we've found it.
296: * Return the action code, which is the character
297: * after the null at the end of the string
298: * in the command table.
299: */
300: if (*p == '\0')
301: {
302: a = *++p & 0377;
303: if (a == A_END_LIST)
304: {
305: /*
306: * We get here only if the original
307: * cmd string passed in was empty ("").
308: * I don't think that can happen,
309: * but just in case ...
310: */
311: return (A_UINVALID);
312: }
313: /*
314: * Check for an "extra" string.
315: */
316: if (a & A_EXTRA)
317: {
318: *sp = ++p;
319: a &= ~A_EXTRA;
320: } else
321: *sp = NULL;
322: return (a);
323: }
324: } else if (*q == '\0')
325: {
326: /*
327: * Hit the end of the user's command,
328: * but not the end of the string in the command table.
329: * The user's command is incomplete.
330: */
331: return (A_PREFIX);
332: } else
333: {
334: /*
335: * Not a match.
336: * Skip ahead to the next command in the
337: * command table, and reset the pointer
338: * to the beginning of the user's command.
339: */
340: if (*p == '\0' && p[1] == A_END_LIST)
341: {
342: /*
343: * A_END_LIST is a special marker that tells
344: * us to abort the cmd search.
345: */
346: return (A_UINVALID);
347: }
348: while (*p++ != '\0') ;
349: if (*p & A_EXTRA)
350: while (*++p != '\0') ;
351: q = cmd-1;
352: }
353: }
354: /*
355: * No match found in the entire command table.
356: */
357: return (A_INVALID);
358: }
359:
360: /*
361: * Decode a command character and return the associated action.
362: * The "extra" string, if any, is returned in sp.
363: */
364: static int
365: cmd_decode(tlist, cmd, sp)
366: struct tablelist *tlist;
367: char *cmd;
368: char **sp;
369: {
1.3 ! mpech 370: struct tablelist *t;
! 371: int action = A_INVALID;
1.1 etheisen 372:
373: /*
374: * Search thru all the command tables.
375: * Stop when we find an action which is not A_INVALID.
376: */
377: for (t = tlist; t != NULL; t = t->t_next)
378: {
379: action = cmd_search(cmd, t->t_start, t->t_end, sp);
380: if (action != A_INVALID)
381: break;
382: }
383: return (action);
384: }
385:
386: /*
387: * Decode a command from the cmdtables list.
388: */
389: public int
390: fcmd_decode(cmd, sp)
391: char *cmd;
392: char **sp;
393: {
394: return (cmd_decode(list_fcmd_tables, cmd, sp));
395: }
396:
397: /*
398: * Decode a command from the edittables list.
399: */
400: public int
401: ecmd_decode(cmd, sp)
402: char *cmd;
403: char **sp;
404: {
405: return (cmd_decode(list_ecmd_tables, cmd, sp));
406: }
407:
408: #if USERFILE
409: static int
410: gint(sp)
411: char **sp;
412: {
413: int n;
414:
415: n = *(*sp)++;
416: n += *(*sp)++ * KRADIX;
417: return (n);
418: }
419:
420: static int
421: old_lesskey(buf, len)
422: char *buf;
423: int len;
424: {
425: /*
426: * Old-style lesskey file.
427: * The file must end with either
428: * ...,cmd,0,action
429: * or ...,cmd,0,action|A_EXTRA,string,0
430: * So the last byte or the second to last byte must be zero.
431: */
432: if (buf[len-1] != '\0' && buf[len-2] != '\0')
433: return (-1);
434: add_fcmd_table(buf, len);
435: return (0);
436: }
437:
438: static int
439: new_lesskey(buf, len)
440: char *buf;
441: int len;
442: {
443: char *p;
1.3 ! mpech 444: int c;
! 445: int done;
! 446: int n;
1.1 etheisen 447:
448: /*
449: * New-style lesskey file.
450: * Extract the pieces.
451: */
452: if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
453: buf[len-2] != C1_END_LESSKEY_MAGIC ||
454: buf[len-1] != C2_END_LESSKEY_MAGIC)
455: return (-1);
456: p = buf + 4;
457: done = 0;
458: while (!done)
459: {
460: c = *p++;
461: switch (c)
462: {
463: case CMD_SECTION:
464: n = gint(&p);
465: add_fcmd_table(p, n);
466: p += n;
467: break;
468: case EDIT_SECTION:
469: n = gint(&p);
470: add_ecmd_table(p, n);
471: p += n;
472: break;
473: case END_SECTION:
474: done = 1;
475: break;
476: default:
477: free(buf);
478: return (-1);
479: }
480: }
481: return (0);
482: }
483:
484: /*
485: * Set up a user command table, based on a "lesskey" file.
486: */
487: public int
488: lesskey(filename)
489: char *filename;
490: {
1.3 ! mpech 491: char *buf;
! 492: POSITION len;
! 493: long n;
! 494: int f;
1.1 etheisen 495:
496: /*
497: * Try to open the lesskey file.
498: */
499: f = open(filename, OPEN_READ);
500: if (f < 0)
501: return (1);
502:
503: /*
504: * Read the file into a buffer.
505: * We first figure out the size of the file and allocate space for it.
506: * {{ Minimal error checking is done here.
507: * A garbage .less file will produce strange results.
508: * To avoid a large amount of error checking code here, we
509: * rely on the lesskey program to generate a good .less file. }}
510: */
511: len = filesize(f);
512: if (len == NULL_POSITION || len < 3)
513: {
514: /*
515: * Bad file (valid file must have at least 3 chars).
516: */
517: close(f);
518: return (-1);
519: }
520: if ((buf = (char *) calloc((int)len, sizeof(char))) == NULL)
521: {
522: close(f);
523: return (-1);
524: }
525: if (lseek(f, (off_t)0, 0) == BAD_LSEEK)
526: {
527: free(buf);
528: close(f);
529: return (-1);
530: }
531: n = read(f, buf, (unsigned int) len);
532: close(f);
533: if (n != len)
534: {
535: free(buf);
536: return (-1);
537: }
538:
539: /*
540: * Figure out if this is an old-style (before version 241)
541: * or new-style lesskey file format.
542: */
543: if (buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
544: buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
545: return (old_lesskey(buf, (int)len));
546: return (new_lesskey(buf, (int)len));
547: }
548:
549: /*
550: * Add the standard lesskey file "$HOME/.less"
551: */
552: public void
553: add_hometable()
554: {
555: char *filename;
556: PARG parg;
557:
558: filename = homefile(LESSKEYFILE);
559: if (filename == NULL)
560: return;
561: if (lesskey(filename) < 0)
562: {
563: parg.p_string = filename;
564: error("Cannot use lesskey file \"%s\"", &parg);
565: }
566: free(filename);
567: }
568: #endif
569:
570: /*
571: * See if a char is a special line-editing command.
572: */
573: public int
574: editchar(c, flags)
575: int c;
576: int flags;
577: {
578: int action;
579: int nch;
580: char *s;
581: char usercmd[MAX_CMDLEN+1];
582:
583: /*
584: * An editing character could actually be a sequence of characters;
585: * for example, an escape sequence sent by pressing the uparrow key.
586: * To match the editing string, we use the command decoder
587: * but give it the edit-commands command table
588: * This table is constructed to match the user's keyboard.
589: */
590: if (c == erase_char)
591: return (EC_BACKSPACE);
592: if (c == kill_char)
593: return (EC_LINEKILL);
594:
595: /*
596: * Collect characters in a buffer.
597: * Start with the one we have, and get more if we need them.
598: */
599: nch = 0;
600: do {
601: if (nch > 0)
602: c = getcc();
603: usercmd[nch] = c;
604: usercmd[nch+1] = '\0';
605: nch++;
606: action = ecmd_decode(usercmd, &s);
607: } while (action == A_PREFIX);
608:
609: if (flags & EC_NOHISTORY)
610: {
611: /*
612: * The caller says there is no history list.
613: * Reject any history-manipulation action.
614: */
615: switch (action)
616: {
617: case EC_UP:
618: case EC_DOWN:
619: action = A_INVALID;
620: break;
621: }
622: }
623: if (flags & EC_NOCOMPLETE)
624: {
625: /*
626: * The caller says we don't want any filename completion cmds.
627: * Reject them.
628: */
629: switch (action)
630: {
631: case EC_F_COMPLETE:
632: case EC_B_COMPLETE:
633: case EC_EXPAND:
634: action = A_INVALID;
635: break;
636: }
637: }
638: if ((flags & EC_PEEK) || action == A_INVALID)
639: {
640: /*
641: * We're just peeking, or we didn't understand the command.
642: * Unget all the characters we read in the loop above.
643: * This does NOT include the original character that was
644: * passed in as a parameter.
645: */
646: while (nch > 1) {
647: ungetcc(usercmd[--nch]);
648: }
649: } else
650: {
651: if (s != NULL)
652: ungetsc(s);
653: }
654: return action;
655: }
656: