| | #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); |
| | } |
| |
|
| | } |
| |
|