From 93e72ed1e9ef573e3bb9cd95d67233df46d33973 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Tue, 2 Jun 2015 09:17:04 +0100 Subject: 35353: print -x and print -X expand tabs --- ChangeLog | 5 +++ Doc/Zsh/builtins.yo | 23 +++++++++++- Src/builtin.c | 41 +++++++++++++++++---- Src/utils.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Test/B03print.ztst | 13 +++++++ 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 + + * 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 * 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 -- cgit 1.4.1