summaryrefslogtreecommitdiffstats
path: root/contrib/replxx/src/terminal.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--contrib/replxx/src/terminal.cxx742
1 files changed, 742 insertions, 0 deletions
diff --git a/contrib/replxx/src/terminal.cxx b/contrib/replxx/src/terminal.cxx
new file mode 100644
index 0000000..e618219
--- /dev/null
+++ b/contrib/replxx/src/terminal.cxx
@@ -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
+
+}
+