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