dfhack/library/depends/libnoise/linenoise_win32.cpp

444 lines
15 KiB
C++

/* 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 };
DWORD count = 0;
WriteConsoleOutputCharacterA(console_out, str, len, pos, &count);
}
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,
FILE * out )
{
int count = -1;
if (buflen != 0)
{
if (enableRawMode() == -1)
return -1;
count = linenoisePrompt(buf, buflen, prompt);
disableRawMode();
fprintf(out, "\n");
}
return count;
}
char* linenoise(const char* prompt, FILE * out)
{
char buf[LINENOISE_MAX_LINE];
int count = linenoiseRaw(buf, LINENOISE_MAX_LINE, prompt, out);
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;
}