From 94f656018d8e26ffe7b91897ee159001ab4c3a5f Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Tue, 24 Aug 2021 15:53:32 +0100 Subject: [PATCH] [Minor] Rework replxx to make in compatible with Rspamd again --- contrib/DEPENDENCY_INFO.md | 4 +- contrib/replxx/CMakeLists.txt | 2 +- contrib/replxx/src/conversion.cxx | 86 ++++++--- contrib/replxx/src/{io.cxx => terminal.cxx} | 203 +++++++++++++------- contrib/replxx/src/{io.hxx => terminal.hxx} | 19 +- 5 files changed, 211 insertions(+), 103 deletions(-) rename contrib/replxx/src/{io.cxx => terminal.cxx} (78%) rename contrib/replxx/src/{io.hxx => terminal.hxx} (79%) diff --git a/contrib/DEPENDENCY_INFO.md b/contrib/DEPENDENCY_INFO.md index 41c53aadc..8b3b89828 100644 --- a/contrib/DEPENDENCY_INFO.md +++ b/contrib/DEPENDENCY_INFO.md @@ -10,7 +10,7 @@ | libottery | ? | Public Domain / CC0 | YES | many changes | | librdns | ? | BSD-2-Clause | YES | | | libucl | ? | BSD-2-Clause | YES | | -| replxx | 0.0.2 | BSD-2-Clause | YES | libicu usage | +| replxx | 6d93360 | BSD-2-Clause | YES | libicu usage | | lua-argparse | 0.7.0 | MIT | NO | | | lua-bit | 1.0.2 | MIT | YES | build fixes | | lua-fun | ? | MIT | YES | rspamd text | @@ -35,4 +35,4 @@ | frozen | 1.0.1 | Apache 2 | NO | | | fmt | 7.1.3 | MIT | NO | | | doctest | 2.4.5 | MIT | NO | | -| function2 | 4.1.0 | Boost | NO | | \ No newline at end of file +| function2 | 4.1.0 | Boost | NO | | diff --git a/contrib/replxx/CMakeLists.txt b/contrib/replxx/CMakeLists.txt index 749e61208..da6e8d865 100644 --- a/contrib/replxx/CMakeLists.txt +++ b/contrib/replxx/CMakeLists.txt @@ -55,11 +55,11 @@ set( src/escape.cxx src/history.cxx src/replxx_impl.cxx - src/io.cxx src/prompt.cxx src/replxx.cxx src/util.cxx src/wcwidth.cpp + src/terminal.cxx src/windows.cxx ) diff --git a/contrib/replxx/src/conversion.cxx b/contrib/replxx/src/conversion.cxx index bcdbe048e..f629f910e 100644 --- a/contrib/replxx/src/conversion.cxx +++ b/contrib/replxx/src/conversion.cxx @@ -2,8 +2,9 @@ #include #include #include -#include +#include +#include "unicode/utf8.h" #include "conversion.hxx" #ifdef _WIN32 @@ -44,20 +45,38 @@ bool is8BitEncoding( is_8bit_encoding() ); ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) { ConversionResult res = ConversionResult::conversionOK; if ( ! locale::is8BitEncoding ) { - const UTF8* sourceStart = reinterpret_cast(src); - const UTF8* sourceEnd = sourceStart + strlen(src); - UTF32* targetStart = reinterpret_cast(dst); - UTF32* targetEnd = targetStart + dstSize; - - res = ConvertUTF8toUTF32( - &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); + auto sourceStart = reinterpret_cast(src); + auto slen = strlen(src); + auto targetStart = reinterpret_cast(dst); + int i = 0, j = 0; + + while (i < slen && j < dstSize) { + UChar32 uc; + auto prev_i = i; + U8_NEXT (sourceStart, i, slen, uc); + + if (uc <= 0) { + if (U8_IS_LEAD (sourceStart[prev_i])) { + auto lead_byte = sourceStart[prev_i]; + auto trailing_bytes = (((uint8_t)(lead_byte)>=0xc2)+ + ((uint8_t)(lead_byte)>=0xe0)+ + ((uint8_t)(lead_byte)>=0xf0)); + + if (trailing_bytes + i > slen) { + return ConversionResult::sourceExhausted; + } + } + + /* Replace with 0xFFFD */ + uc = 0x0000FFFD; + } + targetStart[j++] = uc; + } - if (res == conversionOK) { - dstCount = static_cast( targetStart - reinterpret_cast( dst ) ); + dstCount = j; - if (dstCount < dstSize) { - *targetStart = 0; - } + if (j < dstSize) { + targetStart[j] = 0; } } else { for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) { @@ -69,26 +88,32 @@ ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, cons ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char8_t* src) { return copyString8to32( - dst, dstSize, dstCount, reinterpret_cast(src) + dst, dstSize, dstCount, reinterpret_cast(src) ); } -int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) { - int resCount( 0 ); +int copyString32to8( + char* dst, int dstSize, const char32_t* src, int srcSize +) { + int resCount = 0; + if ( ! locale::is8BitEncoding ) { - const UTF32* sourceStart = reinterpret_cast(src); - const UTF32* sourceEnd = sourceStart + srcSize; - UTF8* targetStart = reinterpret_cast(dst); - UTF8* targetEnd = targetStart + dstSize; - - ConversionResult res = ConvertUTF32toUTF8( - &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion - ); - - if ( res == conversionOK ) { - resCount = static_cast( targetStart - reinterpret_cast( dst ) ); - if ( resCount < dstSize ) { - *targetStart = 0; + int j = 0; + UBool is_error = 0; + + for (auto i = 0; i < srcSize; i ++) { + U8_APPEND ((uint8_t *)dst, j, dstSize, src[i], is_error); + + if (is_error) { + break; + } + } + + if (!is_error) { + resCount = j; + + if (j < dstSize) { + dst[j] = '\0'; } } } else { @@ -101,7 +126,8 @@ int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) dst[i] = 0; } } - return ( resCount ); + + return resCount; } } diff --git a/contrib/replxx/src/io.cxx b/contrib/replxx/src/terminal.cxx similarity index 78% rename from contrib/replxx/src/io.cxx rename to contrib/replxx/src/terminal.cxx index 8df176d1c..e618219e5 100644 --- a/contrib/replxx/src/io.cxx +++ b/contrib/replxx/src/terminal.cxx @@ -16,6 +16,10 @@ #define write _write #define STDIN_FILENO 0 +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; +#endif + #include "windows.hxx" #else /* _WIN32 */ @@ -24,10 +28,11 @@ #include #include #include +#include #endif /* _WIN32 */ -#include "io.hxx" +#include "terminal.hxx" #include "conversion.hxx" #include "escape.hxx" #include "replxx.hxx" @@ -65,21 +70,35 @@ bool out( is_a_tty( 1 ) ); } +#ifndef _WIN32 +Terminal* _terminal_ = nullptr; +static void WindowSizeChanged( int ) { + if ( ! _terminal_ ) { + return; + } + _terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE ); +} +#endif + + Terminal::Terminal( void ) #ifdef _WIN32 : _consoleOut( INVALID_HANDLE_VALUE ) , _consoleIn( INVALID_HANDLE_VALUE ) - , _oldMode() + , _origOutMode() + , _origInMode() , _oldDisplayAttribute() , _inputCodePage( GetConsoleCP() ) , _outputCodePage( GetConsoleOutputCP() ) , _interrupt( INVALID_HANDLE_VALUE ) , _events() + , _empty() #else : _origTermios() , _interrupt() #endif - , _rawMode( false ) { + , _rawMode( false ) + , _utf8() { #ifdef _WIN32 _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) ); #else @@ -100,26 +119,20 @@ Terminal::~Terminal( void ) { } void Terminal::write32( char32_t const* text32, int len32 ) { - int len8 = 4 * len32 + 1; - unique_ptr text8(new char[len8]); - int count8 = 0; - - copyString32to8(text8.get(), len8, text32, len32, &count8); - int nWritten( 0 ); -#ifdef _WIN32 - nWritten = win_write( text8.get(), count8 ); -#else - nWritten = write( 1, text8.get(), count8 ); -#endif - if ( nWritten != count8 ) { - throw std::runtime_error( "write failed" ); - } + _utf8.assign( text32, len32 ); + write8( _utf8.get(), _utf8.size() ); return; } void Terminal::write8( char const* data_, int size_ ) { #ifdef _WIN32 - int nWritten( win_write( data_, size_ ) ); + if ( ! _rawMode ) { + enable_out(); + } + int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) ); + if ( ! _rawMode ) { + disable_out(); + } #else int nWritten( write( 1, data_, size_ ) ); #endif @@ -164,18 +177,45 @@ inline int notty( void ) { } } +void Terminal::enable_out( void ) { +#ifdef _WIN32 + _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE ); + SetConsoleOutputCP( 65001 ); + GetConsoleMode( _consoleOut, &_origOutMode ); + _autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0; +#endif +} + +void Terminal::disable_out( void ) { +#ifdef _WIN32 + SetConsoleMode( _consoleOut, _origOutMode ); + SetConsoleOutputCP( _outputCodePage ); + _consoleOut = INVALID_HANDLE_VALUE; + _autoEscape = false; +#endif +} + +void Terminal::enable_bracketed_paste( void ) { + static char const BRACK_PASTE_INIT[] = "\033[?2004h"; + write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 ); +} + +void Terminal::disable_bracketed_paste( void ) { + static char const BRACK_PASTE_DISABLE[] = "\033[?2004l"; + write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 ); +} + int Terminal::enable_raw_mode( void ) { if ( ! _rawMode ) { #ifdef _WIN32 _consoleIn = GetStdHandle( STD_INPUT_HANDLE ); - _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE ); SetConsoleCP( 65001 ); - SetConsoleOutputCP( 65001 ); - GetConsoleMode( _consoleIn, &_oldMode ); + GetConsoleMode( _consoleIn, &_origInMode ); SetConsoleMode( _consoleIn, - _oldMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) + _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) ); + enable_out(); #else struct termios raw; @@ -208,21 +248,22 @@ int Terminal::enable_raw_mode( void ) { if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) { return ( notty() ); } + _terminal_ = this; #endif _rawMode = true; } - return 0; + return ( 0 ); } void Terminal::disable_raw_mode(void) { if ( _rawMode ) { #ifdef _WIN32 - SetConsoleMode( _consoleIn, _oldMode ); + disable_out(); + SetConsoleMode( _consoleIn, _origInMode ); SetConsoleCP( _inputCodePage ); - SetConsoleOutputCP( _outputCodePage ); _consoleIn = INVALID_HANDLE_VALUE; - _consoleOut = INVALID_HANDLE_VALUE; #else + _terminal_ = nullptr; if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) { return; } @@ -320,31 +361,23 @@ char32_t Terminal::read_char( void ) { } } #endif - if (rec.EventType != KEY_EVENT) { + if ( rec.EventType != KEY_EVENT ) { continue; } - // Windows provides for entry of characters that are not on your keyboard by - // sending the - // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == - // Alt key) ... + // Windows provides for entry of characters that are not on your keyboard by sending the + // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ... // accept these characters, otherwise only process characters on "key down" - if (!rec.Event.KeyEvent.bKeyDown && - rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) { + if ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) { continue; } modifierKeys = 0; - // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't - // treat this - // combination as either CTRL or META we just turn off those two bits, so it - // is still - // possible to combine CTRL and/or META with an AltGr key by using - // right-Ctrl and/or + // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this + // combination as either CTRL or META we just turn off those two bits, so it is still + // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or // left-Alt - if ((rec.Event.KeyEvent.dwControlKeyState & - (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) == - (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) { - rec.Event.KeyEvent.dwControlKeyState &= - ~(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); + if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) { + rec.Event.KeyEvent.dwControlKeyState &= ~( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); } if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) { modifierKeys |= Replxx::KEY::BASE_CONTROL; @@ -352,7 +385,7 @@ char32_t Terminal::read_char( void ) { if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) { modifierKeys |= Replxx::KEY::BASE_META; } - if (escSeen) { + if ( escSeen ) { modifierKeys |= Replxx::KEY::BASE_META; } int key( rec.Event.KeyEvent.uChar.UnicodeChar ); @@ -417,7 +450,7 @@ char32_t Terminal::read_char( void ) { key += 0x10000; } if ( is_control_code( key ) ) { - key += 0x40; + key = control_to_human( key ); modifierKeys |= Replxx::KEY::BASE_CONTROL; } key |= modifierKeys; @@ -474,7 +507,7 @@ char32_t Terminal::read_char( void ) { friendlyTextPtr = const_cast("DEL"); } else { friendlyTextBuf[0] = '^'; - friendlyTextBuf[1] = keyCopy + 0x40; + friendlyTextBuf[1] = control_to_human( keyCopy ); friendlyTextBuf[2] = 0; friendlyTextPtr = friendlyTextBuf; } @@ -494,7 +527,7 @@ char32_t Terminal::read_char( void ) { c = EscapeSequenceProcessing::doDispatch(c); if ( is_control_code( c ) ) { - c = Replxx::KEY::control( c + 0x40 ); + c = Replxx::KEY::control( control_to_human( c ) ); } #endif // #_WIN32 return ( c ); @@ -504,7 +537,7 @@ Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) { #ifdef _WIN32 std::array handles = { _consoleIn, _interrupt }; while ( true ) { - DWORD event( WaitForMultipleObjects( handles.size (), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) ); + DWORD event( WaitForMultipleObjects( static_cast( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) ); switch ( event ) { case ( WAIT_OBJECT_0 + 0 ): { // peek events that will be skipped @@ -580,6 +613,9 @@ Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) { if ( data == 'm' ) { return ( EVENT_TYPE::MESSAGE ); } + if ( data == 'r' ) { + return ( EVENT_TYPE::RESIZE ); + } } if ( FD_ISSET( 0, &fdSet ) ) { return ( EVENT_TYPE::KEY_PRESS ); @@ -593,7 +629,7 @@ void Terminal::notify_event( EVENT_TYPE eventType_ ) { _events.push_back( eventType_ ); SetEvent( _interrupt ); #else - char data( eventType_ == EVENT_TYPE::KEY_PRESS ? 'k' : 'm' ); + char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) ); static_cast( write( _interrupt[1], &data, 1 ) == 1 ); #endif } @@ -603,31 +639,38 @@ void Terminal::notify_event( EVENT_TYPE eventType_ ) { */ void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) { #ifdef _WIN32 + if ( _autoEscape ) { +#endif + if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) { + char const clearCode[] = "\033c\033[H\033[2J\033[0m"; + static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + } else { + char const clearCode[] = "\033[J"; + static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + } + return; +#ifdef _WIN32 + } COORD coord = { 0, 0 }; CONSOLE_SCREEN_BUFFER_INFO inf; - bool toEnd( clearScreen_ == CLEAR_SCREEN::TO_END ); HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) ); GetConsoleScreenBufferInfo( consoleOut, &inf ); - if ( ! toEnd ) { - SetConsoleCursorPosition( consoleOut, coord ); - } else { + if ( clearScreen_ == CLEAR_SCREEN::TO_END ) { coord = inf.dwCursorPosition; - } - DWORD nWritten( 0 ); - DWORD toWrite( - toEnd - ? ( inf.dwSize.Y - inf.dwCursorPosition.Y ) * inf.dwSize.X - inf.dwCursorPosition.X - : inf.dwSize.X * inf.dwSize.Y - ); - FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten ); -#else - if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) { - char const clearCode[] = "\033c\033[H\033[2J\033[0m"; - static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + DWORD nWritten( 0 ); + SHORT height( inf.srWindow.Bottom - inf.srWindow.Top ); + DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top ); + DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X ); +// FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten ); + _empty.resize( toWrite - 1, ' ' ); + WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr ); } else { - char const clearCode[] = "\033[J"; - static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + COORD scrollTarget = { 0, -inf.dwSize.Y }; + CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes }; + SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y }; + ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill ); } + SetConsoleCursorPosition( consoleOut, coord ); #endif } @@ -653,6 +696,18 @@ void Terminal::jump_cursor( int xPos_, int yOffset_ ) { #endif } +#ifdef _WIN32 +void Terminal::set_cursor_visible( bool visible_ ) { + CONSOLE_CURSOR_INFO cursorInfo; + GetConsoleCursorInfo( _consoleOut, &cursorInfo ); + cursorInfo.bVisible = visible_; + SetConsoleCursorInfo( _consoleOut, &cursorInfo ); + return; +} +#else +void Terminal::set_cursor_visible( bool ) {} +#endif + #ifndef _WIN32 int Terminal::read_verbatim( char32_t* buffer_, int size_ ) { int len( 0 ); @@ -669,6 +724,18 @@ int Terminal::read_verbatim( char32_t* buffer_, int size_ ) { ::fcntl( STDIN_FILENO, F_SETFL, statusFlags ); return ( len ); } + +int Terminal::install_window_change_handler( void ) { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = &WindowSizeChanged; + + if (sigaction(SIGWINCH, &sa, nullptr) == -1) { + return errno; + } + return 0; +} #endif } diff --git a/contrib/replxx/src/io.hxx b/contrib/replxx/src/terminal.hxx similarity index 79% rename from contrib/replxx/src/io.hxx rename to contrib/replxx/src/terminal.hxx index 42d8bd5b3..e6a25786b 100644 --- a/contrib/replxx/src/io.hxx +++ b/contrib/replxx/src/terminal.hxx @@ -4,11 +4,14 @@ #include #ifdef _WIN32 +#include #include #else #include #endif +#include "utf8string.hxx" + namespace replxx { class Terminal { @@ -16,24 +19,29 @@ public: enum class EVENT_TYPE { KEY_PRESS, MESSAGE, - TIMEOUT + TIMEOUT, + RESIZE }; private: #ifdef _WIN32 HANDLE _consoleOut; HANDLE _consoleIn; - DWORD _oldMode; + DWORD _origOutMode; + DWORD _origInMode; + bool _autoEscape; WORD _oldDisplayAttribute; UINT const _inputCodePage; UINT const _outputCodePage; HANDLE _interrupt; typedef std::deque events_t; events_t _events; + std::vector _empty; #else struct termios _origTermios; /* in order to restore at exit */ int _interrupt[2]; #endif bool _rawMode; /* for destructor to check if restore is needed */ + Utf8String _utf8; public: enum class CLEAR_SCREEN { WHOLE, @@ -46,6 +54,8 @@ public: void write8( char const*, int ); int get_screen_columns(void); int get_screen_rows(void); + void enable_bracketed_paste( void ); + void disable_bracketed_paste( void ); int enable_raw_mode(void); void disable_raw_mode(void); char32_t read_char(void); @@ -53,9 +63,14 @@ public: EVENT_TYPE wait_for_input( int long = 0 ); void notify_event( EVENT_TYPE ); void jump_cursor( int, int ); + void set_cursor_visible( bool ); #ifndef _WIN32 int read_verbatim( char32_t*, int ); + int install_window_change_handler( void ); #endif +private: + void enable_out( void ); + void disable_out( void ); private: Terminal( Terminal const& ) = delete; Terminal& operator = ( Terminal const& ) = delete; -- 2.39.5