/* * Search & Replace system * Copyright * (C) 1992 Joseph H. Allen * * This file is part of JOE (Joe's Own Editor) */ #include "config.h" #include "types.h" __RCSID("$MirOS: contrib/code/jupp/usearch.c,v 1.21 2017/12/16 22:10:55 tg Exp $"); #include #include "b.h" #include "bw.h" #include "main.h" #include "pw.h" #include "queue.h" #include "qw.h" #include "regex.h" #include "ublock.h" #include "uedit.h" #include "undo.h" #include "usearch.h" #include "utils.h" #include "vs.h" #include "charmap.h" #include "w.h" #include "va.h" #include "tty.h" #include "menu.h" #include "hash.h" int wrap = 0; /* Allow wrap */ int smode = 0; /* Decremented to zero by execmd */ int csmode = 0; /* Set for continued search mode */ int icase = 0; /* Set to force case insensitive search */ B *findhist = NULL; /* Search string history */ B *replhist = NULL; /* Replacement string history */ SRCH *globalsrch = NULL; /* Most recent completed search data */ SRCHREC fsr = { {&fsr, &fsr}, 0, 0, 0 }; /* Completion stuff: should go somewhere else */ unsigned char **word_list; #define MAX_WORD_SIZE 64 static unsigned char ** get_word_list(B *b, int ignore) { unsigned char buf[MAX_WORD_SIZE]; unsigned char *s; unsigned char **list = 0; HASH *h; HENTRY *t; P *p; int c; int idx; int start = 0; h = htmk(1024); p = pdup(b->bof); idx = 0; while ((c=pgetc(p))!=NO_MORE_DATA) if (idx) { if (joe_isalnux(b->o.charmap, c)) { if (idx!=MAX_WORD_SIZE) buf[idx++] = c; } else { if (idx!=MAX_WORD_SIZE && start!=ignore) { buf[idx] = 0; if (!htfind(h,buf)) { s = vsncpy(NULL,0,buf,idx); htadd(h, s, s); } } idx = 0; } } else { start=p->byte-1; if (joe_isalphx(b->o.charmap, c)) buf[idx++] = c; } prm(p); for (idx = 0;idx != h->len;++idx) for (t = h->tab[idx];t;t=t->next) list = vaadd(list, /* checked */ US t->name); if (list) vasort(list, sLEN(list)); htrm(h); return list; } static void fcmplt_ins(BW *bw, unsigned char *line) { P *p; int c; if (!piseol(bw->cursor)) { c = brch(bw->cursor); if (joe_isalnux(bw->b->o.charmap,c)) return; } /* Move p to beginning of word */ p = pdup(bw->cursor); do c = prgetc(p); while (joe_isalnux(bw->b->o.charmap,c)); if (c!=NO_MORE_DATA) pgetc(p); if (bw->cursor->byte!=p->byte && bw->cursor->byte-p->byte<64) { /* Insert single match */ bdel(p,bw->cursor); binsm(bw->cursor,sv(line)); pfwrd(bw->cursor,sLEN(line)); bw->cursor->xcol = piscol(bw->cursor); prm(p); } else { prm(p); } } static int fcmplt_abrt(BW *bw, int x, unsigned char *line) { if (line) { fcmplt_ins(bw, line); vsrm(line); } return -1; } static int fcmplt_rtn(MENU *m, int x, unsigned char *line) { fcmplt_ins(m->parent->win->object.bw, m->list[x]); vsrm(line); m->object = NULL; wabort(m->parent); return 0; } int ufinish(BW *bw) { unsigned char *line; unsigned char *line1; unsigned char **lst; P *p; int c; MENU *m; /* Make sure we're not in a word */ if (!piseol(bw->cursor)) { c = brch(bw->cursor); if (joe_isalnux(bw->b->o.charmap,c)) return -1; } /* Move p to beginning of word */ p = pdup(bw->cursor); do c = prgetc(p); while (joe_isalnux(bw->b->o.charmap,c)); if (c!=NO_MORE_DATA) pgetc(p); if (bw->cursor->byte!=p->byte && bw->cursor->byte-p->byte<64) { line = brvs(p, bw->cursor->byte-p->byte); /* We have a word */ /* Get word list */ if (word_list) varm(word_list); word_list = get_word_list(bw->b, p->byte); if (!word_list) { vsrm(line); prm(p); return -1; } line1 = vsncpy(NULL,0,sv(line)); line1 = vsadd(line1,'*'); lst = regsub(word_list, aLEN(word_list), line1); vsrm(line1); if (!lst) { ttputc(7); vsrm(line); return -1; } m = mkmenu(bw->parent, lst, fcmplt_rtn, fcmplt_abrt, NULL, 0, line, NULL); if (!m) { varm(lst); vsrm(line); return -1; } /* Possible match list is now in lst */ if (aLEN(lst) == 1) return fcmplt_rtn(m, 0, line); else if (smode) return 0; else { unsigned char *com = mcomplete(m); vsrm(m->object); m->object = com; wabort(m->parent); smode = 2; ttputc(7); return 0; } } else { prm(p); return -1; } } static int srch_cmplt(BW *bw) { jobject jO; jO.bw = bw; utypebw(jO, 9); return 0; } /* Search forward. bw, pattern and ignore must be set The first possible string we can find is the one beginning under p Returns p if we found a string: The found string is placed in entire/pieces p is placed right after the found string Return 0 if we did not find the string: p is left in its orignal spot */ static P *searchf(BW *bw,SRCH *srch, P *p) { unsigned char *pattern = srch->pattern; P *start; P *end; int x; start = pdup(p); end = pdup(p); for (x = 0; x != sLEN(pattern) && pattern[x] != '\\' && (pattern[x]<128 || !p->b->o.charmap->type); ++x) if (srch->ignore) pattern[x] = joe_tolower(p->b->o.charmap,pattern[x]); wrapped: while (srch->ignore ? pifind(start, pattern, x) : pfind(start, pattern, x)) { pset(end, start); pfwrd(end, (long) x); if (srch->wrap_flag && start->byte>=srch->wrap_p->byte) break; if (pmatch(srch->pieces, pattern + x, sLEN(pattern) - x, end, 0, srch->ignore)) { srch->entire = vstrunc(srch->entire, (int) (end->byte - start->byte)); brmem(start, srch->entire, (int) (end->byte - start->byte)); pset(p, end); prm(start); prm(end); return p; } if (pgetc(start) == NO_MORE_DATA) break; } if (wrap && !srch->wrap_flag && srch->wrap_p) { msgnw(bw->parent, UC "Wrapped"); srch->wrap_flag = 1; p_goto_bof(start); goto wrapped; } prm(start); prm(end); return NULL; } /* Search backwards. bw, pattern and ignore must be set The first possible string we can find is the one beginning one position to the left of p. Returns 1 if we found a string: The found string is placed in entire p is placed at the beginning of the string Return 0 if we did not find the string: p is left in its orignal spot */ static P *searchb(BW *bw,SRCH *srch, P *p) { unsigned char *pattern = srch->pattern; P *start; P *end; int x; start = pdup(p); end = pdup(p); for (x = 0; x != sLEN(pattern) && pattern[x] != '\\' && (pattern[x]<128 || !p->b->o.charmap->type); ++x) if (srch->ignore) pattern[x] = joe_tolower(p->b->o.charmap,pattern[x]); wrapped: while (pbkwd(start, 1L) && (srch->ignore ? prifind(start, pattern, x) : prfind(start, pattern, x))) { pset(end, start); pfwrd(end, (long) x); if (srch->wrap_flag && start->bytewrap_p->byte) break; if (pmatch(srch->pieces, pattern + x, sLEN(pattern) - x, end, 0, srch->ignore)) { srch->entire = vstrunc(srch->entire, (int) (end->byte - start->byte)); brmem(start, srch->entire, (int) (end->byte - start->byte)); pset(p, start); prm(start); prm(end); return p; } } if (wrap && !srch->wrap_flag && srch->wrap_p) { msgnw(bw->parent, UC "Wrapped"); srch->wrap_flag = 1; p_goto_eof(start); goto wrapped; } prm(start); prm(end); return NULL; } /* Make a search stucture */ static SRCH *setmark(SRCH *srch) { if (markv(0)) srch->valid = 1; srch->markb = markb; if (srch->markb) srch->markb->owner = &srch->markb; markb = NULL; srch->markk = markk; if (srch->markk) srch->markk->owner = &srch->markk; markk = NULL; return srch; } SRCH *mksrch(unsigned char *pattern, unsigned char *replacement, int ignore, int backwards, int repeat, int replace, int rest) { SRCH *srch = malloc(sizeof(SRCH)); int x; srch->pattern = pattern; srch->replacement = replacement; srch->ignore = ignore; srch->backwards = backwards; srch->repeat = repeat; srch->replace = replace; srch->rest = rest; srch->entire = NULL; srch->flg = 0; srch->addr = -1; srch->markb = NULL; srch->markk = NULL; srch->wrap_p = NULL; srch->wrap_flag = 0; srch->valid = 0; srch->block_restrict = 0; izque(SRCHREC, link, &srch->recs); for (x = 0; x != 26; ++x) srch->pieces[x] = NULL; return srch; } /* Eliminate a search structure */ void rmsrch(SRCH *srch) { int x; prm(markb); prm(markk); prm(srch->wrap_p); if (srch->markb) { markb = srch->markb; markb->owner = &markb; markb->xcol = piscol(markb); } if (srch->markk) { markk = srch->markk; markk->owner = &markk; markk->xcol = piscol(markk); } for (x = 0; x != 26; ++x) vsrm(srch->pieces[x]); frchn(&fsr, &srch->recs); vsrm(srch->pattern); vsrm(srch->replacement); vsrm(srch->entire); free(srch); updall(); } /* Insert a replacement string * p is advanced past the inserted text */ static P *insert(SRCH *srch, P *p, unsigned char *s, int len) { int x; while (len) { for (x = 0; x != len && s[x] != '\\'; ++x) ; if (x) { binsm(p, s, x); pfwrd(p, (long) x); len -= x; s += x; } else if (len >= 2) { if (((s[1] | 0x20) >= 'a' && (s[1] | 0x20) <= 'z') && srch->pieces[(s[1] & 0x1f) - 1]) { binsm(p, sv(srch->pieces[(s[1] & 0x1f) - 1])); pfwrd(p, (long) sLEN(srch->pieces[(s[1] & 0x1f) - 1])); s += 2; len -= 2; } else if (s[1] >= '0' && s[1] <= '9' && srch->pieces[s[1] - '0']) { binsm(p, sv(srch->pieces[s[1] - '0'])); pfwrd(p, (long) sLEN(srch->pieces[s[1] - '0'])); s += 2; len -= 2; } else if (s[1] == '&' && srch->entire) { binsm(p, sv(srch->entire)); pfwrd(p, (long) sLEN(srch->entire)); s += 2; len -= 2; } else { unsigned char *a=(unsigned char *)s+x; int l=len-x; binsc(p,escape(p->b->o.charmap->type,&a,&l)); pgetc(p); len -= a - (unsigned char *)s; s = a; } } else len = 0; } return p; } /* Search system user interface */ /* Query for search string, search options, possible replacement string, * and execute first search */ /* Context sensitive help identifier */ const unsigned char srchstr[] = "Search"; static int pfabort(BW *bw, SRCH *srch) { if (srch) rmsrch(srch); return -1; } /* always returns -1 */ static int pfsave(BW *bw, SRCH *srch) { if (srch) { if (globalsrch) rmsrch(globalsrch); globalsrch = srch; srch->rest = 0; srch->repeat = -1; srch->flg = 0; prm(markb); prm(markk); if (srch->markb) { markb = srch->markb; markb->owner = &markb; markb->xcol = piscol(markb); } if (srch->markk) { markk = srch->markk; markk->owner = &markk; markk->xcol = piscol(markk); } srch->markb = NULL; srch->markk = NULL; updall(); } return -1; } static int set_replace(BW *bw, unsigned char *s, SRCH *srch, int *notify) { srch->replacement = s; return dopfnext(bw, srch, notify); } static int set_options(BW *bw, unsigned char *s, SRCH *srch, int *notify) { int x; srch->ignore = icase; for (x = 0; s[x]; ++x) { switch (s[x] | 0x20) { case 'r': srch->replace = 1; break; case 'b': srch->backwards = 1; break; case 'i': srch->ignore = 1; break; case 's': srch->ignore = 0; break; case 'k': srch->block_restrict = 1; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (srch->repeat == -1) srch->repeat = 0; srch->repeat = srch->repeat * 10 + s[x] - '0'; break; } } vsrm(s); if (srch->replace) { if (wmkpw(bw->parent, UC "Replace with (^C to abort): ", &replhist, set_replace, srchstr, pfabort, srch_cmplt, srch, notify, bw->b->o.charmap)) return 0; else return -1; } else return dopfnext(bw, srch, notify); } static int set_pattern(BW *bw, unsigned char *s, SRCH *srch, int *notify) { BW *pbw; const unsigned char *p; if (icase) p = UC "case (S)ensitive (R)eplace (B)ackwards Bloc(K) NNN (^C to abort): "; else p = UC "(I)gnore (R)eplace (B)ackwards Bloc(K) NNN (^C to abort): "; vsrm(srch->pattern); srch->pattern = s; if ((pbw = wmkpw(bw->parent, p, NULL, set_options, srchstr, pfabort, utypebw, srch, notify, bw->b->o.charmap)) != NULL) { unsigned char buf[10]; if (srch->ignore) binsc(pbw->cursor, 'i'); if (srch->replace) binsc(pbw->cursor, 'r'); if (srch->backwards) binsc(pbw->cursor, 'b'); if (srch->repeat >= 0) joe_snprintf_1((char *)buf, sizeof(buf), "%d", srch->repeat), binss(pbw->cursor, buf); pset(pbw->cursor, pbw->b->eof); pbw->cursor->xcol = piscol(pbw->cursor); srch->ignore = 0; srch->replace = 0; srch->backwards = 0; srch->repeat = -1; return 0; } else { rmsrch(srch); return -1; } } static int dofirst(BW *bw, int back, int repl) { SRCH *srch; if (smode && globalsrch) { globalsrch->backwards = back; globalsrch->replace = repl; return pfnext(bw); } if (bw->parent->huh == srchstr) { long byte; jobject jO; p_goto_eol(bw->cursor); byte = bw->cursor->byte; p_goto_bol(bw->cursor); if (byte == bw->cursor->byte) prgetc(bw->cursor); jO.bw = bw; return urtn(jO, -1); } srch = setmark(mksrch(NULL, NULL, 0, back, -1, repl, 0)); srch->addr = bw->cursor->byte; srch->wrap_p = pdup(bw->cursor); srch->wrap_p->owner = &srch->wrap_p; if (wmkpw(bw->parent, UC "Find (^C to abort): ", &findhist, set_pattern, srchstr, pfabort, srch_cmplt, srch, NULL, bw->b->o.charmap)) return 0; else { rmsrch(srch); return -1; } } int pffirst(BW *bw) { return dofirst(bw, 0, 0); } int prfirst(BW *bw) { return dofirst(bw, 1, 0); } int pqrepl(BW *bw) { return dofirst(bw, 0, 1); } /* Execute next search */ static int doreplace(BW *bw, SRCH *srch) { P *q; if (bw->b->rdonly) { msgnw(bw->parent, UC "Read only"); return -1; } if (markk) markk->end = 1; if (srch->markk) srch->markk->end = 1; q = pdup(bw->cursor); if (srch->backwards) { q = pfwrd(q, (long) sLEN(srch->entire)); bdel(bw->cursor, q); prm(q); } else { q = pbkwd(q, (long) sLEN(srch->entire)); bdel(q, bw->cursor); prm(q); } insert(srch, bw->cursor, sv(srch->replacement)); srch->addr = bw->cursor->byte; if (markk) markk->end = 0; if (srch->markk) srch->markk->end = 0; return 0; } static void visit(SRCH *srch, BW *bw, int yn) { SRCHREC *r = (SRCHREC *) alitem(&fsr, sizeof(SRCHREC)); r->addr = bw->cursor->byte; r->yn = yn; r->wrap_flag = srch->wrap_flag; enqueb(SRCHREC, link, &srch->recs, r); } static void goback(SRCH *srch, BW *bw) { SRCHREC *r = srch->recs.link.prev; if (r != &srch->recs) { if (r->yn) uundo(bw); if (bw->cursor->byte != r->addr) pgoto(bw->cursor, r->addr); srch->wrap_flag = r->wrap_flag; demote(SRCHREC, link, &fsr, r); } } static int dopfrepl(BW *bw, int c, SRCH *srch, int *notify) { srch->addr = bw->cursor->byte; if ((c | 0x20) == 'n') return dopfnext(bw, srch, notify); else if ((c | 0x20) == 'y' || (c | 0x20) == 'l' || c == ' ') { srch->recs.link.prev->yn = 1; /* why do I return -1 on 'L' here? */ return ((doreplace(bw, srch) || (c | 0x20) == 'l') ? pfsave(bw, srch) : dopfnext(bw, srch, notify)); } else if ((c | 0x20) == 'r') { if (doreplace(bw, srch)) return -1; srch->rest = 1; return dopfnext(bw, srch, notify); } else if (c == 8 || c == 127 || (c | 0x20) == 'b') { goback(srch, bw); goback(srch, bw); return dopfnext(bw, srch, notify); } else if (c != -1) { if (notify) *notify = 1; pfsave(bw, srch); nungetc(c); return 0; } if (mkqwnsr(bw->parent, sc("Replace (Y)es (N)o (L)ast (R)est (B)ackup (^C to abort)?"), dopfrepl, pfsave, srch, notify)) return 0; else return pfsave(bw, srch); } /* Test if found text is within region * return 0 if it is, * -1 if we should keep searching * 1 if we're done */ static int restrict_to_block(BW *bw, SRCH *srch) { if (!srch->block_restrict) return 0; bw->cursor->xcol = piscol(bw->cursor); if (srch->backwards) if (!square) { if (bw->cursor->byte < srch->markb->byte) return 1; else if (bw->cursor->byte + sLEN(srch->entire) > srch->markk->byte) return -1; } else { if (bw->cursor->line < srch->markb->line) return 1; else if (bw->cursor->line > srch->markk->line) return -1; else if (piscol(bw->cursor) + sLEN(srch->entire) > srch->markk->xcol || piscol(bw->cursor) < srch->markb->xcol) return -1; } else if (!square) { if (bw->cursor->byte > srch->markk->byte) return 1; else if (bw->cursor->byte - sLEN(srch->entire) < srch->markb->byte) return -1; } else { if (bw->cursor->line > srch->markk->line) return 1; if (bw->cursor->line < srch->markb->line) return -1; if (piscol(bw->cursor) > srch->markk->xcol || piscol(bw->cursor) - sLEN(srch->entire) < srch->markb->xcol) return -1; } return 0; } /* Possible results: * 0) Search or search & replace is finished. * 1) Search string was not found. * 2) Search string was found. */ static int fnext(BW *bw, SRCH *srch) { P *sta; next: if (srch->repeat != -1) { if (!srch->repeat) return 0; else --srch->repeat; } again: if (srch->backwards) sta = searchb(bw, srch, bw->cursor); else sta = searchf(bw, srch, bw->cursor); if (!sta) { srch->repeat = -1; return 1; } else if (srch->rest || (srch->repeat != -1 && srch->replace)) { if (srch->valid) switch (restrict_to_block(bw, srch)) { case -1: goto again; case 1: if (srch->addr >= 0) pgoto(bw->cursor, srch->addr); return !srch->rest; } if (doreplace(bw, srch)) return 0; goto next; } else if (srch->repeat != -1) { if (srch->valid) switch (restrict_to_block(bw, srch)) { case -1: goto again; case 1: if (srch->addr >= 0) pgoto(bw->cursor, srch->addr); return 1; } srch->addr = bw->cursor->byte; goto next; } else return 2; } int dopfnext(BW *bw, SRCH *srch, int *notify) { int orgmid = mid; /* Original mid status */ int ret = 0; mid = 1; /* Screen recenters mode during search */ if (csmode) smode = 2; /* We have started a search mode */ if (srch->replace) visit(srch, bw, 0); again: switch (fnext(bw, srch)) { case 0: break; case 1: bye: if (!srch->flg && !srch->rest) { if (srch->valid && srch->block_restrict) msgnw(bw->parent, UC "Not found (search restricted to marked block)"); else msgnw(bw->parent, UC "Not found"); ret = -1; } break; case 2: if (srch->valid) switch (restrict_to_block(bw, srch)) { case -1: goto again; case 1: if (srch->addr >= 0) pgoto(bw->cursor, srch->addr); goto bye; } srch->addr = bw->cursor->byte; /* Make sure found text is fully on screen */ if(srch->backwards) { bw->offset=0; pfwrd(bw->cursor,sLEN(srch->entire)); bw->cursor->xcol = piscol(bw->cursor); dofollows(); pbkwd(bw->cursor,sLEN(srch->entire)); } else { bw->offset=0; pbkwd(bw->cursor,sLEN(srch->entire)); bw->cursor->xcol = piscol(bw->cursor); dofollows(); pfwrd(bw->cursor,sLEN(srch->entire)); } if (srch->replace) { if (square) bw->cursor->xcol = piscol(bw->cursor); if (srch->backwards) { pdupown(bw->cursor, &markb); markb->xcol = piscol(markb); pdupown(markb, &markk); pfwrd(markk, (long) sLEN(srch->entire)); markk->xcol = piscol(markk); } else { pdupown(bw->cursor, &markk); markk->xcol = piscol(markk); pdupown(bw->cursor, &markb); pbkwd(markb, (long) sLEN(srch->entire)); markb->xcol = piscol(markb); } srch->flg = 1; if (dopfrepl(bw, -1, srch, notify)) ret = -1; notify = 0; srch = 0; } break; } bw->cursor->xcol = piscol(bw->cursor); dofollows(); mid = orgmid; if (notify) *notify = 1; if (srch) pfsave(bw, srch); else updall(); return ret; } int pfnext(BW *bw) { SRCH *srch; if (!globalsrch) { /* Query for search string if there isn't any */ return pffirst(bw); } srch = globalsrch; globalsrch = NULL; srch->addr = bw->cursor->byte; if (!srch->wrap_p || srch->wrap_p->b!=bw->b) { prm(srch->wrap_p); srch->wrap_p = pdup(bw->cursor); srch->wrap_p->owner = &srch->wrap_p; srch->wrap_flag = 0; } srch->valid = 0; return dopfnext(bw, setmark(srch), NULL); }