summaryrefslogtreecommitdiff
path: root/src/mongo/shell/linenoise.cpp
diff options
context:
space:
mode:
authorAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
committerAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
commit83957b73f9177f6e38bd5375bd93ca1f6a47188c (patch)
treef20b7d6ac9a9c64ff5bb6b5910a24abbb356b1d5 /src/mongo/shell/linenoise.cpp
parent5071d203970edd4c995493d810abe20987e76fe9 (diff)
Imported Upstream version 2.2.0upstream/2.2.0
Diffstat (limited to 'src/mongo/shell/linenoise.cpp')
-rw-r--r--src/mongo/shell/linenoise.cpp2652
1 files changed, 2652 insertions, 0 deletions
diff --git a/src/mongo/shell/linenoise.cpp b/src/mongo/shell/linenoise.cpp
new file mode 100644
index 00000000000..2b77514bce2
--- /dev/null
+++ b/src/mongo/shell/linenoise.cpp
@@ -0,0 +1,2652 @@
+/* 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, 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.
+ *
+ * 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
+ *
+ */
+
+#ifdef _WIN32
+
+#include <conio.h>
+#include <windows.h>
+#include <io.h>
+#define snprintf _snprintf // Microsoft headers use underscores in some names
+#define strcasecmp _stricmp
+#define strdup _strdup
+#define isatty _isatty
+#define write _write
+#define STDIN_FILENO 0
+
+#else /* _WIN32 */
+
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <cctype>
+#include <wctype.h>
+
+#endif /* _WIN32 */
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "linenoise.h"
+#include "linenoise_utf8.h"
+#include "mk_wcwidth.h"
+#include <string>
+#include <vector>
+#include <boost/smart_ptr/scoped_array.hpp>
+
+using std::string;
+using std::vector;
+
+using boost::scoped_array;
+
+using linenoise_utf8::UChar8;
+using linenoise_utf8::UChar32;
+using linenoise_utf8::copyString8to32;
+using linenoise_utf8::copyString32;
+using linenoise_utf8::copyString32to8;
+using linenoise_utf8::strlen32;
+using linenoise_utf8::strncmp32;
+using linenoise_utf8::write32;
+using linenoise_utf8::Utf8String;
+using linenoise_utf8::Utf32String;
+
+struct linenoiseCompletions {
+ vector<Utf32String> completionStrings;
+};
+
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
+#define LINENOISE_MAX_LINE 4096
+
+// make control-characters more readable
+#define ctrlChar( upperCaseASCII ) ( upperCaseASCII - 0x40 )
+
+/**
+ * Recompute widths of all characters in a UChar32 buffer
+ * @param text input buffer of Unicode characters
+ * @param widths output buffer of character widths
+ * @param charCount number of characters in buffer
+ */
+static void recomputeCharacterWidths( const UChar32* 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)
+ */
+static void calculateScreenPosition( 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;
+ }
+}
+
+static bool isControlChar( UChar32 testChar ) {
+ return ( testChar < ' ' ) || // C0 controls
+ ( testChar >= 0x7F && testChar <= 0x9F ); // DEL and C1 controls
+}
+
+struct PromptBase { // a convenience struct for grouping prompt info
+ Utf32String promptText; // our copy of the prompt text, edited
+ char* promptCharWidths; // character widths from mk_wcwidth()
+ int promptChars; // chars in promptText
+ int promptExtraLines; // extra lines (beyond 1) occupied by prompt
+ int promptIndentation; // column offset to end of prompt
+ int promptLastLinePosition; // index into promptText where last line begins
+ int promptPreviousInputLen; // promptChars of previous input line, for clearing
+ int promptCursorRowOffset; // where the cursor is relative to the start of the prompt
+ int promptScreenColumns; // width of screen in columns
+ int promptPreviousLen; // help erasing
+ int promptErrorCode; // error code (invalid UTF-8) or zero
+
+ PromptBase() : promptPreviousInputLen( 0 ) { }
+};
+
+struct PromptInfo : public PromptBase {
+
+ PromptInfo( const UChar8* textPtr, int columns ) {
+ promptExtraLines = 0;
+ promptLastLinePosition = 0;
+ promptPreviousLen = 0;
+ promptScreenColumns = columns;
+ Utf32String tempUnicode( textPtr );
+
+ // strip control characters from the prompt -- we do allow newline
+ UChar32* pIn = tempUnicode.get();
+ UChar32* pOut = pIn;
+ while ( *pIn ) {
+ UChar32 c = *pIn;
+ if ( '\n' == c || !isControlChar( c ) ) {
+ *pOut = c;
+ ++pOut;
+ }
+ ++pIn;
+ }
+ *pOut = 0;
+ promptChars = pOut - tempUnicode.get();
+ promptText = tempUnicode;
+
+ int x = 0;
+ for ( int i = 0; i < promptChars; ++i ) {
+ UChar32 c = promptText[i];
+ if ( '\n' == c ) {
+ x = 0;
+ ++promptExtraLines;
+ promptLastLinePosition = i + 1;
+ }
+ else {
+ ++x;
+ if ( x >= promptScreenColumns ) {
+ x = 0;
+ ++promptExtraLines;
+ promptLastLinePosition = i + 1;
+ }
+ }
+ }
+ promptIndentation = promptChars - promptLastLinePosition;
+ promptCursorRowOffset = promptExtraLines;
+ }
+};
+
+// Used with DynamicPrompt (history search)
+//
+static const Utf32String forwardSearchBasePrompt( reinterpret_cast<const UChar8*>( "(i-search)`" ) );
+static const Utf32String reverseSearchBasePrompt( reinterpret_cast<const UChar8*>( "(reverse-i-search)`" ) );
+static const Utf32String endSearchBasePrompt( reinterpret_cast<const UChar8*>( "': " ) );
+static Utf32String previousSearchText; // remembered across invocations of linenoise()
+
+// changing prompt for "(reverse-i-search)`text':" etc.
+//
+struct DynamicPrompt : public PromptBase {
+ Utf32String searchText; // text we are searching for
+ char* searchCharWidths; // character widths from mk_wcwidth()
+ int searchTextLen; // chars in searchText
+ int direction; // current search direction, 1=forward, -1=reverse
+
+ DynamicPrompt( PromptBase& pi, int initialDirection ) : searchTextLen( 0 ), direction( initialDirection ) {
+ promptScreenColumns = pi.promptScreenColumns;
+ promptCursorRowOffset = 0;
+ Utf32String emptyString( 1 );
+ searchText = emptyString;
+ const Utf32String* basePrompt = ( direction > 0 ) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
+ size_t promptStartLength = basePrompt->length();
+ promptChars = promptStartLength + endSearchBasePrompt.length();
+ promptLastLinePosition = promptChars; // TODO fix this, we are asssuming that the history prompt won't wrap (!)
+ promptPreviousLen = promptChars;
+ Utf32String tempUnicode( promptChars + 1 );
+ memcpy( tempUnicode.get(), basePrompt->get(), sizeof( UChar32 ) * promptStartLength );
+ memcpy( &tempUnicode[promptStartLength], endSearchBasePrompt.get(), sizeof( UChar32 ) * ( endSearchBasePrompt.length() + 1 ) );
+ tempUnicode.initFromBuffer();
+ promptText = tempUnicode;
+ calculateScreenPosition( 0, 0, pi.promptScreenColumns, promptChars, promptIndentation, promptExtraLines );
+ }
+
+ void updateSearchPrompt( void ) {
+ const Utf32String* basePrompt = ( direction > 0 ) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
+ size_t promptStartLength = basePrompt->length();
+ promptChars = promptStartLength + searchTextLen + endSearchBasePrompt.length();
+ Utf32String tempUnicode( promptChars + 1 );
+ memcpy( tempUnicode.get(), basePrompt->get(), sizeof( UChar32 ) * promptStartLength );
+ memcpy( &tempUnicode[promptStartLength], searchText.get(), sizeof( UChar32 ) * searchTextLen );
+ size_t endIndex = promptStartLength + searchTextLen;
+ memcpy( &tempUnicode[endIndex], endSearchBasePrompt.get(), sizeof( UChar32 ) * ( endSearchBasePrompt.length() + 1 ) );
+ tempUnicode.initFromBuffer();
+ promptText = tempUnicode;
+ }
+
+ void updateSearchText( const UChar32* textPtr ) {
+ Utf32String tempUnicode( textPtr );
+ searchTextLen = tempUnicode.chars();
+ searchText = tempUnicode;
+ updateSearchPrompt();
+ }
+};
+
+class KillRing {
+ static const int capacity = 10;
+ int size;
+ int index;
+ char indexToSlot[10];
+ vector<Utf32String> 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 UChar32* text, int textLen, bool forward ) {
+ if ( textLen == 0 ) {
+ return;
+ }
+ Utf32String killedText( text, textLen );
+ if ( lastAction == actionKill && size > 0 ) {
+ int slot = indexToSlot[0];
+ int currentLen = theRing[slot].length();
+ int resultLen = currentLen + textLen;
+ Utf32String temp( resultLen + 1 );
+ if ( forward ) {
+ memcpy( temp.get(), theRing[slot].get(), currentLen * sizeof( UChar32 ) );
+ memcpy( &temp[currentLen], killedText.get(), textLen * sizeof( UChar32 ) );
+ }
+ else {
+ memcpy( temp.get(), killedText.get(), textLen * sizeof( UChar32 ) );
+ memcpy( &temp[textLen], theRing[slot].get(), currentLen * sizeof( UChar32 ) );
+ }
+ temp[resultLen] = 0;
+ temp.initFromBuffer();
+ 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;
+ }
+ }
+
+ Utf32String* yank() {
+ return ( size > 0 ) ? &theRing[indexToSlot[index]] : 0;
+ }
+
+ Utf32String* yankPop() {
+ if ( size == 0 ) {
+ return 0;
+ }
+ ++index;
+ if ( index == size ) {
+ index = 0;
+ }
+ return &theRing[indexToSlot[index]];
+ }
+
+};
+
+class InputBuffer {
+ UChar32* buf32; // input buffer
+ char* charWidths; // character widths from mk_wcwidth()
+ int buflen; // buffer size in characters
+ int len; // length of text in input buffer
+ int pos; // character position in buffer ( 0 <= pos <= len )
+
+ void clearScreen( PromptBase& pi );
+ int incrementalHistorySearch( PromptBase& pi, int startChar );
+ int completeLine( PromptBase& pi );
+ void refreshLine( PromptBase& pi );
+
+public:
+ InputBuffer( UChar32* buffer, char* widthArray, int bufferLen ) : buf32( buffer ), charWidths( widthArray ), buflen( bufferLen - 1 ), len( 0 ), pos( 0 ) {
+ buf32[0] = 0;
+ }
+ void preloadBuffer( const UChar8* preloadText ) {
+ size_t ucharCount;
+ int errorCode;
+ copyString8to32( buf32, preloadText, buflen + 1, ucharCount, errorCode );
+ recomputeCharacterWidths( buf32, charWidths, ucharCount );
+ len = ucharCount;
+ pos = ucharCount;
+ }
+ int getInputLine( PromptBase& pi );
+ int length( void ) const { return len; }
+};
+
+// Special codes for keyboard input:
+//
+// Between Windows and the various Linux "terminal" programs, there is some
+// pretty diverse behavior in the "scan codes" and escape sequences we are
+// presented with. So ... we'll translate them all into our own pidgin
+// pseudocode, trying to stay out of the way of UTF-8 and international
+// characters. Here's the general plan.
+//
+// "User input keystrokes" (key chords, whatever) will be encoded as a single value.
+// The low 21 bits are reserved for Unicode characters. Popular function-type keys
+// get their own codes in the range 0x10200000 to (if needed) 0x1FE00000, currently
+// just arrow keys, Home, End and Delete. Keypresses with Ctrl get ORed with
+// 0x20000000, with Alt get ORed with 0x40000000. So, Ctrl+Alt+Home is encoded
+// as 0x20000000 + 0x40000000 + 0x10A00000 == 0x70A00000. To keep things complicated,
+// the Alt key is equivalent to prefixing the keystroke with ESC, so ESC followed by
+// D is treated the same as Alt + D ... we'll just use Emacs terminology and call
+// this "Meta". So, we will encode both ESC followed by D and Alt held down while D
+// is pressed the same, as Meta-D, encoded as 0x40000064.
+//
+// Here are the definitions of our component constants:
+//
+// Maximum unsigned 32-bit value = 0xFFFFFFFF; // For reference, max 32-bit value
+// Highest allocated Unicode char = 0x001FFFFF; // For reference, max Unicode value
+static const int META = 0x40000000; // Meta key combination
+static const int CTRL = 0x20000000; // Ctrl key combination
+static const int SPECIAL_KEY = 0x10000000; // Common bit for all special keys
+static const int UP_ARROW_KEY = 0x10200000; // Special keys
+static const int DOWN_ARROW_KEY = 0x10400000;
+static const int RIGHT_ARROW_KEY = 0x10600000;
+static const int LEFT_ARROW_KEY = 0x10800000;
+static const int HOME_KEY = 0x10A00000;
+static const int END_KEY = 0x10C00000;
+static const int DELETE_KEY = 0x10E00000;
+static const int PAGE_UP_KEY = 0x11000000;
+static const int PAGE_DOWN_KEY = 0x11200000;
+
+static const char* unsupported_term[] = { "dumb", "cons25", "emacs", NULL };
+static linenoiseCompletionCallback* completionCallback = NULL;
+
+#ifdef _WIN32
+static HANDLE console_in, console_out;
+static DWORD oldMode;
+static WORD oldDisplayAttribute;
+#else
+static struct termios orig_termios; /* in order to restore at exit */
+#endif
+
+static KillRing killRing;
+
+static int rawmode = 0; /* for atexit() function to check if restore is needed*/
+static int atexit_registered = 0; /* register atexit just 1 time */
+static int historyMaxLen = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
+static int historyLen = 0;
+static int historyIndex = 0;
+static UChar8** history = NULL;
+
+// used to emulate Windows command prompt on down-arrow after a recall
+// we use -2 as our "not set" value because we add 1 to the previous index on down-arrow,
+// and zero is a valid index (so -1 is a valid "previous index")
+static int historyPreviousIndex = -2;
+static bool historyRecallMostRecent = false;
+
+static void linenoiseAtExit( void );
+
+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;
+}
+
+static void beep() {
+ fprintf( stderr, "\x7" ); // ctrl-G == bell/beep
+ fflush( stderr );
+}
+
+void linenoiseHistoryFree( void ) {
+ if ( history ) {
+ for ( int j = 0; j < historyLen; ++j )
+ free( history[j] );
+ historyLen = 0;
+ free( history );
+ history = 0;
+ }
+}
+
+static int enableRawMode( void ) {
+#ifdef _WIN32
+ if ( ! console_in ) {
+ console_in = GetStdHandle( STD_INPUT_HANDLE );
+ console_out = GetStdHandle( STD_OUTPUT_HANDLE );
+
+ GetConsoleMode( console_in, &oldMode );
+ SetConsoleMode( console_in, oldMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) );
+ }
+ return 0;
+#else
+ struct termios raw;
+
+ if ( ! isatty( 0 ) ) goto fatal;
+ if ( ! atexit_registered ) {
+ atexit( linenoiseAtExit );
+ atexit_registered = 1;
+ }
+ if ( tcgetattr( 0, &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 */
+ // 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 ) goto fatal;
+ rawmode = 1;
+ return 0;
+
+fatal:
+ errno = ENOTTY;
+ return -1;
+#endif
+}
+
+static void disableRawMode( void ) {
+#ifdef _WIN32
+ SetConsoleMode( console_in, oldMode );
+ console_in = 0;
+ console_out = 0;
+#else
+ if ( rawmode && tcsetattr ( 0, TCSADRAIN, &orig_termios ) != -1 )
+ rawmode = 0;
+#endif
+}
+
+// At exit we'll try to fix the terminal to the initial conditions
+static void linenoiseAtExit( void ) {
+ disableRawMode();
+}
+
+static int getScreenColumns( void ) {
+ int cols;
+#ifdef _WIN32
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &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;
+}
+
+static int getScreenRows( void ) {
+ int rows;
+#ifdef _WIN32
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &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;
+}
+
+static void setDisplayAttribute( bool enhancedDisplay ) {
+#ifdef _WIN32
+ if ( enhancedDisplay ) {
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo( console_out, &inf );
+ oldDisplayAttribute = inf.wAttributes;
+ BYTE oldLowByte = oldDisplayAttribute & 0xFF;
+ BYTE newLowByte;
+ switch ( oldLowByte ) {
+ case 0x07:
+ //newLowByte = FOREGROUND_BLUE | FOREGROUND_INTENSITY; // too dim
+ //newLowByte = FOREGROUND_BLUE; // even dimmer
+ newLowByte = FOREGROUND_BLUE | FOREGROUND_GREEN; // most similar to xterm appearance
+ break;
+ case 0x70:
+ newLowByte = BACKGROUND_BLUE | BACKGROUND_INTENSITY;
+ break;
+ default:
+ newLowByte = oldLowByte ^ 0xFF; // default to inverse video
+ break;
+ }
+ inf.wAttributes = ( inf.wAttributes & 0xFF00 ) | newLowByte;
+ SetConsoleTextAttribute( console_out, inf.wAttributes );
+ }
+ else {
+ SetConsoleTextAttribute( console_out, oldDisplayAttribute );
+ }
+#else
+ if ( enhancedDisplay ) {
+ if ( write( 1, "\x1b[1;34m", 7 ) == -1 ) return; /* bright blue (visible with both B&W bg) */
+ }
+ else {
+ if ( write( 1, "\x1b[0m", 4 ) == -1 ) return; /* reset */
+ }
+#endif
+}
+
+/**
+ * Display the dynamic incremental search prompt and the current user input line.
+ * @param pi PromptBase 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)
+ */
+static void dynamicRefresh( PromptBase& pi, UChar32* buf32, int len, int pos ) {
+
+ // calculate the position of the end of the prompt
+ int xEndOfPrompt, yEndOfPrompt;
+ calculateScreenPosition( 0, 0, pi.promptScreenColumns, pi.promptChars, xEndOfPrompt, yEndOfPrompt );
+ pi.promptIndentation = xEndOfPrompt;
+
+ // calculate the position of the end of the input line
+ int xEndOfInput, yEndOfInput;
+ calculateScreenPosition( xEndOfPrompt, yEndOfPrompt, pi.promptScreenColumns, len, xEndOfInput, yEndOfInput );
+
+ // calculate the desired position of the cursor
+ int xCursorPos, yCursorPos;
+ calculateScreenPosition( xEndOfPrompt, yEndOfPrompt, pi.promptScreenColumns, pos, xCursorPos, yCursorPos );
+
+#ifdef _WIN32
+ // position at the start of the prompt, clear to end of previous input
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo( console_out, &inf );
+ inf.dwCursorPosition.X = 0;
+ inf.dwCursorPosition.Y -= pi.promptCursorRowOffset /*- pi.promptExtraLines*/;
+ SetConsoleCursorPosition( console_out, inf.dwCursorPosition );
+ DWORD count;
+ FillConsoleOutputCharacterA( console_out, ' ', pi.promptPreviousLen + pi.promptPreviousInputLen, inf.dwCursorPosition, &count );
+ pi.promptPreviousLen = pi.promptIndentation;
+ pi.promptPreviousInputLen = len;
+
+ // display the prompt
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return;
+
+ // display the input line
+ if ( write32( 1, buf32, len ) == -1 ) return;
+
+ // position the cursor
+ GetConsoleScreenBufferInfo( console_out, &inf );
+ inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32
+ inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos;
+ SetConsoleCursorPosition( console_out, inf.dwCursorPosition );
+#else // _WIN32
+ char seq[64];
+ int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines;
+ if ( cursorRowMovement > 0 ) { // move the cursor up as required
+ snprintf( seq, sizeof seq, "\x1b[%dA", cursorRowMovement );
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+ }
+ // position at the start of the prompt, clear to end of screen
+ snprintf( seq, sizeof seq, "\x1b[1G\x1b[J" ); // 1-based on VT100
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+
+ // display the prompt
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return;
+
+ // display the input line
+ if ( write32( 1, buf32, len ) == -1 ) return;
+
+ // we have to generate our own newline on line wrap
+ if ( xEndOfInput == 0 && yEndOfInput > 0 )
+ if ( write( 1, "\n", 1 ) == -1 ) return;
+
+ // position the cursor
+ cursorRowMovement = yEndOfInput - yCursorPos;
+ if ( cursorRowMovement > 0 ) { // move the cursor up as required
+ snprintf( seq, sizeof seq, "\x1b[%dA", cursorRowMovement );
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+ }
+ // position the cursor within the line
+ snprintf( seq, sizeof seq, "\x1b[%dG", xCursorPos + 1 ); // 1-based on VT100
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+#endif
+
+ pi.promptCursorRowOffset = pi.promptExtraLines + yCursorPos; // remember row for next pass
+}
+
+/**
+ * Refresh the user's input line: the prompt is already onscreen and is not redrawn here
+ * @param pi PromptBase struct holding information about the prompt and our screen position
+ */
+void InputBuffer::refreshLine( PromptBase& pi ) {
+
+ // check for a matching brace/bracket/paren, remember its position if found
+ int highlight = -1;
+ if ( pos < len ) {
+ /* this scans for a brace matching buf32[pos] to highlight */
+ int scanDirection = 0;
+ if ( strchr( "}])", buf32[pos] ) )
+ scanDirection = -1; /* backwards */
+ else if ( strchr( "{[(", buf32[pos] ) )
+ scanDirection = 1; /* forwards */
+
+ if ( scanDirection ) {
+ int unmatched = scanDirection;
+ for ( int i = pos + scanDirection; i >= 0 && i < len; i += scanDirection ) {
+ /* TODO: the right thing when inside a string */
+ if ( strchr( "}])", buf32[i] ) )
+ --unmatched;
+ else if ( strchr( "{[(", buf32[i] ) )
+ ++unmatched;
+
+ if ( unmatched == 0 ) {
+ highlight = i;
+ break;
+ }
+ }
+ }
+ }
+
+ // calculate the position of the end of the input line
+ int xEndOfInput, yEndOfInput;
+ calculateScreenPosition( pi.promptIndentation, 0, pi.promptScreenColumns, len, xEndOfInput, yEndOfInput );
+
+ // calculate the desired position of the cursor
+ int xCursorPos, yCursorPos;
+ calculateScreenPosition( pi.promptIndentation, 0, pi.promptScreenColumns, pos, xCursorPos, yCursorPos );
+
+#ifdef _WIN32
+ // position at the end of the prompt, clear to end of previous input
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo( console_out, &inf );
+ inf.dwCursorPosition.X = pi.promptIndentation; // 0-based on Win32
+ inf.dwCursorPosition.Y -= pi.promptCursorRowOffset - pi.promptExtraLines;
+ SetConsoleCursorPosition( console_out, inf.dwCursorPosition );
+ DWORD count;
+ if ( len < pi.promptPreviousInputLen )
+ FillConsoleOutputCharacterA( console_out, ' ', pi.promptPreviousInputLen, inf.dwCursorPosition, &count );
+ pi.promptPreviousInputLen = len;
+
+ // display the input line
+ if (highlight == -1) {
+ if ( write32( 1, buf32, len ) == -1 ) return;
+ }
+ else {
+ if (write32( 1, buf32, highlight ) == -1 ) return;
+ setDisplayAttribute( true ); /* bright blue (visible with both B&W bg) */
+ if ( write32( 1, &buf32[highlight], 1 ) == -1 ) return;
+ setDisplayAttribute( false );
+ if ( write32( 1, buf32 + highlight + 1, len - highlight - 1 ) == -1 ) return;
+ }
+
+ // position the cursor
+ GetConsoleScreenBufferInfo( console_out, &inf );
+ inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32
+ inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos;
+ SetConsoleCursorPosition( console_out, inf.dwCursorPosition );
+#else // _WIN32
+ char seq[64];
+ int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines;
+ if ( cursorRowMovement > 0 ) { // move the cursor up as required
+ snprintf( seq, sizeof seq, "\x1b[%dA", cursorRowMovement );
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+ }
+ // position at the end of the prompt, clear to end of screen
+ snprintf( seq, sizeof seq, "\x1b[%dG\x1b[J", pi.promptIndentation + 1 ); // 1-based on VT100
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+
+ if ( highlight == -1 ) { // write unhighlighted text
+ if ( write32( 1, buf32, len ) == -1 ) return;
+ }
+ else { // highlight the matching brace/bracket/parenthesis
+ if ( write32( 1, buf32, highlight ) == -1 ) return;
+ setDisplayAttribute( true );
+ if ( write32( 1, &buf32[highlight], 1 ) == -1 ) return;
+ setDisplayAttribute( false );
+ if ( write32( 1, buf32 + highlight + 1, len - highlight - 1 ) == -1 ) return;
+ }
+
+ // we have to generate our own newline on line wrap
+ if ( xEndOfInput == 0 && yEndOfInput > 0 )
+ if ( write( 1, "\n", 1 ) == -1 ) return;
+
+ // position the cursor
+ cursorRowMovement = yEndOfInput - yCursorPos;
+ if ( cursorRowMovement > 0 ) { // move the cursor up as required
+ snprintf( seq, sizeof seq, "\x1b[%dA", cursorRowMovement );
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+ }
+ // position the cursor within the line
+ snprintf( seq, sizeof seq, "\x1b[%dG", xCursorPos + 1 ); // 1-based on VT100
+ if ( write( 1, seq, strlen( seq ) ) == -1 ) return;
+#endif
+
+ pi.promptCursorRowOffset = pi.promptExtraLines + yCursorPos; // remember row for next pass
+}
+
+#ifndef _WIN32
+
+/**
+ * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode (UChar32) character it encodes
+ *
+ * @return UChar32 Unicode character
+ */
+static UChar32 readUnicodeCharacter( void ) {
+ static UChar8 utf8String[5];
+ static size_t utf8Count = 0;
+ while ( true ) {
+ UChar8 c;
+ if ( read( 0, &c, 1 ) <= 0 ) return 0;
+ if ( c <= 0x7F ) { // short circuit ASCII
+ utf8Count = 0;
+ return c;
+ }
+ else if ( utf8Count < sizeof( utf8String ) - 1 ) {
+ utf8String[ utf8Count++ ] = c;
+ utf8String[ utf8Count ] = 0;
+ UChar32 unicodeChar[2];
+ size_t ucharCount;
+ int errorCode;
+ copyString8to32( unicodeChar, utf8String, 2, ucharCount, errorCode );
+ if ( ucharCount && errorCode == 0 ) {
+ utf8Count = 0;
+ return unicodeChar[0];
+ }
+ }
+ else {
+ utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
+ }
+ }
+}
+
+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 linenoise.
+//
+// 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.
+
+// 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 UChar32 ( *CharacterDispatchRoutine )( UChar32 );
+
+// 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
+};
+
+// 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.
+//
+static UChar32 doDispatch( UChar32 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 );
+}
+
+static UChar32 thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers
+
+// Final dispatch routines -- return something
+//
+static UChar32 normalKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | c; }
+static UChar32 upArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | UP_ARROW_KEY; }
+static UChar32 downArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | DOWN_ARROW_KEY; }
+static UChar32 rightArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | RIGHT_ARROW_KEY; }
+static UChar32 leftArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | LEFT_ARROW_KEY; }
+static UChar32 homeKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | HOME_KEY; }
+static UChar32 endKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | END_KEY; }
+static UChar32 pageUpKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | PAGE_UP_KEY; }
+static UChar32 pageDownKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | PAGE_DOWN_KEY; }
+static UChar32 deleteCharRoutine( UChar32 c ) { return thisKeyMetaCtrl | ctrlChar( 'H' ); } // key labeled Backspace
+static UChar32 deleteKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | DELETE_KEY; } // key labeled Delete
+static UChar32 ctrlUpArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | CTRL | UP_ARROW_KEY; }
+static UChar32 ctrlDownArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | CTRL | DOWN_ARROW_KEY; }
+static UChar32 ctrlRightArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | CTRL | RIGHT_ARROW_KEY; }
+static UChar32 ctrlLeftArrowKeyRoutine( UChar32 c ) { return thisKeyMetaCtrl | CTRL | LEFT_ARROW_KEY; }
+static UChar32 escFailureRoutine( UChar32 c ) { beep(); return -1; }
+
+// Handle ESC [ 1 ; 3 (or 5) <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket1Semicolon3or5Routines[] = {
+ upArrowKeyRoutine,
+ downArrowKeyRoutine,
+ rightArrowKeyRoutine,
+ leftArrowKeyRoutine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket1Semicolon3or5Dispatch = { 4, "ABCD", escLeftBracket1Semicolon3or5Routines };
+
+// Handle ESC [ 1 ; <more stuff> escape sequences
+//
+static UChar32 escLeftBracket1Semicolon3Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ thisKeyMetaCtrl |= META;
+ return doDispatch( c, escLeftBracket1Semicolon3or5Dispatch );
+}
+static UChar32 escLeftBracket1Semicolon5Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ thisKeyMetaCtrl |= CTRL;
+ return doDispatch( c, escLeftBracket1Semicolon3or5Dispatch );
+}
+static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = {
+ escLeftBracket1Semicolon3Routine,
+ escLeftBracket1Semicolon5Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket1SemicolonDispatch = { 2, "35", escLeftBracket1SemicolonRoutines };
+
+// Handle ESC [ 1 <more stuff> escape sequences
+//
+static UChar32 escLeftBracket1SemicolonRoutine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket1SemicolonDispatch );
+}
+static CharacterDispatchRoutine escLeftBracket1Routines[] = {
+ homeKeyRoutine,
+ escLeftBracket1SemicolonRoutine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket1Dispatch = { 2, "~;", escLeftBracket1Routines };
+
+// 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 escLeftBracket5Routines[] = {
+ pageUpKeyRoutine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket5Dispatch = { 1, "~", escLeftBracket5Routines };
+
+// Handle ESC [ 6 <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracket6Routines[] = {
+ pageDownKeyRoutine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracket6Dispatch = { 1, "~", 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 UChar32 escLeftBracket0Routine( UChar32 c ) {
+ return escFailureRoutine( c );
+}
+static UChar32 escLeftBracket1Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket1Dispatch );
+}
+static UChar32 escLeftBracket2Routine( UChar32 c ) {
+ return escFailureRoutine( c ); // Insert key, unused
+}
+static UChar32 escLeftBracket3Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket3Dispatch );
+}
+static UChar32 escLeftBracket4Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket4Dispatch );
+}
+static UChar32 escLeftBracket5Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket5Dispatch );
+}
+static UChar32 escLeftBracket6Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket6Dispatch );
+}
+static UChar32 escLeftBracket7Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket7Dispatch );
+}
+static UChar32 escLeftBracket8Routine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracket8Dispatch );
+}
+static UChar32 escLeftBracket9Routine( UChar32 c ) {
+ return escFailureRoutine( c );
+}
+
+// Handle ESC [ <more stuff> escape sequences
+//
+static CharacterDispatchRoutine escLeftBracketRoutines[] = {
+ upArrowKeyRoutine,
+ downArrowKeyRoutine,
+ rightArrowKeyRoutine,
+ leftArrowKeyRoutine,
+ homeKeyRoutine,
+ endKeyRoutine,
+ escLeftBracket0Routine,
+ escLeftBracket1Routine,
+ escLeftBracket2Routine,
+ escLeftBracket3Routine,
+ escLeftBracket4Routine,
+ escLeftBracket5Routine,
+ escLeftBracket6Routine,
+ escLeftBracket7Routine,
+ escLeftBracket8Routine,
+ escLeftBracket9Routine,
+ escFailureRoutine
+};
+static CharacterDispatch escLeftBracketDispatch = { 16, "ABCDHF0123456789", escLeftBracketRoutines };
+
+// Handle ESC O <char> escape sequences
+//
+static CharacterDispatchRoutine escORoutines[] = {
+ upArrowKeyRoutine,
+ downArrowKeyRoutine,
+ rightArrowKeyRoutine,
+ leftArrowKeyRoutine,
+ homeKeyRoutine,
+ endKeyRoutine,
+ ctrlUpArrowKeyRoutine,
+ ctrlDownArrowKeyRoutine,
+ ctrlRightArrowKeyRoutine,
+ ctrlLeftArrowKeyRoutine,
+ escFailureRoutine
+};
+static CharacterDispatch escODispatch = { 10, "ABCDHFabcd", escORoutines };
+
+// Initial ESC dispatch -- could be a Meta prefix or the start of an escape sequence
+//
+static UChar32 escLeftBracketRoutine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escLeftBracketDispatch );
+}
+static UChar32 escORoutine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escODispatch );
+}
+static UChar32 setMetaRoutine( UChar32 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 UChar32 escRoutine( UChar32 c ) {
+ c = readUnicodeCharacter();
+ 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 UChar32 setMetaRoutine( UChar32 c ) {
+ thisKeyMetaCtrl = META;
+ if ( c == 0x1B ) { // another ESC, stay in ESC processing mode
+ c = readUnicodeCharacter();
+ if ( c == 0 ) return 0;
+ return doDispatch( c, escDispatch );
+ }
+ return doDispatch( c, initialDispatch );
+}
+
+} // namespace EscapeSequenceProcessing // move these out of global namespace
+
+#endif // #ifndef _WIN32
+
+// linenoiseReadChar -- 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".
+//
+static UChar32 linenoiseReadChar( void ) {
+#ifdef _WIN32
+
+ INPUT_RECORD rec;
+ DWORD count;
+ int modifierKeys = 0;
+ bool escSeen = false;
+ while ( true ) {
+ ReadConsoleInputW( console_in, &rec, 1, &count );
+#if 0 // 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 |= CTRL;
+ }
+ if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) {
+ modifierKeys |= META;
+ }
+ if ( escSeen ) {
+ modifierKeys |= META;
+ }
+ if ( rec.Event.KeyEvent.uChar.UnicodeChar == 0 ) {
+ switch ( rec.Event.KeyEvent.wVirtualKeyCode ) {
+ case VK_LEFT: return modifierKeys | LEFT_ARROW_KEY;
+ case VK_RIGHT: return modifierKeys | RIGHT_ARROW_KEY;
+ case VK_UP: return modifierKeys | UP_ARROW_KEY;
+ case VK_DOWN: return modifierKeys | DOWN_ARROW_KEY;
+ case VK_DELETE: return modifierKeys | DELETE_KEY;
+ case VK_HOME: return modifierKeys | HOME_KEY;
+ case VK_END: return modifierKeys | END_KEY;
+ case VK_PRIOR: return modifierKeys | PAGE_UP_KEY;
+ case VK_NEXT: return modifierKeys | PAGE_DOWN_KEY;
+ default: continue; // in raw mode, ReadConsoleInput shows shift, ctrl ...
+ } // ... ignore them
+ }
+ else if ( rec.Event.KeyEvent.uChar.UnicodeChar == ctrlChar( '[' ) ) { // ESC, set flag for later
+ escSeen = true;
+ continue;
+ }
+ else {
+ // we got a real character, return it
+ return modifierKeys | rec.Event.KeyEvent.uChar.UnicodeChar;
+ }
+ }
+
+#else
+ UChar32 c;
+ c = readUnicodeCharacter();
+ 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.
+ //
+#define _DEBUG_LINUX_KEYBOARD
+#if defined(_DEBUG_LINUX_KEYBOARD)
+ 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 ) {
+ UChar32 key = static_cast<UChar32>( keys[i] );
+ char* friendlyTextPtr;
+ char friendlyTextBuf[10];
+ const char* prefixText = (key < 0x80) ? "" : "0x80+";
+ UChar32 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 // _DEBUG_LINUX_KEYBOARD
+
+ EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch
+ return EscapeSequenceProcessing::doDispatch( c, EscapeSequenceProcessing::initialDispatch );
+#endif // #_WIN32
+}
+
+/**
+ * Free memory used in a recent command completion session
+ *
+ * @param lc pointer to a linenoiseCompletions struct
+ */
+static void freeCompletions( linenoiseCompletions* lc ) {
+ lc->completionStrings.clear();
+}
+
+/**
+ * convert {CTRL + 'A'}, {CTRL + 'a'} and {CTRL + ctrlChar( 'A' )} into ctrlChar( 'A' )
+ * leave META alone
+ *
+ * @param c character to clean up
+ * @return cleaned-up character
+ */
+static int cleanupCtrl( int c ) {
+ if ( c & CTRL ) {
+ int d = c & 0x1FF;
+ if ( d >= 'a' && d <= 'z' ) {
+ c = ( c + ( 'a' - ctrlChar( 'A' ) ) ) & ~CTRL;
+ }
+ if ( d >= 'A' && d <= 'Z' ) {
+ c = ( c + ( 'A' - ctrlChar( 'A' ) ) ) & ~CTRL;
+ }
+ if ( d >= ctrlChar( 'A' ) && d <= ctrlChar( 'Z' ) ) {
+ c = c & ~CTRL;
+ }
+ }
+ return c;
+}
+
+// break characters that may precede items to be completed
+static const char breakChars[] = " =+-/\\*?\"'`&<>;|@{([])}";
+
+// maximum number of completions to display without asking
+static const size_t completionCountCutoff = 100;
+
+/**
+ * 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 PromptBase struct holding information about the prompt and our screen position
+ */
+int InputBuffer::completeLine( PromptBase& pi ) {
+ linenoiseCompletions lc;
+ char 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.
+ int startIndex = pos;
+ while ( --startIndex >= 0 ) {
+ if ( strchr( breakChars, buf32[startIndex] ) ) {
+ break;
+ }
+ }
+ ++startIndex;
+ int itemLength = pos - startIndex;
+ Utf32String unicodeCopy( &buf32[startIndex], itemLength );
+ Utf8String parseItem( unicodeCopy );
+
+ // get a list of completions
+ completionCallback( reinterpret_cast< char * >( parseItem.get() ), &lc );
+
+ // if no completions, we are done
+ if ( lc.completionStrings.size() == 0 ) {
+ beep();
+ freeCompletions( &lc );
+ return 0;
+ }
+
+ // at least one completion
+ int longestCommonPrefix = 0;
+ int displayLength = 0;
+ if ( lc.completionStrings.size() == 1 ) {
+ longestCommonPrefix = lc.completionStrings[0].length();
+ }
+ else {
+ bool keepGoing = true;
+ while ( keepGoing ) {
+ for ( size_t j = 0; j < lc.completionStrings.size() - 1; ++j ) {
+ char c1 = lc.completionStrings[j][longestCommonPrefix];
+ char c2 = lc.completionStrings[j + 1][longestCommonPrefix];
+ if ( ( 0 == c1 ) || ( 0 == c2 ) || ( c1 != c2 ) ) {
+ keepGoing = false;
+ break;
+ }
+ }
+ if ( keepGoing ) {
+ ++longestCommonPrefix;
+ }
+ }
+ }
+ if ( lc.completionStrings.size() != 1 ) { // beep if ambiguous
+ beep();
+ }
+
+ // if we can extend the item, extend it and return to main loop
+ if ( longestCommonPrefix > itemLength ) {
+ displayLength = len + longestCommonPrefix - itemLength;
+ if ( displayLength > buflen ) {
+ longestCommonPrefix -= displayLength - buflen; // don't overflow buffer
+ displayLength = buflen; // truncate the insertion
+ beep(); // and make a noise
+ }
+ Utf32String displayText( displayLength + 1 );
+ memcpy( displayText.get(), buf32, sizeof( UChar32 ) * startIndex );
+ memcpy( &displayText[startIndex], &lc.completionStrings[0][0], sizeof( UChar32 ) * longestCommonPrefix );
+ int tailIndex = startIndex + longestCommonPrefix;
+ memcpy( &displayText[tailIndex], &buf32[pos], sizeof( UChar32 ) * ( displayLength - tailIndex + 1 ) );
+ copyString32( buf32, displayText.get(), buflen + 1 );
+ pos = startIndex + longestCommonPrefix;
+ len = displayLength;
+ refreshLine( pi );
+ return 0;
+ }
+
+ // we can't complete any further, wait for second tab
+ do {
+ c = linenoiseReadChar();
+ c = cleanupCtrl( c );
+ } while ( c == static_cast<char>( -1 ) );
+
+ // if any character other than tab, pass it to the main loop
+ if ( c != ctrlChar( 'I' ) ) {
+ freeCompletions( &lc );
+ return c;
+ }
+
+ // we got a second tab, maybe show list of possible completions
+ bool showCompletions = true;
+ bool onNewLine = false;
+ if ( lc.completionStrings.size() > completionCountCutoff ) {
+ int savePos = pos; // move cursor to EOL to avoid overwriting the command line
+ pos = len;
+ refreshLine( pi );
+ pos = savePos;
+ printf( "\nDisplay all %u possibilities? (y or n)",
+ static_cast<unsigned int>( lc.completionStrings.size() ) );
+ fflush( stdout );
+ onNewLine = true;
+ while ( c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != ctrlChar( 'C' ) ) {
+ do {
+ c = linenoiseReadChar();
+ c = cleanupCtrl( c );
+ } while ( c == static_cast<char>( -1 ) );
+ }
+ switch ( c ) {
+ case 'n':
+ case 'N':
+ showCompletions = false;
+ freeCompletions( &lc );
+ break;
+ case ctrlChar( 'C' ):
+ showCompletions = false;
+ freeCompletions( &lc );
+ if ( write( 1, "^C", 2 ) == -1 ) return -1; // Display the ^C we got
+ 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 < lc.completionStrings.size(); ++j) {
+ itemLength = lc.completionStrings[j].length();
+ if ( itemLength > longestCompletion ) {
+ longestCompletion = itemLength;
+ }
+ }
+ longestCompletion += 2;
+ int columnCount = pi.promptScreenColumns / 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 = len;
+ refreshLine( pi );
+ pos = savePos;
+ }
+ size_t pauseRow = getScreenRows() - 1;
+ size_t rowCount = ( lc.completionStrings.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 != '\r' && c != '\n' && c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != 'q' && c != 'Q' && c != ctrlChar( 'C' ) ) {
+ if ( doBeep ) {
+ beep();
+ }
+ doBeep = true;
+ do {
+ c = linenoiseReadChar();
+ c = cleanupCtrl( c );
+ } while ( c == static_cast<char>( -1 ) );
+ }
+ switch ( c ) {
+ case ' ':
+ case 'y':
+ case 'Y':
+ printf( "\r \r" );
+ pauseRow += getScreenRows() - 1;
+ break;
+ case '\r':
+ case '\n':
+ printf( "\r \r" );
+ ++pauseRow;
+ break;
+ case 'n':
+ case 'N':
+ case 'q':
+ case 'Q':
+ printf( "\r \r" );
+ stopList = true;
+ break;
+ case ctrlChar( 'C' ):
+ if ( write( 1, "^C", 2 ) == -1 ) return -1; // Display the ^C we got
+ stopList = true;
+ break;
+ }
+ }
+ else {
+ printf( "\n" );
+ }
+ if ( stopList ) {
+ break;
+ }
+ for ( int column = 0; column < columnCount; ++column ) {
+ size_t index = ( column * rowCount ) + row;
+ if ( index < lc.completionStrings.size() ) {
+ itemLength = lc.completionStrings[index].length();
+ fflush( stdout );
+ if ( write32( 1, lc.completionStrings[index].get(), itemLength ) == -1 ) return -1;
+ if ( ( ( column + 1 ) * rowCount ) + row < lc.completionStrings.size() ) {
+ for ( int k = itemLength; k < longestCompletion; ++k ) {
+ printf( " " );
+ }
+ }
+ }
+ }
+ }
+ fflush( stdout );
+ freeCompletions( &lc );
+ }
+
+ // display the prompt on a new line, then redisplay the input buffer
+ if ( ! stopList || c == ctrlChar( 'C' ) ) {
+ if ( write( 1, "\n", 1 ) == -1 ) return 0;
+ }
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return 0;
+#ifndef _WIN32
+ // we have to generate our own newline on line wrap on Linux
+ if ( pi.promptIndentation == 0 && pi.promptExtraLines > 0 )
+ if ( write( 1, "\n", 1 ) == -1 ) return 0;
+#endif
+ pi.promptCursorRowOffset = pi.promptExtraLines;
+ refreshLine( pi );
+ return 0;
+}
+
+/**
+ * Clear the screen ONLY (no redisplay of anything)
+ */
+void linenoiseClearScreen( void ) {
+#ifdef _WIN32
+ COORD coord = {0, 0};
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ HANDLE screenHandle = GetStdHandle( STD_OUTPUT_HANDLE );
+ GetConsoleScreenBufferInfo( screenHandle, &inf );
+ SetConsoleCursorPosition( screenHandle, coord );
+ DWORD count;
+ FillConsoleOutputCharacterA( screenHandle, ' ', inf.dwSize.X * inf.dwSize.Y, coord, &count );
+#else
+ if ( write( 1, "\x1b[H\x1b[2J", 7 ) <= 0 ) return;
+#endif
+}
+
+void InputBuffer::clearScreen( PromptBase& pi ) {
+ linenoiseClearScreen();
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return;
+#ifndef _WIN32
+ // we have to generate our own newline on line wrap on Linux
+ if ( pi.promptIndentation == 0 && pi.promptExtraLines > 0 )
+ if ( write( 1, "\n", 1 ) == -1 ) return;
+#endif
+ pi.promptCursorRowOffset = pi.promptExtraLines;
+ refreshLine( pi );
+}
+
+/**
+ * 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 or
+ * editing) or cancels.
+ * @param pi PromptBase struct holding information about the (old, static) prompt and our screen position
+ * @param startChar the character that began the search, used to set the initial direction
+ */
+int InputBuffer::incrementalHistorySearch( PromptBase& pi, int startChar ) {
+ size_t bufferSize;
+ size_t ucharCount;
+ int errorCode;
+
+ // if not already recalling, add the current line to the history list so we don't have to special case it
+ if ( historyIndex == historyLen - 1 ) {
+ free( history[historyLen - 1] );
+ bufferSize = sizeof( UChar32 ) * len + 1;
+ scoped_array< UChar8 > tempBuffer( new UChar8[ bufferSize ] );
+ copyString32to8( tempBuffer.get(), buf32, bufferSize );
+ history[ historyLen - 1 ] = reinterpret_cast< UChar8 * >( strdup( reinterpret_cast< const char * >( tempBuffer.get() ) ) );
+ }
+ int historyLineLength = len;
+ int historyLinePosition = pos;
+ UChar32 emptyBuffer[1];
+ char emptyWidths[1];
+ InputBuffer empty( emptyBuffer, emptyWidths, 1 );
+ empty.refreshLine( pi ); // erase the old input first
+ DynamicPrompt dp( pi, ( startChar == ctrlChar( 'R' ) ) ? -1 : 1 );
+
+ dp.promptPreviousLen = pi.promptPreviousLen;
+ dp.promptPreviousInputLen = pi.promptPreviousInputLen;
+ dynamicRefresh( dp, buf32, historyLineLength, historyLinePosition ); // draw user's text with our prompt
+
+ // loop until we get an exit character
+ int c;
+ bool keepLooping = true;
+ bool useSearchedLine = true;
+ bool searchAgain = false;
+ UChar32* activeHistoryLine = 0;
+ while ( keepLooping ) {
+ c = linenoiseReadChar();
+ c = cleanupCtrl( c ); // convert CTRL + <char> into normal ctrl
+
+ switch ( c ) {
+
+ // these characters keep the selected text but do not execute it
+ case ctrlChar( 'A' ): // ctrl-A, move cursor to start of line
+ case HOME_KEY:
+ case ctrlChar( 'B' ): // ctrl-B, move cursor left by one character
+ case LEFT_ARROW_KEY:
+ case META + 'b': // meta-B, move cursor left by one word
+ case META + 'B':
+ case CTRL + LEFT_ARROW_KEY:
+ case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
+ case ctrlChar( 'D' ):
+ case META + 'd': // meta-D, kill word to right of cursor
+ case META + 'D':
+ case ctrlChar( 'E' ): // ctrl-E, move cursor to end of line
+ case END_KEY:
+ case ctrlChar( 'F' ): // ctrl-F, move cursor right by one character
+ case RIGHT_ARROW_KEY:
+ case META + 'f': // meta-F, move cursor right by one word
+ case META + 'F':
+ case CTRL + RIGHT_ARROW_KEY:
+ case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
+ case META + ctrlChar( 'H' ):
+ case ctrlChar( 'J' ):
+ case ctrlChar( 'K' ): // ctrl-K, kill from cursor to end of line
+ case ctrlChar( 'M' ):
+ case ctrlChar( 'N' ): // ctrl-N, recall next line in history
+ case ctrlChar( 'P' ): // ctrl-P, recall previous line in history
+ case DOWN_ARROW_KEY:
+ case UP_ARROW_KEY:
+ case ctrlChar( 'T' ): // ctrl-T, transpose characters
+ case ctrlChar( 'U' ): // ctrl-U, kill all characters to the left of the cursor
+ case ctrlChar( 'W' ):
+ case META + 'y': // meta-Y, "yank-pop", rotate popped text
+ case META + 'Y':
+ case 127:
+ case DELETE_KEY:
+ case META + '<': // start of history
+ case PAGE_UP_KEY:
+ case META + '>': // end of history
+ case PAGE_DOWN_KEY:
+ keepLooping = false;
+ break;
+
+ // these characters revert the input line to its previous state
+ case ctrlChar( 'C' ): // ctrl-C, abort this line
+ case ctrlChar( 'G' ):
+ case ctrlChar( 'L' ): // ctrl-L, clear screen and redisplay line
+ keepLooping = false;
+ useSearchedLine = false;
+ if ( c != ctrlChar( 'L' ) ) {
+ c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else
+ }
+ break;
+
+ // these characters stay in search mode and update the display
+ case ctrlChar( 'S' ):
+ case ctrlChar( 'R' ):
+ if ( dp.searchTextLen == 0 ) { // if no current search text, recall previous text
+ if ( previousSearchText.length() ) {
+ dp.updateSearchText( previousSearchText.get() );
+ }
+ }
+ if ( ( dp.direction == 1 && c == ctrlChar( 'R' ) ) ||
+ ( dp.direction == -1 && c == ctrlChar( '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 ctrlChar( 'Z' ): // ctrl-Z, job control
+ disableRawMode(); // Returning to Linux (whatever) shell, leave raw mode
+ raise( SIGSTOP ); // Break out in mid-line
+ enableRawMode(); // Back from Linux shell, re-enter raw mode
+ {
+ bufferSize = historyLineLength + 1;
+ scoped_array< UChar32 > tempUnicode( new UChar32[ bufferSize ] );
+ copyString8to32( tempUnicode.get(), history[ historyIndex ], bufferSize, ucharCount, errorCode );
+ dynamicRefresh( dp, tempUnicode.get(), historyLineLength, historyLinePosition );
+ }
+ continue;
+ break;
+#endif
+
+ // these characters update the search string, and hence the selected input line
+ case ctrlChar( 'H' ): // backspace/ctrl-H, delete char to left of cursor
+ if ( dp.searchTextLen > 0 ) {
+ scoped_array<UChar32> tempUnicode( new UChar32[ dp.searchTextLen ] );
+ --dp.searchTextLen;
+ dp.searchText[ dp.searchTextLen ] = 0;
+ copyString32( tempUnicode.get(), dp.searchText.get(), dp.searchTextLen + 1 );
+ dp.updateSearchText( tempUnicode.get() );
+ }
+ else {
+ beep();
+ }
+ break;
+
+ case ctrlChar( 'Y' ): // ctrl-Y, yank killed text
+ break;
+
+ default:
+ if ( !isControlChar( c ) && c <= 0x0010FFFF ) { // not an action character
+ scoped_array< UChar32 > tempUnicode( new UChar32[ dp.searchTextLen + 2 ] );
+ copyString32( tempUnicode.get(), dp.searchText.get(), dp.searchTextLen + 2 );
+ tempUnicode[ dp.searchTextLen ] = c;
+ tempUnicode[ dp.searchTextLen + 1 ] = 0;
+ dp.updateSearchText( tempUnicode.get() );
+ }
+ else {
+ beep();
+ }
+ } // switch
+
+ // if we are staying in search mode, search now
+ if ( keepLooping ) {
+ bufferSize = historyLineLength + 1;
+ activeHistoryLine = new UChar32[ bufferSize ];
+ copyString8to32( activeHistoryLine, history[ historyIndex ], bufferSize, ucharCount, errorCode );
+ if ( dp.searchTextLen > 0 ) {
+ bool found = false;
+ int historySearchIndex = historyIndex;
+ int lineLength = ucharCount;
+ int lineSearchPos = historyLinePosition;
+ if ( searchAgain ) {
+ lineSearchPos += dp.direction;
+ }
+ searchAgain = false;
+ while ( true ) {
+ while ( ( dp.direction > 0 ) ? ( lineSearchPos < lineLength ) : ( lineSearchPos >= 0 ) ) {
+ if ( strncmp32( dp.searchText.get(), &activeHistoryLine[ lineSearchPos ], dp.searchTextLen) == 0 ) {
+ found = true;
+ break;
+ }
+ lineSearchPos += dp.direction;
+ }
+ if ( found ) {
+ historyIndex = historySearchIndex;
+ historyLineLength = lineLength;
+ historyLinePosition = lineSearchPos;
+ break;
+ }
+ else if ( ( dp.direction > 0 ) ? ( historySearchIndex < historyLen - 1 ) : ( historySearchIndex > 0 ) ) {
+ historySearchIndex += dp.direction;
+ bufferSize = strlen( reinterpret_cast< char* >( history[ historySearchIndex ] ) ) + 1;
+ delete [] activeHistoryLine;
+ activeHistoryLine = new UChar32[ bufferSize ];
+ copyString8to32( activeHistoryLine, history[ historySearchIndex ], bufferSize, ucharCount, errorCode );
+ lineLength = ucharCount;
+ lineSearchPos = ( dp.direction > 0 ) ? 0 : ( lineLength - dp.searchTextLen );
+ }
+ else {
+ beep();
+ break;
+ }
+ }; // while
+ }
+ if ( activeHistoryLine ) {
+ delete [] activeHistoryLine;
+ }
+ bufferSize = historyLineLength + 1;
+ activeHistoryLine = new UChar32[ bufferSize ];
+ copyString8to32( activeHistoryLine, history[ historyIndex ], bufferSize, ucharCount, errorCode );
+ dynamicRefresh( dp, activeHistoryLine, historyLineLength, historyLinePosition ); // draw user's text with our prompt
+ }
+ } // while
+
+ // leaving history search, restore previous prompt, maybe make searched line current
+ PromptBase pb;
+ pb.promptChars = pi.promptIndentation;
+ Utf32String tempUnicode( pb.promptChars + 1 );
+ copyString32( tempUnicode.get(), &pi.promptText[ pi.promptLastLinePosition ], pb.promptChars + 1 );
+ tempUnicode.initFromBuffer();
+ pb.promptText = tempUnicode;
+ pb.promptExtraLines = 0;
+ pb.promptIndentation = pi.promptIndentation;
+ pb.promptLastLinePosition = 0;
+ pb.promptPreviousInputLen = historyLineLength;
+ pb.promptCursorRowOffset = dp.promptCursorRowOffset;
+ pb.promptScreenColumns = pi.promptScreenColumns;
+ pb.promptPreviousLen = dp.promptChars;
+ if ( useSearchedLine && activeHistoryLine ) {
+ historyRecallMostRecent = true;
+ copyString32( buf32, activeHistoryLine, buflen + 1 );
+ len = historyLineLength;
+ pos = historyLinePosition;
+ }
+ if ( activeHistoryLine ) {
+ delete [] activeHistoryLine;
+ }
+ dynamicRefresh( pb, buf32, len, pos ); // redraw the original prompt with current input
+ pi.promptPreviousInputLen = len;
+ pi.promptCursorRowOffset = pi.promptExtraLines + pb.promptCursorRowOffset;
+ previousSearchText = dp.searchText; // save search text for possible reuse on ctrl-R ctrl-R
+ return c; // pass a character or -1 back to main loop
+}
+
+static bool isCharacterAlphanumeric( UChar32 testChar ) {
+ return iswalnum( testChar );
+}
+
+int InputBuffer::getInputLine( PromptBase& pi ) {
+
+ // The latest history entry is always our current buffer
+ if ( len > 0 ) {
+ size_t bufferSize = sizeof( UChar32 ) * len + 1;
+ scoped_array< char > tempBuffer( new char[ bufferSize ] );
+ copyString32to8( reinterpret_cast< UChar8 * >( tempBuffer.get() ), buf32, bufferSize );
+ linenoiseHistoryAdd( tempBuffer.get() );
+ }
+ else {
+ linenoiseHistoryAdd( "" );
+ }
+ historyIndex = historyLen - 1;
+ historyRecallMostRecent = false;
+
+ // display the prompt
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return -1;
+
+#ifndef _WIN32
+ // we have to generate our own newline on line wrap on Linux
+ if ( pi.promptIndentation == 0 && pi.promptExtraLines > 0 )
+ if ( write( 1, "\n", 1 ) == -1 ) return -1;
+#endif
+
+ // the cursor starts out at the end of the prompt
+ pi.promptCursorRowOffset = pi.promptExtraLines;
+
+ // kill and yank start in "other" mode
+ killRing.lastAction = KillRing::actionOther;
+
+ // when history search returns control to us, we execute its terminating keystroke
+ int terminatingKeystroke = -1;
+
+ // if there is already text in the buffer, display it first
+ if ( len > 0 ) {
+ refreshLine( pi );
+ }
+
+ // loop collecting characters, respond to line editing characters
+ while ( true ) {
+ int c;
+ if ( terminatingKeystroke == -1 ) {
+ c = linenoiseReadChar(); // get a new keystroke
+ }
+ else {
+ c = terminatingKeystroke; // use the terminating keystroke from search
+ terminatingKeystroke = -1; // clear it once we've used it
+ }
+ c = cleanupCtrl( c ); // convert CTRL + <char> into normal ctrl
+
+ if ( c == 0 ) {
+ return len;
+ }
+
+ if ( c == -1 ) {
+ refreshLine( pi );
+ continue;
+ }
+
+ if ( c == -2 ) {
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return -1;
+ refreshLine( pi );
+ continue;
+ }
+
+ // ctrl-I/tab, command completion, needs to be before switch statement
+ if ( c == ctrlChar( 'I' ) && completionCallback ) {
+
+ if ( pos == 0 ) // SERVER-4967 -- in earlier versions, you could paste previous output
+ continue; // back into the shell ... this output may have leading tabs.
+ // This hack (i.e. what the old code did) prevents command completion
+ // on an empty line but lets users paste text with leading tabs.
+
+ killRing.lastAction = KillRing::actionOther;
+ historyRecallMostRecent = false;
+
+ // completeLine does the actual completion and replacement
+ c = completeLine( pi );
+
+ if ( c < 0 ) // return on error
+ return len;
+
+ if ( c == 0 ) // read next character when 0
+ continue;
+
+ // deliberate fall-through here, so we use the terminating character
+ }
+
+ switch ( c ) {
+
+ case ctrlChar( 'A' ): // ctrl-A, move cursor to start of line
+ case HOME_KEY:
+ killRing.lastAction = KillRing::actionOther;
+ pos = 0;
+ refreshLine( pi );
+ break;
+
+ case ctrlChar( 'B' ): // ctrl-B, move cursor left by one character
+ case LEFT_ARROW_KEY:
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos > 0 ) {
+ --pos;
+ refreshLine( pi );
+ }
+ break;
+
+ case META + 'b': // meta-B, move cursor left by one word
+ case META + 'B':
+ case CTRL + LEFT_ARROW_KEY:
+ case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos > 0 ) {
+ while ( pos > 0 && !isCharacterAlphanumeric( buf32[pos - 1] ) ) {
+ --pos;
+ }
+ while ( pos > 0 && isCharacterAlphanumeric( buf32[pos - 1] ) ) {
+ --pos;
+ }
+ refreshLine( pi );
+ }
+ break;
+
+ case ctrlChar( 'C' ): // ctrl-C, abort this line
+ killRing.lastAction = KillRing::actionOther;
+ historyRecallMostRecent = false;
+ errno = EAGAIN;
+ --historyLen;
+ free( history[historyLen] );
+ // 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 = len; // pass len as pos for EOL
+ refreshLine( pi );
+ if ( write( 1, "^C", 2 ) == -1 ) return -1; // Display the ^C we got
+ return -1;
+
+ case META + 'c': // meta-C, give word initial Cap
+ case META + 'C':
+ killRing.lastAction = KillRing::actionOther;
+ historyRecallMostRecent = false;
+ if ( pos < len ) {
+ while ( pos < len && !isCharacterAlphanumeric( buf32[pos] ) ) {
+ ++pos;
+ }
+ if ( pos < len && isCharacterAlphanumeric( buf32[pos] ) ) {
+ if ( buf32[pos] >= 'a' && buf32[pos] <= 'z' ) {
+ buf32[pos] += 'A' - 'a';
+ }
+ ++pos;
+ }
+ while ( pos < len && isCharacterAlphanumeric( buf32[pos] ) ) {
+ if ( buf32[pos] >= 'A' && buf32[pos] <= 'Z' ) {
+ buf32[pos] += 'a' - 'A';
+ }
+ ++pos;
+ }
+ refreshLine( pi );
+ }
+ break;
+
+ // ctrl-D, delete the character under the cursor
+ // on an empty line, exit the shell
+ case ctrlChar( 'D' ):
+ killRing.lastAction = KillRing::actionOther;
+ if ( len > 0 && pos < len ) {
+ historyRecallMostRecent = false;
+ memmove( buf32 + pos, buf32 + pos + 1, sizeof( UChar32 ) * ( len - pos ) );
+ --len;
+ refreshLine( pi );
+ }
+ else if ( len == 0 ) {
+ --historyLen;
+ free( history[historyLen] );
+ return -1;
+ }
+ break;
+
+ case META + 'd': // meta-D, kill word to right of cursor
+ case META + 'D':
+ if ( pos < len ) {
+ historyRecallMostRecent = false;
+ int endingPos = pos;
+ while ( endingPos < len && !isCharacterAlphanumeric( buf32[endingPos] ) ) {
+ ++endingPos;
+ }
+ while ( endingPos < len && isCharacterAlphanumeric( buf32[endingPos] ) ) {
+ ++endingPos;
+ }
+ killRing.kill( &buf32[pos], endingPos - pos, true );
+ memmove( buf32 + pos, buf32 + endingPos, sizeof( UChar32 ) * ( len - endingPos + 1 ) );
+ len -= endingPos - pos;
+ refreshLine( pi );
+ }
+ killRing.lastAction = KillRing::actionKill;
+ break;
+
+ case ctrlChar( 'E' ): // ctrl-E, move cursor to end of line
+ case END_KEY:
+ killRing.lastAction = KillRing::actionOther;
+ pos = len;
+ refreshLine( pi );
+ break;
+
+ case ctrlChar( 'F' ): // ctrl-F, move cursor right by one character
+ case RIGHT_ARROW_KEY:
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos < len ) {
+ ++pos;
+ refreshLine( pi );
+ }
+ break;
+
+ case META + 'f': // meta-F, move cursor right by one word
+ case META + 'F':
+ case CTRL + RIGHT_ARROW_KEY:
+ case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos < len ) {
+ while ( pos < len && !isCharacterAlphanumeric( buf32[pos] ) ) {
+ ++pos;
+ }
+ while ( pos < len && isCharacterAlphanumeric( buf32[pos] ) ) {
+ ++pos;
+ }
+ refreshLine( pi );
+ }
+ break;
+
+ case ctrlChar( 'H' ): // backspace/ctrl-H, delete char to left of cursor
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos > 0 ) {
+ historyRecallMostRecent = false;
+ memmove( buf32 + pos - 1, buf32 + pos, sizeof( UChar32 ) * ( 1 + len - pos ) );
+ --pos;
+ --len;
+ refreshLine( pi );
+ }
+ break;
+
+ // meta-Backspace, kill word to left of cursor
+ case META + ctrlChar( 'H' ):
+ if ( pos > 0 ) {
+ historyRecallMostRecent = false;
+ int startingPos = pos;
+ while ( pos > 0 && !isCharacterAlphanumeric( buf32[pos - 1] ) ) {
+ --pos;
+ }
+ while ( pos > 0 && isCharacterAlphanumeric( buf32[pos - 1] ) ) {
+ --pos;
+ }
+ killRing.kill( &buf32[pos], startingPos - pos, false );
+ memmove( buf32 + pos, buf32 + startingPos, sizeof( UChar32 ) * ( len - startingPos + 1 ) );
+ len -= startingPos - pos;
+ refreshLine( pi );
+ }
+ killRing.lastAction = KillRing::actionKill;
+ break;
+
+ case ctrlChar( 'J' ): // ctrl-J/linefeed/newline, accept line
+ case ctrlChar( 'M' ): // ctrl-M/return/enter
+ killRing.lastAction = KillRing::actionOther;
+ // 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 = len; // pass len as pos for EOL
+ refreshLine( pi );
+ historyPreviousIndex = historyRecallMostRecent ? historyIndex : -2;
+ --historyLen;
+ free( history[historyLen] );
+ return len;
+
+ case ctrlChar( 'K' ): // ctrl-K, kill from cursor to end of line
+ killRing.kill( &buf32[pos], len - pos, true );
+ buf32[pos] = '\0';
+ len = pos;
+ refreshLine( pi );
+ killRing.lastAction = KillRing::actionKill;
+ historyRecallMostRecent = false;
+ break;
+
+ case ctrlChar( 'L' ): // ctrl-L, clear screen and redisplay line
+ clearScreen( pi );
+ break;
+
+ case META + 'l': // meta-L, lowercase word
+ case META + 'L':
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos < len ) {
+ historyRecallMostRecent = false;
+ while ( pos < len && !isCharacterAlphanumeric( buf32[pos] ) ) {
+ ++pos;
+ }
+ while ( pos < len && isCharacterAlphanumeric( buf32[pos] ) ) {
+ if ( buf32[pos] >= 'A' && buf32[pos] <= 'Z' ) {
+ buf32[pos] += 'a' - 'A';
+ }
+ ++pos;
+ }
+ refreshLine( pi );
+ }
+ break;
+
+ case ctrlChar( 'N' ): // ctrl-N, recall next line in history
+ case ctrlChar( 'P' ): // ctrl-P, recall previous line in history
+ case DOWN_ARROW_KEY:
+ case UP_ARROW_KEY:
+ killRing.lastAction = KillRing::actionOther;
+ // if not already recalling, add the current line to the history list so we don't have to special case it
+ if ( historyIndex == historyLen - 1 ) {
+ free( history[historyLen - 1] );
+ size_t tempBufferSize = sizeof( UChar32 ) * len + 1;
+ scoped_array< UChar8 > tempBuffer( new UChar8[ tempBufferSize ] );
+ copyString32to8( tempBuffer.get(), buf32, tempBufferSize );
+ history[historyLen - 1] = reinterpret_cast< UChar8 * >( strdup( reinterpret_cast< const char * >( tempBuffer.get() ) ) );
+ }
+ if ( historyLen > 1 ) {
+ if ( c == UP_ARROW_KEY ) {
+ c = ctrlChar( 'P' );
+ }
+ if ( historyPreviousIndex != -2 && c != ctrlChar( 'P' ) ) {
+ historyIndex = 1 + historyPreviousIndex; // emulate Windows down-arrow
+ }
+ else {
+ historyIndex += ( c == ctrlChar( 'P' ) ) ? -1 : 1;
+ }
+ historyPreviousIndex = -2;
+ if ( historyIndex < 0 ) {
+ historyIndex = 0;
+ break;
+ }
+ else if ( historyIndex >= historyLen ) {
+ historyIndex = historyLen - 1;
+ break;
+ }
+ historyRecallMostRecent = true;
+ size_t ucharCount;
+ int errorCode;
+ copyString8to32( buf32, history[historyIndex], buflen, ucharCount, errorCode );
+ len = pos = ucharCount;
+ refreshLine( pi );
+ }
+ break;
+
+ case ctrlChar( 'R' ): // ctrl-R, reverse history search
+ case ctrlChar( 'S' ): // ctrl-S, forward history search
+ terminatingKeystroke = incrementalHistorySearch( pi, c );
+ break;
+
+ case ctrlChar( 'T' ): // ctrl-T, transpose characters
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos > 0 && len > 1 ) {
+ historyRecallMostRecent = false;
+ size_t leftCharPos = ( pos == len ) ? pos - 2 : pos - 1;
+ char aux = buf32[leftCharPos];
+ buf32[leftCharPos] = buf32[leftCharPos+1];
+ buf32[leftCharPos+1] = aux;
+ if ( pos != len )
+ ++pos;
+ refreshLine( pi );
+ }
+ break;
+
+ case ctrlChar( 'U' ): // ctrl-U, kill all characters to the left of the cursor
+ if ( pos > 0 ) {
+ historyRecallMostRecent = false;
+ killRing.kill( &buf32[0], pos, false );
+ len -= pos;
+ memmove( buf32, buf32 + pos, sizeof( UChar32 ) * ( len + 1 ) );
+ pos = 0;
+ refreshLine( pi );
+ }
+ killRing.lastAction = KillRing::actionKill;
+ break;
+
+ case META + 'u': // meta-U, uppercase word
+ case META + 'U':
+ killRing.lastAction = KillRing::actionOther;
+ if ( pos < len ) {
+ historyRecallMostRecent = false;
+ while ( pos < len && !isCharacterAlphanumeric( buf32[pos] ) ) {
+ ++pos;
+ }
+ while ( pos < len && isCharacterAlphanumeric( buf32[pos] ) ) {
+ if ( buf32[pos] >= 'a' && buf32[pos] <= 'z' ) {
+ buf32[pos] += 'A' - 'a';
+ }
+ ++pos;
+ }
+ refreshLine( pi );
+ }
+ break;
+
+ // ctrl-W, kill to whitespace (not word) to left of cursor
+ case ctrlChar( 'W' ):
+ if ( pos > 0 ) {
+ historyRecallMostRecent = false;
+ int startingPos = pos;
+ while ( pos > 0 && buf32[pos - 1] == ' ' ) {
+ --pos;
+ }
+ while ( pos > 0 && buf32[pos - 1] != ' ' ) {
+ --pos;
+ }
+ killRing.kill( &buf32[pos], startingPos - pos, false );
+ memmove( buf32 + pos, buf32 + startingPos, sizeof( UChar32 ) * ( len - startingPos + 1 ) );
+ len -= startingPos - pos;
+ refreshLine( pi );
+ }
+ killRing.lastAction = KillRing::actionKill;
+ break;
+
+ case ctrlChar( 'Y' ): // ctrl-Y, yank killed text
+ historyRecallMostRecent = false;
+ {
+ Utf32String* restoredText = killRing.yank();
+ if ( restoredText ) {
+ size_t ucharCount = restoredText->length();
+ memmove( buf32 + pos + ucharCount, buf32 + pos, sizeof( UChar32 ) * ( len - pos + 1 ) );
+ memmove( buf32 + pos, restoredText->get(), sizeof( UChar32 ) * ucharCount );
+ pos += ucharCount;
+ len += ucharCount;
+ refreshLine( pi );
+ killRing.lastAction = KillRing::actionYank;
+ killRing.lastYankSize = ucharCount;
+ }
+ else {
+ beep();
+ }
+ }
+ break;
+
+ case META + 'y': // meta-Y, "yank-pop", rotate popped text
+ case META + 'Y':
+ if ( killRing.lastAction == KillRing::actionYank ) {
+ historyRecallMostRecent = false;
+ Utf32String* restoredText = killRing.yankPop();
+ if ( restoredText ) {
+ size_t ucharCount = restoredText->length();
+ if ( ucharCount > killRing.lastYankSize ) {
+ memmove( buf32 + pos + ucharCount - killRing.lastYankSize, buf32 + pos, sizeof( UChar32 ) * ( len - pos + 1 ) );
+ memmove( buf32 + pos - killRing.lastYankSize, restoredText->get(), sizeof( UChar32 ) * ucharCount );
+ }
+ else {
+ memmove( buf32 + pos - killRing.lastYankSize, restoredText->get(), sizeof( UChar32 ) * ucharCount );
+ memmove( buf32 + pos + ucharCount - killRing.lastYankSize, buf32 + pos, sizeof( UChar32 ) * ( len - pos + 1 ) );
+ }
+ pos += ucharCount - killRing.lastYankSize;
+ len += ucharCount - killRing.lastYankSize;
+ killRing.lastYankSize = ucharCount;
+ refreshLine( pi );
+ break;
+ }
+ }
+ beep();
+ break;
+
+#ifndef _WIN32
+ case ctrlChar( 'Z' ): // ctrl-Z, job control
+ disableRawMode(); // Returning to Linux (whatever) shell, leave raw mode
+ raise( SIGSTOP ); // Break out in mid-line
+ enableRawMode(); // Back from Linux shell, re-enter raw mode
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) break; // Redraw prompt
+ refreshLine( pi ); // Refresh the line
+ break;
+#endif
+
+ // DEL, delete the character under the cursor
+ case 127:
+ case DELETE_KEY:
+ killRing.lastAction = KillRing::actionOther;
+ if ( len > 0 && pos < len ) {
+ historyRecallMostRecent = false;
+ memmove( buf32 + pos, buf32 + pos + 1, sizeof( UChar32 ) * ( len - pos ) );
+ --len;
+ refreshLine( pi );
+ }
+ break;
+
+ case META + '<': // meta-<, beginning of history
+ case PAGE_UP_KEY: // Page Up, beginning of history
+ case META + '>': // meta->, end of history
+ case PAGE_DOWN_KEY: // Page Down, end of history
+ killRing.lastAction = KillRing::actionOther;
+ // if not already recalling, add the current line to the history list so we don't have to special case it
+ if ( historyIndex == historyLen - 1 ) {
+ free( history[historyLen - 1] );
+ size_t tempBufferSize = sizeof( UChar32 ) * len + 1;
+ scoped_array< UChar8 > tempBuffer( new UChar8[ tempBufferSize ] );
+ copyString32to8( tempBuffer.get(), buf32, tempBufferSize );
+ history[historyLen - 1] = reinterpret_cast< UChar8 * >( strdup( reinterpret_cast< const char * >( tempBuffer.get() ) ) );
+ }
+ if ( historyLen > 1 ) {
+ historyIndex = ( c == META + '<' || c == PAGE_UP_KEY ) ? 0 : historyLen - 1;
+ historyPreviousIndex = -2;
+ historyRecallMostRecent = true;
+ size_t ucharCount;
+ int errorCode;
+ copyString8to32( buf32, history[historyIndex], buflen, ucharCount, errorCode );
+ len = pos = ucharCount;
+ refreshLine( pi );
+ }
+ break;
+
+ // not one of our special characters, maybe insert it in the buffer
+ default:
+ killRing.lastAction = KillRing::actionOther;
+ historyRecallMostRecent = false;
+ if ( c & ( META | CTRL ) ) { // beep on unknown Ctrl and/or Meta keys
+ beep();
+ break;
+ }
+ if ( len < buflen ) {
+ if ( isControlChar( c ) ) { // don't insert control characters
+ beep();
+ break;
+ }
+ if ( len == pos ) { // at end of buffer
+ buf32[pos] = c;
+ ++pos;
+ ++len;
+ buf32[len] = '\0';
+ if ( pi.promptIndentation + len < pi.promptScreenColumns ) {
+ if ( len > pi.promptPreviousInputLen )
+ pi.promptPreviousInputLen = len;
+ /* Avoid a full update of the line in the
+ * trivial case. */
+ if ( write32( 1, reinterpret_cast<UChar32 *>( &c ), 1) == -1 ) return -1;
+ }
+ else {
+ refreshLine( pi );
+ }
+ }
+ else { // not at end of buffer, have to move characters to our right
+ memmove( buf32 + pos + 1, buf32 + pos, sizeof( UChar32 ) * ( len - pos ) );
+ buf32[pos] = c;
+ ++len;
+ ++pos;
+ buf32[len] = '\0';
+ refreshLine( pi );
+ }
+ }
+ else {
+ beep(); // buffer is full, beep on new characters
+ }
+ break;
+ }
+ }
+ return len;
+}
+
+string preloadedBufferContents; // used with linenoisePreloadBuffer
+string preloadErrorMessage;
+
+/**
+ * linenoisePreloadBuffer 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 linenoise()
+ *
+ * @param preloadText text to begin with on the next call to linenoise()
+ */
+void linenoisePreloadBuffer( const char* preloadText ) {
+
+ if ( ! preloadText ) {
+ return;
+ }
+ int bufferSize = strlen( preloadText ) + 1;
+ scoped_array< char > tempBuffer( new char[ bufferSize ] );
+ strncpy( &tempBuffer[0], preloadText, bufferSize );
+
+ // remove characters that won't display correctly
+ char* pIn = &tempBuffer[0];
+ char* pOut = pIn;
+ bool controlsStripped = false;
+ bool whitespaceSeen = false;
+ while ( *pIn ) {
+ unsigned char c = *pIn++; // we need unsigned so chars 0x80 and above are allowed
+ if ( '\r' == c ) { // silently skip CR
+ continue;
+ }
+ if ( '\n' == c || '\t' == c ) { // note newline or tab
+ whitespaceSeen = true;
+ continue;
+ }
+ if ( isControlChar( c ) ) { // remove other control characters, flag for message
+ controlsStripped = true;
+ *pOut++ = ' ';
+ continue;
+ }
+ if ( whitespaceSeen ) { // convert whitespace to a single space
+ *pOut++ = ' ';
+ whitespaceSeen = false;
+ }
+ *pOut++ = c;
+ }
+ *pOut = 0;
+ int processedLength = pOut - tempBuffer.get();
+ bool lineTruncated = false;
+ if ( processedLength > ( LINENOISE_MAX_LINE - 1 ) ) {
+ lineTruncated = true;
+ tempBuffer[ LINENOISE_MAX_LINE - 1 ] = 0;
+ }
+ preloadedBufferContents = tempBuffer.get();
+ if ( controlsStripped ) {
+ preloadErrorMessage += " [Edited line: control characters were converted to spaces]\n";
+ }
+ if ( lineTruncated ) {
+ preloadErrorMessage += " [Edited line: the line length was reduced from ";
+ char buf[128];
+ snprintf( buf, sizeof( buf ), "%d to %d]\n", processedLength, ( LINENOISE_MAX_LINE - 1 ) );
+ preloadErrorMessage += buf;
+ }
+}
+
+/**
+ * linenoise 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* linenoise( const char* prompt ) {
+ if ( isatty( STDIN_FILENO ) ) { // input is from a terminal
+ UChar32 buf32[ LINENOISE_MAX_LINE ];
+ char charWidths[ LINENOISE_MAX_LINE ];
+ if ( ! preloadErrorMessage.empty() ) {
+ printf( "%s", preloadErrorMessage.c_str() );
+ fflush( stdout );
+ preloadErrorMessage.clear();
+ }
+ PromptInfo pi( reinterpret_cast< const UChar8* >( prompt ), getScreenColumns() );
+ if ( isUnsupportedTerm() ) {
+ if ( write32( 1, pi.promptText.get(), pi.promptChars ) == -1 ) return 0;
+ fflush( stdout );
+ if ( preloadedBufferContents.empty() ) {
+ scoped_array<char> buf8( new char[ LINENOISE_MAX_LINE ] );
+ if ( fgets( buf8.get(), LINENOISE_MAX_LINE, stdin ) == NULL ) {
+ return NULL;
+ }
+ size_t len = strlen( buf8.get() );
+ while ( len && ( buf8[len - 1] == '\n' || buf8[len - 1] == '\r' ) ) {
+ --len;
+ buf8[len] = '\0';
+ }
+ return strdup( buf8.get() ); // caller must free buffer
+ }
+ else {
+ char* buf8 = strdup( preloadedBufferContents.c_str() );
+ preloadedBufferContents.clear();
+ return buf8; // caller must free buffer
+ }
+ }
+ else {
+ if ( enableRawMode() == -1 ) {
+ return NULL;
+ }
+ InputBuffer ib( buf32, charWidths, LINENOISE_MAX_LINE );
+ if ( ! preloadedBufferContents.empty() ) {
+ ib.preloadBuffer( reinterpret_cast< const UChar8 * >( preloadedBufferContents.c_str() ) );
+ preloadedBufferContents.clear();
+ }
+ int count = ib.getInputLine( pi );
+ disableRawMode();
+ printf( "\n" );
+ if ( count == -1 ) {
+ return NULL;
+ }
+ size_t bufferSize = sizeof( UChar32 ) * ib.length() + 1;
+ scoped_array<UChar8> buf8( new UChar8[ bufferSize ] );
+ copyString32to8( buf8.get(), buf32, bufferSize );
+ return strdup( reinterpret_cast<char*>( buf8.get() ) ); // caller must free buffer
+ }
+ }
+ else { // input not from a terminal, we should work with piped input, i.e. redirected stdin
+ scoped_array<char> buf8( new char[ LINENOISE_MAX_LINE ] );
+ if ( fgets( buf8.get(), LINENOISE_MAX_LINE, stdin ) == NULL ) {
+ return NULL;
+ }
+
+ // if fgets() gave us the newline, remove it
+ int count = strlen( buf8.get() );
+ if ( count > 0 && buf8[ count - 1 ] == '\n' ) {
+ --count;
+ buf8[ count ] = '\0';
+ }
+ return strdup( buf8.get() ); // caller must free buffer
+ }
+}
+
+/* Register a callback function to be called for tab-completion. */
+void linenoiseSetCompletionCallback( linenoiseCompletionCallback* fn ) {
+ completionCallback = fn;
+}
+
+void linenoiseAddCompletion( linenoiseCompletions* lc, const char* str ) {
+ lc->completionStrings.push_back( Utf32String( reinterpret_cast<const UChar8*>( str ) ) );
+}
+
+int linenoiseHistoryAdd( const char* line ) {
+ if ( historyMaxLen == 0 ) {
+ return 0;
+ }
+ if ( history == NULL ) {
+ history = reinterpret_cast< UChar8** >( malloc( sizeof( UChar8* ) * historyMaxLen ) );
+ if (history == NULL) {
+ return 0;
+ }
+ memset( history, 0, ( sizeof( char* ) * historyMaxLen ) );
+ }
+ UChar8* linecopy = reinterpret_cast< UChar8* >( strdup( line ) );
+ if ( ! linecopy ) {
+ return 0;
+ }
+ if ( historyLen == historyMaxLen ) {
+ free( history[0] );
+ memmove( history, history + 1, sizeof( char* ) * ( historyMaxLen - 1 ) );
+ --historyLen;
+ if ( --historyPreviousIndex < -1 ) {
+ historyPreviousIndex = -2;
+ }
+ }
+
+ // convert newlines in multi-line code to spaces before storing
+ UChar8* p = linecopy;
+ while ( *p ) {
+ if ( *p == '\n' ) {
+ *p = ' ';
+ }
+ ++p;
+ }
+ history[historyLen] = linecopy;
+ ++historyLen;
+ return 1;
+}
+
+int linenoiseHistorySetMaxLen( int len ) {
+ if ( len < 1 ) {
+ return 0;
+ }
+ if ( history ) {
+ int tocopy = historyLen;
+ UChar8** newHistory = reinterpret_cast< UChar8** >( malloc( sizeof( UChar8* ) * len ) );
+ if ( newHistory == NULL ) {
+ return 0;
+ }
+ if ( len < tocopy ) {
+ tocopy = len;
+ }
+ memcpy( newHistory, history + historyMaxLen - tocopy, sizeof( UChar8* ) * tocopy );
+ free( history );
+ history = newHistory;
+ }
+ historyMaxLen = len;
+ if ( historyLen > historyMaxLen ) {
+ historyLen = historyMaxLen;
+ }
+ 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, "wt" );
+ if ( fp == NULL ) {
+ return -1;
+ }
+
+ for ( int j = 0; j < historyLen; ++j ) {
+ if ( history[j][0] != '\0' ) {
+ 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, "rt" );
+ if ( fp == NULL ) {
+ return -1;
+ }
+
+ char buf[LINENOISE_MAX_LINE];
+ while ( fgets( buf, LINENOISE_MAX_LINE, fp ) != NULL ) {
+ char* p = strchr( buf, '\r' );
+ if ( ! p ) {
+ p = strchr( buf, '\n' );
+ }
+ if ( p ) {
+ *p = '\0';
+ }
+ if ( p != buf ) {
+ linenoiseHistoryAdd( buf );
+ }
+ }
+ fclose( fp );
+ return 0;
+}