about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@zsh.org>2015-06-05 11:21:22 +0100
committerPeter Stephenson <pws@zsh.org>2015-06-05 11:21:22 +0100
commit2abba7243a736a2fc626f3cc917d8a67014d4d20 (patch)
treec91850e5786a9a8e0c9ebbedc65abbc3a3131cd0
parent4804a7c5ff144fc7cc974484d16f2f88cc131264 (diff)
downloadzsh-2abba7243a736a2fc626f3cc917d8a67014d4d20.tar.gz
zsh-2abba7243a736a2fc626f3cc917d8a67014d4d20.tar.xz
zsh-2abba7243a736a2fc626f3cc917d8a67014d4d20.zip
35386: expand tabs where useful in builtins outputing function.
Also add to zed -f.

Option is -x <numm>.
-rw-r--r--ChangeLog8
-rw-r--r--Doc/Zsh/builtins.yo25
-rw-r--r--Doc/Zsh/contrib.yo9
-rw-r--r--Functions/Misc/zed17
-rw-r--r--Src/builtin.c50
-rw-r--r--Src/hashtable.c74
-rw-r--r--Src/pattern.c13
-rw-r--r--Src/text.c46
-rw-r--r--Src/utils.c43
-rw-r--r--Src/zsh.h9
10 files changed, 243 insertions, 51 deletions
diff --git a/ChangeLog b/ChangeLog
index 24ec700f9..b6c233d8c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2015-06-05  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* 35386: Doc/Zsh/builtins.yo, Doc/Zsh/contrib.yo,
+	Functions/Misc/zed, Src/builtin.c, Src/hashtable.c,
+	Src/pattern.c, Src/text.c, Src/utils.c, Src/zsh.h:
+	expand tabs for function output in functions, whence, where,
+	which and also zed -f using -x num option.
+
 2015-06-03  Oliver Kiddle  <opk@zsh.org>
 
 	* 35360 (replacing 35357): configure.ac, Src/Modules/zpty.c:
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 6fa603ac8..53b668214 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -791,11 +791,18 @@ Equivalent to tt(typeset -E), except that options irrelevant to floating
 point numbers are not permitted.
 )
 findex(functions)
