]> source.dussan.org Git - rspamd.git/commitdiff
[Minor] Rework replxx to make in compatible with Rspamd again
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 24 Aug 2021 14:53:32 +0000 (15:53 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 24 Aug 2021 14:53:32 +0000 (15:53 +0100)
contrib/DEPENDENCY_INFO.md
contrib/replxx/CMakeLists.txt
contrib/replxx/src/conversion.cxx
contrib/replxx/src/io.cxx [deleted file]
contrib/replxx/src/io.hxx [deleted file]
contrib/replxx/src/terminal.cxx [new file with mode: 0644]
contrib/replxx/src/terminal.hxx [new file with mode: 0644]

index 41c53aadca1171165246b7fccf2d6a02e4037f56..8b3b898289db7b3ee13c8ca08006b5f0bb6dca8e 100644 (file)
@@ -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      |                    |
index 749e61208328b8ad31c75584064ca4ce8f2614d0..da6e8d86511dd45db99dec569e6423910c339028 100644 (file)
@@ -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
 )
 
index bcdbe048ec878f130fd72a35268306a24caca9ff..f629f910e4f5a68177ca7253f7bb7c55afb2c000 100644 (file)
@@ -2,8 +2,9 @@
 #include <string>
 #include <cstring>
 #include <cctype>
-#include <locale.h>
+#include <clocale>
 
