/* * 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; /** * im: >= 0: is an insertmode * -1: skip setting insert mode * -2: entering viins at start of editing from clean --- don't use * inrepeat or lastchar, synthesise an i to enter insert mode. */ /**/ void startvichange(int im) { if (im != -1) { vichgflag = 1; if (im > -1) insmode = im; } if (inrepeat && im != -2) { zmod = lastmod; inrepeat = vichgflag = 0; vichgrepeat = 1; } else { lastmod = zmod; if (vichgbuf) free(vichgbuf); vichgbuf = (char *)zalloc(vichgbufsz = 16); if (im == -2) { vichgbuf[0] = zlell ? (insmode ? (zlecs < zlell ? 'i' : 'a') : 'R') : 'o'; } else { vichgbuf[0] = lastchar; } vichgbufptr = 1; vichgrepeat = 0; } } /**/ static void startvitext(int im) { startvichange(im); selectkeymap("main", 1); vistartchange = undo_changeno; viinsbegin = zlecs; } /**/ ZLE_INT_T vigetkey(void) { Keymap mn = openkeymap("main"); char m[3], *str; Thingy cmd; if (getbyte(0L, 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, mpos = mark, ret = 0; int visual = region_active; /* movement command might set it */ int mult1 = zmult, hist1 = histline; Thingy k2; if (visual) { if (!zlell) return -1; pos = mark; vilinerange = (visual == 2); region_active = 0; } else { virangeflag = 1; wordflag = wf; mark = -1; /* use operator-pending keymap if one exists */ Keymap km = openkeymap("viopp"); if (km) selectlocalmap(km); /* 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. */ zmod.flags &= ~MOD_TMULT; do { vilinerange = 0; prefixflag = 0; if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) || k2 == Th(z_sendbreak)) { wordflag = 0; virangeflag = 0; mark = mpos; 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; selectlocalmap(NULL); /* 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; mark = mpos; 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 && (mark == -1 || mark == zlecs) && virangeflag != 2) || ret == -1) { mark = mpos; return -1; } virangeflag = 0; /* if the mark has moved, ignore the original cursor position * * and use the mark. */ if (mark != -1) pos = mark; } mark = mpos; /* 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; } /* visual selection mode needs to include additional position */ if (visual == 1 && invicmdmode()) INCPOS(pos); /* 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. */ vilinerange = (zmod.flags & MOD_LINE) || (vilinerange && !(zmod.flags & MOD_CHAR)); if (vilinerange) { int newcs = findbol(); lastcol = zlecs - newcs; zlecs = pos; pos = findeol(); zlecs = newcs; } else if (!visual) { /* for a character-wise move don't include a newline at the * * end of the range */ int prev = pos; DECPOS(prev); if (zleline[prev] == ZWC('\n')) pos = prev; } 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; } DECCS(); } else { while(n++ && zlecs >= 0) zlecs = findbol() - 1; if (n != 1) { zlecs = pos; return 1; } INCCS(); } virangeflag = 2; return 0; } /**/ int viaddnext(UNUSED(char **args)) { if (zlecs != findeol()) INCCS(); startvitext(1); return 0; } /**/ int viaddeol(UNUSED(char **args)) { zlecs = findeol(); startvitext(1); return 0; } /**/ int viinsert(UNUSED(char **args)) { startvitext(1); return 0; } /* * Go to vi insert mode when we first start the line editor. * Iniialises some other stuff. */ /**/ void viinsert_init(void) { startvitext(-2); } /**/ 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, CUT_RAW); ret = 0; if (vilinerange && zlell) { lastcol = -1; if (zlecs == zlell) DECCS(); foredel(1, 0); 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. */ /* HERE: we should do the test properly with INCPOS(). */ if (n > findeol() - zlecs) { n = findeol() - zlecs; /* do the deletion */ forekill(n, CUT_RAW); } else { 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, CUT_RAW); selectkeymap("main", 1); viinsbegin = zlecs; vistartchange = undo_changeno; } 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; if (region_active) { killregion(zlenoargs); } else { /* 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, CUT_RAW); } startvitext(1); return 0; } /**/ int vichangeeol(UNUSED(char **args)) { int a, b; if (region_active) { regionlines(&a, &b); zlecs = a; region_active = 0; cut(zlecs, b - zlecs, CUT_RAW); shiftchars(zlecs, b - zlecs); } else forekill(findeol() - zlecs, CUT_RAW); startvitext(1); return 0; } /**/ int vichangewholeline(char **args) { vifirstnonblank(args); return vichangeeol(zlenoargs); } /**/ int viyank(UNUSED(char **args)) { int c2, ret = 1; startvichange(1); if ((c2 = getvirange(0)) != -1) { cut(zlecs, c2 - zlecs, CUT_YANK); ret = 0; } vichgflag = 0; /* cursor now at the start of the range yanked. For line mode * restore the column position */ if (vilinerange && lastcol != -1) { int x = findeol(); if ((zlecs += lastcol) >= x) { zlecs = x; if (zlecs > findbol() && invicmdmode()) DECCS(); } #ifdef MULTIBYTE_SUPPORT else CCRIGHT(); #endif lastcol = -1; } return ret; } /**/ int viyankeol(UNUSED(char **args)) { int x = findeol(); startvichange(-1); if (x == zlecs) return 1; cut(zlecs, x - zlecs, CUT_YANK); 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, CUT_YANK); 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, fail = 0, newchars = 0; if (n > 0) { if (region_active) { int a, b; if (region_active == 1) { if (mark > zlecs) { a = zlecs; b = mark; } else { a = mark; b = zlecs; } INCPOS(b); } else regionlines(&a, &b); zlecs = a; if (b > zlell) b = zlell; n = b - a; while (a < b) { newchars++; INCPOS(a); } region_active = 0; } else { int pos = zlecs; while (n-- > 0) { if (pos == zlell || zleline[pos] == ZWC('\n')) { fail = 1; break; } newchars++; INCPOS(pos); } n = pos - zlecs; } } startvichange(1); /* check argument range */ if (n < 1 || fail) { 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, CUT_RAW); zleline[zlecs++] = '\n'; } else { /* * Make sure we delete displayed characters, including * attach combining characters. n includes this as a raw * buffer offset. * Use shiftchars so as not to adjust the cursor position; * we are overwriting anything that remains directly. * With a selection this will replace newlines which vim * doesn't do but this simplifies things a lot. */ if (n > newchars) shiftchars(zlecs, n - newchars); else if (n < newchars) spaceinline(newchars - n); while (newchars--) zleline[zlecs++] = ch; zlecs--; } vichgflag = 0; return 0; } /**/ int vicmdmode(UNUSED(char **args)) { if (invicmdmode() || selectkeymap("vicmd", 0)) return 1; mergeundo(); vichgflag = 0; if (zlecs != findbol()) DECCS(); 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]); INCCS(); } /* 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; startvichange(1); /* force line range */ if (region_active == 1) region_active = 2; /* get the range */ 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 + 1) { if (zleline[zlecs] == '\n') { /* leave blank lines alone */ ++zlecs; } else { 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; startvichange(1); /* force line range */ if (region_active == 1) region_active = 2; /* get the range */ 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, 0); 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. */ /* HERE: we should do the test properly with DECPOS(). */ if (n > zlecs - findbol()) { n = zlecs - findbol(); /* do the deletion */ backkill(n, CUT_FRONT|CUT_RAW); } else backkill(n, CUT_FRONT); return 0; } /**/ int vikillline(UNUSED(char **args)) { if (viinsbegin > zlecs) return 1; backdel(zlecs - viinsbegin, CUT_RAW); return 0; } /**/ int vijoin(UNUSED(char **args)) { int x, pos; int n = zmult; int visual = region_active; startvichange(-1); if (n < 1) return 1; if (visual && zlecs > mark) { exchangepointandmark(zlenoargs); x = findeol(); if (x >= mark) { exchangepointandmark(zlenoargs); return 1; } } else if ((x = findeol()) == zlell || (visual && x >= mark)) return 1; do { zlecs = x + 1; pos = zlecs; for (; zlecs != zlell && ZC_iblank(zleline[zlecs]); INCPOS(zlecs)) ; x = 1 + (zlecs - pos); backdel(x, CUT_RAW); if (zlecs) { int pos = zlecs; DECPOS(pos); if (ZC_iblank(zleline[pos])) { zlecs = pos; continue; } } spaceinline(1); zleline[zlecs] = ZWC(' '); } while (!((!visual && --n < 2) || (x = findeol()) == zlell || (visual && x >= mark))); return 0; } /**/ int viswapcase(UNUSED(char **args)) { int eol, n = zmult; startvichange(-1); if (n < 1) return 1; eol = findeol(); if (zlecs == eol) return 1; 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]); INCCS(); } if (zlecs && zlecs == eol) DECCS(); return 0; } /**/ int vicapslockpanic(UNUSED(char **args)) { clearlist = 1; zbeep(); statusline = "press a lowercase key to continue"; zrefresh(); while (!ZC_ilower(getfullchar(0))); statusline = NULL; return 0; } /**/ int visetbuffer(char **args) { ZLE_INT_T ch; if (*args) { ch = **args; if (args[1] || (ch && (*args)[1])) return 1; } else { ch = getfullchar(0); } if (ch == ZWC('_')) { zmod.flags |= MOD_NULL; prefixflag = 1; return 0; } else zmod.flags &= ~MOD_NULL; if ((ch < ZWC('0') || 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('0') && ch <= ZWC('9')) zmod.vibuf += - (int)ZWC('0') + 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, CUT_RAW); return 0; } /**/ int vipoundinsert(UNUSED(char **args)) { int oldcs = zlecs; startvichange(-1); vifirstnonblank(zlenoargs); if(zleline[zlecs] != '#') { spaceinline(1); zleline[zlecs] = '#'; if(zlecs <= viinsbegin) INCPOS(viinsbegin); if (zlecs <= oldcs) INCPOS(oldcs); zlecs = oldcs; } else { foredel(1, 0); if (zlecs < viinsbegin) DECPOS(viinsbegin); if (zlecs < oldcs) DECPOS(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, 0); 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); } }