diff --git a/.ecode/project_build.json b/.ecode/project_build.json index d1352fa3a..337a0c564 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -24,7 +24,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "linux" @@ -87,7 +88,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "macos" @@ -141,7 +143,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "windows" @@ -191,7 +194,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "linux" @@ -360,7 +364,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "linux" @@ -440,7 +445,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "linux" @@ -515,7 +521,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "macos" @@ -575,7 +582,8 @@ } ], "config": { - "clear_sys_env": false + "clear_sys_env": false, + "strip_ansi_codes": false }, "os": [ "windows" diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index f3ca49093..c7db0ef75 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -1171,6 +1171,10 @@ class EE_API String { static size_t countLines( String::View text ); + /** Strips any ANSI code found in the string. + * @param str The string to strip + */ + static void stripAnsiCodes( std::string& str ); private: friend EE_API bool operator==( const String& left, const String& right ); friend EE_API bool operator<( const String& left, const String& right ); diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 2b1b6e1ae..4bd9d20e4 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -29,15 +29,25 @@ #include #endif +#ifdef EE_ARCH_X86_64 +#if defined( _MSC_VER ) +#define COMPILER_MSVC 1 +#include +#elif ( defined( __GNUC__ ) || defined( __clang__ ) ) +#define COMPILER_GCC_CLANG 1 +#include +#endif +#endif + #if defined( EE_ARCH_X86_64 ) - #if defined( _MSC_VER ) - #include - #elif defined( __GNUC__ ) || defined( __clang__ ) - #include - #include - #endif +#if defined( _MSC_VER ) +#include +#elif defined( __GNUC__ ) || defined( __clang__ ) +#include +#include +#endif #elif defined( EE_ARCH_ARM64 ) - #include +#include #endif namespace EE { @@ -1388,13 +1398,11 @@ bool String::contains( const String& needle ) const { return String::contains( *this, needle ); } -namespace { - #ifdef EE_ARCH_X86_64 #if defined( __GNUC__ ) || defined( __clang__ ) __attribute__( ( target( "avx2" ) ) ) #endif -bool isAsciiAVX2( const char32_t* data, size_t len, uint32_t limit ) { +static bool isAsciiAVX2( const char32_t* data, size_t len, uint32_t limit ) { const char32_t* end = data + len; const __m256i maskVec = _mm256_set1_epi32( ~limit ); @@ -1415,7 +1423,7 @@ bool isAsciiAVX2( const char32_t* data, size_t len, uint32_t limit ) { #endif #ifdef EE_ARCH_ARM64 -bool isAsciiNEON( const char32_t* data, size_t len, uint32_t limit ) { +static bool isAsciiNEON( const char32_t* data, size_t len, uint32_t limit ) { const char32_t* end = data + len; const uint32x4_t maskVec = vdupq_n_u32( ~limit ); @@ -1436,8 +1444,6 @@ bool isAsciiNEON( const char32_t* data, size_t len, uint32_t limit ) { } #endif -} // namespace - template static inline bool isAsciiTpl( const StringType& str ) { #ifdef EE_ARCH_X86_64 @@ -2476,4 +2482,196 @@ bool String::isSentenceBoundary( String::View string, std::size_t position ) { return unicode::is_sentence_boundary( string.data(), string.size(), position ); } +void strip_ansi_scalar( std::string& str ) { + if ( str.empty() ) + return; + + char* data = &str[0]; + const size_t len = str.size(); + size_t read_idx = 0; + size_t write_idx = 0; + + while ( read_idx < len ) { + // Fast scan for ESC + if ( unlikely( data[read_idx] == '\x1B' ) ) { + // Check for CSI sequence: ESC + [ + if ( read_idx + 1 < len && data[read_idx + 1] == '[' ) { + size_t scan = read_idx + 2; + // Scan for the terminator byte (0x40 - 0x7E) + while ( scan < len ) { + unsigned char c = static_cast( data[scan] ); + if ( c >= 0x40 && c <= 0x7E ) { + scan++; // Include the terminator + break; + } + scan++; + } + read_idx = scan; + continue; + } + } + + // Copy char if indices diverged + if ( read_idx != write_idx ) { + data[write_idx] = data[read_idx]; + } + write_idx++; + read_idx++; + } + str.resize( write_idx ); +} + +#ifdef EE_ARCH_X86 + +// Force AVX2 generation for this function on GCC/Clang +#if defined( COMPILER_GCC_CLANG ) +__attribute__( ( target( "avx2" ) ) ) +#endif +void strip_ansi_avx2( std::string& str ) { + if ( str.empty() ) + return; + + char* data = &str[0]; + const size_t len = str.size(); + size_t read_idx = 0; + size_t write_idx = 0; + + const __m256i esc_vec = _mm256_set1_epi8( '\x1B' ); + + // Process 32-byte chunks + while ( read_idx + 32 <= len ) { + __m256i chunk = _mm256_loadu_si256( reinterpret_cast( data + read_idx ) ); + __m256i cmp = _mm256_cmpeq_epi8( chunk, esc_vec ); + int mask = _mm256_movemask_epi8( cmp ); + + if ( likely( mask == 0 ) ) { + if ( read_idx != write_idx ) { + _mm256_storeu_si256( reinterpret_cast<__m256i*>( data + write_idx ), chunk ); + } + read_idx += 32; + write_idx += 32; + } else { + // Found ESC. Let the scalar loop handle the complexity of finding EXACT location + // and parsing the variable length ANSI code. + break; + } + } + + // Finish remaining with Scalar Logic + while ( read_idx < len ) { + if ( unlikely( data[read_idx] == '\x1B' ) ) { + if ( read_idx + 1 < len && data[read_idx + 1] == '[' ) { + size_t scan = read_idx + 2; + while ( scan < len ) { + unsigned char c = static_cast( data[scan] ); + if ( c >= 0x40 && c <= 0x7E ) { + scan++; + break; + } + scan++; + } + read_idx = scan; + continue; + } + } + if ( read_idx != write_idx ) { + data[write_idx] = data[read_idx]; + } + write_idx++; + read_idx++; + } + str.resize( write_idx ); +} +#endif // ARCH_X86 + +#ifdef EE_ARCH_ARM64 + +void strip_ansi_neon( std::string& str ) { + if ( str.empty() ) + return; + + char* data = &str[0]; + const size_t len = str.size(); + size_t read_idx = 0; + size_t write_idx = 0; + + const uint8x16_t esc_vec = vdupq_n_u8( '\x1B' ); + + // Process 16-byte chunks (NEON register size is 128-bit) + while ( read_idx + 16 <= len ) { + // Load 16 bytes + uint8x16_t chunk = vld1q_u8( reinterpret_cast( data + read_idx ) ); + + // Compare with ESC (0xFF if equal, 0x00 if not) + uint8x16_t cmp = vceqq_u8( chunk, esc_vec ); + + // Check if any byte matched. + // vmaxvq_u32 checks 32-bit lanes, but if any byte is 0xFF, the 32-bit lane will be + // non-zero. This is a fast reduction instruction on AArch64. + uint32_t has_esc = vmaxvq_u32( vreinterpretq_u32_u8( cmp ) ); + + if ( likely( has_esc == 0 ) ) { + // No escape codes found + if ( read_idx != write_idx ) { + vst1q_u8( reinterpret_cast( data + write_idx ), chunk ); + } + read_idx += 16; + write_idx += 16; + } else { + // Found ESC. Break to scalar to handle specific parsing. + break; + } + } + + // Scalar fallback for the rest + while ( read_idx < len ) { + if ( unlikely( data[read_idx] == '\x1B' ) ) { + if ( read_idx + 1 < len && data[read_idx + 1] == '[' ) { + size_t scan = read_idx + 2; + while ( scan < len ) { + unsigned char c = static_cast( data[scan] ); + if ( c >= 0x40 && c <= 0x7E ) { + scan++; + break; + } + scan++; + } + read_idx = scan; + continue; + } + } + if ( read_idx != write_idx ) { + data[write_idx] = data[read_idx]; + } + write_idx++; + read_idx++; + } + str.resize( write_idx ); +} +#endif // EE_ARCH_ARM64 + +typedef void ( *stripper_func )( std::string& ); + +stripper_func resolve_ansi_strip_fn() { +#ifdef EE_ARCH_ARM64 + return strip_ansi_neon; +#endif + +#ifdef EE_ARCH_X86 + if ( CPU::hasAVX2() ) { + return strip_ansi_avx2; + } +#endif + + return strip_ansi_scalar; +} + +/** Strips any ANSI code found in the string. + * @param str The string to strip + */ +void String::stripAnsiCodes( std::string& str ) { + static const stripper_func implementation = resolve_ansi_strip_fn(); + implementation( str ); +} + } // namespace EE diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index a0cc7826f..e6c9b2da3 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -914,7 +914,7 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& } maxWidth = eemax( width, maxWidth ); } - return width; + return maxWidth; } #ifdef EE_TEXT_SHAPER_ENABLED diff --git a/src/eepp/ui/doc/languages/c.cpp b/src/eepp/ui/doc/languages/c.cpp index 038e868c4..52f09bdd9 100644 --- a/src/eepp/ui/doc/languages/c.cpp +++ b/src/eepp/ui/doc/languages/c.cpp @@ -12,9 +12,9 @@ void addC() { { { { "//.-\n" }, "comment" }, { { "/%*", "%*/" }, "comment" }, - { { "(#%s*include)%s+([<%\"][%w%d%.%\\%/%_%-]+[>%\"])" }, + { { "%s*(#%s*include)%s+([<%\"][%w%d%.%\\%/%_%-]+[>%\"])" }, { "keyword", "keyword", "literal" } }, - { { "(#%s*include_next)%s+([<%\"][%w%d%.%\\%/%_%-]+[>%\"])" }, + { { "%s*(#%s*include_next)%s+([<%\"][%w%d%.%\\%/%_%-]+[>%\"])" }, { "keyword", "keyword", "literal" } }, { { "\"", "[\"\n]", "\\" }, "string" }, { { "'", "'", "\\" }, "string" }, diff --git a/src/eepp/ui/doc/languages/cpp.cpp b/src/eepp/ui/doc/languages/cpp.cpp index 0d8b22405..8acdfeeec 100644 --- a/src/eepp/ui/doc/languages/cpp.cpp +++ b/src/eepp/ui/doc/languages/cpp.cpp @@ -21,7 +21,7 @@ void addCPP() { { { "/%*", "%*/" }, "comment" }, { { "\"", "[\"\n]", "\\" }, "string" }, { { "'", "'", "\\" }, "string" }, - { { "(#%s*include)%s+([<%\"][%w%d%.%\\%/%_%-%+]+[>%\"])" }, + { { "%s*(#%s*include)%s+([<%\"][%w%d%.%\\%/%_%-%+]+[>%\"])" }, { "keyword", "keyword", "literal" } }, { { "cpp_number_parser" }, "number", "", SyntaxPatternMatchType::Parser }, { { "[%+%-=/%*%^%%<>!~|&]" }, "operator" }, diff --git a/src/tests/unit_tests/stringsoperations.cpp b/src/tests/unit_tests/stringsoperations.cpp index 2b40d5e36..1af8bfd85 100644 --- a/src/tests/unit_tests/stringsoperations.cpp +++ b/src/tests/unit_tests/stringsoperations.cpp @@ -163,3 +163,50 @@ UTEST( String, isLatin1HighBit ) { strHigh += (String::StringBaseType)0x80000000; EXPECT_FALSE( strHigh.isLatin1() ); } + +UTEST( String, stripAnsiCodes ) { + // 1. Basic color codes + std::string redBold = "\x1B[1;31mHello\x1B[0m"; + String::stripAnsiCodes( redBold ); + EXPECT_STREQ( "Hello", redBold.c_str() ); + + // 2. Cursor movement (CSI) + std::string clearScreen = "\x1B[2JMove"; + String::stripAnsiCodes( clearScreen ); + EXPECT_STREQ( "Move", clearScreen.c_str() ); + + // 3. No codes + std::string plain = "Just text"; + String::stripAnsiCodes( plain ); + EXPECT_STREQ( "Just text", plain.c_str() ); + + // 4. Multiple mixed codes + std::string complex = "A\x1B[32mB\x1B[33mC\x1B[0m"; + String::stripAnsiCodes( complex ); + EXPECT_STREQ( "ABC", complex.c_str() ); + + // 5. Code at end + std::string endCode = "End\x1B[K"; + String::stripAnsiCodes( endCode ); + EXPECT_STREQ( "End", endCode.c_str() ); + + // 6. Code at start + std::string startCode = "\x1B[HStart"; + String::stripAnsiCodes( startCode ); + EXPECT_STREQ( "Start", startCode.c_str() ); + + // 7. Long string (trigger SIMD paths) + std::string longStr; + std::string expected; + for ( int i = 0; i < 1000; i++ ) { + longStr += "a\x1B[31mb"; + expected += "ab"; + } + String::stripAnsiCodes( longStr ); + EXPECT_STREQ( expected.c_str(), longStr.c_str() ); + + // 8. Adjacent codes + std::string adjacent = "Double\x1B[1m\x1B[31mColor"; + String::stripAnsiCodes( adjacent ); + EXPECT_STREQ( "DoubleColor", adjacent.c_str() ); +} diff --git a/src/tools/ecode/plugins/debugger/debuggerclient.hpp b/src/tools/ecode/plugins/debugger/debuggerclient.hpp index 7e0a0183e..0081e6e09 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclient.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclient.hpp @@ -83,6 +83,7 @@ class DebuggerClient { virtual void gotoTargets( const Source& source, const int line, const std::vector& targets, const SessionId& sessionId ) = 0; + }; virtual bool start() = 0; diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index 6b37fb22f..58596dfff 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -439,35 +439,41 @@ void DebuggerClientListener::stackTrace( const int threadId, StackTraceInfo&& st mStackModel->setStack( std::move( stack ) ); - for ( const auto& expression : mPlugin->mExpressions ) { - mClient->evaluate( - expression, "watch", getCurrentFrameId(), - [this, expression]( const std::string&, const std::optional& info ) { - Variable var; - var.evaluateName = expression; - var.name = std::move( expression ); - if ( info ) { - var.value = info->result; - var.type = info->type; - var.variablesReference = info->variablesReference; - var.indexedVariables = info->indexedVariables; - var.namedVariables = info->namedVariables; - var.memoryReference = info->memoryReference; - } - mPlugin->mExpressionsHolder->upsertRootChild( std::move( var ) ); - ExpandedState::Location location; - { - Lock l( mMutex ); - if ( !mCurrentScopePos.has_value() ) - return; - location = { mCurrentScopePos->first, mCurrentScopePos->second, - mCurrentFrameId }; - } - mPlugin->mExpressionsHolder->restoreExpandedState( - location, mClient, getStatusDebuggerController()->getUIExpressions(), true, - mUnstableFrameId ); - } ); - } + evaluateExpressions(); +} + +void DebuggerClientListener::evaluateExpression( const std::string& expression ) { + mClient->evaluate( + expression, "watch", getCurrentFrameId(), + [this, expression]( const std::string&, const std::optional& info ) { + Variable var; + var.evaluateName = expression; + var.name = std::move( expression ); + if ( info ) { + var.value = info->result; + var.type = info->type; + var.variablesReference = info->variablesReference; + var.indexedVariables = info->indexedVariables; + var.namedVariables = info->namedVariables; + var.memoryReference = info->memoryReference; + } + mPlugin->mExpressionsHolder->upsertRootChild( std::move( var ) ); + ExpandedState::Location location; + { + Lock l( mMutex ); + if ( !mCurrentScopePos.has_value() ) + return; + location = { mCurrentScopePos->first, mCurrentScopePos->second, mCurrentFrameId }; + } + mPlugin->mExpressionsHolder->restoreExpandedState( + location, mClient, getStatusDebuggerController()->getUIExpressions(), true, + mUnstableFrameId ); + } ); +} + +void DebuggerClientListener::evaluateExpressions() { + for ( const auto& expression : mPlugin->mExpressions ) + evaluateExpression( expression ); } void DebuggerClientListener::scopes( const int /*frameId*/, std::vector&& scopes, diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp index aabf9c2f7..d50f6102d 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp @@ -81,6 +81,10 @@ class DebuggerClientListener : public DebuggerClient::Listener { void gotoTargets( const Source& source, const int line, const std::vector& targets, const SessionId& sessionId ); + void evaluateExpression( const std::string& expression ); + + void evaluateExpressions(); + bool isRemote() const; bool isStopped() const; diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index a6689c6a2..066cf48cf 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -923,6 +923,10 @@ void DebuggerPlugin::openExpressionMenu( ModelIndex idx ) { mExpressionsHolder->addChild( std::make_shared( expression, 0 ) ); msgBox->closeWindow(); + + if ( mDebuggingState == StatusDebuggerController::State::Paused && mListener ) { + mListener->evaluateExpression( expression ); + } } } ); } diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index b548c91a4..09570dd12 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -60,7 +60,7 @@ class LinterPlugin : public Plugin { "Use static code analysis tool used to flag programming errors, bugs, " "stylistic errors, and suspicious constructs.", LinterPlugin::New, - { 0, 2, 8 }, + { 0, 2, 9 }, LinterPlugin::NewSync }; } diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index a177f1fc5..d326419a7 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -113,12 +113,15 @@ json ProjectBuild::serialize( const ProjectBuild::Map& builds ) { step["reuse_previous_terminal"] = run->reusePreviousTerminal; if ( run->useStatusBarTerminal ) step["use_statusbar_terminal"] = run->useStatusBarTerminal; + if ( run->stripAnsiCodes ) + step["strip_ansi_codes"] = run->stripAnsiCodes; jrun.push_back( step ); } } bj["build_types"] = curBuild.buildTypes(); bj["config"]["clear_sys_env"] = curBuild.getConfig().clearSysEnv; + bj["config"]["strip_ansi_codes"] = curBuild.getConfig().stripAnsiCodes; bj["os"] = curBuild.os(); if ( !curBuild.vars().empty() ) { @@ -513,6 +516,7 @@ ProjectBuild::Map ProjectBuild::deserialize( const json& j, const std::string& p if ( buildObj.contains( "config" ) && buildObj["config"].is_object() ) { b.mConfig.clearSysEnv = buildObj.value( "clear_sys_env", false ); + b.mConfig.stripAnsiCodes = buildObj.value( "strip_ansi_codes", false ); } if ( buildObj.contains( "var" ) && buildObj["var"].is_object() ) { @@ -563,6 +567,7 @@ ProjectBuild::Map ProjectBuild::deserialize( const json& j, const std::string& p rstep->runInTerminal = step.value( "run_in_terminal", false ); rstep->reusePreviousTerminal = step.value( "reuse_previous_terminal", false ); rstep->useStatusBarTerminal = step.value( "use_statusbar_terminal", false ); + rstep->stripAnsiCodes = step.value( "strip_ansi_codes", false ); b.mRun.emplace_back( std::move( rstep ) ); } } @@ -1020,8 +1025,11 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str do { bytesRead = mProcess->readStdOut( buffer ); std::string data( buffer.substr( 0, bytesRead ) ); - if ( progressFn ) + if ( progressFn ) { + if ( cmd.config.stripAnsiCodes ) + String::stripAnsiCodes( data ); progressFn( progress, std::move( data ), &cmd ); + } } while ( bytesRead != 0 && mProcess->isAlive() && !mShuttingDown && !mCancelBuild ); if ( mShuttingDown || mCancelBuild ) { @@ -1130,8 +1138,11 @@ void ProjectBuildManager::runApp( const ProjectBuildCommand& cmd, const ProjectB do { bytesRead = mProcessRun->readStdOut( buffer ); std::string data( buffer.substr( 0, bytesRead ) ); - if ( progressFn ) + if ( progressFn ) { + if ( cmd.stripAnsiCodes ) + String::stripAnsiCodes( data ); progressFn( 0, std::move( data ), &cmd ); + } } while ( bytesRead != 0 && mProcessRun->isAlive() && !mShuttingDown && !mCancelRun ); if ( mShuttingDown ) diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index 6c346bbf8..27001d456 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -107,6 +107,7 @@ struct ProjectBuildStep { bool runInTerminal{ false }; bool reusePreviousTerminal{ false }; bool useStatusBarTerminal{ false }; + bool stripAnsiCodes{ false }; }; using ProjectBuildSteps = std::vector>; @@ -114,6 +115,7 @@ using ProjectBuildKeyVal = std::vector>; struct ProjectBuildConfig { bool clearSysEnv{ false }; + bool stripAnsiCodes{ false }; }; enum class ProjectOutputParserTypes { Error = 0, Warning = 1, Notice = 2 }; diff --git a/src/tools/ecode/uibuildsettings.cpp b/src/tools/ecode/uibuildsettings.cpp index 4f6368502..9a63d459e 100644 --- a/src/tools/ecode/uibuildsettings.cpp +++ b/src/tools/ecode/uibuildsettings.cpp @@ -242,6 +242,8 @@ class UIBuildStep : public UILinearLayout { findByClass( "reuse_previous_terminal" ) ); mDataBindHolder += UIDataBindBool::New( &mStep->useStatusBarTerminal, findByClass( "use_statusbar_terminal" ) ); + mDataBindHolder += UIDataBindBool::New( &mStep->stripAnsiCodes, + findByClass( "strip_ansi_codes" ) ); } protected: @@ -278,11 +280,12 @@ class UIBuildStep : public UILinearLayout { - + - + + )xml"; @@ -298,12 +301,15 @@ class UIBuildStep : public UILinearLayout { auto useStatusBarTerminal = findByClass( "use_statusbar_terminal" )->asType(); + auto stripAnsiCodes = findByClass( "strip_ansi_codes" )->asType(); + runInTerminal->setVisible( true ); runInTerminal->setChecked( buildStep->runInTerminal ); runInTerminal->on( Event::OnValueChange, [reusePreviousTerminal, runInTerminal, - useStatusBarTerminal]( auto ) { + useStatusBarTerminal, stripAnsiCodes]( auto ) { reusePreviousTerminal->setEnabled( runInTerminal->isChecked() ); useStatusBarTerminal->setEnabled( runInTerminal->isChecked() ); + stripAnsiCodes->setEnabled( !runInTerminal->isChecked() ); if ( !runInTerminal->isChecked() ) { reusePreviousTerminal->setChecked( false ); useStatusBarTerminal->setChecked( false ); @@ -317,6 +323,10 @@ class UIBuildStep : public UILinearLayout { useStatusBarTerminal->setVisible( true ); useStatusBarTerminal->setEnabled( buildStep->runInTerminal ); useStatusBarTerminal->setChecked( buildStep->useStatusBarTerminal ); + + stripAnsiCodes->setVisible( true ); + stripAnsiCodes->setEnabled( !buildStep->runInTerminal ); + stripAnsiCodes->setChecked( buildStep->stripAnsiCodes ); } findByClass( "details_but" )->onClick( [this]( const MouseEvent* event ) { @@ -447,6 +457,7 @@ static const auto SETTINGS_PANEL_XML = R"xml( + @@ -613,6 +624,8 @@ UIBuildSettings::UIBuildSettings( mDataBindHolder += UIDataBindBool::New( &mBuild.mConfig.clearSysEnv, find( "clear_sys_env" ) ); + mDataBindHolder += UIDataBindBool::New( &mBuild.mConfig.stripAnsiCodes, + find( "output_parsers_strip_ansi_codes" ) ); bindTable( "table_envs", "env", mBuild.mEnvs ); bindTable( "table_vars", "var", mBuild.mVars );