/* * zle_utils.c - miscellaneous line editor utilities * * 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_utils.pro" /* Primary cut buffer */ /**/ struct cutbuffer cutbuf; /* Emacs-style kill buffer ring */ /**/ struct cutbuffer kring[KRINGCT]; /**/ int kringnum; /* Vi named cut buffers. 0-25 are the named buffers "a to "z, and * * 26-34 are the numbered buffer stack "1 to "9. */ /**/ struct cutbuffer vibuf[35]; /* the line before last mod (for undo purposes) */ /**/ char *lastline; /**/ int lastlinesz, lastll, lastcs; /* size of line buffer */ /**/ int linesz; /* make sure that the line buffer has at least sz chars */ /**/ void sizeline(int sz) { while (sz > linesz) line = (unsigned char *)realloc(line, (linesz *= 4) + 2); } /* insert space for ct chars at cursor position */ /**/ mod_export void spaceinline(int ct) { int i; sizeline(ct + ll); for (i = ll; --i >= cs;) line[i + ct] = line[i]; ll += ct; line[ll] = '\0'; if (mark > cs) mark += ct; } /**/ static void shiftchars(int to, int cnt) { if (mark >= to + cnt) mark -= cnt; else if (mark > to) mark = to; while (to + cnt < ll) { line[to] = line[to + cnt]; to++; } line[ll = to] = '\0'; } /**/ mod_export void backkill(int ct, int dir) { int i = (cs -= ct); cut(i, ct, dir); shiftchars(i, ct); } /**/ mod_export void forekill(int ct, int dir) { int i = cs; cut(i, ct, dir); shiftchars(i, ct); } /**/ void cut(int i, int ct, int dir) { if (zmod.flags & MOD_VIBUF) { struct cutbuffer *b = &vibuf[zmod.vibuf]; if (!(zmod.flags & MOD_VIAPP) || !b->buf) { zfree(b->buf, b->len); b->buf = (char *)zalloc(ct); memcpy(b->buf, (char *) line + i, ct); b->len = ct; b->flags = vilinerange ? CUTBUFFER_LINE : 0; } else { int len = b->len; if(vilinerange) b->flags |= CUTBUFFER_LINE; b->buf = realloc(b->buf, ct + len + !!(b->flags & CUTBUFFER_LINE)); if (b->flags & CUTBUFFER_LINE) b->buf[len++] = '\n'; memcpy(b->buf + len, (char *) line + i, ct); b->len = len + ct; } return; } else { /* Save in "1, shifting "1-"8 along to "2-"9 */ int n; zfree(vibuf[34].buf, vibuf[34].len); for(n=34; n>26; n--) vibuf[n] = vibuf[n-1]; vibuf[26].buf = (char *)zalloc(ct); memcpy(vibuf[26].buf, (char *) line + i, ct); vibuf[26].len = ct; vibuf[26].flags = vilinerange ? CUTBUFFER_LINE : 0; } if (!cutbuf.buf) { cutbuf.buf = ztrdup(""); cutbuf.len = cutbuf.flags = 0; } else if (!(lastcmd & ZLE_KILL)) { kringnum = (kringnum + 1) % KRINGCT; if (kring[kringnum].buf) free(kring[kringnum].buf); kring[kringnum] = cutbuf; cutbuf.buf = ztrdup(""); cutbuf.len = cutbuf.flags = 0; } if (dir) { char *s = (char *)zalloc(cutbuf.len + ct); memcpy(s, (char *) line + i, ct); memcpy(s + ct, cutbuf.buf, cutbuf.len); free(cutbuf.buf); cutbuf.buf = s; cutbuf.len += ct; } else { cutbuf.buf = realloc(cutbuf.buf, cutbuf.len + ct); memcpy(cutbuf.buf + cutbuf.len, (char *) line + i, ct); cutbuf.len += ct; } if(vilinerange) cutbuf.flags |= CUTBUFFER_LINE; else cutbuf.flags &= ~CUTBUFFER_LINE; } /**/ mod_export void backdel(int ct) { shiftchars(cs -= ct, ct); } /**/ mod_export void foredel(int ct) { shiftchars(cs, ct); } /**/ void setline(char const *s) { sizeline(strlen(s)); strcpy((char *) line, s); unmetafy((char *) line, &ll); if ((cs = ll) && invicmdmode()) cs--; } /**/ int findbol(void) { int x = cs; while (x > 0 && line[x - 1] != '\n') x--; return x; } /**/ int findeol(void) { int x = cs; while (x != ll && line[x] != '\n') x++; return x; } /**/ void findline(int *a, int *b) { *a = findbol(); *b = findeol(); } /* Search for needle in haystack. Haystack is a metafied string while * * needle is unmetafied and len-long. Start the search at position * * pos. Search forward if dir > 0 otherwise search backward. */ /**/ char * hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens) { char *s = haystack + pos; if (dir > 0) { while (*s) { if (metadiffer(s, needle, len) < sens) return s; s += 1 + (*s == Meta); } } else { for (;;) { if (metadiffer(s, needle, len) < sens) return s; if (s == haystack) break; s -= 1 + (s != haystack+1 && s[-2] == Meta); } } return NULL; } /* Query the user, and return a single character response. The * * question is assumed to have been printed already, and the * * cursor is left immediately after the response echoed. * * (Might cause a problem if this takes it onto the next line.) * * is interpreted as 'y'; any other control character is * * interpreted as 'n'. If there are any characters in the * * buffer, this is taken as a negative response, and no * * characters are read. Case is folded. */ /**/ mod_export int getzlequery(void) { int c; #ifdef FIONREAD int val; /* check for typeahead, which is treated as a negative response */ ioctl(SHTTY, FIONREAD, (char *)&val); if (val) { putc('n', shout); return 'n'; } #endif /* get a character from the tty and interpret it */ c = getkey(0); if (c == '\t') c = 'y'; else if (icntrl(c) || c == EOF) c = 'n'; else c = tulower(c); /* echo response and return */ putc(c, shout); return c; } /* Format a string, keybinding style. */ /**/ char * bindztrdup(char *str) { int c, len = 1; char *buf, *ptr, *ret; for(ptr = str; *ptr; ptr++) { c = *ptr == Meta ? STOUC(*++ptr) ^ 32 : STOUC(*ptr); if(c & 0x80) { len += 3; c &= 0x7f; } if(c < 32 || c == 0x7f) { len++; c ^= 64; } len += c == '\\' || c == '^'; len++; } ptr = buf = zalloc(len); for(; *str; str++) { c = *str == Meta ? STOUC(*++str) ^ 32 : STOUC(*str); if(c & 0x80) { *ptr++ = '\\'; *ptr++ = 'M'; *ptr++ = '-'; c &= 0x7f; } if(c < 32 || c == 0x7f) { *ptr++ = '^'; c ^= 64; } if(c == '\\' || c == '^') *ptr++ = '\\'; *ptr++ = c; } *ptr = 0; ret = dquotedztrdup(buf); zsfree(buf); return ret; } /* Display a metafied string, keybinding-style. */ /**/ int printbind(char *str, FILE *stream) { char *b = bindztrdup(str); int ret = zputs(b, stream); zsfree(b); return ret; } /* Display a message where the completion list normally goes. * * The message must be metafied. */ /**/ void showmsg(char const *msg) { char const *p; int up = 0, cc = 0, c; trashzle(); clearflag = isset(USEZLE) && !termflags && isset(ALWAYSLASTPROMPT); for(p = msg; (c = *p); p++) { if(c == Meta) c = *++p ^ 32; if(c == '\n') { putc('\n', shout); up += 1 + cc / columns; cc = 0; } else { char const *n = nicechar(c); fputs(n, shout); cc += strlen(n); } } up += cc / columns; if (clearflag) { putc('\r', shout); tcmultout(TCUP, TCMULTUP, up + nlnct); } else putc('\n', shout); showinglist = 0; } /* handle the error flag */ /**/ int handlefeep(char **args) { zbeep(); return 0; } /***************/ /* undo system */ /***************/ /* head of the undo list, and the current position */ static struct change *changes, *curchange; /* list of pending changes, not yet in the undo system */ static struct change *nextchanges, *endnextchanges; /**/ void initundo(void) { nextchanges = NULL; changes = curchange = zalloc(sizeof(*curchange)); curchange->prev = curchange->next = NULL; curchange->del = curchange->ins = NULL; lastline = zalloc(lastlinesz = linesz); memcpy(lastline, line, lastll = ll); lastcs = cs; } /**/ void freeundo(void) { freechanges(changes); freechanges(nextchanges); zfree(lastline, lastlinesz); } /**/ static void freechanges(struct change *p) { struct change *n; for(; p; p = n) { n = p->next; zsfree(p->del); zsfree(p->ins); zfree(p, sizeof(*p)); } } /* register pending changes in the undo system */ /**/ void handleundo(void) { mkundoent(); if(!nextchanges) return; setlastline(); if(curchange->next) { freechanges(curchange->next); curchange->next = NULL; zsfree(curchange->del); zsfree(curchange->ins); curchange->del = curchange->ins = NULL; } nextchanges->prev = curchange->prev; if(curchange->prev) curchange->prev->next = nextchanges; else changes = nextchanges; curchange->prev = endnextchanges; endnextchanges->next = curchange; nextchanges = endnextchanges = NULL; } /* add an entry to the undo system, if anything has changed */ /**/ void mkundoent(void) { int pre, suf; int sh = ll < lastll ? ll : lastll; struct change *ch; if(lastll == ll && !memcmp(lastline, line, ll)) return; for(pre = 0; pre < sh && line[pre] == lastline[pre]; ) pre++; for(suf = 0; suf < sh - pre && line[ll - 1 - suf] == lastline[lastll - 1 - suf]; ) suf++; ch = zalloc(sizeof(*ch)); ch->next = NULL; ch->hist = histline; ch->off = pre; ch->old_cs = lastcs; ch->new_cs = cs; if(suf + pre == lastll) ch->del = NULL; else ch->del = metafy(lastline + pre, lastll - pre - suf, META_DUP); if(suf + pre == ll) ch->ins = NULL; else ch->ins = metafy((char *)line + pre, ll - pre - suf, META_DUP); if(nextchanges) { ch->flags = CH_PREV; ch->prev = endnextchanges; endnextchanges->flags |= CH_NEXT; endnextchanges->next = ch; } else { nextchanges = ch; ch->flags = 0; ch->prev = NULL; } endnextchanges = ch; } /* set lastline to match line */ /**/ void setlastline(void) { if(lastlinesz != linesz) lastline = realloc(lastline, lastlinesz = linesz); memcpy(lastline, line, lastll = ll); lastcs = cs; } /* move backwards through the change list */ /**/ int undo(char **args) { handleundo(); do { if(!curchange->prev) return 1; unapplychange(curchange = curchange->prev); } while(curchange->flags & CH_PREV); setlastline(); return 0; } /**/ static void unapplychange(struct change *ch) { if(ch->hist != histline) zle_setline(quietgethist(ch->hist)); cs = ch->off; if(ch->ins) foredel(ztrlen(ch->ins)); if(ch->del) { char *c = ch->del; spaceinline(ztrlen(c)); for(; *c; c++) if(*c == Meta) line[cs++] = STOUC(*++c) ^ 32; else line[cs++] = STOUC(*c); } cs = ch->old_cs; } /* move forwards through the change list */ /**/ int redo(char **args) { handleundo(); do { if(!curchange->next) return 1; applychange(curchange); curchange = curchange->next; } while(curchange->prev->flags & CH_NEXT); setlastline(); return 0; } /**/ static void applychange(struct change *ch) { if(ch->hist != histline) zle_setline(quietgethist(ch->hist)); cs = ch->off; if(ch->del) foredel(ztrlen(ch->del)); if(ch->ins) { char *c = ch->ins; spaceinline(ztrlen(c)); for(; *c; c++) if(*c == Meta) line[cs++] = STOUC(*++c) ^ 32; else line[cs++] = STOUC(*c); } cs = ch->new_cs; } /* vi undo: toggle between the end of the undo list and the preceding point */ /**/ int viundochange(char **args) { handleundo(); if(curchange->next) { do { applychange(curchange); curchange = curchange->next; } while(curchange->next); setlastline(); return 0; } else return undo(args); }