gno/bin/less/command.c
gdr-ftp 784e3de7cd Initial checkin of aroff, binprint, center, less, ls, make, makemake,
passwd, ps, purge, shutdown, stty, upper, and vi.  These sources are
for the versions of the utils shipped with GNO v2.0.4.
1998-03-09 08:30:21 +00:00

1204 lines
21 KiB
C

/*
* User-level command processor.
*/
#pragma noroot
#include "less.h"
#include "position.h"
#include "option.h"
#include "cmd.h"
#ifdef _ORCAC_
segment "LoadSegONE";
#endif
#define NO_MCA 0
#define MCA_DONE 1
#define MCA_MORE 2
extern int erase_char, kill_char;
extern int ispipe;
extern int sigs;
extern int quit_at_eof;
extern int hit_eof;
extern int sc_width;
extern int sc_height;
extern int swindow;
extern int jump_sline;
extern int quitting;
extern int scroll;
extern int nohelp;
extern int ignore_eoi;
extern char *every_first_cmd;
extern char version[];
extern struct scrpos initial_scrpos;
extern IFILE curr_ifile;
#if EDITOR
extern char *editor;
extern char *editproto;
#endif
extern int screen_trashed; /* The screen has been overwritten */
static char ungot[100];
static char *ungotp = NULL;
#if SHELL_ESCAPE
static char *shellcmd = NULL; /* For holding last shell command for "!!" */
#endif
static int mca; /* The multicharacter command (action) */
static int search_type; /* The previous type of search */
static int number; /* The number typed by the user */
static char optchar;
static int optflag;
#if PIPEC
static char pipec;
#endif
static void multi_search(char *pattern, int n);
static void cmd_exec(void);
static void start_mca(int action, char *prompt);
static void mca_search(void);
static void exec_mca(void);
static int mca_char(int c);
static void prompt(void);
static int getcc(void);
/*
* Move the cursor to lower left before executing a command.
* This looks nicer if the command takes a long time before
* updating the screen.
*/
static void
cmd_exec(void)
{
lower_left();
flush();
}
/*
* Set up the display to start a new multi-character command.
*/
static void
start_mca(action, prompt)
int action;
char *prompt;
{
mca = action;
lower_left();
clear_eol();
cmd_putstr(prompt);
}
/*
* Set up the display to start a new search command.
*/
static void
mca_search(void)
{
switch (SRCH_DIR(search_type))
{
case SRCH_FORW:
mca = A_F_SEARCH;
break;
case SRCH_BACK:
mca = A_B_SEARCH;
break;
}
lower_left();
clear_eol();
if (search_type & SRCH_FIRST_FILE)
cmd_putstr("@");
if (search_type & SRCH_PAST_EOF)
cmd_putstr("*");
if (search_type & SRCH_NOMATCH)
cmd_putstr("!");
switch (SRCH_DIR(search_type))
{
case SRCH_FORW:
cmd_putstr("/");
break;
case SRCH_BACK:
cmd_putstr("?");
break;
}
}
/*
* Execute a multicharacter command.
*/
static void
exec_mca(void)
{
register char *cbuf;
register char *s;
cmd_exec();
cbuf = get_cmdbuf();
switch (mca)
{
case A_F_SEARCH:
case A_B_SEARCH:
multi_search(cbuf, number);
break;
case A_FIRSTCMD:
/*
* Skip leading spaces or + signs in the string.
*/
while (*cbuf == '+' || *cbuf == ' ')
cbuf++;
if (every_first_cmd != NULL)
free(every_first_cmd);
if (*cbuf == '\0')
every_first_cmd = NULL;
else
every_first_cmd = save(cbuf);
break;
case A_OPT_TOGGLE:
toggle_option(optchar, cbuf, optflag);
optchar = '\0';
break;
case A_F_BRACKET:
match_brac(cbuf[0], cbuf[1], 1, number);
break;
case A_B_BRACKET:
match_brac(cbuf[1], cbuf[0], 0, number);
break;
case A_EXAMINE:
/*
* Ignore leading spaces and glob the filename.
*/
cbuf = skipsp(cbuf);
s = glob(cbuf);
if (s != NULL)
{
edit_list(s);
free(s);
} else
edit_list(cbuf);
break;
#if SHELL_ESCAPE
case A_SHELL:
/*
* !! just uses whatever is in shellcmd.
* Otherwise, copy cmdbuf to shellcmd,
* expanding any special characters ("%" or "#").
*/
if (*cbuf != '!')
{
if (shellcmd != NULL)
free(shellcmd);
shellcmd = fexpand(cbuf);
if (shellcmd == NULL)
break;
}
if (shellcmd == NULL)
lsystem("");
else
lsystem(shellcmd);
error("!done", NULL_PARG);
break;
#endif
#if PIPEC
case A_PIPE:
(void) pipe_mark(pipec, cbuf);
error("|done", NULL_PARG);
break;
#endif
}
}
/*
* Add a character to a multi-character command.
*/
static int
mca_char(c)
int c;
{
char *p;
int flag;
char buf[3];
switch (mca)
{
case 0:
/*
* Not in a multicharacter command.
*/
return (NO_MCA);
case A_PREFIX:
/*
* In the prefix of a command.
* This not considered a multichar command
* (even tho it uses cmdbuf, etc.).
* It is handled in the commands() switch.
*/
return (NO_MCA);
case A_DIGIT:
/*
* Entering digits of a number.
* Terminated by a non-digit.
*/
if ((c < '0' || c > '9') &&
c != erase_char && c != kill_char)
{
/*
* Not part of the number.
* Treat as a normal command character.
*/
number = cmd_int();
mca = 0;
return (NO_MCA);
}
break;
case A_OPT_TOGGLE:
/*
* Special case for the TOGGLE_OPTION command.
* If the option letter which was entered is a
* single-char option, execute the command immediately,
* so user doesn't have to hit RETURN.
* If the first char is + or -, this indicates
* OPT_UNSET or OPT_SET respectively, instead of OPT_TOGGLE.
*/
if (c == erase_char || c == kill_char)
break;
if (optchar != '\0' && optchar != '+' && optchar != '-')
/*
* We already have the option letter.
*/
break;
switch (c)
{
case '+':
optflag = OPT_UNSET;
break;
case '-':
optflag = OPT_SET;
break;
default:
optchar = c;
if (optflag != OPT_TOGGLE || single_char_option(c))
{
toggle_option(c, "", optflag);
return (MCA_DONE);
}
break;
}
if (optchar == '+' || optchar == '-')
{
optchar = c;
break;
}
/*
* Display a prompt appropriate for the option letter.
*/
if ((p = opt_prompt(c)) == NULL)
{
buf[0] = '-';
buf[1] = c;
buf[2] = '\0';
p = buf;
}
start_mca(A_OPT_TOGGLE, p);
return (MCA_MORE);
case A_F_SEARCH:
case A_B_SEARCH:
/*
* Special case for search commands.
* Certain characters as the first char of
* the pattern have special meaning:
* ! Toggle the NOMATCH flag
* * Toggle the PAST_EOF flag
* @ Toggle the FIRST_FILE flag
*/
if (len_cmdbuf() > 0)
/*
* Only works for the first char of the pattern.
*/
break;
flag = 0;
switch (c)
{
case '!':
flag = SRCH_NOMATCH;
break;
case '@':
flag = SRCH_FIRST_FILE;
break;
case '*':
flag = SRCH_PAST_EOF;
break;
}
if (flag != 0)
{
search_type ^= flag;
mca_search();
return (MCA_MORE);
}
break;
}
/*
* Any other multicharacter command
* is terminated by a newline.
*/
if (c == '\n' || c == '\r')
{
/*
* Execute the command.
*/
exec_mca();
return (MCA_DONE);
}
/*
* Append the char to the command buffer.
*/
if (cmd_char(c))
/*
* Abort the multi-char command.
*/
return (MCA_DONE);
if ((mca == A_F_BRACKET || mca == A_B_BRACKET) && len_cmdbuf() >= 2)
{
/*
* Special case for the bracket-matching commands.
* Execute the command after getting exactly two
* characters from the user.
*/
exec_mca();
return (MCA_DONE);
}
/*
* Need another character.
*/
return (MCA_MORE);
}
/*
* Display the appropriate prompt.
*/
static void
prompt(void)
{
register char *p;
if (ungotp != NULL && ungotp > ungot)
{
/*
* No prompt necessary if commands are from
* ungotten chars rather than from the user.
*/
return;
}
/*
* If nothing is displayed yet, display starting from initial_scrpos.
*/
if (empty_screen())
{
if (initial_scrpos.pos == NULL_POSITION)
/*
* {{ Maybe this should be:
* jump_loc(ch_zero(), jump_sline);
* but this behavior seems rather unexpected
* on the first screen. }}
*/
jump_loc(ch_zero(), 1);
else
jump_loc(initial_scrpos.pos, initial_scrpos.ln);
} else if (screen_trashed)
repaint();
/*
* If the -E flag is set and we've hit EOF on the last file, quit.
*/
if (quit_at_eof == 2 && hit_eof &&
next_ifile(curr_ifile) == NULL_IFILE)
quit(0);
/*
* Select the proper prompt and display it.
*/
lower_left();
clear_eol();
p = pr_string();
if (p == NULL)
putchr(':');
else
{
so_enter();
putstr(p);
so_exit();
}
#if __MSDOS__
scroll_bar();
#endif
}
/*
* Get command character.
* The character normally comes from the keyboard,
* but may come from ungotten characters
* (characters previously given to ungetcc or ungetsc).
*/
static int
getcc(void)
{
if (ungotp == NULL)
/*
* Normal case: no ungotten chars, so get one from the user.
*/
return (getchr());
if (ungotp > ungot)
/*
* Return the next ungotten char.
*/
return (*--ungotp);
/*
* We have just run out of ungotten chars.
*/
ungotp = NULL;
if (len_cmdbuf() == 0 || !empty_screen())
return (getchr());
/*
* Command is incomplete, so try to complete it.
*/
switch (mca)
{
case A_DIGIT:
/*
* We have a number but no command. Treat as #g.
*/
return ('g');
case A_F_SEARCH:
case A_B_SEARCH:
/*
* We have "/string" but no newline. Add the \n.
*/
return ('\n');
default:
/*
* Some other incomplete command. Let user complete it.
*/
return (getchr());
}
}
/*
* "Unget" a command character.
* The next getcc() will return this character.
*/
public void
ungetcc(c)
int c;
{
if (ungotp == NULL)
ungotp = ungot;
if (ungotp >= ungot + sizeof(ungot))
{
error("ungetcc overflow", NULL_PARG);
quit(1);
}
*ungotp++ = c;
}
/*
* Unget a whole string of command characters.
* The next sequence of getcc()'s will return this string.
*/
public void
ungetsc(s)
char *s;
{
register char *p;
for (p = s + strlen(s) - 1; p >= s; p--)
ungetcc(*p);
}
/*
* Search for a pattern, possibly in multiple files.
* If SRCH_FIRST_FILE is set, begin searching at the first file.
* If SRCH_PAST_EOF is set, continue the search thru multiple files.
*/
static void
multi_search(pattern, n)
char *pattern;
int n;
{
register int nomore;
char *curr_filename;
int changed_file;
changed_file = 0;
curr_filename = get_filename(curr_ifile);
if (search_type & SRCH_FIRST_FILE)
{
/*
* Start at the first (or last) file
* in the command line list.
*/
if (SRCH_DIR(search_type) == SRCH_FORW)
nomore = edit_first();
else
nomore = edit_last();
if (nomore)
return;
changed_file = 1;
search_type &= ~SRCH_FIRST_FILE;
}
for (;;)
{
if ((n = search(search_type, pattern, n)) == 0)
/*
* Found it.
*/
return;
if (n < 0)
/*
* Some kind of error in the search.
* Error message has been printed by search().
*/
break;
if ((search_type & SRCH_PAST_EOF) == 0)
/*
* We didn't find a match, but we're
* supposed to search only one file.
*/
break;
/*
* Move on to the next file.
*/
if (SRCH_DIR(search_type) == SRCH_BACK)
nomore = edit_prev(1);
else
nomore = edit_next(1);
if (nomore)
break;
changed_file = 1;
}
/*
* Didn't find it.
* Print an error message if we haven't already.
*/
if (n > 0)
error("Pattern not found", NULL_PARG);
if (changed_file)
/*
* Restore the file we were originally viewing.
*/
(void) edit(curr_filename, 0);
}
/*
* Main command processor.
* Accept and execute commands until a quit command.
*/
public void
commands(void)
{
register int c;
register int action;
register char *cbuf;
int save_search_type;
char *s;
char tbuf[2];
PARG parg;
search_type = SRCH_FORW;
scroll = (sc_height + 1) / 2;
for (;;)
{
mca = 0;
number = 0;
optchar = '\0';
/*
* See if any signals need processing.
*/
if (sigs)
{
psignals();
if (quitting)
quit(-1);
}
/*
* Display prompt and accept a character.
*/
cmd_reset();
prompt();
if (sigs)
continue;
c = getcc();
again:
if (sigs)
continue;
/*
* If we are in a multicharacter command, call mca_char.
* Otherwise we call cmd_decode to determine the
* action to be performed.
*/
if (mca)
switch (mca_char(c))
{
case MCA_MORE:
/*
* Need another character.
*/
c = getcc();
goto again;
case MCA_DONE:
/*
* Command has been handled by mca_char.
* Start clean with a prompt.
*/
continue;
case NO_MCA:
/*
* Not a multi-char command
* (at least, not anymore).
*/
break;
}
/*
* Decode the command character and decide what to do.
*/
if (mca)
{
/*
* We're in a multichar command.
* Add the character to the command buffer
* and display it on the screen.
* If the user backspaces past the start
* of the line, abort the command.
*/
if (cmd_char(c) || len_cmdbuf() == 0)
continue;
cbuf = get_cmdbuf();
} else
{
/*
* Don't use cmd_char if we're starting fresh
* at the beginning of a command, because we
* don't want to echo the command until we know
* it is a multichar command. We also don't
* want erase_char/kill_char to be treated
* as line editing characters.
*/
tbuf[0] = c;
tbuf[1] = '\0';
cbuf = tbuf;
}
s = NULL;
action = cmd_decode(cbuf, &s);
/*
* If an "extra" string was returned,
* process it as a string of command characters.
*/
if (s != NULL)
ungetsc(s);
/*
* Clear the cmdbuf string.
* (But not if we're in the prefix of a command,
* because the partial command string is kept there.)
*/
if (action != A_PREFIX)
cmd_reset();
switch (action)
{
case A_DIGIT:
/*
* First digit of a number.
*/
start_mca(A_DIGIT, ":");
goto again;
case A_F_WINDOW:
/*
* Forward one window (and set the window size).
*/
if (number > 0)
swindow = number;
/* FALLTHRU */
case A_F_SCREEN:
/*
* Forward one screen.
*/
if (number <= 0)
number = swindow;
cmd_exec();
forward(number, 0, 1);
break;
case A_B_WINDOW:
/*
* Backward one window (and set the window size).
*/
if (number > 0)
swindow = number;
/* FALLTHRU */
case A_B_SCREEN:
/*
* Backward one screen.
*/
if (number <= 0)
number = swindow;
cmd_exec();
backward(number, 0, 1);
break;
case A_F_LINE:
/*
* Forward N (default 1) line.
*/
if (number <= 0)
number = 1;
cmd_exec();
forward(number, 0, 0);
break;
case A_B_LINE:
/*
* Backward N (default 1) line.
*/
if (number <= 0)
number = 1;
cmd_exec();
backward(number, 0, 0);
break;
case A_FF_LINE:
/*
* Force forward N (default 1) line.
*/
if (number <= 0)
number = 1;
cmd_exec();
forward(number, 1, 0);
break;
case A_BF_LINE:
/*
* Force backward N (default 1) line.
*/
if (number <= 0)
number = 1;
cmd_exec();
backward(number, 1, 0);
break;
case A_F_FOREVER:
/*
* Forward forever, ignoring EOF.
*/
cmd_exec();
jump_forw();
ignore_eoi = 1;
hit_eof = 0;
while (sigs == 0)
forward(1, 0, 0);
ignore_eoi = 0;
break;
case A_F_SCROLL:
/*
* Forward N lines
* (default same as last 'd' or 'u' command).
*/
if (number > 0)
scroll = number;
cmd_exec();
forward(scroll, 0, 0);
break;
case A_B_SCROLL:
/*
* Forward N lines
* (default same as last 'd' or 'u' command).
*/
if (number > 0)
scroll = number;
cmd_exec();
backward(scroll, 0, 0);
break;
case A_FREPAINT:
/*
* Flush buffers, then repaint screen.
* Don't flush the buffers on a pipe!
*/
ch_flush();
if (!ispipe)
clr_linenum();
/* FALLTHRU */
case A_REPAINT:
/*
* Repaint screen.
*/
cmd_exec();
repaint();
break;
case A_GOLINE:
/*
* Go to line N, default beginning of file.
*/
if (number <= 0)
number = 1;
cmd_exec();
jump_back(number);
break;
case A_PERCENT:
/*
* Go to a specified percentage into the file.
*/
if (number < 0)
number = 0;
if (number > 100)
number = 100;
cmd_exec();
jump_percent(number);
break;
case A_GOEND:
/*
* Go to line N, default end of file.
*/
cmd_exec();
if (number <= 0)
jump_forw();
else
jump_back(number);
break;
case A_GOPOS:
/*
* Go to a specified byte position in the file.
*/
cmd_exec();
if (number < 0)
number = 0;
jump_line_loc((POSITION)number, jump_sline);
break;
case A_STAT:
/*
* Print file name, etc.
*/
cmd_exec();
parg.p_string = eq_message();
error("%s", &parg);
break;
case A_VERSION:
/*
* Print version number, without the "@(#)".
*/
cmd_exec();
parg.p_string = version+4;
error("%s", &parg);
break;
case A_QUIT:
/*
* Exit.
*/
quit(0);
/*
* Define abbreviation for a commonly used sequence below.
*/
#define DO_SEARCH() if (number <= 0) number = 1; \
mca_search(); \
cmd_exec(); \
multi_search((char *)NULL, number);
case A_F_SEARCH:
/*
* Search forward for a pattern.
* Get the first char of the pattern.
*/
search_type = SRCH_FORW;
if (number <= 0)
number = 1;
mca_search();
c = getcc();
goto again;
case A_B_SEARCH:
/*
* Search backward for a pattern.
* Get the first char of the pattern.
*/
search_type = SRCH_BACK;
if (number <= 0)
number = 1;
mca_search();
c = getcc();
goto again;
case A_AGAIN_SEARCH:
/*
* Repeat previous search.
*/
DO_SEARCH();
break;
case A_T_AGAIN_SEARCH:
/*
* Repeat previous search, multiple files.
*/
search_type |= SRCH_PAST_EOF;
DO_SEARCH();
break;
case A_REVERSE_SEARCH:
/*
* Repeat previous search, in reverse direction.
*/
save_search_type = search_type;
search_type = SRCH_REVERSE(search_type);
DO_SEARCH();
search_type = save_search_type;
break;
case A_T_REVERSE_SEARCH:
/*
* Repeat previous search,
* multiple files in reverse direction.
*/
save_search_type = search_type;
search_type = SRCH_REVERSE(search_type);
search_type |= SRCH_PAST_EOF;
DO_SEARCH();
search_type = save_search_type;
break;
case A_HELP:
/*
* Help.
*/
if (nohelp)
{
bell();
break;
}
lower_left();
clear_eol();
putstr("help");
cmd_exec();
help();
break;
case A_EXAMINE:
/*
* Edit a new file. Get the filename.
*/
start_mca(A_EXAMINE, "Examine: ");
c = getcc();
goto again;
case A_VISUAL:
/*
* Invoke an editor on the input file.
*/
#if EDITOR
if (strcmp(get_filename(curr_ifile), "-") == 0)
{
error("Cannot edit standard input", NULL_PARG);
break;
}
/*
* Expand the editor prototype string
* and pass it to the system to execute.
*/
cmd_exec();
lsystem(pr_expand(editproto, 0));
/*
* Re-edit the file, since data may have changed.
* Some editors even recreate the file, so flushing
* buffers is not sufficient.
*/
(void) edit(get_filename(curr_ifile), 0);
break;
#else
error("Command not available", NULL_PARG);
break;
#endif
case A_NEXT_FILE:
/*
* Examine next file.
*/
if (number <= 0)
number = 1;
if (edit_next(number))
{
if (quit_at_eof && hit_eof)
quit(0);
parg.p_string = (number > 1) ? "(N-th) " : "";
error("No %snext file", &parg);
}
break;
case A_PREV_FILE:
/*
* Examine previous file.
*/
if (number <= 0)
number = 1;
if (edit_prev(number))
{
parg.p_string = (number > 1) ? "(N-th) " : "";
error("No %sprevious file", &parg);
}
break;
case A_INDEX_FILE:
/*
* Examine a particular file.
*/
if (number <= 0)
number = 1;
if (edit_index(number))
error("No such file", NULL_PARG);
break;
case A_OPT_TOGGLE:
start_mca(A_OPT_TOGGLE, "-");
optflag = OPT_TOGGLE;
c = getcc();
goto again;
case A_DISP_OPTION:
/*
* Report a flag setting.
*/
start_mca(A_DISP_OPTION, "_");
c = getcc();
if (c == erase_char || c == kill_char)
break;
toggle_option(c, "", OPT_NO_TOGGLE);
break;
case A_FIRSTCMD:
/*
* Set an initial command for new files.
*/
start_mca(A_FIRSTCMD, "+");
c = getcc();
goto again;
case A_SHELL:
/*
* Shell escape.
*/
#if SHELL_ESCAPE
start_mca(A_SHELL, "!");
c = getcc();
goto again;
#else
error("Command not available", NULL_PARG);
break;
#endif
case A_SETMARK:
/*
* Set a mark.
*/
start_mca(A_SETMARK, "mark: ");
c = getcc();
if (c == erase_char || c == kill_char ||
c == '\n' || c == '\r')
break;
setmark(c);
break;
case A_GOMARK:
/*
* Go to a mark.
*/
start_mca(A_GOMARK, "goto mark: ");
c = getcc();
if (c == erase_char || c == kill_char ||
c == '\n' || c == '\r')
break;
gomark(c);
break;
#if PIPEC
case A_PIPE:
start_mca(A_PIPE, "|mark: ");
c = getcc();
if (c == erase_char || c == kill_char)
break;
if (c == '\n' || c == '\r')
c = '.';
if (badmark(c))
break;
pipec = c;
start_mca(A_PIPE, "!");
c = getcc();
goto again;
#endif
case A_B_BRACKET:
case A_F_BRACKET:
start_mca(action, "Brackets: ");
c = getcc();
goto again;
case A_PREFIX:
/*
* The command is incomplete (more chars are needed).
* Display the current char, so the user knows
* what's going on, and get another character.
*/
if (mca != A_PREFIX)
{
start_mca(A_PREFIX, " ");
cmd_reset();
(void) cmd_char(c);
}
c = getcc();
goto again;
case A_NOACTION:
break;
default:
bell();
break;
}
}
}