/* * zle_vi.c - vi-specific functions * * 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_vi.pro" /* != 0 if we're getting a vi range */ /**/ int virangeflag; /* kludge to get cw and dw to work right */ /**/ int wordflag; /* != 0 if we're killing lines into a buffer, vi-style */ /**/ int vilinerange; /* last vi change buffer, for vi change repetition */ /**/ int vichgbufsz, vichgbufptr, vichgflag; /* * Examination of the code suggests vichgbuf is consistently tied * to raw byte input, so it is left as a character array rather * than turned into wide characters. In particular, when we replay * it we use ungetbytes(). */ /**/ char *vichgbuf; /* point where vi insert mode was last entered */ /**/ int viinsbegin; static struct modifier lastmod; static int inrepeat, vichgrepeat; /**/ static void startvichange(int im) { if (im != -1) { insmode = im; vichgflag = 1; } if (inrepeat) { zmod = lastmod; inrepeat = vichgflag = 0; vichgrepeat = 1; } else { lastmod = zmod; if (vichgbuf) free(vichgbuf); vichgbuf = (char *)zalloc(vichgbufsz = 16); vichgbuf[0] = lastchar; vichgbufptr = 1; vichgrepeat = 0; } } /**/ static void startvitext(int im) { startvichange(im); selectkeymap("main", 1); undoing = 0; viinsbegin = zlecs; } /**/ ZLE_INT_T vigetkey(void) { Keymap mn = openkeymap("main"); char m[3], *str; Thingy cmd; if (getbyte(0, NULL) == EOF) return ZLEEOF; m[0] = lastchar; metafy(m, 1, META_NOALLOC); if(mn) cmd = keybind(mn, m, &str); else cmd = t_undefinedkey; if (!cmd || cmd == Th(z_sendbreak)) { return ZLEEOF; } else if (cmd == Th(z_quotedinsert)) { if (getfullchar(0) == ZLEEOF) return ZLEEOF; } else if(cmd == Th(z_viquotedinsert)) { ZLE_CHAR_T sav = zleline[zlecs]; zleline[zlecs] = '^'; zrefresh(); getfullchar(0); zleline[zlecs] = sav; if(LASTFULLCHAR == ZLEEOF) return ZLEEOF; } else if (cmd == Th(z_vicmdmode)) { return ZLEEOF; } #ifdef MULTIBYTE_SUPPORT if (!lastchar_wide_valid) { getrestchar(lastchar); } #endif return LASTFULLCHAR; } /**/ static int getvirange(int wf) { int pos = zlecs, ret = 0; int mult1 = zmult, hist1 = histline; Thingy k2; virangeflag = 1; wordflag = wf; /* Now we need to execute the movement command, to see where it * * actually goes. virangeflag here indicates to the movement * * function that it should place the cursor at the end of the * * range, rather than where the cursor would actually go if it * * were executed normally. This makes a difference to some * * commands, but not all. For example, if searching forward * * for a character, under normal circumstances the cursor lands * * on the character. For a range, the range must include the * * character, so the cursor gets placed after the character if * * virangeflag is set. vi-match-bracket needs to change the * * value of virangeflag under some circumstances, meaning that * * we need to change the *starting* position. */ zmod.flags &= ~MOD_TMULT; do { vilinerange = 0; prefixflag = 0; if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) || k2 == Th(z_sendbreak)) { wordflag = 0; virangeflag = 0; return -1; } /* * With k2 == bindk, the command key is repeated: * a number of lines is used. If the function used * returns 1, we fail. */ if ((k2 == bindk) ? dovilinerange() : execzlefunc(k2, zlenoargs, 1)) ret = -1; if(vichgrepeat) zmult = mult1; else zmult = mult1 * zmod.tmult; } while(prefixflag && !ret); wordflag = 0; virangeflag = 0; /* It is an error to use a non-movement command to delimit the * * range. We here reject the case where the command modified * * the line, or selected a different history line. */ if (histline != hist1 || zlell != lastll || memcmp(zleline, lastline, zlell)) { histline = hist1; ZS_memcpy(zleline, lastline, zlell = lastll); zlecs = pos; return -1; } /* Can't handle an empty file. Also, if the movement command * * failed, or didn't move, it is an error. */ if (!zlell || (zlecs == pos && virangeflag != 2) || ret == -1) return -1; /* vi-match-bracket changes the value of virangeflag when * * moving to the opening bracket, meaning that we need to * * change the *starting* position. */ if(virangeflag == -1) pos++; /* Get the range the right way round. zlecs is placed at the * * start of the range, and pos (the return value of this * * function) is the end. */ if (zlecs > pos) { int tmp = zlecs; zlecs = pos; pos = tmp; } /* Was it a line-oriented move? If so, the command will have set * * the vilinerange flag. In this case, entire lines are taken, * * rather than just the sequence of characters delimited by pos * * and zlecs. The terminating newline is left out of the range, * * which the real command must deal with appropriately. At this * * point we just need to make the range encompass entire lines. */ if(vilinerange) { int newcs = findbol(); zlecs = pos; pos = findeol(); zlecs = newcs; } return pos; } /**/ static int dovilinerange(void) { int pos = zlecs, n = zmult; /* A number of lines is taken as the range. The current line * * is included. If the repeat count is positive the lines go * * downward, otherwise upward. The repeat count gives the * * number of lines. */ vilinerange = 1; if (!n) return 1; if (n > 0) { while(n-- && zlecs <= zlell) zlecs = findeol() + 1; if (n != -1) { zlecs = pos; return 1; } zlecs--; } else { while(n++ && zlecs >= 0) zlecs = findbol() - 1; if (n != 1) { zlecs = pos; return 1; } zlecs++; } virangeflag = 2; return 0; } /**/ int viaddnext(UNUSED(char **args)) { if (zlecs != findeol()) zlecs++; startvitext(1); return 0; } /**/ int viaddeol(UNUSED(char **args)) { zlecs = findeol(); startvitext(1); return 0; } /**/ int viinsert(UNUSED(char **args)) { startvitext(1); return 0; } /**/ int viinsertbol(UNUSED(char **args)) { vifirstnonblank(zlenoargs); startvitext(1); return 0; } /**/ int videlete(UNUSED(char **args)) { int c2, ret = 1; startvichange(1); if ((c2 = getvirange(0)) != -1) { forekill(c2 - zlecs, 0); ret = 0; if (vilinerange && zlell) { if (zlecs == zlell) zlecs--; foredel(1); vifirstnonblank(zlenoargs); } } vichgflag = 0; return ret; } /**/ int videletechar(char **args) { int n = zmult; startvichange(-1); /* handle negative argument */ if (n < 0) { int ret; zmult = -n; ret = vibackwarddeletechar(args); zmult = n; return ret; } /* it is an error to be on the end of line */ if (zlecs == zlell || zleline[zlecs] == '\n') return 1; /* Put argument into the acceptable range -- it is not an error to * * specify a greater count than the number of available characters. */ if (n > findeol() - zlecs) n = findeol() - zlecs; /* do the deletion */ forekill(n, 0); return 0; } /**/ int vichange(UNUSED(char **args)) { int c2, ret = 1; startvichange(1); if ((c2 = getvirange(1)) != -1) { ret = 0; forekill(c2 - zlecs, 0); selectkeymap("main", 1); viinsbegin = zlecs; undoing = 0; } return ret; } /**/ int visubstitute(UNUSED(char **args)) { int n = zmult; startvichange(1); if (n < 0) return 1; /* it is an error to be on the end of line */ if (zlecs == zlell || zleline[zlecs] == '\n') return 1; /* Put argument into the acceptable range -- it is not an error to * * specify a greater count than the number of available characters. */ if (n > findeol() - zlecs) n = findeol() - zlecs; /* do the substitution */ forekill(n, 0); startvitext(1); return 0; } /**/ int vichangeeol(UNUSED(char **args)) { forekill(findeol() - zlecs, 0); startvitext(1); return 0; } /**/ int vichangewholeline(char **args) { vifirstnonblank(args); return vichangeeol(zlenoargs); } /**/ int viyank(UNUSED(char **args)) { int oldcs = zlecs, c2, ret = 1; startvichange(1); if ((c2 = getvirange(0)) != -1) { cut(zlecs, c2 - zlecs, 0); ret = 0; } vichgflag = 0; zlecs = oldcs; return ret; } /**/ int viyankeol(UNUSED(char **args)) { int x = findeol(); startvichange(-1); if (x == zlecs) return 1; cut(zlecs, x - zlecs, 0); return 0; } /**/ int viyankwholeline(UNUSED(char **args)) { int bol = findbol(), oldcs = zlecs; int n = zmult; startvichange(-1); if (n < 1) return 1; while(n--) { if (zlecs > zlell) { zlecs = oldcs; return 1; } zlecs = findeol() + 1; } vilinerange = 1; cut(bol, zlecs - bol - 1, 0); zlecs = oldcs; return 0; } /**/ int vireplace(UNUSED(char **args)) { startvitext(0); return 0; } /* vi-replace-chars has some oddities relating to vi-repeat-change. In * * the real vi, if one does 3r at the end of a line, it feeps without * * reading the argument, and won't repeat the action. A successful rx * * followed by 3. at the end of a line (or 3rx followed by . at the end * * of a line) will obviously feep after the ., even though it has the * * argument available. Here repeating is tied very closely to argument * * reading, so some trickery is needed to emulate this. When repeating * * a change, we always read the argument normally, even if the count * * was bad. When recording a change for repeating, and a bad count is * * given, we squash the repeat buffer to avoid repeating the partial * * command; we've lost the previous change, but that can't be avoided * * without a rewrite of the repeat code. */ /**/ int vireplacechars(UNUSED(char **args)) { ZLE_INT_T ch; int n = zmult; startvichange(1); /* check argument range */ if (n < 1 || n + zlecs > findeol()) { if(vichgrepeat) vigetkey(); if(vichgflag) { free(vichgbuf); vichgbuf = NULL; vichgflag = 0; } return 1; } /* get key */ if((ch = vigetkey()) == ZLEEOF) { vichgflag = 0; return 1; } /* do change */ if (ch == ZWC('\r') || ch == ZWC('\n')) { /* handled specially */ zlecs += n - 1; backkill(n - 1, 0); zleline[zlecs++] = '\n'; } else { while (n--) zleline[zlecs++] = ch; zlecs--; } vichgflag = 0; return 0; } /**/ int vicmdmode(UNUSED(char **args)) { if (invicmdmode() || selectkeymap("vicmd", 0)) return 1; undoing = 1; vichgflag = 0; if (zlecs != findbol()) zlecs--; return 0; } /**/ int viopenlinebelow(UNUSED(char **args)) { zlecs = findeol(); spaceinline(1); zleline[zlecs++] = '\n'; startvitext(1); clearlist = 1; return 0; } /**/ int viopenlineabove(UNUSED(char **args)) { zlecs = findbol(); spaceinline(1); zleline[zlecs] = '\n'; startvitext(1); clearlist = 1; return 0; } /**/ int vioperswapcase(UNUSED(char **args)) { int oldcs, c2, ret = 1; /* get the range */ startvichange(1); if ((c2 = getvirange(0)) != -1) { oldcs = zlecs; /* swap the case of all letters within range */ while (zlecs < c2) { if (ZC_ilower(zleline[zlecs])) zleline[zlecs] = ZC_toupper(zleline[zlecs]); else if (ZC_iupper(zleline[zlecs])) zleline[zlecs] = ZC_tolower(zleline[zlecs]); zlecs++; } /* go back to the first line of the range */ zlecs = oldcs; ret = 0; #if 0 vifirstnonblank(); #endif } vichgflag = 0; return ret; } /**/ int virepeatchange(UNUSED(char **args)) { /* make sure we have a change to repeat */ if (!vichgbuf || vichgflag) return 1; /* restore or update the saved count and buffer */ if (zmod.flags & MOD_MULT) { lastmod.mult = zmod.mult; lastmod.flags |= MOD_MULT; } if (zmod.flags & MOD_VIBUF) { lastmod.vibuf = zmod.vibuf; lastmod.flags = (lastmod.flags & ~MOD_VIAPP) | MOD_VIBUF | (zmod.flags & MOD_VIAPP); } /* repeat the command */ inrepeat = 1; ungetbytes(vichgbuf, vichgbufptr); return 0; } /**/ int viindent(UNUSED(char **args)) { int oldcs = zlecs, c2; /* get the range */ startvichange(1); if ((c2 = getvirange(0)) == -1) { vichgflag = 0; return 1; } vichgflag = 0; /* must be a line range */ if (!vilinerange) { zlecs = oldcs; return 1; } oldcs = zlecs; /* add a tab to the beginning of each line within range */ while (zlecs < c2) { spaceinline(1); zleline[zlecs] = '\t'; zlecs = findeol() + 1; } /* go back to the first line of the range */ zlecs = oldcs; vifirstnonblank(zlenoargs); return 0; } /**/ int viunindent(UNUSED(char **args)) { int oldcs = zlecs, c2; /* get the range */ startvichange(1); if ((c2 = getvirange(0)) == -1) { vichgflag = 0; return 1; } vichgflag = 0; /* must be a line range */ if (!vilinerange) { zlecs = oldcs; return 1; } oldcs = zlecs; /* remove a tab from the beginning of each line within range */ while (zlecs < c2) { if (zleline[zlecs] == '\t') foredel(1); zlecs = findeol() + 1; } /* go back to the first line of the range */ zlecs = oldcs; vifirstnonblank(zlenoargs); return 0; } /**/ int vibackwarddeletechar(char **args) { int n = zmult; if (invicmdmode()) startvichange(-1); /* handle negative argument */ if (n < 0) { int ret; zmult = -n; ret = videletechar(args); zmult = n; return ret; } /* It is an error to be at the beginning of the line, or (in * * insert mode) to delete past the beginning of insertion. */ if ((!invicmdmode() && zlecs - n < viinsbegin) || zlecs == findbol()) { return 1; } /* Put argument into the acceptable range -- it is not an error to * * specify a greater count than the number of available characters. */ if (n > zlecs - findbol()) n = zlecs - findbol(); /* do the deletion */ backkill(n, 1); return 0; } /**/ int vikillline(UNUSED(char **args)) { if (viinsbegin > zlecs) return 1; backdel(zlecs - viinsbegin); return 0; } /**/ int viputbefore(UNUSED(char **args)) { Cutbuffer buf = &cutbuf; int n = zmult; startvichange(-1); if (n < 0) return 1; if (zmod.flags & MOD_VIBUF) buf = &vibuf[zmod.vibuf]; if (!buf->buf) return 1; if(buf->flags & CUTBUFFER_LINE) { zlecs = findbol(); spaceinline(buf->len + 1); ZS_memcpy(zleline + zlecs, buf->buf, buf->len); zleline[zlecs + buf->len] = ZWC('\n'); vifirstnonblank(zlenoargs); } else { while (n--) { spaceinline(buf->len); ZS_memcpy(zleline + zlecs, buf->buf, buf->len); zlecs += buf->len; } if (zlecs) zlecs--; } return 0; } /**/ int viputafter(UNUSED(char **args)) { Cutbuffer buf = &cutbuf; int n = zmult; startvichange(-1); if (n < 0) return 1; if (zmod.flags & MOD_VIBUF) buf = &vibuf[zmod.vibuf]; if (!buf->buf) return 1; if(buf->flags & CUTBUFFER_LINE) { zlecs = findeol(); spaceinline(buf->len + 1); zleline[zlecs++] = ZWC('\n'); ZS_memcpy(zleline + zlecs, buf->buf, buf->len); vifirstnonblank(zlenoargs); } else { if (zlecs != findeol()) zlecs++; while (n--) { spaceinline(buf->len); ZS_memcpy(zleline + zlecs, buf->buf, buf->len); zlecs += buf->len; } if (zlecs) zlecs--; } return 0; } /**/ int vijoin(UNUSED(char **args)) { int x; startvichange(-1); if ((x = findeol()) == zlell) return 1; zlecs = x + 1; for (x = 1; zlecs != zlell && ZC_iblank(zleline[zlecs]); zlecs++, x++); backdel(x); if (zlecs && ZC_iblank(zleline[zlecs-1])) zlecs--; else { spaceinline(1); zleline[zlecs] = ZWC(' '); } return 0; } /**/ int viswapcase(UNUSED(char **args)) { int eol, n = zmult; startvichange(-1); if (n < 1) return 1; eol = findeol(); while (zlecs < eol && n--) { if (ZC_ilower(zleline[zlecs])) zleline[zlecs] = ZC_toupper(zleline[zlecs]); else if (ZC_iupper(zleline[zlecs])) zleline[zlecs] = ZC_tolower(zleline[zlecs]); zlecs++; } if (zlecs && zlecs == eol) zlecs--; return 0; } /**/ int vicapslockpanic(UNUSED(char **args)) { clearlist = 1; zbeep(); statusline = ZWS("press a lowercase key to continue"); statusll = ZS_strlen(statusline); zrefresh(); while (!ZC_ilower(getfullchar(0))); statusline = NULL; return 0; } /**/ int visetbuffer(UNUSED(char **args)) { ZLE_INT_T ch; if ((zmod.flags & MOD_VIBUF) || (((ch = getfullchar(0)) < ZWC('1') || ch > ZWC('9')) && (ch < ZWC('a') || ch > ZWC('z')) && (ch < ZWC('A') || ch > ZWC('Z')))) return 1; if (ch >= ZWC('A') && ch <= ZWC('Z')) /* needed in cut() */ zmod.flags |= MOD_VIAPP; else zmod.flags &= ~MOD_VIAPP; /* FIXME how portable is it for multibyte encoding? */ zmod.vibuf = ZC_tolower(ch); if (ch >= ZWC('1') && ch <= ZWC('9')) zmod.vibuf += - (int)ZWC('1') + 26; else zmod.vibuf += - (int)ZWC('a'); zmod.flags |= MOD_VIBUF; prefixflag = 1; return 0; } /**/ int vikilleol(UNUSED(char **args)) { int n = findeol() - zlecs; startvichange(-1); if (!n) { /* error -- line already empty */ return 1; } /* delete to end of line */ forekill(findeol() - zlecs, 0); return 0; } /**/ int vipoundinsert(UNUSED(char **args)) { int oldcs = zlecs; startvichange(-1); vifirstnonblank(zlenoargs); if(zleline[zlecs] != '#') { spaceinline(1); zleline[zlecs] = '#'; if(zlecs <= viinsbegin) viinsbegin++; zlecs = oldcs + (zlecs <= oldcs); } else { foredel(1); if (zlecs < viinsbegin) viinsbegin--; zlecs = oldcs - (zlecs < oldcs); } return 0; } /**/ int viquotedinsert(char **args) { #ifndef HAS_TIO struct sgttyb sob; #endif spaceinline(1); zleline[zlecs] = '^'; zrefresh(); #ifndef HAS_TIO sob = shttyinfo.sgttyb; sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO; ioctl(SHTTY, TIOCSETN, &sob); #endif getfullchar(0); #ifndef HAS_TIO zsetterm(); #endif foredel(1); if(LASTFULLCHAR == ZLEEOF) return 1; else return selfinsert(args); } /* the 0 key in vi: continue a repeat count in the manner of * * digit-argument if possible, otherwise do vi-beginning-of-line. */ /**/ int vidigitorbeginningofline(char **args) { if(zmod.flags & MOD_TMULT) return digitargument(args); else { removesuffix(); invalidatelist(); return vibeginningofline(args); } }