-xitem(tt(functions) [ {tt(PLUS())|tt(-)}tt(UkmtTuz) ] [ var(name) ... ])
+xitem(tt(functions) [ {tt(PLUS())|tt(-)}tt(UkmtTuz) ] [ tt(-x) var(num) ] [ var(name) ... ])
 xitem(tt(functions -M) var(mathfn) [ var(min) [ var(max) [ var(shellfn) ] ] ])
 xitem(tt(functions -M) [ tt(-m) var(pattern) ... ])
 item(tt(functions +M) [ tt(-m) ] var(mathfn) ... )(
-Equivalent to tt(typeset -f), with the exception of the tt(-M) option.
+Equivalent to tt(typeset -f), with the exception of the tt(-x) and
+tt(-M) options.
+
+The tt(-x) option indicates that any functions output will have
+each leading tab for indentation, added by the shell to show syntactic
+structure, expanded to the given number var(num) of spaces.  var(num)
+can also be 0 to suppress all indentation.
+
 Use of the tt(-M) option may not be combined with any of the options
 handled by tt(typeset -f).
 
@@ -1927,6 +1934,9 @@ function is first referenced; see noderef(Functions). The tt(-k) and
 tt(-z) flags make the function be loaded using ksh-style or zsh-style
 autoloading respectively. If neither is given, the setting of the
 tt(KSH_AUTOLOAD) option determines how the function is loaded.
+
+Note that the builtin tt(functions) provides the same basic capabilities
+as tt(typeset -f) but gives access to a few extra options.
 )
 item(tt(-h))(
 Hide: only useful for special parameters (those marked `<S>' in the table in
@@ -2180,7 +2190,7 @@ the user is potentially interested in both, so this problem is intrinsic
 to process IDs.
 )
 findex(whence)
-item(tt(whence) [ tt(-vcwfpamsS) ] var(name) ...)(
+item(tt(whence) [ tt(-vcwfpamsS) ] [ tt(-x) var(num) ] var(name) ...)(
 For each var(name), indicate how it would be interpreted if used as a
 command name.
 
@@ -2233,14 +2243,19 @@ As tt(-s), but if the pathname had to be resolved by following
 multiple symlinks, the intermediate steps are printed, too.  The
 symlink resolved at each step might be anywhere in the path.
 )
+item(tt(-x) var(num))(
+Expand tabs when outputting shell functions using the tt(-c) option.
+This has the same effect as the tt(-x) option to the tt(functions)
+builtin.
+)
 enditem()
 )
 findex(where)
-item(tt(where) [ tt(-wpmsS) ] var(name) ...)(
+item(tt(where) [ tt(-wpmsS) ] [ tt(-x) var(num) ] var(name) ...)(
 Equivalent to tt(whence -ca).
 )
 findex(which)
-item(tt(which) [ tt(-wpamsS) ] var(name) ...)(
+item(tt(which) [ tt(-wpamsS) ] [ tt(-x) var(num) ] var(name) ...)(
 Equivalent to tt(whence -c).
 )
 findex(zcompile)
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 8b6b7d3b7..323bf0f9a 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -3561,7 +3561,7 @@ set to the ANSI terminal escapes that turn off all attributes and turn on
 bold intensity, respectively.
 )
 findex(fned)
-item(tt(fned) var(name))(
+item(tt(fned) [ tt(-x) var(num) ] var(name))(
 Same as tt(zed -f).  This function does not appear in the zsh
 distribution, but can be created by linking tt(zed) to the name tt(fned)
 in some directory in your tt(fpath).
@@ -3749,7 +3749,7 @@ the difference in function between tt(zargs) and tt(xargs)) or run
 tt(zargs) with the tt(-)tt(-help) option.
 )
 findex(zed)
-xitem(tt(zed) [ tt(-f) ] var(name))
+xitem(tt(zed) [ tt(-f) [ tt(-x) var(num) ] var(name))
 item(tt(zed -b))(
 This function uses the ZLE editor to edit a file or function.
 
@@ -3758,7 +3758,10 @@ If the tt(-f) option is given, the name is taken to be that of
 a function; if the function is marked for autoloading, tt(zed) searches
 for it in the tt(fpath) and loads it.  Note that functions edited this way
 are installed into the current shell, but em(not) written back to the
-autoload file.
+autoload file.  In this case the tt(-x) option specifies that leading
+tabs indenting the function according to syntax should be converted into
+the given number of spaces; `tt(-x 2)' is consistent with the layout
+of functions distributed with the shell.
 
 Without tt(-f), var(name) is the path name of the file to edit, which need
 not exist; it is created on write, if necessary.
diff --git a/Functions/Misc/zed b/Functions/Misc/zed
index c2caaf3f5..010b69bee 100644
--- a/Functions/Misc/zed
+++ b/Functions/Misc/zed
@@ -9,8 +9,9 @@
 local var opt zed_file_name
 # We do not want timeout while we are editing a file
 integer TMOUT=0 okargs=1 fun bind
+local -a expand
 
-while getopts "fb" opt; do
+while getopts "fbx:" opt; do
   case $opt in
     (f)
     fun=1
@@ -19,6 +20,14 @@ while getopts "fb" opt; do
     (b)
     bind=1
     ;;
+
+    (x)
+    if [[ $OPTARG != <-> ]]; then
+      print -r "Integer expected after -x: $OPTARG" >&2
+      return 1
+    fi
+    expand=(-x $OPTARG)
+    ;;
   esac
 done
 shift $(( OPTIND - 1 ))
@@ -29,8 +38,8 @@ shift $(( OPTIND - 1 ))
 if (( $# != okargs )); then
     echo 'Usage:
 zed filename
-zed -f function
-zed -b'
+zed -f [ -x N ] function
+zed -b' >&2
     return 1
 fi
 
@@ -71,7 +80,7 @@ fi
 setopt localoptions nobanghist
 
 if ((fun)) then
-  var="$(functions $1)"
+  var="$(functions $expand $1)"
   # If function is undefined but autoloadable, load it
   if [[ $var = *\#\ undefined* ]] then
     var="$(autoload +X $1; functions $1)"
diff --git a/Src/builtin.c b/Src/builtin.c
index 4b081468d..0d1d00ec3 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -72,7 +72,7 @@ static struct builtin builtins[] =
     BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
     BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
     BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
-    BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUz", NULL),
+    BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUx:z", NULL),
     BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
     BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
     BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "Ldfmrv", NULL),
@@ -128,9 +128,9 @@ static struct builtin builtins[] =
     BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fmv", NULL),
     BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL),
     BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL),
-    BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsSw", NULL),
-    BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsSw", "ca"),
-    BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsSw", "c"),
+    BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsSwx:", NULL),
+    BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsSwx:", "ca"),
+    BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsSwx:", "c"),
     BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilmpue", NULL),
     BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL),
 };
@@ -2749,7 +2749,7 @@ bin_functions(char *name, char **argv, Options ops, int func)
     Patprog pprog;
     Shfunc shf;
     int i, returnval = 0;
-    int on = 0, off = 0, pflags = 0, roff;
+    int on = 0, off = 0, pflags = 0, roff, expand = 0;
 
     /* Do we have any flags defined? */
     if (OPT_PLUS(ops,'u'))
@@ -2785,11 +2785,23 @@ bin_functions(char *name, char **argv, Options ops, int func)
     }
 
     if ((off & PM_UNDEFINED) || (OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
+	(OPT_ISSET(ops,'x') && !OPT_HASARG(ops,'x')) ||
 	(OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || *argv || !scriptname))) {
 	zwarnnam(name, "invalid option(s)");
 	return 1;
     }
 
