diff --git a/lib/argp-fmtstream.c b/lib/argp-fmtstream.c index 70e3eb8..2db995c 100644 --- a/lib/argp-fmtstream.c +++ b/lib/argp-fmtstream.c @@ -29,9 +29,11 @@ #include #include #include +#include #include "argp-fmtstream.h" #include "argp-namefrob.h" +#include "mbswidth.h" #ifndef ARGP_FMTSTREAM_USE_LINEWRAP @@ -116,6 +118,51 @@ weak_alias (__argp_fmtstream_free, argp_fmtstream_free) #endif #endif + +/* Return the pointer to the first character that doesn't fit in l columns. */ +static inline const ptrdiff_t +add_width (const char *ptr, const char *end, size_t l) +{ + mbstate_t ps; + const char *ptr0 = ptr; + + memset (&ps, 0, sizeof (ps)); + + while (ptr < end) + { + wchar_t wc; + size_t s, k; + + s = mbrtowc (&wc, ptr, end - ptr, &ps); + if (s == (size_t) -1) + break; + if (s == (size_t) -2) + { + if (1 >= l) + break; + l--; + ptr++; + continue; + } + + if (wc == '\e' && ptr + 3 < end + && ptr[1] == '[' && (ptr[2] == '0' || ptr[2] == '1') + && ptr[3] == 'm') + { + ptr += 4; + continue; + } + + k = wcwidth (wc); + + if (k >= l) + break; + l -= k; + ptr += s; + } + return ptr - ptr0; +} + /* Process FS's buffer so that line wrapping is done from POINT_OFFS to the end of its buffer. This code is mostly from glibc stdio/linewrap.c. */ void @@ -168,14 +215,15 @@ __argp_fmtstream_update (argp_fmtstream_t fs) if (!nl) { + size_t display_width = mbsnwidth (buf, fs->p - buf, MBSW_STOP_AT_NUL); /* The buffer ends in a partial line. */ - if (fs->point_col + len < fs->rmargin) + if (fs->point_col + display_width < fs->rmargin) { /* The remaining buffer text is a partial line and fits within the maximum line width. Advance point for the characters to be written and stop scanning. */ - fs->point_col += len; + fs->point_col += display_width; break; } else @@ -183,14 +231,18 @@ __argp_fmtstream_update (argp_fmtstream_t fs) the end of the buffer. */ nl = fs->p; } - else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin) - { - /* The buffer contains a full line that fits within the maximum - line width. Reset point and scan the next line. */ - fs->point_col = 0; - buf = nl + 1; - continue; - } + else + { + size_t display_width = mbsnwidth (buf, nl - buf, MBSW_STOP_AT_NUL); + if (display_width < (ssize_t) fs->rmargin) + { + /* The buffer contains a full line that fits within the maximum + line width. Reset point and scan the next line. */ + fs->point_col = 0; + buf = nl + 1; + continue; + } + } /* This line is too long. */ r = fs->rmargin - 1; @@ -226,7 +278,7 @@ __argp_fmtstream_update (argp_fmtstream_t fs) char *p, *nextline; int i; - p = buf + (r + 1 - fs->point_col); + p = buf + add_width (buf, fs->p, (r + 1 - fs->point_col)); while (p >= buf && !isblank ((unsigned char) *p)) --p; nextline = p + 1; /* This will begin the next line. */ @@ -244,7 +296,7 @@ __argp_fmtstream_update (argp_fmtstream_t fs) { /* A single word that is greater than the maximum line width. Oh well. Put it on an overlong line by itself. */ - p = buf + (r + 1 - fs->point_col); + p = buf + add_width (buf, fs->p, (r + 1 - fs->point_col)); /* Find the end of the long word. */ if (p < nl) do @@ -278,7 +330,8 @@ __argp_fmtstream_update (argp_fmtstream_t fs) && fs->p > nextline) { /* The margin needs more blanks than we removed. */ - if (fs->end - fs->p > fs->wmargin + 1) + if (mbsnwidth (fs->p, fs->end - fs->p, MBSW_STOP_AT_NUL) + > fs->wmargin + 1) /* Make some space for them. */ { size_t mv = fs->p - nextline; diff --git a/lib/argp-help.c b/lib/argp-help.c index a126acb..2bfd6a3 100644 --- a/lib/argp-help.c +++ b/lib/argp-help.c @@ -49,6 +49,7 @@ #include "argp.h" #include "argp-fmtstream.h" #include "argp-namefrob.h" +#include "mbswidth.h" #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) @@ -1451,7 +1452,7 @@ argp_args_usage (const struct argp *argp, const struct argp_state *state, /* Manually do line wrapping so that it (probably) won't get wrapped at any embedded spaces. */ - space (stream, 1 + nl - cp); + space (stream, 1 + mbsnwidth (cp, nl - cp, MBSW_STOP_AT_NUL)); __argp_fmtstream_write (stream, cp, nl - cp); } diff --git a/lib/mbswidth.c b/lib/mbswidth.c index d81b5c8..aa6be62 100644 --- a/lib/mbswidth.c +++ b/lib/mbswidth.c @@ -90,6 +90,9 @@ mbsnwidth (const char *string, size_t nbytes, int flags) p++; width++; break; + case '\0': + if (flags & MBSW_STOP_AT_NUL) + return width; default: /* If we have a multibyte sequence, scan it up to its end. */ { @@ -168,6 +171,9 @@ mbsnwidth (const char *string, size_t nbytes, int flags) { unsigned char c = (unsigned char) *p++; + if (c == 0 && (flags & MBSW_STOP_AT_NUL)) + return width; + if (isprint (c)) { if (width == INT_MAX) diff --git a/lib/mbswidth.h b/lib/mbswidth.h index 690a5a7..1a5fe44 100644 --- a/lib/mbswidth.h +++ b/lib/mbswidth.h @@ -45,6 +45,9 @@ extern "C" { control characters and 1 otherwise. */ #define MBSW_REJECT_UNPRINTABLE 2 +/* If this bit is set \0 is treated as the end of string. + Otherwise it's treated as a normal one column width character. */ +#define MBSW_STOP_AT_NUL 4 /* Returns the number of screen columns needed for STRING. */ #define mbswidth gnu_mbswidth /* avoid clash with UnixWare 7.1.1 function */ diff --git a/modules/argp b/modules/argp index 8d49681..7dce95a 100644 --- a/modules/argp +++ b/modules/argp @@ -39,6 +39,7 @@ stdalign strerror memchr memmove +mbswidth configure.ac: gl_ARGP diff --git a/modules/argp-tests b/modules/argp-tests index 8f92a4d..0463927 100644 --- a/modules/argp-tests +++ b/modules/argp-tests @@ -1,11 +1,13 @@ Files: tests/test-argp.c tests/test-argp-2.sh +tests/test-argp-2-utf.sh Depends-on: progname Makefile.am: TESTS += test-argp test-argp-2.sh -check_PROGRAMS += test-argp +TESTS += test-argp test-argp-2.sh test-argp-2-utf.sh +check_PROGRAMS += test-argp test-argp-utf8 test_argp_LDADD = $(LDADD) @LIBINTL@ diff --git a/tests/test-argp-2-utf.sh b/tests/test-argp-2-utf.sh index 935cce5..409a92a 100755 --- a/tests/test-argp-2-utf.sh +++ b/tests/test-argp-2-utf.sh @@ -16,6 +16,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . */ +# Test whether a specific UTF-8 locale is installed. +: ${LOCALE_FR_UTF8=fr_FR.UTF-8} +if test $LOCALE_FR_UTF8 = none; then + if test -f /usr/bin/localedef; then + echo "Skipping test: no french Unicode locale is installed" + else + echo "Skipping test: no french Unicode locale is supported" + fi + exit 77 +fi + +export LC_ALL=$LOCALE_FR_UTF8 + TMP=argp.$$ unset ARGP_HELP_FMT