/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ /* * Modified 1988-91 by Tom Hageman. * - handle marks correctly in substitute * - allow numeric arguments on replace * - replace UNDO stuff with more general undo (see also io.c) * - improved tag-search (fast tags inspired from 4.14 JOVE) * - and more... */ #include "jove.h" RCS("$Id: search.c,v 14.32.0.9 1994/02/02 19:19:55 tom Exp tom $") #include "io.h" #include "re.h" #include "ctype.h" #include "maps.h" /* default (literal/regexp) search strings. */ private char *search_strings[2] ZERO; /* * [TRH] search exp'th occurrence, backward if exp < 0. * temporarily enable Wrap search if exp equals zero. * If argument flag is non-zero in invoking command, * the search string is saved */ DEF_CMD( "search-forward-re", Search, ARG(YES)|ARG(8) ); _IF(ndef TINY) DEF_CMD( "search-forward-re-nd", Search, ARG(NO)|ARG(8) ); _IF(ndef TINY) DEF_CMD( "search-reverse-re", Search, NEGATE|ARG(YES)|ARG(8) ); _IF(ndef TINY) DEF_CMD( "search-reverse-re-nd", Search, NEGATE|ARG(NO)|ARG(8) ); _IF(ndef TINY) DEF_CMD( "search-forward-lit", Search, ARG(YES)|ARG(4) ); _IF(ndef TINY) DEF_CMD( "search-forward-lit-nd", Search, ARG(NO)|ARG(4) ); _IF(ndef TINY) DEF_CMD( "search-reverse-lit", Search, NEGATE|ARG(YES)|ARG(4) ); _IF(ndef TINY) DEF_CMD( "search-reverse-lit-nd", Search, NEGATE|ARG(NO)|ARG(4) ); _IF(ndef TINY) DEF_CMD( "search-forward", Search, ARG(YES) ); DEF_CMD( "search-forward-nd", Search, ARG(NO) ); DEF_CMD( "search-reverse", Search, NEGATE|ARG(YES) ); DEF_CMD( "search-reverse-nd", Search, NEGATE|ARG(NO) ) { const char *s; { register char **p_searchstr = &search_strings[NO]; #ifndef TINY register int re = NO; if ((LastCmd->Type & ARG(8)) || /* Force regexp */ (!(LastCmd->Type & ARG(4)) && /* Force literal */ True(UseRE))) { ++re; ++p_searchstr; } #else # define re UseRE if (True(UseRE)) ++p_searchstr; #endif s = re_ask((*p_searchstr), re, ProcFmt); if (LastCmd->Type & ARG(YES)) set_str(p_searchstr, s); } #undef re { register int num; int dir = FORWARD; register int i; Bufpos prevdot; /* to remember last match */ register Bufpos *newdot = NULL; Line *end_lp = NULL; if ((num = exp) < 0) { num = -num; dir = BACKWARD; } if ((i = num) == 0 || True(WrapScan)) /* enable wrap search */ end_lp = curline; do { if (!(newdot = docompiled(dir, compbuf, newdot, end_lp))) { char *where = (end_lp) ? "in buffer" : (dir > 0) ? "to bottom" : "to top"; if (i == num) complain("No \"%s\" found %s.", s, where); confirm("No %d \"%s\"%n %s; use last match (%d) instead? ", num, s, where, num - i); newdot = &prevdot; break; } prevdot = *newdot; /* well, remember */ } while (--i > 0); PushPntp(newdot->p_line); SetDot(newdot); } } private char QueryHelp[] = "\ Space,Y Rubout,N Return,Q !,P(all) .(y+q) ^]D(el) ^]R(ecur) ^]W(d+r) ^]U(ndo)\ "; private int substitute __(( const char *_(query), Line *_(l1), int _(char1), Line *_(line2), int _(char2) )); private substitute(query, l1, char1, l2, char2) const char *query; Line *l1, *l2; { register Mark *end = MakeMark(l2, char2, FLOATER); register Bufpos *bp = NULL; register int matchlen, numdone = 0, stop = 0, undo_incr; /* * delete the match (assuming matchlen is set to the negated length * of the current match (i.e. matchlen = -(REeom - REbom)) and curchar * is at end of match. We use DelNChar with negative argument here * since DelPChar behaves funny in overwrite mode. */ #define DelMatch() { if (exp = matchlen) exp_p = NO, DelNChar(); } /* * In query mode "stop" is used to flag a user-break, and * repeat count is ignored (except for its sign -- see replace); * In non-query mode, replace is halted when number * of substitutions equals repeat count (if given) * (negative repeat count is ignored here, but taken care of in * Replace) */ if (!query && exp_p == YES) stop = exp; UndoReset(); DotTo(l1, char1); /* * [TRH 6/89] end scan on line AFTER last line of region to avoid * problems with wraparound. */ l2 = l2->l_next; for (; bp = docompiled(FORWARD, compbuf, bp, l2); DOTsave(bp), (matchlen == 0 && linebuf[bp->p_char]) && (bp->p_char++, 0) /* ...to avoid infinite loops */ ) { if (bp->p_line == l2) break; if (bp->p_line == end->m_line && bp->p_char > end->m_char) break; /* nope, leave this alone */ /* * [TRH 4/89] new routine REsubst() is a replacement for * re_dosub(). It just builds the final substitution string * from the replacement string and the current match. * Do this now since there is a distinct chance that * the disk buffer containing the match is invalidated * later on. */ REsubst(genbuf, rep_str); matchlen = REbom - REeom; /* (negated) length of match */ SetDot(bp); if (query) { register int c; message(query); reswitch: redisplay(); if ((c = getchar()) == AbortChar) break; switch (toupper(c)) { case '.': stop++; break; case ' ': case 'Y': break; case BS: case RUBOUT: case 'N': continue; case CTL('D'): case 'D': genbuf[0] = '\0'; break; case CTL('W'): case 'W': lsave(); /* so that it can be undone */ DelMatch(); numdone++; /* Fall into ... */ case CTL('R'): case 'R': message("Type C-X C-C to continue with query replace."); curchar = REbom; RErecur(); UndoReset(); /* cannot reliably Undo */ continue; case CTL('U'): case 'U': exp_p++; /* to get most recent version */ if (!UndoLine()) goto reswitch; numdone -= undo_incr; undo_incr = -undo_incr; /* UndoLine toggles */ continue; case 'P': case '!': query = NO; break; case CR: case LF: case 'Q': goto done; /* ``break 2;'' */ case CTL('L'): RedrawDisplay(); goto reswitch; default: rbell(); message(QueryHelp); goto reswitch; } lsave(); /* * so that UNDO leaves any previous substitutions * (in fact: any previous change) in this line alone. * {{ if you want UNDO to restore the entire previous * line, put this lsave() before the loop or you may * lose all changes to curline made before * substitute }} */ undo_incr = 1; } /* now do the substitution */ DelMatch(); ins_str(genbuf, NO); numdone++; if (query) { if (stop) break; updmesg(); /* No blinking. */ redisplay(); /* Show the change. */ } else if (numdone == stop) /* * In non-query mode, replace is halted when number * of substitutions equals repeat count (if given) * (negative repeat count is ignored) */ break; } done: DelMark(end); return numdone; } /* TODO: separate default search/replacement strings for (literal/regexp) */ /* * Prompt for search and replacement strings and do the substitution. The * point is restored when we're done. * * Rules: * 1. if `inreg', constrict replace to Region. * 2. replace {from top of buffer to Point, from Point to end of buffer, * entire buffer} if repeat count is {negative,positive,zero} resp. * 3. if not query, and repeat count is valid, replace only the first * |repeat-count| occurrences (zero means all). this is handled by * substitute() */ DEF_CMD( "query-replace-in-region", Replace, EDIT|ARG(1|2) ); DEF_CMD( "query-replace-string", Replace, EDIT|ARG(2) ); DEF_CMD( "replace-in-region", Replace, EDIT|ARG(1) ); DEF_CMD( "replace-string", Replace, EDIT|ARG(0) ) { # define query (LastCmd->Type & ARG(2)) register Buffer *cb = curbuf; register char *rep_ptr; Line *l1 = cb->b_dot, *l2 = cb->b_last; int char1 = cb->b_char, char2 = length(l2); int in_region; if ((in_region = (LastCmd->Type & ARG(1)))) { register Mark *m = CurMark(); l2 = m->m_line; char2 = m->m_char; fixorder(&l1, &char1, &l2, &char2); } else if (exp <= 0) { if (exp < 0) { exp = -exp; l2 = l1; char2 = char1; } l1 = cb->b_first; char1 = 0; } /* Get search string. */ if ((rep_ptr = rep_search) == NULL) rep_ptr = search_strings[NO]; set_str(&rep_search, re_ask(rep_ptr, UseRE, ProcFmt)); /* * Now the replacement string. Do_ask() so the user can play with * the default (previous) replacement string by typing C-R in ask(), * OR, (s)he can just hit Return to replace with nothing. */ if (rep_ptr = do_ask("\r\n", (int (*)())0, rep_str, "%N: %f %s with ", rep_search)) set_str(&rep_str, rep_ptr); else rep_ptr = rep_str, rep_str = (char *) NullStr; if (in_region && True(SaveRegion)) CopyRegion(); /* so you can undo it */ /* * build a a prompt for query-replace * just copy message left around by ask() */ { char prompt[MESG_SIZE]; register Mark *m = MakeMark(cb->b_dot, cb->b_char, FLOATER); register int n; if ((n = substitute(query ? strcpy(prompt, mesgbuf) : (char *)0, l1, char1, l2, char2)) && !in_region) set_mark(); ToMark(m); DelMark(m); if (rep_str[0] == '\0') /* restore default */ rep_str = rep_ptr; s_mess("%d substitution%n.", n); } # undef query } #ifdef CTAGS /* C tags package. */ /* * This search string matches lines (in the tag file) of the form: * "tagfile/line-containing-tag/" * or "tagfile?line-containing-tag?" * or "tagfilelineno" (for typedefs) * After a succesful match, sub-expression * \1 holds the actual tag, * \2 holds "file", * \3 holds "lineno", and iff this is an empty string, * \4 holds the "/" or "?" (only used internally), and * \5 holds "line-containing-tag", which is a search pattern itself, * * [15-Apr-90, adapt for NeXT ctags: multiple tabs, junk after pattern] */ private const char TagPattern[] = "^\\(%s[^\t]*\\)\t+\\([^\t]*\\)\t\\{\\([0-9]+\\),\\([?/]\\)\\(.*\\)\\4\\}"; #define TAG_TAG 1 #define TAG_FILE 2 #define TAG_LINENO 3 #define TAG_PATTERN 5 DEF_STR( "tag-file", TagFile, 128, V_FILENAME ) _IF(def CTAGS) = "tags"; _IF(def PRIVATE) DEF_INT( "unsorted-tags", SlowTags, V_BOOL ) _IF(def CTAGS)_IF(ndef TINY) ZERO; _IF(def PRIVATE) void find_tag(tag, localp) const char *tag; { char filebuf[FILESIZE*2], tag_lineno[10], tag_pattern[100], actual_tag[100]; { register File *fp; { register char *tagfile = filebuf; register const char *default_tagfile = TagFile; strcpy(tagfile, default_tagfile); /* need it later on */ if (localp) ask_file(sprint("With tag file (default \"%s\"): ", default_tagfile), default_tagfile, tagfile); if (!(fp = open_file(tagfile, iobuff, F_READ|F_TEXT|F_QUIET))) return; } { register const char *orgtag = tag; register int try = 2; for (;;) { /* until we have a match, or we failed completely */ if (try) f_bsearch(fp, tag, 0, strlen(orgtag)); if (re_fsearch(fp, sprint(TagPattern, orgtag))) break; /* * The search for the tag failed. If we are case-insensitive * retry binary search with an all-uppercase tag. Else if * we allow unsorted tag files and haven't already done * this, rewind the file and retry without binary search. */ if (--try > 0) { if (True(CaseIgnore) && islower(*orgtag)) { tag = strupr(strcpy(actual_tag, orgtag)); continue; } --try; } if (try == 0) { #ifndef TINY if (True(SlowTags)) { f_rewind(fp); continue; } #endif --try; } s_mess("Can't find tag \"%s\".", orgtag); break; } f_close(fp); if (try < 0) return; } } { register Buffer *b; register char *tag_file = filebuf, *tagf_base = basename(tag_file); /* to search file relative to tag file directory */ /* * we need to save all matches BEFORE do_find, since that may * clobber the match registers when reading a file (auto-execute). */ putmatch(TAG_FILE, tagf_base, (int)(sizeof filebuf - (tagf_base - tag_file))); putmatch(TAG_TAG, actual_tag, sizeof actual_tag); putmatch(TAG_LINENO, tag_lineno, sizeof tag_lineno); putmatch(TAG_PATTERN, tag_pattern, sizeof tag_pattern); if (ISABSPATH(tagf_base)) /* handle absolute tag-file name */ tag_file = tagf_base; b = do_find(curwind, tag_file, NO); if (curbuf != b) { SetABuf(curbuf); SetBuf(b); } /* if we can't seem to find the file, delete this buffer and give up. */ if (b_nofile(b) && b_empty(b)) { kill_buf(b); s_mess("I can't find the file \"%s\" for this tag.", tag_file); return; } } { register Bufpos *bp; Bufpos old, new; /* * Try to locate the tag. Use line number if non-empty, else use * search pattern. We want exact match, so temporarily disable * CaseIgnore here. */ CaseIgnore--; if (tag_lineno[0]) { register int dir = FORWARD; DOTsave(&old); /* Try to locate the actual tag in a limited region around where it's supposed to be. */ to_line(chr_to_int(tag_lineno, 10, NO)); while ((bp = dosearch(actual_tag, dir, NO)) == NULL || LineDist(bp->p_line, curline) >= MarkThresh) { if ((dir = -dir) > 0) { /* tried both ways */ bp = &new; DOTsave(bp); break; } } SetDot(&old); } else { register char *s = tag_pattern, *d = genbuf; /* * Unfortunately CTAGS only escapes '/' (and '\' I presume) but * not any other RE-special characters in the search pattern, * so we have to do it ourselves. (we cannot use non-RE search * anymore since that is truly literal now...) */ for (;;) { if (*s == '\\') s++; if ((*d = *s++) == '\0') break; if (index(".*?+[\\", *d++)) *d++ = s[-1], d[-2] = '\\'; } /* search through the whole file */ okay_wrap++; WrapScan++; if (bp = dosearch(genbuf, FORWARD, YES)) { /* * display tag near top of window * (to display more of the routine's body) */ SetTop(curwind, bp->p_line->l_prev); } okay_wrap = NO; WrapScan--; } CaseIgnore++; if (bp) { #if (HIGHLIGHT) WHighLight(curwind, bp->p_line); #endif PushPntp(bp->p_line); SetDot(bp); } else message("Well, I found the file, but the tag is missing."); } } DEF_CMD( "find-tag-at-point", NonExisting, ARG(YES) );_IF(ndef CTAGS) DEF_CMD( "find-tag", NonExisting, ARG(NO) ); _IF(ndef CTAGS) DEF_CMD( "find-tag-at-point", FindTag, ARG(YES) ); _IF(def CTAGS) DEF_CMD( "find-tag", FindTag, ARG(NO) ) _IF(def CTAGS) { char tagbuf[100]; register char *tag = tagbuf; WordAtDot(tag, sizeof tagbuf); if (LastCmd->Type & ARG(YES)) { /* Find Tag at Dot. */ if (tag[0] == '\0') complain("not a tag."); } else { register const char *prompt = ProcDefFmt; if (tag[0] == '\0') tag = NULL, prompt = ProcFmt; tag = strcpy(tagbuf, ask(tag, prompt, tag)); } find_tag(tag, (exp_p & YES)); } #else # ifndef CLIOPTIONS void find_tag() { /* dummy, gets called by main() */ } # endif #endif /* CTAGS */ #ifdef ISEARCH /* I-search returns a code saying what to do: STOP: We found the match, so unwind the stack and leave where it is. DELETE: Rubout the last command. BACKUP: Back up to where the isearch was last NOT failing. When a character is typed it is appended to the search string, and then, isearch is called recursively. When C-S or C-R is typed, isearch is again called recursively. */ #define TOSTART 0 #define STOP 1 #define DELETE 2 #define BACKUP 3 private char *ISdef ZERO; #define ISbuf genbuf /* well, it's there... */ #define ISBUFSIZE LBSIZE DEF_INT( "search-exit-char", SExitChar, V_CHAR ) _IF(def ISEARCH) = CR; _IF(def PRIVATE) #define DIR_CMD 0 /* value of c on one of CTL('R') or CTL('S') */ #ifdef OLD_CASEIGNORE #define cmp_char(a, b) ((a)==(b) || (True(CaseIgnore) && CEquiv(a)==CEquiv(b))) #else /* This assumes `b' is the pattern character to match against. */ #define cmp_char(a, b) ((a)==(b) || (True(CaseIgnore) && CEquiv(a)==(b))) #endif private Bufpos *doisearch __(( int _(dir), int _(ch), int _(failing) )); private Bufpos * doisearch(dir, ch, failing) { static Bufpos buf; register Bufpos *bp = &buf; register int c; extern int okay_wrap; if ((c = ch) != DIR_CMD) { if (failing) return NULL; DOTsave(bp); if (dir > 0) { /* FORWARD */ register int curc = linebuf[bp->p_char]; if (cmp_char(curc, c)) { bp->p_char++; /* i.e. curchar + 1 */ return bp; } } else { /* BACKWARD */ if (look_at(ISbuf)) return bp; } } okay_wrap++; if ((bp = dosearch(ISbuf, dir, NO)) == NULL) rbell(); /* ring the first time there's no match */ okay_wrap = NO; return bp; } DEF_REF( "quoted-insert", QuoteCmd ); /* Nicely recursive. */ private int isearch __(( char *_(push_incp), int _(push_dir), Bufpos *_(bp) )); private int isearch(push_incp, push_dir, bp) char *push_incp; int push_dir; Bufpos *bp; { Bufpos push_bp; register char *incp = push_incp; register int dir = push_dir, c, failing = NO; if (bp) { /* Move to the new position. */ push_bp = *bp; } else { DOTsave(&push_bp); failing++; } for (;;) { SetDot(&push_bp); f_mess("%s%sI-search: %s", (failing) ? "Failing " : NullStr, (dir < 0) ? "reverse-" : NullStr, ISbuf); c = getch(); if (Inputp) { /* Regurgitated search string, to be taken literally. */ #if (BPC > 7) /* Oh dear! 8-bit hack strikes back! */ if (c == QuoteChar) { if ((c = getch()) < 0200) { Ungetc(c); c = QuoteChar; } } #endif goto append_char; } if (c == SExitChar) return STOP; if (c == AbortChar) /* If we're failing, we backup until we're no longer failing or we've reached the beginning; else, we just about the search and go back to the start. */ return (failing) ? BACKUP : TOSTART; switch (c) { case RUBOUT: case BS: return DELETE; case CTL('\\'): case CTL('S'): dir = FORWARD; c = DIR_CMD; break; case CTL('R'): dir = BACKWARD; c = DIR_CMD; break; #ifndef TINY case CTL('W'): /* Yank next word. */ case CTL('Y'): /* Yank rest of line. */ Inputp = strcpy(Minibuf, &linebuf[curchar + (dir > 0 ? 0 : incp - ISbuf)]); if (c == CTL('W')) { /* Truncate at end of word. */ incp = Inputp; while (isword(*incp++)) ; *--incp = '\0'; incp = push_incp; } continue; #endif #if NO /* [TRH] I don't see the point in this, now that non-RE search is truly literal... */ case '\\': if (incp >= &ISbuf[ISBUFSIZE - 1]) { rbell(); continue; } *incp++ = '\\'; add_mess("\\"); /* Fall into ... */ #endif case_QuoteChar: updmesg(); /* a fast add_mess(NullStr); */ c = getch(); #ifdef NULLCHARS if (c == '\0') c = '\n'; #endif break; default: if (printable(c)) break; #ifndef TINY /* Lookahead to handle quoted characters, macros, non-standard bindings to incremental-search and accented characters transparently. */ { register data_obj *dp; register char *end_prompt = mesgbuf + strlen(mesgbuf); /* Use stroke-buffer to remember keys, so we can backtrack if necessary. */ init_strokes(); Ungetc(c); add_stroke(c); /* Ungetc()'ed chars are not automatically added. */ add_mess(" "); if ((dp = *MapKey(active_map(), MAP_FOLLOW_ACTIVE_LIST))) { # define cp ((Command *) dp) extern void IncSearch __(( void )); extern void ClAndRedraw __(( void )); extern void RedrawDisplay __(( void )); # if (BPC > 7) extern void Accent __(( void )); if (cp->c_proc == Accent) { (cp->c_proc)(); /* "accent" leaves its result in the Ungetc() buffer, so read them. */ continue; } # endif if (dp == QuoteCmd) { goto case_QuoteChar; } if (cp->c_proc == IncSearch) { *end_prompt = '\0'; dir = FORWARD; if (cp->Type & NEGATE) dir = BACKWARD; c = DIR_CMD; break; } if ((dp->Type & TYPEMASK) == MACRO || (cp->c_proc == ClAndRedraw) || (cp->c_proc == RedrawDisplay)) { ExecCmd(dp); continue; } # undef cp } } /* Backtrack if no special case. */ while ((c = pop_stroke()) >= 0) Ungetc(c); #else /* TINY */ if (get_bind(active_map(), c) == QuoteCmd) { goto case_QuoteChar; } Ungetc(c); #endif /* TINY */ return STOP; } if (c == DIR_CMD) { /* CTL('S') or CTL('R') */ /* If this is the first time through and we have a search string left over from last time, use that one now. Setup Inputp so that chars are read through getch() and the recursion gets right. */ if (incp == ISbuf) { if (push_dir == dir) Inputp = ISdef; else push_dir = dir; /* so next time will work. */ continue; } /* If we're failing and we're not changing our direction, don't recur since there's no way the search can work. */ if (failing && push_dir == dir) { rbell(); continue; } } else { /* ordinary character */ append_char: if (incp >= &ISbuf[ISBUFSIZE - 1]) { rbell(); continue; } *incp++ = c; *incp = '\0'; } add_mess("%s ...", push_incp); /* so we know what's going on */ DrawMesg(NO); /* do it now */ switch (c = isearch(incp, dir, doisearch(dir, c, failing))) { case BACKUP: /* If we're not failing, we just continue to to the for loop; otherwise we keep returning to the previous levels until we find one that isn't failing OR we reach the beginning. */ if (failing) return BACKUP; /* Fall into ... */ case DELETE: incp = push_incp; if (*incp == '\0') dir = push_dir; /* "rubout" direction */ else *incp = '\0'; continue; default: /* TOSTART or STOP */ return c; } } } DEF_CMD( "i-search-reverse", NonExisting, NEGATE ); _IF(ndef ISEARCH) DEF_CMD( "i-search-forward", NonExisting, NO ); _IF(ndef ISEARCH) DEF_CMD( "i-search-reverse", IncSearch, NEGATE ); _IF(def ISEARCH) DEF_CMD( "i-search-forward", IncSearch, NO ) _IF(def ISEARCH) { Bufpos save; register Bufpos *sp = &save; register char *str = ISbuf; #ifndef TINY /* Interactive search from .joverc? You gotta be kidding!! */ if (InJoverc) complain((char *) 0); #endif #if 0 /* Ugh! this creates more trouble than it solves! */ Interactive++; /* In case we're in a macro. */ #endif DOTsave(sp); str[0] = '\0'; if (isearch(str, (exp > 0) ? FORWARD : BACKWARD, sp) == TOSTART) SetDot(sp); else if (LineDist(curline, sp->p_line) >= MarkThresh) DoSetMark(sp->p_line, sp->p_char); if (str[0] != '\0') /* save default */ set_str(&ISdef, str); s_mess(NullStr); /* remove prompt */ #if 0 Interactive--; #endif } #endif /* ISEARCH */ /*====================================================================== * $Log: search.c,v $ * Revision 14.32.0.9 1994/02/02 19:19:55 tom * (ISearch): disable Interactive because of unwanted side-effects in Macros. * * Revision 14.32.0.8 1993/11/16 14:11:00 tom * (Search): use separate default strings for regexp and literal search; * (search-{forward,backward}-{lit,re}{,-nd}): new user-commands; * (Replace): reshuffles in preparation of separate re,lit replace strings; * (isearch): handle "accent" and macro input transparently, fix bug in * regurgitated input handling, add C-W (yank word) and C-Y (yank line), * allow "clear-and-redraw" and "redraw-display" without exiting i-search, * disallow i-search from .joverc. * * Revision 14.32.0.1 1993/07/07 12:12:57 tom * (F_TEXT): new option for f_open et al. * * Revision 14.32 1993/04/09 02:36:02 tom * (cmp_char): change to new case-ignore scheme. * * Revision 14.31 1993/02/17 23:36:12 tom * lotsa random optimizations. * * Revision 14.30 1993/01/27 01:33:21 tom * cleanup whitespace; parenthize || && expr to avoid compiler warnings. * * Revision 14.27 1992/09/22 15:59:20 tom * rename "re1.c" to "search.c"; replace CTL('Q') with `QuoteChar'. * * Revision 14.26 1992/08/26 23:56:58 tom * NULLCHARS fix in i-search; PRIVATE-ized some Variable defs; * add RCS directives. * */