From 369350be78940f1a15273c747788cbc1aa331380 Mon Sep 17 00:00:00 2001 From: Anton Lindqvist Date: Sun, 16 Apr 2017 20:29:46 +0200 Subject: [PATCH 1/2] Calculate the width of each displayed character In an attempt to fix a bug caused by lines including Unicode characters that occupy more than one column. While at it, fix another bug related to the column counter when a line contains more than one escape sequence. --- pick.c | 112 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/pick.c b/pick.c index 337d520d..097d4da5 100644 --- a/pick.c +++ b/pick.c @@ -667,65 +667,95 @@ tty_restore(void) } void -print_line(const char *string, size_t length, int so, ssize_t ulso, ssize_t uleo) +print_line(const char *str, size_t len, int standout, + ssize_t enter_underline, ssize_t exit_underline) { size_t i; - int c, col, tabwidth; - int in_esc_seq = 0; - int non_printable = 0; + wchar_t wc; + int col, in_esc_seq, nbytes, width; - if (so) + if (standout) tty_putp(enter_standout_mode, 1); - for (col = i = 0; i < length && col < columns; i++) { - if (i == (size_t)ulso) + col = i = in_esc_seq = 0; + while (col < columns) { + if (enter_underline == (ssize_t)i) tty_putp(enter_underline_mode, 1); + else if (exit_underline == (ssize_t)i) + tty_putp(exit_underline_mode, 1); + if (i == len) + break; + + if (str[i] == '\t') { + width = 8 - (col & 7); /* ceil to multiple of 8 */ + if (col + width > columns) + break; + for (; width > 0; width--) + if (tty_putc(' ') == ERR) + err(1, "tty_putc"); + + i++, col += width; + continue; + } /* - * Count the number of outputted ANSI escape sequences - * in order to adjust the column count since they do not - * occupy any screen real-estate. + * A NUL will be present prior the NUL-terminator if + * descriptions are enabled. */ - if (in_esc_seq) { - non_printable++; - if (string[i] >= '@' && string[i] <= '~') - in_esc_seq = 0; - } else if (i > 0 && string[i - 1] == '\033' - && string[i] == '[') { - in_esc_seq = 1; - non_printable = 2; + if (str[i] == '\0') { + if (tty_putc(' ') == ERR) + err(1, "tty_putc"); + + i++, col++; + continue; } - c = string[i]; - if (c == '\t') { - /* Ceil column count to multiple of 8. */ - col += tabwidth = 8 - (col & 7); - while (tabwidth-- > 0) - if (tty_putc(' ') == ERR) - err(1, "tty_putc"); - } else { + /* + * Due to the explicit NUL-check above, the case where + * mbtowc(3) returns 0 is not handled here. + */ + if ((nbytes = mbtowc(&wc, &str[i], MB_CUR_MAX)) == -1) { + mbtowc(NULL, NULL, MB_CUR_MAX); + i++; + continue; + } + + width = 0; + if (i > 0 && str[i - 1] == '\033' && str[i] == '[') /* - * A null character will be present prior the - * terminating null character if descriptions is - * enabled. + * Start of ANSI escape sequence. The previous + * ESC-character already has a zero width but any + * following characters will not consume any columns + * once displayed. */ - if (c == '\0') - c = ' '; - if (!isu8cont(c)) - col++; - if (tty_putc(c) == EOF) - err(1, "tty_putc"); - } + in_esc_seq = 1; + else if (!in_esc_seq && (width = wcwidth(wc)) < 0) + /* + * The character is not printable. However, it could be + * an ESC-character marking the the beginning an escape + * sequence so make sure to display every valid + * characters. + */ + width = 0; + else if (str[i] >= '@' && str[i] <= '~') + in_esc_seq = 0; - if (i + 1 == (size_t)uleo) - tty_putp(exit_underline_mode, 1); + if (col + width > columns) + break; + col += width; + + for (; nbytes > 0; nbytes--, i++) + if (tty_putc(str[i]) == EOF) + err(1, "tty_putc"); } - for (col -= non_printable; col < columns; col++) + for (; col < columns; col++) if (tty_putc(' ') == EOF) err(1, "tty_putc"); - /* If uleo is greater than columns the underline attribute will spill - * over on the next line unless all attributes are exited. */ + /* + * If exit_underline is greater than columns the underline attribute + * will spill over on the next line unless all attributes are exited. + */ tty_putp(exit_attribute_mode, 1); } From ffb4ca48e9efd1eae7ebc61f30f4eee2c5ba609e Mon Sep 17 00:00:00 2001 From: Anton Lindqvist Date: Mon, 17 Apr 2017 10:12:30 +0200 Subject: [PATCH 2/2] Define _GNU_SOURCE Required by posix_openpt(3) and wcwidth(3) on Linux. --- Makefile.am | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index a91231b5..09d131d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,7 @@ AUTOMAKE_OPTIONS=foreign AM_CFLAGS=-pedantic -Wall -Werror -Wextra +AM_CPPFLAGS=-D_GNU_SOURCE bin_PROGRAMS=pick dist_pick_SOURCES=pick.c compat.h compat.c @@ -19,8 +20,6 @@ T_LOG_COMPILER=$(top_srcdir)/tests/pick-test.sh check_PROGRAMS=tests/pick-test tests_pick_test_SOURCES=tests/pick-test.c tests_pick_test_CFLAGS=$(AM_CFLAGS) -# Required by posix_openpt on Linux. -tests_pick_test_CFLAGS+=-D_XOPEN_SOURCE=600 EXTRA_DIST=INSTALL.md INSTALL.md.in LICENSE README.md tests/pick-test.sh $(TESTS)