From: Samuel Schlesinger Date: Mon, 3 Aug 2026 20:30:09 +0000 (+0000) Subject: patch 9.2.0906: slow transstr() with long strings X-Git-Tag: v9.2.0906^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=124c86868c253a5ec1347e7cbe102504d2d66a07;p=thirdparty%2Fvim.git patch 9.2.0906: slow transstr() with long strings Problem: transstr() appends with STRCAT()/STRLEN() from the start of the result on every iteration, making it quadratic to the length of the string. Solution: Keep a tail pointer and append at it. (Samuel Schlesinger). closes: #20925 Signed-off-by: Samuel Schlesinger Signed-off-by: Christian Brabandt --- diff --git a/src/charset.c b/src/charset.c index 1c45688fe3..3be32f5fb4 100644 --- a/src/charset.c +++ b/src/charset.c @@ -383,7 +383,10 @@ transstr(char_u *s) if (res == NULL) return NULL; - *res = NUL; + // Keep a tail pointer to append to, appending with STRCAT would make + // this loop quadratic. + char_u *d = res; + p = s; while (*p != NUL) { @@ -391,14 +394,28 @@ transstr(char_u *s) { c = (*mb_ptr2char)(p); if (vim_isprintc(c)) - STRNCAT(res, p, l); // append printable multi-byte char + { + // append printable multi-byte char + mch_memmove(d, p, (size_t)l); + d += l; + } else - transchar_hex(res + STRLEN(res), c); + { + transchar_hex(d, c); + d += STRLEN(d); + } p += l; } else - STRCAT(res, transchar_byte(*p++)); + { + char_u *trs = transchar_byte(*p++); + int trs_len = (int)STRLEN(trs); + + mch_memmove(d, trs, (size_t)trs_len); + d += trs_len; + } } + *d = NUL; return res; } diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index 645ff531ed..2f2a7fc5f6 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -211,6 +211,56 @@ func Test_strwidth() set ambiwidth& endfunc +func Test_strtrans() + " The default of 'isprint' is platform-dependent: 0x7f and 0x9f are + " printable on Win32 and VMS. Set it so the expectations below hold + " everywhere. + let save_isprint = &isprint + set isprint=@,161-255 + + " printable ASCII is unchanged + call assert_equal('', strtrans('')) + call assert_equal('abc', strtrans('abc')) + + " control characters are displayed as ^X + call assert_equal('^I', strtrans("\t")) + call assert_equal('a^Mb^[c', strtrans("a\rb\ec")) + call assert_equal('^A^_^?', strtrans("\x01\x1f\x7f")) + + " printable multibyte characters are unchanged, including composing + " characters and characters above 0xffff + call assert_equal('héllo 你好', strtrans('héllo 你好')) + let s = 'e' .. nr2char(0x301) .. 'x' + call assert_equal(s, strtrans(s)) + call assert_equal(nr2char(0x1d11e), strtrans(nr2char(0x1d11e))) + + " unprintable multibyte characters are displayed in hex form + call assert_equal('<9f>', strtrans(nr2char(0x9f))) + call assert_equal('<200b>', strtrans(nr2char(0x200b))) + call assert_equal('', strtrans(nr2char(0xfeff))) + + " illegal bytes are displayed in hex form + call assert_equal('AB', strtrans("A\xffB")) + + " a long string mixing all kinds of characters + call assert_equal(repeat('a^Bé<9f>', 100), + \ strtrans(repeat("a\x02é" .. nr2char(0x9f), 100))) + + " the non-multi-byte code path + set encoding=latin1 + set isprint=@,161-255 + call assert_equal('a^Mb^[c', strtrans("a\rb\ec")) + call assert_equal('^A^_^?', strtrans("\x01\x1f\x7f")) + " an unprintable byte above 0x7f uses the meta notation + call assert_equal('| ', strtrans("\xa0")) + " a printable high byte is unchanged + call assert_equal("\xe9", strtrans("\xe9")) + call assert_equal("x^B\xe9| y", strtrans("x\x02\xe9\xa0y")) + set encoding=utf-8 + + let &isprint = save_isprint +endfunc + func Test_str2nr() call assert_equal(0, str2nr('')) call assert_equal(1, str2nr('1')) diff --git a/src/version.c b/src/version.c index 7bb915f932..13c9fc7d31 100644 --- a/src/version.c +++ b/src/version.c @@ -763,6 +763,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 906, /**/ 905, /**/