xref: /openbsd/usr.bin/less/lesskey.c (revision 78b63d65)
1 /*	$OpenBSD: lesskey.c,v 1.3 2001/11/19 19:02:14 mpech Exp $	*/
2 
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  *	lesskey [-o output] [input]
32  *
33  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
34  *
35  *	Make a .less file.
36  *	If no input file is specified, standard input is used.
37  *	If no output file is specified, $HOME/.less is used.
38  *
39  *	The .less file is used to specify (to "less") user-defined
40  *	key bindings.  Basically any sequence of 1 to MAX_CMDLEN
41  *	keystrokes may be bound to an existing less function.
42  *
43  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
44  *
45  *	The input file is an ascii file consisting of a
46  *	sequence of lines of the form:
47  *		string <whitespace> action [chars] <newline>
48  *
49  *	"string" is a sequence of command characters which form
50  *		the new user-defined command.  The command
51  *		characters may be:
52  *		1. The actual character itself.
53  *		2. A character preceded by ^ to specify a
54  *		   control character (e.g. ^X means control-X).
55  *		3. A backslash followed by one to three octal digits
56  *		   to specify a character by its octal value.
57  *		4. A backslash followed by b, e, n, r or t
58  *		   to specify \b, ESC, \n, \r or \t, respectively.
59  *		5. Any character (other than those mentioned above) preceded
60  *		   by a \ to specify the character itself (characters which
61  *		   must be preceded by \ include ^, \, and whitespace.
62  *	"action" is the name of a "less" action, from the table below.
63  *	"chars" is an optional sequence of characters which is treated
64  *		as keyboard input after the command is executed.
65  *
66  *	Blank lines and lines which start with # are ignored,
67  *	except for the special control lines:
68  *		#line-edit	Signals the beginning of the line-editing
69  *				keys section.
70  *		#stop		Stops command parsing in less;
71  *				causes all default keys to be disabled.
72  *
73  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
74  *
75  *	The output file is a non-ascii file, consisting of a header,
76  *	one or more sections, and a trailer.
77  *	Each section begins with a section header, a section length word
78  *	and the section data.  Normally there are three sections:
79  *		CMD_SECTION	Definition of command keys.
80  *		EDIT_SECTION	Definition of editing keys.
81  *		END_SECTION	A special section header, with no
82  *				length word or section data.
83  *
84  *	Section data consists of zero or more byte sequences of the form:
85  *		string <0> <action>
86  *	or
87  *		string <0> <action|A_EXTRA> chars <0>
88  *
89  *	"string" is the command string.
90  *	"<0>" is one null byte.
91  *	"<action>" is one byte containing the action code (the A_xxx value).
92  *	If action is ORed with A_EXTRA, the action byte is followed
93  *		by the null-terminated "chars" string.
94  *
95  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
96  */
97 
98 #include "less.h"
99 #include "lesskey.h"
100 #include "cmd.h"
101 
102 struct cmdname
103 {
104 	char *cn_name;
105 	int cn_action;
106 };
107 
108 struct cmdname cmdnames[] =
109 {
110 	"back-bracket",		A_B_BRACKET,
111 	"back-line",		A_B_LINE,
112 	"back-line-force",	A_BF_LINE,
113 	"back-screen",		A_B_SCREEN,
114 	"back-scroll",		A_B_SCROLL,
115 	"back-search",		A_B_SEARCH,
116 	"back-window",		A_B_WINDOW,
117 	"debug",		A_DEBUG,
118 	"display-flag",		A_DISP_OPTION,
119 	"display-option",	A_DISP_OPTION,
120 	"end",			A_GOEND,
121 	"examine",		A_EXAMINE,
122 	"first-cmd",		A_FIRSTCMD,
123 	"firstcmd",		A_FIRSTCMD,
124 	"flush-repaint",	A_FREPAINT,
125 	"forw-bracket",		A_F_BRACKET,
126 	"forw-forever",		A_F_FOREVER,
127 	"forw-line",		A_F_LINE,
128 	"forw-line-force",	A_FF_LINE,
129 	"forw-screen",		A_F_SCREEN,
130 	"forw-scroll",		A_F_SCROLL,
131 	"forw-search",		A_F_SEARCH,
132 	"forw-window",		A_F_WINDOW,
133 	"goto-end",		A_GOEND,
134 	"goto-line",		A_GOLINE,
135 	"goto-mark",		A_GOMARK,
136 	"help",			A_HELP,
137 	"index-file",		A_INDEX_FILE,
138 	"invalid",		A_UINVALID,
139 	"next-file",		A_NEXT_FILE,
140 	"noaction",		A_NOACTION,
141 	"percent",		A_PERCENT,
142 	"pipe",			A_PIPE,
143 	"prev-file",		A_PREV_FILE,
144 	"quit",			A_QUIT,
145 	"repaint",		A_REPAINT,
146 	"repaint-flush",	A_FREPAINT,
147 	"repeat-search",	A_AGAIN_SEARCH,
148 	"repeat-search-all",	A_T_AGAIN_SEARCH,
149 	"reverse-search",	A_REVERSE_SEARCH,
150 	"reverse-search-all",	A_T_REVERSE_SEARCH,
151 	"set-mark",		A_SETMARK,
152 	"shell",		A_SHELL,
153 	"status",		A_STAT,
154 	"toggle-flag",		A_OPT_TOGGLE,
155 	"toggle-option",	A_OPT_TOGGLE,
156 	"undo-hilite",		A_UNDO_SEARCH,
157 	"version",		A_VERSION,
158 	"visual",		A_VISUAL,
159 	NULL,			0
160 };
161 
162 struct cmdname editnames[] =
163 {
164 	"back-complete",	EC_B_COMPLETE,
165 	"backspace",		EC_BACKSPACE,
166 	"delete",		EC_DELETE,
167 	"down",			EC_DOWN,
168 	"end",			EC_END,
169 	"expand",		EC_EXPAND,
170 	"forw-complete",	EC_F_COMPLETE,
171 	"home",			EC_HOME,
172 	"insert",		EC_INSERT,
173 	"invalid",		EC_UINVALID,
174 	"kill-line",		EC_LINEKILL,
175 	"left",			EC_LEFT,
176 	"literal",		EC_LITERAL,
177 	"right",		EC_RIGHT,
178 	"up",			EC_UP,
179 	"word-backspace",	EC_W_BACKSPACE,
180 	"word-delete",		EC_W_DELETE,
181 	"word-left",		EC_W_RIGHT,
182 	"word-right",		EC_W_LEFT,
183 	NULL,			0
184 };
185 
186 struct table
187 {
188 	struct cmdname *names;
189 	char *pbuffer;
190 	char buffer[MAX_USERCMD];
191 };
192 
193 struct table cmdtable;
194 struct table edittable;
195 struct table *currtable = &cmdtable;
196 
197 char fileheader[] = {
198 	C0_LESSKEY_MAGIC,
199 	C1_LESSKEY_MAGIC,
200 	C2_LESSKEY_MAGIC,
201 	C3_LESSKEY_MAGIC
202 };
203 char filetrailer[] = {
204 	C0_END_LESSKEY_MAGIC,
205 	C1_END_LESSKEY_MAGIC,
206 	C2_END_LESSKEY_MAGIC
207 };
208 char cmdsection[1] =	{ CMD_SECTION };
209 char editsection[1] =	{ EDIT_SECTION };
210 char endsection[1] =	{ END_SECTION };
211 
212 char *infile = NULL;
213 char *outfile = NULL ;
214 
215 int linenum;
216 int errors;
217 
218 extern char version[];
219 
220 	char *
221 mkpathname(dirname, filename)
222 	char *dirname;
223 	char *filename;
224 {
225 	char *pathname;
226 
227 	pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
228 	strcpy(pathname, dirname);
229 #if MSOFTC || OS2
230 	strcat(pathname, "\\");
231 #else
232 	strcat(pathname, "/");
233 #endif
234 	strcat(pathname, filename);
235 	return (pathname);
236 }
237 
238 /*
239  * Figure out the name of a default file (in the user's HOME directory).
240  */
241 	char *
242 homefile(filename)
243 	char *filename;
244 {
245 	char *p;
246 	char *pathname;
247 
248 	if ((p = getenv("HOME")) != NULL && *p != '\0')
249 		pathname = mkpathname(p, filename);
250 #if OS2
251 	else if ((p = getenv("INIT")) != NULL && *p != '\0')
252 		pathname = mkpathname(p, filename);
253 #endif
254 	else
255 	{
256 		fprintf(stderr, "cannot find $HOME - using current directory\n");
257 		pathname = mkpathname(".", filename);
258 	}
259 	return (pathname);
260 }
261 
262 /*
263  * Parse command line arguments.
264  */
265 	void
266 parse_args(argc, argv)
267 	int argc;
268 	char **argv;
269 {
270 	outfile = NULL;
271 	while (--argc > 0 && **(++argv) == '-' && argv[0][1] != '\0')
272 	{
273 		switch (argv[0][1])
274 		{
275 		case 'o':
276 			outfile = &argv[0][2];
277 			if (*outfile == '\0')
278 			{
279 				if (--argc <= 0)
280 					usage();
281 				outfile = *(++argv);
282 			}
283 			break;
284 		case 'V':
285 			printf("lesskey  version %s\n", version);
286 			exit(0);
287 		default:
288 			usage();
289 		}
290 	}
291 	if (argc > 1)
292 		usage();
293 	/*
294 	 * Open the input file, or use DEF_LESSKEYINFILE if none specified.
295 	 */
296 	if (argc > 0)
297 		infile = *argv;
298 	else
299 		infile = homefile(DEF_LESSKEYINFILE);
300 }
301 
302 /*
303  * Initialize data structures.
304  */
305 	void
306 init_tables()
307 {
308 	cmdtable.names = cmdnames;
309 	cmdtable.pbuffer = cmdtable.buffer;
310 
311 	edittable.names = editnames;
312 	edittable.pbuffer = edittable.buffer;
313 }
314 
315 /*
316  * Parse one character of a string.
317  */
318 	int
319 tchar(pp)
320 	char **pp;
321 {
322 	char *p;
323 	char ch;
324 	int i;
325 
326 	p = *pp;
327 	switch (*p)
328 	{
329 	case '\\':
330 		++p;
331 		switch (*p)
332 		{
333 		case '0': case '1': case '2': case '3':
334 		case '4': case '5': case '6': case '7':
335 			/*
336 			 * Parse an octal number.
337 			 */
338 			ch = 0;
339 			i = 0;
340 			do
341 				ch = 8*ch + (*p - '0');
342 			while (*++p >= '0' && *p <= '7' && ++i < 3);
343 			*pp = p;
344 			return (ch);
345 		case 'b':
346 			*pp = p+1;
347 			return ('\r');
348 		case 'e':
349 			*pp = p+1;
350 			return (ESC);
351 		case 'n':
352 			*pp = p+1;
353 			return ('\n');
354 		case 'r':
355 			*pp = p+1;
356 			return ('\r');
357 		case 't':
358 			*pp = p+1;
359 			return ('\t');
360 		default:
361 			/*
362 			 * Backslash followed by any other char
363 			 * just means that char.
364 			 */
365 			*pp = p+1;
366 			return (*p);
367 		}
368 	case '^':
369 		/*
370 		 * Carat means CONTROL.
371 		 */
372 		*pp = p+2;
373 		return (CONTROL(p[1]));
374 	}
375 	*pp = p+1;
376 	return (*p);
377 }
378 
379 /*
380  * Skip leading spaces in a string.
381  */
382 	public char *
383 skipsp(s)
384 	char *s;
385 {
386 	while (*s == ' ' || *s == '\t')
387 		s++;
388 	return (s);
389 }
390 
391 /*
392  * Skip non-space characters in a string.
393  */
394 	public char *
395 skipnsp(s)
396 	char *s;
397 {
398 	while (*s != '\0' && *s != ' ' && *s != '\t')
399 		s++;
400 	return (s);
401 }
402 
403 /*
404  * Clean up an input line:
405  * strip off the trailing newline & any trailing # comment.
406  */
407 	char *
408 clean_line(s)
409 	char *s;
410 {
411 	int i;
412 
413 	s = skipsp(s);
414 	for (i = 0;  s[i] != '\n' && s[i] != '\0';  i++)
415 		if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
416 			break;
417 	s[i] = '\0';
418 	return (s);
419 }
420 
421 /*
422  * Add a byte to the output command table.
423  */
424 	void
425 add_cmd_char(c)
426 	int c;
427 {
428 	if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD)
429 	{
430 		error("too many commands");
431 		exit(1);
432 	}
433 	*(currtable->pbuffer)++ = c;
434 }
435 
436 /*
437  * See if we have a special "control" line.
438  */
439 	int
440 control_line(s)
441 	char *s;
442 {
443 #define	PREFIX(str,pat)	(strncmp(str,pat,strlen(pat)-1) == 0)
444 
445 	if (PREFIX(s, "#line-edit"))
446 	{
447 		currtable = &edittable;
448 		return (1);
449 	}
450 	if (PREFIX(s, "#command"))
451 	{
452 		currtable = &cmdtable;
453 		return (1);
454 	}
455 	if (PREFIX(s, "#stop"))
456 	{
457 		add_cmd_char('\0');
458 		add_cmd_char(A_END_LIST);
459 		return (1);
460 	}
461 	return (0);
462 }
463 
464 /*
465  * Output some bytes.
466  */
467 	void
468 fputbytes(fd, buf, len)
469 	FILE *fd;
470 	char *buf;
471 	int len;
472 {
473 	while (len-- > 0)
474 	{
475 		fwrite(buf, sizeof(char), 1, fd);
476 		buf++;
477 	}
478 }
479 
480 /*
481  * Output an integer, in special KRADIX form.
482  */
483 	void
484 fputint(fd, val)
485 	FILE *fd;
486 	unsigned int val;
487 {
488 	char c;
489 
490 	if (val >= KRADIX*KRADIX)
491 	{
492 		fprintf(stderr, "error: integer too big (%d > %d)\n",
493 			val, KRADIX*KRADIX);
494 		exit(1);
495 	}
496 	c = val % KRADIX;
497 	fwrite(&c, sizeof(char), 1, fd);
498 	c = val / KRADIX;
499 	fwrite(&c, sizeof(char), 1, fd);
500 }
501 
502 /*
503  * Find an action, given the name of the action.
504  */
505 	int
506 findaction(actname)
507 	char *actname;
508 {
509 	int i;
510 
511 	for (i = 0;  currtable->names[i].cn_name != NULL;  i++)
512 		if (strcmp(currtable->names[i].cn_name, actname) == 0)
513 			return (currtable->names[i].cn_action);
514 	error("unknown action");
515 	return (A_INVALID);
516 }
517 
518 usage()
519 {
520 	fprintf(stderr, "usage: lesskey [-o output] [input]\n");
521 	exit(1);
522 }
523 
524 	void
525 error(s)
526 	char *s;
527 {
528 	fprintf(stderr, "line %d: %s\n", linenum, s);
529 	errors++;
530 }
531 
532 
533 /*
534  * Parse a line from the lesskey file.
535  */
536 	void
537 parse_line(line)
538 	char *line;
539 {
540 	char *p;
541 	int cmdlen;
542 	char *actname;
543 	int action;
544 	int c;
545 
546 	/*
547 	 * See if it is a control line.
548 	 */
549 	if (control_line(line))
550 		return;
551 	/*
552 	 * Skip leading white space.
553 	 * Replace the final newline with a null byte.
554 	 * Ignore blank lines and comments.
555 	 */
556 	p = clean_line(line);
557 	if (*p == '\0')
558 		return;
559 
560 	/*
561 	 * Parse the command string and store it in the current table.
562 	 */
563 	cmdlen = 0;
564 	do
565 	{
566 		c = tchar(&p);
567 		if (++cmdlen > MAX_CMDLEN)
568 			error("command too long");
569 		else
570 			add_cmd_char(c);
571 	} while (*p != ' ' && *p != '\t' && *p != '\0');
572 	/*
573 	 * Terminate the command string with a null byte.
574 	 */
575 	add_cmd_char('\0');
576 
577 	/*
578 	 * Skip white space between the command string
579 	 * and the action name.
580 	 * Terminate the action name with a null byte.
581 	 */
582 	p = skipsp(p);
583 	if (*p == '\0')
584 	{
585 		error("missing action");
586 		return;
587 	}
588 	actname = p;
589 	p = skipnsp(p);
590 	c = *p;
591 	*p = '\0';
592 
593 	/*
594 	 * Parse the action name and store it in the current table.
595 	 */
596 	action = findaction(actname);
597 
598 	/*
599 	 * See if an extra string follows the action name.
600 	 */
601 	*p = c;
602 	p = skipsp(p);
603 	if (*p == '\0')
604 	{
605 		add_cmd_char(action);
606 	} else
607 	{
608 		/*
609 		 * OR the special value A_EXTRA into the action byte.
610 		 * Put the extra string after the action byte.
611 		 */
612 		add_cmd_char(action | A_EXTRA);
613 		while (*p != '\0')
614 			add_cmd_char(tchar(&p));
615 		add_cmd_char('\0');
616 	}
617 }
618 
619 main(argc, argv)
620 	int argc;
621 	char *argv[];
622 {
623 	FILE *desc;
624 	FILE *out;
625 	char line[200];
626 
627 	/*
628 	 * Process command line arguments.
629 	 */
630 	parse_args(argc, argv);
631 	init_tables();
632 
633 	/*
634 	 * Open the input file.
635 	 */
636 	if (strcmp(infile, "-") == 0)
637 		desc = stdin;
638 	else if ((desc = fopen(infile, "r")) == NULL)
639 	{
640 		perror(infile);
641 		exit(1);
642 	}
643 
644 	/*
645 	 * Read and parse the input file, one line at a time.
646 	 */
647 	errors = 0;
648 	linenum = 0;
649 	while (fgets(line, sizeof(line), desc) != NULL)
650 	{
651 		++linenum;
652 		parse_line(line);
653 	}
654 
655 	/*
656 	 * Write the output file.
657 	 * If no output file was specified, use "$HOME/.less"
658 	 */
659 	if (errors > 0)
660 	{
661 		fprintf(stderr, "%d errors; no output produced\n", errors);
662 		exit(1);
663 	}
664 
665 	if (outfile == NULL)
666 		outfile = homefile(LESSKEYFILE);
667 	if ((out = fopen(outfile, "wb")) == NULL)
668 	{
669 		perror(outfile);
670 		exit(1);
671 	}
672 
673 	/* File header */
674 	fputbytes(out, fileheader, sizeof(fileheader));
675 
676 	/* Command key section */
677 	fputbytes(out, cmdsection, sizeof(cmdsection));
678 	fputint(out, cmdtable.pbuffer - cmdtable.buffer);
679 	fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
680 	/* Edit key section */
681 	fputbytes(out, editsection, sizeof(editsection));
682 	fputint(out, edittable.pbuffer - edittable.buffer);
683 	fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
684 
685 	/* File trailer */
686 	fputbytes(out, endsection, sizeof(endsection));
687 	fputbytes(out, filetrailer, sizeof(filetrailer));
688 	exit(0);
689 }
690