gno/bin/less/lesskey.c

362 lines
7.6 KiB
C

/*
* lesskey [-o output] [input]
*
* Make a .less file.
* If no input file is specified, standard input is used.
* If no output file is specified, $HOME/.less is used.
*
* The .less file is used to specify (to "less") user-defined
* key bindings. Basically any sequence of 1 to MAX_CMDLEN
* keystrokes may be bound to an existing less function.
*
* The input file is an ascii file consisting of a
* sequence of lines of the form:
* string <whitespace> action [chars] <newline>
*
* "string" is a sequence of command characters which form
* the new user-defined command. The command
* characters may be:
* 1. The actual character itself.
* 2. A character preceded by ^ to specify a
* control character (e.g. ^X means control-X).
* 3. Any character (other than an octal digit) preceded by
* a \ to specify the character itself (characters which
* must be preceded by \ include ^, \, and whitespace.
* 4. A backslash followed by one to three octal digits
* to specify a character by its octal value.
* "action" is the name of a "less" action, from the table below.
* "chars" is an optional sequence of characters which is treated
* as keyboard input after the command is executed.
*
* Blank lines and lines which start with # are ignored.
*
*
* The output file is a non-ascii file, consisting of
* zero or more byte sequences of the form:
* string <0> <action>
* or
* string <0> <action|A_EXTRA> chars <0>
*
* "string" is the command string.
* "<0>" is one null byte.
* "<action>" is one byte containing the action code (the A_xxx value).
* If action is ORed with A_EXTRA, the action byte is followed
* by the null-terminated "chars" string.
*/
#include <stdio.h>
#include <string.h>
#include "less.h"
#include "cmd.h"
char usertable[MAX_USERCMD];
struct cmdname
{
char *cn_name;
int cn_action;
} cmdnames[] =
{
"back-bracket", A_B_BRACKET,
"back-line", A_B_LINE,
"back-line-force", A_BF_LINE,
"back-screen", A_B_SCREEN,
"back-scroll", A_B_SCROLL,
"back-search", A_B_SEARCH,
"back-window", A_B_WINDOW,
"debug", A_DEBUG,
"display-flag", A_DISP_OPTION,
"display-option", A_DISP_OPTION,
"end", A_GOEND,
"examine", A_EXAMINE,
"first-cmd", A_FIRSTCMD,
"firstcmd", A_FIRSTCMD,
"flush-repaint", A_FREPAINT,
"forw-bracket", A_F_BRACKET,
"forw-forever", A_F_FOREVER,
"forw-line", A_F_LINE,
"forw-line-force", A_FF_LINE,
"forw-screen", A_F_SCREEN,
"forw-scroll", A_F_SCROLL,
"forw-search", A_F_SEARCH,
"forw-window", A_F_WINDOW,
"goto-end", A_GOEND,
"goto-line", A_GOLINE,
"goto-mark", A_GOMARK,
"help", A_HELP,
"index-file", A_INDEX_FILE,
"invalid", A_UINVALID,
"next-file", A_NEXT_FILE,
"noaction", A_NOACTION,
"percent", A_PERCENT,
"pipe", A_PIPE,
"prev-file", A_PREV_FILE,
"quit", A_QUIT,
"repaint", A_REPAINT,
"repaint-flush", A_FREPAINT,
"repeat-search", A_AGAIN_SEARCH,
"repeat-search-all", A_T_AGAIN_SEARCH,
"reverse-search", A_REVERSE_SEARCH,
"reverse-search-all", A_T_REVERSE_SEARCH,
"set-mark", A_SETMARK,
"shell", A_SHELL,
"status", A_STAT,
"toggle-flag", A_OPT_TOGGLE,
"toggle-option", A_OPT_TOGGLE,
"version", A_VERSION,
"visual", A_VISUAL,
NULL, 0
};
main(argc, argv)
int argc;
char *argv[];
{
char *p; /* {{ Can't be register since we use &p }} */
register char *up; /* Pointer into usertable */
FILE *desc; /* Description file (input) */
FILE *out; /* Output file */
int linenum; /* Line number in input file */
char *currcmd; /* Start of current command string */
int errors;
int i, j;
char line[200];
char *outfile;
extern char *getenv();
/*
* Process command line arguments.
*/
outfile = NULL;
while (--argc > 0 && **(++argv) == '-')
{
switch (argv[0][1])
{
case 'o':
outfile = &argv[0][2];
if (*outfile == '\0')
{
if (--argc <= 0)
usage();
outfile = *(++argv);
}
break;
default:
usage();
}
}
if (argc > 1)
usage();
/*
* Open the input file, or use standard input if none specified.
*/
if (argc > 0)
{
if ((desc = fopen(*argv, "r")) == NULL)
{
perror(*argv);
exit(1);
}
} else
desc = stdin;
/*
* Read the input file, one line at a time.
* Each line consists of a command string,
* followed by white space, followed by an action name.
*/
linenum = 0;
errors = 0;
up = usertable;
while (fgets(line, sizeof(line), desc) != NULL)
{
++linenum;
/*
* Skip leading white space.
* Replace the final newline with a null byte.
* Ignore blank lines and comment lines.
*/
p = line;
while (*p == ' ' || *p == '\t')
++p;
for (i = 0; p[i] != '\n' && p[i] != '\0'; i++)
;
p[i] = '\0';
if (*p == '#' || *p == '\0')
continue;
/*
* Parse the command string and store it in the usertable.
*/
currcmd = up;
do
{
if (up >= usertable + MAX_USERCMD)
{
fprintf(stderr, "too many commands, line %d\n",
linenum);
exit(1);
}
if (up >= currcmd + MAX_CMDLEN)
{
fprintf(stderr, "command too long on line %d\n",
linenum);
errors++;
break;
}
*up++ = tchar(&p);
} while (*p != ' ' && *p != '\t' && *p != '\0');
/*
* Terminate the command string with a null byte.
*/
*up++ = '\0';
/*
* Skip white space between the command string
* and the action name.
* Terminate the action name with a null byte if it
* is followed by whitespace or a # comment.
*/
if (*p == '\0')
{
fprintf(stderr, "missing whitespace on line %d\n",
linenum);
errors++;
continue;
}
while (*p == ' ' || *p == '\t')
++p;
for (j = 0; p[j] != ' ' && p[j] != '\t' &&
p[j] != '#' && p[j] != '\0'; j++)
;
p[j] = '\0';
/*
* Parse the action name and store it in the usertable.
*/
for (i = 0; cmdnames[i].cn_name != NULL; i++)
if (strcmp(cmdnames[i].cn_name, p) == 0)
break;
if (cmdnames[i].cn_name == NULL)
{
fprintf(stderr, "unknown action <%s> on line %d\n",
p, linenum);
errors++;
continue;
}
*up++ = cmdnames[i].cn_action;
/*
* See if an extra string follows the action name.
*/
for (j = j+1; p[j] == ' ' || p[j] == '\t'; j++)
;
p += j;
if (*p != '\0')
{
/*
* OR the special value A_EXTRA into the action byte.
* Put the extra string after the action byte.
*/
up[-1] |= A_EXTRA;
while (*p != '\0')
*up++ = tchar(&p);
*up++ = '\0';
}
}
if (errors > 0)
{
fprintf(stderr, "%d errors; no output produced\n", errors);
exit(1);
}
/*
* Write the output file.
* If no output file was specified, use "$HOME/.less"
*/
if (outfile == NULL)
{
p = getenv("HOME");
if (p == NULL || *p == '\0')
{
fprintf(stderr, "cannot find $HOME - using current directory\n");
#if __MSDOS__
strcpy(line, "_less");
#else
strcpy(line, "lessrc");
#endif
} else
{
strcpy(line, p);
#if __MSDOS__
strcat(line, "\\_less");
#else
strcat(line, "/lessrc");
#endif
}
outfile = line;
}
if ((out = fopen(outfile, "w")) == NULL)
perror(outfile);
else
fwrite((char *)usertable, 1, up-usertable, out);
exit(0);
}
/*
* Parse one character of a string.
*/
tchar(pp)
char **pp;
{
register char *p;
register char ch;
register int i;
p = *pp;
switch (*p)
{
case '\\':
if (*++p >= '0' && *p <= '7')
{
/*
* Parse an octal number.
*/
ch = 0;
i = 0;
do
ch = 8*ch + (*p - '0');
while (*++p >= '0' && *p <= '7' && ++i < 3);
*pp = p;
return (ch);
}
/*
* Backslash followed by a char just means that char.
*/
*pp = p+1;
return (*p);
case '^':
/*
* Carat means CONTROL.
*/
*pp = p+2;
return (CONTROL(p[1]));
}
*pp = p+1;
return (*p);
}
usage()
{
fprintf(stderr, "usage: lesskey [-o output] [input]\n");
exit(1);
}