This is input.c in view mode; [Download] [Up]
/* INPUT: Various input routines for vile * written by Daniel Lawrence 5/9/86 * variously munged/massaged/relayered/slashed/burned * since then. -pgf * * TTgetc() raw 8-bit key from terminal driver. * * sysmapped_c() single "keystroke" -- may have SPEC bit, if it was * a sytem-mapped function key. calls TTgetc(). these * system-mapped keys will never map to a multi-char * sequence. the routine does have storage, to hold * keystrokes gathered "in error". * * tgetc() fresh, pushed back, or recorded output of result of * sysmapped_c() (i.e. dotcmd and the keyboard macros * are recordedand played back at this level). this is * only called from mapgetc() in map.c * * mapped_c() (map.c) worker routine which will return a mapped * or non-mapped character from the mapping engine. * determines correct map, and uses its own pushback * buffers on top of calls to tgetc() (see mapgetc/ * mapungetc). * * mapped_keystroke() applies user-specified maps to user's input. * correct map used depending on mode (insert, command, * message-line). * * keystroke() returns pushback from mappings, the results of * previous calls to mapped_keystroke(). * * keystroke8() as above, but masks off any "wideness", i.e. SPEC bits. * * keystroke_raw() as above, but recording is forced even if * sysmapped_c() returns intrc. (old "tgetc(TRUE)") * * kbd_seq() the vile prefix keys (^X,^A,#) are checked for, and * appropriate key pairs are turned into CTLA|c, CTLX|c, * SPEC|c. * * * TTtypahead() true if a key is avail from TTgetc(). * sysmapped_c_avail() " if a key is avail from sysmapped_c() or below. * tgetc_avail() true if a key is avail from tgetc() or below. * keystroke_avail() true if a key is avail from keystroke() or below. * * $Header: /home/tom/src/vile/RCS/input.c,v 1.158 1997/02/09 19:47:13 tom Exp $ * */ #include "estruct.h" #include "edef.h" #define DEFAULT_REG -1 #define INFINITE_LOOP_COUNT 1200 typedef struct _kstack { struct _kstack *m_link; int m_save; /* old value of 'kbdmode' */ int m_indx; /* index identifying this macro */ int m_rept; /* the number of times to execute the macro */ ITBUFF *m_kbdm; /* the macro-text to execute */ ITBUFF *m_dots; /* workspace for "." command */ #ifdef GMDDOTMACRO ITBUFF *m_DOTS; /* save-area for "." command */ int m_RPT0; /* saves 'dotcmdcnt' */ int m_RPT1; /* saves 'dotcmdrep' */ #endif } KSTACK; /*--------------------------------------------------------------------------*/ static void finish_kbm (void); static KSTACK *KbdStack; /* keyboard/@-macros that are replaying */ static ITBUFF *KbdMacro; /* keyboard macro, recorded */ static int last_eolchar; /* records last eolchar-match in 'kbd_string' */ /*--------------------------------------------------------------------------*/ /* * Returns a pointer to the buffer that we use for saving text to replay with * the "." command. */ static ITBUFF * TempDot(int init) { static ITBUFF *tmpcmd; /* dot commands, 'til we're sure */ if (kbdmode == PLAY) { if (init) (void)itb_init(&(KbdStack->m_dots), abortc); return KbdStack->m_dots; } if (init || (tmpcmd == 0)) (void)itb_init(&tmpcmd, abortc); return tmpcmd; } /* * Dummy function to use when 'kbd_string()' does not handle automatic completion */ /*ARGSUSED*/ int no_completion(int c, char *buf, int *pos) { return FALSE; } /* * Complete names in a shell command using the filename-completion. We'll have * to scan back to the beginning of the appropriate name since that module * expects only one name in a buffer. * * We only do shell-completion if filename-completion is configured. */ #if COMPLETE_FILES int shell_complete( int c, char *buf, int *pos) { int status; int len = *pos; int base; int first = 0; TRACE(("shell_complete %d:'%s'\n", *pos, buf)) if (isShellOrPipe(buf)) first++; for (base = len; base > first; ) { base--; if (isspace(buf[base])) { base++; break; } else if (buf[base] == '$') { break; } } len -= base; status = path_completion(c, buf+base, &len); *pos = len + base; return status; } #endif /* * Ask a yes or no question in the message line. Return either TRUE, FALSE, or * ABORT. The ABORT status is returned if the user bumps out of the question * with an abortc. Used any time a confirmation is required. */ int mlyesno(const char *prompt) { char c; /* input character */ /* in case this is right after a shell escape */ if (!update(TRUE)) return (ABORT); for_ever { mlforce("%s [y/n]? ",prompt); c = (char)keystroke(); /* get the response */ if (ABORTED(c)) /* Bail out! */ return(ABORT); if (c=='y' || c=='Y') return(TRUE); if (c=='n' || c=='N') return(FALSE); } } /* * Ask a simple question in the message line. Return the single char response, * if it was one of the valid responses. */ int mlquickask(const char *prompt, const char *respchars, int *cp) { if (!update(TRUE)) return (ABORT); for_ever { mlforce("%s ",prompt); *cp = keystroke(); /* get the response */ if (ABORTED(*cp)) /* Bail out! */ return(ABORT); if (strchr(respchars,*cp)) return TRUE; kbd_alarm(); } } /* * Prompt for a named-buffer (i.e., "register") */ int mlreply_reg( const char *prompt, char *cbuf, /* 2-char buffer for register+eol */ int *retp, /* => the register-name */ int at_dft) /* default-value (e.g., for "@@" command) */ { register int status; register int c; if (clexec || isnamedcmd) { if ((status = mlreply(prompt, cbuf, 2)) != TRUE) return status; c = cbuf[0]; } else { c = keystroke(); if (ABORTED(c)) return ABORT; } if (c == '@' && at_dft != -1) { c = at_dft; } else if (reg2index(c) < 0) { mlwarn("[Invalid register name]"); return FALSE; } *retp = c; return TRUE; } /* * Prompt for a register-name and/or line-count (e.g., for the ":yank" and * ":put" commands). The register-name, if given, is first. */ int mlreply_reg_count( int state, /* negative=register, positive=count, zero=either */ int *retp, /* returns the register-index or line-count */ int *next) /* returns 0/1=register, 2=count */ { register int status; char prompt[80]; char expect[80]; char buffer[10]; int length; *expect = EOS; if (state <= 0) (void)strcat(expect, " register"); if (state == 0) (void)strcat(expect, " or"); if (state >= 0) { (void)strcat(expect, " line-count"); length = sizeof(buffer); } else length = 2; (void)lsprintf(prompt, "Specify%s: ", expect); *buffer = EOS; status = kbd_string(prompt, buffer, length, ' ', 0, no_completion); if (status == TRUE) { if (state <= 0 && isalpha(buffer[0]) && buffer[1] == EOS && (*retp = reg2index(*buffer)) >= 0) { *next = isupper(*buffer) ? 1 : 0; } else if (state >= 0 && string_to_number(buffer, retp) && *retp) { *next = 2; } else { mlforce("[Expected%s]", expect); kbd_alarm(); status = ABORT; } } return status; } /* * Write a prompt into the message line, then read back a response. Keep * track of the physical position of the cursor. If we are in a keyboard * macro throw the prompt away, and return the remembered response. This * lets macros run at full speed. The reply is always terminated by a carriage * return. Handle erase, kill, and abort keys. */ int mlreply(const char *prompt, char *buf, int bufn) { return kbd_string(prompt, buf, bufn, '\n', KBD_NORMAL, no_completion); } /* as above, but don't do anything to backslashes */ int mlreply_no_bs(const char *prompt, char *buf, int bufn) { #if COMPLETE_FILES init_filec(FILECOMPLETION_BufName); #endif return kbd_string(prompt, buf, bufn, '\n', KBD_EXPAND|KBD_SHPIPE, shell_complete); } /* as above, but neither expand nor do anything to backslashes */ int mlreply_no_opts(const char *prompt, char *buf, int bufn) { return kbd_string(prompt, buf, bufn, '\n', 0, no_completion); } /* the numbered buffer names increment each time they are referenced */ void incr_dot_kregnum(void) { if (dotcmdmode == PLAY) { register int c = itb_peek(dotcmd); if (isdigit(c) && c < '9') itb_stuff(dotcmd, ++c); } } /* * Record a character for "." commands */ static void record_dot_char(int c) { if (dotcmdmode == RECORD) { ITBUFF *tmp = TempDot(FALSE); (void)itb_append(&tmp, c); } } /* * Record a character for kbd-macros */ static void record_kbd_char(int c) { if (dotcmdmode != PLAY && kbdmode == RECORD) (void)itb_append(&KbdMacro, c); } /* if we should preserve this input, do so */ static void record_char(int c) { record_dot_char(c); record_kbd_char(c); } /* get the next character of a replayed '.' or macro */ int get_recorded_char( int eatit) /* consume the character? */ { register int c = -1; register ITBUFF *buffer; if (dotcmdmode == PLAY) { if (interrupted()) { dotcmdmode = STOP; return intrc; } else { if (!itb_more(buffer = dotcmd)) { if (!eatit) { if (dotcmdrep > 1) return itb_get(buffer, 0); } else { /* at the end of last repetition? */ if (--dotcmdrep < 1) { dotcmdmode = STOP; (void)dotcmdbegin(); /* immediately start recording * again, just in case. */ } else { /* reset the macro to the * beginning for the next rep. */ itb_first(buffer); } } } /* if there is some left... */ if (itb_more(buffer)) { if (eatit) c = itb_next(buffer); else c = itb_peek(buffer); return c; } } } if (kbdmode == PLAY) { /* if we are playing a keyboard macro back, */ if (interrupted()) { while (kbdmode == PLAY) finish_kbm(); return intrc; } else { if (!itb_more(buffer = KbdStack->m_kbdm)) { if (--(KbdStack->m_rept) >= 1) itb_first(buffer); else finish_kbm(); } if (kbdmode == PLAY) { buffer = KbdStack->m_kbdm; if (eatit) record_dot_char(c = itb_next(buffer)); else c = itb_peek(buffer); } } } return c; } void unkeystroke(int c) { mapungetc(c|NOREMAP); } int mapped_keystroke(void) { return lastkey = mapped_c(DOMAP,NOQUOTED); } int keystroke(void) { return lastkey = mapped_c(NODOMAP,NOQUOTED); } int keystroke8(void) { int c; for_ever { c = mapped_c(NODOMAP,NOQUOTED); if ((c & ~0xff) == 0) return lastkey = c; kbd_alarm(); } } int keystroke_raw8(void) { int c; for_ever { c = mapped_c(NODOMAP,QUOTED); if ((c & ~0xff) == 0) return lastkey = c; kbd_alarm(); } } static int mapped_keystroke_raw(void) { return lastkey = mapped_c(DOMAP,QUOTED); } int keystroke_avail(void) { return mapped_c_avail(); } int tgetc_avail(void) { return get_recorded_char(FALSE) != -1 || sysmapped_c_avail(); } int tgetc(int quoted) { register int c; /* fetched character */ if ((c = get_recorded_char(TRUE)) == -1) { /* fetch a character from the terminal driver */ not_interrupted(); if (setjmp(read_jmp_buf)) { c = kcod2key(intrc); TRACE(("setjmp/getc:%c (%#x)\n", c, c)) #if defined(linux) && defined(DISP_TERMCAP) /* * Linux bug (observed with kernels 1.2.13 & 2.0.0): * when interrupted, the _next_ character that * getchar() returns will be the same as the last * character before the interrupt. Eat it. */ (void)ttgetc(); #endif } else { doing_kbd_read = TRUE; do { /* if it's sysV style signals, we want to try again, since this must not have been SIGINT, but was probably SIGWINCH */ c = sysmapped_c(); } while (c == -1); } doing_kbd_read = FALSE; if (quoted || (c != kcod2key(intrc))) record_char(c); } /* and finally give the char back */ return c; } /* KBD_SEQ: Get a command sequence (multiple keystrokes) from the keyboard. Process all applicable prefix keys. Set lastcmd for commands which want stuttering. */ int kbd_seq(void) { int c; /* fetched keystroke */ int prefix = 0; /* accumulate prefix */ c = mapped_keystroke(); if (c == cntl_a) { prefix = CTLA; c = keystroke(); } else if (c == cntl_x) { prefix = CTLX; c = keystroke(); } else if (c == poundc) { prefix = SPEC; c = keystroke(); } c |= prefix; /* otherwise, just return it */ return (lastcmd = c); } /* get a string consisting of inclchartype characters from the current position. if inclchartype is 0, return everything to eol */ int screen_string (char *buf, int bufn, CHARTYPE inclchartype) { register int i = 0; MARK mk; mk = DOT; while ( i < (bufn-1) && !is_at_end_of_line(DOT)) { buf[i] = char_at(DOT); #if OPT_WIDE_CTYPES if (i == 0) { if (inclchartype & _scrtch) { if (buf[0] != SCRTCH_LEFT[0]) inclchartype &= ~_scrtch; } if (inclchartype & _shpipe) { if (buf[0] != SHPIPE_LEFT[0]) inclchartype &= ~_shpipe; } } /* allow "[!command]" */ if ((inclchartype & _scrtch) && (i == 1) && (buf[1] == SHPIPE_LEFT[0])) { ; /* guard against things like "[Buffer List]" on VMS */ } else if ((inclchartype & _pathn) && !ispath(buf[i]) && (inclchartype == _pathn)) { break; } else #endif if (inclchartype && !istype(inclchartype, buf[i])) break; DOT.o++; i++; #if OPT_WIDE_CTYPES if (inclchartype & _scrtch) { if ((i < bufn) && (inclchartype & _pathn) && ispath(char_at(DOT))) continue; if (buf[i-1] == SCRTCH_RIGHT[0]) break; } #endif } #if OPT_WIDE_CTYPES #if OPT_VMS_PATH if (inclchartype & _pathn) { ; /* override conflict with "[]" */ } else #endif if (inclchartype & _scrtch) { if (buf[i-1] != SCRTCH_RIGHT[0]) i = 0; } #endif buf[i] = EOS; DOT = mk; return buf[0] != EOS; } /* * Returns the character that ended the last call on 'kbd_string()' */ int end_string(void) { return last_eolchar; } void set_end_string(int c) { last_eolchar = c; } /* * Returns an appropriate delimiter for /-commands, based on the end of the * last reply. That is, in a command such as * * :s/first/last/ * * we will get prompts for * * :s/ /-delimiter saved in 'end_string()' * first/ * last/ * * If a newline is used at any stage, subsequent delimiters are forced to a * newline. */ int kbd_delimiter(void) { register int c = '\n'; if (isnamedcmd) { register int d = end_string(); if (ispunct(d)) c = d; } return c; } /* turn \X into X */ static void remove_backslashes(char *s) { register char *cp; while (*s) { if (*s == '\\') { /* shift left */ for (cp = s; *cp; cp++) *cp = *(cp+1); } s++; } } /* count backslashes so we can tell at any point whether we have the current * position escaped by one. */ static int countBackSlashes(char * buffer, int len) { register int count; if (len && buffer[len-1] == '\\') { count = 1; while (count+1 <= len && buffer[len-1-count] == '\\') count++; } else { count = 0; } return count; } static void showChar(int c) { if (disinp) { int save_expand = kbd_expand; kbd_expand = 1; /* show all controls */ kbd_putc(c); kbd_expand = save_expand; } } static void show1Char(int c) { if (disinp) { showChar(c); TTflush(); } } static void eraseChar(int c) { if (disinp) { kbd_erase(); if (!isprint(c)) { kbd_erase(); /* e.g. back up over ^H */ if (c & HIGHBIT) { kbd_erase(); /* e.g. back up over \200 */ kbd_erase(); } } } } /* * Macro result is true if the buffer is a normal :-command with a leading "!", * or is invoked from one of the places that supplies an implicit "!" for shell * commands. */ #define editingShellCmd(buf,options) \ (((options & KBD_EXPCMD) && isShellOrPipe(buf)) \ || (options & KBD_SHPIPE)) /* expand a single character (only used on interactive input) */ static int expandChar( char * buf, int bufn, int * position, int c, int options) { register int cpos = *position; register char * cp; register BUFFER *bp; char str[NFILEN]; int shell = editingShellCmd(buf,options); int expand = ((options & KBD_EXPAND) || shell); int exppat = (options & KBD_EXPPAT); /* Are we allowed to expand anything? */ if (!(expand || exppat || shell)) return FALSE; /* Is this a character that we know about? */ if (strchr(global_g_val_ptr(GVAL_EXPAND_CHARS),c) == 0) return FALSE; switch (c) { case EXPC_THIS: case EXPC_THAT: if (!expand) return FALSE; bp = (c == EXPC_THIS) ? curbp : find_alt(); if (bp == 0 || b_is_invisible(bp)) { kbd_alarm(); /* complain a little */ return FALSE; /* ...and let the user type it as-is */ } cp = bp->b_fname; if (isInternalName(cp)) { cp = bp->b_bname; } else if (!global_g_val(GMDEXPAND_PATH)) { cp = shorten_path(strcpy(str, cp), FALSE); #if OPT_MSDOS_PATH /* always use backslashes if invoking external prog */ if (shell) cp = SL_TO_BSL(cp); #endif } break; case EXPC_TOKEN: if (!expand) return FALSE; if (screen_string(str, sizeof(str), _pathn)) cp = str; else cp = NULL; break; case EXPC_RPAT: if (!exppat) return FALSE; if (rpat[0]) cp = rpat; else cp = NULL; break; case EXPC_SHELL: if (!shell) return FALSE; #ifdef only_expand_first_bang /* without this check, do as vi does -- expand '!' to previous command anywhere it's typed */ if (cpos > (buf[0] == '!')) return FALSE; #endif cp = tb_values(save_shell[!isShellOrPipe(buf)]); if (cp != NULL && isShellOrPipe(cp)) cp++; /* skip the '!' */ break; default: return FALSE; } if (cp != NULL) { while (cpos < bufn-1 && ((c = *cp++) != EOS)) { buf[cpos++] = (char)c; showChar(c); } buf[cpos] = EOS; TTflush(); } *position = cpos; return TRUE; } /* * Returns true for the (presumably control-chars) that we use for line edits */ int is_edit_char(int c) { return (isreturn(c) || isbackspace(c) || (c == wkillc) || (c == killc)); } /* * Erases the response from the screen for 'kbd_string()' */ void kbd_kill_response(char * buf, int * position, int c) { register int cpos = *position; while (cpos > 0) { cpos--; eraseChar(buf[cpos]); if (c == wkillc) { if (!isspace(buf[cpos])) { if (cpos > 0 && isspace(buf[cpos-1])) break; } } if (c != killc && c != wkillc) break; } if (disinp) TTflush(); buf[*position = cpos] = EOS; } /* * Display the default response for 'kbd_string()', escaping backslashes if * necessary. * * patch: make 'dst[]' a TBUFF so we don't have to worry about overflow. */ int kbd_show_response( char *dst, /* string with escapes */ char *src, /* string w/o escapes */ int bufn, /* maximum # of chars we read from 'src[]' */ int eolchar, int options) { register int c, j, k; /* add backslash escapes in front of volatile characters */ for (j = k = 0; k < bufn; k++) { if ((c = src[k]) == EOS) break; if ((c == '\\') || (c == eolchar && eolchar != '\n')) { if (options & KBD_QUOTES) dst[j++] = '\\'; /* add extra */ } else if (strchr(global_g_val_ptr(GVAL_EXPAND_CHARS),c) != 0) { if (c == EXPC_RPAT && !(options & KBD_EXPPAT)) ; else if ((options & KBD_QUOTES) && (options & KBD_EXPAND)) dst[j++] = '\\'; /* add extra */ } dst[j++] = (char)c; } dst[j] = EOS; /* put out the default response, which is in the buffer */ j = 0; kbd_init(); while ((c = dst[j]) != EOS && j < NLINE-1) { showChar(c); ++j; } if (disinp) TTflush(); return j; } /* default function for 'edithistory()' */ static int /*ARGSUSED*/ eol_history(char * buffer, int cpos, int c, int eolchar) { if (isprint(eolchar)) { if (c == eolchar) return TRUE; } return FALSE; } /* * Store a one-level push-back of the shell command text. This allows simple * prompt/substitution of shell commands, while keeping the "!" and text * separate for the command decoder. */ static int pushed_back; static int pushback_flg; static const char * pushback_ptr; void kbd_pushback(char *buffer, int skip) { static TBUFF *PushBack; if (macroize(&PushBack, buffer+1, buffer)) { pushed_back = TRUE; pushback_flg = clexec; pushback_ptr = execstr; clexec = TRUE; execstr = tb_values(PushBack); buffer[skip] = EOS; } } int kbd_is_pushed_back(void) { return clexec && pushed_back; } /* A more generalized prompt/reply function allowing the caller to specify a terminator other than '\n'. Both are accepted. Assumes the buffer already contains a valid (possibly null) string to use as the default response. */ int kbd_string( const char *prompt, /* put this out first */ char *extbuf, /* the caller's (possibly full) buffer */ int bufn, /* the length of " */ int eolchar, /* char we can terminate on, in addition to '\n' */ int options, /* KBD_EXPAND/KBD_QUOTES, etc. */ int (*complete)(int,char *,int *)) /* handles completion */ { return kbd_reply(prompt, extbuf, bufn, eol_history, eolchar, options, complete); } /* * Same as 'kbd_string()', except for adding the 'endfunc' parameter. * * Returns: * ABORT - abort character given (usually ESC) * SORTOFTRUE - backspace from empty-buffer * TRUE - buffer is not empty * FALSE - buffer is empty */ int kbd_reply( const char *prompt, /* put this out first */ char *extbuf, /* the caller's (possibly full) buffer */ int bufn, /* the length of " */ int (*endfunc)(EOL_ARGS), /* parsing with 'eolchar' delimiter */ int eolchar, /* char we can terminate on, in addition to '\n' */ int options, /* KBD_EXPAND/KBD_QUOTES */ int (*complete)(DONE_ARGS)) /* handles completion */ { int c; int done; int cpos; /* current character position in string */ int status; int shell; register int quotef; /* are we quoting the next char? */ register int backslashes; /* are we quoting the next expandable char? */ int dontmap = (options & KBD_NOMAP); int firstch = TRUE; int newpos; char buf[NLINE]; last_eolchar = EOS; /* ...in case we don't set it elsewhere */ if (clexec) { execstr = token(execstr, extbuf, eolchar); status = (*extbuf != EOS); if (status) { /* i.e. we got some input */ #if !SMALLER if ((options & KBD_LOWERC)) (void)mklower(extbuf); else if ((options & KBD_UPPERC)) (void)mkupper(extbuf); #endif if (!(options & KBD_NOEVAL)) { (void)strncpy(extbuf, tokval(strcpy(buf, extbuf)), (SIZE_T)bufn); } if (complete != no_completion && !editingShellCmd(extbuf,options)) { cpos = newpos = strlen(extbuf); if (!(*complete)(NAMEC, extbuf, &newpos)) extbuf[cpos] = EOS; } /* * Splice for multi-part command */ if ((last_eolchar = *execstr) != EOS) last_eolchar = ' '; extbuf[bufn-1] = EOS; } if (pushed_back && (*execstr == EOS)) { pushed_back = FALSE; clexec = pushback_flg; execstr = pushback_ptr; #if OPT_HISTORY if (!pushback_flg) hst_append(extbuf,EOS); #endif } return status; } quotef = FALSE; reading_msg_line = TRUE; /* prompt the user for the input string */ if (prompt != 0) mlprompt("%s", prompt); if (bufn > sizeof(buf)) bufn = sizeof(buf); cpos = kbd_show_response(buf, extbuf, bufn, eolchar, options); backslashes = 0; /* by definition, there is an even number of backslashes */ for_ever { int EscOrQuo = ((quotef == TRUE) || ((backslashes & 1) != 0)); /* * Get a character from the user. If not quoted, treat escape * sequences as a single (16-bit) special character. */ if (quotef) c = keystroke_raw8(); else if (dontmap) /* this looks wrong, but isn't. no mapping will happen anyway, since we're on the command line. we want SPEC keys to be expanded to #c, but no further. this does that */ c = mapped_keystroke_raw(); else c = keystroke(); /* if we echoed ^V, erase it now */ if (quotef) { firstch = FALSE; eraseChar(quotec); TTflush(); } /* If it is a <ret>, change it to a <NL> */ if (c == '\r' && quotef == FALSE) c = '\n'; /* * If they hit the line terminate (i.e., newline or unescaped * eolchar), wrap it up. * * Don't allow newlines in the string -- they cause real * problems, especially when searching for patterns * containing them -pgf */ done = FALSE; if (c == '\n') { done = TRUE; } else if (!EscOrQuo && !is_edit_char(c)) { if ((*endfunc)(buf,cpos,c,eolchar)) { done = TRUE; } } if (complete != no_completion) { if (c == EOS) { /* conflicts with null-terminated strings */ kbd_alarm(); continue; } kbd_unquery(); shell = editingShellCmd(buf,options); if (done && (options & KBD_NULLOK) && cpos == 0) /*EMPTY*/; else if ((done && !(options & KBD_MAYBEC)) || (!EscOrQuo && !(shell && isprint(c)) && (c == TESTC || c == NAMEC))) { if (shell && isreturn(c)) { ; } else if ((*complete)(c, buf, &cpos)) { done = TRUE; if (c != NAMEC) /* cancel the unget */ (void)keystroke(); } else { if (done) { /* stay til matched! */ buf[cpos] = EOS; kbd_unquery(); (void)((*complete)(TESTC, buf, &cpos)); } firstch = FALSE; continue; } } } if (done) { last_eolchar = c; if (options & KBD_QUOTES) remove_backslashes(buf); /* take out quoters */ /* if buffer is empty, return FALSE */ hst_append(buf, eolchar); (void)strncpy0(extbuf, buf, (SIZE_T)bufn); status = (*extbuf != EOS); /* If this is a shell command, push-back the actual * text to separate the "!" from the command. Note * that the history command tries to do this already, * but cannot for some special cases, e.g., the user * types * :execute-named-command !ls -l * which is equivalent to * :!ls -l */ if (status == TRUE /* ...we have some text */ && (options & KBD_EXPCMD) #if OPT_HISTORY && (strlen(buf) == cpos) /* history didn't split it */ #endif && isShellOrPipe(buf)) { kbd_pushback(extbuf, 1); } break; } #if OPT_HISTORY if (!EscOrQuo && edithistory(buf, &cpos, &c, options, endfunc, eolchar)) { backslashes = countBackSlashes(buf, cpos); firstch = TRUE; continue; } else #endif if (ABORTED(c) && quotef == FALSE && !dontmap) { buf[cpos] = EOS; status = esc_func(FALSE, 1); break; } else if ((isbackspace(c) || c == wkillc || c == killc) && quotef==FALSE) { if (prompt == 0 && c == killc) cpos = 0; if (cpos == 0) { buf[0] = EOS; if (prompt) mlerase(); if (isbackspace(c)) { /* splice calls */ unkeystroke(c); status = SORTOFTRUE; break; } status = FALSE; break; } killit: kbd_kill_response(buf, &cpos, c); backslashes = countBackSlashes(buf, cpos); } else if (firstch == TRUE) { /* clean the buffer on the first char typed */ unkeystroke(c); c = killc; goto killit; } else if (c == quotec && quotef == FALSE) { quotef = TRUE; show1Char(c); continue; /* keep firstch==TRUE */ } else { if (EscOrQuo || !expandChar(buf, bufn, &cpos, c, options)) { if (c == '\\') backslashes++; else backslashes = 0; quotef = FALSE; if (isspecial(c) || (cpos >= bufn-1)) { if (!keystroke_avail()) kbd_alarm(); continue; /* keep firstch==TRUE */ } else { #if !SMALLER if ((options & KBD_LOWERC) && isupper(c)) c = tolower(c); else if ((options & KBD_UPPERC) && islower(c)) c = toupper(c); #endif buf[cpos++] = (char)c; buf[cpos] = EOS; show1Char(c); } } } firstch = FALSE; } #if OPT_POPUPCHOICE popdown_completions(); #endif reading_msg_line = FALSE; return status; } /* * Make the "." replay the keyboard macro */ #ifdef GMDDOTMACRO static void dot_replays_macro(int macnum) { extern const CMDFUNC f_kbd_mac_exec; char temp[NSTRING]; ITBUFF *tmp; int c; if (macnum == DEFAULT_REG) { if ((c = fnc2kcod(&f_kbd_mac_exec)) == -1) return; (void)kcod2str(c, temp); } else { (void)lsprintf(temp, "@%c", index2reg(macnum)); } dotcmdbegin(); tmp = TempDot(FALSE); (void)itb_sappend(&tmp, temp); dotcmdfinish(); dotcmdbegin(); } #endif /* * Begin recording the dot command macro. * Set up variables and return. * we use a temporary accumulator, in case this gets stopped prematurely */ int dotcmdbegin(void) { /* never record a playback */ if (dotcmdmode != PLAY) { (void)TempDot(TRUE); dotcmdmode = RECORD; return TRUE; } return FALSE; } /* * Finish dot command, and copy it to the real holding area */ int dotcmdfinish(void) { if (dotcmdmode == RECORD) { ITBUFF *tmp = TempDot(FALSE); if (itb_length(tmp) == 0 /* keep the old value */ || itb_copy(&dotcmd, tmp) != 0) { itb_first(dotcmd); dotcmdmode = STOP; return TRUE; } } return FALSE; } /* stop recording a dot command, probably because the command is not re-doable */ void dotcmdstop(void) { if (dotcmdmode == RECORD) dotcmdmode = STOP; } /* * Execute the '.' command, by putting us in PLAY mode. * The command argument is the number of times to loop. Quit as soon as a * command gets an error. Return TRUE if all ok, else FALSE. */ int dotcmdplay(int f, int n) { if (!f) n = dotcmdarg ? dotcmdcnt:1; else if (n < 0) return TRUE; if (f) /* we definitely have an argument */ dotcmdarg = TRUE; /* else leave dotcmdarg alone; */ if (dotcmdmode != STOP || itb_length(dotcmd) == 0) { dotcmdmode = STOP; dotcmdarg = FALSE; return FALSE; } if (n == 0) n = 1; dotcmdcnt = dotcmdrep = n; /* remember how many times to execute */ dotcmdmode = PLAY; /* put us in play mode */ itb_first(dotcmd); /* at the beginning */ if (ukb != 0) /* save our kreg, if one was specified */ dotcmdkreg = ukb; else /* use our old one, if it wasn't */ ukb = dotcmdkreg; return TRUE; } /* * Test if we are replaying either '.' command, or keyboard macro. */ int kbd_replaying(int match) { if (dotcmdmode == PLAY) { /* * Force a false-return if we are in insert-mode and have * only one character to display. */ if (match && insertmode == INSERT && b_val(curbp, MDSHOWMAT) && KbdStack == 0 && (dotcmd->itb_last+1 >= dotcmd->itb_used)) { return FALSE; } return TRUE; } return (kbdmode == PLAY); } /* * Begin recording a keyboard macro. */ /* ARGSUSED */ int kbd_mac_begin(int f, int n) { if (kbdmode != STOP) { mlforce("[Macro already active]"); return FALSE; } mlwrite("[Start macro]"); kbdmode = RECORD; return (itb_init(&KbdMacro, abortc) != 0); } /* * End keyboard macro. Check for the same limit conditions as the above * routine. Set up the variables and return to the caller. */ /* ARGSUSED */ int kbd_mac_end(int f, int n) { if (kbdmode == STOP) { mlforce("[Macro not active]"); return FALSE; } if (kbdmode == RECORD) { mlwrite("[End macro]"); kbdmode = STOP; #ifdef GMDDOTMACRO dot_replays_macro(DEFAULT_REG); #endif } /* note that if kbd_mode == PLAY, we do nothing -- that makes the '^X-)' at the of the recorded buffer a no-op during playback */ return TRUE; } /* * Execute a macro. * The command argument is the number of times to loop. Quit as soon as a * command gets an error. Return TRUE if all ok, else FALSE. */ /* ARGSUSED */ int kbd_mac_exec(int f, int n) { if (kbdmode != STOP) { mlforce("[Can't execute macro while recording]"); return FALSE; } if (n <= 0) return TRUE; return start_kbm(n, DEFAULT_REG, KbdMacro); } /* ARGSUSED */ int kbd_mac_save(int f, int n) { ksetup(); itb_first(KbdMacro); while (itb_more(KbdMacro)) if (!kinsert(itb_next(KbdMacro))) break; kdone(); mlwrite("[Keyboard macro saved in register %c.]", index2reg(ukb)); return TRUE; } /* * Test if the given macro has already been started. */ int kbm_started(int macnum, int force) { if (force || (kbdmode == PLAY)) { register KSTACK *sp; for (sp = KbdStack; sp != 0; sp = sp->m_link) { if (sp->m_indx == macnum) { while (kbdmode == PLAY) finish_kbm(); mlwarn("[Error: currently executing %s%c]", macnum == DEFAULT_REG ? "macro" : "register ", index2reg(macnum)); return TRUE; } } } return FALSE; } /* * Start playback of the given keyboard command-string */ int start_kbm( int n, /* # of times to repeat */ int macnum, /* register to execute */ ITBUFF *ptr) /* data to interpret */ { register KSTACK *sp; ITBUFF *tp = 0; if (interrupted()) return FALSE; if (kbdmode == RECORD && KbdStack != 0) return TRUE; if (itb_length(ptr) && (sp = typealloc(KSTACK)) != 0 && itb_copy(&tp, ptr) != 0) { /* make a copy of the macro in case recursion alters it */ itb_first(tp); sp->m_save = kbdmode; sp->m_indx = macnum; sp->m_rept = n; sp->m_kbdm = tp; sp->m_link = KbdStack; KbdStack = sp; kbdmode = PLAY; /* start us in play mode */ /* save data for "." on the same stack */ sp->m_dots = 0; if (dotcmdmode == PLAY) { #ifdef GMDDOTMACRO sp->m_DOTS = dotcmd; sp->m_RPT0 = dotcmdcnt; sp->m_RPT1 = dotcmdrep; #endif dotcmd = 0; dotcmdmode = RECORD; } #ifdef GMDDOTMACRO else { sp->m_DOTS = 0; } #endif return (itb_init(&dotcmd, abortc) != 0 && itb_init(&(sp->m_dots), abortc) != 0); } return FALSE; } /* * Finish a macro begun via 'start_kbm()' */ static void finish_kbm(void) { if (kbdmode == PLAY) { register KSTACK *sp = KbdStack; kbdmode = STOP; if (sp != 0) { kbdmode = sp->m_save; KbdStack = sp->m_link; itb_free(&(sp->m_kbdm)); itb_free(&(sp->m_dots)); #ifdef GMDDOTMACRO itb_free(&dotcmd); if (sp->m_DOTS != 0) { dotcmd = sp->m_DOTS; dotcmdcnt = sp->m_RPT0; dotcmdrep = sp->m_RPT1; dotcmdmode = PLAY; } dot_replays_macro(sp->m_indx); #endif free((char *)sp); } } }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.