"SfR Fresh" - the SfR Freeware/Shareware Archive 
Member "less-424/lesskey.c" of archive less-424.tar.gz:
As a special service "SfR Fresh" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting with prefixed line numbers.
Alternatively you can here view or download the uninterpreted source code file.
That can be also achieved for any archive member file by clicking within an archive contents listing on the first character of the file(path) respectively on the according byte size field.
1 /*
2 * Copyright (C) 1984-2008 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 * lesskey [-o output] [input]
14 *
15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16 *
17 * Make a .less file.
18 * If no input file is specified, standard input is used.
19 * If no output file is specified, $HOME/.less is used.
20 *
21 * The .less file is used to specify (to "less") user-defined
22 * key bindings. Basically any sequence of 1 to MAX_CMDLEN
23 * keystrokes may be bound to an existing less function.
24 *
25 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
26 *
27 * The input file is an ascii file consisting of a
28 * sequence of lines of the form:
29 * string <whitespace> action [chars] <newline>
30 *
31 * "string" is a sequence of command characters which form
32 * the new user-defined command. The command
33 * characters may be:
34 * 1. The actual character itself.
35 * 2. A character preceded by ^ to specify a
36 * control character (e.g. ^X means control-X).
37 * 3. A backslash followed by one to three octal digits
38 * to specify a character by its octal value.
39 * 4. A backslash followed by b, e, n, r or t
40 * to specify \b, ESC, \n, \r or \t, respectively.
41 * 5. Any character (other than those mentioned above) preceded
42 * by a \ to specify the character itself (characters which
43 * must be preceded by \ include ^, \, and whitespace.
44 * "action" is the name of a "less" action, from the table below.
45 * "chars" is an optional sequence of characters which is treated
46 * as keyboard input after the command is executed.
47 *
48 * Blank lines and lines which start with # are ignored,
49 * except for the special control lines:
50 * #command Signals the beginning of the command
51 * keys section.
52 * #line-edit Signals the beginning of the line-editing
53 * keys section.
54 * #env Signals the beginning of the environment
55 * variable section.
56 * #stop Stops command parsing in less;
57 * causes all default keys to be disabled.
58 *
59 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
60 *
61 * The output file is a non-ascii file, consisting of a header,
62 * one or more sections, and a trailer.
63 * Each section begins with a section header, a section length word
64 * and the section data. Normally there are three sections:
65 * CMD_SECTION Definition of command keys.
66 * EDIT_SECTION Definition of editing keys.
67 * END_SECTION A special section header, with no
68 * length word or section data.
69 *
70 * Section data consists of zero or more byte sequences of the form:
71 * string <0> <action>
72 * or
73 * string <0> <action|A_EXTRA> chars <0>
74 *
75 * "string" is the command string.
76 * "<0>" is one null byte.
77 * "<action>" is one byte containing the action code (the A_xxx value).
78 * If action is ORed with A_EXTRA, the action byte is followed
79 * by the null-terminated "chars" string.
80 *
81 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
82 */
83
84 #include "less.h"
85 #include "lesskey.h"
86 #include "cmd.h"
87
88 struct cmdname
89 {
90 char *cn_name;
91 int cn_action;
92 };
93
94 struct cmdname cmdnames[] =
95 {
96 { "back-bracket", A_B_BRACKET },
97 { "back-line", A_B_LINE },
98 { "back-line-force", A_BF_LINE },
99 { "back-screen", A_B_SCREEN },
100 { "back-scroll", A_B_SCROLL },
101 { "back-search", A_B_SEARCH },
102 { "back-window", A_B_WINDOW },
103 { "debug", A_DEBUG },
104 { "digit", A_DIGIT },
105 { "display-flag", A_DISP_OPTION },
106 { "display-option", A_DISP_OPTION },
107 { "end", A_GOEND },
108 { "examine", A_EXAMINE },
109 { "filter", A_FILTER },
110 { "first-cmd", A_FIRSTCMD },
111 { "firstcmd", A_FIRSTCMD },
112 { "flush-repaint", A_FREPAINT },
113 { "forw-bracket", A_F_BRACKET },
114 { "forw-forever", A_F_FOREVER },
115 { "forw-line", A_F_LINE },
116 { "forw-line-force", A_FF_LINE },
117 { "forw-screen", A_F_SCREEN },
118 { "forw-screen-force", A_FF_SCREEN },
119 { "forw-scroll", A_F_SCROLL },
120 { "forw-search", A_F_SEARCH },
121 { "forw-window", A_F_WINDOW },
122 { "goto-end", A_GOEND },
123 { "goto-line", A_GOLINE },
124 { "goto-mark", A_GOMARK },
125 { "help", A_HELP },
126 { "index-file", A_INDEX_FILE },
127 { "invalid", A_UINVALID },
128 { "left-scroll", A_LSHIFT },
129 { "next-file", A_NEXT_FILE },
130 { "next-tag", A_NEXT_TAG },
131 { "noaction", A_NOACTION },
132 { "percent", A_PERCENT },
133 { "pipe", A_PIPE },
134 { "prev-file", A_PREV_FILE },
135 { "prev-tag", A_PREV_TAG },
136 { "quit", A_QUIT },
137 { "remove-file", A_REMOVE_FILE },
138 { "repaint", A_REPAINT },
139 { "repaint-flush", A_FREPAINT },
140 { "repeat-search", A_AGAIN_SEARCH },
141 { "repeat-search-all", A_T_AGAIN_SEARCH },
142 { "reverse-search", A_REVERSE_SEARCH },
143 { "reverse-search-all", A_T_REVERSE_SEARCH },
144 { "right-scroll", A_RSHIFT },
145 { "set-mark", A_SETMARK },
146 { "shell", A_SHELL },
147 { "status", A_STAT },
148 { "toggle-flag", A_OPT_TOGGLE },
149 { "toggle-option", A_OPT_TOGGLE },
150 { "undo-hilite", A_UNDO_SEARCH },
151 { "version", A_VERSION },
152 { "visual", A_VISUAL },
153 { NULL, 0 }
154 };
155
156 struct cmdname editnames[] =
157 {
158 { "back-complete", EC_B_COMPLETE },
159 { "backspace", EC_BACKSPACE },
160 { "delete", EC_DELETE },
161 { "down", EC_DOWN },
162 { "end", EC_END },
163 { "expand", EC_EXPAND },
164 { "forw-complete", EC_F_COMPLETE },
165 { "home", EC_HOME },
166 { "insert", EC_INSERT },
167 { "invalid", EC_UINVALID },
168 { "kill-line", EC_LINEKILL },
169 { "left", EC_LEFT },
170 { "literal", EC_LITERAL },
171 { "right", EC_RIGHT },
172 { "up", EC_UP },
173 { "word-backspace", EC_W_BACKSPACE },
174 { "word-delete", EC_W_DELETE },
175 { "word-left", EC_W_LEFT },
176 { "word-right", EC_W_RIGHT },
177 { NULL, 0 }
178 };
179
180 struct table
181 {
182 struct cmdname *names;
183 char *pbuffer;
184 char buffer[MAX_USERCMD];
185 };
186
187 struct table cmdtable;
188 struct table edittable;
189 struct table vartable;
190 struct table *currtable = &cmdtable;
191
192 char fileheader[] = {
193 C0_LESSKEY_MAGIC,
194 C1_LESSKEY_MAGIC,
195 C2_LESSKEY_MAGIC,
196 C3_LESSKEY_MAGIC
197 };
198 char filetrailer[] = {
199 C0_END_LESSKEY_MAGIC,
200 C1_END_LESSKEY_MAGIC,
201 C2_END_LESSKEY_MAGIC
202 };
203 char cmdsection[1] = { CMD_SECTION };
204 char editsection[1] = { EDIT_SECTION };
205 char varsection[1] = { VAR_SECTION };
206 char endsection[1] = { END_SECTION };
207
208 char *infile = NULL;
209 char *outfile = NULL ;
210
211 int linenum;
212 int errors;
213
214 extern char version[];
215
216 void
217 usage()
218 {
219 fprintf(stderr, "usage: lesskey [-o output] [input]\n");
220 exit(1);
221 }
222
223 char *
224 mkpathname(dirname, filename)
225 char *dirname;
226 char *filename;
227 {
228 char *pathname;
229
230 pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
231 strcpy(pathname, dirname);
232 strcat(pathname, PATHNAME_SEP);
233 strcat(pathname, filename);
234 return (pathname);
235 }
236
237 /*
238 * Figure out the name of a default file (in the user's HOME directory).
239 */
240 char *
241 homefile(filename)
242 char *filename;
243 {
244 char *p;
245 char *pathname;
246
247 if ((p = getenv("HOME")) != NULL && *p != '\0')
248 pathname = mkpathname(p, filename);
249 #if OS2
250 else if ((p = getenv("INIT")) != NULL && *p != '\0')
251 pathname = mkpathname(p, filename);
252 #endif
253 else
254 {
255 fprintf(stderr, "cannot find $HOME - using current directory\n");
256 pathname = mkpathname(".", filename);
257 }
258 return (pathname);
259 }
260
261 /*
262 * Parse command line arguments.
263 */
264 void
265 parse_args(argc, argv)
266 int argc;
267 char **argv;
268 {
269 char *arg;
270
271 outfile = NULL;
272 while (--argc > 0)
273 {
274 arg = *++argv;
275 if (arg[0] != '-')
276 /* Arg does not start with "-"; it's not an option. */
277 break;
278 if (arg[1] == '\0')
279 /* "-" means standard input. */
280 break;
281 if (arg[1] == '-' && arg[2] == '\0')
282 {
283 /* "--" means end of options. */
284 argc--;
285 argv++;
286 break;
287 }
288 switch (arg[1])
289 {
290 case '-':
291 if (strncmp(arg, "--output", 8) == 0)
292 {
293 if (arg[8] == '\0')
294 outfile = &arg[8];
295 else if (arg[8] == '=')
296 outfile = &arg[9];
297 else
298 usage();
299 goto opt_o;
300 }
301 if (strcmp(arg, "--version") == 0)
302 {
303 goto opt_V;
304 }
305 usage();
306 break;
307 case 'o':
308 outfile = &argv[0][2];
309 opt_o:
310 if (*outfile == '\0')
311 {
312 if (--argc <= 0)
313 usage();
314 outfile = *(++argv);
315 }
316 break;
317 case 'V':
318 opt_V:
319 printf("lesskey version %s\n", version);
320 exit(0);
321 default:
322 usage();
323 }
324 }
325 if (argc > 1)
326 usage();
327 /*
328 * Open the input file, or use DEF_LESSKEYINFILE if none specified.
329 */
330 if (argc > 0)
331 infile = *argv;
332 else
333 infile = homefile(DEF_LESSKEYINFILE);
334 }
335
336 /*
337 * Initialize data structures.
338 */
339 void
340 init_tables()
341 {
342 cmdtable.names = cmdnames;
343 cmdtable.pbuffer = cmdtable.buffer;
344
345 edittable.names = editnames;
346 edittable.pbuffer = edittable.buffer;
347
348 vartable.names = NULL;
349 vartable.pbuffer = vartable.buffer;
350 }
351
352 /*
353 * Parse one character of a string.
354 */
355 char *
356 tstr(pp, xlate)
357 char **pp;
358 int xlate;
359 {
360 register char *p;
361 register char ch;
362 register int i;
363 static char buf[10];
364 static char tstr_control_k[] =
365 { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' };
366
367 p = *pp;
368 switch (*p)
369 {
370 case '\\':
371 ++p;
372 switch (*p)
373 {
374 case '0': case '1': case '2': case '3':
375 case '4': case '5': case '6': case '7':
376 /*
377 * Parse an octal number.
378 */
379 ch = 0;
380 i = 0;
381 do
382 ch = 8*ch + (*p - '0');
383 while (*++p >= '0' && *p <= '7' && ++i < 3);
384 *pp = p;
385 if (xlate && ch == CONTROL('K'))
386 return tstr_control_k;
387 buf[0] = ch;
388 buf[1] = '\0';
389 return (buf);
390 case 'b':
391 *pp = p+1;
392 return ("\b");
393 case 'e':
394 *pp = p+1;
395 buf[0] = ESC;
396 buf[1] = '\0';
397 return (buf);
398 case 'n':
399 *pp = p+1;
400 return ("\n");
401 case 'r':
402 *pp = p+1;
403 return ("\r");
404 case 't':
405 *pp = p+1;
406 return ("\t");
407 case 'k':
408 if (xlate)
409 {
410 switch (*++p)
411 {
412 case 'u': ch = SK_UP_ARROW; break;
413 case 'd': ch = SK_DOWN_ARROW; break;
414 case 'r': ch = SK_RIGHT_ARROW; break;
415 case 'l': ch = SK_LEFT_ARROW; break;
416 case 'U': ch = SK_PAGE_UP; break;
417 case 'D': ch = SK_PAGE_DOWN; break;
418 case 'h': ch = SK_HOME; break;
419 case 'e': ch = SK_END; break;
420 case 'x': ch = SK_DELETE; break;
421 default:
422 error("illegal char after \\k");
423 *pp = p+1;
424 return ("");
425 }
426 *pp = p+1;
427 buf[0] = SK_SPECIAL_KEY;
428 buf[1] = ch;
429 buf[2] = 6;
430 buf[3] = 1;
431 buf[4] = 1;
432 buf[5] = 1;
433 buf[6] = '\0';
434 return (buf);
435 }
436 /* FALLTHRU */
437 default:
438 /*
439 * Backslash followed by any other char
440 * just means that char.
441 */
442 *pp = p+1;
443 buf[0] = *p;
444 buf[1] = '\0';
445 if (xlate && buf[0] == CONTROL('K'))
446 return tstr_control_k;
447 return (buf);
448 }
449 case '^':
450 /*
451 * Carat means CONTROL.
452 */
453 *pp = p+2;
454 buf[0] = CONTROL(p[1]);
455 buf[1] = '\0';
456 if (buf[0] == CONTROL('K'))
457 return tstr_control_k;
458 return (buf);
459 }
460 *pp = p+1;
461 buf[0] = *p;
462 buf[1] = '\0';
463 if (xlate && buf[0] == CONTROL('K'))
464 return tstr_control_k;
465 return (buf);
466 }
467
468 /*
469 * Skip leading spaces in a string.
470 */
471 public char *
472 skipsp(s)
473 register char *s;
474 {
475 while (*s == ' ' || *s == '\t')
476 s++;
477 return (s);
478 }
479
480 /*
481 * Skip non-space characters in a string.
482 */
483 public char *
484 skipnsp(s)
485 register char *s;
486 {
487 while (*s != '\0' && *s != ' ' && *s != '\t')
488 s++;
489 return (s);
490 }
491
492 /*
493 * Clean up an input line:
494 * strip off the trailing newline & any trailing # comment.
495 */
496 char *
497 clean_line(s)
498 char *s;
499 {
500 register int i;
501
502 s = skipsp(s);
503 for (i = 0; s[i] != '\n' && s[i] != '\r' && s[i] != '\0'; i++)
504 if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
505 break;
506 s[i] = '\0';
507 return (s);
508 }
509
510 /*
511 * Add a byte to the output command table.
512 */
513 void
514 add_cmd_char(c)
515 int c;
516 {
517 if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD)
518 {
519 error("too many commands");
520 exit(1);
521 }
522 *(currtable->pbuffer)++ = c;
523 }
524
525 /*
526 * Add a string to the output command table.
527 */
528 void
529 add_cmd_str(s)
530 char *s;
531 {
532 for ( ; *s != '\0'; s++)
533 add_cmd_char(*s);
534 }
535
536 /*
537 * See if we have a special "control" line.
538 */
539 int
540 control_line(s)
541 char *s;
542 {
543 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)-1) == 0)
544
545 if (PREFIX(s, "#line-edit"))
546 {
547 currtable = &edittable;
548 return (1);
549 }
550 if (PREFIX(s, "#command"))
551 {
552 currtable = &cmdtable;
553 return (1);
554 }
555 if (PREFIX(s, "#env"))
556 {
557 currtable = &vartable;
558 return (1);
559 }
560 if (PREFIX(s, "#stop"))
561 {
562 add_cmd_char('\0');
563 add_cmd_char(A_END_LIST);
564 return (1);
565 }
566 return (0);
567 }
568
569 /*
570 * Output some bytes.
571 */
572 void
573 fputbytes(fd, buf, len)
574 FILE *fd;
575 char *buf;
576 int len;
577 {
578 while (len-- > 0)
579 {
580 fwrite(buf, sizeof(char), 1, fd);
581 buf++;
582 }
583 }
584
585 /*
586 * Output an integer, in special KRADIX form.
587 */
588 void
589 fputint(fd, val)
590 FILE *fd;
591 unsigned int val;
592 {
593 char c;
594
595 if (val >= KRADIX*KRADIX)
596 {
597 fprintf(stderr, "error: integer too big (%d > %d)\n",
598 val, KRADIX*KRADIX);
599 exit(1);
600 }
601 c = val % KRADIX;
602 fwrite(&c, sizeof(char), 1, fd);
603 c = val / KRADIX;
604 fwrite(&c, sizeof(char), 1, fd);
605 }
606
607 /*
608 * Find an action, given the name of the action.
609 */
610 int
611 findaction(actname)
612 char *actname;
613 {
614 int i;
615
616 for (i = 0; currtable->names[i].cn_name != NULL; i++)
617 if (strcmp(currtable->names[i].cn_name, actname) == 0)
618 return (currtable->names[i].cn_action);
619 error("unknown action");
620 return (A_INVALID);
621 }
622
623 void
624 error(s)
625 char *s;
626 {
627 fprintf(stderr, "line %d: %s\n", linenum, s);
628 errors++;
629 }
630
631
632 void
633 parse_cmdline(p)
634 char *p;
635 {
636 int cmdlen;
637 char *actname;
638 int action;
639 char *s;
640 char c;
641
642 /*
643 * Parse the command string and store it in the current table.
644 */
645 cmdlen = 0;
646 do
647 {
648 s = tstr(&p, 1);
649 cmdlen += strlen(s);
650 if (cmdlen > MAX_CMDLEN)
651 error("command too long");
652 else
653 add_cmd_str(s);
654 } while (*p != ' ' && *p != '\t' && *p != '\0');
655 /*
656 * Terminate the command string with a null byte.
657 */
658 add_cmd_char('\0');
659
660 /*
661 * Skip white space between the command string
662 * and the action name.
663 * Terminate the action name with a null byte.
664 */
665 p = skipsp(p);
666 if (*p == '\0')
667 {
668 error("missing action");
669 return;
670 }
671 actname = p;
672 p = skipnsp(p);
673 c = *p;
674 *p = '\0';
675
676 /*
677 * Parse the action name and store it in the current table.
678 */
679 action = findaction(actname);
680
681 /*
682 * See if an extra string follows the action name.
683 */
684 *p = c;
685 p = skipsp(p);
686 if (*p == '\0')
687 {
688 add_cmd_char(action);
689 } else
690 {
691 /*
692 * OR the special value A_EXTRA into the action byte.
693 * Put the extra string after the action byte.
694 */
695 add_cmd_char(action | A_EXTRA);
696 while (*p != '\0')
697 add_cmd_str(tstr(&p, 0));
698 add_cmd_char('\0');
699 }
700 }
701
702 void
703 parse_varline(p)
704 char *p;
705 {
706 char *s;
707
708 do
709 {
710 s = tstr(&p, 0);
711 add_cmd_str(s);
712 } while (*p != ' ' && *p != '\t' && *p != '=' && *p != '\0');
713 /*
714 * Terminate the variable name with a null byte.
715 */
716 add_cmd_char('\0');
717
718 p = skipsp(p);
719 if (*p++ != '=')
720 {
721 error("missing =");
722 return;
723 }
724
725 add_cmd_char(EV_OK|A_EXTRA);
726
727 p = skipsp(p);
728 while (*p != '\0')
729 {
730 s = tstr(&p, 0);
731 add_cmd_str(s);
732 }
733 add_cmd_char('\0');
734 }
735
736 /*
737 * Parse a line from the lesskey file.
738 */
739 void
740 parse_line(line)
741 char *line;
742 {
743 char *p;
744
745 /*
746 * See if it is a control line.
747 */
748 if (control_line(line))
749 return;
750 /*
751 * Skip leading white space.
752 * Replace the final newline with a null byte.
753 * Ignore blank lines and comments.
754 */
755 p = clean_line(line);
756 if (*p == '\0')
757 return;
758
759 if (currtable == &vartable)
760 parse_varline(p);
761 else
762 parse_cmdline(p);
763 }
764
765 int
766 main(argc, argv)
767 int argc;
768 char *argv[];
769 {
770 FILE *desc;
771 FILE *out;
772 char line[1024];
773
774 #ifdef WIN32
775 if (getenv("HOME") == NULL)
776 {
777 /*
778 * If there is no HOME environment variable,
779 * try the concatenation of HOMEDRIVE + HOMEPATH.
780 */
781 char *drive = getenv("HOMEDRIVE");
782 char *path = getenv("HOMEPATH");
783 if (drive != NULL && path != NULL)
784 {
785 char *env = (char *) calloc(strlen(drive) +
786 strlen(path) + 6, sizeof(char));
787 strcpy(env, "HOME=");
788 strcat(env, drive);
789 strcat(env, path);
790 putenv(env);
791 }
792 }
793 #endif /* WIN32 */
794
795 /*
796 * Process command line arguments.
797 */
798 parse_args(argc, argv);
799 init_tables();
800
801 /*
802 * Open the input file.
803 */
804 if (strcmp(infile, "-") == 0)
805 desc = stdin;
806 else if ((desc = fopen(infile, "r")) == NULL)
807 {
808 #if HAVE_PERROR
809 perror(infile);
810 #else
811 fprintf(stderr, "Cannot open %s\n", infile);
812 #endif
813 usage();
814 }
815
816 /*
817 * Read and parse the input file, one line at a time.
818 */
819 errors = 0;
820 linenum = 0;
821 while (fgets(line, sizeof(line), desc) != NULL)
822 {
823 ++linenum;
824 parse_line(line);
825 }
826
827 /*
828 * Write the output file.
829 * If no output file was specified, use "$HOME/.less"
830 */
831 if (errors > 0)
832 {
833 fprintf(stderr, "%d errors; no output produced\n", errors);
834 exit(1);
835 }
836
837 if (outfile == NULL)
838 outfile = getenv("LESSKEY");
839 if (outfile == NULL)
840 outfile = homefile(LESSKEYFILE);
841 if ((out = fopen(outfile, "wb")) == NULL)
842 {
843 #if HAVE_PERROR
844 perror(outfile);
845 #else
846 fprintf(stderr, "Cannot open %s\n", outfile);
847 #endif
848 exit(1);
849 }
850
851 /* File header */
852 fputbytes(out, fileheader, sizeof(fileheader));
853
854 /* Command key section */
855 fputbytes(out, cmdsection, sizeof(cmdsection));
856 fputint(out, cmdtable.pbuffer - cmdtable.buffer);
857 fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
858 /* Edit key section */
859 fputbytes(out, editsection, sizeof(editsection));
860 fputint(out, edittable.pbuffer - edittable.buffer);
861 fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
862
863 /* Environment variable section */
864 fputbytes(out, varsection, sizeof(varsection));
865 fputint(out, vartable.pbuffer - vartable.buffer);
866 fputbytes(out, (char *)vartable.buffer, vartable.pbuffer-vartable.buffer);
867
868 /* File trailer */
869 fputbytes(out, endsection, sizeof(endsection));
870 fputbytes(out, filetrailer, sizeof(filetrailer));
871 return (0);
872 }