string-util,format-table: measure console width with unichar_console_width()
ellipsize_mem() and align_string_mem() open coded the width of a character as unichar_iswide(c) ? 2 : 1, but unichar_console_width() counts a tab as eight cells. The two disagreed, and format-table.c uses both: it decides a field is too wide with utf8_console_width(), then asks ellipsize() to fit it into that many columns, and ellipsize() believed a tab was one cell. The ellipsized cell then still overflowed the column, measured by the very function that had just asked for it to be truncated. A table pinned to table_set_width(20) emitted lines of 25, 44 and 39 columns. align_string_mem() undercounted the same way and so padded already overlong cells further. Use unichar_console_width(), split out for this purpose in5c0d4b5061. While at it, let align_string_mem() decode with utf8_char_console_width() as well, so that the second pass shares both the width model and the validity check with the measuring pass, instead of keeping a more permissive decoder of its own around for the next caller to trip over. Its "count invalid chars as 1" fallback now skips the whole rejected sequence rather than one byte, so that the continuation bytes are not charged a cell each of their own; the branch should be unreachable anyway, since our caller only gets there after utf8_console_width() succeeded on the same string. Note that unichar_console_width() charges a tab a flat eight cells, while a terminal advances to the next multiple of eight, so this is deliberately an upper bound: the two agree only when the tab itself starts on a multiple of eight. A per character helper cannot do better, because a tab's width is a function of the column it starts in, and neither ellipsize_mem() nor align_string_mem() is told what column its string starts at. Overestimating is the safe direction: it can leave a column padded short, but it cannot overflow one. ascii_ellipsize_mem() equates bytes with character cells, which holds for ASCII except for the tab, so keep tabs off that fast path. Like the ANSI sequence case just above it, such strings now take the generic path, which means they get the Unicode ellipsis and one reserved cell rather than "..." and three, even in a non UTF-8 locale. (cherry picked from commit0834378fb9)
This commit is contained in:
committed by
Luca Boccassi
parent
5a263ef7ff
commit
dd0699f558
@@ -414,8 +414,10 @@ char* ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
|
||||
|
||||
bool has_ansi_seq = string_has_ansi_sequence(s, old_length);
|
||||
|
||||
/* If no multibyte characters or ANSI sequences, use ascii_ellipsize_mem for speed */
|
||||
if (!has_ansi_seq && ascii_is_valid_n(s, old_length))
|
||||
/* If no multibyte characters or ANSI sequences, use ascii_ellipsize_mem for speed. It equates
|
||||
* bytes with character cells, which holds for ASCII except for the tab, which
|
||||
* unichar_console_width() counts as 8 cells. */
|
||||
if (!has_ansi_seq && ascii_is_valid_n(s, old_length) && !memchr(s, '\t', old_length))
|
||||
return ascii_ellipsize_mem(s, old_length, new_length, percent);
|
||||
|
||||
x = (new_length - 1) * percent / 100;
|
||||
@@ -436,7 +438,7 @@ char* ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
|
||||
char32_t c;
|
||||
assert_se(utf8_encoded_to_unichar(i, &c) == r);
|
||||
|
||||
int w = unichar_iswide(c) ? 2 : 1;
|
||||
int w = unichar_console_width(c);
|
||||
if (k + w > x)
|
||||
break;
|
||||
|
||||
@@ -467,7 +469,7 @@ char* ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
|
||||
if (!tt)
|
||||
return NULL;
|
||||
|
||||
w = unichar_iswide(c) ? 2 : 1;
|
||||
w = unichar_console_width(c);
|
||||
if (k + w > new_length)
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user