| 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 |
| 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 | |
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
)
#include <string>
#include <cstring>
#include <cctype>
-#include <locale.h>
+#include <clocale>
+#include "unicode/utf8.h"
#include "conversion.hxx"
#ifdef _WIN32
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 ) {
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 {
dst[i] = 0;
}
}
- return ( resCount );
+
+ return resCount;
}
}
+++ /dev/null
-#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
-
-}
-
+++ /dev/null
-#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
-
--- /dev/null
+#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
+
+}
+
--- /dev/null
+#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
+