diff --git a/src/common/impl/format.c b/src/common/impl/format.c index 88698147b..ae3e89c60 100644 --- a/src/common/impl/format.c +++ b/src/common/impl/format.c @@ -425,6 +425,33 @@ static bool parseQuickJSString(FFstrbuf* buffer, const char* script, uint32_t sc } #endif +static bool skipAnsiEscape(FFstrbuf* in, FFstrbuf* out, FFstrbuf* trailingEscape) { + if (__builtin_expect(in->chars[0] == '\e' && in->chars[1] == '[', false)) { + // skip ANSI escape codes at the start of the string for truncation + const char* p = in->chars + 2; + while (*p && (ffCharIsDigit(*p) || *p == ';')) { + ++p; + } + if (*p && isascii(*p)) { + ++p; + } + uint32_t prefixLen = (uint32_t) (p - in->chars); + ffStrbufAppendNS(out, prefixLen, in->chars); + ffStrbufSubstrAfter(in, prefixLen - 1); + + if (trailingEscape) { + // likely have a `CSI m` reset at the end of the string + uint32_t iLastEscape = ffStrbufLastIndexC(in, '\e'); + if (iLastEscape != in->length) { + ffStrbufSetNS(trailingEscape, in->length - iLastEscape, in->chars + iLastEscape); + ffStrbufSubstrBefore(in, iLastEscape); + } + } + return true; + } + return false; +} + static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) { uint32_t argCounter = 0; @@ -617,6 +644,8 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3 } else if (cSep == '~') { FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate(); ffFormatAppendFormatArg(&tempString, &arguments[index - 1]); + FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate(); + skipAnsiEscape(&tempString, buffer, &trailingEscape); char* pEnd = NULL; int32_t start = (int32_t) strtol(pSep + 1, &pEnd, 10); @@ -642,6 +671,10 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3 } } + if (trailingEscape.length > 0) { + ffStrbufAppend(buffer, &trailingEscape); + } + if (*pEnd) { *pSep = cSep; appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); @@ -664,6 +697,9 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3 FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate(); ffFormatAppendFormatArg(&tempString, &arguments[index - 1]); + FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate(); + skipAnsiEscape(&tempString, buffer, &trailingEscape); + if (tempString.length == (uint32_t) truncLength) { ffStrbufAppend(buffer, &tempString); } else if (tempString.length > (uint32_t) truncLength) { @@ -689,6 +725,10 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3 ffStrbufAppend(buffer, &tempString); } } + + if (trailingEscape.length > 0) { + ffStrbufAppend(buffer, &trailingEscape); + } } } diff --git a/tests/format.c b/tests/format.c index b29b6be79..390b8e91a 100644 --- a/tests/format.c +++ b/tests/format.c @@ -110,6 +110,29 @@ int main(void) { VERIFY("output({?1}OK{?}{/1}NOT OK{/})", "", "output(NOT OK)"); } + { + VERIFY("output({1:3})", "\e[1m12345", "output(\e[1m123)"); + VERIFY("output({1:-3})", "\e[1m12345", "output(\e[1m123…)"); + VERIFY("output({1:3})", "\e[1m12345\e[m", "output(\e[1m123\e[m)"); + VERIFY("output({1:-3})", "\e[1m12345\e[m", "output(\e[1m123…\e[m)"); + } + + { + VERIFY("output({1<8})", "\e[1m12345", "output(\e[1m12345 )"); + VERIFY("output({1>8})", "\e[1m12345", "output(\e[1m 12345)"); + VERIFY("output({1<8})", "\e[1m12345\e[m", "output(\e[1m12345 \e[m)"); + VERIFY("output({1>8})", "\e[1m12345\e[m", "output(\e[1m 12345\e[m)"); + VERIFY("output({1<1})", "\e[30;47m12345\e[m", "output(\e[30;47m1\e[m)"); + } + + { + VERIFY("output({1~0})", "\e[1m12345", "output(\e[1m12345)"); + VERIFY("output({1~1})", "\e[1m12345", "output(\e[1m2345)"); + VERIFY("output({1~0})", "\e[1m12345\e[m", "output(\e[1m12345\e[m)"); + VERIFY("output({1~1})", "\e[1m12345\e[m", "output(\e[1m2345\e[m)"); + VERIFY("output({1~2})", "\e[30;47m12345\e[m", "output(\e[30;47m345\e[m)"); + } + #ifndef _WIN32 // Windows doesn't have setenv { ffListInit(&instance.config.display.constants);