+#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<const UTF8*>(src);
-               const UTF8* sourceEnd = sourceStart + strlen(src);
-               UTF32* targetStart = reinterpret_cast<UTF32*>(dst);
-               UTF32* targetEnd = targetStart + dstSize;
-
-               res = ConvertUTF8toUTF32(
-                               &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion);
+               auto sourceStart = reinterpret_cast<const unsigned char*>(src);
+               auto slen = strlen(src);
+               auto targetStart = reinterpret_cast<UChar32*>(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<int>( targetStart - reinterpret_cast<UTF32*>( 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<const char*>(src)
+                       dst, dstSize, dstCount, reinterpret_cast<const char*>(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<const UTF32*>(src);
-               const UTF32* sourceEnd = sourceStart + srcSize;
-               UTF8* targetStart = reinterpret_cast<UTF8*>(dst);
-               UTF8* targetEnd = targetStart + dstSize;
-
-               ConversionResult res = ConvertUTF32toUTF8(
-                       &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion
-               );
-
-               if ( res == conversionOK ) {
-                       resCount = static_cast<int>( targetStart - reinterpret_cast<UTF8*>( 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/io.cxx
deleted file mode 100644 (file)
index 8df176d..0000000
+++ /dev/null
@@ -1,675 +0,0 @@
-#include <memory>
-#include <cerrno>
-#include <cstdlib>
-#include <cstring>
-#include <array>
-#include <stdexcept>
-
-#ifdef _WIN32
-
-#include <conio.h>
-#include <windows.h>
-#include <io.h>
-#define isatty _isatty
-#define strcasecmp _stricmp
-#define strdup _strdup
-#define write _write
-#define STDIN_FILENO 0
-
-#include "windows.hxx"
-
-#else /* _WIN32 */
-
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <sys/select.h>
-#include <fcntl.h>
-
-#endif /* _WIN32 */
-
-#include "io.hxx"
-#include "conversion.hxx"
-#include "escape.hxx"
-#include "replxx.hxx"
-#include "util.hxx"
-
-using namespace std;
-
-namespace replxx {
-
-namespace tty {
-
-bool is_a_tty( int fd_ ) {
-       bool aTTY( isatty( fd_ ) != 0 );
-#ifdef _WIN32
-       do {
-               if ( aTTY ) {
-                       break;
-               }
-               HANDLE h( (HANDLE)_get_osfhandle( fd_ ) );
-               if ( h == INVALID_HANDLE_VALUE ) {
-                       break;
-               }
-               DWORD st( 0 );
-               if ( ! GetConsoleMode( h, &st ) ) {
-                       break;
-               }
-               aTTY = true;
-       } while ( false );
-#endif
-       return ( aTTY );
-}
-
-bool in( is_a_tty( 0 ) );
-bool out( is_a_tty( 1 ) );
-
-}
-
-Terminal::Terminal( void )
-#ifdef _WIN32
-       : _consoleOut( INVALID_HANDLE_VALUE )
-       , _consoleIn( INVALID_HANDLE_VALUE )
-       , _oldMode()
-       , _oldDisplayAttribute()
-       , _inputCodePage( GetConsoleCP() )
-       , _outputCodePage( GetConsoleOutputCP() )
-       , _interrupt( INVALID_HANDLE_VALUE )
-       , _events()
-#else
-       : _origTermios()
-       , _interrupt()
-#endif
-       , _rawMode( false ) {
-#ifdef _WIN32
-       _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) );
-#else
-       static_cast<void>( ::pipe( _interrupt ) == 0 );
-#endif
-}
-
-Terminal::~Terminal( void ) {
-       if ( _rawMode ) {
-               disable_raw_mode();
-       }
-#ifdef _WIN32
-       CloseHandle( _interrupt );
-#else
-       static_cast<void>( ::close( _interrupt[0] ) == 0 );
-       static_cast<void>( ::close( _interrupt[1] ) == 0 );
-#endif
-}
-
-void Terminal::write32( char32_t const* text32, int len32 ) {
-       int len8 = 4 * len32 + 1;
-       unique_ptr<char[]> 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" );
-       }
-       return;
-}
-
-void Terminal::write8( char const* data_, int size_ ) {
-#ifdef _WIN32
-       int nWritten( win_write( data_, size_ ) );
-#else
-       int nWritten( write( 1, data_, size_ ) );
-#endif
-       if ( nWritten != size_ ) {
-               throw std::runtime_error( "write failed" );
-       }
-       return;
-}
-
-int Terminal::get_screen_columns( void ) {
-       int cols( 0 );
-#ifdef _WIN32
-       CONSOLE_SCREEN_BUFFER_INFO inf;
-       GetConsoleScreenBufferInfo( _consoleOut, &inf );
-       cols = inf.dwSize.X;
-#else
-       struct winsize ws;
-       cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col;
-#endif
-       // cols is 0 in certain circumstances like inside debugger, which creates
-       // further issues
-       return ( cols > 0 ) ? cols : 80;
-}
-
-int Terminal::get_screen_rows( void ) {
-       int rows;
-#ifdef _WIN32
-       CONSOLE_SCREEN_BUFFER_INFO inf;
-       GetConsoleScreenBufferInfo( _consoleOut, &inf );
-       rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top;
-#else
-       struct winsize ws;
-       rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row;
-#endif
-       return (rows > 0) ? rows : 24;
-}
-
-namespace {
-inline int notty( void ) {
-       errno = ENOTTY;
-       return ( -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 );
-               SetConsoleMode(
-                       _consoleIn,
-                       _oldMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT )
-               );
-#else
-               struct termios raw;
-
-               if ( ! tty::in ) {
-                       return ( notty() );
-               }
-               if ( tcgetattr( 0, &_origTermios ) == -1 ) {
-                       return ( notty() );
-               }
-
-               raw = _origTermios; /* modify the original mode */
-               /* input modes: no break, no CR to NL, no parity check, no strip char,
-                * no start/stop output control. */
-               raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
-               /* output modes - disable post processing */
-               // this is wrong, we don't want raw output, it turns newlines into straight
-               // linefeeds
-               // raw.c_oflag &= ~(OPOST);
-               /* control modes - set 8 bit chars */
-               raw.c_cflag |= (CS8);
-               /* local modes - echoing off, canonical off, no extended functions,
-                * no signal chars (^Z,^C) */
-               raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
-               /* control chars - set return condition: min number of bytes and timer.
-                * We want read to return every single byte, without timeout. */
-               raw.c_cc[VMIN] = 1;
-               raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
-
-               /* put terminal in raw mode after flushing */
-               if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) {
-                       return ( notty() );
-               }
-#endif
-               _rawMode = true;
-       }
-       return 0;
-}
-
-void Terminal::disable_raw_mode(void) {
-       if ( _rawMode ) {
-#ifdef _WIN32
-               SetConsoleMode( _consoleIn, _oldMode );
-               SetConsoleCP( _inputCodePage );
-               SetConsoleOutputCP( _outputCodePage );
-               _consoleIn = INVALID_HANDLE_VALUE;
-               _consoleOut = INVALID_HANDLE_VALUE;
-#else
-               if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) {
-                       return;
-               }
-#endif
-               _rawMode = false;
-       }
-}
-
-#ifndef _WIN32
-
-/**
- * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode
- * (char32_t) character it encodes
- *
- * @return char32_t Unicode character
- */
-char32_t read_unicode_character(void) {
-       static char8_t utf8String[5];
-       static size_t utf8Count = 0;
-       while (true) {
-               char8_t c;
-
-               /* Continue reading if interrupted by signal. */
-               ssize_t nread;
-               do {
-                       nread = read( STDIN_FILENO, &c, 1 );
-               } while ((nread == -1) && (errno == EINTR));
-
-               if (nread <= 0) return 0;
-               if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII
-                       utf8Count = 0;
-                       return c;
-               } else if (utf8Count < sizeof(utf8String) - 1) {
-                       utf8String[utf8Count++] = c;
-                       utf8String[utf8Count] = 0;
-                       char32_t unicodeChar[2];
-                       int ucharCount( 0 );
-                       ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String);
-                       if (res == conversionOK && ucharCount) {
-                               utf8Count = 0;
-                               return unicodeChar[0];
-                       }
-               } else {
-                       utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
-               }
-       }
-}
-
-#endif // #ifndef _WIN32
-
-void beep() {
-       fprintf(stderr, "\x7"); // ctrl-G == bell/beep
-       fflush(stderr);
-}
-
-// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it
-// into an encoded "keystroke".        When convenient, extended keys are translated into their
-// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B.
-//
-// A return value of zero means "no input available", and a return value of -1
-// means "invalid key".
-//
-char32_t Terminal::read_char( void ) {
-       char32_t c( 0 );
-#ifdef _WIN32
-       INPUT_RECORD rec;
-       DWORD count;
-       char32_t modifierKeys = 0;
-       bool escSeen = false;
-       int highSurrogate( 0 );
-       while (true) {
-               ReadConsoleInputW( _consoleIn, &rec, 1, &count );
-#if __REPLXX_DEBUG__   // helper for debugging keystrokes, display info in the debug "Output"
-               // window in the debugger
-               {
-                       if ( rec.EventType == KEY_EVENT ) {
-                               //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) {
-                               char buf[1024];
-                               sprintf(
-                                       buf,
-                                       "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, "
-                                       "virtual scancode 0x%04X, key %s%s%s%s%s\n",
-                                       rec.Event.KeyEvent.uChar.UnicodeChar,
-                                       rec.Event.KeyEvent.wRepeatCount,
-                                       rec.Event.KeyEvent.wVirtualKeyCode,
-                                       rec.Event.KeyEvent.wVirtualScanCode,
-                                       rec.Event.KeyEvent.bKeyDown ? "down" : "up",
-                                       (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "",
-                                       (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "",
-                                       (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "",
-                                       (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : ""
-                               );
-                               OutputDebugStringA( buf );
-                               //}
-                       }
-               }
-#endif
-               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) ...
-               // accept these characters, otherwise only process characters on "key down"
-               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
-               // 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);
-               }
-               if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) {
-                       modifierKeys |= Replxx::KEY::BASE_CONTROL;
-               }
-               if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) {
-                       modifierKeys |= Replxx::KEY::BASE_META;
-               }
-               if (escSeen) {
-                       modifierKeys |= Replxx::KEY::BASE_META;
-               }
-               int key( rec.Event.KeyEvent.uChar.UnicodeChar );
-               if ( key == 0 ) {
-                       switch (rec.Event.KeyEvent.wVirtualKeyCode) {
-                               case VK_LEFT:
-                                       return modifierKeys | Replxx::KEY::LEFT;
-                               case VK_RIGHT:
-                                       return modifierKeys | Replxx::KEY::RIGHT;
-                               case VK_UP:
-                                       return modifierKeys | Replxx::KEY::UP;
-                               case VK_DOWN:
-                                       return modifierKeys | Replxx::KEY::DOWN;
-                               case VK_DELETE:
-                                       return modifierKeys | Replxx::KEY::DELETE;
-                               case VK_HOME:
-                                       return modifierKeys | Replxx::KEY::HOME;
-                               case VK_END:
-                                       return modifierKeys | Replxx::KEY::END;
-                               case VK_PRIOR:
-                                       return modifierKeys | Replxx::KEY::PAGE_UP;
-                               case VK_NEXT:
-                                       return modifierKeys | Replxx::KEY::PAGE_DOWN;
-                               case VK_F1:
-                                       return modifierKeys | Replxx::KEY::F1;
-                               case VK_F2:
-                                       return modifierKeys | Replxx::KEY::F2;
-                               case VK_F3:
-                                       return modifierKeys | Replxx::KEY::F3;
-                               case VK_F4:
-                                       return modifierKeys | Replxx::KEY::F4;
-                               case VK_F5:
-                                       return modifierKeys | Replxx::KEY::F5;
-                               case VK_F6:
-                                       return modifierKeys | Replxx::KEY::F6;
-                               case VK_F7:
-                                       return modifierKeys | Replxx::KEY::F7;
-                               case VK_F8:
-                                       return modifierKeys | Replxx::KEY::F8;
-                               case VK_F9:
-                                       return modifierKeys | Replxx::KEY::F9;
-                               case VK_F10:
-                                       return modifierKeys | Replxx::KEY::F10;
-                               case VK_F11:
-                                       return modifierKeys | Replxx::KEY::F11;
-                               case VK_F12:
-                                       return modifierKeys | Replxx::KEY::F12;
-                               default:
-                                       continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
-                       }
-               } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later
-                       escSeen = true;
-                       continue;
-               } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) {
-                       highSurrogate = key - 0xD800;
-                       continue;
-               } else {
-                       // we got a real character, return it
-                       if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) {
-                               key -= 0xDC00;
-                               key |= ( highSurrogate << 10 );
-                               key += 0x10000;
-                       }
-                       if ( is_control_code( key ) ) {
-                               key += 0x40;
-                               modifierKeys |= Replxx::KEY::BASE_CONTROL;
-                       }
-                       key |= modifierKeys;
-                       highSurrogate = 0;
-                       c = key;
-                       break;
-               }
-       }
-
-#else
-       c = read_unicode_character();
-       if (c == 0) {
-               return 0;
-       }
-
-// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard
-// debugging mode
-// where we print out decimal and decoded values for whatever the "terminal"
-// program
-// gives us on different keystrokes.   Hit ctrl-C to exit this mode.
-//
-#ifdef __REPLXX_DEBUG__
-       if (c == ctrlChar('^')) {       // ctrl-^, special debug mode, prints all keys hit,
-                                                                                                                // ctrl-C to get out
-               printf(
-                               "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit "
-                               "this mode\n");
-               while (true) {
-                       unsigned char keys[10];
-                       int ret = read(0, keys, 10);
-
-                       if (ret <= 0) {
-                               printf("\nret: %d\n", ret);
-                       }
-                       for (int i = 0; i < ret; ++i) {
-                               char32_t key = static_cast<char32_t>(keys[i]);
-                               char* friendlyTextPtr;
-                               char friendlyTextBuf[10];
-                               const char* prefixText = (key < 0x80) ? "" : "0x80+";
-                               char32_t keyCopy = (key < 0x80) ? key : key - 0x80;
-                               if (keyCopy >= '!' && keyCopy <= '~') { // printable
-                                       friendlyTextBuf[0] = '\'';
-                                       friendlyTextBuf[1] = keyCopy;
-                                       friendlyTextBuf[2] = '\'';
-                                       friendlyTextBuf[3] = 0;
-                                       friendlyTextPtr = friendlyTextBuf;
-                               } else if (keyCopy == ' ') {
-                                       friendlyTextPtr = const_cast<char*>("space");
-                               } else if (keyCopy == 27) {
-                                       friendlyTextPtr = const_cast<char*>("ESC");
-                               } else if (keyCopy == 0) {
-                                       friendlyTextPtr = const_cast<char*>("NUL");
-                               } else if (keyCopy == 127) {
-                                       friendlyTextPtr = const_cast<char*>("DEL");
-                               } else {
-                                       friendlyTextBuf[0] = '^';
-                                       friendlyTextBuf[1] = keyCopy + 0x40;
-                                       friendlyTextBuf[2] = 0;
-                                       friendlyTextPtr = friendlyTextBuf;
-                               }
-                               printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr);
-                       }
-                       printf("\x1b[1G\n");    // go to first column of new line
-
-                       // drop out of this loop on ctrl-C
-                       if (keys[0] == ctrlChar('C')) {
-                               printf("Leaving keyboard debugging mode (on ctrl-C)\n");
-                               fflush(stdout);
-                               return -2;
-                       }
-               }
-       }
-#endif // __REPLXX_DEBUG__
-
-       c = EscapeSequenceProcessing::doDispatch(c);
-       if ( is_control_code( c ) ) {
-               c = Replxx::KEY::control( c + 0x40 );
-       }
-#endif // #_WIN32
-       return ( c );
-}
-
-Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) {
-#ifdef _WIN32
-       std::array<HANDLE,2> handles = { _consoleIn, _interrupt };
-       while ( true ) {
-               DWORD event( WaitForMultipleObjects( handles.size (), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) );
-               switch ( event ) {
-                       case ( WAIT_OBJECT_0 + 0 ): {
-                               // peek events that will be skipped
-                               INPUT_RECORD rec;
-                               DWORD count;
-                               PeekConsoleInputW( _consoleIn, &rec, 1, &count );
-
-                               if (
-                                       ( rec.EventType != KEY_EVENT )
-                                       || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) )
-                               ) {
-                                       // read the event to unsignal the handle
-                                       ReadConsoleInputW( _consoleIn, &rec, 1, &count );
-                                       continue;
-                               } else if (rec.EventType == KEY_EVENT) {
-                                       int key(rec.Event.KeyEvent.uChar.UnicodeChar);
-                                       if (key == 0) {
-                                               switch (rec.Event.KeyEvent.wVirtualKeyCode) {
-                                               case VK_LEFT:
-                                               case VK_RIGHT:
-                                               case VK_UP:
-                                               case VK_DOWN:
-                                               case VK_DELETE:
-                                               case VK_HOME:
-                                               case VK_END:
-                                               case VK_PRIOR:
-                                               case VK_NEXT:
-                                                       break;
-                                               default:
-                                                       ReadConsoleInputW(_consoleIn, &rec, 1, &count);
-                                                       continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
-                                               }
-                                       }
-                               }
-
-                               return ( EVENT_TYPE::KEY_PRESS );
-                       }
-                       case ( WAIT_OBJECT_0 + 1 ): {
-                               ResetEvent( _interrupt );
-                               if ( _events.empty() ) {
-                                       continue;
-                               }
-                               EVENT_TYPE eventType( _events.front() );
-                               _events.pop_front();
-                               return ( eventType );
-                       }
-                       case ( WAIT_TIMEOUT ): {
-                               return ( EVENT_TYPE::TIMEOUT );
-                       }
-               }
-       }
-#else
-       fd_set fdSet;
-       int nfds( max( _interrupt[0], _interrupt[1] ) + 1 );
-       while ( true ) {
-               FD_ZERO( &fdSet );
-               FD_SET( 0, &fdSet );
-               FD_SET( _interrupt[0], &fdSet );
-               timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) };
-               int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) );
-               if ( ( err == -1 ) && ( errno == EINTR ) ) {
-                       continue;
-               }
-               if ( err == 0 ) {
-                       return ( EVENT_TYPE::TIMEOUT );
-               }
-               if ( FD_ISSET( _interrupt[0], &fdSet ) ) {
-                       char data( 0 );
-                       static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 );
-                       if ( data == 'k' ) {
-                               return ( EVENT_TYPE::KEY_PRESS );
-                       }
-                       if ( data == 'm' ) {
-                               return ( EVENT_TYPE::MESSAGE );
-                       }
-               }
-               if ( FD_ISSET( 0, &fdSet ) ) {
-                       return ( EVENT_TYPE::KEY_PRESS );
-               }
-       }
-#endif
-}
-
-void Terminal::notify_event( EVENT_TYPE eventType_ ) {
-#ifdef _WIN32
-       _events.push_back( eventType_ );
-       SetEvent( _interrupt );
-#else
-       char data( eventType_ == EVENT_TYPE::KEY_PRESS ? 'k' : 'm' );
-       static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 );
-#endif
-}
-
-/**
- * Clear the screen ONLY (no redisplay of anything)
- */
-void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) {
-#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 {
-               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<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
-       } else {
-               char const clearCode[] = "\033[J";
-               static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
-       }
-#endif
-}
-
-void Terminal::jump_cursor( int xPos_, int yOffset_ ) {
-#ifdef _WIN32
-       CONSOLE_SCREEN_BUFFER_INFO inf;
-       GetConsoleScreenBufferInfo( _consoleOut, &inf );
-       inf.dwCursorPosition.X = xPos_;
-       inf.dwCursorPosition.Y += yOffset_;
-       SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition );
-#else
-       char seq[64];
-       if ( yOffset_ != 0 ) { // move the cursor up as required
-               snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' );
-               write8( seq, strlen( seq ) );
-       }
-       // position at the end of the prompt, clear to end of screen
-       snprintf(
-               seq, sizeof seq, "\033[%dG",
-               xPos_ + 1 /* 1-based on VT100 */
-       );
-       write8( seq, strlen( seq ) );
-#endif
-}
-
-#ifndef _WIN32
-int Terminal::read_verbatim( char32_t* buffer_, int size_ ) {
-       int len( 0 );
-       buffer_[len ++] = read_unicode_character();
-       int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) );
-       ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK );
-       while ( len < size_ ) {
-               char32_t c( read_unicode_character() );
-               if ( c == 0 ) {
-                       break;
-               }
-               buffer_[len ++] = c;
-       }
-       ::fcntl( STDIN_FILENO, F_SETFL, statusFlags );
-       return ( len );
-}
-#endif
-
-}
-
diff --git a/contrib/replxx/src/io.hxx b/contrib/replxx/src/io.hxx
deleted file mode 100644 (file)
index 42d8bd5..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef REPLXX_IO_HXX_INCLUDED
-#define REPLXX_IO_HXX_INCLUDED 1
-
-#include <deque>
-
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <termios.h>
-#endif
-
-namespace replxx {
-
-class Terminal {
-public:
-       enum class EVENT_TYPE {
-               KEY_PRESS,
-               MESSAGE,
-               TIMEOUT
-       };
-private:
-#ifdef _WIN32
-       HANDLE _consoleOut;
-       HANDLE _consoleIn;
-       DWORD _oldMode;
-       WORD _oldDisplayAttribute;
-       UINT const _inputCodePage;
-       UINT const _outputCodePage;
-       HANDLE _interrupt;
-       typedef std::deque<EVENT_TYPE> events_t;
-       events_t _events;
-#else
-       struct termios _origTermios; /* in order to restore at exit */
-       int _interrupt[2];
-#endif
-       bool _rawMode; /* for destructor to check if restore is needed */
-public:
-       enum class CLEAR_SCREEN {
-               WHOLE,
-               TO_END
-       };
-public:
-       Terminal( void );
-       ~Terminal( void );
-       void write32( char32_t const*, int );
-       void write8( char const*, int );
-       int get_screen_columns(void);
-       int get_screen_rows(void);
-       int enable_raw_mode(void);
-       void disable_raw_mode(void);
-       char32_t read_char(void);
-       void clear_screen( CLEAR_SCREEN );
-       EVENT_TYPE wait_for_input( int long = 0 );
-       void notify_event( EVENT_TYPE );
-       void jump_cursor( int, int );
-#ifndef _WIN32
-       int read_verbatim( char32_t*, int );
-#endif
-private:
-       Terminal( Terminal const& ) = delete;
-       Terminal& operator = ( Terminal const& ) = delete;
-       Terminal( Terminal&& ) = delete;
-       Terminal& operator = ( Terminal&& ) = delete;
-};
-
-void beep();
-char32_t read_unicode_character(void);
-
-namespace tty {
-
-extern bool in;
-extern bool out;
-
-}
-
-}
-
-#endif
-
diff --git a/contrib/replxx/src/terminal.cxx b/contrib/replxx/src/terminal.cxx
new file mode 100644 (file)
index 0000000..e618219
--- /dev/null
@@ -0,0 +1,742 @@
+#include <memory>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <array>
+#include <stdexcept>
+
+#ifdef _WIN32
+
+#include <conio.h>
+#include <windows.h>
+#include <io.h>
+#define isatty _isatty
+#define strcasecmp _stricmp
+#define strdup _strdup
+#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 */
+
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#endif /* _WIN32 */
+
+#include "terminal.hxx"
+#include "conversion.hxx"
+#include "escape.hxx"
+#include "replxx.hxx"
+#include "util.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+namespace tty {
+
+bool is_a_tty( int fd_ ) {
+       bool aTTY( isatty( fd_ ) != 0 );
+#ifdef _WIN32
+       do {
+               if ( aTTY ) {
+                       break;
+               }
+               HANDLE h( (HANDLE)_get_osfhandle( fd_ ) );
+               if ( h == INVALID_HANDLE_VALUE ) {
+                       break;
+               }
+               DWORD st( 0 );
+               if ( ! GetConsoleMode( h, &st ) ) {
+                       break;
+               }
+               aTTY = true;
+       } while ( false );
+#endif
+       return ( aTTY );
+}
+
+bool in( is_a_tty( 0 ) );
+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 )
+       , _origOutMode()
+       , _origInMode()
+       , _oldDisplayAttribute()
+       , _inputCodePage( GetConsoleCP() )
+       , _outputCodePage( GetConsoleOutputCP() )
+       , _interrupt( INVALID_HANDLE_VALUE )
+       , _events()
+       , _empty()
+#else
+       : _origTermios()
+       , _interrupt()
+#endif
+       , _rawMode( false )
+       , _utf8() {
+#ifdef _WIN32
+       _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) );
+#else
+       static_cast<void>( ::pipe( _interrupt ) == 0 );
+#endif
+}
+
+Terminal::~Terminal( void ) {
+       if ( _rawMode ) {
+               disable_raw_mode();
+       }
+#ifdef _WIN32
+       CloseHandle( _interrupt );
+#else
+       static_cast<void>( ::close( _interrupt[0] ) == 0 );
+       static_cast<void>( ::close( _interrupt[1] ) == 0 );
+#endif
+}
+
+void Terminal::write32( char32_t const* text32, int len32 ) {
+       _utf8.assign( text32, len32 );
+       write8( _utf8.get(), _utf8.size() );
+       return;
+}
+
+void Terminal::write8( char const* data_, int size_ ) {
+#ifdef _WIN32
+       if ( ! _rawMode ) {
+               enable_out();
+       }
+       int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) );
+       if ( ! _rawMode ) {
+               disable_out();
+       }
+#else
+       int nWritten( write( 1, data_, size_ ) );
+#endif
+       if ( nWritten != size_ ) {
+               throw std::runtime_error( "write failed" );
+       }
+       return;
+}
+
+int Terminal::get_screen_columns( void ) {
+       int cols( 0 );
+#ifdef _WIN32
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       GetConsoleScreenBufferInfo( _consoleOut, &inf );
+       cols = inf.dwSize.X;
+#else
+       struct winsize ws;
+       cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col;
+#endif
+       // cols is 0 in certain circumstances like inside debugger, which creates
+       // further issues
+       return ( cols > 0 ) ? cols : 80;
+}
+
+int Terminal::get_screen_rows( void ) {
+       int rows;
+#ifdef _WIN32
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       GetConsoleScreenBufferInfo( _consoleOut, &inf );
+       rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top;
+#else
+       struct winsize ws;
+       rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row;
+#endif
+       return (rows > 0) ? rows : 24;
+}
+
+namespace {
+inline int notty( void ) {
+       errno = ENOTTY;
+       return ( -1 );
+}
+}
+
+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 );
+               SetConsoleCP( 65001 );
+               GetConsoleMode( _consoleIn, &_origInMode );
+               SetConsoleMode(
+                       _consoleIn,
+                       _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT )
+               );
+               enable_out();
+#else
+               struct termios raw;
+
+               if ( ! tty::in ) {
+                       return ( notty() );
+               }
+               if ( tcgetattr( 0, &_origTermios ) == -1 ) {
+                       return ( notty() );
+               }
+
+               raw = _origTermios; /* modify the original mode */
+               /* input modes: no break, no CR to NL, no parity check, no strip char,
+                * no start/stop output control. */
+               raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+               /* output modes - disable post processing */
+               // this is wrong, we don't want raw output, it turns newlines into straight
+               // linefeeds
+               // raw.c_oflag &= ~(OPOST);
+               /* control modes - set 8 bit chars */
+               raw.c_cflag |= (CS8);
+               /* local modes - echoing off, canonical off, no extended functions,
+                * no signal chars (^Z,^C) */
+               raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+               /* control chars - set return condition: min number of bytes and timer.
+                * We want read to return every single byte, without timeout. */
+               raw.c_cc[VMIN] = 1;
+               raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+               /* put terminal in raw mode after flushing */
+               if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) {
+                       return ( notty() );
+               }
+               _terminal_ = this;
+#endif
+               _rawMode = true;
+       }
+       return ( 0 );
+}
+
+void Terminal::disable_raw_mode(void) {
+       if ( _rawMode ) {
+#ifdef _WIN32
+               disable_out();
+               SetConsoleMode( _consoleIn, _origInMode );
+               SetConsoleCP( _inputCodePage );
+               _consoleIn = INVALID_HANDLE_VALUE;
+#else
+               _terminal_ = nullptr;
+               if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) {
+                       return;
+               }
+#endif
+               _rawMode = false;
+       }
+}
+
+#ifndef _WIN32
+
+/**
+ * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode
+ * (char32_t) character it encodes
+ *
+ * @return char32_t Unicode character
+ */
+char32_t read_unicode_character(void) {
+       static char8_t utf8String[5];
+       static size_t utf8Count = 0;
+       while (true) {
+               char8_t c;
+
+               /* Continue reading if interrupted by signal. */
+               ssize_t nread;
+               do {
+                       nread = read( STDIN_FILENO, &c, 1 );
+               } while ((nread == -1) && (errno == EINTR));
+
+               if (nread <= 0) return 0;
+               if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII
+                       utf8Count = 0;
+                       return c;
+               } else if (utf8Count < sizeof(utf8String) - 1) {
+                       utf8String[utf8Count++] = c;
+                       utf8String[utf8Count] = 0;
+                       char32_t unicodeChar[2];
+                       int ucharCount( 0 );
+                       ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String);
+                       if (res == conversionOK && ucharCount) {
+                               utf8Count = 0;
+                               return unicodeChar[0];
+                       }
+               } else {
+                       utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
+               }
+       }
+}
+
+#endif // #ifndef _WIN32
+
+void beep() {
+       fprintf(stderr, "\x7"); // ctrl-G == bell/beep
+       fflush(stderr);
+}
+
+// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it
+// into an encoded "keystroke".        When convenient, extended keys are translated into their
+// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B.
+//
+// A return value of zero means "no input available", and a return value of -1
+// means "invalid key".
+//
+char32_t Terminal::read_char( void ) {
+       char32_t c( 0 );
+#ifdef _WIN32
+       INPUT_RECORD rec;
+       DWORD count;
+       char32_t modifierKeys = 0;
+       bool escSeen = false;
+       int highSurrogate( 0 );
+       while (true) {
+               ReadConsoleInputW( _consoleIn, &rec, 1, &count );
+#if __REPLXX_DEBUG__   // helper for debugging keystrokes, display info in the debug "Output"
+               // window in the debugger
+               {
+                       if ( rec.EventType == KEY_EVENT ) {
+                               //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) {
+                               char buf[1024];
+                               sprintf(
+                                       buf,
+                                       "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, "
+                                       "virtual scancode 0x%04X, key %s%s%s%s%s\n",
+                                       rec.Event.KeyEvent.uChar.UnicodeChar,
+                                       rec.Event.KeyEvent.wRepeatCount,
+                                       rec.Event.KeyEvent.wVirtualKeyCode,
+                                       rec.Event.KeyEvent.wVirtualScanCode,
+                                       rec.Event.KeyEvent.bKeyDown ? "down" : "up",
+                                       (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "",
+                                       (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "",
+                                       (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "",
+                                       (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : ""
+                               );
+                               OutputDebugStringA( buf );
+                               //}
+                       }
+               }
+#endif
+               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) ...
+               // accept these characters, otherwise only process characters on "key down"
+               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
+               // left-Alt
+               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;
+               }
+               if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) {
+                       modifierKeys |= Replxx::KEY::BASE_META;
+               }
+               if ( escSeen ) {
+                       modifierKeys |= Replxx::KEY::BASE_META;
+               }
+               int key( rec.Event.KeyEvent.uChar.UnicodeChar );
+               if ( key == 0 ) {
+                       switch (rec.Event.KeyEvent.wVirtualKeyCode) {
+                               case VK_LEFT:
+                                       return modifierKeys | Replxx::KEY::LEFT;
+                               case VK_RIGHT:
+                                       return modifierKeys | Replxx::KEY::RIGHT;
+                               case VK_UP:
+                                       return modifierKeys | Replxx::KEY::UP;
+                               case VK_DOWN:
+                                       return modifierKeys | Replxx::KEY::DOWN;
+                               case VK_DELETE:
+                                       return modifierKeys | Replxx::KEY::DELETE;
+                               case VK_HOME:
+                                       return modifierKeys | Replxx::KEY::HOME;
+                               case VK_END:
+                                       return modifierKeys | Replxx::KEY::END;
+                               case VK_PRIOR:
+                                       return modifierKeys | Replxx::KEY::PAGE_UP;
+                               case VK_NEXT:
+                                       return modifierKeys | Replxx::KEY::PAGE_DOWN;
+                               case VK_F1:
+                                       return modifierKeys | Replxx::KEY::F1;
+                               case VK_F2:
+                                       return modifierKeys | Replxx::KEY::F2;
+                               case VK_F3:
+                                       return modifierKeys | Replxx::KEY::F3;
+                               case VK_F4:
+                                       return modifierKeys | Replxx::KEY::F4;
+                               case VK_F5:
+                                       return modifierKeys | Replxx::KEY::F5;
+                               case VK_F6:
+                                       return modifierKeys | Replxx::KEY::F6;
+                               case VK_F7:
+                                       return modifierKeys | Replxx::KEY::F7;
+                               case VK_F8:
+                                       return modifierKeys | Replxx::KEY::F8;
+                               case VK_F9:
+                                       return modifierKeys | Replxx::KEY::F9;
+                               case VK_F10:
+                                       return modifierKeys | Replxx::KEY::F10;
+                               case VK_F11:
+                                       return modifierKeys | Replxx::KEY::F11;
+                               case VK_F12:
+                                       return modifierKeys | Replxx::KEY::F12;
+                               default:
+                                       continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
+                       }
+               } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later
+                       escSeen = true;
+                       continue;
+               } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) {
+                       highSurrogate = key - 0xD800;
+                       continue;
+               } else {
+                       // we got a real character, return it
+                       if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) {
+                               key -= 0xDC00;
+                               key |= ( highSurrogate << 10 );
+                               key += 0x10000;
+                       }
+                       if ( is_control_code( key ) ) {
+                               key = control_to_human( key );
+                               modifierKeys |= Replxx::KEY::BASE_CONTROL;
+                       }
+                       key |= modifierKeys;
+                       highSurrogate = 0;
+                       c = key;
+                       break;
+               }
+       }
+
+#else
+       c = read_unicode_character();
+       if (c == 0) {
+               return 0;
+       }
+
+// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard
+// debugging mode
+// where we print out decimal and decoded values for whatever the "terminal"
+// program
+// gives us on different keystrokes.   Hit ctrl-C to exit this mode.
+//
+#ifdef __REPLXX_DEBUG__
+       if (c == ctrlChar('^')) {       // ctrl-^, special debug mode, prints all keys hit,
+                                                                                                                // ctrl-C to get out
+               printf(
+                               "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit "
+                               "this mode\n");
+               while (true) {
+                       unsigned char keys[10];
+                       int ret = read(0, keys, 10);
+
+                       if (ret <= 0) {
+                               printf("\nret: %d\n", ret);
+                       }
+                       for (int i = 0; i < ret; ++i) {
+                               char32_t key = static_cast<char32_t>(keys[i]);
+                               char* friendlyTextPtr;
+                               char friendlyTextBuf[10];
+                               const char* prefixText = (key < 0x80) ? "" : "0x80+";
+                               char32_t keyCopy = (key < 0x80) ? key : key - 0x80;
+                               if (keyCopy >= '!' && keyCopy <= '~') { // printable
+                                       friendlyTextBuf[0] = '\'';
+                                       friendlyTextBuf[1] = keyCopy;
+                                       friendlyTextBuf[2] = '\'';
+                                       friendlyTextBuf[3] = 0;
+                                       friendlyTextPtr = friendlyTextBuf;
+                               } else if (keyCopy == ' ') {
+                                       friendlyTextPtr = const_cast<char*>("space");
+                               } else if (keyCopy == 27) {
+                                       friendlyTextPtr = const_cast<char*>("ESC");
+                               } else if (keyCopy == 0) {
+                                       friendlyTextPtr = const_cast<char*>("NUL");
+                               } else if (keyCopy == 127) {
+                                       friendlyTextPtr = const_cast<char*>("DEL");
+                               } else {
+                                       friendlyTextBuf[0] = '^';
+                                       friendlyTextBuf[1] = control_to_human( keyCopy );
+                                       friendlyTextBuf[2] = 0;
+                                       friendlyTextPtr = friendlyTextBuf;
+                               }
+                               printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr);
+                       }
+                       printf("\x1b[1G\n");    // go to first column of new line
+
+                       // drop out of this loop on ctrl-C
+                       if (keys[0] == ctrlChar('C')) {
+                               printf("Leaving keyboard debugging mode (on ctrl-C)\n");
+                               fflush(stdout);
+                               return -2;
+                       }
+               }
+       }
+#endif // __REPLXX_DEBUG__
+
+       c = EscapeSequenceProcessing::doDispatch(c);
+       if ( is_control_code( c ) ) {
+               c = Replxx::KEY::control( control_to_human( c ) );
+       }
+#endif // #_WIN32
+       return ( c );
+}
+
+Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) {
+#ifdef _WIN32
+       std::array<HANDLE,2> handles = { _consoleIn, _interrupt };
+       while ( true ) {
+               DWORD event( WaitForMultipleObjects( static_cast<DWORD>( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) );
+               switch ( event ) {
+                       case ( WAIT_OBJECT_0 + 0 ): {
+                               // peek events that will be skipped
+                               INPUT_RECORD rec;
+                               DWORD count;
+                               PeekConsoleInputW( _consoleIn, &rec, 1, &count );
+
+                               if (
+                                       ( rec.EventType != KEY_EVENT )
+                                       || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) )
+                               ) {
+                                       // read the event to unsignal the handle
+                                       ReadConsoleInputW( _consoleIn, &rec, 1, &count );
+                                       continue;
+                               } else if (rec.EventType == KEY_EVENT) {
+                                       int key(rec.Event.KeyEvent.uChar.UnicodeChar);
+                                       if (key == 0) {
+                                               switch (rec.Event.KeyEvent.wVirtualKeyCode) {
+                                               case VK_LEFT:
+                                               case VK_RIGHT:
+                                               case VK_UP:
+                                               case VK_DOWN:
+                                               case VK_DELETE:
+                                               case VK_HOME:
+                                               case VK_END:
+                                               case VK_PRIOR:
+                                               case VK_NEXT:
+                                                       break;
+                                               default:
+                                                       ReadConsoleInputW(_consoleIn, &rec, 1, &count);
+                                                       continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
+                                               }
+                                       }
+                               }
+
+                               return ( EVENT_TYPE::KEY_PRESS );
+                       }
+                       case ( WAIT_OBJECT_0 + 1 ): {
+                               ResetEvent( _interrupt );
+                               if ( _events.empty() ) {
+                                       continue;
+                               }
+                               EVENT_TYPE eventType( _events.front() );
+                               _events.pop_front();
+                               return ( eventType );
+                       }
+                       case ( WAIT_TIMEOUT ): {
+                               return ( EVENT_TYPE::TIMEOUT );
+                       }
+               }
+       }
+#else
+       fd_set fdSet;
+       int nfds( max( _interrupt[0], _interrupt[1] ) + 1 );
+       while ( true ) {
+               FD_ZERO( &fdSet );
+               FD_SET( 0, &fdSet );
+               FD_SET( _interrupt[0], &fdSet );
+               timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) };
+               int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) );
+               if ( ( err == -1 ) && ( errno == EINTR ) ) {
+                       continue;
+               }
+               if ( err == 0 ) {
+                       return ( EVENT_TYPE::TIMEOUT );
+               }
+               if ( FD_ISSET( _interrupt[0], &fdSet ) ) {
+                       char data( 0 );
+                       static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 );
+                       if ( data == 'k' ) {
+                               return ( EVENT_TYPE::KEY_PRESS );
+                       }
+                       if ( data == 'm' ) {
+                               return ( EVENT_TYPE::MESSAGE );
+                       }
+                       if ( data == 'r' ) {
+                               return ( EVENT_TYPE::RESIZE );
+                       }
+               }
+               if ( FD_ISSET( 0, &fdSet ) ) {
+                       return ( EVENT_TYPE::KEY_PRESS );
+               }
+       }
+#endif
+}
+
+void Terminal::notify_event( EVENT_TYPE eventType_ ) {
+#ifdef _WIN32
+       _events.push_back( eventType_ );
+       SetEvent( _interrupt );
+#else
+       char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) );
+       static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 );
+#endif
+}
+
+/**
+ * Clear the screen ONLY (no redisplay of anything)
+ */
+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<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
+               } else {
+                       char const clearCode[] = "\033[J";
+                       static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
+               }
+               return;
+#ifdef _WIN32
+       }
+       COORD coord = { 0, 0 };
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) );
+       GetConsoleScreenBufferInfo( consoleOut, &inf );
+       if ( clearScreen_ == CLEAR_SCREEN::TO_END ) {
+               coord = inf.dwCursorPosition;
+               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 {
+               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
+}
+
+void Terminal::jump_cursor( int xPos_, int yOffset_ ) {
+#ifdef _WIN32
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       GetConsoleScreenBufferInfo( _consoleOut, &inf );
+       inf.dwCursorPosition.X = xPos_;
+       inf.dwCursorPosition.Y += yOffset_;
+       SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition );
+#else
+       char seq[64];
+       if ( yOffset_ != 0 ) { // move the cursor up as required
+               snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' );
+               write8( seq, strlen( seq ) );
+       }
+       // position at the end of the prompt, clear to end of screen
+       snprintf(
+               seq, sizeof seq, "\033[%dG",
+               xPos_ + 1 /* 1-based on VT100 */
+       );
+       write8( seq, strlen( seq ) );
+#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 );
+       buffer_[len ++] = read_unicode_character();
+       int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) );
+       ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK );
+       while ( len < size_ ) {
+               char32_t c( read_unicode_character() );
+               if ( c == 0 ) {
+                       break;
+               }
+               buffer_[len ++] = c;
+       }
+       ::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/terminal.hxx b/contrib/replxx/src/terminal.hxx
new file mode 100644 (file)
index 0000000..e6a2578
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef REPLXX_IO_HXX_INCLUDED
+#define REPLXX_IO_HXX_INCLUDED 1
+
+#include <deque>
+
+#ifdef _WIN32
+#include <vector>
+#include <windows.h>
+#else
+#include <termios.h>
+#endif
+
+#include "utf8string.hxx"
+
+namespace replxx {
+
+class Terminal {
+public:
+       enum class EVENT_TYPE {
+               KEY_PRESS,
+               MESSAGE,
+               TIMEOUT,
+               RESIZE
+       };
+private:
+#ifdef _WIN32
+       HANDLE _consoleOut;
+       HANDLE _consoleIn;
+       DWORD _origOutMode;
+       DWORD _origInMode;
+       bool _autoEscape;
+       WORD _oldDisplayAttribute;
+       UINT const _inputCodePage;
+       UINT const _outputCodePage;
+       HANDLE _interrupt;
+       typedef std::deque<EVENT_TYPE> events_t;
+       events_t _events;
+       std::vector<char> _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,
+               TO_END
+       };
+public:
+       Terminal( void );
+       ~Terminal( void );
+       void write32( char32_t const*, int );
+       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);
+       void clear_screen( CLEAR_SCREEN );
+       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;
+       Terminal( Terminal&& ) = delete;
+       Terminal& operator = ( Terminal&& ) = delete;
+};
+
+void beep();
+char32_t read_unicode_character(void);
+
+namespace tty {
+
+extern bool in;
+extern bool out;
+
+}
+
+}
+
+#endif
+