string-util: pass ANSI sequences through unchanged

Cutting off in the middle may leave the terminal in a bad state, breaking
further output. But we don't know what a given ANSI sequence does, e.g.
ANSI_NORMAL should not be skipped. But it is also nice to keep various
sequences intact, so that if we had part of the string in blue, and we cut out
the beginning of the blue part, we still want to keep the remainder in color.
So let's just pass them through, stripping out the characters that take up
actual space.

Also, use memcpy_safe as we may end up copying zero bytes when ellipsizing at
the start/end of a string.

Fixes: #24502

This also fixes an ugliness where we would ellipsize string with ANSI
sequences too much, leading to output that was narrower on screen than the
requested length:

  Starting AAAAAAAAAAAAAAAAAAAAA.service
  Starting BBBBBBBBBBBBBBBBBBBBB.service
  Starting LONG…ER.service

Co-authored-by: Jan Janssen <medhefgo@web.de>
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2023-06-27 16:55:02 -06:00
co-authored by Jan Janssen
parent d9c72e5419
commit cb558ab222
2 changed files with 173 additions and 33 deletions
+41
View File
@@ -4,6 +4,7 @@
#include "alloc-util.h"
#include "constants.h"
#include "escape.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
@@ -115,4 +116,44 @@ TEST(ellipsize) {
test_ellipsize_one("shórt");
}
TEST(ellipsize_ansi) {
const char *s = ANSI_HIGHLIGHT_YELLOW_UNDERLINE "yęllow"
ANSI_HIGHLIGHT_GREY_UNDERLINE "grěy"
ANSI_HIGHLIGHT_BLUE_UNDERLINE "blue"
ANSI_NORMAL "nórmął";
size_t len = strlen(s);
for (unsigned percent = 0; percent <= 100; percent += 15)
for (ssize_t x = 21; x >= 0; x--) {
_cleanup_free_ char *t = ellipsize_mem(s, len, x, percent);
printf("%02zd: \"%s\"\n", x, t);
assert_se(utf8_is_valid(t));
if (DEBUG_LOGGING) {
_cleanup_free_ char *e = cescape(t);
printf(" : \"%s\"\n", e);
}
}
}
TEST(ellipsize_ansi_cats) {
_cleanup_free_ char *e, *f, *g, *h;
/* Make sure we don't cut off in the middle of an ANSI escape sequence. */
e = ellipsize("01" ANSI_NORMAL "23", 4, 0);
puts(e);
assert_se(streq(e, "01" ANSI_NORMAL "23"));
f = ellipsize("ab" ANSI_NORMAL "cd", 4, 90);
puts(f);
assert_se(streq(f, "ab" ANSI_NORMAL "cd"));
g = ellipsize("🐱🐱" ANSI_NORMAL "🐱🐱" ANSI_NORMAL, 5, 0);
puts(g);
assert_se(streq(g, "…" ANSI_NORMAL "🐱🐱" ANSI_NORMAL));
h = ellipsize("🐱🐱" ANSI_NORMAL "🐱🐱" ANSI_NORMAL, 5, 90);
puts(h);
assert_se(streq(h, "🐱…" ANSI_NORMAL "🐱" ANSI_NORMAL));
}
DEFINE_TEST_MAIN(LOG_INFO);