/* * zle_move.c - editor movement * * 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_move.pro" static int vimarkcs[27], vimarkline[27]; #ifdef MULTIBYTE_SUPPORT /* * Take account of combining characters when moving left. If * we are on a zero-width printable wide character and are * treating these as part of the base character for display purposes, * move left until we reach a non-zero-width printable character * (the base character). If we reach something else first, stay where we * were. * * If setpos is non-zero, update zlecs on success. * Return 1 if we were on a combining char and could move, else 0. */ /**/ int alignmultiwordleft(int *pos, int setpos) { int loccs = *pos; /* generic nothing to do test */ if (!isset(COMBININGCHARS) || loccs == zlell || loccs == 0) return 0; /* need to be on combining character */ if (!IS_COMBINING(zleline[loccs])) return 0; /* yes, go left */ loccs--; for (;;) { if (IS_BASECHAR(zleline[loccs])) { /* found start position */ if (setpos) *pos = loccs; return 1; } else if (!IS_COMBINING(zleline[loccs])) { /* no go */ return 0; } /* combining char, keep going */ if (loccs-- == 0) return 0; } } /* * Same principle when moving right. We need to check if * alignmultiwordleft() would be successful in order to decide * if we're on a combining character, and if so we move right to * anything that isn't one. */ /**/ int alignmultiwordright(int *pos, int setpos) { int loccs; /* * Are we on a suitable character? */ if (!alignmultiwordleft(pos, 0)) return 0; /* yes, go right */ loccs = *pos + 1; while (loccs < zlell) { /* Anything other than a combining char will do here */ if (!IS_COMBINING(zleline[loccs])) { if (setpos) *pos = loccs; return 1; } loccs++; } if (setpos) *pos = loccs; return 1; } /* Move cursor right, checking for combining characters */ /**/ mod_export void inccs(void) { zlecs++; alignmultiwordright(&zlecs, 1); } /* Move cursor left, checking for combining characters */ /**/ mod_export void deccs(void) { zlecs--; alignmultiwordleft(&zlecs, 1); } /* Same utilities for general position */ /**/ mod_export void incpos(int *pos) { (*pos)++; alignmultiwordright(pos, 1); } /**/ mod_export void decpos(int *pos) { (*pos)--; alignmultiwordleft(pos, 1); } #endif /* Size of buffer in the following function */ #define BMC_BUFSIZE MB_CUR_MAX /* * For a metafied string that starts at "start" and where the * current position is "ptr", go back one full character, * taking account of combining characters if necessary. */ /**/ char * backwardmetafiedchar(char *start, char *endptr, convchar_t *retchr) { #ifdef MULTIBYTE_SUPPORT int charlen = 0; char *last = NULL, *bufptr, *ptr = endptr; convchar_t lastc = (convchar_t)0; /* not used, silence compiler */ mbstate_t mbs; size_t ret; wchar_t wc; VARARR(char, buf, BMC_BUFSIZE); bufptr = buf + BMC_BUFSIZE; while (ptr > start) { ptr--; /* * Scanning backwards we're not guaranteed ever to find a * valid character. If we've looked as far as we should * need to, give up. */ if (bufptr-- == buf) break; charlen++; if (ptr > start && ptr[-1] == Meta) *bufptr = *ptr-- ^ 32; else *bufptr = *ptr; /* we always need to restart the character from scratch */ memset(&mbs, 0, sizeof(mbs)); ret = mbrtowc(&wc, bufptr, charlen, &mbs); if (ret == 0) { /* NULL: unlikely, but handle anyway. */ if (last) { if (retchr) *retchr = lastc; return last; } else { if (retchr) *retchr = wc; return ptr; } } if (ret != (size_t)-1) { if (ret < (size_t)charlen) { /* The last character didn't convert, so use it raw. */ break; } if (!isset(COMBININGCHARS)) { if (retchr) *retchr = wc; return ptr; } if (!IS_COMBINING(wc)) { /* not a combining character... */ if (last) { /* * ... but we were looking for a suitable base character, * test it. */ if (IS_BASECHAR(wc)) { /* * Yes, this will do. */ if (retchr) *retchr = wc; return ptr; } else { /* No, just return the first character we found */ if (retchr) *retchr = lastc; return last; } } /* This is the first character, so just return it. */ if (retchr) *retchr = wc; return ptr; } if (!last) { /* still looking for the character immediately before ptr */ last = ptr; lastc = wc; } /* searching for base character of combining character */ charlen = 0; bufptr = buf + BMC_BUFSIZE; } /* * Else keep scanning this character even if MB_INVALID: we can't * expect MB_INCOMPLETE to work when moving backwards. */ } /* * Found something we didn't like, was there a good character * immediately before ptr? */ if (last) { if (retchr) *retchr = lastc; return last; } /* * No, we couldn't find any good character, so just treat * the last unmetafied byte we found as a character. */ #endif if (endptr > start) { if (endptr > start - 1 && endptr[-2] == Meta) { if (retchr) *retchr = (convchar_t)(endptr[-1] ^ 32); return endptr - 2; } else { if (retchr) *retchr = (convchar_t)endptr[-1]; return endptr - 1; } } if (retchr) *retchr = (convchar_t)0; return endptr; } /**/ int beginningofline(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = endofline(args); zmult = n; return ret; } while (n--) { int pos; if (zlecs == 0) return 0; pos = zlecs; DECPOS(pos); if (zleline[pos] == '\n') { zlecs = pos; if (!zlecs) return 0; } /* works OK with combining chars since '\n' must be on its own */ while (zlecs && zleline[zlecs - 1] != '\n') zlecs--; } return 0; } /**/ int endofline(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = beginningofline(args); zmult = n; return ret; } while (n--) { if (zlecs >= zlell) { zlecs = zlell; return 0; } if ((zlecs += invicmdmode()) == zlell) break; if (zleline[zlecs] == '\n') if (++zlecs == zlell) return 0; while (zlecs != zlell && zleline[zlecs] != '\n') zlecs++; } return 0; } /**/ int beginningoflinehist(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = endoflinehist(args); zmult = n; return ret; } while (n) { int pos; if (zlecs == 0) break; pos = zlecs; DECPOS(pos); if (zleline[pos] == '\n') { zlecs = pos; if (!pos) break; } /* works OK with combining chars since '\n' must be on its own */ while (zlecs && zleline[zlecs - 1] != '\n') zlecs--; n--; } if (n) { int m = zmult, ret; zmult = n; ret = uphistory(args); zmult = m; zlecs = 0; return ret; } return 0; } /**/ int endoflinehist(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = beginningoflinehist(args); zmult = n; return ret; } while (n) { if (zlecs >= zlell) { zlecs = zlell; break; } if ((zlecs += invicmdmode()) == zlell) break; if (zleline[zlecs] == '\n') if (++zlecs == zlell) break; while (zlecs != zlell && zleline[zlecs] != '\n') zlecs++; n--; } if (n) { int m = zmult, ret; zmult = n; ret = downhistory(args); zmult = m; return ret; } return 0; } /**/ int forwardchar(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = backwardchar(args); zmult = n; return ret; } /* * If handling combining characters with the base character, * we skip over the whole set in one go, so need to check. */ while (zlecs < zlell && n--) INCCS(); return 0; } /**/ int backwardchar(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = forwardchar(args); zmult = n; return ret; } while (zlecs > 0 && n--) DECCS(); return 0; } /**/ int setmarkcommand(UNUSED(char **args)) { if (zmult < 0) { region_active = 0; return 0; } mark = zlecs; region_active = 1; return 0; } /**/ int exchangepointandmark(UNUSED(char **args)) { int x; if (zmult == 0) { region_active = 1; return 0; } x = mark; mark = zlecs; zlecs = x; if (zlecs > zlell) zlecs = zlell; if (zmult > 0) region_active = 1; return 0; } /**/ int visualmode(UNUSED(char **args)) { if (virangeflag) { prefixflag = 1; zmod.flags &= ~MOD_LINE; zmod.flags |= MOD_CHAR; return 0; } switch (region_active) { case 1: region_active = 0; break; case 0: mark = zlecs; /* fall through */ case 2: region_active = 1; break; } return 0; } /**/ int visuallinemode(UNUSED(char **args)) { if (virangeflag) { prefixflag = 1; zmod.flags &= ~MOD_CHAR; zmod.flags |= MOD_LINE; return 0; } switch (region_active) { case 2: region_active = 0; break; case 0: mark = zlecs; /* fall through */ case 1: region_active = 2; break; } return 0; } /**/ int deactivateregion(UNUSED(char **args)) { region_active = 0; return 0; } /**/ int vigotocolumn(UNUSED(char **args)) { int x, y, n = zmult; findline(&x, &y); if (n >= 0) { if (n) n--; zlecs = x; while (zlecs < y && n--) INCCS(); } else { zlecs = y; n = -n; while (zlecs > x && n--) DECCS(); } return 0; } /**/ int matchbracket(char **args) { int ocs = zlecs, dir, ct; unsigned char oth, me; if (*args) { char *end = NULL; zlecs = zstrtol(*args, &end, 10); if (end && end != *args && *end == '\0') args++; else zlecs = ocs; } if (zlecs == zlell || zleline[zlecs] == '\n') { zlecs = ocs; return 1; } switch (me = zleline[zlecs]) { case '{': dir = 1; oth = '}'; break; case /*{*/ '}': dir = -1; oth = '{'; /*}*/ break; case '(': dir = 1; oth = ')'; break; case ')': dir = -1; oth = '('; break; case '[': dir = 1; oth = ']'; break; case ']': dir = -1; oth = '['; break; default: zlecs = ocs; return 1; } ct = 1; while (zlecs >= 0 && zlecs < zlell && ct) { if (dir < 0) DECCS(); else INCCS(); if (zleline[zlecs] == oth) ct--; else if (zleline[zlecs] == me) ct++; } if (zlecs < 0 || zlecs >= zlell) { zlecs = ocs; return 1; } if (*args) { char digs[100]; sprintf(digs, "%d", zlecs); zlecs = ocs; setsparam(*args, ztrdup(digs)); } return 0; } /**/ int vimatchbracket(UNUSED(char **args)) { int ocs = zlecs, dir, ct; unsigned char oth, me; if ((zlecs == zlell || zleline[zlecs] == '\n') && zlecs > 0) DECCS(); if (virangeflag) mark = zlecs; otog: if (zlecs == zlell || zleline[zlecs] == '\n') { zlecs = ocs; return 1; } switch (me = zleline[zlecs]) { case '{': dir = 1; oth = '}'; break; case /*{*/ '}': dir = -1; oth = '{'; /*}*/ break; case '(': dir = 1; oth = ')'; break; case ')': dir = -1; oth = '('; break; case '[': dir = 1; oth = ']'; break; case ']': dir = -1; oth = '['; break; default: INCCS(); goto otog; } if (virangeflag && dir < 0) INCPOS(mark); /* include starting position when going backwards */ ct = 1; while (zlecs >= 0 && zlecs < zlell && ct) { if (dir < 0) DECCS(); else INCCS(); if (zleline[zlecs] == oth) ct--; else if (zleline[zlecs] == me) ct++; } if (zlecs < 0 || zlecs >= zlell) { zlecs = ocs; return 1; } else if(dir > 0 && virangeflag) INCCS(); return 0; } /**/ int viforwardchar(char **args) { int lim = findeol(); int n = zmult; if (n < 0) { int ret; zmult = -n; ret = vibackwardchar(args); zmult = n; return ret; } if (invicmdmode() && !virangeflag) DECPOS(lim); if (zlecs >= lim) return 1; while (n-- && zlecs < lim) INCCS(); return 0; } /**/ int vibackwardchar(char **args) { int n = zmult; if (n < 0) { int ret; zmult = -n; ret = viforwardchar(args); zmult = n; return ret; } if (zlecs == findbol()) return 1; while (n-- && zlecs > 0) { DECCS(); if (zleline[zlecs] == '\n') { zlecs++; break; } } return 0; } /**/ int viendofline(UNUSED(char **args)) { int oldcs = zlecs, n = zmult; if (n < 1) return 1; while(n--) { if (zlecs > zlell) { zlecs = oldcs; return 1; } zlecs = findeol() + 1; } DECCS(); lastcol = 1<<30; return 0; } /**/ int vibeginningofline(UNUSED(char **args)) { zlecs = findbol(); return 0; } static ZLE_INT_T vfindchar; static int vfinddir, tailadd; /**/ int vifindnextchar(char **args) { if ((vfindchar = vigetkey()) != ZLEEOF) { vfinddir = 1; tailadd = 0; return vifindchar(0, args); } return 1; } /**/ int vifindprevchar(char **args) { if ((vfindchar = vigetkey()) != ZLEEOF) { vfinddir = -1; tailadd = 0; return vifindchar(0, args); } return 1; } /**/ int vifindnextcharskip(char **args) { if ((vfindchar = vigetkey()) != ZLEEOF) { vfinddir = 1; tailadd = -1; return vifindchar(0, args); } return 1; } /**/ int vifindprevcharskip(char **args) { if ((vfindchar = vigetkey()) != ZLEEOF) { vfinddir = -1; tailadd = 1; return vifindchar(0, args); } return 1; } /**/ int vifindchar(int repeat, char **args) { int ocs = zlecs, n = zmult; if (!vfinddir) return 1; if (n < 0) { int ret; zmult = -n; ret = virevrepeatfind(args); zmult = n; return ret; } if (repeat && tailadd != 0) { if (vfinddir > 0) { if(zlecs < zlell && (ZLE_INT_T)zleline[zlecs+1] == vfindchar) INCCS(); } else { if(zlecs > 0 && (ZLE_INT_T)zleline[zlecs-1] == vfindchar) DECCS(); } } while (n--) { do { if (vfinddir > 0) INCCS(); else DECCS(); } while (zlecs >= 0 && zlecs < zlell && (ZLE_INT_T)zleline[zlecs] != vfindchar && zleline[zlecs] != ZWC('\n')); if (zlecs < 0 || zlecs >= zlell || zleline[zlecs] == ZWC('\n')) { zlecs = ocs; return 1; } } if (tailadd > 0) INCCS(); else if (tailadd < 0) DECCS(); if (vfinddir == 1 && virangeflag) INCCS(); return 0; } /**/ int virepeatfind(char **args) { return vifindchar(1, args); } /**/ int virevrepeatfind(char **args) { int ret; if (zmult < 0) { zmult = -zmult; ret = vifindchar(1, args); zmult = -zmult; return ret; } tailadd = -tailadd; vfinddir = -vfinddir; ret = vifindchar(1, args); vfinddir = -vfinddir; tailadd = -tailadd; return ret; } /**/ int vifirstnonblank(UNUSED(char **args)) { zlecs = findbol(); while (zlecs != zlell && ZC_iblank(zleline[zlecs])) INCCS(); return 0; } /**/ int visetmark(UNUSED(char **args)) { ZLE_INT_T ch; ch = getfullchar(0); if (ch < ZWC('a') || ch > ZWC('z')) return 1; ch -= ZWC('a'); vimarkcs[ch] = zlecs; vimarkline[ch] = histline; return 0; } /**/ int vigotomark(UNUSED(char **args)) { ZLE_INT_T ch; int *markcs, *markhist = 0; int oldcs = zlecs; int oldline = histline; int tmpcs, tmphist; ch = getfullchar(0); if (ch == ZWC('\'') || ch == ZWC('`')) { markhist = vimarkline + 26; markcs = vimarkcs + 26; } else if (ch == ZWC('.') && curchange->prev) { /* position cursor where it was after the last change. not exactly * what vim does but close enough */ tmpcs = curchange->prev->new_cs; tmphist = curchange->prev->hist; markcs = &tmpcs; markhist = &tmphist; } else if (ch >= ZWC('a') && ch <= ZWC('z')) { markhist = vimarkline + (ch - ZWC('a')); markcs = vimarkcs + (ch - ZWC('a')); } else return 1; if (markhist) { if (!*markhist) return 1; if (histline != *markhist && !zle_goto_hist(*markhist, 0, 0)) { *markhist = 0; return 1; } } zlecs = *markcs; vimarkcs[26] = oldcs; vimarkline[26] = oldline; if (zlecs > zlell) zlecs = zlell; return 0; } /**/ int vigotomarkline(char **args) { vigotomark(args); return vifirstnonblank(zlenoargs); }