|
#include "console.h" |
|
#include <vector> |
|
#include <iostream> |
|
|
|
#if defined(_WIN32) |
|
#define WIN32_LEAN_AND_MEAN |
|
#ifndef NOMINMAX |
|
#define NOMINMAX |
|
#endif |
|
#include <windows.h> |
|
#include <fcntl.h> |
|
#include <io.h> |
|
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING |
|
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 |
|
#endif |
|
#else |
|
#include <climits> |
|
#include <sys/ioctl.h> |
|
#include <unistd.h> |
|
#include <wchar.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <signal.h> |
|
#include <termios.h> |
|
#endif |
|
|
|
#define ANSI_COLOR_RED "\x1b[31m" |
|
#define ANSI_COLOR_GREEN "\x1b[32m" |
|
#define ANSI_COLOR_YELLOW "\x1b[33m" |
|
#define ANSI_COLOR_BLUE "\x1b[34m" |
|
#define ANSI_COLOR_MAGENTA "\x1b[35m" |
|
#define ANSI_COLOR_CYAN "\x1b[36m" |
|
#define ANSI_COLOR_RESET "\x1b[0m" |
|
#define ANSI_BOLD "\x1b[1m" |
|
|
|
namespace console { |
|
|
|
|
|
|
|
|
|
|
|
static bool advanced_display = false; |
|
static bool simple_io = true; |
|
static display_t current_display = reset; |
|
|
|
static FILE* out = stdout; |
|
|
|
#if defined (_WIN32) |
|
static void* hConsole; |
|
#else |
|
static FILE* tty = nullptr; |
|
static termios initial_state; |
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
void init(bool use_simple_io, bool use_advanced_display) { |
|
advanced_display = use_advanced_display; |
|
simple_io = use_simple_io; |
|
#if defined(_WIN32) |
|
|
|
DWORD dwMode = 0; |
|
hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
|
if (hConsole == INVALID_HANDLE_VALUE || !GetConsoleMode(hConsole, &dwMode)) { |
|
hConsole = GetStdHandle(STD_ERROR_HANDLE); |
|
if (hConsole != INVALID_HANDLE_VALUE && (!GetConsoleMode(hConsole, &dwMode))) { |
|
hConsole = nullptr; |
|
simple_io = true; |
|
} |
|
} |
|
if (hConsole) { |
|
|
|
if (advanced_display && !(dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && |
|
!SetConsoleMode(hConsole, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { |
|
advanced_display = false; |
|
} |
|
|
|
SetConsoleOutputCP(CP_UTF8); |
|
} |
|
HANDLE hConIn = GetStdHandle(STD_INPUT_HANDLE); |
|
if (hConIn != INVALID_HANDLE_VALUE && GetConsoleMode(hConIn, &dwMode)) { |
|
|
|
_setmode(_fileno(stdin), _O_WTEXT); |
|
|
|
|
|
if (simple_io) { |
|
dwMode |= ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT; |
|
} else { |
|
dwMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); |
|
} |
|
if (!SetConsoleMode(hConIn, dwMode)) { |
|
simple_io = true; |
|
} |
|
} |
|
if (simple_io) { |
|
_setmode(_fileno(stdin), _O_U8TEXT); |
|
} |
|
#else |
|
|
|
if (!simple_io) { |
|
struct termios new_termios; |
|
tcgetattr(STDIN_FILENO, &initial_state); |
|
new_termios = initial_state; |
|
new_termios.c_lflag &= ~(ICANON | ECHO); |
|
new_termios.c_cc[VMIN] = 1; |
|
new_termios.c_cc[VTIME] = 0; |
|
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios); |
|
|
|
tty = fopen("/dev/tty", "w+"); |
|
if (tty != nullptr) { |
|
out = tty; |
|
} |
|
} |
|
|
|
setlocale(LC_ALL, ""); |
|
#endif |
|
} |
|
|
|
void cleanup() { |
|
|
|
set_display(reset); |
|
|
|
#if !defined(_WIN32) |
|
|
|
if (!simple_io) { |
|
if (tty != nullptr) { |
|
out = stdout; |
|
fclose(tty); |
|
tty = nullptr; |
|
} |
|
tcsetattr(STDIN_FILENO, TCSANOW, &initial_state); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
void set_display(display_t display) { |
|
if (advanced_display && current_display != display) { |
|
fflush(stdout); |
|
switch(display) { |
|
case reset: |
|
fprintf(out, ANSI_COLOR_RESET); |
|
break; |
|
case prompt: |
|
fprintf(out, ANSI_COLOR_YELLOW); |
|
break; |
|
case user_input: |
|
fprintf(out, ANSI_BOLD ANSI_COLOR_GREEN); |
|
break; |
|
case error: |
|
fprintf(out, ANSI_BOLD ANSI_COLOR_RED); |
|
} |
|
current_display = display; |
|
fflush(out); |
|
} |
|
} |
|
|
|
static char32_t getchar32() { |
|
#if defined(_WIN32) |
|
HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); |
|
wchar_t high_surrogate = 0; |
|
|
|
while (true) { |
|
INPUT_RECORD record; |
|
DWORD count; |
|
if (!ReadConsoleInputW(hConsole, &record, 1, &count) || count == 0) { |
|
return WEOF; |
|
} |
|
|
|
if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) { |
|
wchar_t wc = record.Event.KeyEvent.uChar.UnicodeChar; |
|
if (wc == 0) { |
|
continue; |
|
} |
|
|
|
if ((wc >= 0xD800) && (wc <= 0xDBFF)) { |
|
high_surrogate = wc; |
|
continue; |
|
} |
|
if ((wc >= 0xDC00) && (wc <= 0xDFFF)) { |
|
if (high_surrogate != 0) { |
|
return ((high_surrogate - 0xD800) << 10) + (wc - 0xDC00) + 0x10000; |
|
} |
|
} |
|
|
|
high_surrogate = 0; |
|
return static_cast<char32_t>(wc); |
|
} |
|
} |
|
#else |
|
wchar_t wc = getwchar(); |
|
if (static_cast<wint_t>(wc) == WEOF) { |
|
return WEOF; |
|
} |
|
|
|
#if WCHAR_MAX == 0xFFFF |
|
if ((wc >= 0xD800) && (wc <= 0xDBFF)) { |
|
wchar_t low_surrogate = getwchar(); |
|
if ((low_surrogate >= 0xDC00) && (low_surrogate <= 0xDFFF)) { |
|
return (static_cast<char32_t>(wc & 0x03FF) << 10) + (low_surrogate & 0x03FF) + 0x10000; |
|
} |
|
} |
|
if ((wc >= 0xD800) && (wc <= 0xDFFF)) { |
|
return 0xFFFD; |
|
} |
|
#endif |
|
|
|
return static_cast<char32_t>(wc); |
|
#endif |
|
} |
|
|
|
static void pop_cursor() { |
|
#if defined(_WIN32) |
|
if (hConsole != NULL) { |
|
CONSOLE_SCREEN_BUFFER_INFO bufferInfo; |
|
GetConsoleScreenBufferInfo(hConsole, &bufferInfo); |
|
|
|
COORD newCursorPosition = bufferInfo.dwCursorPosition; |
|
if (newCursorPosition.X == 0) { |
|
newCursorPosition.X = bufferInfo.dwSize.X - 1; |
|
newCursorPosition.Y -= 1; |
|
} else { |
|
newCursorPosition.X -= 1; |
|
} |
|
|
|
SetConsoleCursorPosition(hConsole, newCursorPosition); |
|
return; |
|
} |
|
#endif |
|
putc('\b', out); |
|
} |
|
|
|
static int estimateWidth(char32_t codepoint) { |
|
#if defined(_WIN32) |
|
(void)codepoint; |
|
return 1; |
|
#else |
|
return wcwidth(codepoint); |
|
#endif |
|
} |
|
|
|
static int put_codepoint(const char* utf8_codepoint, size_t length, int expectedWidth) { |
|
#if defined(_WIN32) |
|
CONSOLE_SCREEN_BUFFER_INFO bufferInfo; |
|
if (!GetConsoleScreenBufferInfo(hConsole, &bufferInfo)) { |
|
|
|
return expectedWidth; |
|
} |
|
COORD initialPosition = bufferInfo.dwCursorPosition; |
|
DWORD nNumberOfChars = length; |
|
WriteConsole(hConsole, utf8_codepoint, nNumberOfChars, &nNumberOfChars, NULL); |
|
|
|
CONSOLE_SCREEN_BUFFER_INFO newBufferInfo; |
|
GetConsoleScreenBufferInfo(hConsole, &newBufferInfo); |
|
|
|
|
|
if (utf8_codepoint[0] != 0x09 && initialPosition.X == newBufferInfo.dwSize.X - 1) { |
|
DWORD nNumberOfChars; |
|
WriteConsole(hConsole, &" \b", 2, &nNumberOfChars, NULL); |
|
GetConsoleScreenBufferInfo(hConsole, &newBufferInfo); |
|
} |
|
|
|
int width = newBufferInfo.dwCursorPosition.X - initialPosition.X; |
|
if (width < 0) { |
|
width += newBufferInfo.dwSize.X; |
|
} |
|
return width; |
|
#else |
|
|
|
if (expectedWidth >= 0 || tty == nullptr) { |
|
fwrite(utf8_codepoint, length, 1, out); |
|
return expectedWidth; |
|
} |
|
|
|
fputs("\033[6n", tty); |
|
int x1; |
|
int y1; |
|
int x2; |
|
int y2; |
|
int results = 0; |
|
results = fscanf(tty, "\033[%d;%dR", &y1, &x1); |
|
|
|
fwrite(utf8_codepoint, length, 1, tty); |
|
|
|
fputs("\033[6n", tty); |
|
results += fscanf(tty, "\033[%d;%dR", &y2, &x2); |
|
|
|
if (results != 4) { |
|
return expectedWidth; |
|
} |
|
|
|
int width = x2 - x1; |
|
if (width < 0) { |
|
|
|
struct winsize w; |
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); |
|
width += w.ws_col; |
|
} |
|
return width; |
|
#endif |
|
} |
|
|
|
static void replace_last(char ch) { |
|
#if defined(_WIN32) |
|
pop_cursor(); |
|
put_codepoint(&ch, 1, 1); |
|
#else |
|
fprintf(out, "\b%c", ch); |
|
#endif |
|
} |
|
|
|
static void append_utf8(char32_t ch, std::string & out) { |
|
if (ch <= 0x7F) { |
|
out.push_back(static_cast<unsigned char>(ch)); |
|
} else if (ch <= 0x7FF) { |
|
out.push_back(static_cast<unsigned char>(0xC0 | ((ch >> 6) & 0x1F))); |
|
out.push_back(static_cast<unsigned char>(0x80 | (ch & 0x3F))); |
|
} else if (ch <= 0xFFFF) { |
|
out.push_back(static_cast<unsigned char>(0xE0 | ((ch >> 12) & 0x0F))); |
|
out.push_back(static_cast<unsigned char>(0x80 | ((ch >> 6) & 0x3F))); |
|
out.push_back(static_cast<unsigned char>(0x80 | (ch & 0x3F))); |
|
} else if (ch <= 0x10FFFF) { |
|
out.push_back(static_cast<unsigned char>(0xF0 | ((ch >> 18) & 0x07))); |
|
out.push_back(static_cast<unsigned char>(0x80 | ((ch >> 12) & 0x3F))); |
|
out.push_back(static_cast<unsigned char>(0x80 | ((ch >> 6) & 0x3F))); |
|
out.push_back(static_cast<unsigned char>(0x80 | (ch & 0x3F))); |
|
} else { |
|
|
|
} |
|
} |
|
|
|
|
|
static void pop_back_utf8_char(std::string & line) { |
|
if (line.empty()) { |
|
return; |
|
} |
|
|
|
size_t pos = line.length() - 1; |
|
|
|
|
|
for (size_t i = 0; i < 3 && pos > 0; ++i, --pos) { |
|
if ((line[pos] & 0xC0) != 0x80) { |
|
break; |
|
} |
|
} |
|
line.erase(pos); |
|
} |
|
|
|
static bool readline_advanced(std::string & line, bool multiline_input) { |
|
if (out != stdout) { |
|
fflush(stdout); |
|
} |
|
|
|
line.clear(); |
|
std::vector<int> widths; |
|
bool is_special_char = false; |
|
bool end_of_stream = false; |
|
|
|
char32_t input_char; |
|
while (true) { |
|
fflush(out); |
|
input_char = getchar32(); |
|
|
|
if (input_char == '\r' || input_char == '\n') { |
|
break; |
|
} |
|
|
|
if (input_char == (char32_t) WEOF || input_char == 0x04 ) { |
|
end_of_stream = true; |
|
break; |
|
} |
|
|
|
if (is_special_char) { |
|
set_display(user_input); |
|
replace_last(line.back()); |
|
is_special_char = false; |
|
} |
|
|
|
if (input_char == '\033') { |
|
char32_t code = getchar32(); |
|
if (code == '[' || code == 0x1B) { |
|
|
|
while ((code = getchar32()) != (char32_t) WEOF) { |
|
if ((code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z') || code == '~') { |
|
break; |
|
} |
|
} |
|
} |
|
} else if (input_char == 0x08 || input_char == 0x7F) { |
|
if (!widths.empty()) { |
|
int count; |
|
do { |
|
count = widths.back(); |
|
widths.pop_back(); |
|
|
|
for (int i = 0; i < count; i++) { |
|
replace_last(' '); |
|
pop_cursor(); |
|
} |
|
pop_back_utf8_char(line); |
|
} while (count == 0 && !widths.empty()); |
|
} |
|
} else { |
|
int offset = line.length(); |
|
append_utf8(input_char, line); |
|
int width = put_codepoint(line.c_str() + offset, line.length() - offset, estimateWidth(input_char)); |
|
if (width < 0) { |
|
width = 0; |
|
} |
|
widths.push_back(width); |
|
} |
|
|
|
if (!line.empty() && (line.back() == '\\' || line.back() == '/')) { |
|
set_display(prompt); |
|
replace_last(line.back()); |
|
is_special_char = true; |
|
} |
|
} |
|
|
|
bool has_more = multiline_input; |
|
if (is_special_char) { |
|
replace_last(' '); |
|
pop_cursor(); |
|
|
|
char last = line.back(); |
|
line.pop_back(); |
|
if (last == '\\') { |
|
line += '\n'; |
|
fputc('\n', out); |
|
has_more = !has_more; |
|
} else { |
|
|
|
if (line.length() == 1 && line.back() == ' ') { |
|
line.clear(); |
|
pop_cursor(); |
|
} |
|
has_more = false; |
|
} |
|
} else { |
|
if (end_of_stream) { |
|
has_more = false; |
|
} else { |
|
line += '\n'; |
|
fputc('\n', out); |
|
} |
|
} |
|
|
|
fflush(out); |
|
return has_more; |
|
} |
|
|
|
static bool readline_simple(std::string & line, bool multiline_input) { |
|
#if defined(_WIN32) |
|
std::wstring wline; |
|
if (!std::getline(std::wcin, wline)) { |
|
|
|
line.clear(); |
|
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); |
|
return false; |
|
} |
|
|
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wline[0], (int)wline.size(), NULL, 0, NULL, NULL); |
|
line.resize(size_needed); |
|
WideCharToMultiByte(CP_UTF8, 0, &wline[0], (int)wline.size(), &line[0], size_needed, NULL, NULL); |
|
#else |
|
if (!std::getline(std::cin, line)) { |
|
|
|
line.clear(); |
|
return false; |
|
} |
|
#endif |
|
if (!line.empty()) { |
|
char last = line.back(); |
|
if (last == '/') { |
|
line.pop_back(); |
|
return false; |
|
} |
|
if (last == '\\') { |
|
line.pop_back(); |
|
multiline_input = !multiline_input; |
|
} |
|
} |
|
line += '\n'; |
|
|
|
|
|
return multiline_input; |
|
} |
|
|
|
bool readline(std::string & line, bool multiline_input) { |
|
set_display(user_input); |
|
|
|
if (simple_io) { |
|
return readline_simple(line, multiline_input); |
|
} |
|
return readline_advanced(line, multiline_input); |
|
} |
|
|
|
} |
|
|