]> source.dussan.org Git - rspamd.git/commitdiff
[Rework] Replace linenoise with replxx
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 3 Sep 2019 16:45:58 +0000 (17:45 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 3 Sep 2019 16:45:58 +0000 (17:45 +0100)
Source: https://github.com/AmokHuginnsson/replxx

32 files changed:
contrib/linenoise/CMakeLists.txt [deleted file]
contrib/linenoise/LICENSE [deleted file]
contrib/linenoise/linenoise.c [deleted file]
contrib/linenoise/linenoise.h [deleted file]
contrib/replxx/CMakeLists.txt [new file with mode: 0644]
contrib/replxx/LICENSE.md [new file with mode: 0644]
contrib/replxx/README.md [new file with mode: 0644]
contrib/replxx/include/replxx.h [new file with mode: 0644]
contrib/replxx/include/replxx.hxx [new file with mode: 0644]
contrib/replxx/src/ConvertUTF.cpp [new file with mode: 0644]
contrib/replxx/src/ConvertUTF.h [new file with mode: 0644]
contrib/replxx/src/conversion.cxx [new file with mode: 0644]
contrib/replxx/src/conversion.hxx [new file with mode: 0644]
contrib/replxx/src/escape.cxx [new file with mode: 0644]
contrib/replxx/src/escape.hxx [new file with mode: 0644]
contrib/replxx/src/history.cxx [new file with mode: 0644]
contrib/replxx/src/history.hxx [new file with mode: 0644]
contrib/replxx/src/io.cxx [new file with mode: 0644]
contrib/replxx/src/io.hxx [new file with mode: 0644]
contrib/replxx/src/killring.hxx [new file with mode: 0644]
contrib/replxx/src/prompt.cxx [new file with mode: 0644]
contrib/replxx/src/prompt.hxx [new file with mode: 0644]
contrib/replxx/src/replxx.cxx [new file with mode: 0644]
contrib/replxx/src/replxx_impl.cxx [new file with mode: 0644]
contrib/replxx/src/replxx_impl.hxx [new file with mode: 0644]
contrib/replxx/src/unicodestring.hxx [new file with mode: 0644]
contrib/replxx/src/utf8string.hxx [new file with mode: 0644]
contrib/replxx/src/util.cxx [new file with mode: 0644]
contrib/replxx/src/util.hxx [new file with mode: 0644]
contrib/replxx/src/wcwidth.cpp [new file with mode: 0644]
contrib/replxx/src/windows.cxx [new file with mode: 0644]
contrib/replxx/src/windows.hxx [new file with mode: 0644]

diff --git a/contrib/linenoise/CMakeLists.txt b/contrib/linenoise/CMakeLists.txt
deleted file mode 100644 (file)
index 8fc9ff8..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-SET(LINENOISESRC linenoise.c)
-
-ADD_LIBRARY(rspamd-linenoise STATIC ${LINENOISESRC})
-SET_TARGET_PROPERTIES(rspamd-linenoise PROPERTIES VERSION ${RSPAMD_VERSION})
-
-IF(ENABLE_FULL_DEBUG MATCHES "OFF")
-if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
-       SET_TARGET_PROPERTIES(rspamd-linenoise PROPERTIES COMPILE_FLAGS "-O3")
-endif ()
-ENDIF()
diff --git a/contrib/linenoise/LICENSE b/contrib/linenoise/LICENSE
deleted file mode 100644 (file)
index 18e8148..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
-Copyright (c) 2010-2013, 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.
-
-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.
diff --git a/contrib/linenoise/linenoise.c b/contrib/linenoise/linenoise.c
deleted file mode 100644 (file)
index 8e7ba96..0000000
+++ /dev/null
@@ -1,1194 +0,0 @@
-/* linenoise.c -- guerrilla line editing library against the idea that a
- * 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.
- *
- * ------------------------------------------------------------------------
- *
- * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
- * Copyright (c) 2010-2013, 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.
- *
- * 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
- * HOLDER 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.
- *
- * ------------------------------------------------------------------------
- *
- * References:
- * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
- * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
- *
- * Todo list:
- * - Filter bogus Ctrl+<char> combinations.
- * - Win32 support
- *
- * Bloat:
- * - 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.
- *
- * 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 n chars
- *
- * CUB (CUrsor Backward)
- *    Sequence: ESC [ n D
- *    Effect: moves cursor backward n chars
- *
- * The following is used to get the terminal width if getting
- * the width with the TIOCGWINSZ ioctl fails
- *
- * DSR (Device Status Report)
- *    Sequence: ESC [ 6 n
- *    Effect: reports the current cusor position as ESC [ n ; m R
- *            where n is the row and m is the column
- *
- * When multi line mode is enabled, we also use an additional escape
- * sequence. However multi line editing is disabled by default.
- *
- * CUU (Cursor Up)
- *    Sequence: ESC [ n A
- *    Effect: moves cursor up of n chars.
- *
- * CUD (Cursor Down)
- *    Sequence: ESC [ n B
- *    Effect: moves cursor down of n chars.
- *
- * When linenoiseClearScreen() is called, two additional escape sequences
- * are used in order to clear the screen and position the cursor at home
- * position.
- *
- * CUP (Cursor position)
- *    Sequence: ESC [ H
- *    Effect: moves the cursor to upper left corner
- *
- * ED (Erase display)
- *    Sequence: ESC [ 2 J
- *    Effect: clear the whole screen
- *
- */
-
-#include <termios.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-#include "linenoise.h"
-
-#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
-#define LINENOISE_MAX_LINE 4096
-static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
-static linenoiseCompletionCallback *completionCallback = NULL;
-static linenoiseHintsCallback *hintsCallback = NULL;
-static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
-
-static struct termios orig_termios; /* In order to restore at exit.*/
-static int rawmode = 0; /* For atexit() function to check if restore is needed*/
-static int mlmode = 0;  /* Multi line mode. Default is single line. */
-static int atexit_registered = 0; /* Register atexit just 1 time. */
-static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
-static int history_len = 0;
-static char **history = NULL;
-
-/* The linenoiseState structure represents the state during line editing.
- * We pass this state to functions implementing specific editing
- * functionalities. */
-struct linenoiseState {
-    int ifd;            /* Terminal stdin file descriptor. */
-    int ofd;            /* Terminal stdout file descriptor. */
-    char *buf;          /* Edited line buffer. */
-    size_t buflen;      /* Edited line buffer size. */
-    const char *prompt; /* Prompt to display. */
-    size_t plen;        /* Prompt length. */
-    size_t pos;         /* Current cursor position. */
-    size_t oldpos;      /* Previous refresh cursor position. */
-    size_t len;         /* Current edited line length. */
-    size_t cols;        /* Number of columns in terminal. */
-    size_t maxrows;     /* Maximum num of rows used so far (multiline mode) */
-    int history_index;  /* The history index we are currently editing. */
-};
-
-enum KEY_ACTION{
-       KEY_NULL = 0,       /* NULL */
-       CTRL_A = 1,         /* Ctrl+a */
-       CTRL_B = 2,         /* Ctrl-b */
-       CTRL_C = 3,         /* Ctrl-c */
-       CTRL_D = 4,         /* Ctrl-d */
-       CTRL_E = 5,         /* Ctrl-e */
-       CTRL_F = 6,         /* Ctrl-f */
-       CTRL_H = 8,         /* Ctrl-h */
-       TAB = 9,            /* Tab */
-       CTRL_K = 11,        /* Ctrl+k */
-       CTRL_L = 12,        /* Ctrl+l */
-       ENTER = 13,         /* Enter */
-       CTRL_N = 14,        /* Ctrl-n */
-       CTRL_P = 16,        /* Ctrl-p */
-       CTRL_T = 20,        /* Ctrl-t */
-       CTRL_U = 21,        /* Ctrl+u */
-       CTRL_W = 23,        /* Ctrl+w */
-       ESC = 27,           /* Escape */
-       BACKSPACE =  127    /* Backspace */
-};
-
-static void linenoiseAtExit(void);
-int linenoiseHistoryAdd(const char *line);
-static void refreshLine(struct linenoiseState *l);
-
-/* Debugging macro. */
-#if 0
-FILE *lndebug_fp = NULL;
-#define lndebug(...) \
-    do { \
-        if (lndebug_fp == NULL) { \
-            lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
-            fprintf(lndebug_fp, \
-            "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
-            (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
-            (int)l->maxrows,old_rows); \
-        } \
-        fprintf(lndebug_fp, ", " __VA_ARGS__); \
-        fflush(lndebug_fp); \
-    } while (0)
-#else
-#define lndebug(...)
-#endif
-
-/* ======================= Low level terminal handling ====================== */
-
-/* Set if to use or not the multi line mode. */
-void linenoiseSetMultiLine(int ml) {
-    mlmode = ml;
-}
-
-/* Return true if the terminal name is in the list of terminals we know are
- * not able to understand basic escape sequences. */
-static int isUnsupportedTerm(void) {
-    char *term = getenv("TERM");
-    int j;
-
-    if (term == NULL) return 0;
-    for (j = 0; unsupported_term[j]; j++)
-        if (!strcasecmp(term,unsupported_term[j])) return 1;
-    return 0;
-}
-
-/* Raw mode: 1960 magic shit. */
-static int enableRawMode(int fd) {
-    struct termios raw;
-
-    if (!isatty(STDIN_FILENO)) goto fatal;
-    if (!atexit_registered) {
-        atexit(linenoiseAtExit);
-        atexit_registered = 1;
-    }
-    if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
-
-    raw = orig_termios;  /* 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 */
-    raw.c_oflag &= ~(OPOST);
-    /* control modes - set 8 bit chars */
-    raw.c_cflag |= (CS8);
-    /* local modes - choing 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(fd,TCSAFLUSH,&raw) < 0) goto fatal;
-    rawmode = 1;
-    return 0;
-
-fatal:
-    errno = ENOTTY;
-    return -1;
-}
-
-static void disableRawMode(int fd) {
-    /* Don't even check the return value as it's too late. */
-    if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
-        rawmode = 0;
-}
-
-/* Use the ESC [6n escape sequence to query the horizontal cursor position
- * and return it. On error -1 is returned, on success the position of the
- * cursor. */
-static int getCursorPosition(int ifd, int ofd) {
-    char buf[32];
-    int cols, rows;
-    unsigned int i = 0;
-
-    /* Report cursor location */
-    if (write(ofd, "\x1b[6n", 4) != 4) return -1;
-
-    /* Read the response: ESC [ rows ; cols R */
-    while (i < sizeof(buf)-1) {
-        if (read(ifd,buf+i,1) != 1) break;
-        if (buf[i] == 'R') break;
-        i++;
-    }
-    buf[i] = '\0';
-
-    /* Parse it. */
-    if (buf[0] != ESC || buf[1] != '[') return -1;
-    if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
-    return cols;
-}
-
-/* Try to get the number of columns in the current terminal, or assume 80
- * if it fails. */
-int linenoiseGetColumns(int ifd, int ofd) {
-    struct winsize ws;
-
-    if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
-        /* ioctl() failed. Try to query the terminal itself. */
-        int start, cols;
-
-        /* Get the initial position so we can restore it later. */
-        start = getCursorPosition(ifd,ofd);
-        if (start == -1) goto failed;
-
-        /* Go to right margin and get position. */
-        if (write(ofd,"\x1b[999C",6) != 6) goto failed;
-        cols = getCursorPosition(ifd,ofd);
-        if (cols == -1) goto failed;
-
-        /* Restore position. */
-        if (cols > start) {
-            char seq[32];
-            snprintf(seq,32,"\x1b[%dD",cols-start);
-            if (write(ofd,seq,strlen(seq)) == -1) {
-                /* Can't recover... */
-            }
-        }
-        return cols;
-    } else {
-        return ws.ws_col;
-    }
-
-failed:
-    return 80;
-}
-
-/* Clear the screen. Used to handle ctrl+l */
-void linenoiseClearScreen(void) {
-    if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
-        /* nothing to do, just to avoid warning. */
-    }
-}
-
-/* Beep, used for completion when there is nothing to complete or when all
- * the choices were already shown. */
-static void linenoiseBeep(void) {
-    fprintf(stderr, "\x7");
-    fflush(stderr);
-}
-
-/* ============================== Completion ================================ */
-
-/* Free a list of completion option populated by linenoiseAddCompletion(). */
-static void freeCompletions(linenoiseCompletions *lc) {
-    size_t i;
-    for (i = 0; i < lc->len; i++)
-        free(lc->cvec[i]);
-    if (lc->cvec != NULL)
-        free(lc->cvec);
-}
-
-/* This is an helper function for linenoiseEdit() and is called when the
- * user types the <tab> key in order to complete the string currently in the
- * input.
- *
- * The state of the editing is encapsulated into the pointed linenoiseState
- * structure as described in the structure definition. */
-static int completeLine(struct linenoiseState *ls) {
-    linenoiseCompletions lc = { 0, NULL };
-    int nread, nwritten;
-    char c = 0;
-
-    completionCallback(ls->buf,&lc);
-    if (lc.len == 0) {
-        linenoiseBeep();
-    } else {
-        size_t stop = 0, i = 0;
-
-        while(!stop) {
-            /* Show completion or original buffer */
-            if (i < lc.len) {
-                struct linenoiseState saved = *ls;
-
-                ls->len = ls->pos = strlen(lc.cvec[i]);
-                ls->buf = lc.cvec[i];
-                refreshLine(ls);
-                ls->len = saved.len;
-                ls->pos = saved.pos;
-                ls->buf = saved.buf;
-            } else {
-                refreshLine(ls);
-            }
-
-            nread = read(ls->ifd,&c,1);
-            if (nread <= 0) {
-                freeCompletions(&lc);
-                return -1;
-            }
-
-            switch(c) {
-                case 9: /* tab */
-                    i = (i+1) % (lc.len+1);
-                    if (i == lc.len) linenoiseBeep();
-                    break;
-                case 27: /* escape */
-                    /* Re-show original buffer */
-                    if (i < lc.len) refreshLine(ls);
-                    stop = 1;
-                    break;
-                default:
-                    /* Update buffer and return */
-                    if (i < lc.len) {
-                        nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
-                        ls->len = ls->pos = nwritten;
-                    }
-                    stop = 1;
-                    break;
-            }
-        }
-    }
-
-    freeCompletions(&lc);
-    return c; /* Return last read character */
-}
-
-/* Register a callback function to be called for tab-completion. */
-void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
-    completionCallback = fn;
-}
-
-/* Register a hits function to be called to show hits to the user at the
- * right of the prompt. */
-void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
-    hintsCallback = fn;
-}
-
-/* Register a function to free the hints returned by the hints callback
- * registered with linenoiseSetHintsCallback(). */
-void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
-    freeHintsCallback = fn;
-}
-
-/* This function is used by the callback function registered by the user
- * in order to add completion options given the input string when the
- * user typed <tab>. See the example.c source code for a very easy to
- * understand example. */
-void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
-    size_t len = strlen(str);
-    char *copy, **cvec;
-
-    copy = malloc(len+1);
-    if (copy == NULL) return;
-    memcpy(copy,str,len+1);
-    cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
-    if (cvec == NULL) {
-        free(copy);
-        return;
-    }
-    lc->cvec = cvec;
-    lc->cvec[lc->len++] = copy;
-}
-
-/* =========================== Line editing ================================= */
-
-/* We define a very simple "append buffer" structure, that is an heap
- * allocated string where we can append to. This is useful in order to
- * write all the escape sequences in a buffer and flush them to the standard
- * output in a single call, to avoid flickering effects. */
-struct abuf {
-    char *b;
-    int len;
-};
-
-static void abInit(struct abuf *ab) {
-    ab->b = NULL;
-    ab->len = 0;
-}
-
-static void abAppend(struct abuf *ab, const char *s, int len) {
-    char *new = realloc(ab->b,ab->len+len);
-
-    if (new == NULL) return;
-    memcpy(new+ab->len,s,len);
-    ab->b = new;
-    ab->len += len;
-}
-
-static void abFree(struct abuf *ab) {
-    free(ab->b);
-}
-
-/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
- * to the right of the prompt. */
-void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
-    char seq[64];
-    if (hintsCallback && plen+l->len < l->cols) {
-        int color = -1, bold = 0;
-        char *hint = hintsCallback(l->buf,&color,&bold);
-        if (hint) {
-            int hintlen = strlen(hint);
-            int hintmaxlen = l->cols-(plen+l->len);
-            if (hintlen > hintmaxlen) hintlen = hintmaxlen;
-            if (bold == 1 && color == -1) color = 37;
-            if (color != -1 || bold != 0)
-                snprintf(seq,64,"\033[%d;%d;49m",bold,color);
-            abAppend(ab,seq,strlen(seq));
-            abAppend(ab,hint,hintlen);
-            if (color != -1 || bold != 0)
-                abAppend(ab,"\033[0m",4);
-            /* Call the function to free the hint returned. */
-            if (freeHintsCallback) freeHintsCallback(hint);
-        }
-    }
-}
-
-/* Single line low level line refresh.
- *
- * Rewrite the currently edited line accordingly to the buffer content,
- * cursor position, and number of columns of the terminal. */
-static void refreshSingleLine(struct linenoiseState *l) {
-    char seq[64];
-    size_t plen = strlen(l->prompt);
-    int fd = l->ofd;
-    char *buf = l->buf;
-    size_t len = l->len;
-    size_t pos = l->pos;
-    struct abuf ab;
-
-    while((plen+pos) >= l->cols) {
-        buf++;
-        len--;
-        pos--;
-    }
-    while (plen+len > l->cols) {
-        len--;
-    }
-
-    abInit(&ab);
-    /* Cursor to left edge */
-    snprintf(seq,64,"\r");
-    abAppend(&ab,seq,strlen(seq));
-    /* Write the prompt and the current buffer content */
-    abAppend(&ab,l->prompt,strlen(l->prompt));
-    abAppend(&ab,buf,len);
-    /* Show hits if any. */
-    refreshShowHints(&ab,l,plen);
-    /* Erase to right */
-    snprintf(seq,64,"\x1b[0K");
-    abAppend(&ab,seq,strlen(seq));
-    /* Move cursor to original position. */
-    snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
-    abAppend(&ab,seq,strlen(seq));
-    if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
-    abFree(&ab);
-}
-
-/* Multi line low level line refresh.
- *
- * Rewrite the currently edited line accordingly to the buffer content,
- * cursor position, and number of columns of the terminal. */
-static void refreshMultiLine(struct linenoiseState *l) {
-    char seq[64];
-    int plen = strlen(l->prompt);
-    int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
-    int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
-    int rpos2; /* rpos after refresh. */
-    int col; /* colum position, zero-based. */
-    int old_rows = l->maxrows;
-    int fd = l->ofd, j;
-    struct abuf ab;
-
-    /* Update maxrows if needed. */
-    if (rows > (int)l->maxrows) l->maxrows = rows;
-
-    /* First step: clear all the lines used before. To do so start by
-     * going to the last row. */
-    abInit(&ab);
-    if (old_rows-rpos > 0) {
-        lndebug("go down %d", old_rows-rpos);
-        snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
-        abAppend(&ab,seq,strlen(seq));
-    }
-
-    /* Now for every row clear it, go up. */
-    for (j = 0; j < old_rows-1; j++) {
-        lndebug("clear+up");
-        snprintf(seq,64,"\r\x1b[0K\x1b[1A");
-        abAppend(&ab,seq,strlen(seq));
-    }
-
-    /* Clean the top line. */
-    lndebug("clear");
-    snprintf(seq,64,"\r\x1b[0K");
-    abAppend(&ab,seq,strlen(seq));
-
-    /* Write the prompt and the current buffer content */
-    abAppend(&ab,l->prompt,strlen(l->prompt));
-    abAppend(&ab,l->buf,l->len);
-
-    /* Show hits if any. */
-    refreshShowHints(&ab,l,plen);
-
-    /* If we are at the very end of the screen with our prompt, we need to
-     * emit a newline and move the prompt to the first column. */
-    if (l->pos &&
-        l->pos == l->len &&
-        (l->pos+plen) % l->cols == 0)
-    {
-        lndebug("<newline>");
-        abAppend(&ab,"\n",1);
-        snprintf(seq,64,"\r");
-        abAppend(&ab,seq,strlen(seq));
-        rows++;
-        if (rows > (int)l->maxrows) l->maxrows = rows;
-    }
-
-    /* Move cursor to right position. */
-    rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */
-    lndebug("rpos2 %d", rpos2);
-
-    /* Go up till we reach the expected positon. */
-    if (rows-rpos2 > 0) {
-        lndebug("go-up %d", rows-rpos2);
-        snprintf(seq,64,"\x1b[%dA", rows-rpos2);
-        abAppend(&ab,seq,strlen(seq));
-    }
-
-    /* Set column. */
-    col = (plen+(int)l->pos) % (int)l->cols;
-    lndebug("set col %d", 1+col);
-    if (col)
-        snprintf(seq,64,"\r\x1b[%dC", col);
-    else
-        snprintf(seq,64,"\r");
-    abAppend(&ab,seq,strlen(seq));
-
-    lndebug("\n");
-    l->oldpos = l->pos;
-
-    if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
-    abFree(&ab);
-}
-
-/* Calls the two low level functions refreshSingleLine() or
- * refreshMultiLine() according to the selected mode. */
-static void refreshLine(struct linenoiseState *l) {
-    if (mlmode)
-        refreshMultiLine(l);
-    else
-        refreshSingleLine(l);
-}
-
-/* Insert the character 'c' at cursor current position.
- *
- * On error writing to the terminal -1 is returned, otherwise 0. */
-int linenoiseEditInsert(struct linenoiseState *l, char c) {
-    if (l->len < l->buflen) {
-        if (l->len == l->pos) {
-            l->buf[l->pos] = c;
-            l->pos++;
-            l->len++;
-            l->buf[l->len] = '\0';
-            if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
-                /* Avoid a full update of the line in the
-                 * trivial case. */
-                if (write(l->ofd,&c,1) == -1) return -1;
-            } else {
-                refreshLine(l);
-            }
-        } else {
-            memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
-            l->buf[l->pos] = c;
-            l->len++;
-            l->pos++;
-            l->buf[l->len] = '\0';
-            refreshLine(l);
-        }
-    }
-    return 0;
-}
-
-/* Move cursor on the left. */
-void linenoiseEditMoveLeft(struct linenoiseState *l) {
-    if (l->pos > 0) {
-        l->pos--;
-        refreshLine(l);
-    }
-}
-
-/* Move cursor on the right. */
-void linenoiseEditMoveRight(struct linenoiseState *l) {
-    if (l->pos != l->len) {
-        l->pos++;
-        refreshLine(l);
-    }
-}
-
-/* Move cursor to the start of the line. */
-void linenoiseEditMoveHome(struct linenoiseState *l) {
-    if (l->pos != 0) {
-        l->pos = 0;
-        refreshLine(l);
-    }
-}
-
-/* Move cursor to the end of the line. */
-void linenoiseEditMoveEnd(struct linenoiseState *l) {
-    if (l->pos != l->len) {
-        l->pos = l->len;
-        refreshLine(l);
-    }
-}
-
-/* Substitute the currently edited line with the next or previous history
- * entry as specified by 'dir'. */
-#define LINENOISE_HISTORY_NEXT 0
-#define LINENOISE_HISTORY_PREV 1
-void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
-    if (history_len > 1) {
-        /* Update the current history entry before to
-         * overwrite it with the next one. */
-        free(history[history_len - 1 - l->history_index]);
-        history[history_len - 1 - l->history_index] = strdup(l->buf);
-        /* Show the new entry */
-        l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
-        if (l->history_index < 0) {
-            l->history_index = 0;
-            return;
-        } else if (l->history_index >= history_len) {
-            l->history_index = history_len-1;
-            return;
-        }
-        strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
-        l->buf[l->buflen-1] = '\0';
-        l->len = l->pos = strlen(l->buf);
-        refreshLine(l);
-    }
-}
-
-/* Delete the character at the right of the cursor without altering the cursor
- * position. Basically this is what happens with the "Delete" keyboard key. */
-void linenoiseEditDelete(struct linenoiseState *l) {
-    if (l->len > 0 && l->pos < l->len) {
-        memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
-        l->len--;
-        l->buf[l->len] = '\0';
-        refreshLine(l);
-    }
-}
-
-/* Backspace implementation. */
-void linenoiseEditBackspace(struct linenoiseState *l) {
-    if (l->pos > 0 && l->len > 0) {
-        memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
-        l->pos--;
-        l->len--;
-        l->buf[l->len] = '\0';
-        refreshLine(l);
-    }
-}
-
-/* Delete the previosu word, maintaining the cursor at the start of the
- * current word. */
-void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
-    size_t old_pos = l->pos;
-    size_t diff;
-
-    while (l->pos > 0 && l->buf[l->pos-1] == ' ')
-        l->pos--;
-    while (l->pos > 0 && l->buf[l->pos-1] != ' ')
-        l->pos--;
-    diff = old_pos - l->pos;
-    memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
-    l->len -= diff;
-    refreshLine(l);
-}
-
-/* This function is the core of the line editing capability of linenoise.
- * It expects 'fd' to be already in "raw mode" so that every key pressed
- * will be returned ASAP to read().
- *
- * The resulting string is put into 'buf' when the user type enter, or
- * when ctrl+d is typed.
- *
- * The function returns the length of the current buffer. */
-static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
-{
-    struct linenoiseState l;
-
-    /* Populate the linenoise state that we pass to functions implementing
-     * specific editing functionalities. */
-    l.ifd = stdin_fd;
-    l.ofd = stdout_fd;
-    l.buf = buf;
-    l.buflen = buflen;
-    l.prompt = prompt;
-    l.plen = strlen(prompt);
-    l.oldpos = l.pos = 0;
-    l.len = 0;
-    l.cols = linenoiseGetColumns(stdin_fd, stdout_fd);
-    l.maxrows = 0;
-    l.history_index = 0;
-
-    /* Buffer starts empty. */
-    l.buf[0] = '\0';
-    l.buflen--; /* Make sure there is always space for the nulterm */
-
-    /* The latest history entry is always our current buffer, that
-     * initially is just an empty string. */
-    linenoiseHistoryAdd("");
-
-    if (write(l.ofd,prompt,l.plen) == -1) return -1;
-    while(1) {
-        char c;
-        int nread;
-        char seq[3];
-
-        nread = read(l.ifd,&c,1);
-        if (nread <= 0) return l.len;
-
-        /* Only autocomplete when the callback is set. It returns < 0 when
-         * there was an error reading from fd. Otherwise it will return the
-         * character that should be handled next. */
-        if (c == 9 && completionCallback != NULL) {
-            c = completeLine(&l);
-            /* Return on errors */
-            if (c < 0) return l.len;
-            /* Read next character when 0 */
-            if (c == 0) continue;
-        }
-
-        switch(c) {
-        case ENTER:    /* enter */
-            history_len--;
-            free(history[history_len]);
-            if (mlmode) linenoiseEditMoveEnd(&l);
-            if (hintsCallback) {
-                /* Force a refresh without hints to leave the previous
-                 * line as the user typed it after a newline. */
-                linenoiseHintsCallback *hc = hintsCallback;
-                hintsCallback = NULL;
-                refreshLine(&l);
-                hintsCallback = hc;
-            }
-            return (int)l.len;
-        case CTRL_C:     /* ctrl-c */
-            errno = EAGAIN;
-            return -1;
-        case BACKSPACE:   /* backspace */
-        case 8:     /* ctrl-h */
-            linenoiseEditBackspace(&l);
-            break;
-        case CTRL_D:     /* ctrl-d, remove char at right of cursor, or if the
-                            line is empty, act as end-of-file. */
-            if (l.len > 0) {
-                linenoiseEditDelete(&l);
-            } else {
-                history_len--;
-                free(history[history_len]);
-                return -1;
-            }
-            break;
-        case CTRL_T:    /* ctrl-t, swaps current character with previous. */
-            if (l.pos > 0 && l.pos < l.len) {
-                int aux = buf[l.pos-1];
-                buf[l.pos-1] = buf[l.pos];
-                buf[l.pos] = aux;
-                if (l.pos != l.len-1) l.pos++;
-                refreshLine(&l);
-            }
-            break;
-        case CTRL_B:     /* ctrl-b */
-            linenoiseEditMoveLeft(&l);
-            break;
-        case CTRL_F:     /* ctrl-f */
-            linenoiseEditMoveRight(&l);
-            break;
-        case CTRL_P:    /* ctrl-p */
-            linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
-            break;
-        case CTRL_N:    /* ctrl-n */
-            linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
-            break;
-        case ESC:    /* escape sequence */
-            /* Read the next two bytes representing the escape sequence.
-             * Use two calls to handle slow terminals returning the two
-             * chars at different times. */
-            if (read(l.ifd,seq,1) == -1) break;
-            if (read(l.ifd,seq+1,1) == -1) break;
-
-            /* ESC [ sequences. */
-            if (seq[0] == '[') {
-                if (seq[1] >= '0' && seq[1] <= '9') {
-                    /* Extended escape, read additional byte. */
-                    if (read(l.ifd,seq+2,1) == -1) break;
-                    if (seq[2] == '~') {
-                        switch(seq[1]) {
-                        case '3': /* Delete key. */
-                            linenoiseEditDelete(&l);
-                            break;
-                        }
-                    }
-                } else {
-                    switch(seq[1]) {
-                    case 'A': /* Up */
-                        linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
-                        break;
-                    case 'B': /* Down */
-                        linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
-                        break;
-                    case 'C': /* Right */
-                        linenoiseEditMoveRight(&l);
-                        break;
-                    case 'D': /* Left */
-                        linenoiseEditMoveLeft(&l);
-                        break;
-                    case 'H': /* Home */
-                        linenoiseEditMoveHome(&l);
-                        break;
-                    case 'F': /* End*/
-                        linenoiseEditMoveEnd(&l);
-                        break;
-                    }
-                }
-            }
-
-            /* ESC O sequences. */
-            else if (seq[0] == 'O') {
-                switch(seq[1]) {
-                case 'H': /* Home */
-                    linenoiseEditMoveHome(&l);
-                    break;
-                case 'F': /* End*/
-                    linenoiseEditMoveEnd(&l);
-                    break;
-                }
-            }
-            break;
-        default:
-            if (linenoiseEditInsert(&l,c)) return -1;
-            break;
-        case CTRL_U: /* Ctrl+u, delete the whole line. */
-            buf[0] = '\0';
-            l.pos = l.len = 0;
-            refreshLine(&l);
-            break;
-        case CTRL_K: /* Ctrl+k, delete from current to end of line. */
-            buf[l.pos] = '\0';
-            l.len = l.pos;
-            refreshLine(&l);
-            break;
-        case CTRL_A: /* Ctrl+a, go to the start of the line */
-            linenoiseEditMoveHome(&l);
-            break;
-        case CTRL_E: /* ctrl+e, go to the end of the line */
-            linenoiseEditMoveEnd(&l);
-            break;
-        case CTRL_L: /* ctrl+l, clear screen */
-            linenoiseClearScreen();
-            refreshLine(&l);
-            break;
-        case CTRL_W: /* ctrl+w, delete previous word */
-            linenoiseEditDeletePrevWord(&l);
-            break;
-        }
-    }
-    return l.len;
-}
-
-/* This special mode is used by linenoise in order to print scan codes
- * on screen for debugging / development purposes. It is implemented
- * by the linenoise_example program using the --keycodes option. */
-void linenoisePrintKeyCodes(void) {
-    char quit[4];
-
-    printf("Linenoise key codes debugging mode.\n"
-            "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
-    if (enableRawMode(STDIN_FILENO) == -1) return;
-    memset(quit,' ',4);
-    while(1) {
-        char c;
-        int nread;
-
-        nread = read(STDIN_FILENO,&c,1);
-        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(STDIN_FILENO);
-}
-
-/* This function calls the line editing function linenoiseEdit() using
- * the STDIN file descriptor set in raw mode. */
-static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
-    int count;
-
-    if (buflen == 0) {
-        errno = EINVAL;
-        return -1;
-    }
-
-    if (enableRawMode(STDIN_FILENO) == -1) return -1;
-    count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
-    disableRawMode(STDIN_FILENO);
-    printf("\n");
-    return count;
-}
-
-/* This function is called when linenoise() is called with the standard
- * input file descriptor not attached to a TTY. So for example when the
- * program using linenoise is called in pipe or with a file redirected
- * to its standard input. In this case, we want to be able to return the
- * line regardless of its length (by default we are limited to 4k). */
-static char *linenoiseNoTTY(void) {
-    char *line = NULL;
-    size_t len = 0, maxlen = 0;
-
-    while(1) {
-        if (len == maxlen) {
-            if (maxlen == 0) maxlen = 16;
-            maxlen *= 2;
-            char *oldval = line;
-            line = realloc(line,maxlen);
-            if (line == NULL) {
-                if (oldval) free(oldval);
-                return NULL;
-            }
-        }
-        int c = fgetc(stdin);
-        if (c == EOF || c == '\n') {
-            if (c == EOF && len == 0) {
-                free(line);
-                return NULL;
-            } else {
-                line[len] = '\0';
-                return line;
-            }
-        } else {
-            line[len] = c;
-            len++;
-        }
-    }
-}
-
-/* The high level function that is the main API of the linenoise library.
- * This function checks if the terminal has basic capabilities, just checking
- * for a blacklist of stupid terminals, and later either calls the line
- * editing function or uses dummy fgets() so that you will be able to type
- * something even in the most desperate of the conditions. */
-char *linenoise(const char *prompt) {
-    char buf[LINENOISE_MAX_LINE];
-    int count;
-
-    if (!isatty(STDIN_FILENO)) {
-        /* Not a tty: read from file / pipe. In this mode we don't want any
-         * limit to the line size, so we call a function to handle that. */
-        return linenoiseNoTTY();
-    } else if (isUnsupportedTerm()) {
-        size_t len;
-
-        printf("%s",prompt);
-        fflush(stdout);
-        if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
-        len = strlen(buf);
-        while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
-            len--;
-            buf[len] = '\0';
-        }
-        return strdup(buf);
-    } else {
-        count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
-        if (count == -1) return NULL;
-        return strdup(buf);
-    }
-}
-
-/* This is just a wrapper the user may want to call in order to make sure
- * the linenoise returned buffer is freed with the same allocator it was
- * created with. Useful when the main program is using an alternative
- * allocator. */
-void linenoiseFree(void *ptr) {
-    free(ptr);
-}
-
-/* ================================ History ================================= */
-
-/* Free the history, but does not reset it. Only used when we have to
- * exit() to avoid memory leaks are reported by valgrind & co. */
-static void freeHistory(void) {
-    if (history) {
-        int j;
-
-        for (j = 0; j < history_len; j++)
-            free(history[j]);
-        free(history);
-    }
-}
-
-/* At exit we'll try to fix the terminal to the initial conditions. */
-static void linenoiseAtExit(void) {
-    disableRawMode(STDIN_FILENO);
-    freeHistory();
-}
-
-/* This is the API call to add a new entry in the linenoise history.
- * It uses a fixed array of char pointers that are shifted (memmoved)
- * when the history max length is reached in order to remove the older
- * entry and make room for the new one, so it is not exactly suitable for huge
- * histories, but will work well for a few hundred of entries.
- *
- * Using a circular buffer is smarter, but a bit more complex to handle. */
-int linenoiseHistoryAdd(const char *line) {
-    char *linecopy;
-
-    if (history_max_len == 0) return 0;
-
-    /* Initialization on first call. */
-    if (history == NULL) {
-        history = malloc(sizeof(char*)*history_max_len);
-        if (history == NULL) return 0;
-        memset(history,0,(sizeof(char*)*history_max_len));
-    }
-
-    /* Don't add duplicated lines. */
-    if (history_len && !strcmp(history[history_len-1], line)) return 0;
-
-    /* Add an heap allocated copy of the line in the history.
-     * If we reached the max length, remove the older line. */
-    linecopy = strdup(line);
-    if (!linecopy) return 0;
-    if (history_len == history_max_len) {
-        free(history[0]);
-        memmove(history,history+1,sizeof(char*)*(history_max_len-1));
-        history_len--;
-    }
-    history[history_len] = linecopy;
-    history_len++;
-    return 1;
-}
-
-/* Set the maximum length for the history. This function can be called even
- * if there is already some history, the function will make sure to retain
- * just the latest 'len' elements if the new history length value is smaller
- * than the amount of items already inside the history. */
-int linenoiseHistorySetMaxLen(int len) {
-    char **new;
-
-    if (len < 1) return 0;
-    if (history) {
-        int tocopy = history_len;
-
-        new = malloc(sizeof(char*)*len);
-        if (new == NULL) return 0;
-
-        /* If we can't copy everything, free the elements we'll not use. */
-        if (len < tocopy) {
-            int j;
-
-            for (j = 0; j < tocopy-len; j++) free(history[j]);
-            tocopy = len;
-        }
-        memset(new,0,sizeof(char*)*len);
-        memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
-        free(history);
-        history = new;
-    }
-    history_max_len = len;
-    if (history_len > history_max_len)
-        history_len = history_max_len;
-    return 1;
-}
-
-/* Save the history in the specified file. On success 0 is returned
- * otherwise -1 is returned. */
-int linenoiseHistorySave(const char *filename) {
-    FILE *fp = fopen(filename,"w");
-    int j;
-
-    if (fp == NULL) return -1;
-    for (j = 0; j < history_len; j++)
-        fprintf(fp,"%s\n",history[j]);
-    fclose(fp);
-    return 0;
-}
-
-/* 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 linenoiseHistoryLoad(const char *filename) {
-    FILE *fp = fopen(filename,"r");
-    char buf[LINENOISE_MAX_LINE];
-
-    if (fp == NULL) return -1;
-
-    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
-        char *p;
-
-        p = strchr(buf,'\r');
-        if (!p) p = strchr(buf,'\n');
-        if (p) *p = '\0';
-        linenoiseHistoryAdd(buf);
-    }
-    fclose(fp);
-    return 0;
-}
diff --git a/contrib/linenoise/linenoise.h b/contrib/linenoise/linenoise.h
deleted file mode 100644 (file)
index ca855f0..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/* linenoise.h -- VERSION 1.0
- *
- * Guerrilla line editing library against the idea that a line editing lib
- * needs to be 20,000 lines of C code.
- *
- * See linenoise.c for more information.
- *
- * ------------------------------------------------------------------------
- *
- * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
- * Copyright (c) 2010-2013, 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.
- *
- * 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
- * HOLDER 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 __LINENOISE_H
-#define __LINENOISE_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct linenoiseCompletions {
-  size_t len;
-  char **cvec;
-} linenoiseCompletions;
-
-typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
-typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
-typedef void(linenoiseFreeHintsCallback)(void *);
-void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
-void linenoiseSetHintsCallback(linenoiseHintsCallback *);
-void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
-void linenoiseAddCompletion(linenoiseCompletions *, const char *);
-
-char *linenoise(const char *prompt);
-void linenoiseFree(void *ptr);
-int linenoiseHistoryAdd(const char *line);
-int linenoiseHistorySetMaxLen(int len);
-int linenoiseHistorySave(const char *filename);
-int linenoiseHistoryLoad(const char *filename);
-void linenoiseClearScreen(void);
-void linenoiseSetMultiLine(int ml);
-void linenoisePrintKeyCodes(void);
-
-/* Rspamd specific */
-int linenoiseGetColumns(int ifd, int ofd);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __LINENOISE_H */
diff --git a/contrib/replxx/CMakeLists.txt b/contrib/replxx/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a2dcf2d
--- /dev/null
@@ -0,0 +1,82 @@
+# -*- mode: CMAKE; -*-
+
+cmake_minimum_required(VERSION 3.0)
+
+project( replxx VERSION 0.0.2 LANGUAGES CXX C )
+message(STATUS "Build mode: ${CMAKE_BUILD_TYPE}")
+
+# INFO
+set(REPLXX_DISPLAY_NAME "replxx")
+set(REPLXX_URL_INFO_ABOUT "https://github.com/AmokHuginnsson/replxx")
+set(REPLXX_CONTACT "amok@codestation.org")
+set(REPLXX_FRIENDLY_STRING "replxx - Read Evaluate Print Loop library")
+
+# compiler options
+if(CMAKE_COMPILER_IS_GNUCXX)
+       message(STATUS "Compiler type GNU: ${CMAKE_CXX_COMPILER}")
+       set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -D_GNU_SOURCE -pthread")
+       set(CMAKE_CXX_FLAGS                "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}")
+       set(CMAKE_CXX_FLAGS_COVERAGE       "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS} -O0 --coverage -fno-inline -fno-default-inline -fno-inline-small-functions")
+       set(CMAKE_CXX_FLAGS_DEBUG          "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g -ggdb -g3 -ggdb3")
+       set(CMAKE_CXX_FLAGS_MINSIZEREL     "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os")
+       set(CMAKE_CXX_FLAGS_RELEASE        "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer")
+       set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g")
+       set(CMAKE_C_FLAGS "-std=c99")
+elseif(CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+       # using regular Clang or AppleClang
+       message(STATUS "Compiler type CLANG: ${CMAKE_CXX_COMPILER}")
+       set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -Wextra -D_GNU_SOURCE -pthread")
+       set(CMAKE_CXX_FLAGS                "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}")
+       set(CMAKE_CXX_FLAGS_DEBUG          "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g")
+       set(CMAKE_CXX_FLAGS_MINSIZEREL     "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os")
+       set(CMAKE_CXX_FLAGS_RELEASE        "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer")
+       set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g")
+       set(CMAKE_C_FLAGS "-std=c99")
+elseif(MSVC)
+       message(STATUS "Compiler type MSVC: ${CMAKE_CXX_COMPILER}")
+       add_definitions("-D_CRT_SECURE_NO_WARNINGS=1")
+
+       set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO /SUBSYSTEM:CONSOLE /LTCG /ignore:4099")
+       set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /SUBSYSTEM:CONSOLE /ignore:4099")
+       set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /SUBSYSTEM:CONSOLE /ignore:4099")
+       set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /SUBSYSTEM:CONSOLE /ignore:4099")
+else()
+       # unknown compiler
+       message(STATUS "Compiler type UNKNOWN: ${CMAKE_CXX_COMPILER}")
+       set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -pthread")
+       set(CMAKE_CXX_FLAGS                "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}")
+       set(CMAKE_CXX_FLAGS_DEBUG          "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g")
+       set(CMAKE_CXX_FLAGS_MINSIZEREL     "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os")
+       set(CMAKE_CXX_FLAGS_RELEASE        "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer")
+       set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g")
+       set(CMAKE_C_FLAGS "-std=c99")
+endif()
+
+# build libreplxx
+set(
+       REPLXX_SOURCES
+       src/conversion.cxx
+       src/ConvertUTF.cpp
+       src/escape.cxx
+       src/history.cxx
+       src/replxx_impl.cxx
+       src/io.cxx
+       src/prompt.cxx
+       src/replxx.cxx
+       src/util.cxx
+       src/wcwidth.cpp
+       src/windows.cxx
+)
+
+set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+add_library(rspamd-replxx SHARED ${REPLXX_SOURCES})
+
+target_include_directories(
+       rspamd-replxx
+       PUBLIC ${PROJECT_SOURCE_DIR}/include
+       PRIVATE ${PROJECT_SOURCE_DIR}/src
+)
+set( TARGETS ${TARGETS} rspamd-replxx )
+target_compile_definitions(rspamd-replxx PRIVATE REPLXX_BUILDING_DLL)
+
+install( TARGETS ${TARGETS} LIBRARY DESTINATION ${RSPAMD_LIBDIR})
\ No newline at end of file
diff --git a/contrib/replxx/LICENSE.md b/contrib/replxx/LICENSE.md
new file mode 100644 (file)
index 0000000..c73c3f2
--- /dev/null
@@ -0,0 +1,63 @@
+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.
+
+
+wcwidth.cpp
+===========
+
+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.
+
+
+ConvertUTF.cpp
+==============
+
+Copyright 2001-2004 Unicode, Inc.
+
+Disclaimer
+
+This source code is provided as is by Unicode, Inc. No claims are
+made as to fitness for any particular purpose. No warranties of any
+kind are expressed or implied. The recipient agrees to determine
+applicability of information provided. If this file has been
+purchased on magnetic or optical media from Unicode, Inc., the
+sole remedy for any claim will be exchange of defective media
+within 90 days of receipt.
+
+Limitations on Rights to Redistribute This Code
+
+Unicode, Inc. hereby grants the right to freely use the information
+supplied in this file in the creation of products supporting the
+Unicode Standard, and to make copies of this file in any form
+for internal or external distribution as long as this notice
+remains attached.
diff --git a/contrib/replxx/README.md b/contrib/replxx/README.md
new file mode 100644 (file)
index 0000000..21c907a
--- /dev/null
@@ -0,0 +1,119 @@
+# Read Evaluate Print Loop ++
+
+![demo](https://drive.google.com/uc?export=download&id=0B53g2Y3z7rWNT2dCRGVVNldaRnc)
+
+[![Build Status](https://travis-ci.org/AmokHuginnsson/replxx.svg?branch=master)](https://travis-ci.org/AmokHuginnsson/replxx)
+
+A small, portable GNU readline replacement for Linux, Windows and
+MacOS which is capable of handling UTF-8 characters. Unlike GNU
+readline, which is GPL, this library uses a BSD license and can be
+used in any kind of program.
+
+## Origin
+
+This replxx implementation is based on the work by
+[ArangoDB Team](https://github.com/arangodb/linenoise-ng) and
+[Salvatore Sanfilippo](https://github.com/antirez/linenoise) and
+10gen Inc.  The goal is to create a zero-config, BSD
+licensed, readline replacement usable in Apache2 or BSD licensed
+programs.
+
+## Features
+
+* single-line and multi-line editing mode with the usual key bindings implemented
+* history handling
+* completion
+* syntax highlighting
+* hints
+* BSD license source code
+* Only uses a subset of VT100 escapes (ANSI.SYS compatible)
+* UTF8 aware
+* support for Linux, MacOS and Windows
+
+It deviates from Salvatore's original goal to have a minimal readline
+replacement for the sake of supporting UTF8 and Windows. It deviates
+from 10gen Inc.'s goal to create a C++ interface to linenoise. This
+library uses C++ internally, but to the user it provides a pure C
+interface that is compatible with the original linenoise API.
+C interface.
+
+## Requirements
+
+To build this library, you will need a C++11-enabled compiler and
+some recent version of CMake.
+
+## Build instructions
+
+### *nix
+
+1. Create a build directory
+
+```bash
+mkdir -p build && cd build
+```
+
+2. Build the library
+
+```bash
+cmake -DCMAKE_BUILD_TYPE=Release .. && make
+```
+
+3. Install the library at the default target location
+
+```bash
+sudo make install
+```
+
+The default installation location can be adjusted by setting the `DESTDIR`
+variable when invoking `make install`:
+
+```bash
+make DESTDIR=/tmp install
+```
+
+### Windows
+
+1. Create a build directory in MS-DOS command prompt
+
+```
+md build
+cd build
+```
+
+2. Generate Visual Studio solution file with cmake
+
+* 32 bit: 
+```bash
+cmake -G "Visual Studio 12 2013" -DCMAKE_BUILD_TYPE=Release ..`
+```
+* 64 bit:
+```bash
+`cmake -G "Visual Studio 12 2013 Win64" -DCMAKE_BUILD_TYPE=Release ..`
+```
+
+3. Open the generated file `replxx.sln` in the `build` subdirectory with Visual Studio.
+
+## Tested with...
+
+ * Linux text only console ($TERM = linux)
+ * Linux KDE terminal application ($TERM = xterm)
+ * Linux xterm ($TERM = xterm)
+ * Linux Buildroot ($TERM = vt100)
+ * Mac OS X iTerm ($TERM = xterm)
+ * Mac OS X default Terminal.app ($TERM = xterm)
+ * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
+ * IBM AIX 6.1
+ * FreeBSD xterm ($TERM = xterm)
+ * ANSI.SYS
+ * Emacs comint mode ($TERM = dumb)
+ * Windows
+
+Please test it everywhere you can and report back!
+
+## Let's push this forward!
+
+Patches should be provided in the respect of linenoise sensibility for
+small and easy to understand code that and the license
+restrictions. Extensions must be submitted under a BSD license-style.
+A contributor license is required for contributions.
+
diff --git a/contrib/replxx/include/replxx.h b/contrib/replxx/include/replxx.h
new file mode 100644 (file)
index 0000000..cb1c917
--- /dev/null
@@ -0,0 +1,454 @@
+/* linenoise.h -- guerrilla line editing library against the idea that a
+ * line editing lib needs to be 20,000 lines of C code.
+ *
+ * See linenoise.c for more information.
+ *
+ * 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.
+ */
+
+#ifndef __REPLXX_H
+#define __REPLXX_H
+
+#define REPLXX_VERSION "0.0.2"
+#define REPLXX_VERSION_MAJOR 0
+#define REPLXX_VERSION_MINOR 0
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * For use in Windows DLLs:
+ *
+ * If you are building replxx into a DLL,
+ * unless you are using supplied CMake based build,
+ * ensure that 'REPLXX_BUILDING_DLL' is defined when
+ * building the DLL so that proper symbols are exported.
+ */
+#if defined( _WIN32 ) && ! defined( REPLXX_STATIC )
+#      ifdef REPLXX_BUILDING_DLL
+#              define REPLXX_IMPEXP __declspec( dllexport )
+#      else
+#              define REPLXX_IMPEXP __declspec( dllimport )
+#      endif
+#else
+#      define REPLXX_IMPEXP /**/
+#endif
+
+/*! \brief Color definitions to use in highlighter callbacks.
+ */
+typedef enum {
+       REPLXX_COLOR_BLACK         = 0,
+       REPLXX_COLOR_RED           = 1,
+       REPLXX_COLOR_GREEN         = 2,
+       REPLXX_COLOR_BROWN         = 3,
+       REPLXX_COLOR_BLUE          = 4,
+       REPLXX_COLOR_MAGENTA       = 5,
+       REPLXX_COLOR_CYAN          = 6,
+       REPLXX_COLOR_LIGHTGRAY     = 7,
+       REPLXX_COLOR_GRAY          = 8,
+       REPLXX_COLOR_BRIGHTRED     = 9,
+       REPLXX_COLOR_BRIGHTGREEN   = 10,
+       REPLXX_COLOR_YELLOW        = 11,
+       REPLXX_COLOR_BRIGHTBLUE    = 12,
+       REPLXX_COLOR_BRIGHTMAGENTA = 13,
+       REPLXX_COLOR_BRIGHTCYAN    = 14,
+       REPLXX_COLOR_WHITE         = 15,
+       REPLXX_COLOR_NORMAL        = REPLXX_COLOR_LIGHTGRAY,
+       REPLXX_COLOR_DEFAULT       = -1,
+       REPLXX_COLOR_ERROR         = -2
+} ReplxxColor;
+
+enum { REPLXX_KEY_BASE         = 0x0010ffff + 1 };
+enum { REPLXX_KEY_BASE_SHIFT   = 0x01000000 };
+enum { REPLXX_KEY_BASE_CONTROL = 0x02000000 };
+enum { REPLXX_KEY_BASE_META    = 0x04000000 };
+enum { REPLXX_KEY_ESCAPE       = 27 };
+enum { REPLXX_KEY_PAGE_UP      = REPLXX_KEY_BASE      + 1 };
+enum { REPLXX_KEY_PAGE_DOWN    = REPLXX_KEY_PAGE_UP   + 1 };
+enum { REPLXX_KEY_DOWN         = REPLXX_KEY_PAGE_DOWN + 1 };
+enum { REPLXX_KEY_UP           = REPLXX_KEY_DOWN      + 1 };
+enum { REPLXX_KEY_LEFT         = REPLXX_KEY_UP        + 1 };
+enum { REPLXX_KEY_RIGHT        = REPLXX_KEY_LEFT      + 1 };
+enum { REPLXX_KEY_HOME         = REPLXX_KEY_RIGHT     + 1 };
+enum { REPLXX_KEY_END          = REPLXX_KEY_HOME      + 1 };
+enum { REPLXX_KEY_DELETE       = REPLXX_KEY_END       + 1 };
+enum { REPLXX_KEY_INSERT       = REPLXX_KEY_DELETE    + 1 };
+enum { REPLXX_KEY_F1           = REPLXX_KEY_INSERT    + 1 };
+enum { REPLXX_KEY_F2           = REPLXX_KEY_F1        + 1 };
+enum { REPLXX_KEY_F3           = REPLXX_KEY_F2        + 1 };
+enum { REPLXX_KEY_F4           = REPLXX_KEY_F3        + 1 };
+enum { REPLXX_KEY_F5           = REPLXX_KEY_F4        + 1 };
+enum { REPLXX_KEY_F6           = REPLXX_KEY_F5        + 1 };
+enum { REPLXX_KEY_F7           = REPLXX_KEY_F6        + 1 };
+enum { REPLXX_KEY_F8           = REPLXX_KEY_F7        + 1 };
+enum { REPLXX_KEY_F9           = REPLXX_KEY_F8        + 1 };
+enum { REPLXX_KEY_F10          = REPLXX_KEY_F9        + 1 };
+enum { REPLXX_KEY_F11          = REPLXX_KEY_F10       + 1 };
+enum { REPLXX_KEY_F12          = REPLXX_KEY_F11       + 1 };
+enum { REPLXX_KEY_F13          = REPLXX_KEY_F12       + 1 };
+enum { REPLXX_KEY_F14          = REPLXX_KEY_F13       + 1 };
+enum { REPLXX_KEY_F15          = REPLXX_KEY_F14       + 1 };
+enum { REPLXX_KEY_F16          = REPLXX_KEY_F15       + 1 };
+enum { REPLXX_KEY_F17          = REPLXX_KEY_F16       + 1 };
+enum { REPLXX_KEY_F18          = REPLXX_KEY_F17       + 1 };
+enum { REPLXX_KEY_F19          = REPLXX_KEY_F18       + 1 };
+enum { REPLXX_KEY_F20          = REPLXX_KEY_F19       + 1 };
+enum { REPLXX_KEY_F21          = REPLXX_KEY_F20       + 1 };
+enum { REPLXX_KEY_F22          = REPLXX_KEY_F21       + 1 };
+enum { REPLXX_KEY_F23          = REPLXX_KEY_F22       + 1 };
+enum { REPLXX_KEY_F24          = REPLXX_KEY_F23       + 1 };
+enum { REPLXX_KEY_MOUSE        = REPLXX_KEY_F24       + 1 };
+
+#define REPLXX_KEY_SHIFT( key )   ( ( key ) | REPLXX_KEY_BASE_SHIFT )
+#define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL )
+#define REPLXX_KEY_META( key )    ( ( key ) | REPLXX_KEY_BASE_META )
+
+enum { REPLXX_KEY_BACKSPACE    = REPLXX_KEY_CONTROL( 'H' ) };
+enum { REPLXX_KEY_TAB          = REPLXX_KEY_CONTROL( 'I' ) };
+enum { REPLXX_KEY_ENTER        = REPLXX_KEY_CONTROL( 'M' ) };
+
+/*! \brief List of built-in actions that act upon user input.
+ */
+typedef enum {
+       REPLXX_ACTION_INSERT_CHARACTER,
+       REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR,
+       REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR,
+       REPLXX_ACTION_KILL_TO_END_OF_LINE,
+       REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE,
+       REPLXX_ACTION_KILL_TO_END_OF_WORD,
+       REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD,
+       REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT,
+       REPLXX_ACTION_YANK,
+       REPLXX_ACTION_YANK_CYCLE,
+       REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE,
+       REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE,
+       REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT,
+       REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT,
+       REPLXX_ACTION_MOVE_CURSOR_LEFT,
+       REPLXX_ACTION_MOVE_CURSOR_RIGHT,
+       REPLXX_ACTION_HISTORY_NEXT,
+       REPLXX_ACTION_HISTORY_PREVIOUS,
+       REPLXX_ACTION_HISTORY_FIRST,
+       REPLXX_ACTION_HISTORY_LAST,
+       REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH,
+       REPLXX_ACTION_HISTORY_COMMON_PREFIX_SEARCH,
+       REPLXX_ACTION_HINT_NEXT,
+       REPLXX_ACTION_HINT_PREVIOUS,
+       REPLXX_ACTION_CAPITALIZE_WORD,
+       REPLXX_ACTION_LOWERCASE_WORD,
+       REPLXX_ACTION_UPPERCASE_WORD,
+       REPLXX_ACTION_TRANSPOSE_CHARACTERS,
+       REPLXX_ACTION_TOGGLE_OVERWRITE_MODE,
+#ifndef _WIN32
+       REPLXX_ACTION_VERBATIM_INSERT,
+       REPLXX_ACTION_SUSPEND,
+#endif
+       REPLXX_ACTION_CLEAR_SCREEN,
+       REPLXX_ACTION_CLEAR_SELF,
+       REPLXX_ACTION_REPAINT,
+       REPLXX_ACTION_COMPLETE_LINE,
+       REPLXX_ACTION_COMPLETE_NEXT,
+       REPLXX_ACTION_COMPLETE_PREVIOUS,
+       REPLXX_ACTION_COMMIT_LINE,
+       REPLXX_ACTION_ABORT_LINE,
+       REPLXX_ACTION_SEND_EOF
+} ReplxxAction;
+
+/*! \brief Possible results of key-press handler actions.
+ */
+typedef enum {
+       REPLXX_ACTION_RESULT_CONTINUE, /*!< Continue processing user input. */
+       REPLXX_ACTION_RESULT_RETURN,   /*!< Return user input entered so far. */
+       REPLXX_ACTION_RESULT_BAIL      /*!< Stop processing user input, returns nullptr from the \e input() call. */
+} ReplxxActionResult;
+
+typedef struct ReplxxStateTag {
+       char const* text;
+       int cursorPosition;
+} ReplxxState;
+
+typedef struct Replxx Replxx;
+
+/*! \brief Create Replxx library resouce holder.
+ *
+ * Use replxx_end() to free resoiurce acquired with this function.
+ *
+ * \return Replxx library resouce holder.
+ */
+REPLXX_IMPEXP Replxx* replxx_init( void );
+
+/*! \brief Cleanup resources used by Replxx library.
+ *
+ * \param replxx - a Replxx library resource holder.
+ */
+REPLXX_IMPEXP void replxx_end( Replxx* replxx );
+
+/*! \brief Highlighter callback type definition.
+ *
+ * If user want to have colorful input she must simply install highlighter callback.
+ * The callback would be invoked by the library after each change to the input done by
+ * the user. After callback returns library uses data from colors buffer to colorize
+ * displayed user input.
+ *
+ * \e size of \e colors buffer is equal to number of code points in user \e input
+ * which will be different from simple `strlen( input )`!
+ *
+ * \param input - an UTF-8 encoded input entered by the user so far.
+ * \param colors - output buffer for color information.
+ * \param size - size of output buffer for color information.
+ * \param userData - pointer to opaque user data block.
+ */
+typedef void (replxx_highlighter_callback_t)(char const* input, ReplxxColor* colors, int size, void* userData);
+
+/*! \brief Register highlighter callback.
+ *
+ * \param fn - user defined callback function.
+ * \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
+ */
+REPLXX_IMPEXP void replxx_set_highlighter_callback( Replxx*, replxx_highlighter_callback_t* fn, void* userData );
+
+typedef struct replxx_completions replxx_completions;
+
+/*! \brief Completions callback type definition.
+ *
+ * \e contextLen is counted in Unicode code points (not in bytes!).
+ *
+ * For user input:
+ * if ( obj.me
+ *
+ * input == "if ( obj.me"
+ * contextLen == 2 (depending on \e replxx_set_word_break_characters())
+ *
+ * Client application is free to update \e contextLen to be 6 (or any orther non-negative
+ * number not greated than the number of code points in input) if it makes better sense
+ * for given client application semantics.
+ *
+ * \param input - UTF-8 encoded input entered by the user until current cursor position.
+ * \param completions - pointer to opaque list of user completions.
+ * \param contextLen[in,out] - length of the additional context to provide while displaying completions.
+ * \param userData - pointer to opaque user data block.
+ */
+typedef void(replxx_completion_callback_t)(const char* input, replxx_completions* completions, int* contextLen, void* userData);
+
+/*! \brief Register completion callback.
+ *
+ * \param fn - user defined callback function.
+ * \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
+ */
+REPLXX_IMPEXP void replxx_set_completion_callback( Replxx*, replxx_completion_callback_t* fn, void* userData );
+
+/*! \brief Add another possible completion for current user input.
+ *
+ * \param completions - pointer to opaque list of user completions.
+ * \param str - UTF-8 encoded completion string.
+ */
+REPLXX_IMPEXP void replxx_add_completion( replxx_completions* completions, const char* str );
+
+/*! \brief Add another possible completion for current user input.
+ *
+ * \param completions - pointer to opaque list of user completions.
+ * \param str - UTF-8 encoded completion string.
+ * \param color - a color for the completion.
+ */
+REPLXX_IMPEXP void replxx_add_color_completion( replxx_completions* completions, const char* str, ReplxxColor color );
+
+typedef struct replxx_hints replxx_hints;
+
+/*! \brief Hints callback type definition.
+ *
+ * \e contextLen is counted in Unicode code points (not in bytes!).
+ *
+ * For user input:
+ * if ( obj.me
+ *
+ * input == "if ( obj.me"
+ * contextLen == 2 (depending on \e replxx_set_word_break_characters())
+ *
+ * Client application is free to update \e contextLen to be 6 (or any orther non-negative
+ * number not greated than the number of code points in input) if it makes better sense
+ * for given client application semantics.
+ *
+ * \param input - UTF-8 encoded input entered by the user until current cursor position.
+ * \param hints - pointer to opaque list of possible hints.
+ * \param contextLen[in,out] - length of the additional context to provide while displaying hints.
+ * \param color - a color used for displaying hints.
+ * \param userData - pointer to opaque user data block.
+ */
+typedef void(replxx_hint_callback_t)(const char* input, replxx_hints* hints, int* contextLen, ReplxxColor* color, void* userData);
+
+/*! \brief Register hints callback.
+ *
+ * \param fn - user defined callback function.
+ * \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
+ */
+REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn, void* userData );
+
+/*! \brief Key press handler type definition.
+ *
+ * \param code - the key code replxx got from terminal.
+ * \return Decition on how should input() behave after this key handler returns.
+ */
+typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData );
+
+/*! \brief Add another possible hint for current user input.
+ *
+ * \param hints - pointer to opaque list of hints.
+ * \param str - UTF-8 encoded hint string.
+ */
+REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str );
+
+/*! \brief Read line of user input.
+ *
+ * \param prompt - prompt to be displayed before getting user input.
+ * \return An UTF-8 encoded input given by the user (or nullptr on EOF).
+ */
+REPLXX_IMPEXP char const* replxx_input( Replxx*, const char* prompt );
+
+/*! \brief Get current state data.
+ *
+ * This call is intended to be used in handlers.
+ *
+ * \param state - buffer for current state of the model.
+ */
+REPLXX_IMPEXP void replxx_get_state( Replxx*, ReplxxState* state );
+
+/*! \brief Set new state data.
+ *
+ * This call is intended to be used in handlers.
+ *
+ * \param state - new state of the model.
+ */
+REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state );
+
+/*! \brief Print formatted string to standard output.
+ *
+ * This function ensures proper handling of ANSI escape sequences
+ * contained in printed data, which is especially useful on Windows
+ * since Unixes handle them correctly out of the box.
+ *
+ * \param fmt - printf style format.
+ */
+REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... );
+
+/*! \brief Schedule an emulated key press event.
+ *
+ * \param code - key press code to be emulated.
+ */
+REPLXX_IMPEXP void replxx_emulate_key_press( Replxx*, int unsigned code );
+
+/*! \brief Invoke built-in action handler.
+ *
+ * \pre This function can be called only from key-press handler.
+ *
+ * \param action - a built-in action to invoke.
+ * \param code - a supplementary key-code to consume by built-in action handler.
+ * \return The action result informing the replxx what shall happen next.
+ */
+REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, int unsigned code );
+
+/*! \brief Bind user defined action to handle given key-press event.
+ *
+ * \param code - handle this key-press event with following handler.
+ * \param handler - use this handler to handle key-press event.
+ * \param userData - supplementary user data passed to invoked handlers.
+ */
+REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData );
+
+REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText );
+
+REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line );
+REPLXX_IMPEXP int replxx_history_size( Replxx* );
+
+/*! \brief Set set of word break characters.
+ *
+ * This setting influences word based cursor movement and line editing capabilities.
+ *
+ * \param wordBreakers - 7-bit ASCII set of word breaking characters.
+ */
+REPLXX_IMPEXP void replxx_set_word_break_characters( Replxx*, char const* wordBreakers );
+
+/*! \brief How many completions should trigger pagination.
+ */
+REPLXX_IMPEXP void replxx_set_completion_count_cutoff( Replxx*, int count );
+
+/*! \brief Set maximum number of displayed hint rows.
+ */
+REPLXX_IMPEXP void replxx_set_max_hint_rows( Replxx*, int count );
+
+/*! \brief Set a delay before hint are shown after user stopped typing..
+ *
+ * \param milliseconds - a number of milliseconds to wait before showing hints.
+ */
+REPLXX_IMPEXP void replxx_set_hint_delay( Replxx*, int milliseconds );
+
+/*! \brief Set tab completion behavior.
+ *
+ * \param val - use double tab to invoke completions (if != 0).
+ */
+REPLXX_IMPEXP void replxx_set_double_tab_completion( Replxx*, int val );
+
+/*! \brief Set tab completion behavior.
+ *
+ * \param val - invoke completion even if user input is empty (if != 0).
+ */
+REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val );
+
+/*! \brief Set tab completion behavior.
+ *
+ * \param val - beep if completion is ambiguous (if != 0).
+ */
+REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val );
+
+/*! \brief Disable output coloring.
+ *
+ * \param val - if set to non-zero disable output colors.
+ */
+REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val );
+
+/*! \brief Set maximum number of entries in history list.
+ */
+REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len );
+REPLXX_IMPEXP char const* replxx_history_line( Replxx*, int index );
+REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename );
+REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename );
+REPLXX_IMPEXP void replxx_clear_screen( Replxx* );
+#ifdef __REPLXX_DEBUG__
+void replxx_debug_dump_print_codes(void);
+#endif
+/* the following is extension to the original linenoise API */
+REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __REPLXX_H */
+
diff --git a/contrib/replxx/include/replxx.hxx b/contrib/replxx/include/replxx.hxx
new file mode 100644 (file)
index 0000000..3fe90e6
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * 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_HXX_INCLUDED
+#define HAVE_REPLXX_HXX_INCLUDED 1
+
+#include <memory>
+#include <vector>
+#include <string>
+#include <functional>
+
+/*
+ * For use in Windows DLLs:
+ *
+ * If you are building replxx into a DLL,
+ * unless you are using supplied CMake based build,
+ * ensure that 'REPLXX_BUILDING_DLL' is defined when
+ * building the DLL so that proper symbols are exported.
+ */
+#if defined( _WIN32 ) && ! defined( REPLXX_STATIC )
+#      ifdef REPLXX_BUILDING_DLL
+#              define REPLXX_IMPEXP __declspec( dllexport )
+#      else
+#              define REPLXX_IMPEXP __declspec( dllimport )
+#      endif
+#else
+#      define REPLXX_IMPEXP /**/
+#endif
+
+#ifdef ERROR
+enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR };
+#undef ERROR
+enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 };
+#endif
+#ifdef DELETE
+enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE };
+#undef DELETE
+enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E };
+#endif
+
+namespace replxx {
+
+class REPLXX_IMPEXP Replxx {
+public:
+       enum class Color {
+               BLACK         = 0,
+               RED           = 1,
+               GREEN         = 2,
+               BROWN         = 3,
+               BLUE          = 4,
+               MAGENTA       = 5,
+               CYAN          = 6,
+               LIGHTGRAY     = 7,
+               GRAY          = 8,
+               BRIGHTRED     = 9,
+               BRIGHTGREEN   = 10,
+               YELLOW        = 11,
+               BRIGHTBLUE    = 12,
+               BRIGHTMAGENTA = 13,
+               BRIGHTCYAN    = 14,
+               WHITE         = 15,
+               NORMAL        = LIGHTGRAY,
+               DEFAULT       = -1,
+               ERROR         = -2
+       };
+       struct KEY {
+               static char32_t const BASE         = 0x0010ffff + 1;
+               static char32_t const BASE_SHIFT   = 0x01000000;
+               static char32_t const BASE_CONTROL = 0x02000000;
+               static char32_t const BASE_META    = 0x04000000;
+               static char32_t const ESCAPE       = 27;
+               static char32_t const PAGE_UP      = BASE      + 1;
+               static char32_t const PAGE_DOWN    = PAGE_UP   + 1;
+               static char32_t const DOWN         = PAGE_DOWN + 1;
+               static char32_t const UP           = DOWN      + 1;
+               static char32_t const LEFT         = UP        + 1;
+               static char32_t const RIGHT        = LEFT      + 1;
+               static char32_t const HOME         = RIGHT     + 1;
+               static char32_t const END          = HOME      + 1;
+               static char32_t const DELETE       = END       + 1;
+               static char32_t const INSERT       = DELETE    + 1;
+               static char32_t const F1           = INSERT    + 1;
+               static char32_t const F2           = F1        + 1;
+               static char32_t const F3           = F2        + 1;
+               static char32_t const F4           = F3        + 1;
+               static char32_t const F5           = F4        + 1;
+               static char32_t const F6           = F5        + 1;
+               static char32_t const F7           = F6        + 1;
+               static char32_t const F8           = F7        + 1;
+               static char32_t const F9           = F8        + 1;
+               static char32_t const F10          = F9        + 1;
+               static char32_t const F11          = F10       + 1;
+               static char32_t const F12          = F11       + 1;
+               static char32_t const F13          = F12       + 1;
+               static char32_t const F14          = F13       + 1;
+               static char32_t const F15          = F14       + 1;
+               static char32_t const F16          = F15       + 1;
+               static char32_t const F17          = F16       + 1;
+               static char32_t const F18          = F17       + 1;
+               static char32_t const F19          = F18       + 1;
+               static char32_t const F20          = F19       + 1;
+               static char32_t const F21          = F20       + 1;
+               static char32_t const F22          = F21       + 1;
+               static char32_t const F23          = F22       + 1;
+               static char32_t const F24          = F23       + 1;
+               static char32_t const MOUSE        = F24       + 1;
+               static constexpr char32_t shift( char32_t key_ ) {
+                       return ( key_ | BASE_SHIFT );
+               }
+               static constexpr char32_t control( char32_t key_ ) {
+                       return ( key_ | BASE_CONTROL );
+               }
+               static constexpr char32_t meta( char32_t key_ ) {
+                       return ( key_ | BASE_META );
+               }
+               static char32_t const BACKSPACE    = 'H' | BASE_CONTROL;
+               static char32_t const TAB          = 'I' | BASE_CONTROL;
+               static char32_t const ENTER        = 'M' | BASE_CONTROL;
+       };
+       /*! \brief List of built-in actions that act upon user input.
+        */
+       enum class ACTION {
+               INSERT_CHARACTER,
+               DELETE_CHARACTER_UNDER_CURSOR,
+               DELETE_CHARACTER_LEFT_OF_CURSOR,
+               KILL_TO_END_OF_LINE,
+               KILL_TO_BEGINING_OF_LINE,
+               KILL_TO_END_OF_WORD,
+               KILL_TO_BEGINING_OF_WORD,
+               KILL_TO_WHITESPACE_ON_LEFT,
+               YANK,
+               YANK_CYCLE,
+               MOVE_CURSOR_TO_BEGINING_OF_LINE,
+               MOVE_CURSOR_TO_END_OF_LINE,
+               MOVE_CURSOR_ONE_WORD_LEFT,
+               MOVE_CURSOR_ONE_WORD_RIGHT,
+               MOVE_CURSOR_LEFT,
+               MOVE_CURSOR_RIGHT,
+               HISTORY_NEXT,
+               HISTORY_PREVIOUS,
+               HISTORY_FIRST,
+               HISTORY_LAST,
+               HISTORY_INCREMENTAL_SEARCH,
+               HISTORY_COMMON_PREFIX_SEARCH,
+               HINT_NEXT,
+               HINT_PREVIOUS,
+               CAPITALIZE_WORD,
+               LOWERCASE_WORD,
+               UPPERCASE_WORD,
+               TRANSPOSE_CHARACTERS,
+               TOGGLE_OVERWRITE_MODE,
+#ifndef _WIN32
+               VERBATIM_INSERT,
+               SUSPEND,
+#endif
+               CLEAR_SCREEN,
+               CLEAR_SELF,
+               REPAINT,
+               COMPLETE_LINE,
+               COMPLETE_NEXT,
+               COMPLETE_PREVIOUS,
+               COMMIT_LINE,
+               ABORT_LINE,
+               SEND_EOF
+       };
+       /*! \brief Possible results of key-press handler actions.
+        */
+       enum class ACTION_RESULT {
+               CONTINUE, /*!< Continue processing user input. */
+               RETURN,   /*!< Return user input entered so far. */
+               BAIL      /*!< Stop processing user input, returns nullptr from the \e input() call. */
+       };
+       typedef std::vector<Color> colors_t;
+       class Completion {
+               std::string _text;
+               Color _color;
+       public:
+               Completion( char const* text_ )
+                       : _text( text_ )
+                       , _color( Color::DEFAULT ) {
+               }
+               Completion( std::string const& text_ )
+                       : _text( text_ )
+                       , _color( Color::DEFAULT ) {
+               }
+               Completion( std::string const& text_, Color color_ )
+                       : _text( text_ )
+                       , _color( color_ ) {
+               }
+               std::string const& text( void ) const {
+                       return ( _text );
+               }
+               Color color( void ) const {
+                       return ( _color );
+               }
+       };
+       typedef std::vector<Completion> completions_t;
+       typedef std::vector<std::string> hints_t;
+
+       /*! \brief Completions callback type definition.
+        *
+        * \e contextLen is counted in Unicode code points (not in bytes!).
+        *
+        * For user input:
+        * if ( obj.me
+        *
+        * input == "if ( obj.me"
+        * contextLen == 2 (depending on \e set_word_break_characters())
+        *
+        * Client application is free to update \e contextLen to be 6 (or any orther non-negative
+        * number not greated than the number of code points in input) if it makes better sense
+        * for given client application semantics.
+        *
+        * \param input - UTF-8 encoded input entered by the user until current cursor position.
+        * \param[in,out] contextLen - length of the additional context to provide while displaying completions.
+        * \return A list of user completions.
+        */
+       typedef std::function<completions_t ( std::string const& input, int& contextLen )> completion_callback_t;
+
+       /*! \brief Highlighter callback type definition.
+        *
+        * If user want to have colorful input she must simply install highlighter callback.
+        * The callback would be invoked by the library after each change to the input done by
+        * the user. After callback returns library uses data from colors buffer to colorize
+        * displayed user input.
+        *
+        * Size of \e colors buffer is equal to number of code points in user \e input
+        * which will be different from simple `input.lenght()`!
+        *
+        * \param input - an UTF-8 encoded input entered by the user so far.
+        * \param colors - output buffer for color information.
+        */
+       typedef std::function<void ( std::string const& input, colors_t& colors )> highlighter_callback_t;
+
+       /*! \brief Hints callback type definition.
+        *
+        * \e contextLen is counted in Unicode code points (not in bytes!).
+        *
+        * For user input:
+        * if ( obj.me
+        *
+        * input == "if ( obj.me"
+        * contextLen == 2 (depending on \e set_word_break_characters())
+        *
+        * Client application is free to update \e contextLen to be 6 (or any orther non-negative
+        * number not greated than the number of code points in input) if it makes better sense
+        * for given client application semantics.
+        *
+        * \param input - UTF-8 encoded input entered by the user until current cursor position.
+        * \param contextLen[in,out] - length of the additional context to provide while displaying hints.
+        * \param color - a color used for displaying hints.
+        * \return A list of possible hints.
+        */
+       typedef std::function<hints_t ( std::string const& input, int& contextLen, Color& color )> hint_callback_t;
+
+       /*! \brief Key press handler type definition.
+        *
+        * \param code - the key code replxx got from terminal.
+        * \return Decition on how should input() behave after this key handler returns.
+        */
+       typedef std::function<ACTION_RESULT ( char32_t code )> key_press_handler_t;
+
+       struct State {
+               char const* _text;
+               int _cursorPosition;
+               State( char const* text_, int cursorPosition_ = -1 )
+                       : _text( text_ )
+                       , _cursorPosition( cursorPosition_ ) {
+               }
+               State( State const& ) = default;
+               State& operator = ( State const& ) = default;
+               char const* text( void ) const {
+                       return ( _text );
+               }
+               int cursor_position( void ) const {
+                       return ( _cursorPosition );
+               }
+       };
+
+       class ReplxxImpl;
+private:
+       typedef std::unique_ptr<ReplxxImpl, void (*)( ReplxxImpl* )> impl_t;
+#ifdef _WIN32
+#pragma warning(push)
+#pragma warning(disable:4251)
+#endif
+       impl_t _impl;
+#ifdef _WIN32
+#pragma warning(pop)
+#endif
+
+public:
+       Replxx( void );
+       Replxx( Replxx&& ) = default;
+       Replxx& operator = ( Replxx&& ) = default;
+
+       /*! \brief Register completion callback.
+        *
+        * \param fn - user defined callback function.
+        */
+       void set_completion_callback( completion_callback_t const& fn );
+
+       /*! \brief Register highlighter callback.
+        *
+        * \param fn - user defined callback function.
+        */
+       void set_highlighter_callback( highlighter_callback_t const& fn );
+
+       /*! \brief Register hints callback.
+        *
+        * \param fn - user defined callback function.
+        */
+       void set_hint_callback( hint_callback_t const& fn );
+
+       /*! \brief Read line of user input.
+        *
+        * \param prompt - prompt to be displayed before getting user input.
+        * \return An UTF-8 encoded input given by the user (or nullptr on EOF).
+        */
+       char const* input( std::string const& prompt );
+
+       /*! \brief Get current state data.
+        *
+        * This call is intended to be used in handlers.
+        *
+        * \return Current state of the model.
+        */
+       State get_state( void ) const;
+
+       /*! \brief Set new state data.
+        *
+        * This call is intended to be used in handlers.
+        *
+        * \param state - new state of the model.
+        */
+       void set_state( State const& state );
+
+       /*! \brief Print formatted string to standard output.
+        *
+        * This function ensures proper handling of ANSI escape sequences
+        * contained in printed data, which is especially useful on Windows
+        * since Unixes handle them correctly out of the box.
+        *
+        * \param fmt - printf style format.
+        */
+       void print( char const* fmt, ... );
+
+       /*! \brief Schedule an emulated key press event.
+        *
+        * \param code - key press code to be emulated.
+        */
+       void emulate_key_press( char32_t code );
+
+       /*! \brief Invoke built-in action handler.
+        *
+        * \pre This method can be called only from key-press handler.
+        *
+        * \param action - a built-in action to invoke.
+        * \param code - a supplementary key-code to consume by built-in action handler.
+        * \return The action result informing the replxx what shall happen next.
+        */
+       ACTION_RESULT invoke( ACTION action, char32_t code );
+
+       /*! \brief Bind user defined action to handle given key-press event.
+        *
+        * \param code - handle this key-press event with following handler.
+        * \param handle - use this handler to handle key-press event.
+        */
+       void bind_key( char32_t code, key_press_handler_t handler );
+
+       void history_add( std::string const& line );
+       int history_save( std::string const& filename );
+       int history_load( std::string const& filename );
+       int history_size( void ) const;
+       std::string history_line( int index );
+
+       void set_preload_buffer( std::string const& preloadText );
+
+       /*! \brief Set set of word break characters.
+        *
+        * This setting influences word based cursor movement and line editing capabilities.
+        *
+        * \param wordBreakers - 7-bit ASCII set of word breaking characters.
+        */
+       void set_word_break_characters( char const* wordBreakers );
+
+       /*! \brief How many completions should trigger pagination.
+        */
+       void set_completion_count_cutoff( int count );
+
+       /*! \brief Set maximum number of displayed hint rows.
+        */
+       void set_max_hint_rows( int count );
+
+       /*! \brief Set a delay before hint are shown after user stopped typing..
+        *
+        * \param milliseconds - a number of milliseconds to wait before showing hints.
+        */
+       void set_hint_delay( int milliseconds );
+
+       /*! \brief Set tab completion behavior.
+        *
+        * \param val - use double tab to invoke completions.
+        */
+       void set_double_tab_completion( bool val );
+
+       /*! \brief Set tab completion behavior.
+        *
+        * \param val - invoke completion even if user input is empty.
+        */
+       void set_complete_on_empty( bool val );
+
+       /*! \brief Set tab completion behavior.
+        *
+        * \param val - beep if completion is ambiguous.
+        */
+       void set_beep_on_ambiguous_completion( bool val );
+
+       /*! \brief Disable output coloring.
+        *
+        * \param val - if set to non-zero disable output colors.
+        */
+       void set_no_color( bool val );
+
+       /*! \brief Set maximum number of entries in history list.
+        */
+       void set_max_history_size( int len );
+       void clear_screen( void );
+       int install_window_change_handler( void );
+
+private:
+       Replxx( Replxx const& ) = delete;
+       Replxx& operator = ( Replxx const& ) = delete;
+};
+
+}
+
+#endif /* HAVE_REPLXX_HXX_INCLUDED */
+
diff --git a/contrib/replxx/src/ConvertUTF.cpp b/contrib/replxx/src/ConvertUTF.cpp
new file mode 100644 (file)
index 0000000..3609c62
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2001-2004 Unicode, Inc.
+ * 
+ * Disclaimer
+ * 
+ * This source code is provided as is by Unicode, Inc. No claims are
+ * made as to fitness for any particular purpose. No warranties of any
+ * kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been
+ * purchased on magnetic or optical media from Unicode, Inc., the
+ * sole remedy for any claim will be exchange of defective media
+ * within 90 days of receipt.
+ * 
+ * Limitations on Rights to Redistribute This Code
+ * 
+ * Unicode, Inc. hereby grants the right to freely use the information
+ * supplied in this file in the creation of products supporting the
+ * Unicode Standard, and to make copies of this file in any form
+ * for internal or external distribution as long as this notice
+ * remains attached.
+ */
+
+/* ---------------------------------------------------------------------
+
+               Conversions between UTF32, UTF-16, and UTF-8. Source code file.
+               Author: Mark E. Davis, 1994.
+               Rev History: Rick McGowan, fixes & updates May 2001.
+               Sept 2001: fixed const & error conditions per
+                               mods suggested by S. Parent & A. Lillich.
+               June 2002: Tim Dodd added detection and handling of incomplete
+                               source sequences, enhanced error detection, added casts
+                               to eliminate compiler warnings.
+               July 2003: slight mods to back out aggressive FFFE detection.
+               Jan 2004: updated switches in from-UTF8 conversions.
+               Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions.
+
+               See the header file "ConvertUTF.h" for complete documentation.
+
+------------------------------------------------------------------------ */
+
+#include "ConvertUTF.h"
+#ifdef CVTUTF_DEBUG
+#include <stdio.h>
+#endif
+
+namespace replxx {
+
+#define UNI_SUR_HIGH_START  (UTF32)0xD800
+#define UNI_SUR_LOW_END     (UTF32)0xDFFF
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Index into the table below with the first byte of a UTF-8 sequence to
+ * get the number of trailing bytes that are supposed to follow it.
+ * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
+ * left as-is for anyone who may want to do such conversion, which was
+ * allowed in earlier algorithms.
+ */
+static const char trailingBytesForUTF8[256] = {
+               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+               1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+               2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
+};
+
+/*
+ * Magic values subtracted from a buffer value during UTF8 conversion.
+ * This table contains as many values as there might be trailing bytes
+ * in a UTF-8 sequence.
+ */
+static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 
+                                                                                0x03C82080UL, 0xFA082080UL, 0x82082080UL };
+
+/*
+ * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
+ * into the first byte, depending on how many bytes follow.    There are
+ * as many entries in this table as there are UTF-8 sequence types.
+ * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
+ * for *legal* UTF-8 will be 4 or fewer bytes total.
+ */
+static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+
+/* --------------------------------------------------------------------- */
+
+/* The interface converts a whole buffer to avoid function-call overhead.
+ * Constants have been gathered. Loops & conditionals have been removed as
+ * much as possible for efficiency, in favor of drop-through switches.
+ * (See "Note A" at the bottom of the file for equivalent code.)
+ * If your compiler supports it, the "isLegalUTF8" call can be turned
+ * into an inline function.
+ */
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Utility routine to tell whether a sequence of bytes is legal UTF-8.
+ * This must be called with the length pre-determined by the first byte.
+ * If not calling this from ConvertUTF8to*, then the length can be set by:
+ *     length = trailingBytesForUTF8[*source]+1;
+ * and the sequence is illegal right away if there aren't that many bytes
+ * available.
+ * If presented with a length > 4, this returns false. The Unicode
+ * definition of UTF-8 goes up to 4-byte sequences.
+ */
+
+static bool isLegalUTF8(const UTF8 *source, int length) {
+               UTF8 a;
+               const UTF8 *srcptr = source+length;
+               switch (length) {
+               default: return false;
+                               /* Everything else falls through when "true"... */
+               case 4: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */
+               case 3: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */
+               case 2: {
+                               if ((a = (*--srcptr)) > 0xBF) return false;
+
+                               switch (*source) {
+                                               /* no fall-through in this inner switch */
+                                               case 0xE0: if (a < 0xA0) return false; break;
+                                               case 0xED: if (a > 0x9F) return false; break;
+                                               case 0xF0: if (a < 0x90) return false; break;
+                                               case 0xF4: if (a > 0x8F) return false; break;
+                                               default:         if (a < 0x80) return false;
+                               }
+               } /* fall through */
+               case 1: { if (*source >= 0x80 && *source < 0xC2) return false; } /* fall through */
+               }
+               if (*source > 0xF4) return false;
+               return true;
+}
+
+/* --------------------------------------------------------------------- */
+
+ConversionResult ConvertUTF32toUTF8 (
+                               const UTF32** sourceStart, const UTF32* sourceEnd, 
+                               UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
+               ConversionResult result = conversionOK;
+               const UTF32* source = *sourceStart;
+               UTF8* target = *targetStart;
+               while (source < sourceEnd) {
+                               UTF32 ch;
+                               unsigned short bytesToWrite = 0;
+                               const UTF32 byteMask = 0xBF;
+                               const UTF32 byteMark = 0x80; 
+                               ch = *source++;
+                               if (flags == strictConversion ) {
+                                               /* UTF-16 surrogate values are illegal in UTF-32 */
+                                               if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
+                                                               --source; /* return to the illegal value itself */
+                                                               result = sourceIllegal;
+                                                               break;
+                                               }
+                               }
+                               /*
+                                * Figure out how many bytes the result will require. Turn any
+                                * illegally large UTF32 things (> Plane 17) into replacement chars.
+                                */
+                               if (ch < (UTF32)0x80) {                 bytesToWrite = 1;
+                               } else if (ch < (UTF32)0x800) {          bytesToWrite = 2;
+                               } else if (ch < (UTF32)0x10000) {        bytesToWrite = 3;
+                               } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4;
+                               } else {                                                                                                                bytesToWrite = 3;
+                                                                                                                                                                               ch = UNI_REPLACEMENT_CHAR;
+                                                                                                                                                                               result = sourceIllegal;
+                               }
+                               
+                               target += bytesToWrite;
+                               if (target > targetEnd) {
+                                               --source; /* Back up source pointer! */
+                                               target -= bytesToWrite; result = targetExhausted; break;
+                               }
+                               switch (bytesToWrite) { /* note: everything falls through. */
+                                               case 4: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */
+                                               case 3: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */
+                                               case 2: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */
+                                               case 1: { *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); } /* fall through */
+                               }
+                               target += bytesToWrite;
+               }
+               *sourceStart = source;
+               *targetStart = target;
+               return result;
+}
+
+/* --------------------------------------------------------------------- */
+
+ConversionResult ConvertUTF8toUTF32 (
+                               const UTF8** sourceStart, const UTF8* sourceEnd, 
+                               UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) {
+               ConversionResult result = conversionOK;
+               const UTF8* source = *sourceStart;
+               UTF32* target = *targetStart;
+               while (source < sourceEnd) {
+                               UTF32 ch = 0;
+                               unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
+                               if (source + extraBytesToRead >= sourceEnd) {
+                                               result = sourceExhausted; break;
+                               }
+                               /* Do this check whether lenient or strict */
+                               if (! isLegalUTF8(source, extraBytesToRead+1)) {
+                                               result = sourceIllegal;
+                                               break;
+                               }
+                               /*
+                                * The cases all fall through. See "Note A" below.
+                                */
+                               switch (extraBytesToRead) {
+                                               case 5: { ch += *source++; ch <<= 6; } /* fall through */
+                                               case 4: { ch += *source++; ch <<= 6; } /* fall through */
+                                               case 3: { ch += *source++; ch <<= 6; } /* fall through */
+                                               case 2: { ch += *source++; ch <<= 6; } /* fall through */
+                                               case 1: { ch += *source++; ch <<= 6; } /* fall through */
+                                               case 0: { ch += *source++; } /* fall through */
+                               }
+                               ch -= offsetsFromUTF8[extraBytesToRead];
+
+                               if (target >= targetEnd) {
+                                               source -= (extraBytesToRead+1); /* Back up the source pointer! */
+                                               result = targetExhausted; break;
+                               }
+                               if (ch <= UNI_MAX_LEGAL_UTF32) {
+                                               /*
+                                                * UTF-16 surrogate values are illegal in UTF-32, and anything
+                                                * over Plane 17 (> 0x10FFFF) is illegal.
+                                                */
+                                               if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
+                                                               if (flags == strictConversion) {
+                                                                               source -= (extraBytesToRead+1); /* return to the illegal value itself */
+                                                                               result = sourceIllegal;
+                                                                               break;
+                                                               } else {
+                                                                               *target++ = UNI_REPLACEMENT_CHAR;
+                                                               }
+                                               } else {
+                                                               *target++ = ch;
+                                               }
+                               } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */
+                                               result = sourceIllegal;
+                                               *target++ = UNI_REPLACEMENT_CHAR;
+                               }
+               }
+               *sourceStart = source;
+               *targetStart = target;
+               return result;
+}
+
+}
+
+/* ---------------------------------------------------------------------
+
+               Note A.
+               The fall-through switches in UTF-8 reading code save a
+               temp variable, some decrements & conditionals.  The switches
+               are equivalent to the following loop:
+                               {
+                                               int tmpBytesToRead = extraBytesToRead+1;
+                                               do {
+                                                               ch += *source++;
+                                                               --tmpBytesToRead;
+                                                               if (tmpBytesToRead) ch <<= 6;
+                                               } while (tmpBytesToRead > 0);
+                               }
+               In UTF-8 writing code, the switches on "bytesToWrite" are
+               similarly unrolled loops.
+
+        --------------------------------------------------------------------- */
diff --git a/contrib/replxx/src/ConvertUTF.h b/contrib/replxx/src/ConvertUTF.h
new file mode 100644 (file)
index 0000000..f91d557
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2001-2004 Unicode, Inc.
+ * 
+ * Disclaimer
+ * 
+ * This source code is provided as is by Unicode, Inc. No claims are
+ * made as to fitness for any particular purpose. No warranties of any
+ * kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been
+ * purchased on magnetic or optical media from Unicode, Inc., the
+ * sole remedy for any claim will be exchange of defective media
+ * within 90 days of receipt.
+ * 
+ * Limitations on Rights to Redistribute This Code
+ * 
+ * Unicode, Inc. hereby grants the right to freely use the information
+ * supplied in this file in the creation of products supporting the
+ * Unicode Standard, and to make copies of this file in any form
+ * for internal or external distribution as long as this notice
+ * remains attached.
+ */
+
+/* ---------------------------------------------------------------------
+
+               Conversions between UTF32, UTF-16, and UTF-8.   Header file.
+
+               Several funtions are included here, forming a complete set of
+               conversions between the three formats.  UTF-7 is not included
+               here, but is handled in a separate source file.
+
+               Each of these routines takes pointers to input buffers and output
+               buffers.        The input buffers are const.
+
+               Each routine converts the text between *sourceStart and sourceEnd,
+               putting the result into the buffer between *targetStart and
+               targetEnd. Note: the end pointers are *after* the last item: e.g. 
+               *(sourceEnd - 1) is the last item.
+
+               The return result indicates whether the conversion was successful,
+               and if not, whether the problem was in the source or target buffers.
+               (Only the first encountered problem is indicated.)
+
+               After the conversion, *sourceStart and *targetStart are both
+               updated to point to the end of last text successfully converted in
+               the respective buffers.
+
+               Input parameters:
+                               sourceStart - pointer to a pointer to the source buffer.
+                                                               The contents of this are modified on return so that
+                                                               it points at the next thing to be converted.
+                               targetStart - similarly, pointer to pointer to the target buffer.
+                               sourceEnd, targetEnd - respectively pointers to the ends of the
+                                                               two buffers, for overflow checking only.
+
+               These conversion functions take a ConversionFlags argument. When this
+               flag is set to strict, both irregular sequences and isolated surrogates
+               will cause an error.    When the flag is set to lenient, both irregular
+               sequences and isolated surrogates are converted.
+
+               Whether the flag is strict or lenient, all illegal sequences will cause
+               an error return. This includes sequences such as: <F4 90 80 80>, <C0 80>,
+               or <A0> in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code
+               must check for illegal sequences.
+
+               When the flag is set to lenient, characters over 0x10FFFF are converted
+               to the replacement character; otherwise (when the flag is set to strict)
+               they constitute an error.
+
+               Output parameters:
+                               The value "sourceIllegal" is returned from some routines if the input
+                               sequence is malformed.  When "sourceIllegal" is returned, the source
+                               value will point to the illegal value that caused the problem. E.g.,
+                               in UTF-8 when a sequence is malformed, it points to the start of the
+                               malformed sequence.     
+
+               Author: Mark E. Davis, 1994.
+               Rev History: Rick McGowan, fixes & updates May 2001.
+                                                                Fixes & updates, Sept 2001.
+
+------------------------------------------------------------------------ */
+
+/* ---------------------------------------------------------------------
+               The following 4 definitions are compiler-specific.
+               The C standard does not guarantee that wchar_t has at least
+               16 bits, so wchar_t is no less portable than unsigned short!
+               All should be unsigned values to avoid sign extension during
+               bit mask & shift operations.
+------------------------------------------------------------------------ */
+
+#ifndef REPLXX_CONVERT_UTF8_H_INCLUDED
+#define REPLXX_CONVERT_UTF8_H_INCLUDED 1
+
+#if 0
+typedef unsigned long  UTF32; /* at least 32 bits */
+typedef unsigned short UTF16; /* at least 16 bits */
+typedef unsigned char  UTF8;   /* typically 8 bits */
+#endif
+
+#include <stdint.h>
+#include <string>
+
+namespace replxx {
+
+typedef uint32_t      UTF32;
+typedef uint16_t      UTF16;
+typedef uint8_t       UTF8;
+
+/* Some fundamental constants */
+#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
+#define UNI_MAX_BMP (UTF32)0x0000FFFF
+#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
+#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
+#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
+
+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;
+
+typedef enum {
+                               strictConversion = 0,
+                               lenientConversion
+} ConversionFlags;
+
+ConversionResult ConvertUTF8toUTF32 (
+                                                               const UTF8** sourceStart, const UTF8* sourceEnd,
+                                                               UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags);
+
+ConversionResult ConvertUTF32toUTF8 (
+                                                               const UTF32** sourceStart, const UTF32* sourceEnd,
+                                                               UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags);
+
+}
+
+#endif /* REPLXX_CONVERT_UTF8_H_INCLUDED */
+
+/* --------------------------------------------------------------------- */
diff --git a/contrib/replxx/src/conversion.cxx b/contrib/replxx/src/conversion.cxx
new file mode 100644 (file)
index 0000000..0b4c5fa
--- /dev/null
@@ -0,0 +1,113 @@
+#include <algorithm>
+#include <string>
+#include <cstring>
+#include <cctype>
+#include <locale.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 ) {
+               const UTF8* sourceStart = reinterpret_cast<const UTF8*>(src);
+               const UTF8* sourceEnd = sourceStart + strlen(src);
+               UTF32* targetStart = reinterpret_cast<UTF32*>(dst);
+               UTF32* targetEnd = targetStart + dstSize;
+
+               res = ConvertUTF8toUTF32(
+                               &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion);
+
+               if (res == conversionOK) {
+                       dstCount = targetStart - reinterpret_cast<UTF32*>(dst);
+
+                       if (dstCount < dstSize) {
+                               *targetStart = 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)
+       );
+}
+
+void copyString32to8(
+       char* dst, int dstSize, const char32_t* src, int srcSize, int* dstCount
+) {
+       if ( ! locale::is8BitEncoding ) {
+               const UTF32* sourceStart = reinterpret_cast<const UTF32*>(src);
+               const UTF32* sourceEnd = sourceStart + srcSize;
+               UTF8* targetStart = reinterpret_cast<UTF8*>(dst);
+               UTF8* targetEnd = targetStart + dstSize;
+
+               ConversionResult res = ConvertUTF32toUTF8(
+                               &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion);
+
+               if (res == conversionOK) {
+                       int resCount( targetStart - reinterpret_cast<UTF8*>( dst ) );
+
+                       if ( resCount < dstSize ) {
+                               *targetStart = 0;
+                       }
+                       if ( dstCount ) {
+                               *dstCount = resCount;
+                       }
+               }
+       } else {
+               int i( 0 );
+               for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) {
+                       dst[i] = static_cast<char>( src[i] );
+               }
+               if ( dstCount ) {
+                       *dstCount = i;
+               }
+               if ( i < dstSize ) {
+                       dst[i] = 0;
+               }
+       }
+}
+
+}
+
diff --git a/contrib/replxx/src/conversion.hxx b/contrib/replxx/src/conversion.hxx
new file mode 100644 (file)
index 0000000..45d251a
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef REPLXX_CONVERSION_HXX_INCLUDED
+#define REPLXX_CONVERSION_HXX_INCLUDED 1
+
+#include "ConvertUTF.h"
+
+namespace replxx {
+
+typedef unsigned char char8_t;
+
+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 );
+void copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize, int* dstCount = nullptr );
+
+namespace locale {
+extern bool is8BitEncoding;
+}
+
+}
+
+#endif
diff --git a/contrib/replxx/src/escape.cxx b/contrib/replxx/src/escape.cxx
new file mode 100644 (file)
index 0000000..3edc4c1
--- /dev/null
@@ -0,0 +1,860 @@
+#include "escape.hxx"
+#include "io.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 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 escLeftBracket20Routines[] = {
+       f9KeyRoutine, escLeftBracket20SemicolonRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket20Dispatch = {
+       2, "~;", 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 (file)
index 0000000..6597395
--- /dev/null
@@ -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 (file)
index 0000000..6c6eff3
--- /dev/null
@@ -0,0 +1,148 @@
+#include <fstream>
+#include <cstring>
+
+#ifndef _WIN32
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#endif /* _WIN32 */
+
+#include "history.hxx"
+#include "utf8string.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 );
+
+History::History( void )
+       : _data()
+       , _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN )
+       , _maxLineLength( 0 )
+       , _index( 0 )
+       , _previousIndex( -2 )
+       , _recallMostRecent( false ) {
+}
+
+void History::add( UnicodeString const& line ) {
+       if ( ( _maxSize > 0 ) && ( _data.empty() || ( line != _data.back() ) ) ) {
+               if ( size() > _maxSize ) {
+                       _data.erase( _data.begin() );
+                       if ( -- _previousIndex < -1 ) {
+                               _previousIndex = -2;
+                       }
+               }
+               if ( static_cast<int>( line.length() ) > _maxLineLength ) {
+                       _maxLineLength = static_cast<int>( line.length() );
+               }
+               _data.push_back( line );
+       }
+}
+
+int History::save( std::string const& filename ) {
+#ifndef _WIN32
+       mode_t old_umask = umask( S_IXUSR | S_IRWXG| S_IRWXO );
+#endif
+       ofstream histFile( filename );
+       if ( ! histFile ) {
+               return ( -1 );
+       }
+#ifndef _WIN32
+       umask( old_umask );
+       chmod( filename.c_str(), S_IRUSR | S_IWUSR );
+#endif
+       Utf8String utf8;
+       for ( UnicodeString const& h : _data ) {
+               if ( ! h.is_empty() ) {
+                       utf8.assign( h );
+                       histFile << utf8.get() << endl;
+               }
+       }
+       return ( 0 );
+}
+
+int History::load( std::string const& filename ) {
+       ifstream histFile( filename );
+       if ( ! histFile ) {
+               return ( -1 );
+       }
+       string line;
+       while ( getline( histFile, line ).good() ) {
+               string::size_type eol( line.find_first_of( "\r\n" ) );
+               if ( eol != string::npos ) {
+                       line.erase( eol );
+               }
+               if ( ! line.empty() ) {
+                       add( UnicodeString( line ) );
+               }
+       }
+       return 0;
+}
+
+void History::set_max_size( int size_ ) {
+       if ( size_ >= 0 ) {
+               _maxSize = size_;
+               int curSize( size() );
+               if ( _maxSize < curSize ) {
+                       _data.erase( _data.begin(), _data.begin() + ( curSize - _maxSize ) );
+               }
+       }
+}
+
+void History::reset_pos( int pos_ ) {
+       if ( pos_ == -1 ) {
+               _index = size() - 1;
+               _recallMostRecent = false;
+       } else {
+               _index = pos_;
+       }
+}
+
+bool History::move( bool up_ ) {
+       if (_previousIndex != -2 && ! up_ ) {
+               _index = 1 + _previousIndex;    // emulate Windows down-arrow
+       } else {
+               _index += up_ ? -1 : 1;
+       }
+       _previousIndex = -2;
+       if (_index < 0) {
+               _index = 0;
+               return ( false );
+       } else if ( _index >= size() ) {
+               _index = size() - 1;
+               return ( false );
+       }
+       _recallMostRecent = true;
+       return ( true );
+}
+
+void History::jump( bool start_ ) {
+       _index = start_ ? 0 : size() - 1;
+       _previousIndex = -2;
+       _recallMostRecent = true;
+}
+
+bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_ ) {
+       int direct( size() + ( back_ ? -1 : 1 ) );
+       int i( ( _index + direct ) % _data.size() );
+       while ( i != _index ) {
+               if ( _data[i].starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) {
+                       _index = i;
+                       _previousIndex = -2;
+                       _recallMostRecent = true;
+                       return ( true );
+               }
+               i += direct;
+               i %= _data.size();
+       }
+       return ( false );
+}
+
+UnicodeString const& History::operator[] ( int idx_ ) const {
+       return ( _data[ idx_ ] );
+}
+
+}
+
diff --git a/contrib/replxx/src/history.hxx b/contrib/replxx/src/history.hxx
new file mode 100644 (file)
index 0000000..33aed14
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef REPLXX_HISTORY_HXX_INCLUDED
+#define REPLXX_HISTORY_HXX_INCLUDED 1
+
+#include <vector>
+
+#include "unicodestring.hxx"
+#include "conversion.hxx"
+
+namespace replxx {
+
+class History {
+public:
+       typedef std::vector<UnicodeString> lines_t;
+private:
+       lines_t _data;
+       int _maxSize;
+       int _maxLineLength;
+       int _index;
+       int _previousIndex;
+       bool _recallMostRecent;
+public:
+       History( void );
+       void add( UnicodeString const& line );
+       int save( std::string const& filename );
+       int load( std::string const& filename );
+       void set_max_size( int len );
+       void reset_pos( int = -1 );
+       UnicodeString const& operator[] ( int ) const;
+       void set_recall_most_recent( void ) {
+               _recallMostRecent = true;
+       }
+       void reset_recall_most_recent( void ) {
+               _recallMostRecent = false;
+       }
+       void drop_last( void ) {
+               _data.pop_back();
+       }
+       void commit_index( void ) {
+               _previousIndex = _recallMostRecent ? _index : -2;
+       }
+       int current_pos( void ) const {
+               return ( _index );
+       }
+       bool is_last( void ) const {
+               return ( _index == ( size() - 1 ) );
+       }
+       bool is_empty( void ) const {
+               return ( _data.empty() );
+       }
+       void update_last( UnicodeString const& line_ ) {
+               _data.back() = line_;
+       }
+       bool move( bool );
+       UnicodeString const& current( void ) const {
+               return ( _data[_index] );
+       }
+       void jump( bool );
+       bool common_prefix_search( UnicodeString const&, int, bool );
+       int size( void ) const {
+               return ( static_cast<int>( _data.size() ) );
+       }
+       int max_line_length( void ) {
+               return ( _maxLineLength );
+       }
+private:
+       History( History const& ) = delete;
+       History& operator = ( History const& ) = delete;
+};
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/io.cxx b/contrib/replxx/src/io.cxx
new file mode 100644 (file)
index 0000000..a098867
--- /dev/null
@@ -0,0 +1,674 @@
+#include <memory>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <array>
+
+#ifdef _WIN32
+
+#include <conio.h>
+#include <windows.h>
+#include <io.h>
+#define isatty _isatty
+#define strcasecmp _stricmp
+#define strdup _strdup
+#define write _write
+#define STDIN_FILENO 0
+
+#include "windows.hxx"
+
+#else /* _WIN32 */
+
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <fcntl.h>
+
+#endif /* _WIN32 */
+
+#include "io.hxx"
+#include "conversion.hxx"
+#include "escape.hxx"
+#include "replxx.hxx"
+#include "util.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+namespace tty {
+
+bool is_a_tty( int fd_ ) {
+       bool aTTY( isatty( fd_ ) != 0 );
+#ifdef _WIN32
+       do {
+               if ( aTTY ) {
+                       break;
+               }
+               HANDLE h( (HANDLE)_get_osfhandle( fd_ ) );
+               if ( h == INVALID_HANDLE_VALUE ) {
+                       break;
+               }
+               DWORD st( 0 );
+               if ( ! GetConsoleMode( h, &st ) ) {
+                       break;
+               }
+               aTTY = true;
+       } while ( false );
+#endif
+       return ( aTTY );
+}
+
+bool in( is_a_tty( 0 ) );
+bool out( is_a_tty( 1 ) );
+
+}
+
+Terminal::Terminal( void )
+#ifdef _WIN32
+       : _consoleOut( INVALID_HANDLE_VALUE )
+       , _consoleIn( INVALID_HANDLE_VALUE )
+       , _oldMode()
+       , _oldDisplayAttribute()
+       , _inputCodePage( GetConsoleCP() )
+       , _outputCodePage( GetConsoleOutputCP() )
+       , _interrupt( INVALID_HANDLE_VALUE )
+       , _events()
+#else
+       : _origTermios()
+       , _interrupt()
+#endif
+       , _rawMode( false ) {
+#ifdef _WIN32
+       _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) );
+#else
+       static_cast<void>( ::pipe( _interrupt ) == 0 );
+#endif
+}
+
+Terminal::~Terminal( void ) {
+       if ( _rawMode ) {
+               disable_raw_mode();
+       }
+#ifdef _WIN32
+       CloseHandle( _interrupt );
+#else
+       static_cast<void>( ::close( _interrupt[0] ) == 0 );
+       static_cast<void>( ::close( _interrupt[1] ) == 0 );
+#endif
+}
+
+void Terminal::write32( char32_t const* text32, int len32 ) {
+       int len8 = 4 * len32 + 1;
+       unique_ptr<char[]> text8(new char[len8]);
+       int count8 = 0;
+
+       copyString32to8(text8.get(), len8, text32, len32, &count8);
+       int nWritten( 0 );
+#ifdef _WIN32
+       nWritten = win_write( text8.get(), count8 );
+#else
+       nWritten = write( 1, text8.get(), count8 );
+#endif
+       if ( nWritten != count8 ) {
+               throw std::runtime_error( "write failed" );
+       }
+       return;
+}
+
+void Terminal::write8( char const* data_, int size_ ) {
+#ifdef _WIN32
+       int nWritten( win_write( data_, size_ ) );
+#else
+       int nWritten( write( 1, data_, size_ ) );
+#endif
+       if ( nWritten != size_ ) {
+               throw std::runtime_error( "write failed" );
+       }
+       return;
+}
+
+int Terminal::get_screen_columns( void ) {
+       int cols( 0 );
+#ifdef _WIN32
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       GetConsoleScreenBufferInfo( _consoleOut, &inf );
+       cols = inf.dwSize.X;
+#else
+       struct winsize ws;
+       cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col;
+#endif
+       // cols is 0 in certain circumstances like inside debugger, which creates
+       // further issues
+       return ( cols > 0 ) ? cols : 80;
+}
+
+int Terminal::get_screen_rows( void ) {
+       int rows;
+#ifdef _WIN32
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       GetConsoleScreenBufferInfo( _consoleOut, &inf );
+       rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top;
+#else
+       struct winsize ws;
+       rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row;
+#endif
+       return (rows > 0) ? rows : 24;
+}
+
+namespace {
+inline int notty( void ) {
+       errno = ENOTTY;
+       return ( -1 );
+}
+}
+
+int Terminal::enable_raw_mode( void ) {
+       if ( ! _rawMode ) {
+#ifdef _WIN32
+               _consoleIn = GetStdHandle( STD_INPUT_HANDLE );
+               _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE );
+               SetConsoleCP( 65001 );
+               SetConsoleOutputCP( 65001 );
+               GetConsoleMode( _consoleIn, &_oldMode );
+               SetConsoleMode(
+                       _consoleIn,
+                       _oldMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT )
+               );
+#else
+               struct termios raw;
+
+               if ( ! tty::in ) {
+                       return ( notty() );
+               }
+               if ( tcgetattr( 0, &_origTermios ) == -1 ) {
+                       return ( notty() );
+               }
+
+               raw = _origTermios; /* modify the original mode */
+               /* input modes: no break, no CR to NL, no parity check, no strip char,
+                * no start/stop output control. */
+               raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+               /* output modes - disable post processing */
+               // this is wrong, we don't want raw output, it turns newlines into straight
+               // linefeeds
+               // raw.c_oflag &= ~(OPOST);
+               /* control modes - set 8 bit chars */
+               raw.c_cflag |= (CS8);
+               /* local modes - echoing off, canonical off, no extended functions,
+                * no signal chars (^Z,^C) */
+               raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+               /* control chars - set return condition: min number of bytes and timer.
+                * We want read to return every single byte, without timeout. */
+               raw.c_cc[VMIN] = 1;
+               raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+               /* put terminal in raw mode after flushing */
+               if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) {
+                       return ( notty() );
+               }
+#endif
+               _rawMode = true;
+       }
+       return 0;
+}
+
+void Terminal::disable_raw_mode(void) {
+       if ( _rawMode ) {
+#ifdef _WIN32
+               SetConsoleMode( _consoleIn, _oldMode );
+               SetConsoleCP( _inputCodePage );
+               SetConsoleOutputCP( _outputCodePage );
+               _consoleIn = INVALID_HANDLE_VALUE;
+               _consoleOut = INVALID_HANDLE_VALUE;
+#else
+               if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) {
+                       return;
+               }
+#endif
+               _rawMode = false;
+       }
+}
+
+#ifndef _WIN32
+
+/**
+ * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode
+ * (char32_t) character it encodes
+ *
+ * @return char32_t Unicode character
+ */
+char32_t read_unicode_character(void) {
+       static char8_t utf8String[5];
+       static size_t utf8Count = 0;
+       while (true) {
+               char8_t c;
+
+               /* Continue reading if interrupted by signal. */
+               ssize_t nread;
+               do {
+                       nread = read( STDIN_FILENO, &c, 1 );
+               } while ((nread == -1) && (errno == EINTR));
+
+               if (nread <= 0) return 0;
+               if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII
+                       utf8Count = 0;
+                       return c;
+               } else if (utf8Count < sizeof(utf8String) - 1) {
+                       utf8String[utf8Count++] = c;
+                       utf8String[utf8Count] = 0;
+                       char32_t unicodeChar[2];
+                       int ucharCount( 0 );
+                       ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String);
+                       if (res == conversionOK && ucharCount) {
+                               utf8Count = 0;
+                               return unicodeChar[0];
+                       }
+               } else {
+                       utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
+               }
+       }
+}
+
+#endif // #ifndef _WIN32
+
+void beep() {
+       fprintf(stderr, "\x7"); // ctrl-G == bell/beep
+       fflush(stderr);
+}
+
+// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it
+// into an encoded "keystroke".        When convenient, extended keys are translated into their
+// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B.
+//
+// A return value of zero means "no input available", and a return value of -1
+// means "invalid key".
+//
+char32_t Terminal::read_char( void ) {
+       char32_t c( 0 );
+#ifdef _WIN32
+       INPUT_RECORD rec;
+       DWORD count;
+       char32_t modifierKeys = 0;
+       bool escSeen = false;
+       int highSurrogate( 0 );
+       while (true) {
+               ReadConsoleInputW( _consoleIn, &rec, 1, &count );
+#if __REPLXX_DEBUG__   // helper for debugging keystrokes, display info in the debug "Output"
+               // window in the debugger
+               {
+                       if ( rec.EventType == KEY_EVENT ) {
+                               //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) {
+                               char buf[1024];
+                               sprintf(
+                                       buf,
+                                       "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, "
+                                       "virtual scancode 0x%04X, key %s%s%s%s%s\n",
+                                       rec.Event.KeyEvent.uChar.UnicodeChar,
+                                       rec.Event.KeyEvent.wRepeatCount,
+                                       rec.Event.KeyEvent.wVirtualKeyCode,
+                                       rec.Event.KeyEvent.wVirtualScanCode,
+                                       rec.Event.KeyEvent.bKeyDown ? "down" : "up",
+                                       (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "",
+                                       (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "",
+                                       (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "",
+                                       (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : ""
+                               );
+                               OutputDebugStringA( buf );
+                               //}
+                       }
+               }
+#endif
+               if (rec.EventType != KEY_EVENT) {
+                       continue;
+               }
+               // Windows provides for entry of characters that are not on your keyboard by
+               // sending the
+               // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU ==
+               // Alt key) ...
+               // accept these characters, otherwise only process characters on "key down"
+               if (!rec.Event.KeyEvent.bKeyDown &&
+                               rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) {
+                       continue;
+               }
+               modifierKeys = 0;
+               // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't
+               // treat this
+               // combination as either CTRL or META we just turn off those two bits, so it
+               // is still
+               // possible to combine CTRL and/or META with an AltGr key by using
+               // right-Ctrl and/or
+               // left-Alt
+               if ((rec.Event.KeyEvent.dwControlKeyState &
+                                (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) ==
+                               (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) {
+                       rec.Event.KeyEvent.dwControlKeyState &=
+                                       ~(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
+               }
+               if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) {
+                       modifierKeys |= Replxx::KEY::BASE_CONTROL;
+               }
+               if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) {
+                       modifierKeys |= Replxx::KEY::BASE_META;
+               }
+               if (escSeen) {
+                       modifierKeys |= Replxx::KEY::BASE_META;
+               }
+               int key( rec.Event.KeyEvent.uChar.UnicodeChar );
+               if ( key == 0 ) {
+                       switch (rec.Event.KeyEvent.wVirtualKeyCode) {
+                               case VK_LEFT:
+                                       return modifierKeys | Replxx::KEY::LEFT;
+                               case VK_RIGHT:
+                                       return modifierKeys | Replxx::KEY::RIGHT;
+                               case VK_UP:
+                                       return modifierKeys | Replxx::KEY::UP;
+                               case VK_DOWN:
+                                       return modifierKeys | Replxx::KEY::DOWN;
+                               case VK_DELETE:
+                                       return modifierKeys | Replxx::KEY::DELETE;
+                               case VK_HOME:
+                                       return modifierKeys | Replxx::KEY::HOME;
+                               case VK_END:
+                                       return modifierKeys | Replxx::KEY::END;
+                               case VK_PRIOR:
+                                       return modifierKeys | Replxx::KEY::PAGE_UP;
+                               case VK_NEXT:
+                                       return modifierKeys | Replxx::KEY::PAGE_DOWN;
+                               case VK_F1:
+                                       return modifierKeys | Replxx::KEY::F1;
+                               case VK_F2:
+                                       return modifierKeys | Replxx::KEY::F2;
+                               case VK_F3:
+                                       return modifierKeys | Replxx::KEY::F3;
+                               case VK_F4:
+                                       return modifierKeys | Replxx::KEY::F4;
+                               case VK_F5:
+                                       return modifierKeys | Replxx::KEY::F5;
+                               case VK_F6:
+                                       return modifierKeys | Replxx::KEY::F6;
+                               case VK_F7:
+                                       return modifierKeys | Replxx::KEY::F7;
+                               case VK_F8:
+                                       return modifierKeys | Replxx::KEY::F8;
+                               case VK_F9:
+                                       return modifierKeys | Replxx::KEY::F9;
+                               case VK_F10:
+                                       return modifierKeys | Replxx::KEY::F10;
+                               case VK_F11:
+                                       return modifierKeys | Replxx::KEY::F11;
+                               case VK_F12:
+                                       return modifierKeys | Replxx::KEY::F12;
+                               default:
+                                       continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
+                       }
+               } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later
+                       escSeen = true;
+                       continue;
+               } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) {
+                       highSurrogate = key - 0xD800;
+                       continue;
+               } else {
+                       // we got a real character, return it
+                       if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) {
+                               key -= 0xDC00;
+                               key |= ( highSurrogate << 10 );
+                               key += 0x10000;
+                       }
+                       if ( is_control_code( key ) ) {
+                               key += 0x40;
+                               modifierKeys |= Replxx::KEY::BASE_CONTROL;
+                       }
+                       key |= modifierKeys;
+                       highSurrogate = 0;
+                       c = key;
+                       break;
+               }
+       }
+
+#else
+       c = read_unicode_character();
+       if (c == 0) {
+               return 0;
+       }
+
+// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard
+// debugging mode
+// where we print out decimal and decoded values for whatever the "terminal"
+// program
+// gives us on different keystrokes.   Hit ctrl-C to exit this mode.
+//
+#ifdef __REPLXX_DEBUG__
+       if (c == ctrlChar('^')) {       // ctrl-^, special debug mode, prints all keys hit,
+                                                                                                                // ctrl-C to get out
+               printf(
+                               "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit "
+                               "this mode\n");
+               while (true) {
+                       unsigned char keys[10];
+                       int ret = read(0, keys, 10);
+
+                       if (ret <= 0) {
+                               printf("\nret: %d\n", ret);
+                       }
+                       for (int i = 0; i < ret; ++i) {
+                               char32_t key = static_cast<char32_t>(keys[i]);
+                               char* friendlyTextPtr;
+                               char friendlyTextBuf[10];
+                               const char* prefixText = (key < 0x80) ? "" : "0x80+";
+                               char32_t keyCopy = (key < 0x80) ? key : key - 0x80;
+                               if (keyCopy >= '!' && keyCopy <= '~') { // printable
+                                       friendlyTextBuf[0] = '\'';
+                                       friendlyTextBuf[1] = keyCopy;
+                                       friendlyTextBuf[2] = '\'';
+                                       friendlyTextBuf[3] = 0;
+                                       friendlyTextPtr = friendlyTextBuf;
+                               } else if (keyCopy == ' ') {
+                                       friendlyTextPtr = const_cast<char*>("space");
+                               } else if (keyCopy == 27) {
+                                       friendlyTextPtr = const_cast<char*>("ESC");
+                               } else if (keyCopy == 0) {
+                                       friendlyTextPtr = const_cast<char*>("NUL");
+                               } else if (keyCopy == 127) {
+                                       friendlyTextPtr = const_cast<char*>("DEL");
+                               } else {
+                                       friendlyTextBuf[0] = '^';
+                                       friendlyTextBuf[1] = keyCopy + 0x40;
+                                       friendlyTextBuf[2] = 0;
+                                       friendlyTextPtr = friendlyTextBuf;
+                               }
+                               printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr);
+                       }
+                       printf("\x1b[1G\n");    // go to first column of new line
+
+                       // drop out of this loop on ctrl-C
+                       if (keys[0] == ctrlChar('C')) {
+                               printf("Leaving keyboard debugging mode (on ctrl-C)\n");
+                               fflush(stdout);
+                               return -2;
+                       }
+               }
+       }
+#endif // __REPLXX_DEBUG__
+
+       c = EscapeSequenceProcessing::doDispatch(c);
+       if ( is_control_code( c ) ) {
+               c = Replxx::KEY::control( c + 0x40 );
+       }
+#endif // #_WIN32
+       return ( c );
+}
+
+Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) {
+#ifdef _WIN32
+       std::array<HANDLE,2> handles = { _consoleIn, _interrupt };
+       while ( true ) {
+               DWORD event( WaitForMultipleObjects( handles.size (), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) );
+               switch ( event ) {
+                       case ( WAIT_OBJECT_0 + 0 ): {
+                               // peek events that will be skipped
+                               INPUT_RECORD rec;
+                               DWORD count;
+                               PeekConsoleInputW( _consoleIn, &rec, 1, &count );
+
+                               if (
+                                       ( rec.EventType != KEY_EVENT )
+                                       || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) )
+                               ) {
+                                       // read the event to unsignal the handle
+                                       ReadConsoleInputW( _consoleIn, &rec, 1, &count );
+                                       continue;
+                               } else if (rec.EventType == KEY_EVENT) {
+                                       int key(rec.Event.KeyEvent.uChar.UnicodeChar);
+                                       if (key == 0) {
+                                               switch (rec.Event.KeyEvent.wVirtualKeyCode) {
+                                               case VK_LEFT:
+                                               case VK_RIGHT:
+                                               case VK_UP:
+                                               case VK_DOWN:
+                                               case VK_DELETE:
+                                               case VK_HOME:
+                                               case VK_END:
+                                               case VK_PRIOR:
+                                               case VK_NEXT:
+                                                       break;
+                                               default:
+                                                       ReadConsoleInputW(_consoleIn, &rec, 1, &count);
+                                                       continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them
+                                               }
+                                       }
+                               }
+
+                               return ( EVENT_TYPE::KEY_PRESS );
+                       }
+                       case ( WAIT_OBJECT_0 + 1 ): {
+                               ResetEvent( _interrupt );
+                               if ( _events.empty() ) {
+                                       continue;
+                               }
+                               EVENT_TYPE eventType( _events.front() );
+                               _events.pop_front();
+                               return ( eventType );
+                       }
+                       case ( WAIT_TIMEOUT ): {
+                               return ( EVENT_TYPE::TIMEOUT );
+                       }
+               }
+       }
+#else
+       fd_set fdSet;
+       int nfds( max( _interrupt[0], _interrupt[1] ) + 1 );
+       while ( true ) {
+               FD_ZERO( &fdSet );
+               FD_SET( 0, &fdSet );
+               FD_SET( _interrupt[0], &fdSet );
+               timeval tv{ timeout_ / 1000, static_cast<suseconds_t>( ( timeout_ % 1000 ) * 1000 ) };
+               int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) );
+               if ( ( err == -1 ) && ( errno == EINTR ) ) {
+                       continue;
+               }
+               if ( err == 0 ) {
+                       return ( EVENT_TYPE::TIMEOUT );
+               }
+               if ( FD_ISSET( _interrupt[0], &fdSet ) ) {
+                       char data( 0 );
+                       static_cast<void>( read( _interrupt[0], &data, 1 ) == 1 );
+                       if ( data == 'k' ) {
+                               return ( EVENT_TYPE::KEY_PRESS );
+                       }
+                       if ( data == 'm' ) {
+                               return ( EVENT_TYPE::MESSAGE );
+                       }
+               }
+               if ( FD_ISSET( 0, &fdSet ) ) {
+                       return ( EVENT_TYPE::KEY_PRESS );
+               }
+       }
+#endif
+}
+
+void Terminal::notify_event( EVENT_TYPE eventType_ ) {
+#ifdef _WIN32
+       _events.push_back( eventType_ );
+       SetEvent( _interrupt );
+#else
+       char data( eventType_ == EVENT_TYPE::KEY_PRESS ? 'k' : 'm' );
+       static_cast<void>( write( _interrupt[1], &data, 1 ) == 1 );
+#endif
+}
+
+/**
+ * Clear the screen ONLY (no redisplay of anything)
+ */
+void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) {
+#ifdef _WIN32
+       COORD coord = { 0, 0 };
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       bool toEnd( clearScreen_ == CLEAR_SCREEN::TO_END );
+       HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) );
+       GetConsoleScreenBufferInfo( consoleOut, &inf );
+       if ( ! toEnd ) {
+               SetConsoleCursorPosition( consoleOut, coord );
+       } else {
+               coord = inf.dwCursorPosition;
+       }
+       DWORD nWritten( 0 );
+       DWORD toWrite(
+               toEnd
+                       ? ( inf.dwSize.Y - inf.dwCursorPosition.Y ) * inf.dwSize.X - inf.dwCursorPosition.X
+                       : inf.dwSize.X * inf.dwSize.Y
+       );
+       FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten );
+#else
+       if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) {
+               char const clearCode[] = "\033c\033[H\033[2J\033[0m";
+               static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
+       } else {
+               char const clearCode[] = "\033[J";
+               static_cast<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
+       }
+#endif
+}
+
+void Terminal::jump_cursor( int xPos_, int yOffset_ ) {
+#ifdef _WIN32
+       CONSOLE_SCREEN_BUFFER_INFO inf;
+       GetConsoleScreenBufferInfo( _consoleOut, &inf );
+       inf.dwCursorPosition.X = xPos_;
+       inf.dwCursorPosition.Y += yOffset_;
+       SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition );
+#else
+       char seq[64];
+       if ( yOffset_ != 0 ) { // move the cursor up as required
+               snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' );
+               write8( seq, strlen( seq ) );
+       }
+       // position at the end of the prompt, clear to end of screen
+       snprintf(
+               seq, sizeof seq, "\033[%dG",
+               xPos_ + 1 /* 1-based on VT100 */
+       );
+       write8( seq, strlen( seq ) );
+#endif
+}
+
+#ifndef _WIN32
+int Terminal::read_verbatim( char32_t* buffer_, int size_ ) {
+       int len( 0 );
+       buffer_[len ++] = read_unicode_character();
+       int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) );
+       ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK );
+       while ( len < size_ ) {
+               char32_t c( read_unicode_character() );
+               if ( c == 0 ) {
+                       break;
+               }
+               buffer_[len ++] = c;
+       }
+       ::fcntl( STDIN_FILENO, F_SETFL, statusFlags );
+       return ( len );
+}
+#endif
+
+}
+
diff --git a/contrib/replxx/src/io.hxx b/contrib/replxx/src/io.hxx
new file mode 100644 (file)
index 0000000..42d8bd5
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef REPLXX_IO_HXX_INCLUDED
+#define REPLXX_IO_HXX_INCLUDED 1
+
+#include <deque>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <termios.h>
+#endif
+
+namespace replxx {
+
+class Terminal {
+public:
+       enum class EVENT_TYPE {
+               KEY_PRESS,
+               MESSAGE,
+               TIMEOUT
+       };
+private:
+#ifdef _WIN32
+       HANDLE _consoleOut;
+       HANDLE _consoleIn;
+       DWORD _oldMode;
+       WORD _oldDisplayAttribute;
+       UINT const _inputCodePage;
+       UINT const _outputCodePage;
+       HANDLE _interrupt;
+       typedef std::deque<EVENT_TYPE> events_t;
+       events_t _events;
+#else
+       struct termios _origTermios; /* in order to restore at exit */
+       int _interrupt[2];
+#endif
+       bool _rawMode; /* for destructor to check if restore is needed */
+public:
+       enum class CLEAR_SCREEN {
+               WHOLE,
+               TO_END
+       };
+public:
+       Terminal( void );
+       ~Terminal( void );
+       void write32( char32_t const*, int );
+       void write8( char const*, int );
+       int get_screen_columns(void);
+       int get_screen_rows(void);
+       int enable_raw_mode(void);
+       void disable_raw_mode(void);
+       char32_t read_char(void);
+       void clear_screen( CLEAR_SCREEN );
+       EVENT_TYPE wait_for_input( int long = 0 );
+       void notify_event( EVENT_TYPE );
+       void jump_cursor( int, int );
+#ifndef _WIN32
+       int read_verbatim( char32_t*, int );
+#endif
+private:
+       Terminal( Terminal const& ) = delete;
+       Terminal& operator = ( Terminal const& ) = delete;
+       Terminal( Terminal&& ) = delete;
+       Terminal& operator = ( Terminal&& ) = delete;
+};
+
+void beep();
+char32_t read_unicode_character(void);
+
+namespace tty {
+
+extern bool in;
+extern bool out;
+
+}
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/killring.hxx b/contrib/replxx/src/killring.hxx
new file mode 100644 (file)
index 0000000..9eca23a
--- /dev/null
@@ -0,0 +1,76 @@
+#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;
+       size_t lastYankSize;
+
+       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 (file)
index 0000000..391d374
--- /dev/null
@@ -0,0 +1,150 @@
+#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 )
+       , _previousInputLen( 0 )
+       , _previousLen( 0 )
+       , _screenColumns( 0 )
+       , _terminal( terminal_ ) {
+}
+
+void Prompt::write() {
+       _terminal.write32( _text.get(), _byteCount );
+}
+
+void Prompt::update_screen_columns( void ) {
+       _screenColumns = _terminal.get_screen_columns();
+}
+
+void Prompt::set_text( UnicodeString const& text_ ) {
+       update_screen_columns();
+       // strip control characters from the prompt -- we do allow newline
+       _text = text_;
+       UnicodeString::const_iterator in( text_.begin() );
+       UnicodeString::iterator out( _text.begin() );
+
+       int len = 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;
+                       ++len;
+                       if ('\n' == c || ++x >= _screenColumns) {
+                               x = 0;
+                               ++_extraLines;
+                               _lastLinePosition = len;
+                       }
+               } 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 = len;
+       _byteCount = static_cast<int>(out - _text.begin());
+
+       _indentation = len - _lastLinePosition;
+       _cursorRowOffset = _extraLines;
+}
+
+// Used with DynamicPrompt (history search)
+//
+const UnicodeString forwardSearchBasePrompt("(i-search)`");
+const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`");
+const UnicodeString endSearchBasePrompt("': ");
+UnicodeString previousSearchText;      // remembered across invocations of replxx_input()
+
+DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection )
+       : Prompt( terminal_ )
+       , _searchText()
+       , _direction( initialDirection ) {
+       update_screen_columns();
+       _cursorRowOffset = 0;
+       const UnicodeString* basePrompt =
+                       (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
+       size_t promptStartLength = basePrompt->length();
+       _characterCount = static_cast<int>(promptStartLength + endSearchBasePrompt.length());
+       _byteCount = _characterCount;
+       _lastLinePosition = _characterCount; // TODO fix this, we are asssuming
+                                               // that the history prompt won't wrap (!)
+       _previousLen = _characterCount;
+       _text.assign( *basePrompt ).append( endSearchBasePrompt );
+       calculate_screen_position(
+               0, 0, screen_columns(), _characterCount,
+               _indentation, _extraLines
+       );
+}
+
+void DynamicPrompt::updateSearchPrompt(void) {
+       const UnicodeString* basePrompt =
+                       (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
+       size_t promptStartLength = basePrompt->length();
+       _characterCount = static_cast<int>(promptStartLength + _searchText.length() +
+                                                                                                                                endSearchBasePrompt.length());
+       _byteCount = _characterCount;
+       _text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt );
+}
+
+}
+
diff --git a/contrib/replxx/src/prompt.hxx b/contrib/replxx/src/prompt.hxx
new file mode 100644 (file)
index 0000000..aabff0a
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef REPLXX_PROMPT_HXX_INCLUDED
+#define REPLXX_PROMPT_HXX_INCLUDED 1
+
+#include <cstdlib>
+
+#include "unicodestring.hxx"
+#include "io.hxx"
+
+namespace replxx {
+
+class Prompt {           // a convenience struct for grouping prompt info
+public:
+       UnicodeString _text;   // our copy of the prompt text, edited
+       int _characterCount;   // chars in _text
+       int _byteCount;        // bytes in _text
+       int _extraLines;       // extra lines (beyond 1) occupied by prompt
+       int _indentation;      // column offset to end of prompt
+       int _lastLinePosition; // index into _text where last line begins
+       int _previousInputLen; // _characterCount of previous input line, for clearing
+       int _cursorRowOffset;  // where the cursor is relative to the start of the prompt
+       int _previousLen;      // help erasing
+private:
+       int _screenColumns;    // width of screen in columns [cache]
+       Terminal& _terminal;
+public:
+       Prompt( Terminal& );
+       void set_text( UnicodeString const& textPtr );
+       void update_screen_columns( void );
+       int screen_columns() const {
+               return ( _screenColumns );
+       }
+       void write();
+};
+
+extern UnicodeString previousSearchText; // remembered across invocations of replxx_input()
+
+// 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 (file)
index 0000000..7803d87
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+ * 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 "io.hxx"
+
+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_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 );
+}
+
+int Replxx::history_save( std::string const& filename ) {
+       return ( _impl->history_save( filename ) );
+}
+
+int Replxx::history_load( std::string const& filename ) {
+       return ( _impl->history_load( filename ) );
+}
+
+int Replxx::history_size( void ) const {
+       return ( _impl->history_size() );
+}
+
+std::string Replxx::history_line( int index ) {
+       return ( _impl->history_line( index ) );
+}
+
+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_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_ );
+}
+
+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::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 ) );
+}
+
+}
+
+::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_ ) );
+}
+
+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 belongs to the caller on return and must be
+ * freed to prevent memory leaks
+ */
+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 );
+}
+
+struct replxx_completions {
+       replxx::Replxx::completions_t data;
+};
+
+struct replxx_hints {
+       replxx::Replxx::hints_t data;
+};
+
+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(), 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_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 );
+}
+
+/* Fetch a line of the history by (zero-based) index.  If the requested
+ * line does not exist, NULL is returned.      The return value is a heap-allocated
+ * copy of the line. */
+char const* replxx_history_line( ::Replxx* replxx_, int index ) {
+       replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+       return ( replxx->history_line( index ).c_str() );
+}
+
+/* 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 ) );
+}
+
+/* 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 ) );
+}
+
+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 (file)
index 0000000..14f3cbd
--- /dev/null
@@ -0,0 +1,1984 @@
+#include <algorithm>
+#include <memory>
+#include <cerrno>
+#include <iostream>
+
+#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 "io.hxx"
+#include "history.hxx"
+#include "replxx.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+#ifndef _WIN32
+
+bool gotResize = false;
+
+#endif
+
+namespace {
+
+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 defaultBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?";
+
+#ifndef _WIN32
+
+static void WindowSizeChanged(int) {
+       // do nothing here but setting this flag
+       gotResize = true;
+}
+
+#endif
+
+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;
+}
+
+}
+
+Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* )
+       : _utf8Buffer()
+       , _data()
+       , _charWidths()
+       , _display()
+       , _displayInputLength( 0 )
+       , _hint()
+       , _pos( 0 )
+       , _prefix( 0 )
+       , _hintSelection( -1 )
+       , _history()
+       , _killRing()
+       , _maxHintRows( REPLXX_MAX_HINT_ROWS )
+       , _hintDelay( 0 )
+       , _breakChars( defaultBreakChars )
+       , _completionCountCutoff( 100 )
+       , _overwrite( false )
+       , _doubleTabCompletion( false )
+       , _completeOnEmpty( true )
+       , _beepOnAmbiguousCompletion( false )
+       , _noColor( false )
+       , _keyPressHandlers()
+       , _terminal()
+       , _currentThread()
+       , _prompt( _terminal )
+       , _completionCallback( nullptr )
+       , _highlighterCallback( nullptr )
+       , _hintCallback( nullptr )
+       , _keyPresses()
+       , _messages()
+       , _completions()
+       , _completionContextLength( 0 )
+       , _completionSelection( -1 )
+       , _preloadedBuffer()
+       , _errorMessage()
+       , _modifiedState( false )
+       , _mutex() {
+       using namespace std::placeholders;
+       bind_key( Replxx::KEY::control( 'A' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ) );
+       bind_key( Replxx::KEY::HOME + 0,                       std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ) );
+       bind_key( Replxx::KEY::control( 'E' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE,      _1 ) );
+       bind_key( Replxx::KEY::END + 0,                        std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE,      _1 ) );
+       bind_key( Replxx::KEY::control( 'B' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT,                _1 ) );
+       bind_key( Replxx::KEY::LEFT + 0,                       std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT,                _1 ) );
+       bind_key( Replxx::KEY::control( 'F' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT,               _1 ) );
+       bind_key( Replxx::KEY::RIGHT + 0,                      std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT,               _1 ) );
+       bind_key( Replxx::KEY::meta( 'b' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT,       _1 ) );
+       bind_key( Replxx::KEY::meta( 'B' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT,       _1 ) );
+       bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ),   std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT,       _1 ) );
+       bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ),      std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT,       _1 ) ); // Emacs allows Meta, readline don't
+       bind_key( Replxx::KEY::meta( 'f' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT,      _1 ) );
+       bind_key( Replxx::KEY::meta( 'F' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT,      _1 ) );
+       bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ),  std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT,      _1 ) );
+       bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ),     std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT,      _1 ) ); // Emacs allows Meta, readline don't
+       bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT,      _1 ) );
+       bind_key( Replxx::KEY::meta( 'd' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD,             _1 ) );
+       bind_key( Replxx::KEY::meta( 'D' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD,             _1 ) );
+       bind_key( Replxx::KEY::control( 'W' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD,        _1 ) );
+       bind_key( Replxx::KEY::control( 'U' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE,        _1 ) );
+       bind_key( Replxx::KEY::control( 'K' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE,             _1 ) );
+       bind_key( Replxx::KEY::control( 'Y' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK,                            _1 ) );
+       bind_key( Replxx::KEY::meta( 'y' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE,                      _1 ) );
+       bind_key( Replxx::KEY::meta( 'Y' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE,                      _1 ) );
+       bind_key( Replxx::KEY::meta( 'c' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD,                 _1 ) );
+       bind_key( Replxx::KEY::meta( 'C' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD,                 _1 ) );
+       bind_key( Replxx::KEY::meta( 'l' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD,                  _1 ) );
+       bind_key( Replxx::KEY::meta( 'L' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD,                  _1 ) );
+       bind_key( Replxx::KEY::meta( 'u' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD,                  _1 ) );
+       bind_key( Replxx::KEY::meta( 'U' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD,                  _1 ) );
+       bind_key( Replxx::KEY::control( 'T' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS,            _1 ) );
+       bind_key( Replxx::KEY::control( 'C' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE,                      _1 ) );
+       bind_key( Replxx::KEY::control( 'D' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF,                        _1 ) );
+       bind_key( Replxx::KEY::INSERT + 0,                     std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE,           _1 ) );
+       bind_key( 127,                                         std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR,   _1 ) );
+       bind_key( Replxx::KEY::DELETE + 0,                     std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR,   _1 ) );
+       bind_key( Replxx::KEY::BACKSPACE + 0,                  std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 ) );
+       bind_key( Replxx::KEY::control( 'J' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE,                     _1 ) );
+       bind_key( Replxx::KEY::ENTER + 0,                      std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE,                     _1 ) );
+       bind_key( Replxx::KEY::control( 'L' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN,                    _1 ) );
+       bind_key( Replxx::KEY::control( 'N' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT,                   _1 ) );
+       bind_key( Replxx::KEY::control( 'P' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS,               _1 ) );
+       bind_key( Replxx::KEY::DOWN + 0,                       std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT,                    _1 ) );
+       bind_key( Replxx::KEY::UP + 0,                         std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS,                _1 ) );
+       bind_key( Replxx::KEY::meta( '>' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST,                    _1 ) );
+       bind_key( Replxx::KEY::meta( '<' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST,                   _1 ) );
+       bind_key( Replxx::KEY::PAGE_DOWN + 0,                  std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST,                    _1 ) );
+       bind_key( Replxx::KEY::PAGE_UP + 0,                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST,                   _1 ) );
+       bind_key( Replxx::KEY::control( Replxx::KEY::UP ),     std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS,                   _1 ) );
+       bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ),   std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT,                       _1 ) );
+#ifndef _WIN32
+       bind_key( Replxx::KEY::control( 'V' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT,                 _1 ) );
+       bind_key( Replxx::KEY::control( 'Z' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND,                         _1 ) );
+#endif
+       bind_key( Replxx::KEY::TAB + 0,                        std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE,                   _1 ) );
+       bind_key( Replxx::KEY::control( 'R' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH,      _1 ) );
+       bind_key( Replxx::KEY::control( 'S' ),                 std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH,      _1 ) );
+       bind_key( Replxx::KEY::meta( 'p' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH,    _1 ) );
+       bind_key( Replxx::KEY::meta( 'P' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH,    _1 ) );
+       bind_key( Replxx::KEY::meta( 'n' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH,    _1 ) );
+       bind_key( Replxx::KEY::meta( 'N' ),                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH,    _1 ) );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32_t code ) {
+       switch ( action_ ) {
+               case ( Replxx::ACTION::INSERT_CHARACTER ):                return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::insert_character, code ) );
+               case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ):   return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::delete_character, code ) );
+               case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::backspace_character, code ) );
+               case ( Replxx::ACTION::KILL_TO_END_OF_LINE ):             return ( action( WANT_REFRESH | SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_end_of_line, code ) );
+               case ( Replxx::ACTION::KILL_TO_BEGINING_OF_LINE ):        return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_begining_of_line, code ) );
+               case ( Replxx::ACTION::KILL_TO_END_OF_WORD ):             return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_word_to_right, code ) );
+               case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ):        return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_word_to_left, code ) );
+               case ( Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT ):      return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_whitespace_to_left, code ) );
+               case ( Replxx::ACTION::YANK ):                            return ( action( NOOP, &Replxx::ReplxxImpl::yank, code ) );
+               case ( Replxx::ACTION::YANK_CYCLE ):                      return ( action( NOOP, &Replxx::ReplxxImpl::yank_cycle, 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, code ) );
+               case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ):      return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, 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, &Replxx::ReplxxImpl::capitalize_word, code ) );
+               case ( Replxx::ACTION::LOWERCASE_WORD ):                  return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::lowercase_word, code ) );
+               case ( Replxx::ACTION::UPPERCASE_WORD ):                  return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::uppercase_word, code ) );
+               case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ):            return ( action( RESET_KILL_ACTION, &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( NOOP, &Replxx::ReplxxImpl::complete_line, code ) );
+               case ( Replxx::ACTION::COMPLETE_NEXT ):                   return ( action( DONT_RESET_COMPLETIONS, &Replxx::ReplxxImpl::complete_next, code ) );
+               case ( Replxx::ACTION::COMPLETE_PREVIOUS ):               return ( action( DONT_RESET_COMPLETIONS, &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, &Replxx::ReplxxImpl::abort_line, code ) );
+               case ( Replxx::ACTION::SEND_EOF ):                        return ( action( NOOP, &Replxx::ReplxxImpl::send_eof, code ) );
+       }
+       return ( Replxx::ACTION_RESULT::BAIL );
+}
+
+void Replxx::ReplxxImpl::bind_key( char32_t code_, Replxx::key_press_handler_t handler_ ) {
+       _keyPressHandlers[code_] = handler_;
+}
+
+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( 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( HINT_ACTION::REPAINT );
+                       hintDelay = 0;
+                       continue;
+               }
+               if ( eventType == Terminal::EVENT_TYPE::KEY_PRESS ) {
+                       break;
+               }
+               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(), 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;
+}
+
+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 ) {
+#ifndef _WIN32
+       gotResize = false;
+#endif
+       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 ) );
+               }
+               printf("\n");
+               _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
+       struct sigaction sa;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = 0;
+       sa.sa_handler = &WindowSizeChanged;
+
+       if (sigaction(SIGWINCH, &sa, nullptr) == -1) {
+               return errno;
+       }
+#endif
+       return 0;
+}
+
+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 );
+       _charWidths.resize( _data.length() );
+       recompute_character_widths( _data.get(), _charWidths.data(), _data.length() );
+       _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 ) ) {
+               _display.push_back( '^' );
+               _display.push_back( ch + 0x40 );
+       } 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() );
+               return;
+       }
+       if ( hintAction_ == HINT_ACTION::SKIP ) {
+               return;
+       }
+       _display.clear();
+       if ( _noColor ) {
+               for ( char32_t ch : _data ) {
+                       render( ch );
+               }
+               _displayInputLength = _display.size();
+               return;
+       }
+       Replxx::colors_t colors( _data.length(), Replxx::Color::DEFAULT );
+       _utf8Buffer.assign( _data );
+       if ( !! _highlighterCallback ) {
+               _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 = _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;
+       }
+       Replxx::Color c( Replxx::Color::GRAY );
+       _utf8Buffer.assign( _data, _pos );
+       int contextLen( context_length() );
+       Replxx::ReplxxImpl::hints_t hints( call_hinter( _utf8Buffer.get(), contextLen, c ) );
+       int hintCount( hints.size() );
+       if ( hintCount == 1 ) {
+               _hint = hints.front();
+               len = _hint.length() - contextLen;
+               if ( len > 0 ) {
+                       set_color( c );
+                       for ( int i( 0 ); i < len; ++ i ) {
+                               _display.push_back( _hint[i + contextLen] );
+                       }
+                       set_color( Replxx::Color::DEFAULT );
+               }
+       } else if ( ( _maxHintRows > 0 ) && ( hintCount > 0 ) ) {
+               int startCol( _prompt._indentation + _pos - contextLen );
+               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 = hints[_hintSelection];
+                       len = min<int>( _hint.length(), maxCol - startCol - _data.length() );
+                       if ( contextLen < len ) {
+                               set_color( c );
+                               for ( int i( contextLen ); i < len; ++ i ) {
+                                       _display.push_back( _hint[i] );
+                               }
+                               set_color( Replxx::Color::DEFAULT );
+                       }
+               }
+               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( c );
+                       for ( int i( _pos - contextLen ); ( 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( hints[hintNo % hintCount] );
+                       for ( int i( contextLen ); ( 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_ ) {
+       // 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 += 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.jump_cursor(
+               _prompt._indentation, // 0-based on Win32
+               -( _prompt._cursorRowOffset - _prompt._extraLines )
+       );
+       _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
+       _prompt._previousInputLen = _data.length();
+       // display the input line
+       _terminal.write32( _display.data(), _display.size() );
+#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 ) );
+       _prompt._cursorRowOffset = _prompt._extraLines + yCursorPos; // remember row for next pass
+}
+
+int Replxx::ReplxxImpl::context_length() {
+       int prefixLength = _pos;
+       while ( prefixLength > 0 ) {
+               if ( is_word_break_character( _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( void ) {
+       // position at the start of the prompt, clear to end of previous input
+       _terminal.jump_cursor( 0, -_prompt._cursorRowOffset );
+       _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
+       return;
+}
+
+namespace {
+int longest_common_prefix( Replxx::ReplxxImpl::completions_t const& completions ) {
+       int completionsCount( 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();
+       _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( _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 {
+                               printf("\n");
+                       }
+                       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();
+#ifndef _WIN32
+       // we have to generate our own newline on line wrap on Linux
+       if (_prompt._indentation == 0 && _prompt._extraLines > 0) {
+               _terminal.write8( "\n", 1 );
+       }
+#endif
+       _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.reset_pos();
+
+       // display the prompt
+       _prompt.write();
+
+#ifndef _WIN32
+       // we have to generate our own newline on line wrap on Linux
+       if ( ( _prompt._indentation == 0 ) && ( _prompt._extraLines > 0 ) ) {
+               _terminal.write8( "\n", 1 );
+       }
+#endif
+
+       // 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
+#ifndef _WIN32
+               if (c == 0 && gotResize) {
+                       // caught a window resize event
+                       // now redraw the prompt and line
+                       gotResize = false;
+                       _prompt.update_screen_columns();
+                       // redraw the original prompt with current input
+                       dynamicRefresh( _prompt, _data.get(), _data.length(), _pos );
+                       continue;
+               }
+#endif
+
+               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, &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_ ) );
+       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_ & WANT_REFRESH ) {
+               _modifiedState = true;
+       }
+       return ( res );
+}
+
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) {
+       _history.reset_recall_most_recent();
+       /*
+        * 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 ) ) {
+               beep();
+               return ( Replxx::ACTION_RESULT::CONTINUE );
+       }
+       if ( ! _overwrite || ( _pos >= _data.length() ) ) {
+               _data.insert( _pos, c );
+       } else {
+               _data[_pos] = c;
+       }
+       ++ _pos;
+       int inputLen = calculate_displayed_length( _data.get(), _data.length() );
+       if (
+               ( _pos == _data.length() )
+               && ( _noColor || ! ( !! _highlighterCallback || !! _hintCallback ) )
+               && ( _prompt._indentation + inputLen < _prompt.screen_columns() )
+       ) {
+               /* Avoid a full assign of the line in the
+                * trivial case. */
+               if (inputLen > _prompt._previousInputLen) {
+                       _prompt._previousInputLen = inputLen;
+               }
+               render( c );
+               _displayInputLength = _display.size();
+               _terminal.write32(reinterpret_cast<char32_t*>(&c), 1);
+       } else {
+               refresh_line();
+       }
+       return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// 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
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) {
+       if (_pos > 0) {
+               while (_pos > 0 && is_word_break_character( _data[_pos - 1] ) ) {
+                       --_pos;
+               }
+               while (_pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) {
+                       --_pos;
+               }
+               refresh_line();
+       }
+       return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-F, move cursor right by one word
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) {
+       if ( _pos < _data.length() ) {
+               while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
+                       ++_pos;
+               }
+               while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
+                       ++_pos;
+               }
+               refresh_line();
+       }
+       return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-Backspace, kill word to left of cursor
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) {
+       if ( _pos > 0 ) {
+               _history.reset_recall_most_recent();
+               int startingPos = _pos;
+               while ( _pos > 0 && is_word_break_character( _data[_pos - 1] ) ) {
+                       -- _pos;
+               }
+               while ( _pos > 0 && !is_word_break_character( _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
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) {
+       if ( _pos < _data.length() ) {
+               _history.reset_recall_most_recent();
+               int endingPos = _pos;
+               while ( endingPos < _data.length() && is_word_break_character( _data[endingPos] ) ) {
+                       ++ endingPos;
+               }
+               while ( endingPos < _data.length() && !is_word_break_character( _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 ) {
+               _history.reset_recall_most_recent();
+               int startingPos = _pos;
+               while ( _pos > 0 && _data[_pos - 1] == ' ' ) {
+                       --_pos;
+               }
+               while ( _pos > 0 && _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 );
+       _history.reset_recall_most_recent();
+       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) {
+               _history.reset_recall_most_recent();
+               _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 ) {
+       _history.reset_recall_most_recent();
+       UnicodeString* restoredText( _killRing.yank() );
+       if ( restoredText ) {
+               _data.insert( _pos, *restoredText, 0, restoredText->length() );
+               _pos += restoredText->length();
+               refresh_line();
+               _killRing.lastAction = KillRing::actionYank;
+               _killRing.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 );
+       }
+       _history.reset_recall_most_recent();
+       UnicodeString* restoredText = _killRing.yankPop();
+       if ( !restoredText ) {
+               beep();
+               return ( Replxx::ACTION_RESULT::CONTINUE );
+       }
+       _pos -= _killRing.lastYankSize;
+       _data.erase( _pos, _killRing.lastYankSize );
+       _data.insert( _pos, *restoredText, 0, restoredText->length() );
+       _pos += restoredText->length();
+       _killRing.lastYankSize = restoredText->length();
+       refresh_line();
+       return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// meta-C, give word initial Cap
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) {
+       _history.reset_recall_most_recent();
+       if (_pos < _data.length()) {
+               while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
+                       ++_pos;
+               }
+               if (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
+                       if ( _data[_pos] >= 'a' && _data[_pos] <= 'z' ) {
+                               _data[_pos] += 'A' - 'a';
+                       }
+                       ++_pos;
+               }
+               while (_pos < _data.length() && !is_word_break_character( _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
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) {
+       if (_pos < _data.length()) {
+               _history.reset_recall_most_recent();
+               while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
+                       ++ _pos;
+               }
+               while (_pos < _data.length() && !is_word_break_character( _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
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) {
+       if (_pos < _data.length()) {
+               _history.reset_recall_most_recent();
+               while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
+                       ++ _pos;
+               }
+               while ( _pos < _data.length() && !is_word_break_character( _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 ) {
+               _history.reset_recall_most_recent();
+               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 ) {
+       _history.reset_recall_most_recent();
+       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
+       refresh_line( 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() ) ) {
+               _history.reset_recall_most_recent();
+               _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 ) {
+               _history.reset_recall_most_recent();
+               -- _pos;
+               _data.erase( _pos );
+               refresh_line();
+       }
+       return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+// ctrl-J/linefeed/newline, accept line
+// ctrl-M/return/enter
+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
+       refresh_line( HINT_ACTION::TRIM );
+       _history.commit_index();
+       _history.drop_last();
+       return ( Replxx::ACTION_RESULT::RETURN );
+}
+
+// ctrl-N, recall next line in history
+Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) {
+       return ( history_move( false ) );
+}
+
+// ctrl-P, 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 ) {
+       _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode
+       raise(SIGSTOP);   // Break out in mid-line
+       _terminal.enable_raw_mode();  // Back from Linux shell, re-enter raw mode
+       // 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 ) ) ) {
+               _killRing.lastAction = KillRing::actionOther;
+               _history.reset_recall_most_recent();
+
+               // 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() );
+               complete_line( first ? '\t' : 0 );
+               if ( first ) {
+                       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( _completions[_completionSelection].text().length() - _completionContextLength );
+               _pos -= oldCompletionLength;
+               _data.erase( _pos, oldCompletionLength );
+       }
+       if ( newSelection != -1 ) {
+               int newCompletionLength( _completions[newSelection].text().length() - _completionContextLength );
+               _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 );
+       }
+       int historyLinePosition( _pos );
+       clear_self_to_end_of_screen();
+
+       DynamicPrompt dp( _terminal, (startChar == Replxx::KEY::control('R')) ? -1 : 1 );
+
+       dp._previousLen = _prompt._previousLen;
+       dp._previousInputLen = _prompt._previousInputLen;
+       // draw user's text with our prompt
+       dynamicRefresh(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
+                               _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode
+                               raise(SIGSTOP);   // Break out in mid-line
+                               _terminal.enable_raw_mode();  // Back from Linux shell, re-enter raw mode
+                               dynamicRefresh(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.reset_pos( dp._direction == -1 ? _history.size() - 1 : 0 );
+                               } 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 historySearchIndex = _history.current_pos();
+                       int lineSearchPos = historyLinePosition;
+                       if ( searchAgain ) {
+                               lineSearchPos += dp._direction;
+                       }
+                       searchAgain = false;
+                       while ( true ) {
+                               while ( ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) && ( lineSearchPos >= 0 ) ) {
+                                       if ( std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos ) ) {
+                                               found = true;
+                                               break;
+                                       }
+                                       lineSearchPos += dp._direction;
+                               }
+                               if ( found ) {
+                                       _history.reset_pos( historySearchIndex );
+                                       historyLinePosition = lineSearchPos;
+                                       break;
+                               } else if ( ( dp._direction > 0 ) ? ( historySearchIndex < _history.size() ) : ( historySearchIndex > 0 ) ) {
+                                       historySearchIndex += dp._direction;
+                                       activeHistoryLine.assign( _history[historySearchIndex] );
+                                       lineSearchPos = ( dp._direction > 0 ) ? 0 : ( activeHistoryLine.length() - dp._searchText.length() );
+                               } else {
+                                       beep();
+                                       break;
+                               }
+                       } // while
+               }
+               activeHistoryLine.assign( _history.current() );
+               dynamicRefresh(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 );
+       pb._characterCount = _prompt._indentation;
+       pb._byteCount = _prompt._byteCount;
+       UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], pb._byteCount - _prompt._lastLinePosition );
+       pb._text = tempUnicode;
+       pb._extraLines = 0;
+       pb._indentation = _prompt._indentation;
+       pb._lastLinePosition = 0;
+       pb._previousInputLen = activeHistoryLine.length();
+       pb._cursorRowOffset = dp._cursorRowOffset;
+       pb.update_screen_columns();
+       pb._previousLen = dp._characterCount;
+       if ( useSearchedLine && ( activeHistoryLine.length() > 0 ) ) {
+               _history.set_recall_most_recent();
+               _data.assign( activeHistoryLine );
+               _pos = historyLinePosition;
+       }
+       dynamicRefresh(pb, _data.get(), _data.length(), _pos); // redraw the original prompt with current input
+       _prompt._previousInputLen = _data.length();
+       _prompt._cursorRowOffset = _prompt._extraLines + pb._cursorRowOffset;
+       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();
+#ifndef _WIN32
+               // we have to generate our own newline on line wrap on Linux
+               if (_prompt._indentation == 0 && _prompt._extraLines > 0) {
+                       _terminal.write8( "\n", 1 );
+               }
+#endif
+               _prompt._cursorRowOffset = _prompt._extraLines;
+               refresh_line();
+       }
+       return ( Replxx::ACTION_RESULT::CONTINUE );
+}
+
+bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const {
+       bool wbc( false );
+       if ( char_ < 128 ) {
+               wbc = strchr( _breakChars, static_cast<char>( char_ ) ) != nullptr;
+       }
+       return ( wbc );
+}
+
+void Replxx::ReplxxImpl::history_add( std::string const& line ) {
+       _history.add( UnicodeString( line ) );
+}
+
+int Replxx::ReplxxImpl::history_save( std::string const& filename ) {
+       return ( _history.save( filename ) );
+}
+
+int Replxx::ReplxxImpl::history_load( std::string const& filename ) {
+       return ( _history.load( filename ) );
+}
+
+int Replxx::ReplxxImpl::history_size( void ) const {
+       return ( _history.size() );
+}
+
+std::string Replxx::ReplxxImpl::history_line( int index ) {
+       _utf8Buffer.assign( _history[index] );
+       return ( _utf8Buffer.get() );
+}
+
+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 ) {
+       _breakChars = wordBreakers;
+}
+
+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_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& pi, char32_t* buf32, int len, int pos) {
+       clear_self_to_end_of_screen();
+       // calculate the position of the end of the prompt
+       int xEndOfPrompt, yEndOfPrompt;
+       calculate_screen_position(
+               0, 0, pi.screen_columns(), pi._characterCount,
+               xEndOfPrompt, yEndOfPrompt
+       );
+       pi._indentation = xEndOfPrompt;
+
+       // calculate the position of the end of the input line
+       int xEndOfInput, yEndOfInput;
+       calculate_screen_position(
+               xEndOfPrompt, yEndOfPrompt, pi.screen_columns(),
+               calculate_displayed_length(buf32, len), xEndOfInput,
+               yEndOfInput
+       );
+
+       // calculate the desired position of the cursor
+       int xCursorPos, yCursorPos;
+       calculate_screen_position(
+               xEndOfPrompt, yEndOfPrompt, pi.screen_columns(),
+               calculate_displayed_length(buf32, pos), xCursorPos,
+               yCursorPos
+       );
+
+       pi._previousLen = pi._indentation;
+       pi._previousInputLen = len;
+
+       // display the prompt
+       pi.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 )
+       );
+       pi._cursorRowOffset = pi._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 (file)
index 0000000..3cf1e82
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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 "replxx.hxx"
+#include "history.hxx"
+#include "killring.hxx"
+#include "utf8string.hxx"
+#include "prompt.hxx"
+#include "io.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> hints_t;
+       typedef std::unique_ptr<char[]> utf8_buffer_t;
+       typedef std::unique_ptr<char32_t[]> input_buffer_t;
+       typedef std::vector<char> char_widths_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 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;
+private:
+       mutable Utf8String     _utf8Buffer;
+       UnicodeString  _data;
+       char_widths_t  _charWidths; // character widths from mk_wcwidth()
+       display_t      _display;
+       int _displayInputLength;
+       UnicodeString  _hint;
+       int _pos;    // character position in buffer ( 0 <= _pos <= _len )
+       int _prefix; // prefix length used in common prefix search
+       int _hintSelection; // Currently selected hint.
+       History _history;
+       KillRing _killRing;
+       int _maxHintRows;
+       int _hintDelay;
+       char const* _breakChars;
+       int _completionCountCutoff;
+       bool _overwrite;
+       bool _doubleTabCompletion;
+       bool _completeOnEmpty;
+       bool _beepOnAmbiguousCompletion;
+       bool _noColor;
+       key_press_handlers_t _keyPressHandlers;
+       Terminal _terminal;
+       std::thread::id _currentThread;
+       Prompt _prompt;
+       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;
+       bool _modifiedState;
+       mutable std::mutex _mutex;
+public:
+       ReplxxImpl( FILE*, FILE*, FILE* );
+       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 );
+       int history_save( std::string const& filename );
+       int history_load( std::string const& filename );
+       std::string history_line( int index );
+       int history_size( void ) const;
+       void set_preload_buffer(std::string const& preloadText);
+       void set_word_break_characters( char const* wordBreakers );
+       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_no_color( bool val );
+       void set_max_history_size( int len );
+       void set_completion_count_cutoff( int len );
+       int install_window_change_handler( 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 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 );
+       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 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 );
+       Replxx::ACTION_RESULT move_one_word_left( char32_t );
+       Replxx::ACTION_RESULT move_one_word_right( char32_t );
+       Replxx::ACTION_RESULT kill_word_to_left( char32_t );
+       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 capitalize_word( char32_t );
+       Replxx::ACTION_RESULT lowercase_word( char32_t );
+       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 );
+       char32_t read_char( HINT_ACTION = HINT_ACTION::SKIP );
+       char const* read_from_stdin( void );
+       char32_t do_complete_line( bool );
+       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 );
+       bool is_word_break_character( char32_t ) const;
+       void dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos);
+       char const* finalize_input( char const* );
+       void clear_self_to_end_of_screen( void );
+       typedef struct {
+               int index;
+               bool error;
+       } paren_info_t;
+       paren_info_t matching_paren( void );
+};
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/unicodestring.hxx b/contrib/replxx/src/unicodestring.hxx
new file mode 100644 (file)
index 0000000..53a7372
--- /dev/null
@@ -0,0 +1,179 @@
+#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED
+#define REPLXX_UNICODESTRING_HXX_INCLUDED
+
+#include <vector>
+#include <cstring>
+
+#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( 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( str_.length() );
+               int len( 0 );
+               copyString8to32( _data.data(), str_.length(), len, str_.c_str() );
+               _data.resize( len );
+               return *this;
+       }
+
+       UnicodeString& assign( char const* str_ ) {
+               size_t byteCount( 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;
+       }
+
+       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 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 (file)
index 0000000..3adf17a
--- /dev/null
@@ -0,0 +1,71 @@
+#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;
+public:
+       Utf8String( void )
+               : _data()
+               , _bufSize( 0 ) {
+       }
+       explicit Utf8String( UnicodeString const& src )
+               : _data()
+               , _bufSize( 0 ) {
+               assign( src, src.length() );
+       }
+
+       Utf8String( UnicodeString const& src_, int len_ )
+               : _data()
+               , _bufSize( 0 ) {
+               assign( src_, len_ );
+       }
+
+       void assign( UnicodeString const& str_ ) {
+               assign( str_, str_.length() );
+       }
+
+       void assign( UnicodeString const& str_, int len_ ) {
+               int len( len_ * 4 );
+               realloc( len );
+               copyString32to8( _data.get(), len, str_.get(), len_ );
+       }
+
+       void assign( std::string const& str_ ) {
+               realloc( str_.length() );
+               strncpy( _data.get(), str_.c_str(), str_.length() );
+       }
+
+       char const* get() const {
+               return _data.get();
+       }
+
+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 (file)
index 0000000..9beb96b
--- /dev/null
@@ -0,0 +1,152 @@
+#include <cstdlib>
+#include <cstring>
+#include <wctype.h>
+
+#include "util.hxx"
+
+namespace replxx {
+
+int mk_wcwidth( char32_t );
+
+/**
+ * Recompute widths of all characters in a char32_t buffer
+ * @param text      - input buffer of Unicode characters
+ * @param widths    - output buffer of character widths
+ * @param charCount - number of characters in buffer
+ */
+void recompute_character_widths( char32_t const* text, char* widths, int charCount ) {
+       for (int i = 0; i < charCount; ++i) {
+               widths[i] = mk_wcwidth(text[i]);
+       }
+}
+
+/**
+ * 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 );
+}
+
+}
+
diff --git a/contrib/replxx/src/util.hxx b/contrib/replxx/src/util.hxx
new file mode 100644 (file)
index 0000000..8afa0fa
--- /dev/null
@@ -0,0 +1,21 @@
+#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
+}
+
+void recompute_character_widths( char32_t const* text, char* widths, int charCount );
+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 );
+
+}
+
+#endif
+
diff --git a/contrib/replxx/src/wcwidth.cpp b/contrib/replxx/src/wcwidth.cpp
new file mode 100644 (file)
index 0000000..c6c05fa
--- /dev/null
@@ -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 (file)
index 0000000..e5b6de4
--- /dev/null
@@ -0,0 +1,158 @@
+#ifdef _WIN32
+
+#include <iostream>
+
+#include "windows.hxx"
+#include "conversion.hxx"
+#include "io.hxx"
+
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
+#endif
+
+using namespace std;
+
+namespace replxx {
+
+WinAttributes WIN_ATTR;
+
+template<typename T>
+T* HandleEsc(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;
+       }
+
+       auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
+       SetConsoleTextAttribute(
+               handle,
+               WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor
+       );
+
+       return p;
+}
+
+int win_write( char const* str_, int size_ ) {
+       int count( 0 );
+       DWORD currentMode( 0 );
+       HANDLE consoleOut( GetStdHandle( STD_OUTPUT_HANDLE ) );
+       if ( tty::out && GetConsoleMode( consoleOut, &currentMode ) ) {
+               UINT inputCodePage( GetConsoleCP() );
+               UINT outputCodePage( GetConsoleOutputCP() );
+               SetConsoleCP( 65001 );
+               SetConsoleOutputCP( 65001 );
+               DWORD nWritten( 0 );
+               if ( SetConsoleMode( consoleOut, currentMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) ) {
+                       WriteConsoleA( consoleOut, str_, size_, &nWritten, nullptr );
+                       count = nWritten;
+                       SetConsoleMode( consoleOut, currentMode );
+               } else {
+                       char const* s( str_ );
+                       char const* e( str_ + size_ );
+                       while ( str_ < e ) {
+                               if ( *str_ == 27 ) {
+                                       if ( s < str_ ) {
+                                               int toWrite( str_ - s );
+                                               WriteConsoleA( consoleOut, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr );
+                                               count += nWritten;
+                                               if ( nWritten != toWrite ) {
+                                                       s = str_ = nullptr;
+                                                       break;
+                                               }
+                                       }
+                                       s = HandleEsc( str_ + 1, e );
+                                       int escaped( s - str_);
+                                       count += escaped;
+                                       str_ = s;
+                               } else {
+                                       ++ str_;
+                               }
+                       }
+
+                       if ( s < str_ ) {
+                               WriteConsoleA( consoleOut, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr );
+                               count += nWritten;
+                       }
+               }
+               SetConsoleCP( inputCodePage );
+               SetConsoleOutputCP( outputCodePage );
+       } 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 (file)
index 0000000..d49484f
--- /dev/null
@@ -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( char const*, int );
+
+extern WinAttributes WIN_ATTR;
+
+}
+
+#endif