+    if (OPT_ISSET(ops,'x')) {
+	char *eptr;
+	expand = (int)zstrtol(OPT_ARG(ops,'x'), &eptr, 10);
+	if (*eptr) {
+	    zwarnnam(name, "number expected after -x");
+	    return 1;
+	}
+	if (expand == 0)	/* no indentation at all */
+	    expand = -1;
+    }
+
     if (OPT_PLUS(ops,'f') || roff || OPT_ISSET(ops,'+'))
 	pflags |= PRINT_NAMEONLY;
 
@@ -2948,8 +2960,8 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	} else {
 	    if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u'))
 		on &= ~PM_UNDEFINED;
-	    scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode,
-			  pflags);
+	    scanshfunc(1, on|off, DISABLED, shfunctab->printnode,
+		       pflags, expand);
 	}
 	unqueue_signals();
 	return ret;
@@ -2965,8 +2977,8 @@ bin_functions(char *name, char **argv, Options ops, int func)
 		/* with no options, just print all functions matching the glob pattern */
 		queue_signals();
 		if (!(on|off) && !OPT_ISSET(ops,'X')) {
-		    scanmatchtable(shfunctab, pprog, 1, 0, DISABLED,
-				   shfunctab->printnode, pflags);
+		    scanmatchshfunc(pprog, 1, 0, DISABLED,
+				   shfunctab->printnode, pflags, expand);
 		} else {
 		    /* apply the options to all functions matching the glob pattern */
 		    for (i = 0; i < shfunctab->hsize; i++) {
@@ -3008,7 +3020,7 @@ bin_functions(char *name, char **argv, Options ops, int func)
 		    returnval = 1;
 	    } else
 		/* no flags, so just print */
-		shfunctab->printnode(&shf->node, pflags);
+		printshfuncexpand(&shf->node, pflags, expand);
 	} else if (on & PM_UNDEFINED) {
 	    int signum = -1, ok = 1;
 
@@ -3222,6 +3234,7 @@ bin_whence(char *nam, char **argv, Options ops, int func)
     int aliasflags;
     int csh, all, v, wd;
     int informed = 0;
+    int expand = 0;
     char *cnam, **allmatched = 0;
 
     /* Check some option information */
@@ -3230,6 +3243,17 @@ bin_whence(char *nam, char **argv, Options ops, int func)
     all = OPT_ISSET(ops,'a');
     wd  = OPT_ISSET(ops,'w');
 
+    if (OPT_ISSET(ops,'x')) {
+	char *eptr;
+	expand = (int)zstrtol(OPT_ARG(ops,'x'), &eptr, 10);
+	if (*eptr) {
+	    zwarnnam(nam, "number expected after -x");
+	    return 1;
+	}
+	if (expand == 0)	/* no indentation at all */
+	    expand = -1;
+    }
+
     if (OPT_ISSET(ops,'w'))
 	printflags |= PRINT_WHENCE_WORD;
     else if (OPT_ISSET(ops,'c'))
@@ -3286,8 +3310,8 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 
 		/* and shell functions... */
 		informed +=
-		scanmatchtable(shfunctab, pprog, 1, 0, DISABLED,
-			       shfunctab->printnode, printflags);
+		scanmatchshfunc(pprog, 1, 0, DISABLED,
+			       shfunctab->printnode, printflags, expand);
 
 		/* and builtins. */
 		informed +=
@@ -3342,7 +3366,7 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 	    }
 	    /* Look for shell function */
 	    if ((hn = shfunctab->getnode(shfunctab, *argv))) {
-		shfunctab->printnode(hn, printflags);
+		printshfuncexpand(hn, printflags, expand);
 		informed = 1;
 		if (!all)
 		    continue;
diff --git a/Src/hashtable.c b/Src/hashtable.c
index ab381cc6a..2b5524999 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -937,13 +937,17 @@ printshfuncnode(HashNode hn, int printflags)
  
     quotedzputs(f->node.nam, stdout);
     if (f->funcdef || f->node.flags & PM_UNDEFINED) {
-	printf(" () {\n\t");
-	if (f->node.flags & PM_UNDEFINED)
-	    printf("%c undefined\n\t", hashchar);
-	else
+	printf(" () {\n");
+	zoutputtab(stdout);
+	if (f->node.flags & PM_UNDEFINED) {
+	    printf("%c undefined\n", hashchar);
+	    zoutputtab(stdout);
+	} else
 	    t = getpermtext(f->funcdef, NULL, 1);
-	if (f->node.flags & (PM_TAGGED|PM_TAGGED_LOCAL))
-	    printf("%c traced\n\t", hashchar);
+	if (f->node.flags & (PM_TAGGED|PM_TAGGED_LOCAL)) {
+	    printf("%c traced\n", hashchar);
+	    zoutputtab(stdout);
+	}
 	if (!t) {
 	    char *fopt = "UtTkz";
 	    int flgs[] = {
@@ -959,11 +963,12 @@ printshfuncnode(HashNode hn, int printflags)
 	    zputs(t, stdout);
 	    zsfree(t);
 	    if (f->funcdef->flags & EF_RUN) {
-		printf("\n\t");
+		printf("\n");
+		zoutputtab(stdout);
 		quotedzputs(f->node.nam, stdout);
 		printf(" \"$@\"");
 	    }
-	}   
+	}
 	printf("\n}");
     } else {
 	printf(" () { }");
@@ -979,6 +984,59 @@ printshfuncnode(HashNode hn, int printflags)
     putchar('\n');
 }
 
+/*
+ * Wrap scanmatchtable for shell functions with optional
+ * expansion of leading tabs.
+ * expand = 0 is standard: use hard tabs.
+ * expand > 0 uses that many spaces.
+ * expand < 0 uses no identation.
+ *
+ * Note this function and the following two are called with
+ * interrupts queued, so saving and restoring text_expand_tabs
+ * is safe.
+ */
+
+/**/
+mod_export int
+scanmatchshfunc(Patprog pprog, int sorted, int flags1, int flags2,
+		ScanFunc scanfunc, int scanflags, int expand)
+{
+    int ret, save_expand;
+
+    save_expand = text_expand_tabs;
+    text_expand_tabs = expand;
+    ret = scanmatchtable(shfunctab, pprog, sorted, flags1, flags2,
+			scanfunc, scanflags);
+    text_expand_tabs = save_expand;
+
+    return ret;
+}
+
+/* Wrap scanhashtable to expand tabs for shell functions */
+
+/**/
+mod_export int
+scanshfunc(int sorted, int flags1, int flags2,
+	      ScanFunc scanfunc, int scanflags, int expand)
+{
+    return scanmatchshfunc(NULL, sorted, flags1, flags2,
+			   scanfunc, scanflags, expand);
+}
+
+/* Wrap shfunctab->printnode to expand tabs */
+
+/**/
+mod_export void
+printshfuncexpand(HashNode hn, int printflags, int expand)
+{
+    int save_expand;
+
+    save_expand = text_expand_tabs;
+    text_expand_tabs = expand;
+    shfunctab->printnode(hn, printflags);
+    text_expand_tabs = save_expand;
+}
+
 /**************************************/
 /* Reserved Word Hash Table Functions */
 /**************************************/
diff --git a/Src/pattern.c b/Src/pattern.c
index 4e5e8a110..7e07548f9 100644
--- a/Src/pattern.c
+++ b/Src/pattern.c
@@ -2202,20 +2202,15 @@ pattryrefs(Patprog prog, char *string, int stringlen, int unmetalen,
 		if ((patglobflags & GF_MATCHREF) &&
 		    !(patflags & PAT_FILE)) {
 		    char *str = ztrduppfx(patinstart, patinlen);
-		    char *ptr = patinstart;
-		    int mlen = 0;
+		    int mlen;
 
 		    /*
 		     * Count the characters.  We're not using CHARSUB()
-		     * because the string is still metafied.  We're
-		     * not using mb_metastrlen() because that expects
-		     * the string to be null terminated.
+		     * because the string is still metafied.
 		     */
 		    MB_METACHARINIT();
-		    while (ptr < patinstart + patinlen) {
-			mlen++;
-			ptr += MB_METACHARLEN(ptr);
-		    }
+		    mlen = MB_METASTRLEN2END(patinstart, 0,
+					     patinstart + patinlen);
 
 		    setsparam("MATCH", str);
 		    setiparam("MBEGIN",
diff --git a/Src/text.c b/Src/text.c
index 958303c68..850879699 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -30,6 +30,16 @@
 #include "zsh.mdh"
 #include "text.pro"
 
+/*
+ * If non-zero, expand syntactically significant leading tabs in text
+ * to this number of spaces.
+ *
+ * If negative, don't output leading whitespace at all.
+ */
+
+/**/
+int text_expand_tabs;
+
 static char *tptr, *tbuf, *tlim, *tpending;
 static int tsiz, tindent, tnewlins, tjob;
 
@@ -156,8 +166,16 @@ taddnl(int no_semicolon)
     if (tnewlins) {
 	tdopending();
 	taddchr('\n');
-	for (t0 = 0; t0 != tindent; t0++)
-	    taddchr('\t');
+	for (t0 = 0; t0 != tindent; t0++) {
+	    if (text_expand_tabs >= 0) {
+		if (text_expand_tabs) {
+		    int t1;
+		    for (t1 = 0; t1 < text_expand_tabs; t1++)
+			taddchr(' ');
+		} else
+		    taddchr('\t');
+	    }
+	}
     } else if (no_semicolon) {
 	taddstr(" ");
     } else {
@@ -165,6 +183,30 @@ taddnl(int no_semicolon)
     }
 }
 
+/*
+ * Output a tab that may be expanded as part of a leading set.
+ * Note this is not part of the text framework; it's for
+ * code that needs to output its own tabs that are to be
+ * consistent with those from getpermtext().
+ *
+ * Note these tabs are only expected to be useful at the
+ * start of the line, so we make no attempt to count columns.
+ */
+
+/**/
+void
+zoutputtab(FILE *outf)
+{
+    if (text_expand_tabs < 0)
+	return;
+    if (text_expand_tabs) {
+	int i;
+	for (i = 0; i < text_expand_tabs; i++)
+	    fputc(' ', outf);
+    } else
+	fputc('\t', outf);
+}
+
 /* get a permanent textual representation of n */
 
 /**/
diff --git a/Src/utils.c b/Src/utils.c
index 7409dc876..c33c16d5a 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -4471,9 +4471,37 @@ ztrlen(char const *s)
     for (l = 0; *s; l++) {
 	if (*s++ == Meta) {
 #ifdef DEBUG
-	    if (! *s)
+	    if (! *s) {
 		fprintf(stderr, "BUG: unexpected end of string in ztrlen()\n");
-	    else
+		break;
+	    } else
+#endif
+	    s++;
+	}
+    }
+    return l;
+}
+
+#ifndef MULTIBYTE_SUPPORT
+/*
+ * ztrlen() but with explicit end point for non-null-terminated
+ * segments.  eptr may not be NULL.
+ */
+
+/**/
+mod_export int
+ztrlenend(char const *s, char const *eptr)
+{
+    int l;
+
+    for (l = 0; s < eptr; l++) {
+	if (*s++ == Meta) {
+#ifdef DEBUG
+	    if (! *s) {
+		fprintf(stderr,
+			"BUG: unexpected end of string in ztrlenend()\n");
+		break;
+	    } else
 #endif
 	    s++;
 	}
@@ -4481,6 +4509,8 @@ ztrlen(char const *s)
     return l;
 }
 
+#endif /* MULTIBYTE_SUPPORT */
+
 /* Subtract two pointers in a metafied string. */
 
 /**/
@@ -4879,11 +4909,16 @@ mb_metacharlenconv(const char *s, wint_t *wcp)
  * If width is 1, return total character width rather than number.
  * If width is greater than 1, return 1 if character has non-zero width,
  * else 0.
+ *
+ * Ends if either *ptr is '\0', the normal case (eptr may be NULL for
+ * this), or ptr is eptr (i.e.  *eptr is where the null would be if null
+ * terminated) for strings not delimited by nulls --- note these are
+ * still metafied.
  */
 
 /**/
 mod_export int
-mb_metastrlen(char *ptr, int width)
+mb_metastrlenend(char *ptr, int width, char *eptr)
 {
     char inchar, *laststart;
     size_t ret;
@@ -4898,7 +4933,7 @@ mb_metastrlen(char *ptr, int width)
     num = num_in_char = 0;
 
     memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
-    while (*ptr) {
+    while (*ptr && !(eptr && ptr >= eptr)) {
 	if (*ptr == Meta)
 	    inchar = *++ptr ^ 32;
 	else
diff --git a/Src/zsh.h b/Src/zsh.h
index f6e08e28d..c88c2e739 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2926,9 +2926,11 @@ enum {
 typedef wint_t convchar_t;
 #define MB_METACHARLENCONV(str, cp)	mb_metacharlenconv((str), (cp))
 #define MB_METACHARLEN(str)	mb_metacharlenconv(str, NULL)
-#define MB_METASTRLEN(str)	mb_metastrlen(str, 0)
-#define MB_METASTRWIDTH(str)	mb_metastrlen(str, 1)
-#define MB_METASTRLEN2(str, widthp)	mb_metastrlen(str, widthp)
+#define MB_METASTRLEN(str)	mb_metastrlenend(str, 0, NULL)
+#define MB_METASTRWIDTH(str)	mb_metastrlenend(str, 1, NULL)
+#define MB_METASTRLEN2(str, widthp)	mb_metastrlenend(str, widthp, NULL)
+#define MB_METASTRLEN2END(str, widthp, eptr)	\
+    mb_metastrlenend(str, widthp, eptr)
 
 /*
  * We replace broken implementations with one that uses Unicode
@@ -3011,6 +3013,7 @@ typedef int convchar_t;
 #define MB_METASTRLEN(str)	ztrlen(str)
 #define MB_METASTRWIDTH(str)	ztrlen(str)
 #define MB_METASTRLEN2(str, widthp)	ztrlen(str)
+#define MB_METASTRLEN2END(str, widthp, eptr)	ztrlenend(str, eptr)
 
 #define WCWIDTH_WINT(c)	(1)