Mercurial > hg > Applications > mh
diff miscellany/less-177/command.c @ 0:bce86c4163a3
Initial revision
author | kono |
---|---|
date | Mon, 18 Apr 2005 23:46:02 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/miscellany/less-177/command.c Mon Apr 18 23:46:02 2005 +0900 @@ -0,0 +1,1192 @@ +/* + * User-level command processor. + */ + +#include "less.h" +#include "position.h" +#include "option.h" +#include "cmd.h" + +#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(); + +/* + * 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() +{ + 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() +{ + 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() +{ + 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() +{ + 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() +{ + 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() +{ + 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; + } + } +}