summaryrefslogtreecommitdiffstats
path: root/contrib/replxx/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
commit133a45c109da5310add55824db21af5239951f93 (patch)
treeba6ac4c0a950a0dda56451944315d66409923918 /contrib/replxx/src
parentInitial commit. (diff)
downloadrspamd-upstream.tar.xz
rspamd-upstream.zip
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/replxx/src')
-rw-r--r--contrib/replxx/src/conversion.cxx134
-rw-r--r--contrib/replxx/src/conversion.hxx35
-rw-r--r--contrib/replxx/src/escape.cxx890
-rw-r--r--contrib/replxx/src/escape.hxx37
-rw-r--r--contrib/replxx/src/history.cxx402
-rw-r--r--contrib/replxx/src/history.hxx141
-rw-r--r--contrib/replxx/src/killring.hxx78
-rw-r--r--contrib/replxx/src/prompt.cxx144
-rw-r--r--contrib/replxx/src/prompt.hxx45
-rw-r--r--contrib/replxx/src/replxx.cxx648
-rw-r--r--contrib/replxx/src/replxx_impl.cxx2248
-rw-r--r--contrib/replxx/src/replxx_impl.hxx280
-rw-r--r--contrib/replxx/src/terminal.cxx742
-rw-r--r--contrib/replxx/src/terminal.hxx94
-rw-r--r--contrib/replxx/src/unicodestring.hxx201
-rw-r--r--contrib/replxx/src/utf8string.hxx94
-rw-r--r--contrib/replxx/src/util.cxx158
-rw-r--r--contrib/replxx/src/util.hxx25
-rw-r--r--contrib/replxx/src/wcwidth.cpp296
-rw-r--r--contrib/replxx/src/windows.cxx144
-rw-r--r--contrib/replxx/src/windows.hxx44
21 files changed, 6880 insertions, 0 deletions
diff --git a/contrib/replxx/src/conversion.cxx b/contrib/replxx/src/conversion.cxx
new file mode 100644
index 0000000..f629f91
--- /dev/null
+++ b/contrib/replxx/src/conversion.cxx
@@ -0,0 +1,134 @@
+#include <algorithm>
+#include <string>
+#include <cstring>
+#include <cctype>
+#include <clocale>
+
+#include "unicode/utf8.h"
+#include "conversion.hxx"
+
+#ifdef _WIN32
+#define strdup _strdup
+#endif
+
+using namespace std;
+
+namespace replxx {
+
+namespace locale {
+
+void to_lower( std::string& s_ ) {
+ transform( s_.begin(), s_.end(), s_.begin(), static_cast<int(*)(int)>( &tolower ) );
+}
+
+bool is_8bit_encoding( void ) {
+ bool is8BitEncoding( false );
+ string origLC( setlocale( LC_CTYPE, nullptr ) );
+ string lc( origLC );
+ to_lower( lc );
+ if ( lc == "c" ) {
+ setlocale( LC_CTYPE, "" );
+ }
+ lc = setlocale( LC_CTYPE, nullptr );
+ setlocale( LC_CTYPE, origLC.c_str() );
+ to_lower( lc );
+ if ( lc.find( "8859" ) != std::string::npos ) {
+ is8BitEncoding = true;
+ }
+ return ( is8BitEncoding );
+}
+
+bool is8BitEncoding( is_8bit_encoding() );
+
+}
+
+ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) {
+ ConversionResult res = ConversionResult::conversionOK;
+ if ( ! locale::is8BitEncoding ) {
+ 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;
+ }
+
+ dstCount = j;
+
+ if (j < dstSize) {
+ targetStart[j] = 0;
+ }
+ } else {
+ for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) {
+ dst[dstCount] = src[dstCount];
+ }
+ }
+ return res;
+}
+
+ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char8_t* src) {
+ return copyString8to32(
+ dst, dstSize, dstCount, reinterpret_cast<const char*>(src)
+ );
+}
+
+int copyString32to8(
+ char* dst, int dstSize, const char32_t* src, int srcSize
+) {
+ int resCount = 0;
+
+ if ( ! locale::is8BitEncoding ) {
+ 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 {
+ int i( 0 );
+ for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) {
+ dst[i] = static_cast<char>( src[i] );
+ }
+ resCount = i;
+ if ( i < dstSize ) {
+ dst[i] = 0;
+ }
+ }
+
+ return resCount;
+}
+
+}
+
diff --git a/contrib/replxx/src/conversion.hxx b/contrib/replxx/src/conversion.hxx
new file mode 100644
index 0000000..05ea64f
--- /dev/null
+++ b/contrib/replxx/src/conversion.hxx
@@ -0,0 +1,35 @@
+#ifndef REPLXX_CONVERSION_HXX_INCLUDED
+#define REPLXX_CONVERSION_HXX_INCLUDED 1
+
+#ifdef __has_include
+#if __has_include( <version> )
+#include <version>
+#endif
+#endif
+
+typedef enum {
+ conversionOK, /* conversion successful */
+ sourceExhausted, /* partial character in source, but hit end */
+ targetExhausted, /* insuff. room in target for conversion */
+ sourceIllegal /* source sequence is illegal/malformed */
+} ConversionResult;
+
+#if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) )
+namespace replxx {
+typedef unsigned char char8_t;
+}
+#endif
+
+namespace replxx {
+
+ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char const* src );
+ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char8_t const* src );
+int copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize );
+
+namespace locale {
+extern bool is8BitEncoding;
+}
+
+}
+
+#endif
diff --git a/contrib/replxx/src/escape.cxx b/contrib/replxx/src/escape.cxx
new file mode 100644
index 0000000..dda1ab0
--- /dev/null
+++ b/contrib/replxx/src/escape.cxx
@@ -0,0 +1,890 @@
+#include "escape.hxx"
+#include "terminal.hxx"
+#include "replxx.hxx"
+
+#ifndef _WIN32
+
+namespace replxx {
+
+namespace EscapeSequenceProcessing { // move these out of global namespace
+
+// This chunk of code does parsing of the escape sequences sent by various Linux
+// terminals.
+//
+// It handles arrow keys, Home, End and Delete keys by interpreting the
+// sequences sent by
+// gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and
+// Ctrl key
+// combinations that are understood by replxx.
+//
+// The parsing uses tables, a bunch of intermediate dispatch routines and a
+// doDispatch
+// loop that reads the tables and sends control to "deeper" routines to continue
+// the
+// parsing. The starting call to doDispatch( c, initialDispatch ) will
+// eventually return
+// either a character (with optional CTRL and META bits set), or -1 if parsing
+// fails, or
+// zero if an attempt to read from the keyboard fails.
+//
+// This is rather sloppy escape sequence processing, since we're not paying
+// attention to what the
+// actual TERM is set to and are processing all key sequences for all terminals,
+// but it works with
+// the most common keystrokes on the most common terminals. It's intricate, but
+// the nested 'if'
+// statements required to do it directly would be worse. This way has the
+// advantage of allowing
+// changes and extensions without having to touch a lot of code.
+
+
+static char32_t thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers
+
+// This dispatch routine is given a dispatch table and then farms work out to
+// routines
+// listed in the table based on the character it is called with. The dispatch
+// routines can
+// read more input characters to decide what should eventually be returned.
+// Eventually,
+// a called routine returns either a character or -1 to indicate parsing
+// failure.
+//
+char32_t doDispatch(char32_t c, CharacterDispatch& dispatchTable) {
+ for (unsigned int i = 0; i < dispatchTable.len; ++i) {
+ if (static_cast<unsigned char>(dispatchTable.chars[i]) == c) {
+ return dispatchTable.dispatch[i](c);
+ }
+ }
+ return dispatchTable.dispatch[dispatchTable.len](c);
+}
+
+// Final dispatch routines -- return something
+//
+static char32_t normalKeyRoutine(char32_t c) { return thisKeyMetaCtrl | c; }
+static char32_t upArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::UP;;
+}
+static char32_t downArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::DOWN;
+}
+static char32_t rightArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::RIGHT;
+}
+static char32_t leftArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::LEFT;
+}
+static char32_t homeKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::HOME; }
+static char32_t endKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::END; }
+static char32_t shiftTabRoutine(char32_t) { return Replxx::KEY::BASE_SHIFT | Replxx::KEY::TAB; }
+static char32_t f1KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F1; }
+static char32_t f2KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F2; }
+static char32_t f3KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F3; }
+static char32_t f4KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F4; }
+static char32_t f5KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F5; }
+static char32_t f6KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F6; }
+static char32_t f7KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F7; }
+static char32_t f8KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F8; }
+static char32_t f9KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F9; }
+static char32_t f10KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F10; }
+static char32_t f11KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F11; }
+static char32_t f12KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F12; }
+static char32_t pageUpKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::PAGE_UP;
+}
+static char32_t pageDownKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::PAGE_DOWN;
+}
+static char32_t deleteCharRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::BACKSPACE;
+} // key labeled Backspace
+static char32_t insertKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::INSERT;
+} // key labeled Delete
+static char32_t deleteKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::DELETE;
+} // key labeled Delete
+static char32_t ctrlUpArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::UP;
+}
+static char32_t ctrlDownArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::DOWN;
+}
+static char32_t ctrlRightArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::RIGHT;
+}
+static char32_t ctrlLeftArrowKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::LEFT;
+}
+static char32_t bracketPasteStartKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::PASTE_START;
+}
+static char32_t bracketPasteFinishKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::PASTE_FINISH;
+}
+static char32_t escFailureRoutine(char32_t) {
+ beep();
+ return -1;
+}
+
+// Handle ESC [ 1 ; 2 or 3 (or 5) <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket1Semicolon2or3or5Routines[] = {
+ upArrowKeyRoutine,
+ downArrowKeyRoutine,
+ rightArrowKeyRoutine,
+ leftArrowKeyRoutine,
+ homeKeyRoutine,
+ endKeyRoutine,
+ f1KeyRoutine,
+ f2KeyRoutine,
+ f3KeyRoutine,
+ f4KeyRoutine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket1Semicolon2or3or5Dispatch = {
+ 10, "ABCDHFPQRS", escLeftBracket1Semicolon2or3or5Routines
+};
+
+// Handle ESC [ 1 ; <more stuff> escape sequences
+//
+static char32_t escLeftBracket1Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch);
+}
+static char32_t escLeftBracket1Semicolon3Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_META;
+ return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch);
+}
+static char32_t escLeftBracket1Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch);
+}
+static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = {
+ escLeftBracket1Semicolon2Routine,
+ escLeftBracket1Semicolon3Routine,
+ escLeftBracket1Semicolon5Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket1SemicolonDispatch = {
+ 3, "235", escLeftBracket1SemicolonRoutines
+};
+
+// Handle ESC [ 1 ; <more stuff> escape sequences
+//
+static char32_t escLeftBracket1SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket1SemicolonDispatch);
+}
+
+// (S)-F5
+static CharacterDispatchRoutine escLeftBracket15Semicolon2Routines[] = {
+ f5KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket15Semicolon2Dispatch = {
+ 1, "~", escLeftBracket15Semicolon2Routines
+};
+static char32_t escLeftBracket15Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket15Semicolon2Dispatch);
+}
+
+// (C)-F5
+static CharacterDispatchRoutine escLeftBracket15Semicolon5Routines[] = {
+ f5KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket15Semicolon5Dispatch = {
+ 1, "~", escLeftBracket15Semicolon5Routines
+};
+static char32_t escLeftBracket15Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket15Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket15SemicolonRoutines[] = {
+ escLeftBracket15Semicolon2Routine, escLeftBracket15Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket15SemicolonDispatch = {
+ 2, "25", escLeftBracket15SemicolonRoutines
+};
+static char32_t escLeftBracket15SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket15SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket15Routines[] = {
+ f5KeyRoutine, escLeftBracket15SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket15Dispatch = {
+ 2, "~;", escLeftBracket15Routines
+};
+static char32_t escLeftBracket15Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket15Dispatch);
+}
+
+// (S)-F6
+static CharacterDispatchRoutine escLeftBracket17Semicolon2Routines[] = {
+ f6KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket17Semicolon2Dispatch = {
+ 1, "~", escLeftBracket17Semicolon2Routines
+};
+static char32_t escLeftBracket17Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket17Semicolon2Dispatch);
+}
+
+// (C)-F6
+static CharacterDispatchRoutine escLeftBracket17Semicolon5Routines[] = {
+ f6KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket17Semicolon5Dispatch = {
+ 1, "~", escLeftBracket17Semicolon5Routines
+};
+static char32_t escLeftBracket17Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket17Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket17SemicolonRoutines[] = {
+ escLeftBracket17Semicolon2Routine, escLeftBracket17Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket17SemicolonDispatch = {
+ 2, "25", escLeftBracket17SemicolonRoutines
+};
+static char32_t escLeftBracket17SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket17SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket17Routines[] = {
+ f6KeyRoutine, escLeftBracket17SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket17Dispatch = {
+ 2, "~;", escLeftBracket17Routines
+};
+static char32_t escLeftBracket17Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket17Dispatch);
+}
+
+// (S)-F7
+static CharacterDispatchRoutine escLeftBracket18Semicolon2Routines[] = {
+ f7KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket18Semicolon2Dispatch = {
+ 1, "~", escLeftBracket18Semicolon2Routines
+};
+static char32_t escLeftBracket18Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket18Semicolon2Dispatch);
+}
+
+// (C)-F7
+static CharacterDispatchRoutine escLeftBracket18Semicolon5Routines[] = {
+ f7KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket18Semicolon5Dispatch = {
+ 1, "~", escLeftBracket18Semicolon5Routines
+};
+static char32_t escLeftBracket18Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket18Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket18SemicolonRoutines[] = {
+ escLeftBracket18Semicolon2Routine, escLeftBracket18Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket18SemicolonDispatch = {
+ 2, "25", escLeftBracket18SemicolonRoutines
+};
+static char32_t escLeftBracket18SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket18SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket18Routines[] = {
+ f7KeyRoutine, escLeftBracket18SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket18Dispatch = {
+ 2, "~;", escLeftBracket18Routines
+};
+static char32_t escLeftBracket18Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket18Dispatch);
+}
+
+// (S)-F8
+static CharacterDispatchRoutine escLeftBracket19Semicolon2Routines[] = {
+ f8KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket19Semicolon2Dispatch = {
+ 1, "~", escLeftBracket19Semicolon2Routines
+};
+static char32_t escLeftBracket19Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket19Semicolon2Dispatch);
+}
+
+// (C)-F8
+static CharacterDispatchRoutine escLeftBracket19Semicolon5Routines[] = {
+ f8KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket19Semicolon5Dispatch = {
+ 1, "~", escLeftBracket19Semicolon5Routines
+};
+static char32_t escLeftBracket19Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket19Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket19SemicolonRoutines[] = {
+ escLeftBracket19Semicolon2Routine, escLeftBracket19Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket19SemicolonDispatch = {
+ 2, "25", escLeftBracket19SemicolonRoutines
+};
+static char32_t escLeftBracket19SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket19SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket19Routines[] = {
+ f8KeyRoutine, escLeftBracket19SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket19Dispatch = {
+ 2, "~;", escLeftBracket19Routines
+};
+static char32_t escLeftBracket19Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket19Dispatch);
+}
+
+// Handle ESC [ 1 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket1Routines[] = {
+ homeKeyRoutine, escLeftBracket1SemicolonRoutine,
+ escLeftBracket15Routine,
+ escLeftBracket17Routine,
+ escLeftBracket18Routine,
+ escLeftBracket19Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket1Dispatch = {
+ 6, "~;5789", escLeftBracket1Routines
+};
+
+// Handle ESC [ 2 <more stuff> escape sequences
+//
+
+// (S)-F9
+static CharacterDispatchRoutine escLeftBracket20Semicolon2Routines[] = {
+ f9KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket20Semicolon2Dispatch = {
+ 1, "~", escLeftBracket20Semicolon2Routines
+};
+static char32_t escLeftBracket20Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket20Semicolon2Dispatch);
+}
+
+// (C)-F9
+static CharacterDispatchRoutine escLeftBracket20Semicolon5Routines[] = {
+ f9KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket20Semicolon5Dispatch = {
+ 1, "~", escLeftBracket20Semicolon5Routines
+};
+static char32_t escLeftBracket20Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket20Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket20SemicolonRoutines[] = {
+ escLeftBracket20Semicolon2Routine, escLeftBracket20Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket20SemicolonDispatch = {
+ 2, "25", escLeftBracket20SemicolonRoutines
+};
+static char32_t escLeftBracket20SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket20SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket200Routines[] = {
+ bracketPasteStartKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket200Dispatch = {
+ 1, "~", escLeftBracket200Routines
+};
+static char32_t escLeftBracket200Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket200Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket201Routines[] = {
+ bracketPasteFinishKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket201Dispatch = {
+ 1, "~", escLeftBracket201Routines
+};
+static char32_t escLeftBracket201Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket201Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket20Routines[] = {
+ f9KeyRoutine, escLeftBracket20SemicolonRoutine, escLeftBracket200Routine, escLeftBracket201Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket20Dispatch = {
+ 4, "~;01", escLeftBracket20Routines
+};
+static char32_t escLeftBracket20Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket20Dispatch);
+}
+
+// (S)-F10
+static CharacterDispatchRoutine escLeftBracket21Semicolon2Routines[] = {
+ f10KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket21Semicolon2Dispatch = {
+ 1, "~", escLeftBracket21Semicolon2Routines
+};
+static char32_t escLeftBracket21Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket21Semicolon2Dispatch);
+}
+
+// (C)-F10
+static CharacterDispatchRoutine escLeftBracket21Semicolon5Routines[] = {
+ f10KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket21Semicolon5Dispatch = {
+ 1, "~", escLeftBracket21Semicolon5Routines
+};
+static char32_t escLeftBracket21Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket21Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket21SemicolonRoutines[] = {
+ escLeftBracket21Semicolon2Routine, escLeftBracket21Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket21SemicolonDispatch = {
+ 2, "25", escLeftBracket21SemicolonRoutines
+};
+static char32_t escLeftBracket21SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket21SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket21Routines[] = {
+ f10KeyRoutine, escLeftBracket21SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket21Dispatch = {
+ 2, "~;", escLeftBracket21Routines
+};
+static char32_t escLeftBracket21Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket21Dispatch);
+}
+
+// (S)-F11
+static CharacterDispatchRoutine escLeftBracket23Semicolon2Routines[] = {
+ f11KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket23Semicolon2Dispatch = {
+ 1, "~", escLeftBracket23Semicolon2Routines
+};
+static char32_t escLeftBracket23Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket23Semicolon2Dispatch);
+}
+
+// (C)-F11
+static CharacterDispatchRoutine escLeftBracket23Semicolon5Routines[] = {
+ f11KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket23Semicolon5Dispatch = {
+ 1, "~", escLeftBracket23Semicolon5Routines
+};
+static char32_t escLeftBracket23Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket23Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket23SemicolonRoutines[] = {
+ escLeftBracket23Semicolon2Routine, escLeftBracket23Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket23SemicolonDispatch = {
+ 2, "25", escLeftBracket23SemicolonRoutines
+};
+static char32_t escLeftBracket23SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket23SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket23Routines[] = {
+ f11KeyRoutine, escLeftBracket23SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket23Dispatch = {
+ 2, "~;", escLeftBracket23Routines
+};
+static char32_t escLeftBracket23Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket23Dispatch);
+}
+
+// (S)-F12
+static CharacterDispatchRoutine escLeftBracket24Semicolon2Routines[] = {
+ f12KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket24Semicolon2Dispatch = {
+ 1, "~", escLeftBracket24Semicolon2Routines
+};
+static char32_t escLeftBracket24Semicolon2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT;
+ return doDispatch(c, escLeftBracket24Semicolon2Dispatch);
+}
+
+// (C)-F12
+static CharacterDispatchRoutine escLeftBracket24Semicolon5Routines[] = {
+ f12KeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket24Semicolon5Dispatch = {
+ 1, "~", escLeftBracket24Semicolon5Routines
+};
+static char32_t escLeftBracket24Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket24Semicolon5Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket24SemicolonRoutines[] = {
+ escLeftBracket24Semicolon2Routine, escLeftBracket24Semicolon5Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket24SemicolonDispatch = {
+ 2, "25", escLeftBracket24SemicolonRoutines
+};
+static char32_t escLeftBracket24SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket24SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket24Routines[] = {
+ f12KeyRoutine, escLeftBracket24SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket24Dispatch = {
+ 2, "~;", escLeftBracket24Routines
+};
+static char32_t escLeftBracket24Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket24Dispatch);
+}
+
+// Handle ESC [ 2 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket2Routines[] = {
+ insertKeyRoutine,
+ escLeftBracket20Routine,
+ escLeftBracket21Routine,
+ escLeftBracket23Routine,
+ escLeftBracket24Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket2Dispatch = {
+ 5, "~0134", escLeftBracket2Routines
+};
+
+// Handle ESC [ 3 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket3Routines[] = {
+ deleteKeyRoutine, escFailureRoutine
+};
+
+static CharacterDispatch escLeftBracket3Dispatch = {
+ 1, "~", escLeftBracket3Routines
+};
+
+// Handle ESC [ 4 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket4Routines[] = {
+ endKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket4Dispatch = {
+ 1, "~", escLeftBracket4Routines
+};
+
+// Handle ESC [ 5 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket5Semicolon5Routines[] = {
+ pageUpKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket5Semicolon5Dispatch = {
+ 1, "~", escLeftBracket5Semicolon5Routines
+};
+static char32_t escLeftBracket5Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket5Semicolon5Dispatch);
+}
+static CharacterDispatchRoutine escLeftBracket5SemicolonRoutines[] = {
+ escLeftBracket5Semicolon5Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket5SemicolonDispatch = {
+ 1, "5", escLeftBracket5SemicolonRoutines
+};
+static char32_t escLeftBracket5SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket5SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket5Routines[] = {
+ pageUpKeyRoutine, escLeftBracket5SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket5Dispatch = {
+ 2, "~;", escLeftBracket5Routines
+};
+
+// Handle ESC [ 6 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket6Semicolon5Routines[] = {
+ pageDownKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket6Semicolon5Dispatch = {
+ 1, "~", escLeftBracket6Semicolon5Routines
+};
+static char32_t escLeftBracket6Semicolon5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL;
+ return doDispatch(c, escLeftBracket6Semicolon5Dispatch);
+}
+static CharacterDispatchRoutine escLeftBracket6SemicolonRoutines[] = {
+ escLeftBracket6Semicolon5Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket6SemicolonDispatch = {
+ 1, "5", escLeftBracket6SemicolonRoutines
+};
+static char32_t escLeftBracket6SemicolonRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket6SemicolonDispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket6Routines[] = {
+ pageDownKeyRoutine, escLeftBracket6SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket6Dispatch = {
+ 2, "~;", escLeftBracket6Routines
+};
+
+// Handle ESC [ 7 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket7Routines[] = {
+ homeKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket7Dispatch = {
+ 1, "~", escLeftBracket7Routines
+};
+
+// Handle ESC [ 8 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket8Routines[] = {
+ endKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket8Dispatch = {
+ 1, "~", escLeftBracket8Routines
+};
+
+// Handle ESC [ <digit> escape sequences
+//
+static char32_t escLeftBracket0Routine(char32_t c) {
+ return escFailureRoutine(c);
+}
+static char32_t escLeftBracket1Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket1Dispatch);
+}
+static char32_t escLeftBracket2Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket2Dispatch);
+}
+static char32_t escLeftBracket3Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket3Dispatch);
+}
+static char32_t escLeftBracket4Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket4Dispatch);
+}
+static char32_t escLeftBracket5Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket5Dispatch);
+}
+static char32_t escLeftBracket6Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket6Dispatch);
+}
+static char32_t escLeftBracket7Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket7Dispatch);
+}
+static char32_t escLeftBracket8Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket8Dispatch);
+}
+static char32_t escLeftBracket9Routine(char32_t c) {
+ return escFailureRoutine(c);
+}
+
+// Handle ESC [ <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracketRoutines[] = {
+ upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine,
+ leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine,
+ shiftTabRoutine,
+ escLeftBracket0Routine, escLeftBracket1Routine, escLeftBracket2Routine,
+ escLeftBracket3Routine, escLeftBracket4Routine, escLeftBracket5Routine,
+ escLeftBracket6Routine, escLeftBracket7Routine, escLeftBracket8Routine,
+ escLeftBracket9Routine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracketDispatch = {17, "ABCDHFZ0123456789",
+ escLeftBracketRoutines};
+
+// Handle ESC O <char> escape sequences
+//
+static CharacterDispatchRoutine escORoutines[] = {
+ upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine,
+ leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine,
+ f1KeyRoutine, f2KeyRoutine, f3KeyRoutine,
+ f4KeyRoutine,
+ ctrlUpArrowKeyRoutine, ctrlDownArrowKeyRoutine, ctrlRightArrowKeyRoutine,
+ ctrlLeftArrowKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escODispatch = {14, "ABCDHFPQRSabcd", escORoutines};
+
+// Initial ESC dispatch -- could be a Meta prefix or the start of an escape
+// sequence
+//
+static char32_t escLeftBracketRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracketDispatch);
+}
+static char32_t escORoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escODispatch);
+}
+static char32_t setMetaRoutine(char32_t c); // need forward reference
+static CharacterDispatchRoutine escRoutines[] = {
+ escLeftBracketRoutine, escORoutine, setMetaRoutine
+};
+static CharacterDispatch escDispatch = {2, "[O", escRoutines};
+
+// Initial dispatch -- we are not in the middle of anything yet
+//
+static char32_t escRoutine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escDispatch);
+}
+static CharacterDispatchRoutine initialRoutines[] = {
+ escRoutine, deleteCharRoutine, normalKeyRoutine
+};
+static CharacterDispatch initialDispatch = {2, "\x1B\x7F", initialRoutines};
+
+// Special handling for the ESC key because it does double duty
+//
+static char32_t setMetaRoutine(char32_t c) {
+ thisKeyMetaCtrl = Replxx::KEY::BASE_META;
+ if (c == 0x1B) { // another ESC, stay in ESC processing mode
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escDispatch);
+ }
+ return doDispatch(c, initialDispatch);
+}
+
+char32_t doDispatch(char32_t c) {
+ EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch
+ return doDispatch(c, initialDispatch);
+}
+
+} // namespace EscapeSequenceProcessing // move these out of global namespace
+
+}
+
+#endif /* #ifndef _WIN32 */
+
diff --git a/contrib/replxx/src/escape.hxx b/contrib/replxx/src/escape.hxx
new file mode 100644
index 0000000..6597395
--- /dev/null
+++ b/contrib/replxx/src/escape.hxx
@@ -0,0 +1,37 @@
+#ifndef REPLXX_ESCAPE_HXX_INCLUDED
+#define REPLXX_ESCAPE_HXX_INCLUDED 1
+
+namespace replxx {
+
+namespace EscapeSequenceProcessing {
+
+// This is a typedef for the routine called by doDispatch(). It takes the
+// current character
+// as input, does any required processing including reading more characters and
+// calling other
+// dispatch routines, then eventually returns the final (possibly extended or
+// special) character.
+//
+typedef char32_t (*CharacterDispatchRoutine)(char32_t);
+
+// This structure is used by doDispatch() to hold a list of characters to test
+// for and
+// a list of routines to call if the character matches. The dispatch routine
+// list is one
+// longer than the character list; the final entry is used if no character
+// matches.
+//
+struct CharacterDispatch {
+ unsigned int len; // length of the chars list
+ const char* chars; // chars to test
+ CharacterDispatchRoutine* dispatch; // array of routines to call
+};
+
+char32_t doDispatch(char32_t c);
+
+}
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/history.cxx b/contrib/replxx/src/history.cxx
new file mode 100644
index 0000000..fe691df
--- /dev/null
+++ b/contrib/replxx/src/history.cxx
@@ -0,0 +1,402 @@
+#include <algorithm>
+#include <memory>
+#include <fstream>
+#include <cstring>
+
+#ifndef _WIN32
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#endif /* _WIN32 */
+
+#include "replxx.hxx"
+#include "history.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+namespace {
+void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) {
+ delete impl_;
+}
+}
+
+static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 );
+
+Replxx::HistoryScan::HistoryScan( impl_t impl_ )
+ : _impl( std::move( impl_ ) ) {
+}
+
+bool Replxx::HistoryScan::next( void ) {
+ return ( _impl->next() );
+}
+
+Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ )
+ : _entries( entries_ )
+ , _it( _entries.end() )
+ , _utf8Cache()
+ , _entryCache( std::string(), std::string() )
+ , _cacheValid( false ) {
+}
+
+Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const {
+ return ( _impl->get() );
+}
+
+bool Replxx::HistoryScanImpl::next( void ) {
+ if ( _it == _entries.end() ) {
+ _it = _entries.begin();
+ } else {
+ ++ _it;
+ }
+ _cacheValid = false;
+ return ( _it != _entries.end() );
+}
+
+Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const {
+ if ( _cacheValid ) {
+ return ( _entryCache );
+ }
+ _utf8Cache.assign( _it->text() );
+ _entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() );
+ _cacheValid = true;
+ return ( _entryCache );
+}
+
+Replxx::HistoryScan::impl_t History::scan( void ) const {
+ return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) );
+}
+
+History::History( void )
+ : _entries()
+ , _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN )
+ , _current( _entries.begin() )
+ , _yankPos( _entries.end() )
+ , _previous( _entries.begin() )
+ , _recallMostRecent( false )
+ , _unique( true ) {
+}
+
+void History::add( UnicodeString const& line, std::string const& when ) {
+ if ( _maxSize <= 0 ) {
+ return;
+ }
+ if ( ! _entries.empty() && ( line == _entries.back().text() ) ) {
+ _entries.back() = Entry( now_ms_str(), line );
+ return;
+ }
+ remove_duplicate( line );
+ trim_to_max_size();
+ _entries.emplace_back( when, line );
+ _locations.insert( make_pair( line, last() ) );
+ if ( _current == _entries.end() ) {
+ _current = last();
+ }
+ _yankPos = _entries.end();
+}
+
+#ifndef _WIN32
+class FileLock {
+ std::string _path;
+ int _lockFd;
+public:
+ FileLock( std::string const& name_ )
+ : _path( name_ + ".lock" )
+ , _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) {
+ static_cast<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
+ }
+ ~FileLock( void ) {
+ static_cast<void>( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 );
+ ::close( _lockFd );
+ ::unlink( _path.c_str() );
+ return;
+ }
+};
+#endif
+
+bool History::save( std::string const& filename, bool sync_ ) {
+#ifndef _WIN32
+ mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO );
+ FileLock fileLock( filename );
+#endif
+ entries_t entries;
+ locations_t locations;
+ if ( ! sync_ ) {
+ entries.swap( _entries );
+ locations.swap( _locations );
+ _entries = entries;
+ reset_iters();
+ }
+ do_load( filename );
+ sort();
+ remove_duplicates();
+ trim_to_max_size();
+ ofstream histFile( filename );
+ if ( ! histFile ) {
+ return ( false );
+ }
+#ifndef _WIN32
+ umask( old_umask );
+ chmod( filename.c_str(), S_IRUSR | S_IWUSR );
+#endif
+ Utf8String utf8;
+ for ( Entry const& h : _entries ) {
+ if ( ! h.text().is_empty() ) {
+ utf8.assign( h.text() );
+ histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl;
+ }
+ }
+ if ( ! sync_ ) {
+ _entries = std::move( entries );
+ _locations = std::move( locations );
+ }
+ reset_iters();
+ return ( true );
+}
+
+namespace {
+
+bool is_timestamp( std::string const& s ) {
+ static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd";
+ static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 );
+ if ( s.length() != TIMESTAMP_LENGTH ) {
+ return ( false );
+ }
+ for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) {
+ if ( TIMESTAMP_PATTERN[i] == 'd' ) {
+ if ( ! isdigit( s[i] ) ) {
+ return ( false );
+ }
+ } else if ( s[i] != TIMESTAMP_PATTERN[i] ) {
+ return ( false );
+ }
+ }
+ return ( true );
+}
+
+}
+
+bool History::do_load( std::string const& filename ) {
+ ifstream histFile( filename );
+ if ( ! histFile ) {
+ return ( false );
+ }
+ string line;
+ string when( "0000-00-00 00:00:00.000" );
+ while ( getline( histFile, line ).good() ) {
+ string::size_type eol( line.find_first_of( "\r\n" ) );
+ if ( eol != string::npos ) {
+ line.erase( eol );
+ }
+ if ( is_timestamp( line ) ) {
+ when.assign( line, 4, std::string::npos );
+ continue;
+ }
+ if ( ! line.empty() ) {
+ _entries.emplace_back( when, UnicodeString( line ) );
+ }
+ }
+ return ( true );
+}
+
+bool History::load( std::string const& filename ) {
+ clear();
+ bool success( do_load( filename ) );
+ sort();
+ remove_duplicates();
+ trim_to_max_size();
+ _previous = _current = last();
+ _yankPos = _entries.end();
+ return ( success );
+}
+
+void History::sort( void ) {
+ typedef std::vector<Entry> sortable_entries_t;
+ _locations.clear();
+ sortable_entries_t sortableEntries( _entries.begin(), _entries.end() );
+ std::stable_sort( sortableEntries.begin(), sortableEntries.end() );
+ _entries.clear();
+ _entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() );
+}
+
+void History::clear( void ) {
+ _locations.clear();
+ _entries.clear();
+ _current = _entries.begin();
+ _recallMostRecent = false;
+}
+
+void History::set_max_size( int size_ ) {
+ if ( size_ >= 0 ) {
+ _maxSize = size_;
+ trim_to_max_size();
+ }
+}
+
+void History::reset_yank_iterator( void ) {
+ _yankPos = _entries.end();
+}
+
+bool History::next_yank_position( void ) {
+ bool resetYankSize( false );
+ if ( _yankPos == _entries.end() ) {
+ resetYankSize = true;
+ }
+ if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) {
+ -- _yankPos;
+ } else {
+ _yankPos = moved( _entries.end(), -2 );
+ }
+ return ( resetYankSize );
+}
+
+bool History::move( bool up_ ) {
+ bool doRecall( _recallMostRecent && ! up_ );
+ if ( doRecall ) {
+ _current = _previous; // emulate Windows down-arrow
+ }
+ _recallMostRecent = false;
+ return ( doRecall || move( _current, up_ ? -1 : 1 ) );
+}
+
+void History::jump( bool start_, bool reset_ ) {
+ if ( start_ ) {
+ _current = _entries.begin();
+ } else {
+ _current = last();
+ }
+ if ( reset_ ) {
+ _recallMostRecent = false;
+ }
+}
+
+void History::save_pos( void ) {
+ _previous = _current;
+}
+
+void History::restore_pos( void ) {
+ _current = _previous;
+}
+
+bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_ ) {
+ int step( back_ ? -1 : 1 );
+ entries_t::const_iterator it( moved( _current, step, true ) );
+ while ( it != _current ) {
+ if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) {
+ _current = it;
+ commit_index();
+ return ( true );
+ }
+ move( it, step, true );
+ }
+ return ( false );
+}
+
+bool History::move( entries_t::const_iterator& it_, int by_, bool wrapped_ ) const {
+ if ( by_ > 0 ) {
+ for ( int i( 0 ); i < by_; ++ i ) {
+ ++ it_;
+ if ( it_ != _entries.end() ) {
+ } else if ( wrapped_ ) {
+ it_ = _entries.begin();
+ } else {
+ -- it_;
+ return ( false );
+ }
+ }
+ } else {
+ for ( int i( 0 ); i > by_; -- i ) {
+ if ( it_ != _entries.begin() ) {
+ -- it_;
+ } else if ( wrapped_ ) {
+ it_ = last();
+ } else {
+ return ( false );
+ }
+ }
+ }
+ return ( true );
+}
+
+History::entries_t::const_iterator History::moved( entries_t::const_iterator it_, int by_, bool wrapped_ ) const {
+ move( it_, by_, wrapped_ );
+ return ( it_ );
+}
+
+void History::erase( entries_t::const_iterator it_ ) {
+ bool invalidated( it_ == _current );
+ _locations.erase( it_->text() );
+ it_ = _entries.erase( it_ );
+ if ( invalidated ) {
+ _current = it_;
+ }
+ if ( ( _current == _entries.end() ) && ! _entries.empty() ) {
+ -- _current;
+ }
+ _yankPos = _entries.end();
+ _previous = _current;
+}
+
+void History::trim_to_max_size( void ) {
+ while ( size() > _maxSize ) {
+ erase( _entries.begin() );
+ }
+}
+
+void History::remove_duplicate( UnicodeString const& line_ ) {
+ if ( ! _unique ) {
+ return;
+ }
+ locations_t::iterator it( _locations.find( line_ ) );
+ if ( it == _locations.end() ) {
+ return;
+ }
+ erase( it->second );
+}
+
+void History::remove_duplicates( void ) {
+ if ( ! _unique ) {
+ return;
+ }
+ _locations.clear();
+ typedef std::pair<locations_t::iterator, bool> locations_insertion_result_t;
+ for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) {
+ locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) );
+ if ( ! locationsInsertionResult.second ) {
+ _entries.erase( locationsInsertionResult.first->second );
+ locationsInsertionResult.first->second = it;
+ }
+ }
+}
+
+void History::update_last( UnicodeString const& line_ ) {
+ if ( _unique ) {
+ _locations.erase( _entries.back().text() );
+ remove_duplicate( line_ );
+ _locations.insert( make_pair( line_, last() ) );
+ }
+ _entries.back() = Entry( now_ms_str(), line_ );
+}
+
+void History::drop_last( void ) {
+ erase( last() );
+}
+
+bool History::is_last( void ) const {
+ return ( _current == last() );
+}
+
+History::entries_t::const_iterator History::last( void ) const {
+ return ( moved( _entries.end(), -1 ) );
+}
+
+void History::reset_iters( void ) {
+ _previous = _current = last();
+ _yankPos = _entries.end();
+}
+
+}
+
diff --git a/contrib/replxx/src/history.hxx b/contrib/replxx/src/history.hxx
new file mode 100644
index 0000000..4e72c03
--- /dev/null
+++ b/contrib/replxx/src/history.hxx
@@ -0,0 +1,141 @@
+#ifndef REPLXX_HISTORY_HXX_INCLUDED
+#define REPLXX_HISTORY_HXX_INCLUDED 1
+
+#include <list>
+#include <unordered_map>
+
+#include "unicodestring.hxx"
+#include "utf8string.hxx"
+#include "conversion.hxx"
+#include "util.hxx"
+
+namespace std {
+template<>
+struct hash<replxx::UnicodeString> {
+ std::size_t operator()( replxx::UnicodeString const& us_ ) const {
+ std::size_t h( 0 );
+ char32_t const* p( us_.get() );
+ char32_t const* e( p + us_.length() );
+ while ( p != e ) {
+ h *= 31;
+ h += *p;
+ ++ p;
+ }
+ return ( h );
+ }
+};
+}
+
+namespace replxx {
+
+class History {
+public:
+ class Entry {
+ std::string _timestamp;
+ UnicodeString _text;
+ public:
+ Entry( std::string const& timestamp_, UnicodeString const& text_ )
+ : _timestamp( timestamp_ )
+ , _text( text_ ) {
+ }
+ std::string const& timestamp( void ) const {
+ return ( _timestamp );
+ }
+ UnicodeString const& text( void ) const {
+ return ( _text );
+ }
+ bool operator < ( Entry const& other_ ) const {
+ return ( _timestamp < other_._timestamp );
+ }
+ };
+ typedef std::list<Entry> entries_t;
+ typedef std::unordered_map<UnicodeString, entries_t::const_iterator> locations_t;
+private:
+ entries_t _entries;
+ locations_t _locations;
+ int _maxSize;
+ entries_t::const_iterator _current;
+ entries_t::const_iterator _yankPos;
+ /*
+ * _previous and _recallMostRecent are used to allow
+ * HISTORY_NEXT action (a down-arrow key) to have a special meaning
+ * if invoked after a line from history was accepted without
+ * any modification.
+ * Special meaning is: a down arrow shall jump to the line one
+ * after previously accepted from history.
+ */
+ entries_t::const_iterator _previous;
+ bool _recallMostRecent;
+ bool _unique;
+public:
+ History( void );
+ void add( UnicodeString const& line, std::string const& when = now_ms_str() );
+ bool save( std::string const& filename, bool );
+ bool load( std::string const& filename );
+ void clear( void );
+ void set_max_size( int len );
+ void set_unique( bool unique_ ) {
+ _unique = unique_;
+ remove_duplicates();
+ }
+ void reset_yank_iterator();
+ bool next_yank_position( void );
+ void reset_recall_most_recent( void ) {
+ _recallMostRecent = false;
+ }
+ void commit_index( void ) {
+ _previous = _current;
+ _recallMostRecent = true;
+ }
+ bool is_empty( void ) const {
+ return ( _entries.empty() );
+ }
+ void update_last( UnicodeString const& );
+ void drop_last( void );
+ bool is_last( void ) const;
+ bool move( bool );
+ UnicodeString const& current( void ) const {
+ return ( _current->text() );
+ }
+ UnicodeString const& yank_line( void ) const {
+ return ( _yankPos->text() );
+ }
+ void jump( bool, bool = true );
+ bool common_prefix_search( UnicodeString const&, int, bool );
+ int size( void ) const {
+ return ( static_cast<int>( _entries.size() ) );
+ }
+ Replxx::HistoryScan::impl_t scan( void ) const;
+ void save_pos( void );
+ void restore_pos( void );
+private:
+ History( History const& ) = delete;
+ History& operator = ( History const& ) = delete;
+ bool move( entries_t::const_iterator&, int, bool = false ) const;
+ entries_t::const_iterator moved( entries_t::const_iterator, int, bool = false ) const;
+ void erase( entries_t::const_iterator );
+ void trim_to_max_size( void );
+ void remove_duplicate( UnicodeString const& );
+ void remove_duplicates( void );
+ bool do_load( std::string const& );
+ entries_t::const_iterator last( void ) const;
+ void sort( void );
+ void reset_iters( void );
+};
+
+class Replxx::HistoryScanImpl {
+ History::entries_t const& _entries;
+ History::entries_t::const_iterator _it;
+ mutable Utf8String _utf8Cache;
+ mutable Replxx::HistoryEntry _entryCache;
+ mutable bool _cacheValid;
+public:
+ HistoryScanImpl( History::entries_t const& );
+ bool next( void );
+ Replxx::HistoryEntry const& get( void ) const;
+};
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/killring.hxx b/contrib/replxx/src/killring.hxx
new file mode 100644
index 0000000..0baf108
--- /dev/null
+++ b/contrib/replxx/src/killring.hxx
@@ -0,0 +1,78 @@
+#ifndef REPLXX_KILLRING_HXX_INCLUDED
+#define REPLXX_KILLRING_HXX_INCLUDED 1
+
+#include <vector>
+
+#include "unicodestring.hxx"
+
+namespace replxx {
+
+class KillRing {
+ static const int capacity = 10;
+ int size;
+ int index;
+ char indexToSlot[10];
+ std::vector<UnicodeString> theRing;
+
+public:
+ enum action { actionOther, actionKill, actionYank };
+ action lastAction;
+
+ KillRing()
+ : size(0)
+ , index(0)
+ , lastAction(actionOther) {
+ theRing.reserve(capacity);
+ }
+
+ void kill(const char32_t* text, int textLen, bool forward) {
+ if (textLen == 0) {
+ return;
+ }
+ UnicodeString killedText(text, textLen);
+ if (lastAction == actionKill && size > 0) {
+ int slot = indexToSlot[0];
+ int currentLen = static_cast<int>(theRing[slot].length());
+ UnicodeString temp;
+ if ( forward ) {
+ temp.append( theRing[slot].get(), currentLen ).append( killedText.get(), textLen );
+ } else {
+ temp.append( killedText.get(), textLen ).append( theRing[slot].get(), currentLen );
+ }
+ theRing[slot] = temp;
+ } else {
+ if (size < capacity) {
+ if (size > 0) {
+ memmove(&indexToSlot[1], &indexToSlot[0], size);
+ }
+ indexToSlot[0] = size;
+ size++;
+ theRing.push_back(killedText);
+ } else {
+ int slot = indexToSlot[capacity - 1];
+ theRing[slot] = killedText;
+ memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1);
+ indexToSlot[0] = slot;
+ }
+ index = 0;
+ }
+ }
+
+ UnicodeString* yank() { return (size > 0) ? &theRing[indexToSlot[index]] : 0; }
+
+ UnicodeString* yankPop() {
+ if (size == 0) {
+ return 0;
+ }
+ ++index;
+ if (index == size) {
+ index = 0;
+ }
+ return &theRing[indexToSlot[index]];
+ }
+};
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/prompt.cxx b/contrib/replxx/src/prompt.cxx
new file mode 100644
index 0000000..c13ea80
--- /dev/null
+++ b/contrib/replxx/src/prompt.cxx
@@ -0,0 +1,144 @@
+#ifdef _WIN32
+
+#include <conio.h>
+#include <windows.h>
+#include <io.h>
+#if _MSC_VER < 1900 && defined (_MSC_VER)
+#define snprintf _snprintf // Microsoft headers use underscores in some names
+#endif
+#define strcasecmp _stricmp
+#define strdup _strdup
+#define write _write
+#define STDIN_FILENO 0
+
+#else /* _WIN32 */
+
+#include <unistd.h>
+
+#endif /* _WIN32 */
+
+#include "prompt.hxx"
+#include "util.hxx"
+
+namespace replxx {
+
+Prompt::Prompt( Terminal& terminal_ )
+ : _extraLines( 0 )
+ , _lastLinePosition( 0 )
+ , _cursorRowOffset( 0 )
+ , _screenColumns( 0 )
+ , _terminal( terminal_ ) {
+}
+
+void Prompt::write() {
+ _terminal.write32( _text.get(), _text.length() );
+}
+
+void Prompt::update_screen_columns( void ) {
+ _screenColumns = _terminal.get_screen_columns();
+}
+
+void Prompt::set_text( UnicodeString const& text_ ) {
+ _text = text_;
+ update_state();
+}
+
+void Prompt::update_state() {
+ _cursorRowOffset -= _extraLines;
+ _extraLines = 0;
+ _lastLinePosition = 0;
+ _screenColumns = 0;
+ update_screen_columns();
+ // strip control characters from the prompt -- we do allow newline
+ UnicodeString::const_iterator in( _text.begin() );
+ UnicodeString::iterator out( _text.begin() );
+
+ int visibleCount = 0;
+ int x = 0;
+
+ bool const strip = !tty::out;
+
+ while (in != _text.end()) {
+ char32_t c = *in;
+ if ('\n' == c || !is_control_code(c)) {
+ *out = c;
+ ++out;
+ ++in;
+ ++visibleCount;
+ if ('\n' == c || ++x >= _screenColumns) {
+ x = 0;
+ ++_extraLines;
+ _lastLinePosition = visibleCount;
+ }
+ } else if (c == '\x1b') {
+ if ( strip ) {
+ // jump over control chars
+ ++in;
+ if (*in == '[') {
+ ++in;
+ while ( ( in != _text.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) {
+ ++in;
+ }
+ if (*in == 'm') {
+ ++in;
+ }
+ }
+ } else {
+ // copy control chars
+ *out = *in;
+ ++out;
+ ++in;
+ if (*in == '[') {
+ *out = *in;
+ ++out;
+ ++in;
+ while ( ( in != _text.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) {
+ *out = *in;
+ ++out;
+ ++in;
+ }
+ if (*in == 'm') {
+ *out = *in;
+ ++out;
+ ++in;
+ }
+ }
+ }
+ } else {
+ ++in;
+ }
+ }
+ _characterCount = visibleCount;
+ int charCount( static_cast<int>( out - _text.begin() ) );
+ _text.erase( charCount, _text.length() - charCount );
+
+ _cursorRowOffset += _extraLines;
+}
+
+int Prompt::indentation() const {
+ return _characterCount - _lastLinePosition;
+}
+
+// Used with DynamicPrompt (history search)
+//
+const UnicodeString forwardSearchBasePrompt("(i-search)`");
+const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`");
+const UnicodeString endSearchBasePrompt("': ");
+
+DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection )
+ : Prompt( terminal_ )
+ , _searchText()
+ , _direction( initialDirection ) {
+ updateSearchPrompt();
+}
+
+void DynamicPrompt::updateSearchPrompt(void) {
+ update_screen_columns();
+ const UnicodeString* basePrompt =
+ (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
+ _text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt );
+ update_state();
+}
+
+}
+
diff --git a/contrib/replxx/src/prompt.hxx b/contrib/replxx/src/prompt.hxx
new file mode 100644
index 0000000..9ed3f5f
--- /dev/null
+++ b/contrib/replxx/src/prompt.hxx
@@ -0,0 +1,45 @@
+#ifndef REPLXX_PROMPT_HXX_INCLUDED
+#define REPLXX_PROMPT_HXX_INCLUDED 1
+
+#include <cstdlib>
+
+#include "unicodestring.hxx"
+#include "terminal.hxx"
+
+namespace replxx {
+
+class Prompt { // a convenience struct for grouping prompt info
+public:
+ UnicodeString _text; // our copy of the prompt text, edited
+ int _characterCount; // visible characters in _text
+ int _extraLines; // extra lines (beyond 1) occupied by prompt
+ int _lastLinePosition; // index into _text where last line begins
+ int _cursorRowOffset; // where the cursor is relative to the start of the prompt
+private:
+ int _screenColumns; // width of screen in columns [cache]
+ Terminal& _terminal;
+public:
+ Prompt( Terminal& );
+ void set_text( UnicodeString const& textPtr );
+ void update_state();
+ void update_screen_columns( void );
+ int screen_columns() const {
+ return ( _screenColumns );
+ }
+ void write();
+ int indentation() const;
+};
+
+// changing prompt for "(reverse-i-search)`text':" etc.
+//
+struct DynamicPrompt : public Prompt {
+ UnicodeString _searchText; // text we are searching for
+ int _direction; // current search _direction, 1=forward, -1=reverse
+
+ DynamicPrompt( Terminal&, int initialDirection );
+ void updateSearchPrompt(void);
+};
+
+}
+
+#endif
diff --git a/contrib/replxx/src/replxx.cxx b/contrib/replxx/src/replxx.cxx
new file mode 100644
index 0000000..29d35a2
--- /dev/null
+++ b/contrib/replxx/src/replxx.cxx
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
+ * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * line editing lib needs to be 20,000 lines of C code.
+ *
+ * You can find the latest source code at:
+ *
+ * http://github.com/antirez/linenoise
+ *
+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
+ * the 2010 UNIX computers around.
+ *
+ * References:
+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
+ *
+ * Todo list:
+ * - Switch to gets() if $TERM is something we can't support.
+ * - Filter bogus Ctrl+<char> combinations.
+ * - Win32 support
+ *
+ * Bloat:
+ * - Completion?
+ * - History search like Ctrl+r in readline?
+ *
+ * List of escape sequences used by this program, we do everything just
+ * with three sequences. In order to be so cheap we may have some
+ * flickering effect with some slow terminal, but the lesser sequences
+ * the more compatible.
+ *
+ * CHA (Cursor Horizontal Absolute)
+ * Sequence: ESC [ n G
+ * Effect: moves cursor to column n (1 based)
+ *
+ * EL (Erase Line)
+ * Sequence: ESC [ n K
+ * Effect: if n is 0 or missing, clear from cursor to end of line
+ * Effect: if n is 1, clear from beginning of line to cursor
+ * Effect: if n is 2, clear entire line
+ *
+ * CUF (Cursor Forward)
+ * Sequence: ESC [ n C
+ * Effect: moves cursor forward of n chars
+ *
+ * The following are used to clear the screen: ESC [ H ESC [ 2 J
+ * This is actually composed of two sequences:
+ *
+ * cursorhome
+ * Sequence: ESC [ H
+ * Effect: moves the cursor to upper left corner
+ *
+ * ED2 (Clear entire screen)
+ * Sequence: ESC [ 2 J
+ * Effect: clear the whole screen
+ *
+ */
+
+#include <algorithm>
+#include <cstdarg>
+
+#ifdef _WIN32
+
+#include <io.h>
+#define STDIN_FILENO 0
+
+#else /* _WIN32 */
+
+#include <signal.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#endif /* _WIN32 */
+
+#include "replxx.h"
+#include "replxx.hxx"
+#include "replxx_impl.hxx"
+#include "history.hxx"
+
+static_assert(
+ static_cast<int>( replxx::Replxx::ACTION::SEND_EOF ) == static_cast<int>( REPLXX_ACTION_SEND_EOF ),
+ "C and C++ `ACTION` APIs are missaligned!"
+);
+
+static_assert(
+ static_cast<int>( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast<int>( REPLXX_KEY_PASTE_FINISH ),
+ "C and C++ `KEY` APIs are missaligned!"
+);
+
+using namespace std;
+using namespace std::placeholders;
+using namespace replxx;
+
+namespace replxx {
+
+namespace {
+void delete_ReplxxImpl( Replxx::ReplxxImpl* impl_ ) {
+ delete impl_;
+}
+}
+
+Replxx::Replxx( void )
+ : _impl( new Replxx::ReplxxImpl( nullptr, nullptr, nullptr ), delete_ReplxxImpl ) {
+}
+
+void Replxx::set_completion_callback( completion_callback_t const& fn ) {
+ _impl->set_completion_callback( fn );
+}
+
+void Replxx::set_modify_callback( modify_callback_t const& fn ) {
+ _impl->set_modify_callback( fn );
+}
+
+void Replxx::set_highlighter_callback( highlighter_callback_t const& fn ) {
+ _impl->set_highlighter_callback( fn );
+}
+
+void Replxx::set_hint_callback( hint_callback_t const& fn ) {
+ _impl->set_hint_callback( fn );
+}
+
+char const* Replxx::input( std::string const& prompt ) {
+ return ( _impl->input( prompt ) );
+}
+
+void Replxx::history_add( std::string const& line ) {
+ _impl->history_add( line );
+}
+
+bool Replxx::history_sync( std::string const& filename ) {
+ return ( _impl->history_sync( filename ) );
+}
+
+bool Replxx::history_save( std::string const& filename ) {
+ return ( _impl->history_save( filename ) );
+}
+
+bool Replxx::history_load( std::string const& filename ) {
+ return ( _impl->history_load( filename ) );
+}
+
+void Replxx::history_clear( void ) {
+ _impl->history_clear();
+}
+
+int Replxx::history_size( void ) const {
+ return ( _impl->history_size() );
+}
+
+Replxx::HistoryScan Replxx::history_scan( void ) const {
+ return ( _impl->history_scan() );
+}
+
+void Replxx::set_preload_buffer( std::string const& preloadText ) {
+ _impl->set_preload_buffer( preloadText );
+}
+
+void Replxx::set_word_break_characters( char const* wordBreakers ) {
+ _impl->set_word_break_characters( wordBreakers );
+}
+
+void Replxx::set_max_hint_rows( int count ) {
+ _impl->set_max_hint_rows( count );
+}
+
+void Replxx::set_hint_delay( int milliseconds ) {
+ _impl->set_hint_delay( milliseconds );
+}
+
+void Replxx::set_completion_count_cutoff( int count ) {
+ _impl->set_completion_count_cutoff( count );
+}
+
+void Replxx::set_double_tab_completion( bool val ) {
+ _impl->set_double_tab_completion( val );
+}
+
+void Replxx::set_complete_on_empty( bool val ) {
+ _impl->set_complete_on_empty( val );
+}
+
+void Replxx::set_beep_on_ambiguous_completion( bool val ) {
+ _impl->set_beep_on_ambiguous_completion( val );
+}
+
+void Replxx::set_immediate_completion( bool val ) {
+ _impl->set_immediate_completion( val );
+}
+
+void Replxx::set_unique_history( bool val ) {
+ _impl->set_unique_history( val );
+}
+
+void Replxx::set_no_color( bool val ) {
+ _impl->set_no_color( val );
+}
+
+void Replxx::set_max_history_size( int len ) {
+ _impl->set_max_history_size( len );
+}
+
+void Replxx::clear_screen( void ) {
+ _impl->clear_screen( 0 );
+}
+
+void Replxx::emulate_key_press( char32_t keyPress_ ) {
+ _impl->emulate_key_press( keyPress_ );
+}
+
+Replxx::ACTION_RESULT Replxx::invoke( ACTION action_, char32_t keyPress_ ) {
+ return ( _impl->invoke( action_, keyPress_ ) );
+}
+
+void Replxx::bind_key( char32_t keyPress_, key_press_handler_t handler_ ) {
+ _impl->bind_key( keyPress_, handler_ );
+}
+
+void Replxx::bind_key_internal( char32_t keyPress_, char const* actionName_ ) {
+ _impl->bind_key_internal( keyPress_, actionName_ );
+}
+
+Replxx::State Replxx::get_state( void ) const {
+ return ( _impl->get_state() );
+}
+
+void Replxx::set_state( Replxx::State const& state_ ) {
+ _impl->set_state( state_ );
+}
+
+int Replxx::install_window_change_handler( void ) {
+ return ( _impl->install_window_change_handler() );
+}
+
+void Replxx::enable_bracketed_paste( void ) {
+ _impl->enable_bracketed_paste();
+}
+
+void Replxx::disable_bracketed_paste( void ) {
+ _impl->disable_bracketed_paste();
+}
+
+void Replxx::print( char const* format_, ... ) {
+ ::std::va_list ap;
+ va_start( ap, format_ );
+ int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) );
+ va_end( ap );
+ va_start( ap, format_ );
+ unique_ptr<char[]> buf( new char[size + 1] );
+ vsnprintf( buf.get(), static_cast<size_t>( size + 1 ), format_, ap );
+ va_end( ap );
+ return ( _impl->print( buf.get(), size ) );
+}
+
+void Replxx::write( char const* str, int length ) {
+ return ( _impl->print( str, length ) );
+}
+
+}
+
+::Replxx* replxx_init() {
+ typedef ::Replxx* replxx_data_t;
+ return ( reinterpret_cast<replxx_data_t>( new replxx::Replxx::ReplxxImpl( nullptr, nullptr, nullptr ) ) );
+}
+
+void replxx_end( ::Replxx* replxx_ ) {
+ delete reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ );
+}
+
+void replxx_clear_screen( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->clear_screen( 0 );
+}
+
+void replxx_emulate_key_press( ::Replxx* replxx_, int unsigned keyPress_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->emulate_key_press( keyPress_ );
+}
+
+ReplxxActionResult replxx_invoke( ::Replxx* replxx_, ReplxxAction action_, int unsigned keyPress_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( static_cast<ReplxxActionResult>( replxx->invoke( static_cast<replxx::Replxx::ACTION>( action_ ), keyPress_ ) ) );
+}
+
+replxx::Replxx::ACTION_RESULT key_press_handler_forwarder( key_press_handler_t handler_, char32_t code_, void* userData_ ) {
+ return ( static_cast<replxx::Replxx::ACTION_RESULT>( handler_( code_, userData_ ) ) );
+}
+
+void replxx_bind_key( ::Replxx* replxx_, int code_, key_press_handler_t handler_, void* userData_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->bind_key( code_, std::bind( key_press_handler_forwarder, handler_, _1, userData_ ) );
+}
+
+int replxx_bind_key_internal( ::Replxx* replxx_, int code_, char const* actionName_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ try {
+ replxx->bind_key_internal( code_, actionName_ );
+ } catch ( ... ) {
+ return ( -1 );
+ }
+ return ( 0 );
+}
+
+void replxx_get_state( ::Replxx* replxx_, ReplxxState* state ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx::Replxx::State s( replxx->get_state() );
+ state->text = s.text();
+ state->cursorPosition = s.cursor_position();
+}
+
+void replxx_set_state( ::Replxx* replxx_, ReplxxState* state ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_state( replxx::Replxx::State( state->text, state->cursorPosition ) );
+}
+
+/**
+ * replxx_set_preload_buffer provides text to be inserted into the command buffer
+ *
+ * the provided text will be processed to be usable and will be used to preload
+ * the input buffer on the next call to replxx_input()
+ *
+ * @param preloadText text to begin with on the next call to replxx_input()
+ */
+void replxx_set_preload_buffer(::Replxx* replxx_, const char* preloadText) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_preload_buffer( preloadText ? preloadText : "" );
+}
+
+/**
+ * replxx_input is a readline replacement.
+ *
+ * call it with a prompt to display and it will return a line of input from the
+ * user
+ *
+ * @param prompt text of prompt to display to the user
+ * @return the returned string is managed by replxx library
+ * and it must NOT be freed in the client.
+ */
+char const* replxx_input( ::Replxx* replxx_, const char* prompt ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( replxx->input( prompt ) );
+}
+
+int replxx_print( ::Replxx* replxx_, char const* format_, ... ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ ::std::va_list ap;
+ va_start( ap, format_ );
+ int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) );
+ va_end( ap );
+ va_start( ap, format_ );
+ unique_ptr<char[]> buf( new char[size + 1] );
+ vsnprintf( buf.get(), static_cast<size_t>( size + 1 ), format_, ap );
+ va_end( ap );
+ try {
+ replxx->print( buf.get(), size );
+ } catch ( ... ) {
+ return ( -1 );
+ }
+ return ( size );
+}
+
+int replxx_write( ::Replxx* replxx_, char const* str, int length ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ try {
+ replxx->print( str, length );
+ } catch ( ... ) {
+ return ( -1 );
+ }
+ return static_cast<int>( length );
+}
+
+struct replxx_completions {
+ replxx::Replxx::completions_t data;
+};
+
+struct replxx_hints {
+ replxx::Replxx::hints_t data;
+};
+
+void modify_fwd( replxx_modify_callback_t fn, std::string& line_, int& cursorPosition_, void* userData_ ) {
+#ifdef _WIN32
+#define strdup _strdup
+#endif
+ char* s( strdup( line_.c_str() ) );
+#undef strdup
+ fn( &s, &cursorPosition_, userData_ );
+ line_ = s;
+ free( s );
+ return;
+}
+
+void replxx_set_modify_callback(::Replxx* replxx_, replxx_modify_callback_t* fn, void* userData) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_modify_callback( std::bind( &modify_fwd, fn, _1, _2, userData ) );
+}
+
+replxx::Replxx::completions_t completions_fwd( replxx_completion_callback_t fn, std::string const& input_, int& contextLen_, void* userData ) {
+ replxx_completions completions;
+ fn( input_.c_str(), &completions, &contextLen_, userData );
+ return ( completions.data );
+}
+
+/* Register a callback function to be called for tab-completion. */
+void replxx_set_completion_callback(::Replxx* replxx_, replxx_completion_callback_t* fn, void* userData) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_completion_callback( std::bind( &completions_fwd, fn, _1, _2, userData ) );
+}
+
+void highlighter_fwd( replxx_highlighter_callback_t fn, std::string const& input, replxx::Replxx::colors_t& colors, void* userData ) {
+ std::vector<ReplxxColor> colorsTmp( colors.size() );
+ std::transform(
+ colors.begin(),
+ colors.end(),
+ colorsTmp.begin(),
+ []( replxx::Replxx::Color c ) {
+ return ( static_cast<ReplxxColor>( c ) );
+ }
+ );
+ fn( input.c_str(), colorsTmp.data(), static_cast<int>( colors.size() ), userData );
+ std::transform(
+ colorsTmp.begin(),
+ colorsTmp.end(),
+ colors.begin(),
+ []( ReplxxColor c ) {
+ return ( static_cast<replxx::Replxx::Color>( c ) );
+ }
+ );
+}
+
+void replxx_set_highlighter_callback( ::Replxx* replxx_, replxx_highlighter_callback_t* fn, void* userData ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_highlighter_callback( std::bind( &highlighter_fwd, fn, _1, _2, userData ) );
+}
+
+replxx::Replxx::hints_t hints_fwd( replxx_hint_callback_t fn, std::string const& input_, int& contextLen_, replxx::Replxx::Color& color_, void* userData ) {
+ replxx_hints hints;
+ ReplxxColor c( static_cast<ReplxxColor>( color_ ) );
+ fn( input_.c_str(), &hints, &contextLen_, &c, userData );
+ return ( hints.data );
+}
+
+void replxx_set_hint_callback( ::Replxx* replxx_, replxx_hint_callback_t* fn, void* userData ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_hint_callback( std::bind( &hints_fwd, fn, _1, _2, _3, userData ) );
+}
+
+void replxx_add_hint(replxx_hints* lh, const char* str) {
+ lh->data.emplace_back(str);
+}
+
+void replxx_add_completion( replxx_completions* lc, const char* str ) {
+ lc->data.emplace_back( str );
+}
+
+void replxx_add_color_completion( replxx_completions* lc, const char* str, ReplxxColor color ) {
+ lc->data.emplace_back( str, static_cast<replxx::Replxx::Color>( color ) );
+}
+
+void replxx_history_add( ::Replxx* replxx_, const char* line ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->history_add( line );
+}
+
+void replxx_set_max_history_size( ::Replxx* replxx_, int len ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_max_history_size( len );
+}
+
+void replxx_set_max_hint_rows( ::Replxx* replxx_, int count ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_max_hint_rows( count );
+}
+
+void replxx_set_hint_delay( ::Replxx* replxx_, int milliseconds ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_hint_delay( milliseconds );
+}
+
+void replxx_set_completion_count_cutoff( ::Replxx* replxx_, int count ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_completion_count_cutoff( count );
+}
+
+void replxx_set_word_break_characters( ::Replxx* replxx_, char const* breakChars_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_word_break_characters( breakChars_ );
+}
+
+void replxx_set_double_tab_completion( ::Replxx* replxx_, int val ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_double_tab_completion( val ? true : false );
+}
+
+void replxx_set_complete_on_empty( ::Replxx* replxx_, int val ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_complete_on_empty( val ? true : false );
+}
+
+void replxx_set_no_color( ::Replxx* replxx_, int val ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_no_color( val ? true : false );
+}
+
+void replxx_set_beep_on_ambiguous_completion( ::Replxx* replxx_, int val ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_beep_on_ambiguous_completion( val ? true : false );
+}
+
+void replxx_set_immediate_completion( ::Replxx* replxx_, int val ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_immediate_completion( val ? true : false );
+}
+
+void replxx_set_unique_history( ::Replxx* replxx_, int val ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->set_unique_history( val ? true : false );
+}
+
+void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->enable_bracketed_paste();
+}
+
+void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->disable_bracketed_paste();
+}
+
+ReplxxHistoryScan* replxx_history_scan_start( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( reinterpret_cast<ReplxxHistoryScan*>( replxx->history_scan().release() ) );
+}
+
+void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) {
+ delete reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ );
+}
+
+int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) {
+ replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ ) );
+ bool hasNext( historyScan->next() );
+ if ( hasNext ) {
+ replxx::Replxx::HistoryEntry const& historyEntry( historyScan->get() );
+ historyEntry_->timestamp = historyEntry.timestamp().c_str();
+ historyEntry_->text = historyEntry.text().c_str();
+ }
+ return ( hasNext ? 0 : -1 );
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int replxx_history_sync( ::Replxx* replxx_, const char* filename ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( replxx->history_sync( filename ) ? 0 : -1 );
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int replxx_history_save( ::Replxx* replxx_, const char* filename ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( replxx->history_save( filename ) ? 0 : -1 );
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+int replxx_history_load( ::Replxx* replxx_, const char* filename ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( replxx->history_load( filename ) ? 0 : -1 );
+}
+
+void replxx_history_clear( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ replxx->history_clear();
+}
+
+int replxx_history_size( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( replxx->history_size() );
+}
+
+/* This special mode is used by replxx in order to print scan codes
+ * on screen for debugging / development purposes. It is implemented
+ * by the replxx-c-api-example program using the --keycodes option. */
+#ifdef __REPLXX_DEBUG__
+void replxx_debug_dump_print_codes(void) {
+ char quit[4];
+
+ printf(
+ "replxx key codes debugging mode.\n"
+ "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
+ if (enableRawMode() == -1) return;
+ memset(quit, ' ', 4);
+ while (1) {
+ char c;
+ int nread;
+
+#if _WIN32
+ nread = _read(STDIN_FILENO, &c, 1);
+#else
+ nread = read(STDIN_FILENO, &c, 1);
+#endif
+ if (nread <= 0) continue;
+ memmove(quit, quit + 1, sizeof(quit) - 1); /* shift string to left. */
+ quit[sizeof(quit) - 1] = c; /* Insert current char on the right. */
+ if (memcmp(quit, "quit", sizeof(quit)) == 0) break;
+
+ printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c,
+ (int)c);
+ printf("\r"); /* Go left edge manually, we are in raw mode. */
+ fflush(stdout);
+ }
+ disableRawMode();
+}
+#endif // __REPLXX_DEBUG__
+
+int replxx_install_window_change_handler( ::Replxx* replxx_ ) {
+ replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+ return ( replxx->install_window_change_handler() );
+}
+
diff --git a/contrib/replxx/src/replxx_impl.cxx b/contrib/replxx/src/replxx_impl.cxx
new file mode 100644
index 0000000..28152d5
--- /dev/null
+++ b/contrib/replxx/src/replxx_impl.cxx
@@ -0,0 +1,2248 @@
+#include <algorithm>
+#include <memory>
+#include <cerrno>
+#include <iostream>
+#include <chrono>
+
+#ifdef _WIN32
+
+#include <windows.h>
+#include <io.h>
+#if _MSC_VER < 1900
+#define snprintf _snprintf // Microsoft headers use underscores in some names
+#endif
+#define strcasecmp _stricmp
+#define write _write
+#define STDIN_FILENO 0
+
+#else /* _WIN32 */
+
+#include <unistd.h>
+#include <signal.h>
+
+#endif /* _WIN32 */
+
+#ifdef _WIN32
+#include "windows.hxx"
+#endif
+
+#include "replxx_impl.hxx"
+#include "utf8string.hxx"
+#include "prompt.hxx"
+#include "util.hxx"
+#include "terminal.hxx"
+#include "history.hxx"
+#include "replxx.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+namespace {
+
+namespace action_names {
+
+char const INSERT_CHARACTER[] = "insert_character";
+char const NEW_LINE[] = "new_line";
+char const MOVE_CURSOR_TO_BEGINING_OF_LINE[] = "move_cursor_to_begining_of_line";
+char const MOVE_CURSOR_TO_END_OF_LINE[] = "move_cursor_to_end_of_line";
+char const MOVE_CURSOR_LEFT[] = "move_cursor_left";
+char const MOVE_CURSOR_RIGHT[] = "move_cursor_right";
+char const MOVE_CURSOR_ONE_WORD_LEFT[] = "move_cursor_one_word_left";
+char const MOVE_CURSOR_ONE_WORD_RIGHT[] = "move_cursor_one_word_right";
+char const MOVE_CURSOR_ONE_SUBWORD_LEFT[] = "move_cursor_one_subword_left";
+char const MOVE_CURSOR_ONE_SUBWORD_RIGHT[] = "move_cursor_one_subword_right";
+char const KILL_TO_WHITESPACE_ON_LEFT[] = "kill_to_whitespace_on_left";
+char const KILL_TO_END_OF_WORD[] = "kill_to_end_of_word";
+char const KILL_TO_END_OF_SUBWORD[] = "kill_to_end_of_subword";
+char const KILL_TO_BEGINING_OF_WORD[] = "kill_to_begining_of_word";
+char const KILL_TO_BEGINING_OF_SUBWORD[] = "kill_to_begining_of_subword";
+char const KILL_TO_BEGINING_OF_LINE[] = "kill_to_begining_of_line";
+char const KILL_TO_END_OF_LINE[] = "kill_to_end_of_line";
+char const YANK[] = "yank";
+char const YANK_CYCLE[] = "yank_cycle";
+char const YANK_LAST_ARG[] = "yank_last_arg";
+char const CAPITALIZE_WORD[] = "capitalize_word";
+char const LOWERCASE_WORD[] = "lowercase_word";
+char const UPPERCASE_WORD[] = "uppercase_word";
+char const CAPITALIZE_SUBWORD[] = "capitalize_subword";
+char const LOWERCASE_SUBWORD[] = "lowercase_subword";
+char const UPPERCASE_SUBWORD[] = "uppercase_subword";
+char const TRANSPOSE_CHARACTERS[] = "transpose_characters";
+char const ABORT_LINE[] = "abort_line";
+char const SEND_EOF[] = "send_eof";
+char const TOGGLE_OVERWRITE_MODE[] = "toggle_overwrite_mode";
+char const DELETE_CHARACTER_UNDER_CURSOR[] = "delete_character_under_cursor";
+char const DELETE_CHARACTER_LEFT_OF_CURSOR[] = "delete_character_left_of_cursor";
+char const COMMIT_LINE[] = "commit_line";
+char const CLEAR_SCREEN[] = "clear_screen";
+char const COMPLETE_NEXT[] = "complete_next";
+char const COMPLETE_PREVIOUS[] = "complete_previous";
+char const HISTORY_NEXT[] = "history_next";
+char const HISTORY_PREVIOUS[] = "history_previous";
+char const HISTORY_LAST[] = "history_last";
+char const HISTORY_FIRST[] = "history_first";
+char const HINT_PREVIOUS[] = "hint_previous";
+char const HINT_NEXT[] = "hint_next";
+char const VERBATIM_INSERT[] = "verbatim_insert";
+char const SUSPEND[] = "suspend";
+char const COMPLETE_LINE[] = "complete_line";
+char const HISTORY_INCREMENTAL_SEARCH[] = "history_incremental_search";
+char const HISTORY_COMMON_PREFIX_SEARCH[] = "history_common_prefix_search";
+}
+
+static int const REPLXX_MAX_HINT_ROWS( 4 );
+/*
+ * All whitespaces and all non-alphanumerical characters from ASCII range
+ * with an exception of an underscore ('_').
+ */
+char const defaultWordBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?";
+/*
+ * All whitespaces and all non-alphanumerical characters from ASCII range
+ */
+char const defaultSubwordBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?_";
+static const char* unsupported_term[] = {"dumb", "cons25", "emacs", NULL};
+
+static bool isUnsupportedTerm(void) {
+ char* term = getenv("TERM");
+ if (term == NULL) {
+ return false;
+ }
+ for (int j = 0; unsupported_term[j]; ++j) {
+ if (!strcasecmp(term, unsupported_term[j])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int long long RAPID_REFRESH_MS = 1;
+int long long RAPID_REFRESH_US = RAPID_REFRESH_MS * 1000;
+
+inline int long long now_us( void ) {
+ return ( std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count() );
+}
+
+class IOModeGuard {
+ Terminal& _terminal;
+public:
+ IOModeGuard( Terminal& terminal_ )
+ : _terminal( terminal_ ) {
+ _terminal.disable_raw_mode();
+ }
+ ~IOModeGuard( void ) {
+ try {
+ _terminal.enable_raw_mode();
+ } catch ( ... ) {
+ }
+ }
+};
+
+}
+
+Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* )
+ : _utf8Buffer()
+ , _data()
+ , _pos( 0 )
+ , _display()
+ , _displayInputLength( 0 )
+ , _hint()
+ , _prefix( 0 )
+ , _hintSelection( -1 )
+ , _history()
+ , _killRing()
+ , _lastRefreshTime( now_us() )
+ , _refreshSkipped( false )
+ , _lastYankSize( 0 )
+ , _maxHintRows( REPLXX_MAX_HINT_ROWS )
+ , _hintDelay( 0 )
+ , _wordBreakChars( defaultWordBreakChars )
+ , _subwordBreakChars( defaultSubwordBreakChars )
+ , _completionCountCutoff( 100 )
+ , _overwrite( false )
+ , _doubleTabCompletion( false )
+ , _completeOnEmpty( true )
+ , _beepOnAmbiguousCompletion( false )
+ , _immediateCompletion( true )
+ , _bracketedPaste( false )
+ , _noColor( false )
+ , _namedActions()
+ , _keyPressHandlers()
+ , _terminal()
+ , _currentThread()
+ , _prompt( _terminal )
+ , _completionCallback( nullptr )
+ , _highlighterCallback( nullptr )
+ , _hintCallback( nullptr )
+ , _keyPresses()
+ , _messages()
+ , _completions()
+ , _completionContextLength( 0 )
+ , _completionSelection( -1 )
+ , _preloadedBuffer()
+ , _errorMessage()
+ , _previousSearchText()
+ , _modifiedState( false )
+ , _hintColor( Replxx::Color::GRAY )
+ , _hintsCache()
+ , _hintContextLenght( -1 )
+ , _hintSeed()
+ , _mutex() {
+ using namespace std::placeholders;
+ _namedActions[action_names::INSERT_CHARACTER] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::INSERT_CHARACTER, _1 );
+ _namedActions[action_names::NEW_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::NEW_LINE, _1 );
+ _namedActions[action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 );
+ _namedActions[action_names::MOVE_CURSOR_TO_END_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 );
+ _namedActions[action_names::MOVE_CURSOR_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 );
+ _namedActions[action_names::MOVE_CURSOR_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 );
+ _namedActions[action_names::MOVE_CURSOR_ONE_WORD_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 );
+ _namedActions[action_names::MOVE_CURSOR_ONE_WORD_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 );
+ _namedActions[action_names::MOVE_CURSOR_ONE_SUBWORD_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT, _1 );
+ _namedActions[action_names::MOVE_CURSOR_ONE_SUBWORD_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT, _1 );
+ _namedActions[action_names::KILL_TO_WHITESPACE_ON_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT, _1 );
+ _namedActions[action_names::KILL_TO_END_OF_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 );
+ _namedActions[action_names::KILL_TO_BEGINING_OF_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD, _1 );
+ _namedActions[action_names::KILL_TO_END_OF_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_SUBWORD, _1 );
+ _namedActions[action_names::KILL_TO_BEGINING_OF_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_SUBWORD, _1 );
+ _namedActions[action_names::KILL_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE, _1 );
+ _namedActions[action_names::KILL_TO_END_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE, _1 );
+ _namedActions[action_names::YANK] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK, _1 );
+ _namedActions[action_names::YANK_CYCLE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 );
+ _namedActions[action_names::YANK_LAST_ARG] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_LAST_ARG, _1 );
+ _namedActions[action_names::CAPITALIZE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 );
+ _namedActions[action_names::LOWERCASE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 );
+ _namedActions[action_names::UPPERCASE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 );
+ _namedActions[action_names::CAPITALIZE_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_SUBWORD, _1 );
+ _namedActions[action_names::LOWERCASE_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_SUBWORD, _1 );
+ _namedActions[action_names::UPPERCASE_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_SUBWORD, _1 );
+ _namedActions[action_names::TRANSPOSE_CHARACTERS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS, _1 );
+ _namedActions[action_names::ABORT_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE, _1 );
+ _namedActions[action_names::SEND_EOF] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF, _1 );
+ _namedActions[action_names::TOGGLE_OVERWRITE_MODE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE, _1 );
+ _namedActions[action_names::DELETE_CHARACTER_UNDER_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 );
+ _namedActions[action_names::DELETE_CHARACTER_LEFT_OF_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 );
+ _namedActions[action_names::COMMIT_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 );
+ _namedActions[action_names::CLEAR_SCREEN] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN, _1 );
+ _namedActions[action_names::COMPLETE_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT, _1 );
+ _namedActions[action_names::COMPLETE_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS, _1 );
+ _namedActions[action_names::HISTORY_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 );
+ _namedActions[action_names::HISTORY_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 );
+ _namedActions[action_names::HISTORY_LAST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 );
+ _namedActions[action_names::HISTORY_FIRST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 );
+ _namedActions[action_names::HINT_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS, _1 );
+ _namedActions[action_names::HINT_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT, _1 );
+#ifndef _WIN32
+ _namedActions[action_names::VERBATIM_INSERT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT, _1 );
+ _namedActions[action_names::SUSPEND] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND, _1 );
+#else
+ _namedActions[action_names::VERBATIM_INSERT] = _namedActions[action_names::SUSPEND] = Replxx::key_press_handler_t();
+#endif
+ _namedActions[action_names::COMPLETE_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE, _1 );
+ _namedActions[action_names::HISTORY_INCREMENTAL_SEARCH] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 );
+ _namedActions[action_names::HISTORY_COMMON_PREFIX_SEARCH] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 );
+
+ bind_key( Replxx::KEY::control( 'A' ), _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) );
+ bind_key( Replxx::KEY::HOME + 0, _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) );
+ bind_key( Replxx::KEY::control( 'E' ), _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) );
+ bind_key( Replxx::KEY::END + 0, _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) );
+ bind_key( Replxx::KEY::control( 'B' ), _namedActions.at( action_names::MOVE_CURSOR_LEFT ) );
+ bind_key( Replxx::KEY::LEFT + 0, _namedActions.at( action_names::MOVE_CURSOR_LEFT ) );
+ bind_key( Replxx::KEY::control( 'F' ), _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) );
+ bind_key( Replxx::KEY::RIGHT + 0, _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) );
+ bind_key( Replxx::KEY::meta( 'b' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) );
+ bind_key( Replxx::KEY::meta( 'B' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_SUBWORD_LEFT ) );
+ bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) );
+ bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); // Emacs allows Meta, readline don't
+ bind_key( Replxx::KEY::meta( 'f' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) );
+ bind_key( Replxx::KEY::meta( 'F' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_SUBWORD_RIGHT ) );
+ bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) );
+ bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); // Emacs allows Meta, readline don't
+ bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), _namedActions.at( action_names::KILL_TO_WHITESPACE_ON_LEFT ) );
+ bind_key( Replxx::KEY::meta( 'd' ), _namedActions.at( action_names::KILL_TO_END_OF_WORD ) );
+ bind_key( Replxx::KEY::meta( 'D' ), _namedActions.at( action_names::KILL_TO_END_OF_SUBWORD ) );
+ bind_key( Replxx::KEY::control( 'W' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_WORD ) );
+ bind_key( Replxx::KEY::meta( 'W' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_SUBWORD ) );
+ bind_key( Replxx::KEY::control( 'U' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_LINE ) );
+ bind_key( Replxx::KEY::control( 'K' ), _namedActions.at( action_names::KILL_TO_END_OF_LINE ) );
+ bind_key( Replxx::KEY::control( 'Y' ), _namedActions.at( action_names::YANK ) );
+ bind_key( Replxx::KEY::meta( 'y' ), _namedActions.at( action_names::YANK_CYCLE ) );
+ bind_key( Replxx::KEY::meta( 'Y' ), _namedActions.at( action_names::YANK_CYCLE ) );
+ bind_key( Replxx::KEY::meta( '.' ), _namedActions.at( action_names::YANK_LAST_ARG ) );
+ bind_key( Replxx::KEY::meta( 'c' ), _namedActions.at( action_names::CAPITALIZE_WORD ) );
+ bind_key( Replxx::KEY::meta( 'C' ), _namedActions.at( action_names::CAPITALIZE_SUBWORD ) );
+ bind_key( Replxx::KEY::meta( 'l' ), _namedActions.at( action_names::LOWERCASE_WORD ) );
+ bind_key( Replxx::KEY::meta( 'L' ), _namedActions.at( action_names::LOWERCASE_SUBWORD ) );
+ bind_key( Replxx::KEY::meta( 'u' ), _namedActions.at( action_names::UPPERCASE_WORD ) );
+ bind_key( Replxx::KEY::meta( 'U' ), _namedActions.at( action_names::UPPERCASE_SUBWORD ) );
+ bind_key( Replxx::KEY::control( 'T' ), _namedActions.at( action_names::TRANSPOSE_CHARACTERS ) );
+ bind_key( Replxx::KEY::control( 'C' ), _namedActions.at( action_names::ABORT_LINE ) );
+ bind_key( Replxx::KEY::control( 'D' ), _namedActions.at( action_names::SEND_EOF ) );
+ bind_key( Replxx::KEY::INSERT + 0, _namedActions.at( action_names::TOGGLE_OVERWRITE_MODE ) );
+ bind_key( 127, _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) );
+ bind_key( Replxx::KEY::DELETE + 0, _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) );
+ bind_key( Replxx::KEY::BACKSPACE + 0, _namedActions.at( action_names::DELETE_CHARACTER_LEFT_OF_CURSOR ) );
+ bind_key( Replxx::KEY::control( 'J' ), _namedActions.at( action_names::NEW_LINE ) );
+ bind_key( Replxx::KEY::ENTER + 0, _namedActions.at( action_names::COMMIT_LINE ) );
+ bind_key( Replxx::KEY::control( 'L' ), _namedActions.at( action_names::CLEAR_SCREEN ) );
+ bind_key( Replxx::KEY::control( 'N' ), _namedActions.at( action_names::COMPLETE_NEXT ) );
+ bind_key( Replxx::KEY::control( 'P' ), _namedActions.at( action_names::COMPLETE_PREVIOUS ) );
+ bind_key( Replxx::KEY::DOWN + 0, _namedActions.at( action_names::HISTORY_NEXT ) );
+ bind_key( Replxx::KEY::UP + 0, _namedActions.at( action_names::HISTORY_PREVIOUS ) );
+ bind_key( Replxx::KEY::meta( '<' ), _namedActions.at( action_names::HISTORY_FIRST ) );
+ bind_key( Replxx::KEY::PAGE_UP + 0, _namedActions.at( action_names::HISTORY_FIRST ) );
+ bind_key( Replxx::KEY::meta( '>' ), _namedActions.at( action_names::HISTORY_LAST ) );
+ bind_key( Replxx::KEY::PAGE_DOWN + 0, _namedActions.at( action_names::HISTORY_LAST ) );
+ bind_key( Replxx::KEY::control( Replxx::KEY::UP ), _namedActions.at( action_names::HINT_PREVIOUS ) );
+ bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ), _namedActions.at( action_names::HINT_NEXT ) );
+#ifndef _WIN32
+ bind_key( Replxx::KEY::control( 'V' ), _namedActions.at( action_names::VERBATIM_INSERT ) );
+ bind_key( Replxx::KEY::control( 'Z' ), _namedActions.at( action_names::SUSPEND ) );
+#endif
+ bind_key( Replxx::KEY::TAB + 0, _namedActions.at( action_names::COMPLETE_LINE ) );
+ bind_key( Replxx::KEY::control( 'R' ), _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) );
+ bind_key( Replxx::KEY::control( 'S' ), _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) );
+ bind_key( Replxx::KEY::meta( 'p' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
+ bind_key( Replxx::KEY::meta( 'P' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
+ bind_key( Replxx::KEY::meta( 'n' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
+ bind_key( Replxx::KEY::meta( 'N' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
+ bind_key( Replxx::KEY::PASTE_START, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::BRACKETED_PASTE, _1 ) );
+}
+
+Replxx::ReplxxImpl::~ReplxxImpl( void ) {
+ disable_bracketed_paste();
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32_t code ) {
+ switch ( action_ ) {
+ case ( Replxx::ACTION::INSERT_CHARACTER ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, code ) );
+ case ( Replxx::ACTION::NEW_LINE ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::new_line, code ) );
+ case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::delete_character, code ) );
+ case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::backspace_character, code ) );
+ case ( Replxx::ACTION::KILL_TO_END_OF_LINE ): return ( action( WANT_REFRESH | SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_end_of_line, code ) );
+ case ( Replxx::ACTION::KILL_TO_BEGINING_OF_LINE ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_begining_of_line, code ) );
+ case ( Replxx::ACTION::KILL_TO_END_OF_WORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_right<false>, code ) );
+ case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left<false>, code ) );
+ case ( Replxx::ACTION::KILL_TO_END_OF_SUBWORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_right<true>, code ) );
+ case ( Replxx::ACTION::KILL_TO_BEGINING_OF_SUBWORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left<true>, code ) );
+ case ( Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_whitespace_to_left, code ) );
+ case ( Replxx::ACTION::YANK ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank, code ) );
+ case ( Replxx::ACTION::YANK_CYCLE ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank_cycle, code ) );
+ case ( Replxx::ACTION::YANK_LAST_ARG ): return ( action( HISTORY_RECALL_MOST_RECENT | DONT_RESET_HIST_YANK_INDEX, &Replxx::ReplxxImpl::yank_last_arg, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_begining_of_line, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_end_of_line, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left<false>, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right<false>, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left<true>, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right<true>, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_left, code ) );
+ case ( Replxx::ACTION::MOVE_CURSOR_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_right, code ) );
+ case ( Replxx::ACTION::HISTORY_NEXT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_next, code ) );
+ case ( Replxx::ACTION::HISTORY_PREVIOUS ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_previous, code ) );
+ case ( Replxx::ACTION::HISTORY_FIRST ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_first, code ) );
+ case ( Replxx::ACTION::HISTORY_LAST ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_last, code ) );
+ case ( Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH ): return ( action( NOOP, &Replxx::ReplxxImpl::incremental_history_search, code ) );
+ case ( Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH ): return ( action( RESET_KILL_ACTION | DONT_RESET_PREFIX, &Replxx::ReplxxImpl::common_prefix_search, code ) );
+ case ( Replxx::ACTION::HINT_NEXT ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_next, code ) );
+ case ( Replxx::ACTION::HINT_PREVIOUS ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_previous, code ) );
+ case ( Replxx::ACTION::CAPITALIZE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word<false>, code ) );
+ case ( Replxx::ACTION::LOWERCASE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word<false>, code ) );
+ case ( Replxx::ACTION::UPPERCASE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word<false>, code ) );
+ case ( Replxx::ACTION::CAPITALIZE_SUBWORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word<true>, code ) );
+ case ( Replxx::ACTION::LOWERCASE_SUBWORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word<true>, code ) );
+ case ( Replxx::ACTION::UPPERCASE_SUBWORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word<true>, code ) );
+ case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::transpose_characters, code ) );
+ case ( Replxx::ACTION::TOGGLE_OVERWRITE_MODE ): return ( action( NOOP, &Replxx::ReplxxImpl::toggle_overwrite_mode, code ) );
+#ifndef _WIN32
+ case ( Replxx::ACTION::VERBATIM_INSERT ): return ( action( WANT_REFRESH | RESET_KILL_ACTION, &Replxx::ReplxxImpl::verbatim_insert, code ) );
+ case ( Replxx::ACTION::SUSPEND ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::suspend, code ) );
+#endif
+ case ( Replxx::ACTION::CLEAR_SCREEN ): return ( action( NOOP, &Replxx::ReplxxImpl::clear_screen, code ) );
+ case ( Replxx::ACTION::CLEAR_SELF ): clear_self_to_end_of_screen(); return ( Replxx::ACTION_RESULT::CONTINUE );
+ case ( Replxx::ACTION::REPAINT ): repaint(); return ( Replxx::ACTION_RESULT::CONTINUE );
+ case ( Replxx::ACTION::COMPLETE_LINE ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_line, code ) );
+ case ( Replxx::ACTION::COMPLETE_NEXT ): return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_next, code ) );
+ case ( Replxx::ACTION::COMPLETE_PREVIOUS ): return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_previous, code ) );
+ case ( Replxx::ACTION::COMMIT_LINE ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::commit_line, code ) );
+ case ( Replxx::ACTION::ABORT_LINE ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::abort_line, code ) );
+ case ( Replxx::ACTION::SEND_EOF ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::send_eof, code ) );
+ case ( Replxx::ACTION::BRACKETED_PASTE ): return ( action( WANT_REFRESH | RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::bracketed_paste, code ) );
+ }
+ return ( Replxx::ACTION_RESULT::BAIL );
+}
+
+void Replxx::ReplxxImpl::bind_key( char32_t code_, Replxx::key_press_handler_t handler_ ) {
+ _keyPressHandlers[code_] = handler_;
+}
+
+void Replxx::ReplxxImpl::bind_key_internal( char32_t code_, char const* actionName_ ) {
+ named_actions_t::const_iterator it( _namedActions.find( actionName_ ) );
+ if ( it == _namedActions.end() ) {
+ throw std::runtime_error( std::string( "replxx: Unknown action name: " ).append( actionName_ ) );
+ }
+ if ( !! it->second ) {
+ bind_key( code_, it->second );
+ }
+}
+
+Replxx::State Replxx::ReplxxImpl::get_state( void ) const {
+ _utf8Buffer.assign( _data );
+ return ( Replxx::State( _utf8Buffer.get(), _pos ) );
+}
+
+void Replxx::ReplxxImpl::set_state( Replxx::State const& state_ ) {
+ _data.assign( state_.text() );
+ if ( state_.cursor_position() >= 0 ) {
+ _pos = min( state_.cursor_position(), _data.length() );
+ }
+ _modifiedState = true;
+}
+
+char32_t Replxx::ReplxxImpl::read_char( HINT_ACTION hintAction_ ) {
+ /* try scheduled key presses */ {
+ std::lock_guard<std::mutex> l( _mutex );
+ if ( !_keyPresses.empty() ) {
+ char32_t keyPress( _keyPresses.front() );
+ _keyPresses.pop_front();
+ return ( keyPress );
+ }
+ }
+ int hintDelay(
+ _refreshSkipped
+ ? static_cast<int>( RAPID_REFRESH_MS * 2 )
+ : ( hintAction_ != HINT_ACTION::SKIP ? _hintDelay : 0 )
+ );
+ while ( true ) {
+ Terminal::EVENT_TYPE eventType( _terminal.wait_for_input( hintDelay ) );
+ if ( eventType == Terminal::EVENT_TYPE::TIMEOUT ) {
+ refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::REPAINT );
+ hintDelay = 0;
+ _refreshSkipped = false;
+ continue;
+ }
+ if ( eventType == Terminal::EVENT_TYPE::KEY_PRESS ) {
+ break;
+ }
+ if ( eventType == Terminal::EVENT_TYPE::RESIZE ) {
+ // caught a window resize event
+ // now redraw the prompt and line
+ _prompt.update_screen_columns();
+ // redraw the original prompt with current input
+ refresh_line( HINT_ACTION::REPAINT );
+ continue;
+ }
+ std::lock_guard<std::mutex> l( _mutex );
+ clear_self_to_end_of_screen();
+ while ( ! _messages.empty() ) {
+ string const& message( _messages.front() );
+ _terminal.write8( message.data(), static_cast<int>( message.length() ) );
+ _messages.pop_front();
+ }
+ repaint();
+ }
+ /* try scheduled key presses */ {
+ std::lock_guard<std::mutex> l( _mutex );
+ if ( !_keyPresses.empty() ) {
+ char32_t keyPress( _keyPresses.front() );
+ _keyPresses.pop_front();
+ return ( keyPress );
+ }
+ }
+ return ( _terminal.read_char() );
+}
+
+void Replxx::ReplxxImpl::clear( void ) {
+ _pos = 0;
+ _prefix = 0;
+ _completions.clear();
+ _completionContextLength = 0;
+ _completionSelection = -1;
+ _data.clear();
+ _hintSelection = -1;
+ _hint = UnicodeString();
+ _display.clear();
+ _displayInputLength = 0;
+}
+
+void Replxx::ReplxxImpl::call_modify_callback( void ) {
+ if ( ! _modifyCallback ) {
+ return;
+ }
+ _utf8Buffer.assign( _data );
+ std::string origLine( _utf8Buffer.get() );
+ int pos( _pos );
+ std::string line( origLine );
+ /* IOModeGuard scope */ {
+ IOModeGuard ioModeGuard( _terminal );
+ _modifyCallback( line, pos );
+ }
+ if ( ( pos != _pos ) || ( line != origLine ) ) {
+ _data.assign( line.c_str() );
+ _pos = min( pos, _data.length() );
+ _modifiedState = true;
+ }
+}
+
+Replxx::ReplxxImpl::completions_t Replxx::ReplxxImpl::call_completer( std::string const& input, int& contextLen_ ) const {
+ Replxx::completions_t completionsIntermediary(
+ !! _completionCallback
+ ? _completionCallback( input, contextLen_ )
+ : Replxx::completions_t()
+ );
+ completions_t completions;
+ completions.reserve( completionsIntermediary.size() );
+ for ( Replxx::Completion const& c : completionsIntermediary ) {
+ completions.emplace_back( c );
+ }
+ return ( completions );
+}
+
+Replxx::ReplxxImpl::hints_t Replxx::ReplxxImpl::call_hinter( std::string const& input, int& contextLen, Replxx::Color& color ) const {
+ Replxx::hints_t hintsIntermediary(
+ !! _hintCallback
+ ? _hintCallback( input, contextLen, color )
+ : Replxx::hints_t()
+ );
+ hints_t hints;
+ hints.reserve( hintsIntermediary.size() );
+ for ( std::string const& h : hintsIntermediary ) {
+ hints.emplace_back( h.c_str() );
+ }
+ return ( hints );
+}
+
+void Replxx::ReplxxImpl::set_preload_buffer( std::string const& preloadText ) {
+ _preloadedBuffer = preloadText;
+ // remove characters that won't display correctly
+ bool controlsStripped = false;
+ int whitespaceSeen( 0 );
+ for ( std::string::iterator it( _preloadedBuffer.begin() ); it != _preloadedBuffer.end(); ) {
+ unsigned char c = *it;
+ if ( '\r' == c ) { // silently skip CR
+ _preloadedBuffer.erase( it, it + 1 );
+ continue;
+ }
+ if ( ( '\n' == c ) || ( '\t' == c ) ) { // note newline or tab
+ ++ whitespaceSeen;
+ ++ it;
+ continue;
+ }
+ if ( whitespaceSeen > 0 ) {
+ it -= whitespaceSeen;
+ *it = ' ';
+ _preloadedBuffer.erase( it + 1, it + whitespaceSeen - 1 );
+ }
+ if ( is_control_code( c ) ) { // remove other control characters, flag for message
+ controlsStripped = true;
+ if ( whitespaceSeen > 0 ) {
+ _preloadedBuffer.erase( it, it + 1 );
+ -- it;
+ } else {
+ *it = ' ';
+ }
+ }
+ whitespaceSeen = 0;
+ ++ it;
+ }
+ if ( whitespaceSeen > 0 ) {
+ std::string::iterator it = _preloadedBuffer.end() - whitespaceSeen;
+ *it = ' ';
+ if ( whitespaceSeen > 1 ) {
+ _preloadedBuffer.erase( it + 1, _preloadedBuffer.end() );
+ }
+ }
+ _errorMessage.clear();
+ if ( controlsStripped ) {
+ _errorMessage.assign( " [Edited line: control characters were converted to spaces]\n" );
+ }
+}
+
+char const* Replxx::ReplxxImpl::read_from_stdin( void ) {
+ if ( _preloadedBuffer.empty() ) {
+ getline( cin, _preloadedBuffer );
+ if ( ! cin.good() ) {
+ return nullptr;
+ }
+ }
+ while ( ! _preloadedBuffer.empty() && ( ( _preloadedBuffer.back() == '\r' ) || ( _preloadedBuffer.back() == '\n' ) ) ) {
+ _preloadedBuffer.pop_back();
+ }
+ _utf8Buffer.assign( _preloadedBuffer );
+ _preloadedBuffer.clear();
+ return _utf8Buffer.get();
+}
+
+void Replxx::ReplxxImpl::emulate_key_press( char32_t keyCode_ ) {
+ std::lock_guard<std::mutex> l( _mutex );
+ _keyPresses.push_back( keyCode_ );
+ if ( ( _currentThread != std::thread::id() ) && ( _currentThread != std::this_thread::get_id() ) ) {
+ _terminal.notify_event( Terminal::EVENT_TYPE::KEY_PRESS );
+ }
+}
+
+char const* Replxx::ReplxxImpl::input( std::string const& prompt ) {
+ try {
+ errno = 0;
+ if ( ! tty::in ) { // input not from a terminal, we should work with piped input, i.e. redirected stdin
+ return ( read_from_stdin() );
+ }
+ if (!_errorMessage.empty()) {
+ printf("%s", _errorMessage.c_str());
+ fflush(stdout);
+ _errorMessage.clear();
+ }
+ if ( isUnsupportedTerm() ) {
+ cout << prompt << flush;
+ fflush(stdout);
+ return ( read_from_stdin() );
+ }
+ if (_terminal.enable_raw_mode() == -1) {
+ return nullptr;
+ }
+ _prompt.set_text( UnicodeString( prompt ) );
+ _currentThread = std::this_thread::get_id();
+ clear();
+ if (!_preloadedBuffer.empty()) {
+ preload_puffer(_preloadedBuffer.c_str());
+ _preloadedBuffer.clear();
+ }
+ if ( get_input_line() == -1 ) {
+ return ( finalize_input( nullptr ) );
+ }
+ _terminal.write8( "\n", 1 );
+ _utf8Buffer.assign( _data );
+ return ( finalize_input( _utf8Buffer.get() ) );
+ } catch ( std::exception const& ) {
+ return ( finalize_input( nullptr ) );
+ }
+}
+
+char const* Replxx::ReplxxImpl::finalize_input( char const* retVal_ ) {
+ _currentThread = std::thread::id();
+ _terminal.disable_raw_mode();
+ return ( retVal_ );
+}
+
+int Replxx::ReplxxImpl::install_window_change_handler( void ) {
+#ifndef _WIN32
+ return ( _terminal.install_window_change_handler() );
+#else
+ return 0;
+#endif
+}
+
+void Replxx::ReplxxImpl::enable_bracketed_paste( void ) {
+ if ( _bracketedPaste ) {
+ return;
+ }
+ _terminal.enable_bracketed_paste();
+ _bracketedPaste = true;
+}
+
+void Replxx::ReplxxImpl::disable_bracketed_paste( void ) {
+ if ( ! _bracketedPaste ) {
+ return;
+ }
+ _terminal.disable_bracketed_paste();
+ _bracketedPaste = false;
+}
+
+void Replxx::ReplxxImpl::print( char const* str_, int size_ ) {
+ if ( ( _currentThread == std::thread::id() ) || ( _currentThread == std::this_thread::get_id() ) ) {
+ _terminal.write8( str_, size_ );
+ } else {
+ std::lock_guard<std::mutex> l( _mutex );
+ _messages.emplace_back( str_, size_ );
+ _terminal.notify_event( Terminal::EVENT_TYPE::MESSAGE );
+ }
+ return;
+}
+
+void Replxx::ReplxxImpl::preload_puffer(const char* preloadText) {
+ _data.assign( preloadText );
+ _prefix = _pos = _data.length();
+}
+
+void Replxx::ReplxxImpl::set_color( Replxx::Color color_ ) {
+ char const* code( ansi_color( color_ ) );
+ while ( *code ) {
+ _display.push_back( *code );
+ ++ code;
+ }
+}
+
+void Replxx::ReplxxImpl::render( char32_t ch ) {
+ if ( ch == Replxx::KEY::ESCAPE ) {
+ _display.push_back( '^' );
+ _display.push_back( '[' );
+ } else if ( is_control_code( ch ) && ( ch != '\n' ) ) {
+ _display.push_back( '^' );
+ _display.push_back( control_to_human( ch ) );
+ } else {
+ _display.push_back( ch );
+ }
+ return;
+}
+
+void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) {
+ if ( hintAction_ == HINT_ACTION::TRIM ) {
+ _display.erase( _display.begin() + _displayInputLength, _display.end() );
+ _modifiedState = false;
+ return;
+ }
+ if ( hintAction_ == HINT_ACTION::SKIP ) {
+ return;
+ }
+ _display.clear();
+ if ( _noColor ) {
+ for ( char32_t ch : _data ) {
+ render( ch );
+ }
+ _displayInputLength = static_cast<int>( _display.size() );
+ _modifiedState = false;
+ return;
+ }
+ Replxx::colors_t colors( _data.length(), Replxx::Color::DEFAULT );
+ _utf8Buffer.assign( _data );
+ if ( !! _highlighterCallback ) {
+ IOModeGuard ioModeGuard( _terminal );
+ _highlighterCallback( _utf8Buffer.get(), colors );
+ }
+ paren_info_t pi( matching_paren() );
+ if ( pi.index != -1 ) {
+ colors[pi.index] = pi.error ? Replxx::Color::ERROR : Replxx::Color::BRIGHTRED;
+ }
+ Replxx::Color c( Replxx::Color::DEFAULT );
+ for ( int i( 0 ); i < _data.length(); ++ i ) {
+ if ( colors[i] != c ) {
+ c = colors[i];
+ set_color( c );
+ }
+ render( _data[i] );
+ }
+ set_color( Replxx::Color::DEFAULT );
+ _displayInputLength = static_cast<int>( _display.size() );
+ _modifiedState = false;
+ return;
+}
+
+int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) {
+ if ( _noColor ) {
+ return ( 0 );
+ }
+ if ( ! _hintCallback ) {
+ return ( 0 );
+ }
+ if ( ( _hintDelay > 0 ) && ( hintAction_ != HINT_ACTION::REPAINT ) ) {
+ _hintSelection = -1;
+ return ( 0 );
+ }
+ if ( ( hintAction_ == HINT_ACTION::SKIP ) || ( hintAction_ == HINT_ACTION::TRIM ) ) {
+ return ( 0 );
+ }
+ if ( _pos != _data.length() ) {
+ return ( 0 );
+ }
+ _hint = UnicodeString();
+ int len( 0 );
+ if ( hintAction_ == HINT_ACTION::REGENERATE ) {
+ _hintSelection = -1;
+ }
+ _utf8Buffer.assign( _data, _pos );
+ if ( ( _utf8Buffer != _hintSeed ) || ( _hintContextLenght < 0 ) ) {
+ _hintSeed.assign( _utf8Buffer );
+ _hintContextLenght = context_length();
+ _hintColor = Replxx::Color::GRAY;
+ IOModeGuard ioModeGuard( _terminal );
+ _hintsCache = call_hinter( _utf8Buffer.get(), _hintContextLenght, _hintColor );
+ }
+ int hintCount( static_cast<int>( _hintsCache.size() ) );
+ if ( hintCount == 1 ) {
+ _hint = _hintsCache.front();
+ len = _hint.length() - _hintContextLenght;
+ if ( len > 0 ) {
+ set_color( _hintColor );
+ for ( int i( 0 ); i < len; ++ i ) {
+ _display.push_back( _hint[i + _hintContextLenght] );
+ }
+ set_color( Replxx::Color::DEFAULT );
+ }
+ } else if ( ( _maxHintRows > 0 ) && ( hintCount > 0 ) ) {
+ int startCol( _prompt.indentation() + _pos );
+ int maxCol( _prompt.screen_columns() );
+#ifdef _WIN32
+ -- maxCol;
+#endif
+ if ( _hintSelection < -1 ) {
+ _hintSelection = hintCount - 1;
+ } else if ( _hintSelection >= hintCount ) {
+ _hintSelection = -1;
+ }
+ if ( _hintSelection != -1 ) {
+ _hint = _hintsCache[_hintSelection];
+ len = min<int>( _hint.length(), maxCol - startCol );
+ if ( _hintContextLenght < len ) {
+ set_color( _hintColor );
+ for ( int i( _hintContextLenght ); i < len; ++ i ) {
+ _display.push_back( _hint[i] );
+ }
+ set_color( Replxx::Color::DEFAULT );
+ }
+ }
+ startCol -= _hintContextLenght;
+ for ( int hintRow( 0 ); hintRow < min( hintCount, _maxHintRows ); ++ hintRow ) {
+#ifdef _WIN32
+ _display.push_back( '\r' );
+#endif
+ _display.push_back( '\n' );
+ int col( 0 );
+ for ( int i( 0 ); ( i < startCol ) && ( col < maxCol ); ++ i, ++ col ) {
+ _display.push_back( ' ' );
+ }
+ set_color( _hintColor );
+ for ( int i( _pos - _hintContextLenght ); ( i < _pos ) && ( col < maxCol ); ++ i, ++ col ) {
+ _display.push_back( _data[i] );
+ }
+ int hintNo( hintRow + _hintSelection + 1 );
+ if ( hintNo == hintCount ) {
+ continue;
+ } else if ( hintNo > hintCount ) {
+ -- hintNo;
+ }
+ UnicodeString const& h( _hintsCache[hintNo % hintCount] );
+ for ( int i( _hintContextLenght ); ( i < h.length() ) && ( col < maxCol ); ++ i, ++ col ) {
+ _display.push_back( h[i] );
+ }
+ set_color( Replxx::Color::DEFAULT );
+ }
+ }
+ return ( len );
+}
+
+Replxx::ReplxxImpl::paren_info_t Replxx::ReplxxImpl::matching_paren( void ) {
+ if (_pos >= _data.length()) {
+ return ( paren_info_t{ -1, false } );
+ }
+ /* this scans for a brace matching _data[_pos] to highlight */
+ unsigned char part1, part2;
+ int scanDirection = 0;
+ if ( strchr("}])", _data[_pos]) ) {
+ scanDirection = -1; /* backwards */
+ if (_data[_pos] == '}') {
+ part1 = '}'; part2 = '{';
+ } else if (_data[_pos] == ']') {
+ part1 = ']'; part2 = '[';
+ } else {
+ part1 = ')'; part2 = '(';
+ }
+ } else if ( strchr("{[(", _data[_pos]) ) {
+ scanDirection = 1; /* forwards */
+ if (_data[_pos] == '{') {
+ //part1 = '{'; part2 = '}';
+ part1 = '}'; part2 = '{';
+ } else if (_data[_pos] == '[') {
+ //part1 = '['; part2 = ']';
+ part1 = ']'; part2 = '[';
+ } else {
+ //part1 = '('; part2 = ')';
+ part1 = ')'; part2 = '(';
+ }
+ } else {
+ return ( paren_info_t{ -1, false } );
+ }
+ int highlightIdx = -1;
+ bool indicateError = false;
+ int unmatched = scanDirection;
+ int unmatchedOther = 0;
+ for (int i = _pos + scanDirection; i >= 0 && i < _data.length(); i += scanDirection) {
+ /* TODO: the right thing when inside a string */
+ if (strchr("}])", _data[i])) {
+ if (_data[i] == part1) {
+ --unmatched;
+ } else {
+ --unmatchedOther;
+ }
+ } else if (strchr("{[(", _data[i])) {
+ if (_data[i] == part2) {
+ ++unmatched;
+ } else {
+ ++unmatchedOther;
+ }
+ }
+
+ if (unmatched == 0) {
+ highlightIdx = i;
+ indicateError = (unmatchedOther != 0);
+ break;
+ }
+ }
+ return ( paren_info_t{ highlightIdx, indicateError } );
+}
+
+/**
+ * Refresh the user's input line: the prompt is already onscreen and is not
+ * redrawn here screen position
+ */
+void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) {
+ int long long now( now_us() );
+ int long long duration( now - _lastRefreshTime );
+ if ( duration < RAPID_REFRESH_US ) {
+ _lastRefreshTime = now;
+ _refreshSkipped = true;
+ return;
+ }
+ _refreshSkipped = false;
+ // check for a matching brace/bracket/paren, remember its position if found
+ render( hintAction_ );
+ int hintLen( handle_hints( hintAction_ ) );
+ // calculate the position of the end of the input line
+ int xEndOfInput( 0 ), yEndOfInput( 0 );
+ calculate_screen_position(
+ _prompt.indentation(), 0, _prompt.screen_columns(),
+ calculate_displayed_length( _data.get(), _data.length() ) + hintLen,
+ xEndOfInput, yEndOfInput
+ );
+ yEndOfInput += static_cast<int>( count( _display.begin(), _display.end(), '\n' ) );
+
+ // calculate the desired position of the cursor
+ int xCursorPos( 0 ), yCursorPos( 0 );
+ calculate_screen_position(
+ _prompt.indentation(), 0, _prompt.screen_columns(),
+ calculate_displayed_length( _data.get(), _pos ),
+ xCursorPos, yCursorPos
+ );
+
+ // position at the end of the prompt, clear to end of previous input
+ _terminal.set_cursor_visible( false );
+ _terminal.jump_cursor(
+ _prompt.indentation(), // 0-based on Win32
+ -( _prompt._cursorRowOffset - _prompt._extraLines )
+ );
+ // display the input line
+ _terminal.write32( _display.data(), _displayInputLength );
+ _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
+ _terminal.write32( _display.data() + _displayInputLength, static_cast<int>( _display.size() ) - _displayInputLength );
+#ifndef _WIN32
+ // we have to generate our own newline on line wrap
+ if ( ( xEndOfInput == 0 ) && ( yEndOfInput > 0 ) ) {
+ _terminal.write8( "\n", 1 );
+ }
+#endif
+ // position the cursor
+ _terminal.jump_cursor( xCursorPos, -( yEndOfInput - yCursorPos ) );
+ _terminal.set_cursor_visible( true );
+ _prompt._cursorRowOffset = _prompt._extraLines + yCursorPos; // remember row for next pass
+ _lastRefreshTime = now_us();
+}
+
+int Replxx::ReplxxImpl::context_length() {
+ int prefixLength = _pos;
+ while ( prefixLength > 0 ) {
+ if ( is_word_break_character<false>( _data[prefixLength - 1] ) ) {
+ break;
+ }
+ -- prefixLength;
+ }
+ return ( _pos - prefixLength );
+}
+
+void Replxx::ReplxxImpl::repaint( void ) {
+ _prompt.write();
+ for ( int i( _prompt._extraLines ); i < _prompt._cursorRowOffset; ++ i ) {
+ _terminal.write8( "\n", 1 );
+ }
+ refresh_line( HINT_ACTION::SKIP );
+}
+
+void Replxx::ReplxxImpl::clear_self_to_end_of_screen( Prompt const* prompt_ ) {
+ // position at the start of the prompt, clear to end of previous input
+ _terminal.jump_cursor( 0, prompt_ ? -prompt_->_cursorRowOffset : -_prompt._cursorRowOffset );
+ _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
+ return;
+}
+
+namespace {
+int longest_common_prefix( Replxx::ReplxxImpl::completions_t const& completions ) {
+ int completionsCount( static_cast<int>( completions.size() ) );
+ if ( completionsCount < 1 ) {
+ return ( 0 );
+ }
+ int longestCommonPrefix( 0 );
+ UnicodeString const& sample( completions.front().text() );
+ while ( true ) {
+ if ( longestCommonPrefix >= sample.length() ) {
+ return ( longestCommonPrefix );
+ }
+ char32_t sc( sample[longestCommonPrefix] );
+ for ( int i( 1 ); i < completionsCount; ++ i ) {
+ UnicodeString const& candidate( completions[i].text() );
+ if ( longestCommonPrefix >= candidate.length() ) {
+ return ( longestCommonPrefix );
+ }
+ char32_t cc( candidate[longestCommonPrefix] );
+ if ( cc != sc ) {
+ return ( longestCommonPrefix );
+ }
+ }
+ ++ longestCommonPrefix;
+ }
+}
+}
+
+/**
+ * Handle command completion, using a completionCallback() routine to provide
+ * possible substitutions
+ * This routine handles the mechanics of updating the user's input buffer with
+ * possible replacement of text as the user selects a proposed completion string,
+ * or cancels the completion attempt.
+ * @param pi - Prompt struct holding information about the prompt and our
+ * screen position
+ */
+char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) {
+ char32_t c = 0;
+
+ // completionCallback() expects a parsable entity, so find the previous break
+ // character and
+ // extract a copy to parse. we also handle the case where tab is hit while
+ // not at end-of-line.
+
+ _utf8Buffer.assign( _data, _pos );
+ // get a list of completions
+ _completionSelection = -1;
+ _completionContextLength = context_length();
+ /* IOModeGuard scope */ {
+ IOModeGuard ioModeGuard( _terminal );
+ _completions = call_completer( _utf8Buffer.get(), _completionContextLength );
+ }
+
+ // if no completions, we are done
+ if ( _completions.empty() ) {
+ beep();
+ return 0;
+ }
+
+ // at least one completion
+ int longestCommonPrefix = 0;
+ int completionsCount( static_cast<int>( _completions.size() ) );
+ int selectedCompletion( 0 );
+ if ( _hintSelection != -1 ) {
+ selectedCompletion = _hintSelection;
+ completionsCount = 1;
+ }
+ if ( completionsCount == 1 ) {
+ longestCommonPrefix = static_cast<int>( _completions[selectedCompletion].text().length() );
+ } else {
+ longestCommonPrefix = longest_common_prefix( _completions );
+ }
+ if ( _beepOnAmbiguousCompletion && ( completionsCount != 1 ) ) { // beep if ambiguous
+ beep();
+ }
+
+ // if we can extend the item, extend it and return to main loop
+ if ( ( longestCommonPrefix > _completionContextLength ) || ( completionsCount == 1 ) ) {
+ _pos -= _completionContextLength;
+ _data.erase( _pos, _completionContextLength );
+ _data.insert( _pos, _completions[selectedCompletion].text(), 0, longestCommonPrefix );
+ _pos = _pos + longestCommonPrefix;
+ _completionContextLength = longestCommonPrefix;
+ refresh_line();
+ return 0;
+ }
+
+ if ( ! showCompletions_ ) {
+ return ( 0 );
+ }
+
+ if ( _doubleTabCompletion ) {
+ // we can't complete any further, wait for second tab
+ do {
+ c = read_char();
+ } while ( c == static_cast<char32_t>( -1 ) );
+
+ // if any character other than tab, pass it to the main loop
+ if ( c != Replxx::KEY::TAB ) {
+ return c;
+ }
+ }
+
+ // we got a second tab, maybe show list of possible completions
+ bool showCompletions = true;
+ bool onNewLine = false;
+ if ( static_cast<int>( _completions.size() ) > _completionCountCutoff ) {
+ int savePos = _pos; // move cursor to EOL to avoid overwriting the command line
+ _pos = _data.length();
+ refresh_line();
+ _pos = savePos;
+ printf( "\nDisplay all %u possibilities? (y or n)", static_cast<unsigned int>( _completions.size() ) );
+ fflush(stdout);
+ onNewLine = true;
+ while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != Replxx::KEY::control('C')) {
+ do {
+ c = read_char();
+ } while (c == static_cast<char32_t>(-1));
+ }
+ switch (c) {
+ case 'n':
+ case 'N':
+ showCompletions = false;
+ break;
+ case Replxx::KEY::control('C'):
+ showCompletions = false;
+ // Display the ^C we got
+ _terminal.write8( "^C", 2 );
+ c = 0;
+ break;
+ }
+ }
+
+ // if showing the list, do it the way readline does it
+ bool stopList( false );
+ if ( showCompletions ) {
+ int longestCompletion( 0 );
+ for ( size_t j( 0 ); j < _completions.size(); ++ j ) {
+ int itemLength( static_cast<int>( _completions[j].text().length() ) );
+ if ( itemLength > longestCompletion ) {
+ longestCompletion = itemLength;
+ }
+ }
+ longestCompletion += 2;
+ int columnCount = _prompt.screen_columns() / longestCompletion;
+ if ( columnCount < 1 ) {
+ columnCount = 1;
+ }
+ if ( ! onNewLine ) { // skip this if we showed "Display all %d possibilities?"
+ int savePos = _pos; // move cursor to EOL to avoid overwriting the command line
+ _pos = _data.length();
+ refresh_line( HINT_ACTION::TRIM );
+ _pos = savePos;
+ } else {
+ _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
+ }
+ size_t pauseRow = _terminal.get_screen_rows() - 1;
+ size_t rowCount = (_completions.size() + columnCount - 1) / columnCount;
+ for (size_t row = 0; row < rowCount; ++row) {
+ if (row == pauseRow) {
+ printf("\n--More--");
+ fflush(stdout);
+ c = 0;
+ bool doBeep = false;
+ while (c != ' ' && c != Replxx::KEY::ENTER && c != 'y' && c != 'Y' &&
+ c != 'n' && c != 'N' && c != 'q' && c != 'Q' &&
+ c != Replxx::KEY::control('C')) {
+ if (doBeep) {
+ beep();
+ }
+ doBeep = true;
+ do {
+ c = read_char();
+ } while (c == static_cast<char32_t>(-1));
+ }
+ switch (c) {
+ case ' ':
+ case 'y':
+ case 'Y':
+ printf("\r \r");
+ pauseRow += _terminal.get_screen_rows() - 1;
+ break;
+ case Replxx::KEY::ENTER:
+ printf("\r \r");
+ ++pauseRow;
+ break;
+ case 'n':
+ case 'N':
+ case 'q':
+ case 'Q':
+ printf("\r \r");
+ stopList = true;
+ break;
+ case Replxx::KEY::control('C'):
+ // Display the ^C we got
+ _terminal.write8( "^C", 2 );
+ stopList = true;
+ break;
+ }
+ } else {
+ _terminal.write8( "\n", 1 );
+ }
+ if (stopList) {
+ break;
+ }
+ static UnicodeString const res( ansi_color( Replxx::Color::DEFAULT ) );
+ for (int column = 0; column < columnCount; ++column) {
+ size_t index = (column * rowCount) + row;
+ if ( index < _completions.size() ) {
+ Completion const& c( _completions[index] );
+ int itemLength = static_cast<int>(c.text().length());
+ fflush(stdout);
+
+ if ( longestCommonPrefix > 0 ) {
+ static UnicodeString const col( ansi_color( Replxx::Color::BRIGHTMAGENTA ) );
+ if (!_noColor) {
+ _terminal.write32(col.get(), col.length());
+ }
+ _terminal.write32(&_data[_pos - _completionContextLength], longestCommonPrefix);
+ if (!_noColor) {
+ _terminal.write32(res.get(), res.length());
+ }
+ }
+
+ if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) {
+ UnicodeString ac( ansi_color( c.color() ) );
+ _terminal.write32( ac.get(), ac.length() );
+ }
+ _terminal.write32( c.text().get() + longestCommonPrefix, itemLength - longestCommonPrefix );
+ if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) {
+ _terminal.write32( res.get(), res.length() );
+ }
+
+ if ( ((column + 1) * rowCount) + row < _completions.size() ) {
+ for ( int k( itemLength ); k < longestCompletion; ++k ) {
+ printf( " " );
+ }
+ }
+ }
+ }
+ }
+ fflush(stdout);
+ }
+
+ // display the prompt on a new line, then redisplay the input buffer
+ if (!stopList || c == Replxx::KEY::control('C')) {
+ _terminal.write8( "\n", 1 );
+ }
+ _prompt.write();
+ _prompt._cursorRowOffset = _prompt._extraLines;
+ refresh_line();
+ return 0;
+}
+
+int Replxx::ReplxxImpl::get_input_line( void ) {
+ // The latest history entry is always our current buffer
+ if ( _data.length() > 0 ) {
+ _history.add( _data );
+ } else {
+ _history.add( UnicodeString() );
+ }
+ _history.jump( false, false );
+
+ // display the prompt
+ _prompt.write();
+
+ // the cursor starts out at the end of the prompt
+ _prompt._cursorRowOffset = _prompt._extraLines;
+
+ // kill and yank start in "other" mode
+ _killRing.lastAction = KillRing::actionOther;
+
+ // if there is already text in the buffer, display it first
+ if (_data.length() > 0) {
+ refresh_line();
+ }
+
+ // loop collecting characters, respond to line editing characters
+ Replxx::ACTION_RESULT next( Replxx::ACTION_RESULT::CONTINUE );
+ while ( next == Replxx::ACTION_RESULT::CONTINUE ) {
+ int c( read_char( HINT_ACTION::REPAINT ) ); // get a new keystroke
+
+ if (c == 0) {
+ return _data.length();
+ }
+
+ if (c == -1) {
+ refresh_line();
+ continue;
+ }
+
+ if (c == -2) {
+ _prompt.write();
+ refresh_line();
+ continue;
+ }
+
+ key_press_handlers_t::iterator it( _keyPressHandlers.find( c ) );
+ if ( it != _keyPressHandlers.end() ) {
+ next = it->second( c );
+ if ( _modifiedState ) {
+ refresh_line();
+ }
+ } else {
+ next = action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, c );
+ }
+ }
+ return ( next == Replxx::ACTION_RESULT::RETURN ? _data.length() : -1 );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, key_press_handler_raw_t const& handler_, char32_t code_ ) {
+ Replxx::ACTION_RESULT res( ( this->*handler_ )( code_ ) );
+ call_modify_callback();
+ if ( actionTrait_ & HISTORY_RECALL_MOST_RECENT ) {
+ _history.reset_recall_most_recent();
+ }
+ if ( actionTrait_ & RESET_KILL_ACTION ) {
+ _killRing.lastAction = KillRing::actionOther;
+ }
+ if ( actionTrait_ & SET_KILL_ACTION ) {
+ _killRing.lastAction = KillRing::actionKill;
+ }
+ if ( ! ( actionTrait_ & DONT_RESET_PREFIX ) ) {
+ _prefix = _pos;
+ }
+ if ( ! ( actionTrait_ & DONT_RESET_COMPLETIONS ) ) {
+ _completions.clear();
+ _completionSelection = -1;
+ _completionContextLength = 0;
+ }
+ if ( ! ( actionTrait_ & DONT_RESET_HIST_YANK_INDEX ) ) {
+ _history.reset_yank_iterator();
+ }
+ if ( actionTrait_ & WANT_REFRESH ) {
+ _modifiedState = true;
+ }
+ return ( res );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) {
+ /*
+ * beep on unknown Ctrl and/or Meta keys
+ * don't insert control characters
+ */
+ if ( ( c >= static_cast<int>( Replxx::KEY::BASE ) ) || ( is_control_code( c ) && ( c != '\n' ) ) ) {
+ beep();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ if ( ! _overwrite || ( _pos >= _data.length() ) ) {
+ _data.insert( _pos, c );
+ } else {
+ _data[_pos] = c;
+ }
+ ++ _pos;
+ call_modify_callback();
+ int long long now( now_us() );
+ int long long duration( now - _lastRefreshTime );
+ if ( duration < RAPID_REFRESH_US ) {
+ _lastRefreshTime = now;
+ _refreshSkipped = true;
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ int inputLen = calculate_displayed_length( _data.get(), _data.length() );
+ if (
+ ( _pos == _data.length() )
+ && ! _modifiedState
+ && ( _noColor || ! ( !! _highlighterCallback || !! _hintCallback ) )
+ && ( _prompt.indentation() + inputLen < _prompt.screen_columns() )
+ ) {
+ /* Avoid a full assign of the line in the
+ * trivial case. */
+ render( c );
+ _displayInputLength = static_cast<int>( _display.size() );
+ _terminal.write32( reinterpret_cast<char32_t*>( &c ), 1 );
+ } else {
+ refresh_line();
+ }
+ _lastRefreshTime = now_us();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-J/linefeed/newline
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::new_line( char32_t ) {
+ return ( insert_character( '\n' ) );
+}
+
+// ctrl-A, HOME: move cursor to start of line
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_begining_of_line( char32_t ) {
+ _pos = 0;
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_end_of_line( char32_t ) {
+ _pos = _data.length();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-B, move cursor left by one character
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_left( char32_t ) {
+ if (_pos > 0) {
+ --_pos;
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-F, move cursor right by one character
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_right( char32_t ) {
+ if ( _pos < _data.length() ) {
+ ++_pos;
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-B, move cursor left by one word
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) {
+ if (_pos > 0) {
+ while (_pos > 0 && is_word_break_character<subword>( _data[_pos - 1] ) ) {
+ --_pos;
+ }
+ while (_pos > 0 && !is_word_break_character<subword>( _data[_pos - 1] ) ) {
+ --_pos;
+ }
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-f, move cursor right by one word
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) {
+ if ( _pos < _data.length() ) {
+ while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
+ ++_pos;
+ }
+ while ( _pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
+ ++_pos;
+ }
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-Backspace, kill word to left of cursor
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) {
+ if ( _pos > 0 ) {
+ int startingPos = _pos;
+ while ( _pos > 0 && is_word_break_character<subword>( _data[_pos - 1] ) ) {
+ -- _pos;
+ }
+ while ( _pos > 0 && !is_word_break_character<subword>( _data[_pos - 1] ) ) {
+ -- _pos;
+ }
+ _killRing.kill( _data.get() + _pos, startingPos - _pos, false);
+ _data.erase( _pos, startingPos - _pos );
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-D, kill word to right of cursor
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) {
+ if ( _pos < _data.length() ) {
+ int endingPos = _pos;
+ while ( endingPos < _data.length() && is_word_break_character<subword>( _data[endingPos] ) ) {
+ ++ endingPos;
+ }
+ while ( endingPos < _data.length() && !is_word_break_character<subword>( _data[endingPos] ) ) {
+ ++ endingPos;
+ }
+ _killRing.kill( _data.get() + _pos, endingPos - _pos, true );
+ _data.erase( _pos, endingPos - _pos );
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-W, kill to whitespace (not word) to left of cursor
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_whitespace_to_left( char32_t ) {
+ if ( _pos > 0 ) {
+ int startingPos = _pos;
+ while ( ( _pos > 0 ) && isspace( _data[_pos - 1] ) ) {
+ --_pos;
+ }
+ while ( ( _pos > 0 ) && ! isspace( _data[_pos - 1] ) ) {
+ -- _pos;
+ }
+ _killRing.kill( _data.get() + _pos, startingPos - _pos, false );
+ _data.erase( _pos, startingPos - _pos );
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-K, kill from cursor to end of line
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_end_of_line( char32_t ) {
+ _killRing.kill( _data.get() + _pos, _data.length() - _pos, true );
+ _data.erase( _pos, _data.length() - _pos );
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-U, kill all characters to the left of the cursor
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_begining_of_line( char32_t ) {
+ if (_pos > 0) {
+ _killRing.kill( _data.get(), _pos, false );
+ _data.erase( 0, _pos );
+ _pos = 0;
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-Y, yank killed text
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank( char32_t ) {
+ UnicodeString* restoredText( _killRing.yank() );
+ if ( restoredText ) {
+ _data.insert( _pos, *restoredText, 0, restoredText->length() );
+ _pos += restoredText->length();
+ refresh_line();
+ _killRing.lastAction = KillRing::actionYank;
+ _lastYankSize = restoredText->length();
+ } else {
+ beep();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-Y, "yank-pop", rotate popped text
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_cycle( char32_t ) {
+ if ( _killRing.lastAction != KillRing::actionYank ) {
+ beep();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ UnicodeString* restoredText = _killRing.yankPop();
+ if ( !restoredText ) {
+ beep();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ _pos -= _lastYankSize;
+ _data.erase( _pos, _lastYankSize );
+ _data.insert( _pos, *restoredText, 0, restoredText->length() );
+ _pos += restoredText->length();
+ _lastYankSize = restoredText->length();
+ refresh_line();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-., "yank-last-arg", on consecutive uses move back in history for popped text
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_last_arg( char32_t ) {
+ if ( _history.size() < 2 ) {
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ if ( _history.next_yank_position() ) {
+ _lastYankSize = 0;
+ }
+ UnicodeString const& histLine( _history.yank_line() );
+ int endPos( histLine.length() );
+ while ( ( endPos > 0 ) && isspace( histLine[endPos - 1] ) ) {
+ -- endPos;
+ }
+ int startPos( endPos );
+ while ( ( startPos > 0 ) && ! isspace( histLine[startPos - 1] ) ) {
+ -- startPos;
+ }
+ _pos -= _lastYankSize;
+ _data.erase( _pos, _lastYankSize );
+ _lastYankSize = endPos - startPos;
+ _data.insert( _pos, histLine, startPos, _lastYankSize );
+ _pos += _lastYankSize;
+ refresh_line();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-C, give word initial Cap
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) {
+ if (_pos < _data.length()) {
+ while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
+ ++_pos;
+ }
+ if (_pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
+ if ( _data[_pos] >= 'a' && _data[_pos] <= 'z' ) {
+ _data[_pos] += 'A' - 'a';
+ }
+ ++_pos;
+ }
+ while (_pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
+ if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) {
+ _data[_pos] += 'a' - 'A';
+ }
+ ++_pos;
+ }
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-L, lowercase word
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) {
+ if (_pos < _data.length()) {
+ while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
+ ++ _pos;
+ }
+ while (_pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
+ if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) {
+ _data[_pos] += 'a' - 'A';
+ }
+ ++ _pos;
+ }
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-U, uppercase word
+template <bool subword>
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) {
+ if (_pos < _data.length()) {
+ while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
+ ++ _pos;
+ }
+ while ( _pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
+ if ( _data[_pos] >= 'a' && _data[_pos] <= 'z') {
+ _data[_pos] += 'A' - 'a';
+ }
+ ++ _pos;
+ }
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-T, transpose characters
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::transpose_characters( char32_t ) {
+ if ( _pos > 0 && _data.length() > 1 ) {
+ size_t leftCharPos = ( _pos == _data.length() ) ? _pos - 2 : _pos - 1;
+ char32_t aux = _data[leftCharPos];
+ _data[leftCharPos] = _data[leftCharPos + 1];
+ _data[leftCharPos + 1] = aux;
+ if ( _pos != _data.length() ) {
+ ++_pos;
+ }
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-C, abort this line
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::abort_line( char32_t ) {
+ errno = EAGAIN;
+ _history.drop_last();
+ // we need one last refresh with the cursor at the end of the line
+ // so we don't display the next prompt over the previous input line
+ _pos = _data.length(); // pass _data.length() as _pos for EOL
+ _lastRefreshTime = 0;
+ refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM );
+ _terminal.write8( "^C\r\n", 4 );
+ return ( Replxx::ACTION_RESULT::BAIL );
+}
+
+// DEL, delete the character under the cursor
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::delete_character( char32_t ) {
+ if ( ( _data.length() > 0 ) && ( _pos < _data.length() ) ) {
+ _data.erase( _pos );
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-D, delete the character under the cursor
+// on an empty line, exit the shell
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::send_eof( char32_t key_ ) {
+ if ( _data.length() == 0 ) {
+ _history.drop_last();
+ return ( Replxx::ACTION_RESULT::BAIL );
+ }
+ return ( delete_character( key_ ) );
+}
+
+// backspace/ctrl-H, delete char to left of cursor
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::backspace_character( char32_t ) {
+ if ( _pos > 0 ) {
+ -- _pos;
+ _data.erase( _pos );
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-M/return/enter, accept line
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::commit_line( char32_t ) {
+ // we need one last refresh with the cursor at the end of the line
+ // so we don't display the next prompt over the previous input line
+ _pos = _data.length(); // pass _data.length() as _pos for EOL
+ _lastRefreshTime = 0;
+ refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM );
+ _history.commit_index();
+ _history.drop_last();
+ return ( Replxx::ACTION_RESULT::RETURN );
+}
+
+// Down, recall next line in history
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) {
+ return ( history_move( false ) );
+}
+
+// Up, recall previous line in history
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) {
+ return ( history_move( true ) );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_move( bool previous_ ) {
+ // if not already recalling, add the current line to the history list so
+ // we don't
+ // have to special case it
+ if ( _history.is_last() ) {
+ _history.update_last( _data );
+ }
+ if ( _history.is_empty() ) {
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ if ( ! _history.move( previous_ ) ) {
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ _data.assign( _history.current() );
+ _pos = _data.length();
+ refresh_line();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-<, beginning of history
+// Page Up, beginning of history
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_first( char32_t ) {
+ return ( history_jump( true ) );
+}
+
+// meta->, end of history
+// Page Down, end of history
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_last( char32_t ) {
+ return ( history_jump( false ) );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_jump( bool back_ ) {
+ // if not already recalling, add the current line to the history list so
+ // we don't
+ // have to special case it
+ if ( _history.is_last() ) {
+ _history.update_last( _data );
+ }
+ if ( ! _history.is_empty() ) {
+ _history.jump( back_ );
+ _data.assign( _history.current() );
+ _pos = _data.length();
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_next( char32_t ) {
+ return ( hint_move( false ) );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_previous( char32_t ) {
+ return ( hint_move( true ) );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_move( bool previous_ ) {
+ if ( ! _noColor ) {
+ _killRing.lastAction = KillRing::actionOther;
+ if ( previous_ ) {
+ -- _hintSelection;
+ } else {
+ ++ _hintSelection;
+ }
+ refresh_line( HINT_ACTION::REPAINT );
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::toggle_overwrite_mode( char32_t ) {
+ _overwrite = ! _overwrite;
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+#ifndef _WIN32
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::verbatim_insert( char32_t ) {
+ static int const MAX_ESC_SEQ( 32 );
+ char32_t buf[MAX_ESC_SEQ];
+ int len( _terminal.read_verbatim( buf, MAX_ESC_SEQ ) );
+ _data.insert( _pos, UnicodeString( buf, len ), 0, len );
+ _pos += len;
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-Z, job control
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::suspend( char32_t ) {
+ /* IOModeGuard scope */ {
+ IOModeGuard ioModeGuard( _terminal );
+ raise( SIGSTOP ); // Break out in mid-line
+ }
+ // Redraw prompt
+ _prompt.write();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+#endif
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_line( char32_t c ) {
+ if ( !! _completionCallback && ( _completeOnEmpty || ( _pos > 0 ) ) ) {
+ // complete_line does the actual completion and replacement
+ c = do_complete_line( c != 0 );
+
+ if ( static_cast<int>( c ) < 0 ) {
+ return ( Replxx::ACTION_RESULT::BAIL );
+ }
+ if ( c != 0 ) {
+ emulate_key_press( c );
+ }
+ } else {
+ insert_character( c );
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete( bool previous_ ) {
+ if ( _completions.empty() ) {
+ bool first( _completions.empty() );
+ int dataLen( _data.length() );
+ complete_line( 0 );
+ if ( ! _immediateCompletion && first && ( _data.length() > dataLen ) ) {
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+ }
+ }
+ int newSelection( _completionSelection + ( previous_ ? -1 : 1 ) );
+ if ( newSelection >= static_cast<int>( _completions.size() ) ) {
+ newSelection = -1;
+ } else if ( newSelection == -2 ) {
+ newSelection = static_cast<int>( _completions.size() ) - 1;
+ }
+ if ( _completionSelection != -1 ) {
+ int oldCompletionLength( max( _completions[_completionSelection].text().length() - _completionContextLength, 0 ) );
+ _pos -= oldCompletionLength;
+ _data.erase( _pos, oldCompletionLength );
+ }
+ if ( newSelection != -1 ) {
+ int newCompletionLength( max( _completions[newSelection].text().length() - _completionContextLength, 0 ) );
+ _data.insert( _pos, _completions[newSelection].text(), _completionContextLength, newCompletionLength );
+ _pos += newCompletionLength;
+ }
+ _completionSelection = newSelection;
+ refresh_line(); // Refresh the line
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_next( char32_t ) {
+ return ( complete( false ) );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_previous( char32_t ) {
+ return ( complete( true ) );
+}
+
+// Alt-P, reverse history search for prefix
+// Alt-P, reverse history search for prefix
+// Alt-N, forward history search for prefix
+// Alt-N, forward history search for prefix
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::common_prefix_search( char32_t startChar ) {
+ int prefixSize( calculate_displayed_length( _data.get(), _prefix ) );
+ if (
+ _history.common_prefix_search(
+ _data, prefixSize, ( startChar == ( Replxx::KEY::meta( 'p' ) ) ) || ( startChar == ( Replxx::KEY::meta( 'P' ) ) )
+ )
+ ) {
+ _data.assign( _history.current() );
+ _pos = _data.length();
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-R, reverse history search
+// ctrl-S, forward history search
+/**
+ * Incremental history search -- take over the prompt and keyboard as the user
+ * types a search string, deletes characters from it, changes _direction,
+ * and either accepts the found line (for execution orediting) or cancels.
+ * @param startChar - the character that began the search, used to set the initial
+ * _direction
+ */
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t startChar ) {
+ // if not already recalling, add the current line to the history list so we
+ // don't have to special case it
+ if ( _history.is_last() ) {
+ _history.update_last( _data );
+ }
+ _history.save_pos();
+ int historyLinePosition( _pos );
+ clear_self_to_end_of_screen();
+
+ DynamicPrompt dp( _terminal, (startChar == Replxx::KEY::control('R')) ? -1 : 1 );
+
+ // draw user's text with our prompt
+ dynamicRefresh(_prompt, dp, _data.get(), _data.length(), historyLinePosition);
+
+ // loop until we get an exit character
+ char32_t c( 0 );
+ bool keepLooping = true;
+ bool useSearchedLine = true;
+ bool searchAgain = false;
+ UnicodeString activeHistoryLine;
+ while ( keepLooping ) {
+ c = read_char();
+
+ switch (c) {
+ // these characters keep the selected text but do not execute it
+ case Replxx::KEY::control('A'): // ctrl-A, move cursor to start of line
+ case Replxx::KEY::HOME:
+ case Replxx::KEY::control('B'): // ctrl-B, move cursor left by one character
+ case Replxx::KEY::LEFT:
+ case Replxx::KEY::meta( 'b' ): // meta-B, move cursor left by one word
+ case Replxx::KEY::meta( 'B' ):
+ case Replxx::KEY::control( Replxx::KEY::LEFT ):
+ case Replxx::KEY::meta( Replxx::KEY::LEFT ): // Emacs allows Meta, bash & readline don't
+ case Replxx::KEY::control('D'):
+ case Replxx::KEY::meta( 'd' ): // meta-D, kill word to right of cursor
+ case Replxx::KEY::meta( 'D' ):
+ case Replxx::KEY::control('E'): // ctrl-E, move cursor to end of line
+ case Replxx::KEY::END:
+ case Replxx::KEY::control('F'): // ctrl-F, move cursor right by one character
+ case Replxx::KEY::RIGHT:
+ case Replxx::KEY::meta( 'f' ): // meta-F, move cursor right by one word
+ case Replxx::KEY::meta( 'F' ):
+ case Replxx::KEY::control( Replxx::KEY::RIGHT ):
+ case Replxx::KEY::meta( Replxx::KEY::RIGHT ): // Emacs allows Meta, bash & readline don't
+ case Replxx::KEY::meta( Replxx::KEY::BACKSPACE ):
+ case Replxx::KEY::control('J'):
+ case Replxx::KEY::control('K'): // ctrl-K, kill from cursor to end of line
+ case Replxx::KEY::ENTER:
+ case Replxx::KEY::control('N'): // ctrl-N, recall next line in history
+ case Replxx::KEY::control('P'): // ctrl-P, recall previous line in history
+ case Replxx::KEY::DOWN:
+ case Replxx::KEY::UP:
+ case Replxx::KEY::control('T'): // ctrl-T, transpose characters
+ case Replxx::KEY::control('U'): // ctrl-U, kill all characters to the left of the cursor
+ case Replxx::KEY::control('W'):
+ case Replxx::KEY::meta( 'y' ): // meta-Y, "yank-pop", rotate popped text
+ case Replxx::KEY::meta( 'Y' ):
+ case 127:
+ case Replxx::KEY::DELETE:
+ case Replxx::KEY::meta( '<' ): // start of history
+ case Replxx::KEY::PAGE_UP:
+ case Replxx::KEY::meta( '>' ): // end of history
+ case Replxx::KEY::PAGE_DOWN: {
+ keepLooping = false;
+ } break;
+
+ // these characters revert the input line to its previous state
+ case Replxx::KEY::control('C'): // ctrl-C, abort this line
+ case Replxx::KEY::control('G'):
+ case Replxx::KEY::control('L'): { // ctrl-L, clear screen and redisplay line
+ keepLooping = false;
+ useSearchedLine = false;
+ if (c != Replxx::KEY::control('L')) {
+ c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else
+ }
+ } break;
+
+ // these characters stay in search mode and assign the display
+ case Replxx::KEY::control('S'):
+ case Replxx::KEY::control('R'): {
+ if ( dp._searchText.length() == 0 ) { // if no current search text, recall previous text
+ if ( _previousSearchText.length() > 0 ) {
+ dp._searchText = _previousSearchText;
+ }
+ }
+ if (
+ ( ( dp._direction == 1 ) && ( c == Replxx::KEY::control( 'R' ) ) )
+ || ( ( dp._direction == -1 ) && ( c == Replxx::KEY::control( 'S' ) ) )
+ ) {
+ dp._direction = 0 - dp._direction; // reverse direction
+ dp.updateSearchPrompt(); // change the prompt
+ } else {
+ searchAgain = true; // same direction, search again
+ }
+ } break;
+
+// job control is its own thing
+#ifndef _WIN32
+ case Replxx::KEY::control('Z'): { // ctrl-Z, job control
+ /* IOModeGuard scope */ {
+ IOModeGuard ioModeGuard( _terminal );
+ // Returning to Linux (whatever) shell, leave raw mode
+ // Break out in mid-line
+ // Back from Linux shell, re-enter raw mode
+ raise( SIGSTOP );
+ }
+ dynamicRefresh( dp, dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition );
+ continue;
+ } break;
+#endif
+
+ // these characters assign the search string, and hence the selected input line
+ case Replxx::KEY::BACKSPACE: { // backspace/ctrl-H, delete char to left of cursor
+ if ( dp._searchText.length() > 0 ) {
+ dp._searchText.erase( dp._searchText.length() - 1 );
+ dp.updateSearchPrompt();
+ _history.restore_pos();
+ historyLinePosition = _pos;
+ } else {
+ beep();
+ }
+ } break;
+
+ case Replxx::KEY::control('Y'): { // ctrl-Y, yank killed text
+ } break;
+
+ default: {
+ if ( ! is_control_code( c ) && ( c < static_cast<int>( Replxx::KEY::BASE ) ) ) { // not an action character
+ dp._searchText.insert( dp._searchText.length(), c );
+ dp.updateSearchPrompt();
+ } else {
+ beep();
+ }
+ }
+ } // switch
+
+ // if we are staying in search mode, search now
+ if ( ! keepLooping ) {
+ break;
+ }
+ activeHistoryLine.assign( _history.current() );
+ if ( dp._searchText.length() > 0 ) {
+ bool found = false;
+ int lineSearchPos = historyLinePosition;
+ if ( searchAgain ) {
+ lineSearchPos += dp._direction;
+ }
+ searchAgain = false;
+ while ( true ) {
+ while (
+ dp._direction < 0
+ ? ( lineSearchPos >= 0 )
+ : ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() )
+ ) {
+ if (
+ ( lineSearchPos >= 0 )
+ && ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() )
+ && std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos )
+ ) {
+ found = true;
+ break;
+ }
+ lineSearchPos += dp._direction;
+ }
+ if ( found ) {
+ historyLinePosition = lineSearchPos;
+ break;
+ } else if ( _history.move( dp._direction < 0 ) ) {
+ activeHistoryLine.assign( _history.current() );
+ lineSearchPos = ( dp._direction > 0 ) ? 0 : ( activeHistoryLine.length() - dp._searchText.length() );
+ } else {
+ historyLinePosition = _pos;
+ beep();
+ break;
+ }
+ } // while
+ if ( ! found ) {
+ _history.restore_pos();
+ }
+ } else {
+ _history.restore_pos();
+ historyLinePosition = _pos;
+ }
+ activeHistoryLine.assign( _history.current() );
+ dynamicRefresh( dp, dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition ); // draw user's text with our prompt
+ } // while
+
+ // leaving history search, restore previous prompt, maybe make searched line
+ // current
+ Prompt pb( _terminal );
+ UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], _prompt._text.length() - _prompt._lastLinePosition );
+ pb.set_text( tempUnicode );
+ pb.update_screen_columns();
+ if ( useSearchedLine && ( activeHistoryLine.length() > 0 ) ) {
+ _history.commit_index();
+ _data.assign( activeHistoryLine );
+ _pos = historyLinePosition;
+ _modifiedState = true;
+ } else if ( ! useSearchedLine ) {
+ _history.restore_pos();
+ }
+ dynamicRefresh(pb, _prompt, _data.get(), _data.length(), _pos); // redraw the original prompt with current input
+ _previousSearchText = dp._searchText; // save search text for possible reuse on ctrl-R ctrl-R
+ emulate_key_press( c ); // pass a character or -1 back to main loop
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-L, clear screen and redisplay line
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::clear_screen( char32_t c ) {
+ _terminal.clear_screen( Terminal::CLEAR_SCREEN::WHOLE );
+ if ( c ) {
+ _prompt.write();
+ _prompt._cursorRowOffset = _prompt._extraLines;
+ refresh_line();
+ }
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::bracketed_paste( char32_t ) {
+ UnicodeString buf;
+ while ( char32_t c = _terminal.read_char() ) {
+ if ( c == KEY::PASTE_FINISH ) {
+ break;
+ }
+ if ( ( c == '\r' ) || ( c == KEY::control( 'M' ) ) ) {
+ c = '\n';
+ }
+ buf.push_back( c );
+ }
+ _data.insert( _pos, buf, 0, buf.length() );
+ _pos += buf.length();
+ return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+template <bool subword>
+bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const {
+ bool wbc( false );
+ if ( char_ < 128 ) {
+ wbc = strchr( subword ? _subwordBreakChars.c_str() : _wordBreakChars.c_str(), static_cast<char>( char_ ) ) != nullptr;
+ }
+ return ( wbc );
+}
+
+void Replxx::ReplxxImpl::history_add( std::string const& line ) {
+ _history.add( UnicodeString( line ) );
+}
+
+bool Replxx::ReplxxImpl::history_save( std::string const& filename ) {
+ return ( _history.save( filename, false ) );
+}
+
+bool Replxx::ReplxxImpl::history_sync( std::string const& filename ) {
+ return ( _history.save( filename, true ) );
+}
+
+bool Replxx::ReplxxImpl::history_load( std::string const& filename ) {
+ return ( _history.load( filename ) );
+}
+
+void Replxx::ReplxxImpl::history_clear( void ) {
+ _history.clear();
+}
+
+int Replxx::ReplxxImpl::history_size( void ) const {
+ return ( _history.size() );
+}
+
+Replxx::HistoryScan::impl_t Replxx::ReplxxImpl::history_scan( void ) const {
+ return ( _history.scan() );
+}
+
+void Replxx::ReplxxImpl::set_modify_callback( Replxx::modify_callback_t const& fn ) {
+ _modifyCallback = fn;
+}
+
+void Replxx::ReplxxImpl::set_completion_callback( Replxx::completion_callback_t const& fn ) {
+ _completionCallback = fn;
+}
+
+void Replxx::ReplxxImpl::set_highlighter_callback( Replxx::highlighter_callback_t const& fn ) {
+ _highlighterCallback = fn;
+}
+
+void Replxx::ReplxxImpl::set_hint_callback( Replxx::hint_callback_t const& fn ) {
+ _hintCallback = fn;
+}
+
+void Replxx::ReplxxImpl::set_max_history_size( int len ) {
+ _history.set_max_size( len );
+}
+
+void Replxx::ReplxxImpl::set_completion_count_cutoff( int count ) {
+ _completionCountCutoff = count;
+}
+
+void Replxx::ReplxxImpl::set_max_hint_rows( int count ) {
+ _maxHintRows = count;
+}
+
+void Replxx::ReplxxImpl::set_hint_delay( int hintDelay_ ) {
+ _hintDelay = hintDelay_;
+}
+
+void Replxx::ReplxxImpl::set_word_break_characters( char const* wordBreakers ) {
+ _wordBreakChars = wordBreakers;
+}
+
+void Replxx::ReplxxImpl::set_subword_break_characters( char const* subwordBreakers ) {
+ _subwordBreakChars = subwordBreakers;
+}
+
+void Replxx::ReplxxImpl::set_double_tab_completion( bool val ) {
+ _doubleTabCompletion = val;
+}
+
+void Replxx::ReplxxImpl::set_complete_on_empty( bool val ) {
+ _completeOnEmpty = val;
+}
+
+void Replxx::ReplxxImpl::set_beep_on_ambiguous_completion( bool val ) {
+ _beepOnAmbiguousCompletion = val;
+}
+
+void Replxx::ReplxxImpl::set_immediate_completion( bool val ) {
+ _immediateCompletion = val;
+}
+
+void Replxx::ReplxxImpl::set_unique_history( bool val ) {
+ _history.set_unique( val );
+}
+
+void Replxx::ReplxxImpl::set_no_color( bool val ) {
+ _noColor = val;
+}
+
+/**
+ * Display the dynamic incremental search prompt and the current user input
+ * line.
+ * @param pi Prompt struct holding information about the prompt and our
+ * screen position
+ * @param buf32 input buffer to be displayed
+ * @param len count of characters in the buffer
+ * @param pos current cursor position within the buffer (0 <= pos <= len)
+ */
+void Replxx::ReplxxImpl::dynamicRefresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos) {
+ clear_self_to_end_of_screen( &oldPrompt );
+ // calculate the position of the end of the prompt
+ int xEndOfPrompt, yEndOfPrompt;
+ calculate_screen_position(
+ 0, 0, newPrompt.screen_columns(), newPrompt._characterCount,
+ xEndOfPrompt, yEndOfPrompt
+ );
+
+ // calculate the position of the end of the input line
+ int xEndOfInput, yEndOfInput;
+ calculate_screen_position(
+ xEndOfPrompt, yEndOfPrompt, newPrompt.screen_columns(),
+ calculate_displayed_length(buf32, len), xEndOfInput,
+ yEndOfInput
+ );
+
+ // calculate the desired position of the cursor
+ int xCursorPos, yCursorPos;
+ calculate_screen_position(
+ xEndOfPrompt, yEndOfPrompt, newPrompt.screen_columns(),
+ calculate_displayed_length(buf32, pos), xCursorPos,
+ yCursorPos
+ );
+
+ // display the prompt
+ newPrompt.write();
+
+ // display the input line
+ _terminal.write32( buf32, len );
+
+#ifndef _WIN32
+ // we have to generate our own newline on line wrap
+ if (xEndOfInput == 0 && yEndOfInput > 0) {
+ _terminal.write8( "\n", 1 );
+ }
+#endif
+ // position the cursor
+ _terminal.jump_cursor(
+ xCursorPos, // 0-based on Win32
+ -( yEndOfInput - yCursorPos )
+ );
+ newPrompt._cursorRowOffset = newPrompt._extraLines + yCursorPos; // remember row for next pass
+}
+
+}
+
diff --git a/contrib/replxx/src/replxx_impl.hxx b/contrib/replxx/src/replxx_impl.hxx
new file mode 100644
index 0000000..bec9383
--- /dev/null
+++ b/contrib/replxx/src/replxx_impl.hxx
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED
+#define HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED 1
+
+#include <vector>
+#include <deque>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+#include "replxx.hxx"
+#include "history.hxx"
+#include "killring.hxx"
+#include "utf8string.hxx"
+#include "prompt.hxx"
+
+namespace replxx {
+
+class Replxx::ReplxxImpl {
+public:
+ class Completion {
+ UnicodeString _text;
+ Replxx::Color _color;
+ public:
+ Completion( UnicodeString const& text_, Replxx::Color color_ )
+ : _text( text_ )
+ , _color( color_ ) {
+ }
+ Completion( Replxx::Completion const& completion_ )
+ : _text( completion_.text() )
+ , _color( completion_.color() ) {
+ }
+ Completion( Completion const& ) = default;
+ Completion& operator = ( Completion const& ) = default;
+ Completion( Completion&& ) = default;
+ Completion& operator = ( Completion&& ) = default;
+ UnicodeString const& text( void ) const {
+ return ( _text );
+ }
+ Replxx::Color color( void ) const {
+ return ( _color );
+ }
+ };
+ typedef std::vector<Completion> completions_t;
+ typedef std::vector<UnicodeString> data_t;
+ typedef std::vector<UnicodeString> hints_t;
+ typedef std::unique_ptr<char[]> utf8_buffer_t;
+ typedef std::unique_ptr<char32_t[]> input_buffer_t;
+ typedef std::vector<char32_t> display_t;
+ typedef std::deque<char32_t> key_presses_t;
+ typedef std::deque<std::string> messages_t;
+ enum class HINT_ACTION {
+ REGENERATE,
+ REPAINT,
+ TRIM,
+ SKIP
+ };
+ typedef std::unordered_map<std::string, Replxx::key_press_handler_t> named_actions_t;
+ typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t );
+ typedef std::unordered_map<int, Replxx::key_press_handler_t> key_press_handlers_t;
+private:
+ typedef int long long unsigned action_trait_t;
+ static action_trait_t const NOOP = 0;
+ static action_trait_t const WANT_REFRESH = 1;
+ static action_trait_t const RESET_KILL_ACTION = 2;
+ static action_trait_t const SET_KILL_ACTION = 4;
+ static action_trait_t const DONT_RESET_PREFIX = 8;
+ static action_trait_t const DONT_RESET_COMPLETIONS = 16;
+ static action_trait_t const HISTORY_RECALL_MOST_RECENT = 32;
+ static action_trait_t const DONT_RESET_HIST_YANK_INDEX = 64;
+private:
+ mutable Utf8String _utf8Buffer;
+ UnicodeString _data;
+ int _pos; // character position in buffer ( 0 <= _pos <= _data[_line].length() )
+ display_t _display;
+ int _displayInputLength;
+ UnicodeString _hint;
+ int _prefix; // prefix length used in common prefix search
+ int _hintSelection; // Currently selected hint.
+ History _history;
+ KillRing _killRing;
+ int long long _lastRefreshTime;
+ bool _refreshSkipped;
+ int _lastYankSize;
+ int _maxHintRows;
+ int _hintDelay;
+ std::string _wordBreakChars;
+ std::string _subwordBreakChars;
+ int _completionCountCutoff;
+ bool _overwrite;
+ bool _doubleTabCompletion;
+ bool _completeOnEmpty;
+ bool _beepOnAmbiguousCompletion;
+ bool _immediateCompletion;
+ bool _bracketedPaste;
+ bool _noColor;
+ named_actions_t _namedActions;
+ key_press_handlers_t _keyPressHandlers;
+ Terminal _terminal;
+ std::thread::id _currentThread;
+ Prompt _prompt;
+ Replxx::modify_callback_t _modifyCallback;
+ Replxx::completion_callback_t _completionCallback;
+ Replxx::highlighter_callback_t _highlighterCallback;
+ Replxx::hint_callback_t _hintCallback;
+ key_presses_t _keyPresses;
+ messages_t _messages;
+ completions_t _completions;
+ int _completionContextLength;
+ int _completionSelection;
+ std::string _preloadedBuffer; // used with set_preload_buffer
+ std::string _errorMessage;
+ UnicodeString _previousSearchText; // remembered across invocations of replxx_input()
+ bool _modifiedState;
+ Replxx::Color _hintColor;
+ hints_t _hintsCache;
+ int _hintContextLenght;
+ Utf8String _hintSeed;
+ mutable std::mutex _mutex;
+public:
+ ReplxxImpl( FILE*, FILE*, FILE* );
+ virtual ~ReplxxImpl( void );
+ void set_modify_callback( Replxx::modify_callback_t const& fn );
+ void set_completion_callback( Replxx::completion_callback_t const& fn );
+ void set_highlighter_callback( Replxx::highlighter_callback_t const& fn );
+ void set_hint_callback( Replxx::hint_callback_t const& fn );
+ char const* input( std::string const& prompt );
+ void history_add( std::string const& line );
+ bool history_sync( std::string const& filename );
+ bool history_save( std::string const& filename );
+ bool history_load( std::string const& filename );
+ void history_clear( void );
+ Replxx::HistoryScan::impl_t history_scan( void ) const;
+ int history_size( void ) const;
+ void set_preload_buffer(std::string const& preloadText);
+ void set_word_break_characters( char const* wordBreakers );
+ void set_subword_break_characters( char const* subwordBreakers );
+ void set_max_hint_rows( int count );
+ void set_hint_delay( int milliseconds );
+ void set_double_tab_completion( bool val );
+ void set_complete_on_empty( bool val );
+ void set_beep_on_ambiguous_completion( bool val );
+ void set_immediate_completion( bool val );
+ void set_unique_history( bool );
+ void set_no_color( bool val );
+ void set_max_history_size( int len );
+ void set_completion_count_cutoff( int len );
+ int install_window_change_handler( void );
+ void enable_bracketed_paste( void );
+ void disable_bracketed_paste( void );
+ void print( char const*, int );
+ Replxx::ACTION_RESULT clear_screen( char32_t );
+ void emulate_key_press( char32_t );
+ Replxx::ACTION_RESULT invoke( Replxx::ACTION, char32_t );
+ void bind_key( char32_t, Replxx::key_press_handler_t );
+ void bind_key_internal( char32_t, char const* );
+ Replxx::State get_state( void ) const;
+ void set_state( Replxx::State const& );
+private:
+ ReplxxImpl( ReplxxImpl const& ) = delete;
+ ReplxxImpl& operator = ( ReplxxImpl const& ) = delete;
+private:
+ void preload_puffer( char const* preloadText );
+ int get_input_line( void );
+ Replxx::ACTION_RESULT action( action_trait_t, key_press_handler_raw_t const&, char32_t );
+ Replxx::ACTION_RESULT insert_character( char32_t );
+ Replxx::ACTION_RESULT new_line( char32_t );
+ Replxx::ACTION_RESULT go_to_begining_of_line( char32_t );
+ Replxx::ACTION_RESULT go_to_end_of_line( char32_t );
+ Replxx::ACTION_RESULT move_one_char_left( char32_t );
+ Replxx::ACTION_RESULT move_one_char_right( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT move_one_word_left( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT move_one_word_right( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT kill_word_to_left( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT kill_word_to_right( char32_t );
+ Replxx::ACTION_RESULT kill_to_whitespace_to_left( char32_t );
+ Replxx::ACTION_RESULT kill_to_begining_of_line( char32_t );
+ Replxx::ACTION_RESULT kill_to_end_of_line( char32_t );
+ Replxx::ACTION_RESULT yank( char32_t );
+ Replxx::ACTION_RESULT yank_cycle( char32_t );
+ Replxx::ACTION_RESULT yank_last_arg( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT capitalize_word( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT lowercase_word( char32_t );
+ template <bool subword>
+ Replxx::ACTION_RESULT uppercase_word( char32_t );
+ Replxx::ACTION_RESULT transpose_characters( char32_t );
+ Replxx::ACTION_RESULT abort_line( char32_t );
+ Replxx::ACTION_RESULT send_eof( char32_t );
+ Replxx::ACTION_RESULT delete_character( char32_t );
+ Replxx::ACTION_RESULT backspace_character( char32_t );
+ Replxx::ACTION_RESULT commit_line( char32_t );
+ Replxx::ACTION_RESULT history_next( char32_t );
+ Replxx::ACTION_RESULT history_previous( char32_t );
+ Replxx::ACTION_RESULT history_move( bool );
+ Replxx::ACTION_RESULT history_first( char32_t );
+ Replxx::ACTION_RESULT history_last( char32_t );
+ Replxx::ACTION_RESULT history_jump( bool );
+ Replxx::ACTION_RESULT hint_next( char32_t );
+ Replxx::ACTION_RESULT hint_previous( char32_t );
+ Replxx::ACTION_RESULT hint_move( bool );
+ Replxx::ACTION_RESULT toggle_overwrite_mode( char32_t );
+#ifndef _WIN32
+ Replxx::ACTION_RESULT verbatim_insert( char32_t );
+ Replxx::ACTION_RESULT suspend( char32_t );
+#endif
+ Replxx::ACTION_RESULT complete_line( char32_t );
+ Replxx::ACTION_RESULT complete_next( char32_t );
+ Replxx::ACTION_RESULT complete_previous( char32_t );
+ Replxx::ACTION_RESULT complete( bool );
+ Replxx::ACTION_RESULT incremental_history_search( char32_t startChar );
+ Replxx::ACTION_RESULT common_prefix_search( char32_t startChar );
+ Replxx::ACTION_RESULT bracketed_paste( char32_t startChar );
+ char32_t read_char( HINT_ACTION = HINT_ACTION::SKIP );
+ char const* read_from_stdin( void );
+ char32_t do_complete_line( bool );
+ void call_modify_callback( void );
+ completions_t call_completer( std::string const& input, int& ) const;
+ hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const;
+ void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE );
+ void render( char32_t );
+ void render( HINT_ACTION );
+ int handle_hints( HINT_ACTION );
+ void set_color( Replxx::Color );
+ int context_length( void );
+ void clear( void );
+ void repaint( void );
+ template <bool subword>
+ bool is_word_break_character( char32_t ) const;
+ void dynamicRefresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos);
+ char const* finalize_input( char const* );
+ void clear_self_to_end_of_screen( Prompt const* = nullptr );
+ typedef struct {
+ int index;
+ bool error;
+ } paren_info_t;
+ paren_info_t matching_paren( void );
+};
+
+}
+
+#endif
+
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
+
+}
+
diff --git a/contrib/replxx/src/terminal.hxx b/contrib/replxx/src/terminal.hxx
new file mode 100644
index 0000000..e6a2578
--- /dev/null
+++ b/contrib/replxx/src/terminal.hxx
@@ -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
+
diff --git a/contrib/replxx/src/unicodestring.hxx b/contrib/replxx/src/unicodestring.hxx
new file mode 100644
index 0000000..8ff98a7
--- /dev/null
+++ b/contrib/replxx/src/unicodestring.hxx
@@ -0,0 +1,201 @@
+#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED
+#define REPLXX_UNICODESTRING_HXX_INCLUDED
+
+#include <vector>
+#include <cstring>
+#include <string>
+
+#include "conversion.hxx"
+
+namespace replxx {
+
+class UnicodeString {
+public:
+ typedef std::vector<char32_t> data_buffer_t;
+ typedef data_buffer_t::const_iterator const_iterator;
+ typedef data_buffer_t::iterator iterator;
+private:
+ data_buffer_t _data;
+public:
+ UnicodeString()
+ : _data() {
+ }
+
+ explicit UnicodeString( std::string const& src )
+ : _data() {
+ assign( src );
+ }
+
+ explicit UnicodeString( UnicodeString const& other, int offset, int len = -1 )
+ : _data() {
+ _data.insert(
+ _data.end(),
+ other._data.begin() + offset,
+ len > 0 ? other._data.begin() + offset + len : other._data.end()
+ );
+ }
+
+ explicit UnicodeString( char const* src )
+ : _data() {
+ assign( src );
+ }
+
+ explicit UnicodeString( char8_t const* src )
+ : UnicodeString( reinterpret_cast<const char*>( src ) ) {
+ }
+
+ explicit UnicodeString( char32_t const* src )
+ : _data() {
+ int len( 0 );
+ while ( src[len] != 0 ) {
+ ++ len;
+ }
+ _data.assign( src, src + len );
+ }
+
+ explicit UnicodeString( char32_t const* src, int len )
+ : _data() {
+ _data.assign( src, src + len );
+ }
+
+ explicit UnicodeString( int len )
+ : _data() {
+ _data.resize( len );
+ }
+
+ UnicodeString& assign( std::string const& str_ ) {
+ _data.resize( static_cast<int>( str_.length() ) );
+ int len( 0 );
+ copyString8to32( _data.data(), static_cast<int>( str_.length() ), len, str_.c_str() );
+ _data.resize( len );
+ return *this;
+ }
+
+ UnicodeString& assign( char const* str_ ) {
+ int byteCount( static_cast<int>( strlen( str_ ) ) );
+ _data.resize( byteCount );
+ int len( 0 );
+ copyString8to32( _data.data(), byteCount, len, str_ );
+ _data.resize( len );
+ return *this;
+ }
+
+ UnicodeString& assign( UnicodeString const& other_ ) {
+ _data = other_._data;
+ return *this;
+ }
+
+ explicit UnicodeString( UnicodeString const& ) = default;
+ UnicodeString& operator = ( UnicodeString const& ) = default;
+ UnicodeString( UnicodeString&& ) = default;
+ UnicodeString& operator = ( UnicodeString&& ) = default;
+ bool operator == ( UnicodeString const& other_ ) const {
+ return ( _data == other_._data );
+ }
+
+ bool operator != ( UnicodeString const& other_ ) const {
+ return ( _data != other_._data );
+ }
+
+ UnicodeString& append( UnicodeString const& other ) {
+ _data.insert( _data.end(), other._data.begin(), other._data.end() );
+ return *this;
+ }
+
+ void push_back( char32_t c_ ) {
+ _data.push_back( c_ );
+ }
+
+ UnicodeString& append( char32_t const* src, int len ) {
+ _data.insert( _data.end(), src, src + len );
+ return *this;
+ }
+
+ UnicodeString& insert( int pos_, UnicodeString const& str_, int offset_, int len_ ) {
+ _data.insert( _data.begin() + pos_, str_._data.begin() + offset_, str_._data.begin() + offset_ + len_ );
+ return *this;
+ }
+
+ UnicodeString& insert( int pos_, char32_t c_ ) {
+ _data.insert( _data.begin() + pos_, c_ );
+ return *this;
+ }
+
+ UnicodeString& erase( int pos_ ) {
+ _data.erase( _data.begin() + pos_ );
+ return *this;
+ }
+
+ UnicodeString& erase( int pos_, int len_ ) {
+ _data.erase( _data.begin() + pos_, _data.begin() + pos_ + len_ );
+ return *this;
+ }
+
+ char32_t const* get() const {
+ return _data.data();
+ }
+
+ char32_t* get() {
+ return _data.data();
+ }
+
+ int length() const {
+ return static_cast<int>( _data.size() );
+ }
+
+ void clear( void ) {
+ _data.clear();
+ }
+
+ const char32_t& operator[]( size_t pos ) const {
+ return _data[pos];
+ }
+
+ char32_t& operator[]( size_t pos ) {
+ return _data[pos];
+ }
+
+ bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const {
+ return (
+ ( std::distance( first_, last_ ) <= length() )
+ && ( std::equal( first_, last_, _data.begin() ) )
+ );
+ }
+
+ bool ends_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const {
+ int len( static_cast<int>( std::distance( first_, last_ ) ) );
+ return (
+ ( len <= length() )
+ && ( std::equal( first_, last_, _data.end() - len ) )
+ );
+ }
+
+ bool is_empty( void ) const {
+ return ( _data.size() == 0 );
+ }
+
+ void swap( UnicodeString& other_ ) {
+ _data.swap( other_._data );
+ }
+
+ const_iterator begin( void ) const {
+ return ( _data.begin() );
+ }
+
+ const_iterator end( void ) const {
+ return ( _data.end() );
+ }
+
+ iterator begin( void ) {
+ return ( _data.begin() );
+ }
+
+ iterator end( void ) {
+ return ( _data.end() );
+ }
+};
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/utf8string.hxx b/contrib/replxx/src/utf8string.hxx
new file mode 100644
index 0000000..29effa2
--- /dev/null
+++ b/contrib/replxx/src/utf8string.hxx
@@ -0,0 +1,94 @@
+#ifndef REPLXX_UTF8STRING_HXX_INCLUDED
+#define REPLXX_UTF8STRING_HXX_INCLUDED
+
+#include <memory>
+
+#include "unicodestring.hxx"
+
+namespace replxx {
+
+class Utf8String {
+private:
+ typedef std::unique_ptr<char[]> buffer_t;
+ buffer_t _data;
+ int _bufSize;
+ int _len;
+public:
+ Utf8String( void )
+ : _data()
+ , _bufSize( 0 )
+ , _len( 0 ) {
+ }
+ explicit Utf8String( UnicodeString const& src )
+ : _data()
+ , _bufSize( 0 )
+ , _len( 0 ) {
+ assign( src, src.length() );
+ }
+
+ Utf8String( UnicodeString const& src_, int len_ )
+ : _data()
+ , _bufSize( 0 )
+ , _len( 0 ) {
+ assign( src_, len_ );
+ }
+
+ void assign( UnicodeString const& str_ ) {
+ assign( str_, str_.length() );
+ }
+
+ void assign( UnicodeString const& str_, int len_ ) {
+ assign( str_.get(), len_ );
+ }
+
+ void assign( char32_t const* str_, int len_ ) {
+ int len( len_ * 4 );
+ realloc( len );
+ _len = copyString32to8( _data.get(), len, str_, len_ );
+ }
+
+ void assign( std::string const& str_ ) {
+ realloc( static_cast<int>( str_.length() ) );
+ strncpy( _data.get(), str_.c_str(), str_.length() );
+ _len = static_cast<int>( str_.length() );
+ }
+
+ void assign( Utf8String const& other_ ) {
+ realloc( other_._len );
+ strncpy( _data.get(), other_._data.get(), other_._len );
+ _len = other_._len;
+ }
+
+ char const* get() const {
+ return _data.get();
+ }
+
+ int size( void ) const {
+ return ( _len );
+ }
+
+ bool operator != ( Utf8String const& other_ ) {
+ return ( ( other_._len != _len ) || ( memcmp( other_._data.get(), _data.get(), _len ) != 0 ) );
+ }
+
+private:
+ void realloc( int reqLen ) {
+ if ( ( reqLen + 1 ) > _bufSize ) {
+ _bufSize = 1;
+ while ( ( reqLen + 1 ) > _bufSize ) {
+ _bufSize *= 2;
+ }
+ _data.reset( new char[_bufSize] );
+ memset( _data.get(), 0, _bufSize );
+ }
+ _data[reqLen] = 0;
+ return;
+ }
+ Utf8String(const Utf8String&) = delete;
+ Utf8String& operator=(const Utf8String&) = delete;
+};
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/util.cxx b/contrib/replxx/src/util.cxx
new file mode 100644
index 0000000..719d707
--- /dev/null
+++ b/contrib/replxx/src/util.cxx
@@ -0,0 +1,158 @@
+#include <chrono>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <wctype.h>
+
+#include "util.hxx"
+
+namespace replxx {
+
+int mk_wcwidth( char32_t );
+
+/**
+ * Calculate a new screen position given a starting position, screen width and
+ * character count
+ * @param x - initial x position (zero-based)
+ * @param y - initial y position (zero-based)
+ * @param screenColumns - screen column count
+ * @param charCount - character positions to advance
+ * @param xOut - returned x position (zero-based)
+ * @param yOut - returned y position (zero-based)
+ */
+void calculate_screen_position(
+ int x, int y, int screenColumns,
+ int charCount, int& xOut, int& yOut
+) {
+ xOut = x;
+ yOut = y;
+ int charsRemaining = charCount;
+ while ( charsRemaining > 0 ) {
+ int charsThisRow = ( ( x + charsRemaining ) < screenColumns )
+ ? charsRemaining
+ : screenColumns - x;
+ xOut = x + charsThisRow;
+ yOut = y;
+ charsRemaining -= charsThisRow;
+ x = 0;
+ ++ y;
+ }
+ if ( xOut == screenColumns ) { // we have to special-case line wrap
+ xOut = 0;
+ ++ yOut;
+ }
+}
+
+/**
+ * Calculate a column width using mk_wcswidth()
+ * @param buf32 - text to calculate
+ * @param len - length of text to calculate
+ */
+int calculate_displayed_length( char32_t const* buf32_, int size_ ) {
+ int len( 0 );
+ for ( int i( 0 ); i < size_; ++ i ) {
+ char32_t c( buf32_[i] );
+ if ( c == '\033' ) {
+ int escStart( i );
+ ++ i;
+ if ( ( i < size_ ) && ( buf32_[i] != '[' ) ) {
+ i = escStart;
+ ++ len;
+ continue;
+ }
+ ++ i;
+ for ( ; i < size_; ++ i ) {
+ c = buf32_[i];
+ if ( ( c != ';' ) && ( ( c < '0' ) || ( c > '9' ) ) ) {
+ break;
+ }
+ }
+ if ( ( i < size_ ) && ( buf32_[i] == 'm' ) ) {
+ continue;
+ }
+ i = escStart;
+ len += 2;
+ } else if ( is_control_code( c ) ) {
+ len += 2;
+ } else {
+ int wcw( mk_wcwidth( c ) );
+ if ( wcw < 0 ) {
+ len = -1;
+ break;
+ }
+ len += wcw;
+ }
+ }
+ return ( len );
+}
+
+char const* ansi_color( Replxx::Color color_ ) {
+ static char const reset[] = "\033[0m";
+ static char const black[] = "\033[0;22;30m";
+ static char const red[] = "\033[0;22;31m";
+ static char const green[] = "\033[0;22;32m";
+ static char const brown[] = "\033[0;22;33m";
+ static char const blue[] = "\033[0;22;34m";
+ static char const magenta[] = "\033[0;22;35m";
+ static char const cyan[] = "\033[0;22;36m";
+ static char const lightgray[] = "\033[0;22;37m";
+
+#ifdef _WIN32
+ static bool const has256colorDefault( true );
+#else
+ static bool const has256colorDefault( false );
+#endif
+ static char const* TERM( getenv( "TERM" ) );
+ static bool const has256color( TERM ? ( strstr( TERM, "256" ) != nullptr ) : has256colorDefault );
+ static char const* gray = has256color ? "\033[0;1;90m" : "\033[0;1;30m";
+ static char const* brightred = has256color ? "\033[0;1;91m" : "\033[0;1;31m";
+ static char const* brightgreen = has256color ? "\033[0;1;92m" : "\033[0;1;32m";
+ static char const* yellow = has256color ? "\033[0;1;93m" : "\033[0;1;33m";
+ static char const* brightblue = has256color ? "\033[0;1;94m" : "\033[0;1;34m";
+ static char const* brightmagenta = has256color ? "\033[0;1;95m" : "\033[0;1;35m";
+ static char const* brightcyan = has256color ? "\033[0;1;96m" : "\033[0;1;36m";
+ static char const* white = has256color ? "\033[0;1;97m" : "\033[0;1;37m";
+ static char const error[] = "\033[101;1;33m";
+
+ char const* code( reset );
+ switch ( color_ ) {
+ case Replxx::Color::BLACK: code = black; break;
+ case Replxx::Color::RED: code = red; break;
+ case Replxx::Color::GREEN: code = green; break;
+ case Replxx::Color::BROWN: code = brown; break;
+ case Replxx::Color::BLUE: code = blue; break;
+ case Replxx::Color::MAGENTA: code = magenta; break;
+ case Replxx::Color::CYAN: code = cyan; break;
+ case Replxx::Color::LIGHTGRAY: code = lightgray; break;
+ case Replxx::Color::GRAY: code = gray; break;
+ case Replxx::Color::BRIGHTRED: code = brightred; break;
+ case Replxx::Color::BRIGHTGREEN: code = brightgreen; break;
+ case Replxx::Color::YELLOW: code = yellow; break;
+ case Replxx::Color::BRIGHTBLUE: code = brightblue; break;
+ case Replxx::Color::BRIGHTMAGENTA: code = brightmagenta; break;
+ case Replxx::Color::BRIGHTCYAN: code = brightcyan; break;
+ case Replxx::Color::WHITE: code = white; break;
+ case Replxx::Color::ERROR: code = error; break;
+ case Replxx::Color::DEFAULT: code = reset; break;
+ }
+ return ( code );
+}
+
+std::string now_ms_str( void ) {
+ std::chrono::milliseconds ms( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ) );
+ time_t t( ms.count() / 1000 );
+ tm broken;
+#ifdef _WIN32
+#define localtime_r( t, b ) localtime_s( ( b ), ( t ) )
+#endif
+ localtime_r( &t, &broken );
+#undef localtime_r
+ static int const BUFF_SIZE( 32 );
+ char str[BUFF_SIZE];
+ strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken );
+ snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast<int>( ms.count() % 1000 ) );
+ return ( str );
+}
+
+}
+
diff --git a/contrib/replxx/src/util.hxx b/contrib/replxx/src/util.hxx
new file mode 100644
index 0000000..17c1086
--- /dev/null
+++ b/contrib/replxx/src/util.hxx
@@ -0,0 +1,25 @@
+#ifndef REPLXX_UTIL_HXX_INCLUDED
+#define REPLXX_UTIL_HXX_INCLUDED 1
+
+#include "replxx.hxx"
+
+namespace replxx {
+
+inline bool is_control_code(char32_t testChar) {
+ return (testChar < ' ') || // C0 controls
+ (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls
+}
+
+inline char32_t control_to_human( char32_t key ) {
+ return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) );
+}
+
+void calculate_screen_position( int x, int y, int screenColumns, int charCount, int& xOut, int& yOut );
+int calculate_displayed_length( char32_t const* buf32, int size );
+char const* ansi_color( Replxx::Color );
+std::string now_ms_str( void );
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/wcwidth.cpp b/contrib/replxx/src/wcwidth.cpp
new file mode 100644
index 0000000..c6c05fa
--- /dev/null
+++ b/contrib/replxx/src/wcwidth.cpp
@@ -0,0 +1,296 @@
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <wchar.h>
+#include <string>
+#include <memory>
+
+namespace replxx {
+
+struct interval {
+ char32_t first;
+ char32_t last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(char32_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+int mk_is_wide_char(char32_t ucs) {
+ static const struct interval wide[] = {
+ {0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a},
+ {0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3},
+ {0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653},
+ {0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1},
+ {0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5},
+ {0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea},
+ {0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa},
+ {0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b},
+ {0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e},
+ {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
+ {0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c},
+ {0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf},
+ {0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf},
+ {0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3},
+ {0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f},
+ {0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1},
+ {0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff},
+ {0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e},
+ {0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b},
+ {0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265},
+ {0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c},
+ {0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3},
+ {0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e},
+ {0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d},
+ {0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a},
+ {0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f},
+ {0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2},
+ {0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e},
+ {0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997},
+ {0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd},
+ {0x30000, 0x3fffd},
+ };
+
+ if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) {
+ return 1;
+ }
+
+ return 0;
+}
+
+int mk_wcwidth(char32_t ucs) {
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ {0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489},
+ {0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2},
+ {0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a},
+ {0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670},
+ {0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8},
+ {0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a},
+ {0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819},
+ {0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d},
+ {0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902},
+ {0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948},
+ {0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963},
+ {0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4},
+ {0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02},
+ {0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48},
+ {0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71},
+ {0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc},
+ {0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd},
+ {0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01},
+ {0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44},
+ {0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63},
+ {0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd},
+ {0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48},
+ {0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63},
+ {0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf},
+ {0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3},
+ {0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44},
+ {0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca},
+ {0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31},
+ {0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1},
+ {0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd},
+ {0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37},
+ {0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84},
+ {0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc},
+ {0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037},
+ {0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059},
+ {0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082},
+ {0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d},
+ {0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714},
+ {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773},
+ {0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6},
+ {0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e},
+ {0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922},
+ {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b},
+ {0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56},
+ {0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62},
+ {0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f},
+ {0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34},
+ {0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42},
+ {0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5},
+ {0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6},
+ {0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1},
+ {0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2},
+ {0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced},
+ {0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9},
+ {0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e},
+ {0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0},
+ {0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff},
+ {0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672},
+ {0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1},
+ {0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b},
+ {0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1},
+ {0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982},
+ {0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc},
+ {0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32},
+ {0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c},
+ {0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4},
+ {0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1},
+ {0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5},
+ {0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e},
+ {0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff},
+ {0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0},
+ {0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06},
+ {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f},
+ {0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046},
+ {0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba},
+ {0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134},
+ {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be},
+ {0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234},
+ {0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df},
+ {0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c},
+ {0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374},
+ {0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446},
+ {0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0},
+ {0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd},
+ {0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a},
+ {0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab},
+ {0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7},
+ {0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b},
+ {0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38},
+ {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56},
+ {0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99},
+ {0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f},
+ {0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3},
+ {0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a},
+ {0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47},
+ {0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92},
+ {0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169},
+ {0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad},
+ {0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c},
+ {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
+ {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018},
+ {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a},
+ {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001},
+ {0xe0020, 0xe007f}, {0xe0100, 0xe01ef},
+ };
+
+ /* test for 8-bit control characters */
+ if ( ucs == 0 ) {
+ return 0;
+ }
+ if ( ( ucs < 32 ) || ( ( ucs >= 0x7f ) && ( ucs < 0xa0 ) ) ) {
+ return -1;
+ }
+
+ /* binary search in table of non-spacing characters */
+ if ( bisearch( ucs, combining, sizeof( combining ) / sizeof( struct interval ) - 1 ) ) {
+ return 0;
+ }
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+ return ( mk_is_wide_char( ucs ) ? 2 : 1 );
+}
+
+}
+
diff --git a/contrib/replxx/src/windows.cxx b/contrib/replxx/src/windows.cxx
new file mode 100644
index 0000000..715292c
--- /dev/null
+++ b/contrib/replxx/src/windows.cxx
@@ -0,0 +1,144 @@
+#ifdef _WIN32
+
+#include <iostream>
+
+#include "windows.hxx"
+#include "conversion.hxx"
+#include "terminal.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+WinAttributes WIN_ATTR;
+
+template<typename T>
+T* HandleEsc(HANDLE out_, T* p, T* end) {
+ if (*p == '[') {
+ int code = 0;
+
+ int thisBackground( WIN_ATTR._defaultBackground );
+ for (++p; p < end; ++p) {
+ char32_t c = *p;
+
+ if ('0' <= c && c <= '9') {
+ code = code * 10 + (c - '0');
+ } else if (c == 'm' || c == ';') {
+ switch (code) {
+ case 0:
+ WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute;
+ WIN_ATTR._consoleColor = WIN_ATTR._defaultColor | thisBackground;
+ break;
+ case 1: // BOLD
+ case 5: // BLINK
+ WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY;
+ break;
+ case 22:
+ WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute;
+ break;
+ case 30:
+ case 90:
+ WIN_ATTR._consoleColor = thisBackground;
+ break;
+ case 31:
+ case 91:
+ WIN_ATTR._consoleColor = FOREGROUND_RED | thisBackground;
+ break;
+ case 32:
+ case 92:
+ WIN_ATTR._consoleColor = FOREGROUND_GREEN | thisBackground;
+ break;
+ case 33:
+ case 93:
+ WIN_ATTR._consoleColor = FOREGROUND_RED | FOREGROUND_GREEN | thisBackground;
+ break;
+ case 34:
+ case 94:
+ WIN_ATTR._consoleColor = FOREGROUND_BLUE | thisBackground;
+ break;
+ case 35:
+ case 95:
+ WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_RED | thisBackground;
+ break;
+ case 36:
+ case 96:
+ WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_GREEN | thisBackground;
+ break;
+ case 37:
+ case 97:
+ WIN_ATTR._consoleColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | thisBackground;
+ break;
+ case 101:
+ thisBackground = BACKGROUND_RED;
+ break;
+ }
+
+ if ( ( code >= 90 ) && ( code <= 97 ) ) {
+ WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY;
+ }
+
+ code = 0;
+ }
+
+ if (*p == 'm') {
+ ++p;
+ break;
+ }
+ }
+ } else {
+ ++p;
+ }
+
+ SetConsoleTextAttribute(
+ out_,
+ WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor
+ );
+
+ return p;
+}
+
+int win_write( HANDLE out_, bool autoEscape_, char const* str_, int size_ ) {
+ int count( 0 );
+ if ( tty::out ) {
+ DWORD nWritten( 0 );
+ if ( autoEscape_ ) {
+ WriteConsoleA( out_, str_, size_, &nWritten, nullptr );
+ count = nWritten;
+ } else {
+ char const* s( str_ );
+ char const* e( str_ + size_ );
+ while ( str_ < e ) {
+ if ( *str_ == 27 ) {
+ if ( s < str_ ) {
+ int toWrite( static_cast<int>( str_ - s ) );
+ WriteConsoleA( out_, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr );
+ count += nWritten;
+ if ( nWritten != toWrite ) {
+ s = str_ = nullptr;
+ break;
+ }
+ }
+ s = HandleEsc( out_, str_ + 1, e );
+ int escaped( static_cast<int>( s - str_ ) );
+ count += escaped;
+ str_ = s;
+ } else {
+ ++ str_;
+ }
+ }
+
+ if ( s < str_ ) {
+ WriteConsoleA( out_, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr );
+ count += nWritten;
+ }
+ }
+ } else {
+ count = _write( 1, str_, size_ );
+ }
+ return ( count );
+}
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/windows.hxx b/contrib/replxx/src/windows.hxx
new file mode 100644
index 0000000..243f41c
--- /dev/null
+++ b/contrib/replxx/src/windows.hxx
@@ -0,0 +1,44 @@
+#ifndef REPLXX_WINDOWS_HXX_INCLUDED
+#define REPLXX_WINDOWS_HXX_INCLUDED 1
+
+#include <conio.h>
+#include <windows.h>
+#include <io.h>
+
+namespace replxx {
+
+static const int FOREGROUND_WHITE =
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
+static const int BACKGROUND_WHITE =
+ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
+static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
+
+class WinAttributes {
+ public:
+ WinAttributes() {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
+ _defaultAttribute = info.wAttributes & INTENSITY;
+ _defaultColor = info.wAttributes & FOREGROUND_WHITE;
+ _defaultBackground = info.wAttributes & BACKGROUND_WHITE;
+
+ _consoleAttribute = _defaultAttribute;
+ _consoleColor = _defaultColor | _defaultBackground;
+ }
+
+ public:
+ int _defaultAttribute;
+ int _defaultColor;
+ int _defaultBackground;
+
+ int _consoleAttribute;
+ int _consoleColor;
+};
+
+int win_write( HANDLE, bool, char const*, int );
+
+extern WinAttributes WIN_ATTR;
+
+}
+
+#endif