about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Zsh/builtins.yo23
-rw-r--r--Src/builtin.c41
-rw-r--r--Src/utils.c102
-rw-r--r--Test/B03print.ztst13
5 files changed, 177 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index bf10bedc2..090a9be24 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2015-06-02  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* 35353: Doc/Zsh/builtins.yo, Src/builtin.c, Src/utils.c,
+	Test/B03print.ztst: print -x and print -X expand tabs.
+
 2015-06-02  Oliver Kiddle  <opk@zsh.org>
 
 	* 35356: Completion/Unix/Type/_ttys, Completion/Unix/Command/_ps,
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 1fcc7c2b7..6fa603ac8 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -1106,7 +1106,7 @@ tt(popd) that do not change the environment seen by an interactive user.
 )
 findex(print)
 xitem(tt(print )[ tt(-abcDilmnNoOpPrsSz) ] [ tt(-u) var(n) ] [ tt(-f) var(format) ] [ tt(-C) var(cols) ])
-item(SPACES()[ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
+item(SPACES()[ tt(-xX) var(tab-stop) ] [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
 With the `tt(-f)' option the arguments are printed as described by tt(printf).
 With no flags or with the flag `tt(-)', the arguments are printed on
 the standard output as described by tt(echo), with the following differences:
@@ -1201,6 +1201,27 @@ tt(HIST_LEX_WORDS) option active.
 item(tt(-u) var(n))(
 Print the arguments to file descriptor var(n).
 )
+item(tt(-x) var(tab-stop))(
+Expand leading tabs on each line of output in the printed string
+assuming a tab stop every var(tab-stop) characters.  This is appropriate
+for formatting code that may be indented with tabs.  Note that leading
+tabs of any argument to print, not just the first, are expanded, even if
+tt(print) is using spaces to separate arguments (the column count
+is maintained across arguments but may be incorrect on output
+owing to previous unexpanded tabs).
+
+The start of the output of each print command is assumed to be aligned
+with a tab stop.  Widths of multibyte characters are handled if the
+option tt(MULTIBYTE) is in effect.  This option is ignored if other
+formatting options are in effect, namely column alignment or
+tt(printf) style, or if output is to a special location such as shell
+history or the command line editor.
+)
+item(tt(-X) var(tab-stop))(
+This is similar to tt(-x), except that all tabs in the printed string
+are expanded.  This is appropriate if tabs in the arguments are
+being used to produce a table format.
+)
 item(tt(-z))(
 Push the arguments onto the editing buffer stack, separated by spaces.
 )
diff --git a/Src/builtin.c b/Src/builtin.c
index 9358e8b1f..4b081468d 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -99,7 +99,7 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:z-", NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:x:X:z-", NULL),
     BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
     BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
     BUILTIN("pushln", 0, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
@@ -4208,11 +4208,40 @@ bin_print(char *name, char **args, Options ops, int func)
 	    return 0;
 	}
 
-	for (; *args; args++, len++) {
-	    fwrite(*args, *len, 1, fout);
-	    if (args[1])
-		fputc(OPT_ISSET(ops,'l') ? '\n' :
-		      OPT_ISSET(ops,'N') ? '\0' : ' ', fout);
+	if (OPT_HASARG(ops, 'x') || OPT_HASARG(ops, 'X')) {
+	    char *eptr;
+	    int expand, startpos = 0;
+	    int all = OPT_HASARG(ops, 'X');
+	    char *xarg = all ? OPT_ARG(ops, 'X') : OPT_ARG(ops, 'x');
+
+	    expand = (int)zstrtol(xarg, &eptr, 10);
+	    if (*eptr || expand <= 0) {
+		zwarnnam(name, "positive integer expected after -%c: %s", 'x',
+			 xarg);
+		return 1;
+	    }
+	    for (; *args; args++, len++) {
+		startpos = zexpandtabs(*args, *len, expand, startpos, fout,
+				       all);
+		if (args[1]) {
+		    if (OPT_ISSET(ops, 'l')) {
+			fputc('\n', fout);
+			startpos = 0;
+		    } else if (OPT_ISSET(ops,'N')) {
+			fputc('\0', fout);
+		    } else {
+			fputc(' ', fout);
+			startpos++;
+		    }
+		}
+	    }
+	} else {
+	    for (; *args; args++, len++) {
+		fwrite(*args, *len, 1, fout);
+		if (args[1])
+		    fputc(OPT_ISSET(ops,'l') ? '\n' :
+			  OPT_ISSET(ops,'N') ? '\0' : ' ', fout);
+	    }
 	}
 	if (!(OPT_ISSET(ops,'n') || nnl))
 	    fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout);
diff --git a/Src/utils.c b/Src/utils.c
index 271c800fd..7409dc876 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -4964,6 +4964,108 @@ metacharlenconv(const char *x, int *c)
 /**/
 #endif /* MULTIBYTE_SUPPORT */
 
+/*
+ * Expand tabs to given width, with given starting position on line.
+ * len is length of unmetafied string in bytes.
+ * Output to fout.
+ * Return the end position on the line, i.e. if this is 0 modulo width
+ * the next character is aligned with a tab stop.
+ *
+ * If all is set, all tabs are expanded, else only leading tabs.
+ */
+
+/**/
+mod_export int
+zexpandtabs(const char *s, int len, int width, int startpos, FILE *fout,
+	    int all)
+{
+    int at_start = 1;
+
+#ifdef MULTIBYTE_SUPPORT
+    mbstate_t mbs;
+    size_t ret;
+    wchar_t wc;
+
+    memset(&mbs, 0, sizeof(mbs));
+#endif
+
+    while (len) {
+	if (*s == '\t') {
+	    if (all || at_start) {
+		s++;
+		len--;
+		if (width <= 0 || !(startpos % width)) {
+		    /* always output at least one space */
+		    fputc(' ', fout);
+		    startpos++;
+		}
+		if (width <= 0)
+		    continue;	/* paranoia */
+		while (startpos % width) {
+		    fputc(' ', fout);
+		    startpos++;
+		}
+	    } else {
+		/*
+		 * Leave tab alone.
+		 * Guess width to apply... we might get this wrong.
+		 * This is only needed if there's a following string
+		 * that needs tabs expanding, which is unusual.
+		 */
+		startpos += width - startpos % width;
+		s++;
+		len--;
+		fputc('\t', fout);
+	    }
+	    continue;
+	} else if (*s == '\n' || *s == '\r') {
+	    fputc(*s, fout);
+	    s++;
+	    len--;
+	    startpos = 0;
+	    at_start = 1;
+	    continue;
+	}
+
+	at_start = 0;
+#ifdef MULTIBYTE_SUPPORT
+	if (isset(MULTIBYTE)) {
+	    const char *sstart = s;
+	    ret = mbrtowc(&wc, s, len, &mbs);
+	    if (ret == MB_INVALID) {
+		/* Assume single character per character */
+		memset(&mbs, 0, sizeof(mbs));
+		s++;
+		len--;
+	    } else if (ret == MB_INCOMPLETE) {
+		/* incomplete at end --- assume likewise, best we've got */
+		s++;
+		len--;
+	    } else {
+		s += ret;
+		len -= (int)ret;
+	    }
+	    if (ret == MB_INVALID || ret == MB_INCOMPLETE) {
+		startpos++;
+	    } else {
+		int wcw = WCWIDTH(wc);
+		if (wcw > 0)	/* paranoia */
+		    startpos += wcw;
+	    }
+	    fwrite(sstart, s - sstart, 1, fout);
+
+	    continue;
+	}
+#endif /* MULTIBYTE_SUPPORT */
+	fputc(*s, fout);
+	s++;
+	len--;
+	startpos++;
+    }
+
+    return startpos;
+}
+
 /* check for special characters in the string */
 
 /**/
diff --git a/Test/B03print.ztst b/Test/B03print.ztst
index 48574c227..54d6350cf 100644
--- a/Test/B03print.ztst
+++ b/Test/B03print.ztst
@@ -284,3 +284,16 @@
 >610062
 >6100
 >61
+
+ foo=$'one\ttwo\tthree\tfour\n'
+ foo+=$'\tone\ttwo\tthree\tfour\n'
+ foo+=$'\t\tone\t\ttwo\t\tthree\t\tfour'
+ print -x4 $foo
+ print -X4 $foo
+0:Tab expansion by print
+>one	two	three	four
+>    one	two	three	four
+>        one		two		three		four
+>one two three   four
+>    one two three   four
+>        one     two     three       four