diff options
author | Eliot Horowitz <eliot@10gen.com> | 2011-03-18 15:19:43 -0400 |
---|---|---|
committer | Eliot Horowitz <eliot@10gen.com> | 2011-03-18 15:19:43 -0400 |
commit | 9711f4d311c27da7ab94e4cd649f4dab2454d1c4 (patch) | |
tree | 796f265e21891cd104dfd14d34ff93f21a5133a4 /third_party/linenoise | |
parent | ff767886eecd985ee9af28861ef6ce177b849040 (diff) | |
download | mongo-9711f4d311c27da7ab94e4cd649f4dab2454d1c4.tar.gz |
try linenoise on windows
Diffstat (limited to 'third_party/linenoise')
-rw-r--r-- | third_party/linenoise/linenoise_win32.cpp | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/third_party/linenoise/linenoise_win32.cpp b/third_party/linenoise/linenoise_win32.cpp new file mode 100644 index 00000000000..68b58d44197 --- /dev/null +++ b/third_party/linenoise/linenoise_win32.cpp @@ -0,0 +1,441 @@ +/* linenoise_win32.c -- Linenoise win32 port. + * + * Modifications copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>. + * All rights reserved. + * Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>. + * The original linenoise can be found at: http://github.com/antirez/linenoise + * + * 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. + * + * Todo list: + * Actually switch to/from raw mode so emacs key combos work. + * Set a console handler to clean up onn exit. + */ +#include <conio.h> +#include <windows.h> +#include <stdio.h> + +/* If ALT_KEYS is defined, emacs key combos using ALT instead of CTRL are + * available. At this time, you don't get key repeats when enabled though. */ +/* #define ALT_KEYS */ + +static HANDLE console_in, console_out; + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 + +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +char** history = NULL; + +int linenoiseHistoryAdd(const char* line); + +static int enableRawMode() +{ + if (!console_in) + { + console_in = GetStdHandle(STD_INPUT_HANDLE); + console_out = GetStdHandle(STD_OUTPUT_HANDLE); + } + return 0; +} + +static void disableRawMode() +{ + /* Nothing to do yet */ +} + +static void output(const char* str, + size_t len, + int x, + int y) +{ + COORD pos = { (SHORT)x, (SHORT)y }; + WriteConsoleOutputCharacterA(console_out, str, len, pos, 0); +} + +static void refreshLine(const char* prompt, + char* buf, + size_t len, + size_t pos, + size_t cols) +{ + size_t plen = strlen(prompt); + + while ((plen + pos) >= cols) + { + buf++; + len--; + pos--; + } + while (plen + len > cols) + { + len--; + } + + CONSOLE_SCREEN_BUFFER_INFO inf = { 0 }; + GetConsoleScreenBufferInfo(console_out, &inf); + size_t prompt_len = strlen(prompt); + output(prompt, prompt_len, 0, inf.dwCursorPosition.Y); + output(buf, len, prompt_len, inf.dwCursorPosition.Y); + if (prompt_len + len < (size_t)inf.dwSize.X) + { + /* Blank to EOL */ + char* tmp = (char*)malloc(inf.dwSize.X - (prompt_len + len)); + memset(tmp, ' ', inf.dwSize.X - (prompt_len + len)); + output(tmp, inf.dwSize.X - (prompt_len + len), len + prompt_len, inf.dwCursorPosition.Y); + free(tmp); + } + inf.dwCursorPosition.X = (SHORT)(pos + prompt_len); + SetConsoleCursorPosition(console_out, inf.dwCursorPosition); +} + +static int linenoisePrompt(char* buf, + size_t buflen, + const char* prompt) +{ + size_t plen = strlen(prompt); + size_t pos = 0; + size_t len = 0; + int history_index = 0; +#ifdef ALT_KEYS + unsigned char last_down = 0; +#endif + buf[0] = '\0'; + 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(""); + + CONSOLE_SCREEN_BUFFER_INFO inf = { 0 }; + GetConsoleScreenBufferInfo(console_out, &inf); + size_t cols = inf.dwSize.X; + output(prompt, plen, 0, inf.dwCursorPosition.Y); + inf.dwCursorPosition.X = (SHORT)plen; + SetConsoleCursorPosition(console_out, inf.dwCursorPosition); + + for ( ; ; ) + { + INPUT_RECORD rec; + DWORD count; + ReadConsoleInputA(console_in, &rec, 1, &count); + if (rec.EventType != KEY_EVENT) + continue; +#ifdef ALT_KEYS + if (rec.Event.KeyEvent.bKeyDown) + { + last_down = rec.Event.KeyEvent.uChar.AsciiChar; + continue; + } +#else + if (!rec.Event.KeyEvent.bKeyDown) + { + continue; + } +#endif + switch (rec.Event.KeyEvent.wVirtualKeyCode) + { + case VK_RETURN: /* enter */ + history_len--; + free(history[history_len]); + return (int)len; + case VK_BACK: /* backspace */ +#ifdef ALT_KEYS + backspace: +#endif + if (pos > 0 && len > 0) + { + memmove(buf + pos - 1, buf + pos, len - pos); + pos--; + len--; + buf[len] = '\0'; + refreshLine(prompt, buf, len, pos, cols); + } + break; + case VK_LEFT: +#ifdef ALT_KEYS + left_arrow: +#endif + /* left arrow */ + if (pos > 0) + { + pos--; + refreshLine(prompt, buf, len, pos, cols); + } + break; + case VK_RIGHT: +#ifdef ALT_KEYS + right_arrow: +#endif + /* right arrow */ + if (pos != len) + { + pos++; + refreshLine(prompt, buf, len, pos, cols); + } + break; + case VK_UP: + case VK_DOWN: +#ifdef ALT_KEYS + up_down_arrow: +#endif + /* up and down arrow: history */ + if (history_len > 1) + { + /* Update the current history entry before to + * overwrite it with tne next one. */ + free(history[history_len - 1 - history_index]); + history[history_len - 1 - history_index] = _strdup(buf); + /* Show the new entry */ + history_index += (rec.Event.KeyEvent.wVirtualKeyCode == VK_UP) ? 1 : -1; + if (history_index < 0) + { + history_index = 0; + break; + } + else if (history_index >= history_len) + { + history_index = history_len - 1; + break; + } + strncpy(buf, history[history_len - 1 - history_index], buflen); + buf[buflen] = '\0'; + len = pos = strlen(buf); + refreshLine(prompt, buf, len, pos, cols); + } + break; + case VK_DELETE: + /* delete */ + if (len > 0 && pos < len) + { + memmove(buf + pos, buf + pos + 1, len - pos - 1); + len--; + buf[len] = '\0'; + refreshLine(prompt, buf, len, pos, cols); + } + break; + case VK_HOME: /* Ctrl+a, go to the start of the line */ +#ifdef ALT_KEYS + home: +#endif + pos = 0; + refreshLine(prompt, buf, len, pos, cols); + break; + case VK_END: /* ctrl+e, go to the end of the line */ +#ifdef ALT_KEYS + end: +#endif + pos = len; + refreshLine(prompt, buf, len, pos, cols); + break; + default: +#ifdef ALT_KEYS + /* Use alt instead of CTRL since windows eats CTRL+char combos */ + if (rec.Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) + { + switch (last_down) + { + case 'a': /* ctrl-t */ + goto home; + case 'e': /* ctrl-t */ + goto end; + case 't': /* ctrl-t */ + if (pos > 0 && pos < len) + { + int aux = buf[pos - 1]; + buf[pos - 1] = buf[pos]; + buf[pos] = aux; + if (pos != len - 1) + pos++; + refreshLine(prompt, buf, len, pos, cols); + } + break; + case 'h': /* ctrl-h */ + goto backspace; + case 'b': /* ctrl-b */ + goto left_arrow; + case 'f': /* ctrl-f */ + goto right_arrow; + case 'p': /* ctrl-p */ + rec.Event.KeyEvent.wVirtualKeyCode = VK_UP; + goto up_down_arrow; + case 'n': /* ctrl-n */ + rec.Event.KeyEvent.wVirtualKeyCode = VK_DOWN; + goto up_down_arrow; + case 'u': /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + pos = len = 0; + refreshLine(prompt, buf, len, pos, cols); + break; + case 'k': /* Ctrl+k, delete from current to end of line. */ + buf[pos] = '\0'; + len = pos; + refreshLine(prompt, buf, len, pos, cols); + break; + } + continue; + } +#endif /* ALT_KEYS */ + if (rec.Event.KeyEvent.uChar.AsciiChar < ' ' || + rec.Event.KeyEvent.uChar.AsciiChar > '~') + continue; + + if (len < buflen) + { + if (len != pos) + memmove(buf + pos + 1, buf + pos, len - pos); + buf[pos] = rec.Event.KeyEvent.uChar.AsciiChar; + len++; + pos++; + buf[len] = '\0'; + refreshLine(prompt, buf, len, pos, cols); + } + break; + } + } +} + +static int linenoiseRaw(char* buf, + size_t buflen, + const char* prompt) +{ + int count = -1; + + if (buflen != 0) + { + if (enableRawMode() == -1) + return -1; + count = linenoisePrompt(buf, buflen, prompt); + disableRawMode(); + printf("\n"); + } + return count; +} + +char* linenoise(const char* prompt) +{ + char buf[LINENOISE_MAX_LINE]; + int count = linenoiseRaw(buf, LINENOISE_MAX_LINE, prompt); + if (count == -1) + return NULL; + return _strdup(buf); +} + +/* 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; + if (history == NULL) + { + history = (char**)malloc(sizeof(char*) * history_max_len); + if (history == NULL) + return 0; + memset(history, 0, (sizeof(char*) * history_max_len)); + } + 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; +} + +int linenoiseHistorySetMaxLen(int len) +{ + char** new_history; + + if (len < 1) + return 0; + if (history) + { + int tocopy = history_len; + + new_history = (char**)malloc(sizeof(char*) * len); + if (new_history == NULL) + return 0; + if (len < tocopy) + tocopy = len; + memcpy(new_history, history + (history_max_len - tocopy), sizeof(char*) * tocopy); + free(history); + history = new_history; + } + 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; +} |