/* * zle_hist.c - history editing * * This file is part of zsh, the Z shell. * * Copyright (c) 1992-1997 Paul Falstad * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any * purpose, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * In no event shall Paul Falstad or the Zsh Development Group be liable * to any party for direct, indirect, special, incidental, or consequential * damages arising out of the use of this software and its documentation, * even if Paul Falstad and the Zsh Development Group have been advised of * the possibility of such damage. * * Paul Falstad and the Zsh Development Group specifically disclaim any * warranties, including, but not limited to, the implied warranties of * merchantability and fitness for a particular purpose. The software * provided hereunder is on an "as is" basis, and Paul Falstad and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ #include "zle.mdh" #include "zle_hist.pro" /* Column position of vi ideal cursor. -1 if it is unknown -- most * * movements and changes do this. */ /**/ int lastcol; /* current history line number */ /**/ int histline; /* Previous search string use in an incremental search */ /**/ char *previous_search = NULL; /**/ int previous_search_len; /* Previous aborted search string use in an incremental search */ /**/ char *previous_aborted_search = NULL; /* Local keymap in isearch mode */ /**/ Keymap isearch_keymap; /*** History text manipulation utilities ***/ /* * Text for the line: anything previously modified within zle since * the last time the line editor was started, else what was originally * put in the history. */ #define GETZLETEXT(ent) ((ent)->zle_text ? (ent)->zle_text : (ent)->node.nam) /**/ void remember_edits(void) { Histent ent = quietgethist(histline); if (ent) { char *line = zlemetaline ? zlemetaline : zlelineasstring(zleline, zlell, 0, NULL, NULL, 0); if (!ent->zle_text || strcmp(line, ent->zle_text) != 0) { if (ent->zle_text) free(ent->zle_text); ent->zle_text = zlemetaline ? ztrdup(line) : line; } else if (!zlemetaline) free(line); } } /**/ void forget_edits(void) { Histent he; for (he = hist_ring; he; he = up_histent(he)) { if (he->zle_text) { free(he->zle_text); he->zle_text = NULL; } } } /*** Search utilities ***/ /* * Return zero if the ZLE string histp length histl and the ZLE string * inputp length inputl are the same. Return -1 if inputp is a prefix * of histp. Return 1 if inputp is the lowercase version of histp. * Return 2 if inputp is the lowercase prefix of histp and return 3 * otherwise. */ static int zlinecmp(const char *histp, const char *inputp) { const char *hptr = histp, *iptr = inputp; #ifdef MULTIBYTE_SUPPORT mbstate_t hstate, istate; #endif while (*iptr && *hptr == *iptr) { hptr++; iptr++; } if (!*iptr) { if (!*hptr) { /* strings are the same */ return 0; } else { /* inputp is a prefix */ return -1; } } #ifdef MULTIBYTE_SUPPORT memset(&hstate, 0, sizeof(hstate)); memset(&istate, 0, sizeof(istate)); #endif /* look for lower case versions */ while (*histp && *inputp) { #ifdef MULTIBYTE_SUPPORT wint_t hwc, iwc; int hlen, ilen; hlen = mb_metacharlenconv_r(histp, &hwc, &hstate); ilen = mb_metacharlenconv_r(inputp, &iwc, &istate); if (hwc == WEOF || iwc == WEOF) { /* can't convert, compare input characters */ if (ilen != hlen || memcmp(histp, inputp, hlen) != 0) return 3; } else if (towlower(hwc) != iwc) return 3; histp += hlen; inputp += ilen; #else if (tulower(*histp++) != *inputp++) return 3; #endif } if (!*inputp) { /* one string finished, if it's the input... */ if (!*histp) return 1; /* ...same, else */ else return 2; /* ...prefix */ } /* Different */ return 3; } /* * Search for needle in haystack. Haystack and needle are metafied strings. * Start the search at position pos in haystack. * Search forward if dir > 0, otherwise search backward. * sens is used to test against the return value of linecmp. * * Return the pointer to the location in haystack found, else NULL. * * We assume we'll only find needle at some sensible position in a multibyte * string, so we don't bother calculating multibyte character lengths for * incrementing and decrementing the search position. */ static char * zlinefind(char *haystack, int pos, char *needle, int dir, int sens) { char *s = haystack + pos; if (dir > 0) { while (*s) { if (zlinecmp(s, needle) < sens) return s; s++; } } else { for (;;) { if (zlinecmp(s, needle) < sens) return s; if (s == haystack) break; s--; } } return NULL; } /*** Widgets ***/ /**/ int uphistory(UNUSED(char **args)) { int nodups = isset(HISTIGNOREDUPS); if (!zle_goto_hist(histline, -zmult, nodups) && isset(HISTBEEP)) return 1; return 0; } /**/ int upline(char **args) { int n = zmult; if (n < 0) { zmult = -zmult; n = -downline(args); zmult = -zmult; return n; } if (lastcol == -1) lastcol = zlecs - findbol(); zlecs = findbol(); while (n) { if (!zlecs) break; zlecs--; zlecs = findbol(); n--; } if (!n) { int x = findeol(); if ((zlecs += lastcol) >= x) { zlecs = x; if (zlecs > findbol() && invicmdmode()) DECCS(); } #ifdef MULTIBYTE_SUPPORT else CCRIGHT(); #endif } return n; } /**/ int uplineorhistory(char **args) { int ocs = zlecs; int n = upline(args); if (n) { int m = zmult, ret; zlecs = ocs; if (virangeflag || !(zlereadflags & ZLRF_HISTORY)) return 1; zmult = n; ret = uphistory(args); zmult = m; return ret; } return 0; } /**/ int viuplineorhistory(char **args) { int col = lastcol; uplineorhistory(args); lastcol = col; return vifirstnonblank(args); } /**/ int uplineorsearch(char **args) { int ocs = zlecs; int n = upline(args); if (n) { int m = zmult, ret; zlecs = ocs; if (virangeflag || !(zlereadflags & ZLRF_HISTORY)) return 1; zmult = n; ret = historysearchbackward(args); zmult = m; return ret; } return 0; } /**/ int downline(char **args) { int n = zmult; if (n < 0) { zmult = -zmult; n = -upline(args); zmult = -zmult; return n; } if (lastcol == -1) lastcol = zlecs - findbol(); while (n) { int x = findeol(); if (x == zlell) break; zlecs = x + 1; n--; } if (!n) { int x = findeol(); if ((zlecs += lastcol) >= x) { zlecs = x; if (zlecs > findbol() && invicmdmode()) DECCS(); } #ifdef MULTIBYTE_SUPPORT else CCRIGHT(); #endif } return n; } /**/ int downlineorhistory(char **args) { int ocs = zlecs; int n = downline(args); if (n) { int m = zmult, ret; zlecs = ocs; if (virangeflag || !(zlereadflags & ZLRF_HISTORY)) return 1; zmult = n; ret = downhistory(args); zmult = m; return ret; } return 0; } /**/ int vidownlineorhistory(char **args) { int col = lastcol; downlineorhistory(args); lastcol = col; return vifirstnonblank(zlenoargs); } /**/ int downlineorsearch(char **args) { int ocs = zlecs; int n = downline(args); if (n) { int m = zmult, ret; zlecs = ocs; if (virangeflag || !(zlereadflags & ZLRF_HISTORY)) return 1; zmult = n; ret = historysearchforward(args); zmult = m; return ret; } return 0; } /**/ int acceptlineanddownhistory(UNUSED(char **args)) { Histent he = quietgethist(histline); if (he && (he = movehistent(he, 1, HIST_FOREIGN))) { zpushnode(bufstack, ztrdup(he->node.nam)); stackhist = he->histnum; } done = 1; return 0; } /**/ int downhistory(UNUSED(char **args)) { int nodups = isset(HISTIGNOREDUPS); if (!zle_goto_hist(histline, zmult, nodups) && isset(HISTBEEP)) return 1; return 0; } /* * Values remembered for history searches to enable repetition. * srch_hl remembers the old value of histline, to see if it's changed * since the last search. * srch_cs remembers the old value of zlecs for the same purpose (it is * not use for any other purpose, i.e. does not need to be a valid * index into anything). * srch_str is the metafied search string, as extracted from the start * of zleline. */ static int histpos, srch_hl, srch_cs = -1; static char *srch_str; /**/ int historysearchbackward(char **args) { Histent he; int n = zmult; char *str; char *zt; if (zmult < 0) { int ret; zmult = -n; ret = historysearchforward(args); zmult = n; return ret; } if (*args) { str = *args; } else { char *line = zlelineasstring(zleline, zlell, 0, NULL, NULL, 0); if (histline == curhist || histline != srch_hl || zlecs != srch_cs || mark != 0 || strncmp(srch_str, line, histpos) != 0) { free(srch_str); for (histpos = 0; histpos < zlell && !ZC_iblank(zleline[histpos]); histpos++) ; if (histpos < zlell) histpos++; /* ensure we're not on a combining character */ CCRIGHTPOS(histpos); /* histpos from now on is an index into the metafied string */ srch_str = zlelineasstring(zleline, histpos, 0, NULL, NULL, 0); } free(line); str = srch_str; } if (!(he = quietgethist(histline))) return 1; metafy_line(); while ((he = movehistent(he, -1, hist_skip_flags))) { if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP) continue; zt = GETZLETEXT(he); if (zlinecmp(zt, str) < 0 && (*args || strcmp(zt, zlemetaline) != 0)) { if (--n <= 0) { unmetafy_line(); zle_setline(he); srch_hl = histline; srch_cs = zlecs; return 0; } } } unmetafy_line(); return 1; } /**/ int historysearchforward(char **args) { Histent he; int n = zmult; char *str; char *zt; if (zmult < 0) { int ret; zmult = -n; ret = historysearchbackward(args); zmult = n; return ret; } if (*args) { str = *args; } else { char *line = zlelineasstring(zleline, zlell, 0, NULL, NULL, 0); if (histline == curhist || histline != srch_hl || zlecs != srch_cs || mark != 0 || strncmp(srch_str, line, histpos) != 0) { free(srch_str); for (histpos = 0; histpos < zlell && !ZC_iblank(zleline[histpos]); histpos++) ; if (histpos < zlell) histpos++; CCRIGHT(); srch_str = zlelineasstring(zleline, histpos, 0, NULL, NULL, 0); } free(line); str = srch_str; } if (!(he = quietgethist(histline))) return 1; metafy_line(); while ((he = movehistent(he, 1, hist_skip_flags))) { if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP) continue; zt = GETZLETEXT(he); if (zlinecmp(zt, str) < (he->histnum == curhist) && (*args || strcmp(zt, zlemetaline) != 0)) { if (--n <= 0) { unmetafy_line(); zle_setline(he); srch_hl = histline; srch_cs = zlecs; return 0; } } } unmetafy_line(); return 1; } /**/ int beginningofbufferorhistory(char **args) { if (findbol()) zlecs = 0; else return beginningofhistory(args); return 0; } /**/ int beginningofhistory(UNUSED(char **args)) { if (!zle_goto_hist(firsthist(), 0, 0) && isset(HISTBEEP)) return 1; return 0; } /**/ int endofbufferorhistory(char **args) { if (findeol() != zlell) zlecs = zlell; else return endofhistory(args); return 0; } /**/ int endofhistory(UNUSED(char **args)) { zle_goto_hist(curhist, 0, 0); return 0; } /**/ int insertlastword(char **args) { int n, nwords, histstep = -1, wordpos = 0, deleteword = 0, len; char *s, *t; Histent he = NULL; LinkList l = NULL; LinkNode node; ZLE_STRING_T zs; static char *lastinsert; static int lasthist, lastpos, lastlen; int evhist; /* * If we have at least one argument, the first is the history * step. The default is -1 (go back). Repeated calls take * a step in this direction. A value of 0 is allowed and doesn't * move the line. * * If we have two arguments, the second is the position of * the word to extract, 1..N. The default is to use the * numeric argument, or the last word if that is not set. * * If we have three arguments, we reset the history pointer to * the current history event before applying the history step. */ if (*args) { histstep = (int)zstrtol(*args, NULL, 10); if (*++args) { wordpos = (int)zstrtol(*args, NULL, 10); if (*++args) lasthist = curhist; } } fixsuffix(); metafy_line(); if (lastinsert && lastlen && lastpos <= zlemetacs && lastlen == zlemetacs - lastpos && memcmp(lastinsert, &zlemetaline[lastpos], lastlen) == 0) deleteword = 1; else lasthist = curhist; evhist = histstep ? addhistnum(lasthist, histstep, HIST_FOREIGN) : lasthist; if (evhist == curhist) { /* * The line we are currently editing. If we are going to * replace an existing word, delete the old one now to avoid * confusion. */ if (deleteword) { int pos = zlemetacs; zlemetacs = lastpos; foredel(pos - zlemetacs, CUT_RAW); /* * Mark that this has been deleted. * For consistency with history lines, we really ought to * insert it back if the current command later fails. But * - we can't be bothered * - the problem that this can screw up going to other * lines in the history because we don't update * the history line isn't really relevant * - you can see what you're copying, dammit, so you * shouldn't make errors. * Of course, I could have implemented it in the time * it took to say why I haven't. */ deleteword = 0; } /* * Can only happen fail if the line is empty, I hope. * In that case, we don't need to worry about restoring * a deleted word, because that can only have come * from a non-empty line. I think. */ if (!(l = bufferwords(NULL, NULL, NULL, 0))) { unmetafy_line(); return 1; } nwords = countlinknodes(l); } else { /* Some stored line. By default, search for a non-empty line. */ while ((he = quietgethist(evhist)) && histstep == -1 && !*args) { if (he->nwords) break; evhist = addhistnum(evhist, histstep, HIST_FOREIGN); } if (!he || !he->nwords) { unmetafy_line(); return 1; } nwords = he->nwords; } if (wordpos) { n = (wordpos > 0) ? wordpos : nwords + wordpos + 1; } else if (zmult > 0) { n = nwords - (zmult - 1); } else { n = 1 - zmult; } if (n < 1 || n > nwords) { /* * We can't put in the requested word, but we did find the * history entry, so we remember the position in the history * list. This avoids getting stuck on a history line with * fewer words than expected. The cursor location cs * has not changed, and lastinsert is still valid. */ lasthist = evhist; unmetafy_line(); return 1; } /* * Only remove the old word from the command line if we have * successfully found a new one to insert. */ if (deleteword > 0) { int pos = zlemetacs; zlemetacs = lastpos; foredel(pos - zlemetacs, CUT_RAW); } if (lastinsert) { zfree(lastinsert, lastlen); lastinsert = NULL; } if (l) { for (node = firstnode(l); --n; incnode(node)) ; s = (char *)getdata(node); t = s + strlen(s); } else { s = he->node.nam + he->words[2*n-2]; t = he->node.nam + he->words[2*n-1]; } lasthist = evhist; lastpos = zlemetacs; /* ignore trailing whitespace */ lastlen = t - s; lastinsert = zalloc(t - s); memcpy(lastinsert, s, lastlen); n = zmult; zmult = 1; unmetafy_line(); zs = stringaszleline(dupstrpfx(s, t - s), 0, &len, NULL, NULL); doinsert(zs, len); free(zs); zmult = n; return 0; } /**/ void zle_setline(Histent he) { int remetafy; if (zlemetaline) { unmetafy_line(); remetafy = 1; } else remetafy = 0; remember_edits(); mkundoent(); histline = he->histnum; setline(GETZLETEXT(he), ZSL_COPY|ZSL_TOEND); zlecallhook("zle-history-line-set", NULL); setlastline(); clearlist = 1; if (remetafy) metafy_line(); } /**/ int setlocalhistory(UNUSED(char **args)) { if (zmod.flags & MOD_MULT) { hist_skip_flags = zmult? HIST_FOREIGN : 0; } else { hist_skip_flags ^= HIST_FOREIGN; } return 0; } /**/ int zle_goto_hist(int ev, int n, int skipdups) { Histent he = quietgethist(ev); char *line = zlelineasstring(zleline, zlell, 0, NULL, NULL, 1); if (!he || !(he = movehistent(he, n, hist_skip_flags))) return 1; if (skipdups && n) { n = n < 0? -1 : 1; while (he) { int ret; ret = zlinecmp(GETZLETEXT(he), line); if (ret) break; he = movehistent(he, n, hist_skip_flags); } } if (!he) return 0; zle_setline(he); return 1; } /**/ int pushline(UNUSED(char **args)) { int n = zmult; if (n < 0) return 1; zpushnode(bufstack, zlelineasstring(zleline, zlell, 0, NULL, NULL, 0)); while (--n) zpushnode(bufstack, ztrdup("")); if (invicmdmode()) INCCS(); stackcs = zlecs; *zleline = ZWC('\0'); zlell = zlecs = 0; clearlist = 1; return 0; } /**/ int pushlineoredit(char **args) { int ics, ret; ZLE_STRING_T s; char *hline = hgetline(); if (zmult < 0) return 1; if (hline && *hline) { ZLE_STRING_T zhline = stringaszleline(hline, 0, &ics, NULL, NULL); sizeline(ics + zlell + 1); /* careful of overlapping copy */ for (s = zleline + zlell; --s >= zleline; s[ics] = *s) ; ZS_memcpy(zleline, zhline, ics); zlell += ics; zlecs += ics; free(zhline); } ret = pushline(args); if (!isfirstln) { errflag |= ERRFLAG_ERROR|ERRFLAG_INT; done = 1; } clearlist = 1; return ret; } /**/ int pushinput(char **args) { int i, ret; if (zmult < 0) return 1; zmult += i = !isfirstln; ret = pushlineoredit(args); zmult -= i; return ret; } /* Renamed to avoid clash with library function */ /**/ int zgetline(UNUSED(char **args)) { char *s = getlinknode(bufstack); if (!s) { return 1; } else { int cc; ZLE_STRING_T lineadd = stringaszleline(s, 0, &cc, NULL, NULL); spaceinline(cc); ZS_memcpy(zleline + zlecs, lineadd, cc); zlecs += cc; free(s); free(lineadd); clearlist = 1; /* not restoring stackhist as we're inserting into current line */ stackhist = -1; } return 0; } /**/ int historyincrementalsearchbackward(char **args) { return doisearch(args, -1, 0); } /**/ int historyincrementalsearchforward(char **args) { return doisearch(args, 1, 0); } /**/ int historyincrementalpatternsearchbackward(char **args) { return doisearch(args, -1, 1); } /**/ int historyincrementalpatternsearchforward(char **args) { return doisearch(args, 1, 1); } static struct isrch_spot { int hl; /* This spot's histline */ int pat_hl; /* histline where pattern search started */ unsigned short pos; /* The search position in our metafied str */ unsigned short pat_pos; /* pos where pattern search started */ unsigned short end_pos; /* The position of the end of the matched str */ unsigned short cs; /* The visible search position to the user */ unsigned short len; /* The search string's length */ unsigned short flags; /* This spot's flags */ #define ISS_FORWARD 1 #define ISS_NOMATCH_SHIFT 1 } *isrch_spots; static int max_spot = 0; /**/ void free_isrch_spots(void) { zfree(isrch_spots, max_spot * sizeof(*isrch_spots)); max_spot = 0; isrch_spots = NULL; } /**/ static void set_isrch_spot(int num, int hl, int pos, int pat_hl, int pat_pos, int end_pos, int cs, int len, int dir, int nomatch) { if (num >= max_spot) { if (!isrch_spots) { isrch_spots = (struct isrch_spot*) zalloc((max_spot = 64) * sizeof *isrch_spots); } else { isrch_spots = (struct isrch_spot*)realloc((char*)isrch_spots, (max_spot += 64) * sizeof *isrch_spots); } } isrch_spots[num].hl = hl; isrch_spots[num].pos = (unsigned short)pos; isrch_spots[num].pat_hl = pat_hl; isrch_spots[num].pat_pos = (unsigned short)pat_pos; isrch_spots[num].end_pos = (unsigned short)end_pos; isrch_spots[num].cs = (unsigned short)cs; isrch_spots[num].len = (unsigned short)len; isrch_spots[num].flags = (dir > 0? ISS_FORWARD : 0) + (nomatch << ISS_NOMATCH_SHIFT); } /**/ static void get_isrch_spot(int num, int *hlp, int *posp, int *pat_hlp, int *pat_posp, int *end_posp, int *csp, int *lenp, int *dirp, int *nomatch) { *hlp = isrch_spots[num].hl; *posp = (int)isrch_spots[num].pos; *pat_hlp = isrch_spots[num].pat_hl; *pat_posp = (int)isrch_spots[num].pat_pos; *end_posp = (int)isrch_spots[num].end_pos; *csp = (int)isrch_spots[num].cs; *lenp = (int)isrch_spots[num].len; *dirp = (isrch_spots[num].flags & ISS_FORWARD)? 1 : -1; *nomatch = (int)(isrch_spots[num].flags >> ISS_NOMATCH_SHIFT); } /* * In pattern search mode, look through the list for a match at, or * before or after the given position, according to the direction. * Return new position or -1. * * Note this handles curpos out of range correctly, i.e. curpos < 0 * never matches when searching backwards and curpos > length of string * never matches when searching forwards. */ static int isearch_newpos(LinkList matchlist, int curpos, int dir, int *endmatchpos) { LinkNode node; if (dir < 0) { for (node = lastnode(matchlist); node != (LinkNode)matchlist; decnode(node)) { Repldata rdata = (Repldata)getdata(node); if (rdata->b <= curpos) { *endmatchpos = rdata->e; return rdata->b; } } } else { for (node = firstnode(matchlist); node; incnode(node)) { Repldata rdata = (Repldata)getdata(node); if (rdata->b >= curpos) { *endmatchpos = rdata->e; return rdata->b; } } } return -1; } /* * Save an isearch buffer from sbuf to sbuf+sbptr * into the string *search with length *searchlen. * searchlen may be NULL; the string is a NULL-terminated metafied string. */ static void save_isearch_buffer(char *sbuf, int sbptr, char **search, int *searchlen) { if (*search) free(*search); *search = zalloc(sbptr+1); memcpy(*search, sbuf, sbptr); if (searchlen) *searchlen = sbptr; (*search)[sbptr] = '\0'; } #define ISEARCH_PROMPT "XXXXXXX XXX-i-search: " #define FAILING_TEXT "failing" #define INVALID_TEXT "invalid" #define BAD_TEXT_LEN 7 #define NORM_PROMPT_POS (BAD_TEXT_LEN+1) #define FIRST_SEARCH_CHAR (NORM_PROMPT_POS + 14) /**/ int isearch_active, isearch_startpos, isearch_endpos; /**/ static int doisearch(char **args, int dir, int pattern) { /* The full search buffer, including space for all prompts */ char *ibuf = zhalloc(80); /* * The part of the search buffer with the search string. * This is a normal metafied string. */ char *sbuf = ibuf + FIRST_SEARCH_CHAR; /* The previous line shown to the user */ char *last_line = NULL; /* Text of the history line being examined */ char *zt; /* * sbptr: index into sbuf. * top_spot: stack index into the "isrch_spot" stack. * sibuf: allocation size for ibuf */ int sbptr = 0, top_spot = 0, sibuf = 80; /* * nomatch = 1: failing isearch * nomatch = 2: invalid pattern * skip_line: finished with current line, skip to next * skip_pos: keep current line but try before/after current position. */ int nomatch = 0, skip_line = 0, skip_pos = 0; /* * odir: original search direction * sens: limit for zlinecmp to allow (3) or disallow (1) lower case * matching upper case. */ int odir = dir, sens = zmult == 1 ? 3 : 1; /* * hl: the number of the history line we are looking at * pos: the character position into it. On backward matches the * cursor will be set to this; on forward matches to the end * of the matched string */ int hl = histline, pos; /* * The value of hl and pos at which the last pattern match * search started. We need to record these because there's * a pathology with pattern matching. Here's an example. Suppose * the history consists of: * echo '*OH NO*' * echo '\n' * echo "*WHAT?*" * <...backward pattern search starts here...> * The user types "\". As there's nothing after it it's treated * literally (and I certainly don't want to change that). This * goes to the second line. Then the user types "*". This * ought to match the "*" in the line immediately before where the * search started. However, unless we return to that line for the * new search it will instead carry on to the first line. This is * different from straight string matching where we never have * to backtrack. * * I think these need resetting to the current hl and pos when * we start a new search or repeat a search. It seems to work, * anyway. * * We could optimize this more, but I don't think there's a lot * of point. (Translation: it's difficult.) */ int pat_hl = hl, pat_pos; /* * This is the flag that we need to revert the positions to * the above for the next pattern search. */ int revert_patpos = 0; /* * Another nasty feature related to the above. When * we revert the position, we might advance the search to * the same line again. When we do this the test for ignoring * duplicates may trigger. This flag indicates that in this * case it's OK. */ int dup_ok = 0; /* * End position of the match. * When forward matching, this is the position for the cursor. * When backward matching, the cursor position is pos. */ int end_pos = 0; /* * savekeys records the unget buffer, so that if we have arguments * they don't pollute the input. * feep indicates we should feep. This is a well-known word * meaning "to indicate an error in the zsh line editor". */ int savekeys = -1, feep = 0; /* Flag that we are at an old position, no need to search again */ int nosearch = 0; /* Command read as input: we don't read characters directly. */ Thingy cmd; /* Save the keymap if necessary */ char *okeymap; /* The current history entry, corresponding to hl */ Histent he; /* When pattern matching, the compiled pattern */ Patprog patprog = NULL; /* When pattern matching, the list of match positions */ LinkList matchlist = NULL; /* * When we exit isearching this may be a zle command to * execute. We save it and execute it after unmetafying the * command line. */ ZleIntFunc exitfn = (ZleIntFunc)0; /* * Flag that the search was aborted. */ int aborted = 0; if (!(he = quietgethist(hl))) return 1; selectlocalmap(isearch_keymap); clearlist = 1; if (*args) { int len; char *arg; savekeys = kungetct; arg = getkeystring(*args, &len, GETKEYS_BINDKEY, NULL); ungetbytes(arg, len); } strcpy(ibuf, ISEARCH_PROMPT); /* careful with fwd/bck: we don't want the NULL copied */ memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); okeymap = ztrdup(curkeymapname); selectkeymap("main", 1); metafy_line(); remember_edits(); zt = GETZLETEXT(he); pat_pos = pos = zlemetacs; for (;;) { /* Remember the current values in case search fails (doesn't push). */ set_isrch_spot(top_spot, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); if (sbptr == 1 && sbuf[0] == '^') { zlemetacs = 0; nomatch = 0; statusline = ibuf + NORM_PROMPT_POS; } else if (sbptr > 0) { /* The matched text, used as flag that we matched */ char *t = NULL; last_line = zt; sbuf[sbptr] = '\0'; if (pattern && !patprog && !nosearch) { /* avoid too much heap use, can get heavy round here... */ char *patbuf = ztrdup(sbuf); char *patstring; /* * Do not use static pattern buffer (PAT_STATIC) since we * call zle hooks, which might call other pattern * functions. Use PAT_ZDUP because we re-use the pattern * in subsequent loops, so we can't pushheap/popheap. * Use PAT_NOANCH because we don't need the match anchored * to the end, even if it is at the start. */ int patflags = PAT_ZDUP|PAT_NOANCH; if (sbuf[0] == '^') { /* * We'll handle the anchor later when * we call into the globbing code. */ patstring = patbuf + 1; } else { /* Scanning for multiple matches per line */ patflags |= PAT_SCAN; patstring = patbuf; } if (sens == 3) patflags |= PAT_LCMATCHUC; tokenize(patstring); remnulargs(patstring); patprog = patcompile(patstring, patflags, NULL); free(patbuf); if (matchlist) { freematchlist(matchlist); matchlist = NULL; } if (patprog) { revert_patpos = 1; skip_pos = 0; } else { if (nomatch != 2) { handlefeep(zlenoargs); nomatch = 2; } /* indicate "invalid" in status line */ memcpy(ibuf, INVALID_TEXT, BAD_TEXT_LEN); statusline = ibuf; } } /* * skip search if pattern compilation failed, or * if we back somewhere we already searched. */ while ((!pattern || patprog) && !nosearch) { if (patprog) { if (revert_patpos) { /* * Search from where the previous * search started; see note above. * This is down here within the loop because of * the "nosearch" optimisation. */ revert_patpos = 0; dup_ok = 1; he = quietgethist(hl = pat_hl); zt = GETZLETEXT(he); pos = pat_pos; } /* * We are pattern matching against the current * line. If anchored at the start, this is * easy; a single test suffices. * * Otherwise, our strategy is to retrieve a linked * list of all matches within the current line and * scan through it as appropriate. This isn't * actually significantly more efficient, but * it is algorithmically easier since we just * need a single one-off line-matching interface * to the pattern code. We use a variant of * the code used for replacing within parameters * which for historical reasons is in glob.c rather * than pattern.c. * * The code for deciding whether to skip something * is a bit icky but that sort of code always is. */ if (!skip_line) { if (sbuf[0] == '^') { /* * skip_pos applies to the whole line in * this mode. */ if (!skip_pos && pattryrefs(patprog, zt, -1, -1, NULL, 0, NULL, NULL, &end_pos)) t = zt; } else { if (!matchlist && !skip_pos) { if (!getmatchlist(zt, patprog, &matchlist) || !firstnode(matchlist)) { if (matchlist) { freematchlist(matchlist); matchlist = NULL; } } } if (matchlist) { int newpos; if (!skip_pos) { /* OK to match at current pos */ newpos = pos; } else { if (dir < 0) newpos = pos - 1; else newpos = pos + 1; } newpos = isearch_newpos(matchlist, newpos, dir, &end_pos); /* need a new list next time if off the end */ if (newpos < 0) { freematchlist(matchlist); matchlist = NULL; } else { t = zt + newpos; } } } } skip_pos = 0; } else { /* * If instructed, move past a match position: * backwards if searching backwards (skipping * the line if we're at the start), forwards * if searching forwards (skipping a line if we're * at the end). */ if (skip_pos) { if (dir < 0) { if (pos == 0) skip_line = 1; else pos = backwardmetafiedchar(zlemetaline, zlemetaline + pos, NULL) - zlemetaline; } else if (sbuf[0] != '^') { if (pos >= (int)strlen(zt) - 1) skip_line = 1; else pos += 1; } else skip_line = 1; skip_pos = 0; } /* * First search for a(nother) match within the * current line, unless we've been told to skip it. */ if (!skip_line) { if (sbuf[0] == '^') { if (zlinecmp(zt, sbuf + 1) < sens) t = zt; } else t = zlinefind(zt, pos, sbuf, dir, sens); if (t) end_pos = (t - zt) + sbptr - (sbuf[0] == '^'); } } if (t) { pos = t - zt; break; } /* * If not found within that line, move through * the history to try again. */ if (!(zlereadflags & ZLRF_HISTORY) || !(he = movehistent(he, dir, hist_skip_flags))) { if (sbptr == (int)isrch_spots[top_spot-1].len && (isrch_spots[top_spot-1].flags >> ISS_NOMATCH_SHIFT)) top_spot--; get_isrch_spot(top_spot, &hl, &pos, &pat_hl, &pat_pos, &end_pos, &zlemetacs, &sbptr, &dir, &nomatch); if (nomatch != 1) { feep = 1; nomatch = 1; } he = quietgethist(hl); zt = GETZLETEXT(he); skip_line = 0; /* indicate "failing" in status line */ memcpy(ibuf, nomatch == 2 ? INVALID_TEXT :FAILING_TEXT, BAD_TEXT_LEN); statusline = ibuf; break; } hl = he->histnum; zt = GETZLETEXT(he); pos = (dir == 1) ? 0 : strlen(zt); if (dup_ok) skip_line = 0; else skip_line = isset(HISTFINDNODUPS) ? !!(he->node.flags & HIST_DUP) : !strcmp(zt, last_line); } dup_ok = 0; /* * If we matched above (t set), set the new line. * If we didn't, but are here because we are on a previous * match (nosearch set and nomatch not, set the line again). */ if (t || (nosearch && !nomatch)) { zle_setline(he); if (dir == 1) zlemetacs = end_pos; else zlemetacs = pos; statusline = ibuf + NORM_PROMPT_POS; nomatch = 0; } } else { top_spot = 0; nomatch = 0; statusline = ibuf + NORM_PROMPT_POS; } nosearch = 0; if (feep) { handlefeep(zlenoargs); feep = 0; } sbuf[sbptr] = '_'; sbuf[sbptr+1] = '\0'; if (!nomatch && sbptr && (sbptr > 1 || sbuf[0] != '^')) { #ifdef MULTIBYTE_SUPPORT int charpos = 0, charcount = 0, ret; wint_t wc; mbstate_t mbs; /* * Count unmetafied character positions for the * start and end of the match for the benefit of * highlighting. */ memset(&mbs, 0, sizeof(mbs)); while (charpos < end_pos) { ret = mb_metacharlenconv_r(zlemetaline + charpos, &wc, &mbs); if (ret <= 0) /* Unrecognised, treat as single char */ ret = 1; if (charpos <= pos && pos < charpos + ret) isearch_startpos = charcount; charcount++; charpos += ret; } isearch_endpos = charcount; #else isearch_startpos = ztrsub(zlemetaline + pos, zlemetaline); isearch_endpos = ztrsub(zlemetaline + end_pos, zlemetaline); #endif isearch_active = 1; } else isearch_active = 0; ref: zlecallhook("zle-isearch-update", NULL); redrawhook(); zrefresh(); if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { int i; aborted = 1; save_isearch_buffer(sbuf, sbptr, &previous_aborted_search, NULL); get_isrch_spot(0, &hl, &pos, &pat_hl, &pat_pos, &end_pos, &i, &sbptr, &dir, &nomatch); he = quietgethist(hl); zle_setline(he); zt = GETZLETEXT(he); zlemetacs = i; break; } if(cmd == Th(z_clearscreen)) { clearscreen(zlenoargs); goto ref; } else if(cmd == Th(z_redisplay)) { redisplay(zlenoargs); goto ref; } else if(cmd == Th(z_vicmdmode)) { if(selectkeymap(invicmdmode() ? "main" : "vicmd", 0)) feep = 1; goto ref; } else if (cmd == Th(z_vibackwarddeletechar) || cmd == Th(z_backwarddeletechar) || cmd == Th(z_vibackwardkillword) || cmd == Th(z_backwardkillword) || cmd == Th(z_backwarddeleteword)) { int only_one = (cmd == Th(z_vibackwarddeletechar) || cmd == Th(z_backwarddeletechar)); int old_sbptr = sbptr; if (top_spot) { for (;;) { get_isrch_spot(--top_spot, &hl, &pos, &pat_hl, &pat_pos, &end_pos, &zlemetacs, &sbptr, &dir, &nomatch); if (only_one || !top_spot || old_sbptr != sbptr) break; } freepatprog(patprog); patprog = NULL; nosearch = 1; skip_pos = 0; } else feep = 1; if (nomatch) { memcpy(ibuf, nomatch == 2 ? INVALID_TEXT : FAILING_TEXT, BAD_TEXT_LEN); statusline = ibuf; skip_pos = 1; } he = quietgethist(hl); zt = GETZLETEXT(he); /* * Set the line for the cases where we won't go past * the usual line-setting logic: if we're not on a match, * or if we don't have enough to search for. */ if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) { int i = zlemetacs; zle_setline(he); zlemetacs = i; } memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); continue; } else if(cmd == Th(z_acceptandhold)) { exitfn = acceptandhold; break; } else if(cmd == Th(z_acceptandinfernexthistory)) { exitfn = acceptandinfernexthistory; break; } else if(cmd == Th(z_acceptlineanddownhistory)) { exitfn = acceptlineanddownhistory; break; } else if(cmd == Th(z_acceptline)) { exitfn = acceptline; break; } else if(cmd == Th(z_historyincrementalsearchbackward) || cmd == Th(z_historyincrementalpatternsearchbackward)) { pat_hl = hl; pat_pos = pos; set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); if (dir != -1) dir = -1; else skip_pos = 1; goto rpt; } else if(cmd == Th(z_historyincrementalsearchforward) || cmd == Th(z_historyincrementalpatternsearchforward)) { pat_hl = hl; pat_pos = pos; set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); if (dir != 1) dir = 1; else skip_pos = 1; goto rpt; } else if(cmd == Th(z_virevrepeatsearch)) { pat_hl = hl; pat_pos = pos; set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); dir = -odir; skip_pos = 1; goto rpt; } else if(cmd == Th(z_virepeatsearch)) { pat_hl = hl; pat_pos = pos; set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); dir = odir; skip_pos = 1; rpt: if (!sbptr && previous_search_len && dir == odir) { if (previous_search_len > sibuf - FIRST_SEARCH_CHAR - 2) { ibuf = hrealloc((char *)ibuf, sibuf, (sibuf + previous_search_len)); sbuf = ibuf + FIRST_SEARCH_CHAR; sibuf += previous_search_len; } memcpy(sbuf, previous_search, sbptr = previous_search_len); } memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); continue; } else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) { if(cmd == Th(z_viquotedinsert)) { sbuf[sbptr] = '^'; sbuf[sbptr+1] = '\0'; zrefresh(); } if (getfullchar(0) == ZLEEOF) feep = 1; else goto ins; } else if (cmd == Th(z_bracketedpaste)) { char *paste = bracketedstring(); set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); size_t pastelen = strlen(paste); if (sbptr + pastelen >= sibuf - FIRST_SEARCH_CHAR - 2) { int oldsize = sibuf; sibuf += (pastelen >= sibuf) ? pastelen + 1 : sibuf; ibuf = hrealloc(ibuf, oldsize, sibuf); sbuf = ibuf + FIRST_SEARCH_CHAR; } strcpy(sbuf + sbptr, paste); sbptr += pastelen; freepatprog(patprog); patprog = NULL; free(paste); } else if (cmd == Th(z_acceptsearch)) { break; } else { if(cmd == Th(z_selfinsertunmeta)) { fixunmeta(); } else if (cmd == Th(z_magicspace)) { fixmagicspace(); } else if (cmd == Th(z_selfinsert)) { #ifdef MULTIBYTE_SUPPORT if (!lastchar_wide_valid) if (getrestchar(lastchar, NULL, NULL) == WEOF) { handlefeep(zlenoargs); continue; } #else ; #endif } else { ungetkeycmd(); if (cmd == Th(z_sendbreak)) { aborted = 1; save_isearch_buffer(sbuf, sbptr, &previous_aborted_search, NULL); sbptr = 0; } break; } ins: if (sbptr == PATH_MAX) { feep = 1; continue; } set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, zlemetacs, sbptr, dir, nomatch); if (sbptr >= sibuf - FIRST_SEARCH_CHAR - 2 #ifdef MULTIBYTE_SUPPORT - 2 * (int)MB_CUR_MAX #endif ) { ibuf = hrealloc(ibuf, sibuf, sibuf * 2); sbuf = ibuf + FIRST_SEARCH_CHAR; sibuf *= 2; } /* * We've supposedly arranged above that lastchar_wide is * always valid at this point. */ sbptr += zlecharasstring(LASTFULLCHAR, sbuf + sbptr); freepatprog(patprog); patprog = NULL; } if (feep) handlefeep(zlenoargs); feep = 0; } if (sbptr) { save_isearch_buffer(sbuf, sbptr, &previous_search, &previous_search_len); } statusline = NULL; unmetafy_line(); zlecallhook("zle-isearch-exit", NULL); redrawhook(); if (exitfn) exitfn(zlenoargs); selectkeymap(okeymap, 1); zsfree(okeymap); if (matchlist) freematchlist(matchlist); freepatprog(patprog); isearch_active = 0; /* * Don't allow unused characters provided as a string to the * widget to overflow and be used as separated commands. */ if (savekeys >= 0 && kungetct > savekeys) kungetct = savekeys; selectlocalmap(NULL); return aborted ? 3 : nomatch; } static Histent infernexthist(Histent he, UNUSED(char **args)) { metafy_line(); for (he = movehistent(he, -2, HIST_FOREIGN); he; he = movehistent(he, -1, HIST_FOREIGN)) { if (!zlinecmp(GETZLETEXT(he), zlemetaline)) { unmetafy_line(); return movehistent(he, 1, HIST_FOREIGN); } } unmetafy_line(); return NULL; } /**/ int acceptandinfernexthistory(char **args) { Histent he; if (!(he = infernexthist(hist_ring, args))) return 1; zpushnode(bufstack, ztrdup(he->node.nam)); done = 1; stackhist = he->histnum; return 0; } /**/ int infernexthistory(char **args) { Histent he = quietgethist(histline); if (!he || !(he = infernexthist(he, args))) return 1; zle_setline(he); return 0; } /**/ int vifetchhistory(UNUSED(char **args)) { if (zmult < 0) return 1; if (histline == curhist) { if (!(zmod.flags & MOD_MULT)) { zlecs = zlell; zlecs = findbol(); return 0; } } if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist, 0, 0) && isset(HISTBEEP)) { return 1; } return 0; } /* the last vi search */ static char *visrchstr, *vipenultsrchstr; static int visrchsense; /**/ static int getvisrchstr(void) { char *sbuf = zhalloc(80); int sptr = 1, ret = 0, ssbuf = 80, feep = 0; Thingy cmd; char *okeymap = ztrdup(curkeymapname); if (vipenultsrchstr) { zsfree(vipenultsrchstr); vipenultsrchstr = NULL; } if (visrchstr) { vipenultsrchstr = visrchstr; visrchstr = NULL; } clearlist = 1; statusline = sbuf; sbuf[0] = (visrchsense == -1) ? '?' : '/'; selectkeymap("main", 1); while (sptr) { sbuf[sptr] = '_'; sbuf[sptr+1] = '\0'; zrefresh(); if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { ret = 0; break; } if(cmd == Th(z_magicspace)) { fixmagicspace(); cmd = Th(z_selfinsert); } if(cmd == Th(z_redisplay)) { redisplay(zlenoargs); } else if(cmd == Th(z_clearscreen)) { clearscreen(zlenoargs); } else if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) { sbuf[sptr] = ZWC('\0'); visrchstr = ztrdup(sbuf+1); if (!strlen(visrchstr)) { zsfree(visrchstr); visrchstr = ztrdup(vipenultsrchstr); } ret = 1; sptr = 0; } else if(cmd == Th(z_backwarddeletechar) || cmd == Th(z_vibackwarddeletechar)) { sptr = backwardmetafiedchar(sbuf+1, sbuf+sptr, NULL) - sbuf; } else if(cmd == Th(z_backwardkillword) || cmd == Th(z_vibackwardkillword)) { convchar_t cc; char *newpos; while (sptr != 1) { newpos = backwardmetafiedchar(sbuf+1, sbuf+sptr, &cc); if (!ZC_iblank(cc)) break; sptr = newpos - sbuf; } if (sptr > 1) { newpos = backwardmetafiedchar(sbuf+1, sbuf+sptr, &cc); if (ZC_iident(cc)) { for (;;) { sptr = newpos - sbuf; if (sptr == 1) break; newpos = backwardmetafiedchar(sbuf+1, sbuf+sptr, &cc); if (!ZC_iident(cc)) break; } } else { for (;;) { sptr = newpos - sbuf; if (sptr == 1) break; newpos = backwardmetafiedchar(sbuf+1, sbuf+sptr, &cc); if (ZC_iident(cc) || ZC_iblank(cc)) break; } } } } else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) { if(cmd == Th(z_viquotedinsert)) { sbuf[sptr] = '^'; zrefresh(); } if (getfullchar(0) == ZLEEOF) feep = 1; else goto ins; } else if(cmd == Th(z_selfinsertunmeta) || cmd == Th(z_selfinsert)) { if(cmd == Th(z_selfinsertunmeta)) { fixunmeta(); } else { #ifdef MULTIBYTE_SUPPORT if (!lastchar_wide_valid) if (getrestchar(lastchar, NULL, NULL) == WEOF) { handlefeep(zlenoargs); continue; } #else ; #endif } ins: if (sptr == ssbuf - 1) { char *newbuf = (char *)zhalloc((ssbuf *= 2)); strcpy(newbuf, sbuf); statusline = sbuf = newbuf; } sptr += zlecharasstring(LASTFULLCHAR, sbuf + sptr); } else { feep = 1; } if (feep) handlefeep(zlenoargs); feep = 0; } statusline = NULL; selectkeymap(okeymap, 1); zsfree(okeymap); return ret; } /**/ int vihistorysearchforward(char **args) { if (*args) { int ose = visrchsense, ret; char *ost = visrchstr; visrchsense = 1; visrchstr = *args; ret = virepeatsearch(zlenoargs); visrchsense = ose; visrchstr = ost; return ret; } visrchsense = 1; if (getvisrchstr()) return virepeatsearch(zlenoargs); return 1; } /**/ int vihistorysearchbackward(char **args) { if (*args) { int ose = visrchsense, ret; char *ost = visrchstr; visrchsense = -1; visrchstr = *args; ret = virepeatsearch(zlenoargs); visrchsense = ose; visrchstr = ost; return ret; } visrchsense = -1; if (getvisrchstr()) return virepeatsearch(zlenoargs); return 1; } /**/ int virepeatsearch(UNUSED(char **args)) { Histent he; int n = zmult; char *zt; if (!visrchstr) return 1; if (zmult < 0) { n = -n; visrchsense = -visrchsense; } if (!(he = quietgethist(histline))) return 1; metafy_line(); while ((he = movehistent(he, visrchsense, hist_skip_flags))) { if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP) continue; zt = GETZLETEXT(he); if (zlinecmp(zt, zlemetaline) && (*visrchstr == '^' ? strpfx(visrchstr + 1, zt) : zlinefind(zt, 0, visrchstr, 1, 1) != 0)) { if (--n <= 0) { unmetafy_line(); zle_setline(he); return 0; } } } unmetafy_line(); return 1; } /**/ int virevrepeatsearch(char **args) { int ret; visrchsense = -visrchsense; ret = virepeatsearch(args); visrchsense = -visrchsense; return ret; } /* Extra function added by A.R. Iano-Fletcher. */ /*The extern variable "zlecs" is the position of the cursor. */ /* history-beginning-search-backward */ /**/ int historybeginningsearchbackward(char **args) { Histent he; int cpos = zlecs; /* save cursor position */ int n = zmult; char *zt; if (zmult < 0) { int ret; zmult = -n; ret = historybeginningsearchforward(args); zmult = n; return ret; } if (!(he = quietgethist(histline))) return 1; metafy_line(); while ((he = movehistent(he, -1, hist_skip_flags))) { int tst; char sav; if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP) continue; zt = GETZLETEXT(he); sav = zlemetaline[zlemetacs]; zlemetaline[zlemetacs] = '\0'; tst = zlinecmp(zt, zlemetaline); zlemetaline[zlemetacs] = sav; if (tst < 0 && zlinecmp(zt, zlemetaline)) { if (--n <= 0) { unmetafy_line(); zle_setline(he); zlecs = cpos; CCRIGHT(); return 0; } } } unmetafy_line(); return 1; } /* Extra function added by A.R. Iano-Fletcher. */ /* history-beginning-search-forward */ /**/ int historybeginningsearchforward(char **args) { Histent he; int cpos = zlecs; /* save cursor position */ int n = zmult; char *zt; if (zmult < 0) { int ret; zmult = -n; ret = historybeginningsearchbackward(args); zmult = n; return ret; } if (!(he = quietgethist(histline))) return 1; metafy_line(); while ((he = movehistent(he, 1, hist_skip_flags))) { char sav; int tst; if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP) continue; zt = GETZLETEXT(he); sav = zlemetaline[zlemetacs]; zlemetaline[zlemetacs] = '\0'; tst = zlinecmp(zt, zlemetaline) < (he->histnum == curhist); zlemetaline[zlemetacs] = sav; if (tst && zlinecmp(zt, zlemetaline)) { if (--n <= 0) { unmetafy_line(); zle_setline(he); zlecs = cpos; CCRIGHT(); return 0; } } } unmetafy_line(); return 1; }