/* * zle_misc.c - miscellaneous editor routines * * 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_misc.pro" /* insert a zle string, with repetition and suffix removal */ /**/ void doinsert(ZLE_STRING_T zstr, int len) { ZLE_STRING_T s; ZLE_CHAR_T c1 = *zstr; /* first character */ int neg = zmult < 0; /* insert *after* the cursor? */ int m = neg ? -zmult : zmult; /* number of copies to insert */ int count; UNMETACHECK(); iremovesuffix(c1, 0); invalidatelist(); /* In overwrite mode, don't replace newlines. */ if (insmode || zleline[zlecs] == ZWC('\n')) spaceinline(m * len); else { int pos = zlecs, diff, i; #ifdef MULTIBYTE_SUPPORT /* * Calculate the number of character positions we are * going to be using. The algorithm is that * anything that shows up as a logical single character * (i.e. even if control, or double width, or with combining * characters) is treated as 1 for the purpose of replacing * what's there already. * * This can cause inserting of a combining character in * places where it should overwrite, such as the start * of a line. However, combining characters aren't * useful there anyway and this doesn't cause any * particular harm. */ for (i = 0, count = 0; i < len * m; i++) { if (!IS_COMBINING(zstr[i])) count++; } #else count = len * m; #endif /* * Ensure we replace a complete combining characterfor each * character we overwrite. Switch to inserting at first newline. */ for (i = count; pos < zlell && zleline[pos] != ZWC('\n') && i--; ) { INCPOS(pos); } /* * Calculate how many raw line places we need. * pos - zlecs is the raw line distance we're replacing, * m * len the number we're inserting. */ diff = pos - zlecs - m * len; if (diff < 0) { spaceinline(-diff); } else if (diff > 0) { /* * We use shiftchars() here because we don't * want combining char alignment fixed up: we * are going to write over any that remain. */ shiftchars(zlecs, diff); } } while (m--) for (s = zstr, count = len; count; s++, count--) zleline[zlecs++] = *s; if (neg) zlecs += zmult * len; /* if we ended up on a combining character, skip over it */ CCRIGHT(); } /**/ mod_export int selfinsert(UNUSED(char **args)) { ZLE_CHAR_T tmp; #ifdef MULTIBYTE_SUPPORT if (!lastchar_wide_valid) if (getrestchar(lastchar) == WEOF) return 1; #endif tmp = LASTFULLCHAR; doinsert(&tmp, 1); return 0; } /**/ mod_export void fixunmeta(void) { lastchar &= 0x7f; if (lastchar == '\r') lastchar = '\n'; #ifdef MULTIBYTE_SUPPORT /* * TODO: can we do this better? * We need a wide character to insert. * selfinsertunmeta is intrinsically problematic * with multibyte input. */ lastchar_wide = (ZLE_INT_T)lastchar; lastchar_wide_valid = 1; #endif } /**/ mod_export int selfinsertunmeta(char **args) { fixunmeta(); return selfinsert(args); } /**/ int deletechar(char **args) { int n; if (zmult < 0) { int ret; zmult = -zmult; ret = backwarddeletechar(args); zmult = -zmult; return ret; } n = zmult; while (n--) { if (zlecs == zlell) return 1; INCCS(); } backdel(zmult, 0); return 0; } /**/ int backwarddeletechar(char **args) { if (zmult < 0) { int ret; zmult = -zmult; ret = deletechar(args); zmult = -zmult; return ret; } backdel(zmult > zlecs ? zlecs : zmult, 0); return 0; } /**/ int killwholeline(UNUSED(char **args)) { int i, fg, n = zmult; if (n < 0) return 1; while (n--) { if ((fg = (zlecs && zlecs == zlell))) zlecs--; while (zlecs && zleline[zlecs - 1] != '\n') zlecs--; for (i = zlecs; i != zlell && zleline[i] != '\n'; i++); forekill(i - zlecs + (i != zlell), fg ? (CUT_FRONT|CUT_RAW) : CUT_RAW); } clearlist = 1; return 0; } /**/ int killbuffer(UNUSED(char **args)) { zlecs = 0; forekill(zlell, CUT_RAW); clearlist = 1; return 0; } /**/ int backwardkillline(char **args) { int i = 0, n = zmult; if (n < 0) { int ret; zmult = -n; ret = killline(args); zmult = n; return ret; } while (n--) { if (zlecs && zleline[zlecs - 1] == '\n') zlecs--, i++; else while (zlecs && zleline[zlecs - 1] != '\n') zlecs--, i++; } forekill(i, CUT_FRONT|CUT_RAW); clearlist = 1; return 0; } #ifdef MULTIBYTE_SUPPORT /* * Transpose the chunk of the line from start to middle with * that from middle to end. */ static void transpose_swap(int start, int middle, int end) { int len1, len2; ZLE_STRING_T first; len1 = middle - start; len2 = end - middle; first = (ZLE_STRING_T)zalloc(len1 * ZLE_CHAR_SIZE); ZS_memcpy(first, zleline + start, len1); /* Move may be overlapping... */ ZS_memmove(zleline + start, zleline + middle, len2); ZS_memcpy(zleline + start + len2, first, len1); zfree(first, len1 * ZLE_CHAR_SIZE); } #endif /**/ int gosmacstransposechars(UNUSED(char **args)) { if (zlecs < 2 || zleline[zlecs - 1] == '\n' || zleline[zlecs - 2] == '\n') { int twice = (zlecs == 0 || zleline[zlecs - 1] == '\n'); if (zlecs == zlell || zleline[zlecs] == '\n') return 1; INCCS(); if (twice) { if (zlecs == zlell || zleline[zlecs] == '\n') return 1; INCCS(); } } #ifdef MULTIBYTE_SUPPORT { int start, middle; middle = zlecs; DECPOS(middle); start = middle; DECPOS(start); transpose_swap(start, middle, zlecs); } #else { ZLE_CHAR_T cc = zleline[zlecs - 2]; zleline[zlecs - 2] = zleline[zlecs - 1]; zleline[zlecs - 1] = cc; } #endif return 0; } /**/ int transposechars(UNUSED(char **args)) { int ct; int n = zmult; int neg = n < 0; if (neg) n = -n; while (n--) { if (!(ct = zlecs) || zleline[zlecs - 1] == '\n') { if (zlell == zlecs || zleline[zlecs] == '\n') return 1; if (!neg) INCCS(); INCPOS(ct); } if (neg) { if (zlecs && zleline[zlecs - 1] != '\n') { DECCS(); if (ct > 1 && zleline[ct - 2] != '\n') { DECPOS(ct); } } } else { if (zlecs != zlell && zleline[zlecs] != '\n') INCCS(); } if (ct == zlell || zleline[ct] == '\n') { DECPOS(ct); } if (ct < 1 || zleline[ct - 1] == '\n') return 1; #ifdef MULTIBYTE_SUPPORT { /* * We should keep any accents etc. on their original characters. */ int start = ct, end = ct; DECPOS(start); INCPOS(end); transpose_swap(start, ct, end); } #else { ZLE_CHAR_T cc = zleline[ct - 1]; zleline[ct - 1] = zleline[ct]; zleline[ct] = cc; } #endif } return 0; } /**/ int poundinsert(UNUSED(char **args)) { zlecs = 0; vifirstnonblank(zlenoargs); if (zleline[zlecs] != '#') { spaceinline(1); zleline[zlecs] = '#'; zlecs = findeol(); while(zlecs != zlell) { zlecs++; vifirstnonblank(zlenoargs); spaceinline(1); zleline[zlecs] = '#'; zlecs = findeol(); } } else { foredel(1, 0); zlecs = findeol(); while(zlecs != zlell) { zlecs++; vifirstnonblank(zlenoargs); if(zleline[zlecs] == '#') foredel(1, 0); zlecs = findeol(); } } done = 1; return 0; } /**/ int acceptline(UNUSED(char **args)) { done = 1; return 0; } /**/ int acceptandhold(UNUSED(char **args)) { zpushnode(bufstack, zlelineasstring(zleline, zlell, 0, NULL, NULL, 0)); stackcs = zlecs; done = 1; return 0; } /**/ int killline(char **args) { int i = 0, n = zmult; if (n < 0) { int ret; zmult = -n; ret = backwardkillline(args); zmult = n; return ret; } while (n--) { if (zleline[zlecs] == ZWC('\n')) zlecs++, i++; else while (zlecs != zlell && zleline[zlecs] != ZWC('\n')) zlecs++, i++; } backkill(i, CUT_RAW); clearlist = 1; return 0; } /**/ void regionlines(int *start, int *end) { int origcs = zlecs; UNMETACHECK(); if (zlecs < mark) { *start = findbol(); zlecs = (mark > zlell) ? zlell : mark; *end = findeol(); } else { *end = findeol(); zlecs = mark; *start = findbol(); } zlecs = origcs; } /**/ int killregion(UNUSED(char **args)) { if (mark > zlell) mark = zlell; if (region_active == 2) { int a, b; regionlines(&a, &b); zlecs = a; region_active = 0; cut(zlecs, b - zlecs, CUT_RAW); shiftchars(zlecs, b - zlecs); if (zlell) { if (zlecs == zlell) DECCS(); foredel(1, 0); vifirstnonblank(zlenoargs); } } else if (mark > zlecs) { if (invicmdmode()) INCPOS(mark); forekill(mark - zlecs, CUT_RAW); } else { if (invicmdmode()) INCCS(); backkill(zlecs - mark, CUT_FRONT|CUT_RAW); } return 0; } /**/ int copyregionaskill(char **args) { int start, end; if (*args) { int len; ZLE_STRING_T line = stringaszleline(*args, 0, &len, NULL, NULL); cuttext(line, len, CUT_REPLACE); free(line); } else { if (mark > zlell) mark = zlell; if (mark > zlecs) { start = zlecs; end = mark; } else { start = mark; end = zlecs; } if (invicmdmode()) INCPOS(end); cut(start, end - start, mark > zlecs ? 0 : CUT_FRONT); } return 0; } /* * kct: index into kill ring, or -1 for original cutbuffer of yank. * yankb, yanke: mark the start and end of last yank in editing buffer. * yankcs marks the cursor position preceding the last yank */ static int kct, yankb, yanke, yankcs; /* The original cutbuffer, either cutbuf or one of the vi buffers. */ static Cutbuffer kctbuf; /**/ int yank(UNUSED(char **args)) { int n = zmult; if (n < 0) return 1; if (zmod.flags & MOD_VIBUF) kctbuf = &vibuf[zmod.vibuf]; else kctbuf = &cutbuf; if (!kctbuf->buf) return 1; yankb = yankcs = mark = zlecs; while (n--) { kct = -1; spaceinline(kctbuf->len); ZS_memcpy(zleline + zlecs, kctbuf->buf, kctbuf->len); zlecs += kctbuf->len; yanke = zlecs; } return 0; } /* position: 0 is before, 1 after, 2 split the line */ static void pastebuf(Cutbuffer buf, int mult, int position) { int cc; if (buf->flags & CUTBUFFER_LINE) { if (position == 2) { if (!zlecs) position = 0; else if (zlecs == zlell) position = 1; } if (position == 2) { yankb = zlecs; spaceinline(buf->len + 2); zleline[zlecs++] = ZWC('\n'); ZS_memcpy(zleline + zlecs, buf->buf, buf->len); zlecs += buf->len; zleline[zlecs] = ZWC('\n'); yanke = zlecs + 1; } else if (position != 0) { yankb = zlecs = findeol(); spaceinline(buf->len + 1); zleline[zlecs++] = ZWC('\n'); yanke = zlecs + buf->len; ZS_memcpy(zleline + zlecs, buf->buf, buf->len); } else { yankb = zlecs = findbol(); spaceinline(buf->len + 1); ZS_memcpy(zleline + zlecs, buf->buf, buf->len); yanke = zlecs + buf->len + 1; zleline[zlecs + buf->len] = ZWC('\n'); } vifirstnonblank(zlenoargs); } else { if (position == 1 && zlecs != findeol()) INCCS(); yankb = zlecs; cc = buf->len; while (mult--) { spaceinline(cc); ZS_memcpy(zleline + zlecs, buf->buf, cc); zlecs += cc; } yanke = zlecs; if (zlecs) DECCS(); } } /**/ int viputbefore(UNUSED(char **args)) { int n = zmult; startvichange(-1); if (n < 0 || zmod.flags & MOD_NULL) return 1; if (zmod.flags & MOD_VIBUF) kctbuf = &vibuf[zmod.vibuf]; else kctbuf = &cutbuf; if (!kctbuf->buf) return 1; kct = -1; yankcs = zlecs; pastebuf(kctbuf, n, 0); return 0; } /**/ int viputafter(UNUSED(char **args)) { int n = zmult; startvichange(-1); if (n < 0 || zmod.flags & MOD_NULL) return 1; if (zmod.flags & MOD_VIBUF) kctbuf = &vibuf[zmod.vibuf]; else kctbuf = &cutbuf; if (!kctbuf->buf) return 1; kct = -1; yankcs = zlecs; pastebuf(kctbuf, n, 1); return 0; } /**/ int putreplaceselection(UNUSED(char **args)) { int n = zmult; struct cutbuffer prevbuf; Cutbuffer putbuf; int clear = 0; int pos = 2; startvichange(-1); if (n < 0 || zmod.flags & MOD_NULL) return 1; putbuf = (zmod.flags & MOD_VIBUF) ? &vibuf[zmod.vibuf] : &cutbuf; if (!putbuf->buf) return 1; memcpy(&prevbuf, putbuf, sizeof(prevbuf)); /* if "9 was specified, prevent killregion from freeing it */ if (zmod.vibuf == 35) { putbuf->buf = 0; clear = 1; } zmod.flags = 0; /* flags apply to paste not kill */ if (region_active == 2 && prevbuf.flags & CUTBUFFER_LINE) { int a, b; regionlines(&a, &b); pos = (b == zlell); } killregion(zlenoargs); pastebuf(&prevbuf, n, pos); if (clear) free(prevbuf.buf); return 0; } /**/ int yankpop(UNUSED(char **args)) { int kctstart = kct; Cutbuffer buf; if (!(lastcmd & ZLE_YANK) || !kring || !kctbuf) { kctbuf = NULL; return 1; } do { /* * This is supposed to make the yankpop loop * original buffer -> kill ring in order -> original buffer -> ... * where the original buffer is -1 and the remainder are * indices into the kill ring, remember that we need to start * that at kringnum rather than zero. */ if (kct == -1) kct = kringnum; else { int kctnew = (kct + kringsize - 1) % kringsize; if (kctnew == kringnum) kct = -1; else kct = kctnew; } if (kct == -1) buf = kctbuf; /* Use original cutbuffer */ else buf = kring+kct; /* Use somewhere in the kill ring */ /* Careful to prevent infinite looping */ if (kct == kctstart) return 1; /* * Skip unset buffers instead of stopping as we used to do. * Also skip zero-length buffers. * There are two reasons for this: * 1. We now map the array $killring directly into the * killring, instead of via some extra size-checking logic. * When $killring has been set, a buffer will always have * at least a zero-length string in it. * 2. The old logic was inconsistent; when the kill ring * was full, we could loop round and round it, otherwise * we just stopped when we hit the first empty buffer. */ } while (!buf->buf || *buf->buf == ZWC('\0')); zlecs = yankb; foredel(yanke - yankb, CUT_RAW); zlecs = yankcs; pastebuf(buf, 1, !!(lastcmd & ZLE_YANKAFTER)); return 0; } /**/ int bracketedpaste(UNUSED(char **args)) { static const char endesc[] = "\e[201~"; int endpos = 0; size_t psize = 64; char *buf, *pbuf = zalloc(psize); size_t current = 0; int n, next, timeout; ZLE_STRING_T wpaste; while (endesc[endpos]) { if ((next = getbyte(1L, &timeout)) == EOF) break; if (!endpos || next != endesc[endpos++]) endpos = (next == *endesc); if (current + 1 >= psize) pbuf = zrealloc(pbuf, psize *= 2); if (imeta(next)) { pbuf[current++] = Meta; pbuf[current++] = next ^ 32; } else if (next == '\r') pbuf[current++] = '\n'; else pbuf[current++] = next; } pbuf[current-sizeof(endesc)+1] = '\0'; buf = zmult == 1 ? pbuf : quotestring(pbuf, NULL, QT_BACKSLASH); zmult = 1; wpaste = stringaszleline(buf, 0, &n, NULL, NULL); doinsert(wpaste, n); free(pbuf); free(wpaste); return 0; } /**/ int overwritemode(UNUSED(char **args)) { insmode ^= 1; return 0; } /**/ int whatcursorposition(UNUSED(char **args)) { char msg[100]; char *s = msg, *mbstr; int bol = findbol(), len; ZLE_CHAR_T c = zleline[zlecs]; if (zlecs == zlell) strucpy(&s, "EOF"); else { strucpy(&s, "Char: "); switch (c) { case ZWC(' '): strucpy(&s, "SPC"); break; case ZWC('\t'): strucpy(&s, "TAB"); break; case ZWC('\n'): strucpy(&s, "LFD"); break; default: /* * convert a single character, remembering it may * turn into a multibyte string or be metafied. */ mbstr = zlelineasstring(zleline+zlecs, 1, 0, &len, NULL, 1); strcpy(s, mbstr); s += len; } sprintf(s, " (0%o, %u, 0x%x)", (unsigned int)c, (unsigned int)c, (unsigned int)c); s += strlen(s); } sprintf(s, " point %d of %d(%d%%) column %d", zlecs+1, zlell+1, zlell ? 100 * zlecs / zlell : 0, zlecs - bol); showmsg(msg); return 0; } /**/ int undefinedkey(UNUSED(char **args)) { return 1; } /**/ int quotedinsert(char **args) { #ifndef HAS_TIO struct sgttyb sob; sob = shttyinfo.sgttyb; sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO; ioctl(SHTTY, TIOCSETN, &sob); #endif getfullchar(0); #ifndef HAS_TIO zsetterm(); #endif if (LASTFULLCHAR == ZLEEOF) return 1; else return selfinsert(args); } static int parsedigit(int inkey) { #ifdef MULTIBYTE_SUPPORT /* * It's too dangerous to allow metafied input. See * universalargument for comments on (possibly suboptimal) handling * of digits. We are assuming ASCII is a subset of the multibyte * encoding. */ #else /* allow metafied as well as ordinary digits */ inkey &= 0x7f; #endif /* remember inkey is not a wide character */ if (zmod.base > 10) { if (inkey >= 'a' && inkey < 'a' + zmod.base - 10) return inkey - 'a' + 10; if (inkey >= 'A' && inkey < 'A' + zmod.base - 10) return inkey - 'A' + 10; if (idigit(inkey)) return inkey - '0'; return -1; } if (inkey >= '0' && inkey < '0' + zmod.base) return inkey - '0'; return -1; } /**/ int digitargument(UNUSED(char **args)) { int sign = (zmult < 0) ? -1 : 1; int newdigit = parsedigit(lastchar); if (newdigit < 0) return 1; if (!(zmod.flags & MOD_TMULT)) zmod.tmult = 0; if (zmod.flags & MOD_NEG) { /* If we just had a negative argument, this is the digit, * * rather than the -1 assumed by negargument() */ zmod.tmult = sign * newdigit; zmod.flags &= ~MOD_NEG; } else zmod.tmult = zmod.tmult * zmod.base + sign * newdigit; zmod.flags |= MOD_TMULT; prefixflag = 1; return 0; } /**/ int negargument(UNUSED(char **args)) { if (zmod.flags & MOD_TMULT) return 1; zmod.tmult = -1; zmod.flags |= MOD_TMULT|MOD_NEG; prefixflag = 1; return 0; } /**/ int universalargument(char **args) { int digcnt = 0, pref = 0, minus = 1, gotk; if (*args) { zmod.mult = atoi(*args); zmod.flags |= MOD_MULT; return 0; } /* * TODO: this is quite tricky to do when trying to maintain * compatibility between the old input system and Unicode. * We don't know what follows the digits, so if we try to * read wide characters we may fail (e.g. we may come across an old * \M-style binding). * * If we assume individual bytes are either explicitly ASCII or * not (a la UTF-8), we get away with it; we can back up individual * bytes and everything will work. We may want to relax this * assumption later. ("Much later" - (C) Steven Singer, * CSR BlueCore firmware, ca. 2000.) * * Hence for now this remains byte-by-byte. */ while ((gotk = getbyte(0L, NULL)) != EOF) { if (gotk == '-' && !digcnt) { minus = -1; digcnt++; } else { int newdigit = parsedigit(gotk); if (newdigit >= 0) { pref = pref * zmod.base + newdigit; digcnt++; } else { ungetbyte(gotk); break; } } } if (digcnt) zmod.tmult = minus * (pref ? pref : 1); else zmod.tmult *= 4; zmod.flags |= MOD_TMULT; prefixflag = 1; return 0; } /* Set the base for a digit argument. */ /**/ int argumentbase(char **args) { int multbase; if (*args) multbase = (int)zstrtol(*args, NULL, 0); else multbase = zmod.mult; if (multbase < 2 || multbase > ('9' - '0' + 1) + ('z' - 'a' + 1)) return 1; zmod.base = multbase; /* reset modifier, apart from base... */ zmod.flags = 0; zmod.mult = 1; zmod.tmult = 1; zmod.vibuf = 0; /* ...but indicate we are still operating on a prefix argument. */ prefixflag = 1; return 0; } /**/ int copyprevword(UNUSED(char **args)) { int len, t0 = zlecs, t1; if (zmult > 0) { int count = zmult; for (;;) { t1 = t0; while (t0) { int prev = t0; DECPOS(prev); if (ZC_iword(zleline[prev])) break; t0 = prev; } while (t0) { int prev = t0; DECPOS(prev); if (!ZC_iword(zleline[prev])) break; t0 = prev; } if (!--count) break; if (t0 == 0) return 1; } } else return 1; len = t1 - t0; spaceinline(len); ZS_memcpy(zleline + zlecs, zleline + t0, len); zlecs += len; return 0; } /**/ int copyprevshellword(UNUSED(char **args)) { LinkList l; LinkNode n; int i; char *p = NULL; if (zmult <= 0) return 1; if ((l = bufferwords(NULL, NULL, &i, LEXFLAGS_ZLE))) { i -= (zmult-1); if (i < 0) return 1; for (n = firstnode(l); n; incnode(n)) if (!i--) { p = getdata(n); break; } } if (p) { int len; ZLE_STRING_T lineadd = stringaszleline(p, 0, &len, NULL, NULL); spaceinline(len); ZS_memcpy(zleline + zlecs, lineadd, len); zlecs += len; free(lineadd); } return 0; } /**/ int sendbreak(UNUSED(char **args)) { errflag |= ERRFLAG_ERROR|ERRFLAG_INT; return 1; } /**/ int quoteregion(UNUSED(char **args)) { ZLE_STRING_T str; size_t len; int extra = invicmdmode(); if (mark > zlell) mark = zlell; if (region_active == 2) { int a, b; regionlines(&a, &b); zlecs = a; mark = b; extra = 0; } else if (mark < zlecs) { int tmp = mark; mark = zlecs; zlecs = tmp; } if (extra) INCPOS(mark); str = (ZLE_STRING_T)hcalloc((len = mark - zlecs) * ZLE_CHAR_SIZE); ZS_memcpy(str, zleline + zlecs, len); foredel(len, CUT_RAW); str = makequote(str, &len); spaceinline(len); ZS_memcpy(zleline + zlecs, str, len); mark = zlecs; zlecs += len; return 0; } /**/ int quoteline(UNUSED(char **args)) { ZLE_STRING_T str; size_t len = zlell; str = makequote(zleline, &len); sizeline(len); ZS_memcpy(zleline, str, len); zlecs = zlell = len; return 0; } /**/ static ZLE_STRING_T makequote(ZLE_STRING_T str, size_t *len) { int qtct = 0; ZLE_STRING_T l, ol; ZLE_STRING_T end = str + *len; for (l = str; l < end; l++) if (*l == ZWC('\'')) qtct++; *len += 2 + qtct*3; l = ol = (ZLE_STRING_T)zhalloc(*len * ZLE_CHAR_SIZE); *l++ = ZWC('\''); for (; str < end; str++) if (*str == ZWC('\'')) { *l++ = ZWC('\''); *l++ = ZWC('\\'); *l++ = ZWC('\''); *l++ = ZWC('\''); } else *l++ = *str; *l++ = ZWC('\''); return ol; } /* * cmdstr is the buffer used for execute-named-command converted * to a metafied multibyte string. */ static char *namedcmdstr; static LinkList namedcmdll; static int namedcmdambig; /**/ static void scancompcmd(HashNode hn, UNUSED(int flags)) { int l; Thingy t = (Thingy) hn; if(strpfx(namedcmdstr, t->nam)) { addlinknode(namedcmdll, t->nam); l = pfxlen(peekfirst(namedcmdll), t->nam); if (l < namedcmdambig) namedcmdambig = l; } } #define NAMLEN 60 /* * Local keymap used when reading a command name for the * execute-named-command and where-is widgets. */ /**/ Keymap command_keymap; /**/ Thingy executenamedcommand(char *prmt) { Thingy cmd, retval = NULL; int l, len, feep = 0, listed = 0, curlist = 0; int ols = (listshown && validlist), olll = lastlistlen; char *cmdbuf, *ptr; char *okeymap = ztrdup(curkeymapname); clearlist = 1; /* prmt may be constant */ prmt = ztrdup(prmt); l = strlen(prmt); cmdbuf = (char *)zhalloc(l + NAMLEN + 2 #ifdef MULTIBYTE_SUPPORT + 2 * MB_CUR_MAX #endif ); strcpy(cmdbuf, prmt); zsfree(prmt); statusline = cmdbuf; selectlocalmap(command_keymap); selectkeymap("main", 1); ptr = cmdbuf += l; len = 0; for (;;) { *ptr = '_'; ptr[1] = '\0'; zrefresh(); if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { statusline = NULL; selectkeymap(okeymap, 1); zsfree(okeymap); if ((listshown = ols)) { showinglist = -2; lastlistlen = olll; } else if (listed) clearlist = listshown = 1; retval = NULL; goto done; } if(cmd == Th(z_clearscreen)) { clearscreen(zlenoargs); if (curlist) { int zmultsav = zmult; zmult = 1; listlist(namedcmdll); showinglist = 0; zmult = zmultsav; } } else if(cmd == Th(z_redisplay)) { redisplay(zlenoargs); if (curlist) { int zmultsav = zmult; zmult = 1; listlist(namedcmdll); showinglist = 0; zmult = zmultsav; } } else if(cmd == Th(z_viquotedinsert)) { *ptr = '^'; zrefresh(); getfullchar(0); if(LASTFULLCHAR == ZLEEOF || !LASTFULLCHAR || len >= NAMLEN) feep = 1; else { int ret = zlecharasstring(LASTFULLCHAR, ptr); len += ret; ptr += ret; curlist = 0; } } else if(cmd == Th(z_quotedinsert)) { if(getfullchar(0) == ZLEEOF || !LASTFULLCHAR || len == NAMLEN) feep = 1; else { int ret = zlecharasstring(LASTFULLCHAR, ptr); len += ret; ptr += ret; curlist = 0; } } else if(cmd == Th(z_backwarddeletechar) || cmd == Th(z_vibackwarddeletechar)) { if (len) { ptr = backwardmetafiedchar(cmdbuf, ptr, NULL); len = ptr - cmdbuf; curlist = 0; } } else if(cmd == Th(z_killregion) || cmd == Th(z_backwardkillword) || cmd == Th(z_vibackwardkillword)) { if (len) curlist = 0; while (len) { convchar_t cc; ptr = backwardmetafiedchar(cmdbuf, ptr, &cc); len = ptr - cmdbuf; if (cc == ZWC('-')) break; } } else if(cmd == Th(z_killwholeline) || cmd == Th(z_vikillline) || cmd == Th(z_backwardkillline)) { len = 0; ptr = cmdbuf; if (listed) clearlist = listshown = 1; curlist = 0; } else { if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) { Thingy r; unambiguous: *ptr = 0; r = rthingy(cmdbuf); if (!(r->flags & DISABLED)) { unrefthingy(r); statusline = NULL; selectkeymap(okeymap, 1); zsfree(okeymap); if ((listshown = ols)) { showinglist = -2; lastlistlen = olll; } else if (listed) clearlist = listshown = 1; retval = r; goto done; } unrefthingy(r); } if(cmd == Th(z_selfinsertunmeta)) { fixunmeta(); cmd = Th(z_selfinsert); } if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist) || cmd == Th(z_expandorcomplete) || cmd == Th(z_completeword) || cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_vicmdmode) || cmd == Th(z_acceptline) || lastchar == ' ' || lastchar == '\t') { namedcmdambig = 100; namedcmdll = newlinklist(); *ptr = '\0'; namedcmdstr = cmdbuf; scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0); namedcmdstr = NULL; if (empty(namedcmdll)) { feep = 1; if (listed) clearlist = listshown = 1; curlist = 0; } else if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist)) { int zmultsav = zmult; *ptr = '_'; ptr[1] = '\0'; zmult = 1; listlist(namedcmdll); listed = curlist = 1; showinglist = 0; zmult = zmultsav; } else if (!nextnode(firstnode(namedcmdll))) { strcpy(ptr = cmdbuf, peekfirst(namedcmdll)); len = strlen(ptr); ptr += len; if (cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) goto unambiguous; } else { strcpy(cmdbuf, peekfirst(namedcmdll)); ptr = cmdbuf + namedcmdambig; *ptr = '_'; ptr[1] = '\0'; if (isset(AUTOLIST) && !(isset(LISTAMBIGUOUS) && namedcmdambig > len)) { int zmultsav = zmult; if (isset(LISTBEEP)) feep = 1; zmult = 1; listlist(namedcmdll); listed = curlist = 1; showinglist = 0; zmult = zmultsav; } len = namedcmdambig; } } else { if (len == NAMLEN || cmd != Th(z_selfinsert)) feep = 1; else { #ifdef MULTIBYTE_SUPPORT if (!lastchar_wide_valid) getrestchar(lastchar); if (lastchar_wide == WEOF) feep = 1; else #endif if (ZC_icntrl(LASTFULLCHAR)) feep = 1; else { int ret = zlecharasstring(LASTFULLCHAR, ptr); len += ret; ptr += ret; if (listed) { clearlist = listshown = 1; listed = 0; } else curlist = 0; } } } } if (feep) handlefeep(zlenoargs); feep = 0; } done: selectlocalmap(NULL); return retval; } /*****************/ /* Suffix system */ /*****************/ /* * The completion system sometimes tentatively adds a suffix to a word, * which can be removed depending on what is inserted next. These * functions provide the capability to handle a removable suffix. * * Any removable suffix consists of characters immediately before the * cursor. Whether it is removed depends on the next editing action. * There can be more than one suffix simultaneously present, with * different actions deleting different numbers of characters. * * If the next editing action changes the buffer other than by inserting * characters, normally the suffix should be removed so as to leave a * meaningful complete word. The behaviour should be the same if the * next character inserted is a word separator. If the next character * reasonably belongs where it is typed, or if the next editing action * is a deletion, the suffix should not be removed. Other reasons for * suffix removal may have other behaviour. * * In order to maintain a consistent state, after a suffix has been added * the table *must* be zeroed, one way or another, before the buffer is * changed. If the suffix is not being removed, call fixsuffix() to * indicate that it is being permanently fixed. */ struct suffixset; /* An element of a suffix specification */ struct suffixset { struct suffixset *next; /* Next in the list */ int tp; /* The SUFTYP_* from enum suffixtype */ int flags; /* Some of SUFFLAGS_* */ ZLE_STRING_T chars; /* Set of characters to match (or not) */ int lenstr; /* Length of chars */ int lensuf; /* Length of suffix */ }; /* The list of suffix structures */ struct suffixset *suffixlist; /* Shell function to call to remove the suffix. */ /**/ static char *suffixfunc; /* Length associated with the suffix function */ static int suffixfunclen; /* Whether to remove suffix on uninsertable characters */ /**/ int suffixnoinsrem; /* Length of the currently active, auto-removable suffix. */ /**/ mod_export int suffixlen; /**/ mod_export void addsuffix(int tp, int flags, ZLE_STRING_T chars, int lenstr, int lensuf) { struct suffixset *newsuf = zalloc(sizeof(struct suffixset)); newsuf->next = suffixlist; suffixlist = newsuf; newsuf->tp = tp; newsuf->flags = flags; if (lenstr) { newsuf->chars = zalloc(lenstr*sizeof(ZLE_CHAR_T)); ZS_memcpy(newsuf->chars, chars, lenstr); } else newsuf->chars = NULL; newsuf->lenstr = lenstr; newsuf->lensuf = lensuf; } /* Same as addsuffix, but from metafied string */ /**/ mod_export void addsuffixstring(int tp, int flags, char *chars, int lensuf) { int slen, alloclen; ZLE_STRING_T suffixstr; /* string needs to be writable... I've been regretting this for years.. */ chars = ztrdup(chars); suffixstr = stringaszleline(chars, 0, &slen, &alloclen, NULL); addsuffix(tp, flags, suffixstr, slen, lensuf); zfree(suffixstr, alloclen); zsfree(chars); } /* Set up suffix: the last n characters are a suffix that should be * * removed in the usual word end conditions. */ /**/ mod_export void makesuffix(int n) { char *suffixchars; if (!(suffixchars = getsparam("ZLE_REMOVE_SUFFIX_CHARS"))) suffixchars = " \t\n;&|"; addsuffixstring(SUFTYP_POSSTR, 0, suffixchars, n); /* Do this second so it takes precedence */ if ((suffixchars = getsparam("ZLE_SPACE_SUFFIX_CHARS")) && *suffixchars) addsuffixstring(SUFTYP_POSSTR, SUFFLAGS_SPACE, suffixchars, n); suffixlen = n; suffixnoinsrem = 1; } /* Set up suffix for parameter names: the last n characters are a suffix * * that should be removed if the next character is one of the ones that * * needs to go immediately after the parameter name. br indicates that * * the name is in braces (${PATH} instead of $PATH), so the extra * * characters that can only be used in braces are included. */ /**/ mod_export void makeparamsuffix(int br, int n) { ZLE_STRING_T charstr = ZWS(":[#%?-+="); int lenstr = 0; if (br || unset(KSHARRAYS)) { lenstr = 2; if (br) lenstr += 6; } if (lenstr) addsuffix(SUFTYP_POSSTR, 0, charstr, lenstr, n); } /* Set up suffix given a string containing the characters on which to * * remove the suffix. */ /**/ mod_export void makesuffixstr(char *f, char *s, int n) { if (f) { zsfree(suffixfunc); suffixfunc = ztrdup(f); suffixfunclen = n; } else if (s) { int inv, i, z = 0; ZLE_STRING_T ws, lasts, wptr; if (*s == '^' || *s == '!') { inv = 1; s++; } else inv = 0; s = getkeystring(s, &i, GETKEYS_SUFFIX, &z); s = metafy(s, i, META_USEHEAP); ws = stringaszleline(s, 0, &i, NULL, NULL); /* Remove suffix on uninsertable characters if \- was given * * and the character class wasn't negated -- or vice versa. */ suffixnoinsrem = z ^ inv; suffixlen = n; lasts = wptr = ws; while (i) { if (i >= 3 && wptr[1] == ZWC('-')) { ZLE_CHAR_T str[2]; if (wptr > lasts) addsuffix(inv ? SUFTYP_NEGSTR : SUFTYP_POSSTR, 0, lasts, wptr - lasts, n); str[0] = *wptr; str[1] = wptr[2]; addsuffix(inv ? SUFTYP_NEGRNG : SUFTYP_POSRNG, 0, str, 2, n); wptr += 3; i -= 3; lasts = wptr; } else { wptr++; i--; } } if (wptr > lasts) addsuffix(inv ? SUFTYP_NEGSTR : SUFTYP_POSSTR, 0, lasts, wptr - lasts, n); free(ws); } else makesuffix(n); } /* Remove suffix, if there is one, when inserting character c. */ /**/ mod_export void iremovesuffix(ZLE_INT_T c, int keep) { if (suffixfunc) { Shfunc shfunc = getshfunc(suffixfunc); if (shfunc) { LinkList args = newlinklist(); char buf[20]; int osc = sfcontext; int wasmeta = (zlemetaline != 0); if (wasmeta) { /* * The suffix removal function runs as a normal * ZLE function, not a completion function, so * the line should be unmetafied if this was * called from completion. (It may not be since * we may decide to remove the suffix later.) */ unmetafy_line(); } sprintf(buf, "%d", suffixfunclen); addlinknode(args, suffixfunc); addlinknode(args, buf); startparamscope(); makezleparams(0); sfcontext = SFC_COMPLETE; doshfunc(shfunc, args, 1); sfcontext = osc; endparamscope(); if (wasmeta) metafy_line(); } zsfree(suffixfunc); suffixfunc = NULL; } else { int sl = 0, flags = 0; struct suffixset *ss; if (c == NO_INSERT_CHAR) { sl = suffixnoinsrem ? suffixlen : 0; } else { ZLE_CHAR_T ch = c; /* * Search for a match for ch in the suffix list. * We stop if we encounter a match in a positive or negative * list, using the suffix length specified or zero respectively. * If we reached the end and passed through a negative * list, we use the suffix length for that, else zero. * This would break if it were possible to have negative * sets with different suffix length: that's not supposed * to happen. */ int negsuflen = 0, found = 0; for (ss = suffixlist; ss; ss = ss->next) { switch (ss->tp) { case SUFTYP_POSSTR: if (ZS_memchr(ss->chars, ch, ss->lenstr)) { sl = ss->lensuf; found = 1; } break; case SUFTYP_NEGSTR: if (ZS_memchr(ss->chars, ch, ss->lenstr)) { sl = 0; found = 1; } else { negsuflen = ss->lensuf; } break; case SUFTYP_POSRNG: if (ss->chars[0] <= ch && ch <= ss->chars[1]) { sl = ss->lensuf; found = 1; } break; case SUFTYP_NEGRNG: if (ss->chars[0] <= ch && ch <= ss->chars[1]) { sl = 0; found = 1; } else { negsuflen = ss->lensuf; } break; } if (found) { flags = ss->flags; break; } } if (!found) sl = negsuflen; } if (sl) { /* must be shifting wide character lengths */ backdel(sl, CUT_RAW); if (flags & SUFFLAGS_SPACE) { /* Add a space and advance over it */ spaceinline(1); if (zlemetaline) { zlemetaline[zlemetacs++] = ' '; } else { zleline[zlecs++] = ZWC(' '); } } if (!keep) invalidatelist(); } } fixsuffix(); } /* Fix the suffix in place, if there is one, making it non-removable. */ /**/ mod_export void fixsuffix(void) { while (suffixlist) { struct suffixset *next = suffixlist->next; if (suffixlist->lenstr) zfree(suffixlist->chars, suffixlist->lenstr * sizeof(ZLE_CHAR_T)); zfree(suffixlist, sizeof(struct suffixset)); suffixlist = next; } suffixfunclen = suffixnoinsrem = suffixlen = 0; }