From f6496ab20b43be504fc439ff7ce0f6aacedd75c9 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:18:23 -0400 Subject: [PATCH 01/15] Run though clang-format --- linenoise.hpp | 3831 ++++++++++++++++++++++++++----------------------- 1 file changed, 2010 insertions(+), 1821 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index 42db2e9..3f6b5a5 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -12,26 +12,27 @@ * * Copyright (c) 2015 yhirose * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. * 2. 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. - * - * 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. + * + * 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. */ /* linenoise.h -- guerrilla line editing library against the idea that a @@ -77,11 +78,11 @@ * This software is provided 'as-is', without any express or implied * warranty. In no event will the author be held liable for any damages * arising from the use of this software. - * + * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: - * + * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be @@ -89,7 +90,7 @@ * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. - * + * * Jason Hood * jadoxa@yahoo.com.au */ @@ -97,7 +98,8 @@ /* * Win32_ANSI.h and Win32_ANSI.c * - * Derived from ANSI.c by Jason Hood, from his ansicon project (https://github.com/adoxa/ansicon), with modifications. + * Derived from ANSI.c by Jason Hood, from his ansicon project + * (https://github.com/adoxa/ansicon), with modifications. * * Copyright (c), Microsoft Open Technologies, Inc. * All rights reserved. @@ -110,23 +112,24 @@ * and/or other materials provided with the distribution. * 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 HOLDER 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. + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. */ #ifndef LINENOISE_HPP #define LINENOISE_HPP #ifndef _WIN32 +#include #include #include -#include #else #ifndef NOMINMAX #define NOMINMAX @@ -145,117 +148,112 @@ #pragma warning(push) #pragma warning(disable : 4996) #endif -#include -#include -#include -#include #include -#include -#include +#include #include #include -#include #include +#include +#include +#include +#include +#include +#include namespace linenoise { -typedef std::function&)> CompletionCallback; +typedef std::function &)> + CompletionCallback; #ifdef _WIN32 namespace ansi { -#define lenof(array) (sizeof(array)/sizeof(*(array))) - -typedef struct -{ - BYTE foreground; // ANSI base color (0 to 7; add 30) - BYTE background; // ANSI base color (0 to 7; add 40) - BYTE bold; // console FOREGROUND_INTENSITY bit - BYTE underline; // console BACKGROUND_INTENSITY bit - BYTE rvideo; // swap foreground/bold & background/underline - BYTE concealed; // set foreground/bold to background/underline - BYTE reverse; // swap console foreground & background attributes -} GRM, *PGRM; // Graphic Rendition Mode +#define lenof(array) (sizeof(array) / sizeof(*(array))) +typedef struct { + BYTE foreground; // ANSI base color (0 to 7; add 30) + BYTE background; // ANSI base color (0 to 7; add 40) + BYTE bold; // console FOREGROUND_INTENSITY bit + BYTE underline; // console BACKGROUND_INTENSITY bit + BYTE rvideo; // swap foreground/bold & background/underline + BYTE concealed; // set foreground/bold to background/underline + BYTE reverse; // swap console foreground & background attributes +} GRM, *PGRM; // Graphic Rendition Mode inline bool is_digit(char c) { return '0' <= c && c <= '9'; } // ========== Global variables and constants -HANDLE hConOut; // handle to CONOUT$ +HANDLE hConOut; // handle to CONOUT$ const char ESC = '\x1B'; // ESCape character const char BEL = '\x07'; -const char SO = '\x0E'; // Shift Out -const char SI = '\x0F'; // Shift In +const char SO = '\x0E'; // Shift Out +const char SI = '\x0F'; // Shift In const int MAX_ARG = 16; // max number of args in an escape sequence -int state; // automata state +int state; // automata state WCHAR prefix; // escape sequence prefix ( '[', ']' or '(' ); WCHAR prefix2; // secondary prefix ( '?' or '>' ); WCHAR suffix; // escape sequence suffix -int es_argc; // escape sequence args count -int es_argv[MAX_ARG]; // escape sequence args +int es_argc; // escape sequence args count +int es_argv[MAX_ARG]; // escape sequence args WCHAR Pt_arg[MAX_PATH * 2]; // text parameter for Operating System Command -int Pt_len; -BOOL shifted; - +int Pt_len; +BOOL shifted; // DEC Special Graphics Character Set from // http://vt100.net/docs/vt220-rm/table2-4.html // Some of these may not look right, depending on the font and code page (in // particular, the Control Pictures probably won't work at all). -const WCHAR G1[] = -{ - ' ', // _ - blank - L'\x2666', // ` - Black Diamond Suit - L'\x2592', // a - Medium Shade - L'\x2409', // b - HT - L'\x240c', // c - FF - L'\x240d', // d - CR - L'\x240a', // e - LF - L'\x00b0', // f - Degree Sign - L'\x00b1', // g - Plus-Minus Sign - L'\x2424', // h - NL - L'\x240b', // i - VT - L'\x2518', // j - Box Drawings Light Up And Left - L'\x2510', // k - Box Drawings Light Down And Left - L'\x250c', // l - Box Drawings Light Down And Right - L'\x2514', // m - Box Drawings Light Up And Right - L'\x253c', // n - Box Drawings Light Vertical And Horizontal - L'\x00af', // o - SCAN 1 - Macron - L'\x25ac', // p - SCAN 3 - Black Rectangle - L'\x2500', // q - SCAN 5 - Box Drawings Light Horizontal - L'_', // r - SCAN 7 - Low Line - L'_', // s - SCAN 9 - Low Line - L'\x251c', // t - Box Drawings Light Vertical And Right - L'\x2524', // u - Box Drawings Light Vertical And Left - L'\x2534', // v - Box Drawings Light Up And Horizontal - L'\x252c', // w - Box Drawings Light Down And Horizontal - L'\x2502', // x - Box Drawings Light Vertical - L'\x2264', // y - Less-Than Or Equal To - L'\x2265', // z - Greater-Than Or Equal To - L'\x03c0', // { - Greek Small Letter Pi - L'\x2260', // | - Not Equal To - L'\x00a3', // } - Pound Sign - L'\x00b7', // ~ - Middle Dot +const WCHAR G1[] = { + ' ', // _ - blank + L'\x2666', // ` - Black Diamond Suit + L'\x2592', // a - Medium Shade + L'\x2409', // b - HT + L'\x240c', // c - FF + L'\x240d', // d - CR + L'\x240a', // e - LF + L'\x00b0', // f - Degree Sign + L'\x00b1', // g - Plus-Minus Sign + L'\x2424', // h - NL + L'\x240b', // i - VT + L'\x2518', // j - Box Drawings Light Up And Left + L'\x2510', // k - Box Drawings Light Down And Left + L'\x250c', // l - Box Drawings Light Down And Right + L'\x2514', // m - Box Drawings Light Up And Right + L'\x253c', // n - Box Drawings Light Vertical And Horizontal + L'\x00af', // o - SCAN 1 - Macron + L'\x25ac', // p - SCAN 3 - Black Rectangle + L'\x2500', // q - SCAN 5 - Box Drawings Light Horizontal + L'_', // r - SCAN 7 - Low Line + L'_', // s - SCAN 9 - Low Line + L'\x251c', // t - Box Drawings Light Vertical And Right + L'\x2524', // u - Box Drawings Light Vertical And Left + L'\x2534', // v - Box Drawings Light Up And Horizontal + L'\x252c', // w - Box Drawings Light Down And Horizontal + L'\x2502', // x - Box Drawings Light Vertical + L'\x2264', // y - Less-Than Or Equal To + L'\x2265', // z - Greater-Than Or Equal To + L'\x03c0', // { - Greek Small Letter Pi + L'\x2260', // | - Not Equal To + L'\x00a3', // } - Pound Sign + L'\x00b7', // ~ - Middle Dot }; #define FIRST_G1 '_' -#define LAST_G1 '~' - +#define LAST_G1 '~' // color constants #define FOREGROUND_BLACK 0 -#define FOREGROUND_WHITE FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE +#define FOREGROUND_WHITE FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE #define BACKGROUND_BLACK 0 -#define BACKGROUND_WHITE BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE +#define BACKGROUND_WHITE BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE -const BYTE foregroundcolor[8] = - { +const BYTE foregroundcolor[8] = { FOREGROUND_BLACK, // black foreground FOREGROUND_RED, // red foreground FOREGROUND_GREEN, // green foreground @@ -264,30 +262,29 @@ const BYTE foregroundcolor[8] = FOREGROUND_BLUE | FOREGROUND_RED, // magenta foreground FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan foreground FOREGROUND_WHITE // white foreground - }; +}; -const BYTE backgroundcolor[8] = - { - BACKGROUND_BLACK, // black background - BACKGROUND_RED, // red background - BACKGROUND_GREEN, // green background +const BYTE backgroundcolor[8] = { + BACKGROUND_BLACK, // black background + BACKGROUND_RED, // red background + BACKGROUND_GREEN, // green background BACKGROUND_RED | BACKGROUND_GREEN, // yellow background - BACKGROUND_BLUE, // blue background + BACKGROUND_BLUE, // blue background BACKGROUND_BLUE | BACKGROUND_RED, // magenta background BACKGROUND_BLUE | BACKGROUND_GREEN, // cyan background - BACKGROUND_WHITE, // white background - }; - -const BYTE attr2ansi[8] = // map console attribute to ANSI number -{ - 0, // black - 4, // blue - 2, // green - 6, // cyan - 1, // red - 5, // magenta - 3, // yellow - 7 // white + BACKGROUND_WHITE, // white background +}; + +const BYTE attr2ansi[8] = // map console attribute to ANSI number + { + 0, // black + 4, // blue + 2, // green + 6, // cyan + 1, // red + 5, // magenta + 3, // yellow + 7 // white }; GRM grm; @@ -299,7 +296,7 @@ COORD SavePos; #define BUFFER_SIZE 2048 -int nCharInBuffer; +int nCharInBuffer; WCHAR ChBuffer[BUFFER_SIZE]; //----------------------------------------------------------------------------- @@ -307,12 +304,12 @@ WCHAR ChBuffer[BUFFER_SIZE]; // Writes the buffer to the console and empties it. //----------------------------------------------------------------------------- -inline void FlushBuffer(void) -{ - DWORD nWritten; - if (nCharInBuffer <= 0) return; - WriteConsoleW(hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL); - nCharInBuffer = 0; +inline void FlushBuffer(void) { + DWORD nWritten; + if (nCharInBuffer <= 0) + return; + WriteConsoleW(hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL); + nCharInBuffer = 0; } //----------------------------------------------------------------------------- @@ -320,13 +317,12 @@ inline void FlushBuffer(void) // Adds a character in the buffer. //----------------------------------------------------------------------------- -inline void PushBuffer(WCHAR c) -{ - if (shifted && c >= FIRST_G1 && c <= LAST_G1) - c = G1[c - FIRST_G1]; - ChBuffer[nCharInBuffer] = c; - if (++nCharInBuffer == BUFFER_SIZE) - FlushBuffer(); +inline void PushBuffer(WCHAR c) { + if (shifted && c >= FIRST_G1 && c <= LAST_G1) + c = G1[c - FIRST_G1]; + ChBuffer[nCharInBuffer] = c; + if (++nCharInBuffer == BUFFER_SIZE) + FlushBuffer(); } //----------------------------------------------------------------------------- @@ -334,23 +330,21 @@ inline void PushBuffer(WCHAR c) // Send the string to the input buffer. //----------------------------------------------------------------------------- -inline void SendSequence(LPCWSTR seq) -{ - DWORD out; - INPUT_RECORD in; - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - - in.EventType = KEY_EVENT; - in.Event.KeyEvent.bKeyDown = TRUE; - in.Event.KeyEvent.wRepeatCount = 1; - in.Event.KeyEvent.wVirtualKeyCode = 0; - in.Event.KeyEvent.wVirtualScanCode = 0; - in.Event.KeyEvent.dwControlKeyState = 0; - for (; *seq; ++seq) - { - in.Event.KeyEvent.uChar.UnicodeChar = *seq; - WriteConsoleInput(hStdIn, &in, 1, &out); - } +inline void SendSequence(LPCWSTR seq) { + DWORD out; + INPUT_RECORD in; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + + in.EventType = KEY_EVENT; + in.Event.KeyEvent.bKeyDown = TRUE; + in.Event.KeyEvent.wRepeatCount = 1; + in.Event.KeyEvent.wVirtualKeyCode = 0; + in.Event.KeyEvent.wVirtualScanCode = 0; + in.Event.KeyEvent.dwControlKeyState = 0; + for (; *seq; ++seq) { + in.Event.KeyEvent.uChar.UnicodeChar = *seq; + WriteConsoleInput(hStdIn, &in, 1, &out); + } } // ========== Print functions @@ -369,441 +363,478 @@ inline void SendSequence(LPCWSTR seq) // suffix = 'm' //----------------------------------------------------------------------------- -inline void InterpretEscSeq(void) -{ - int i; - WORD attribute; - CONSOLE_SCREEN_BUFFER_INFO Info; - CONSOLE_CURSOR_INFO CursInfo; - DWORD len, NumberOfCharsWritten; - COORD Pos; - SMALL_RECT Rect; - CHAR_INFO CharInfo; - - if (prefix == '[') - { - if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) - { - if (es_argc == 1 && es_argv[0] == 25) - { - GetConsoleCursorInfo(hConOut, &CursInfo); - CursInfo.bVisible = (suffix == 'h'); - SetConsoleCursorInfo(hConOut, &CursInfo); - return; - } +inline void InterpretEscSeq(void) { + int i; + WORD attribute; + CONSOLE_SCREEN_BUFFER_INFO Info; + CONSOLE_CURSOR_INFO CursInfo; + DWORD len, NumberOfCharsWritten; + COORD Pos; + SMALL_RECT Rect; + CHAR_INFO CharInfo; + + if (prefix == '[') { + if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) { + if (es_argc == 1 && es_argv[0] == 25) { + GetConsoleCursorInfo(hConOut, &CursInfo); + CursInfo.bVisible = (suffix == 'h'); + SetConsoleCursorInfo(hConOut, &CursInfo); + return; + } + } + // Ignore any other \e[? or \e[> sequences. + if (prefix2 != 0) + return; + + GetConsoleScreenBufferInfo(hConOut, &Info); + switch (suffix) { + case 'm': + if (es_argc == 0) + es_argv[es_argc++] = 0; + for (i = 0; i < es_argc; i++) { + if (30 <= es_argv[i] && es_argv[i] <= 37) + grm.foreground = es_argv[i] - 30; + else if (40 <= es_argv[i] && es_argv[i] <= 47) + grm.background = es_argv[i] - 40; + else + switch (es_argv[i]) { + case 0: + case 39: + case 49: { + WCHAR def[4]; + int a; + *def = '7'; + def[1] = '\0'; + GetEnvironmentVariableW(L"ANSICON_DEF", def, lenof(def)); + a = wcstol(def, NULL, 16); + grm.reverse = FALSE; + if (a < 0) { + grm.reverse = TRUE; + a = -a; } - // Ignore any other \e[? or \e[> sequences. - if (prefix2 != 0) - return; - - GetConsoleScreenBufferInfo(hConOut, &Info); - switch (suffix) - { - case 'm': - if (es_argc == 0) es_argv[es_argc++] = 0; - for (i = 0; i < es_argc; i++) - { - if (30 <= es_argv[i] && es_argv[i] <= 37) - grm.foreground = es_argv[i] - 30; - else if (40 <= es_argv[i] && es_argv[i] <= 47) - grm.background = es_argv[i] - 40; - else switch (es_argv[i]) - { - case 0: - case 39: - case 49: - { - WCHAR def[4]; - int a; - *def = '7'; def[1] = '\0'; - GetEnvironmentVariableW(L"ANSICON_DEF", def, lenof(def)); - a = wcstol(def, NULL, 16); - grm.reverse = FALSE; - if (a < 0) - { - grm.reverse = TRUE; - a = -a; - } - if (es_argv[i] != 49) - grm.foreground = attr2ansi[a & 7]; - if (es_argv[i] != 39) - grm.background = attr2ansi[(a >> 4) & 7]; - if (es_argv[i] == 0) - { - if (es_argc == 1) - { - grm.bold = a & FOREGROUND_INTENSITY; - grm.underline = a & BACKGROUND_INTENSITY; - } - else - { - grm.bold = 0; - grm.underline = 0; - } - grm.rvideo = 0; - grm.concealed = 0; - } - } - break; - - case 1: grm.bold = FOREGROUND_INTENSITY; break; - case 5: // blink - case 4: grm.underline = BACKGROUND_INTENSITY; break; - case 7: grm.rvideo = 1; break; - case 8: grm.concealed = 1; break; - case 21: // oops, this actually turns on double underline - case 22: grm.bold = 0; break; - case 25: - case 24: grm.underline = 0; break; - case 27: grm.rvideo = 0; break; - case 28: grm.concealed = 0; break; - } - } - if (grm.concealed) - { - if (grm.rvideo) - { - attribute = foregroundcolor[grm.foreground] - | backgroundcolor[grm.foreground]; - if (grm.bold) - attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - else - { - attribute = foregroundcolor[grm.background] - | backgroundcolor[grm.background]; - if (grm.underline) - attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - } - else if (grm.rvideo) - { - attribute = foregroundcolor[grm.background] - | backgroundcolor[grm.foreground]; - if (grm.bold) - attribute |= BACKGROUND_INTENSITY; - if (grm.underline) - attribute |= FOREGROUND_INTENSITY; - } - else - attribute = foregroundcolor[grm.foreground] | grm.bold - | backgroundcolor[grm.background] | grm.underline; - if (grm.reverse) - attribute = ((attribute >> 4) & 15) | ((attribute & 15) << 4); - SetConsoleTextAttribute(hConOut, attribute); - return; - - case 'J': - if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[J == ESC[0J - if (es_argc != 1) return; - switch (es_argv[0]) - { - case 0: // ESC[0J erase from cursor to end of display - len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X - + Info.dwSize.X - Info.dwCursorPosition.X - 1; - FillConsoleOutputCharacter(hConOut, ' ', len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1J erase from start to cursor. - Pos.X = 0; - Pos.Y = 0; - len = Info.dwCursorPosition.Y * Info.dwSize.X - + Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2J Clear screen and home cursor - Pos.X = 0; - Pos.Y = 0; - len = Info.dwSize.X * Info.dwSize.Y; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - SetConsoleCursorPosition(hConOut, Pos); - return; - - default: - return; - } - - case 'K': - if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[K == ESC[0K - if (es_argc != 1) return; - switch (es_argv[0]) - { - case 0: // ESC[0K Clear to end of line - len = Info.dwSize.X - Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1K Clear from start of line to cursor - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2K Clear whole line. - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', Info.dwSize.X, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwSize.X, Pos, - &NumberOfCharsWritten); - return; - - default: - return; - } - - case 'X': // ESC[#X Erase # characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[X == ESC[1X - if (es_argc != 1) return; - FillConsoleOutputCharacter(hConOut, ' ', es_argv[0], - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, es_argv[0], - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 'L': // ESC[#L Insert # blank lines. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[L == ESC[1L - if (es_argc != 1) return; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'M': // ESC[#M Delete # lines. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[M == ESC[1M - if (es_argc != 1) return; - if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) - es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'P': // ESC[#P Delete # characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[P == ESC[1P - if (es_argc != 1) return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X + es_argv[0]; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Info.dwCursorPosition, - &CharInfo); - return; - - case '@': // ESC[#@ Insert # blank characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ - if (es_argc != 1) return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1 - es_argv[0]; - Rect.Bottom = Info.dwCursorPosition.Y; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'k': // ESC[#k - case 'A': // ESC[#A Moves cursor up # lines - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[A == ESC[1A - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) Pos.Y = 0; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'e': // ESC[#e - case 'B': // ESC[#B Moves cursor down # lines - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[B == ESC[1B - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'a': // ESC[#a - case 'C': // ESC[#C Moves cursor forward # spaces - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[C == ESC[1C - if (es_argc != 1) return; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'j': // ESC[#j - case 'D': // ESC[#D Moves cursor back # spaces - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[D == ESC[1D - if (es_argc != 1) return; - Pos.X = Info.dwCursorPosition.X - es_argv[0]; - if (Pos.X < 0) Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'E': // ESC[#E Moves cursor down # lines, column 1. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[E == ESC[1E - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'F': // ESC[#F Moves cursor up # lines, column 1. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[F == ESC[1F - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) Pos.Y = 0; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case '`': // ESC[#` - case 'G': // ESC[#G Moves cursor column # in current row. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[G == ESC[1G - if (es_argc != 1) return; - Pos.X = es_argv[0] - 1; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - if (Pos.X < 0) Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'd': // ESC[#d Moves cursor row #, current column. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[d == ESC[1d - if (es_argc != 1) return; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'f': // ESC[#;#f - case 'H': // ESC[#;#H Moves cursor to line #, column # - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H - if (es_argc == 1) - es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H - if (es_argc > 2) return; - Pos.X = es_argv[1] - 1; - if (Pos.X < 0) Pos.X = 0; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 's': // ESC[s Saves cursor position for recall later - if (es_argc != 0) return; - SavePos = Info.dwCursorPosition; - return; - - case 'u': // ESC[u Return to saved cursor position - if (es_argc != 0) return; - SetConsoleCursorPosition(hConOut, SavePos); - return; - - case 'n': // ESC[#n Device status report - if (es_argc != 1) return; // ESC[n == ESC[0n -> ignored - switch (es_argv[0]) - { - case 5: // ESC[5n Report status - SendSequence(L"\33[0n"); // "OK" - return; - - case 6: // ESC[6n Report cursor position - { - WCHAR buf[32]; - swprintf(buf, 32, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1, - Info.dwCursorPosition.X + 1); - SendSequence(buf); - } - return; - - default: - return; - } - - case 't': // ESC[#t Window manipulation - if (es_argc != 1) return; - if (es_argv[0] == 21) // ESC[21t Report xterm window's title - { - WCHAR buf[MAX_PATH * 2]; - len = GetConsoleTitleW(buf + 3, lenof(buf) - 3 - 2); - // Too bad if it's too big or fails. - buf[0] = ESC; - buf[1] = ']'; - buf[2] = 'l'; - buf[3 + len] = ESC; - buf[3 + len + 1] = '\\'; - buf[3 + len + 2] = '\0'; - SendSequence(buf); - } - return; - - default: - return; - } - } - else // (prefix == ']') - { - // Ignore any \e]? or \e]> sequences. - if (prefix2 != 0) - return; - - if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST - { - SetConsoleTitleW(Pt_arg); + if (es_argv[i] != 49) + grm.foreground = attr2ansi[a & 7]; + if (es_argv[i] != 39) + grm.background = attr2ansi[(a >> 4) & 7]; + if (es_argv[i] == 0) { + if (es_argc == 1) { + grm.bold = a & FOREGROUND_INTENSITY; + grm.underline = a & BACKGROUND_INTENSITY; + } else { + grm.bold = 0; + grm.underline = 0; + } + grm.rvideo = 0; + grm.concealed = 0; } + } break; + + case 1: + grm.bold = FOREGROUND_INTENSITY; + break; + case 5: // blink + case 4: + grm.underline = BACKGROUND_INTENSITY; + break; + case 7: + grm.rvideo = 1; + break; + case 8: + grm.concealed = 1; + break; + case 21: // oops, this actually turns on double underline + case 22: + grm.bold = 0; + break; + case 25: + case 24: + grm.underline = 0; + break; + case 27: + grm.rvideo = 0; + break; + case 28: + grm.concealed = 0; + break; + } + } + if (grm.concealed) { + if (grm.rvideo) { + attribute = + foregroundcolor[grm.foreground] | backgroundcolor[grm.foreground]; + if (grm.bold) + attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } else { + attribute = + foregroundcolor[grm.background] | backgroundcolor[grm.background]; + if (grm.underline) + attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; } + } else if (grm.rvideo) { + attribute = + foregroundcolor[grm.background] | backgroundcolor[grm.foreground]; + if (grm.bold) + attribute |= BACKGROUND_INTENSITY; + if (grm.underline) + attribute |= FOREGROUND_INTENSITY; + } else + attribute = foregroundcolor[grm.foreground] | grm.bold | + backgroundcolor[grm.background] | grm.underline; + if (grm.reverse) + attribute = ((attribute >> 4) & 15) | ((attribute & 15) << 4); + SetConsoleTextAttribute(hConOut, attribute); + return; + + case 'J': + if (es_argc == 0) + es_argv[es_argc++] = 0; // ESC[J == ESC[0J + if (es_argc != 1) + return; + switch (es_argv[0]) { + case 0: // ESC[0J erase from cursor to end of display + len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X + + Info.dwSize.X - Info.dwCursorPosition.X - 1; + FillConsoleOutputCharacter(hConOut, ' ', len, Info.dwCursorPosition, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, + Info.dwCursorPosition, + &NumberOfCharsWritten); + return; + + case 1: // ESC[1J erase from start to cursor. + Pos.X = 0; + Pos.Y = 0; + len = Info.dwCursorPosition.Y * Info.dwSize.X + + Info.dwCursorPosition.X + 1; + FillConsoleOutputCharacter(hConOut, ' ', len, Pos, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, + &NumberOfCharsWritten); + return; + + case 2: // ESC[2J Clear screen and home cursor + Pos.X = 0; + Pos.Y = 0; + len = Info.dwSize.X * Info.dwSize.Y; + FillConsoleOutputCharacter(hConOut, ' ', len, Pos, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, + &NumberOfCharsWritten); + SetConsoleCursorPosition(hConOut, Pos); + return; + + default: + return; + } + + case 'K': + if (es_argc == 0) + es_argv[es_argc++] = 0; // ESC[K == ESC[0K + if (es_argc != 1) + return; + switch (es_argv[0]) { + case 0: // ESC[0K Clear to end of line + len = Info.dwSize.X - Info.dwCursorPosition.X + 1; + FillConsoleOutputCharacter(hConOut, ' ', len, Info.dwCursorPosition, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, + Info.dwCursorPosition, + &NumberOfCharsWritten); + return; + + case 1: // ESC[1K Clear from start of line to cursor + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + FillConsoleOutputCharacter(hConOut, ' ', Info.dwCursorPosition.X + 1, + Pos, &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, + Info.dwCursorPosition.X + 1, Pos, + &NumberOfCharsWritten); + return; + + case 2: // ESC[2K Clear whole line. + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + FillConsoleOutputCharacter(hConOut, ' ', Info.dwSize.X, Pos, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, Info.dwSize.X, + Pos, &NumberOfCharsWritten); + return; + + default: + return; + } + + case 'X': // ESC[#X Erase # characters. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[X == ESC[1X + if (es_argc != 1) + return; + FillConsoleOutputCharacter(hConOut, ' ', es_argv[0], + Info.dwCursorPosition, &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, es_argv[0], + Info.dwCursorPosition, &NumberOfCharsWritten); + return; + + case 'L': // ESC[#L Insert # blank lines. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[L == ESC[1L + if (es_argc != 1) + return; + Rect.Left = 0; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwSize.Y - 1; + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); + return; + + case 'M': // ESC[#M Delete # lines. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[M == ESC[1M + if (es_argc != 1) + return; + if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) + es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; + Rect.Left = 0; + Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwSize.Y - 1; + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); + return; + + case 'P': // ESC[#P Delete # characters. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[P == ESC[1P + if (es_argc != 1) + return; + if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) + es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; + Rect.Left = Info.dwCursorPosition.X + es_argv[0]; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Info.dwCursorPosition, + &CharInfo); + return; + + case '@': // ESC[#@ Insert # blank characters. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ + if (es_argc != 1) + return; + if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) + es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; + Rect.Left = Info.dwCursorPosition.X; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1 - es_argv[0]; + Rect.Bottom = Info.dwCursorPosition.Y; + Pos.X = Info.dwCursorPosition.X + es_argv[0]; + Pos.Y = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); + return; + + case 'k': // ESC[#k + case 'A': // ESC[#A Moves cursor up # lines + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[A == ESC[1A + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; + if (Pos.Y < 0) + Pos.Y = 0; + Pos.X = Info.dwCursorPosition.X; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'e': // ESC[#e + case 'B': // ESC[#B Moves cursor down # lines + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[B == ESC[1B + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + Pos.X = Info.dwCursorPosition.X; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'a': // ESC[#a + case 'C': // ESC[#C Moves cursor forward # spaces + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[C == ESC[1C + if (es_argc != 1) + return; + Pos.X = Info.dwCursorPosition.X + es_argv[0]; + if (Pos.X >= Info.dwSize.X) + Pos.X = Info.dwSize.X - 1; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'j': // ESC[#j + case 'D': // ESC[#D Moves cursor back # spaces + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[D == ESC[1D + if (es_argc != 1) + return; + Pos.X = Info.dwCursorPosition.X - es_argv[0]; + if (Pos.X < 0) + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'E': // ESC[#E Moves cursor down # lines, column 1. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[E == ESC[1E + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + Pos.X = 0; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'F': // ESC[#F Moves cursor up # lines, column 1. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[F == ESC[1F + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; + if (Pos.Y < 0) + Pos.Y = 0; + Pos.X = 0; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case '`': // ESC[#` + case 'G': // ESC[#G Moves cursor column # in current row. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[G == ESC[1G + if (es_argc != 1) + return; + Pos.X = es_argv[0] - 1; + if (Pos.X >= Info.dwSize.X) + Pos.X = Info.dwSize.X - 1; + if (Pos.X < 0) + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'd': // ESC[#d Moves cursor row #, current column. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[d == ESC[1d + if (es_argc != 1) + return; + Pos.Y = es_argv[0] - 1; + if (Pos.Y < 0) + Pos.Y = 0; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'f': // ESC[#;#f + case 'H': // ESC[#;#H Moves cursor to line #, column # + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H + if (es_argc == 1) + es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H + if (es_argc > 2) + return; + Pos.X = es_argv[1] - 1; + if (Pos.X < 0) + Pos.X = 0; + if (Pos.X >= Info.dwSize.X) + Pos.X = Info.dwSize.X - 1; + Pos.Y = es_argv[0] - 1; + if (Pos.Y < 0) + Pos.Y = 0; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 's': // ESC[s Saves cursor position for recall later + if (es_argc != 0) + return; + SavePos = Info.dwCursorPosition; + return; + + case 'u': // ESC[u Return to saved cursor position + if (es_argc != 0) + return; + SetConsoleCursorPosition(hConOut, SavePos); + return; + + case 'n': // ESC[#n Device status report + if (es_argc != 1) + return; // ESC[n == ESC[0n -> ignored + switch (es_argv[0]) { + case 5: // ESC[5n Report status + SendSequence(L"\33[0n"); // "OK" + return; + + case 6: // ESC[6n Report cursor position + { + WCHAR buf[32]; + swprintf(buf, 32, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1, + Info.dwCursorPosition.X + 1); + SendSequence(buf); + } + return; + + default: + return; + } + + case 't': // ESC[#t Window manipulation + if (es_argc != 1) + return; + if (es_argv[0] == 21) // ESC[21t Report xterm window's title + { + WCHAR buf[MAX_PATH * 2]; + len = GetConsoleTitleW(buf + 3, lenof(buf) - 3 - 2); + // Too bad if it's too big or fails. + buf[0] = ESC; + buf[1] = ']'; + buf[2] = 'l'; + buf[3 + len] = ESC; + buf[3 + len + 1] = '\\'; + buf[3 + len + 2] = '\0'; + SendSequence(buf); + } + return; + + default: + return; + } + } else // (prefix == ']') + { + // Ignore any \e]? or \e]> sequences. + if (prefix2 != 0) + return; + + if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST + { + SetConsoleTitleW(Pt_arg); + } + } } //----------------------------------------------------------------------------- @@ -815,116 +846,95 @@ inline void InterpretEscSeq(void) // the last arguments are processed (no es_argv[] overflow). //----------------------------------------------------------------------------- -inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten) -{ - DWORD i; - LPCSTR s; - - if (hDev != hConOut) // reinit if device has changed - { - hConOut = hDev; - state = 1; +inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, + DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten) { + DWORD i; + LPCSTR s; + + if (hDev != hConOut) // reinit if device has changed + { + hConOut = hDev; + state = 1; + shifted = FALSE; + } + for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) { + if (state == 1) { + if (*s == ESC) + state = 2; + else if (*s == SO) + shifted = TRUE; + else if (*s == SI) shifted = FALSE; - } - for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) - { - if (state == 1) - { - if (*s == ESC) state = 2; - else if (*s == SO) shifted = TRUE; - else if (*s == SI) shifted = FALSE; - else PushBuffer(*s); - } - else if (state == 2) - { - if (*s == ESC); // \e\e...\e == \e - else if ((*s == '[') || (*s == ']')) - { - FlushBuffer(); - prefix = *s; - prefix2 = 0; - state = 3; - Pt_len = 0; - *Pt_arg = '\0'; - } - else if (*s == ')' || *s == '(') state = 6; - else state = 1; - } - else if (state == 3) - { - if (is_digit(*s)) - { - es_argc = 0; - es_argv[0] = *s - '0'; - state = 4; - } - else if (*s == ';') - { - es_argc = 1; - es_argv[0] = 0; - es_argv[1] = 0; - state = 4; - } - else if (*s == '?' || *s == '>') - { - prefix2 = *s; - } - else - { - es_argc = 0; - suffix = *s; - InterpretEscSeq(); - state = 1; - } - } - else if (state == 4) - { - if (is_digit(*s)) - { - es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); - } - else if (*s == ';') - { - if (es_argc < MAX_ARG - 1) es_argc++; - es_argv[es_argc] = 0; - if (prefix == ']') - state = 5; - } - else - { - es_argc++; - suffix = *s; - InterpretEscSeq(); - state = 1; - } - } - else if (state == 5) - { - if (*s == BEL) - { - Pt_arg[Pt_len] = '\0'; - InterpretEscSeq(); - state = 1; - } - else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) - { - Pt_arg[--Pt_len] = '\0'; - InterpretEscSeq(); - state = 1; - } - else if (Pt_len < lenof(Pt_arg) - 1) - Pt_arg[Pt_len++] = *s; - } - else if (state == 6) - { - // Ignore it (ESC ) 0 is implicit; nothing else is supported). - state = 1; - } - } - FlushBuffer(); - if (lpNumberOfBytesWritten != NULL) - *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; - return (i == 0); + else + PushBuffer(*s); + } else if (state == 2) { + if (*s == ESC) + ; // \e\e...\e == \e + else if ((*s == '[') || (*s == ']')) { + FlushBuffer(); + prefix = *s; + prefix2 = 0; + state = 3; + Pt_len = 0; + *Pt_arg = '\0'; + } else if (*s == ')' || *s == '(') + state = 6; + else + state = 1; + } else if (state == 3) { + if (is_digit(*s)) { + es_argc = 0; + es_argv[0] = *s - '0'; + state = 4; + } else if (*s == ';') { + es_argc = 1; + es_argv[0] = 0; + es_argv[1] = 0; + state = 4; + } else if (*s == '?' || *s == '>') { + prefix2 = *s; + } else { + es_argc = 0; + suffix = *s; + InterpretEscSeq(); + state = 1; + } + } else if (state == 4) { + if (is_digit(*s)) { + es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); + } else if (*s == ';') { + if (es_argc < MAX_ARG - 1) + es_argc++; + es_argv[es_argc] = 0; + if (prefix == ']') + state = 5; + } else { + es_argc++; + suffix = *s; + InterpretEscSeq(); + state = 1; + } + } else if (state == 5) { + if (*s == BEL) { + Pt_arg[Pt_len] = '\0'; + InterpretEscSeq(); + state = 1; + } else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) { + Pt_arg[--Pt_len] = '\0'; + InterpretEscSeq(); + state = 1; + } else if (Pt_len < lenof(Pt_arg) - 1) + Pt_arg[Pt_len++] = *s; + } else if (state == 6) { + // Ignore it (ESC ) 0 is implicit; nothing else is supported). + state = 1; + } + } + FlushBuffer(); + if (lpNumberOfBytesWritten != NULL) + *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; + return (i == 0); } } // namespace ansi @@ -934,142 +944,151 @@ HANDLE hIn; DWORD consolemodeIn = 0; inline int win32read(int *c) { - DWORD foo; - INPUT_RECORD b; - KEY_EVENT_RECORD e; - BOOL altgr; - - while (1) { - if (!ReadConsoleInput(hIn, &b, 1, &foo)) return 0; - if (!foo) return 0; - - if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { - - e = b.Event.KeyEvent; - *c = b.Event.KeyEvent.uChar.AsciiChar; - - altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); - - if (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && !altgr) { - - /* Ctrl+Key */ - switch (*c) { - case 'D': - *c = 4; - return 1; - case 'C': - *c = 3; - return 1; - case 'H': - *c = 8; - return 1; - case 'T': - *c = 20; - return 1; - case 'B': /* ctrl-b, left_arrow */ - *c = 2; - return 1; - case 'F': /* ctrl-f right_arrow*/ - *c = 6; - return 1; - case 'P': /* ctrl-p up_arrow*/ - *c = 16; - return 1; - case 'N': /* ctrl-n down_arrow*/ - *c = 14; - return 1; - case 'U': /* Ctrl+u, delete the whole line. */ - *c = 21; - return 1; - case 'K': /* Ctrl+k, delete from current to end of line. */ - *c = 11; - return 1; - case 'A': /* Ctrl+a, go to the start of the line */ - *c = 1; - return 1; - case 'E': /* ctrl+e, go to the end of the line */ - *c = 5; - return 1; - } - - /* Other Ctrl+KEYs ignored */ - } else { - - switch (e.wVirtualKeyCode) { - - case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ - *c = 3; - return 1; - case VK_RETURN: /* enter */ - *c = 13; - return 1; - case VK_LEFT: /* left */ - *c = 2; - return 1; - case VK_RIGHT: /* right */ - *c = 6; - return 1; - case VK_UP: /* up */ - *c = 16; - return 1; - case VK_DOWN: /* down */ - *c = 14; - return 1; - case VK_HOME: - *c = 1; - return 1; - case VK_END: - *c = 5; - return 1; - case VK_BACK: - *c = 8; - return 1; - case VK_DELETE: - *c = 4; /* same as Ctrl+D above */ - return 1; - default: - if (*c) return 1; - } - } + DWORD foo; + INPUT_RECORD b; + KEY_EVENT_RECORD e; + BOOL altgr; + + while (1) { + if (!ReadConsoleInput(hIn, &b, 1, &foo)) + return 0; + if (!foo) + return 0; + + if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { + + e = b.Event.KeyEvent; + *c = b.Event.KeyEvent.uChar.AsciiChar; + + altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + + if (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && + !altgr) { + + /* Ctrl+Key */ + switch (*c) { + case 'D': + *c = 4; + return 1; + case 'C': + *c = 3; + return 1; + case 'H': + *c = 8; + return 1; + case 'T': + *c = 20; + return 1; + case 'B': /* ctrl-b, left_arrow */ + *c = 2; + return 1; + case 'F': /* ctrl-f right_arrow*/ + *c = 6; + return 1; + case 'P': /* ctrl-p up_arrow*/ + *c = 16; + return 1; + case 'N': /* ctrl-n down_arrow*/ + *c = 14; + return 1; + case 'U': /* Ctrl+u, delete the whole line. */ + *c = 21; + return 1; + case 'K': /* Ctrl+k, delete from current to end of line. */ + *c = 11; + return 1; + case 'A': /* Ctrl+a, go to the start of the line */ + *c = 1; + return 1; + case 'E': /* ctrl+e, go to the end of the line */ + *c = 5; + return 1; + } + + /* Other Ctrl+KEYs ignored */ + } else { + + switch (e.wVirtualKeyCode) { + + case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ + *c = 3; + return 1; + case VK_RETURN: /* enter */ + *c = 13; + return 1; + case VK_LEFT: /* left */ + *c = 2; + return 1; + case VK_RIGHT: /* right */ + *c = 6; + return 1; + case VK_UP: /* up */ + *c = 16; + return 1; + case VK_DOWN: /* down */ + *c = 14; + return 1; + case VK_HOME: + *c = 1; + return 1; + case VK_END: + *c = 5; + return 1; + case VK_BACK: + *c = 8; + return 1; + case VK_DELETE: + *c = 4; /* same as Ctrl+D above */ + return 1; + default: + if (*c) + return 1; } + } } + } - return -1; /* Makes compiler happy */ + return -1; /* Makes compiler happy */ } inline int win32_write(int fd, const void *buffer, unsigned int count) { - if (fd == _fileno(stdout)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_OUTPUT_HANDLE), buffer, (DWORD)count, &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } - } else if (fd == _fileno(stderr)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_ERROR_HANDLE), buffer, (DWORD)count, &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } + if (fd == _fileno(stdout)) { + DWORD bytesWritten = 0; + if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_OUTPUT_HANDLE), + buffer, (DWORD)count, + &bytesWritten)) { + return (int)bytesWritten; } else { - return _write(fd, buffer, count); + errno = GetLastError(); + return 0; } + } else if (fd == _fileno(stderr)) { + DWORD bytesWritten = 0; + if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_ERROR_HANDLE), + buffer, (DWORD)count, + &bytesWritten)) { + return (int)bytesWritten; + } else { + errno = GetLastError(); + return 0; + } + } else { + return _write(fd, buffer, count); + } } #endif // _WIN32 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 -static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +static const char *unsupported_term[] = {"dumb", "cons25", "emacs", NULL}; static CompletionCallback completionCallback; #ifndef _WIN32 static struct termios orig_termios; /* In order to restore at exit.*/ #endif -static bool rawmode = false; /* For atexit() function to check if restore is needed*/ -static bool mlmode = false; /* Multi line mode. Default is single line. */ +static bool rawmode = + false; /* For atexit() function to check if restore is needed*/ +static bool mlmode = false; /* Multi line mode. Default is single line. */ static bool atexit_registered = false; /* Register atexit just 1 time. */ static size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static std::vector history; @@ -1078,39 +1097,39 @@ static std::vector history; * We pass this state to functions implementing specific editing * functionalities. */ struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - int buflen; /* Edited line buffer size. */ - std::string prompt; /* Prompt to display. */ - int pos; /* Current cursor position. */ - int oldcolpos; /* Previous refresh cursor column position. */ - int len; /* Current edited line length. */ - int cols; /* Number of columns in terminal. */ - int maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + int buflen; /* Edited line buffer size. */ + std::string prompt; /* Prompt to display. */ + int pos; /* Current cursor position. */ + int oldcolpos; /* Previous refresh cursor column position. */ + int len; /* Current edited line length. */ + int cols; /* Number of columns in terminal. */ + int maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ }; enum KEY_ACTION { - KEY_NULL = 0, /* NULL */ - CTRL_A = 1, /* Ctrl+a */ - CTRL_B = 2, /* Ctrl-b */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_E = 5, /* Ctrl-e */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_K = 11, /* Ctrl+k */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_N = 14, /* Ctrl-n */ - CTRL_P = 16, /* Ctrl-p */ - CTRL_T = 20, /* Ctrl-t */ - CTRL_U = 21, /* Ctrl+u */ - CTRL_W = 23, /* Ctrl+w */ - ESC = 27, /* Escape */ - BACKSPACE = 127 /* Backspace */ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ }; void linenoiseAtExit(void); @@ -1120,557 +1139,689 @@ void refreshLine(struct linenoiseState *l); /* ============================ UTF8 utilities ============================== */ static unsigned long unicodeWideCharTable[][2] = { - { 0x1100, 0x115F }, { 0x2329, 0x232A }, { 0x2E80, 0x2E99, }, { 0x2E9B, 0x2EF3, }, - { 0x2F00, 0x2FD5, }, { 0x2FF0, 0x2FFB, }, { 0x3000, 0x303E, }, { 0x3041, 0x3096, }, - { 0x3099, 0x30FF, }, { 0x3105, 0x312D, }, { 0x3131, 0x318E, }, { 0x3190, 0x31BA, }, - { 0x31C0, 0x31E3, }, { 0x31F0, 0x321E, }, { 0x3220, 0x3247, }, { 0x3250, 0x4DBF, }, - { 0x4E00, 0xA48C, }, { 0xA490, 0xA4C6, }, { 0xA960, 0xA97C, }, { 0xAC00, 0xD7A3, }, - { 0xF900, 0xFAFF, }, { 0xFE10, 0xFE19, }, { 0xFE30, 0xFE52, }, { 0xFE54, 0xFE66, }, - { 0xFE68, 0xFE6B, }, { 0xFF01, 0xFFE6, }, - { 0x1B000, 0x1B001, }, { 0x1F200, 0x1F202, }, { 0x1F210, 0x1F23A, }, - { 0x1F240, 0x1F248, }, { 0x1F250, 0x1F251, }, { 0x20000, 0x3FFFD, }, + {0x1100, 0x115F}, + {0x2329, 0x232A}, + { + 0x2E80, + 0x2E99, + }, + { + 0x2E9B, + 0x2EF3, + }, + { + 0x2F00, + 0x2FD5, + }, + { + 0x2FF0, + 0x2FFB, + }, + { + 0x3000, + 0x303E, + }, + { + 0x3041, + 0x3096, + }, + { + 0x3099, + 0x30FF, + }, + { + 0x3105, + 0x312D, + }, + { + 0x3131, + 0x318E, + }, + { + 0x3190, + 0x31BA, + }, + { + 0x31C0, + 0x31E3, + }, + { + 0x31F0, + 0x321E, + }, + { + 0x3220, + 0x3247, + }, + { + 0x3250, + 0x4DBF, + }, + { + 0x4E00, + 0xA48C, + }, + { + 0xA490, + 0xA4C6, + }, + { + 0xA960, + 0xA97C, + }, + { + 0xAC00, + 0xD7A3, + }, + { + 0xF900, + 0xFAFF, + }, + { + 0xFE10, + 0xFE19, + }, + { + 0xFE30, + 0xFE52, + }, + { + 0xFE54, + 0xFE66, + }, + { + 0xFE68, + 0xFE6B, + }, + { + 0xFF01, + 0xFFE6, + }, + { + 0x1B000, + 0x1B001, + }, + { + 0x1F200, + 0x1F202, + }, + { + 0x1F210, + 0x1F23A, + }, + { + 0x1F240, + 0x1F248, + }, + { + 0x1F250, + 0x1F251, + }, + { + 0x20000, + 0x3FFFD, + }, }; -static int unicodeWideCharTableSize = sizeof(unicodeWideCharTable) / sizeof(unicodeWideCharTable[0]); +static int unicodeWideCharTableSize = + sizeof(unicodeWideCharTable) / sizeof(unicodeWideCharTable[0]); -static int unicodeIsWideChar(unsigned long cp) -{ - int i; - for (i = 0; i < unicodeWideCharTableSize; i++) { - if (unicodeWideCharTable[i][0] <= cp && cp <= unicodeWideCharTable[i][1]) { - return 1; - } +static int unicodeIsWideChar(unsigned long cp) { + int i; + for (i = 0; i < unicodeWideCharTableSize; i++) { + if (unicodeWideCharTable[i][0] <= cp && cp <= unicodeWideCharTable[i][1]) { + return 1; } - return 0; + } + return 0; } static unsigned long unicodeCombiningCharTable[] = { - 0x0300,0x0301,0x0302,0x0303,0x0304,0x0305,0x0306,0x0307, - 0x0308,0x0309,0x030A,0x030B,0x030C,0x030D,0x030E,0x030F, - 0x0310,0x0311,0x0312,0x0313,0x0314,0x0315,0x0316,0x0317, - 0x0318,0x0319,0x031A,0x031B,0x031C,0x031D,0x031E,0x031F, - 0x0320,0x0321,0x0322,0x0323,0x0324,0x0325,0x0326,0x0327, - 0x0328,0x0329,0x032A,0x032B,0x032C,0x032D,0x032E,0x032F, - 0x0330,0x0331,0x0332,0x0333,0x0334,0x0335,0x0336,0x0337, - 0x0338,0x0339,0x033A,0x033B,0x033C,0x033D,0x033E,0x033F, - 0x0340,0x0341,0x0342,0x0343,0x0344,0x0345,0x0346,0x0347, - 0x0348,0x0349,0x034A,0x034B,0x034C,0x034D,0x034E,0x034F, - 0x0350,0x0351,0x0352,0x0353,0x0354,0x0355,0x0356,0x0357, - 0x0358,0x0359,0x035A,0x035B,0x035C,0x035D,0x035E,0x035F, - 0x0360,0x0361,0x0362,0x0363,0x0364,0x0365,0x0366,0x0367, - 0x0368,0x0369,0x036A,0x036B,0x036C,0x036D,0x036E,0x036F, - 0x0483,0x0484,0x0485,0x0486,0x0487,0x0591,0x0592,0x0593, - 0x0594,0x0595,0x0596,0x0597,0x0598,0x0599,0x059A,0x059B, - 0x059C,0x059D,0x059E,0x059F,0x05A0,0x05A1,0x05A2,0x05A3, - 0x05A4,0x05A5,0x05A6,0x05A7,0x05A8,0x05A9,0x05AA,0x05AB, - 0x05AC,0x05AD,0x05AE,0x05AF,0x05B0,0x05B1,0x05B2,0x05B3, - 0x05B4,0x05B5,0x05B6,0x05B7,0x05B8,0x05B9,0x05BA,0x05BB, - 0x05BC,0x05BD,0x05BF,0x05C1,0x05C2,0x05C4,0x05C5,0x05C7, - 0x0610,0x0611,0x0612,0x0613,0x0614,0x0615,0x0616,0x0617, - 0x0618,0x0619,0x061A,0x064B,0x064C,0x064D,0x064E,0x064F, - 0x0650,0x0651,0x0652,0x0653,0x0654,0x0655,0x0656,0x0657, - 0x0658,0x0659,0x065A,0x065B,0x065C,0x065D,0x065E,0x065F, - 0x0670,0x06D6,0x06D7,0x06D8,0x06D9,0x06DA,0x06DB,0x06DC, - 0x06DF,0x06E0,0x06E1,0x06E2,0x06E3,0x06E4,0x06E7,0x06E8, - 0x06EA,0x06EB,0x06EC,0x06ED,0x0711,0x0730,0x0731,0x0732, - 0x0733,0x0734,0x0735,0x0736,0x0737,0x0738,0x0739,0x073A, - 0x073B,0x073C,0x073D,0x073E,0x073F,0x0740,0x0741,0x0742, - 0x0743,0x0744,0x0745,0x0746,0x0747,0x0748,0x0749,0x074A, - 0x07A6,0x07A7,0x07A8,0x07A9,0x07AA,0x07AB,0x07AC,0x07AD, - 0x07AE,0x07AF,0x07B0,0x07EB,0x07EC,0x07ED,0x07EE,0x07EF, - 0x07F0,0x07F1,0x07F2,0x07F3,0x0816,0x0817,0x0818,0x0819, - 0x081B,0x081C,0x081D,0x081E,0x081F,0x0820,0x0821,0x0822, - 0x0823,0x0825,0x0826,0x0827,0x0829,0x082A,0x082B,0x082C, - 0x082D,0x0859,0x085A,0x085B,0x08E3,0x08E4,0x08E5,0x08E6, - 0x08E7,0x08E8,0x08E9,0x08EA,0x08EB,0x08EC,0x08ED,0x08EE, - 0x08EF,0x08F0,0x08F1,0x08F2,0x08F3,0x08F4,0x08F5,0x08F6, - 0x08F7,0x08F8,0x08F9,0x08FA,0x08FB,0x08FC,0x08FD,0x08FE, - 0x08FF,0x0900,0x0901,0x0902,0x093A,0x093C,0x0941,0x0942, - 0x0943,0x0944,0x0945,0x0946,0x0947,0x0948,0x094D,0x0951, - 0x0952,0x0953,0x0954,0x0955,0x0956,0x0957,0x0962,0x0963, - 0x0981,0x09BC,0x09C1,0x09C2,0x09C3,0x09C4,0x09CD,0x09E2, - 0x09E3,0x0A01,0x0A02,0x0A3C,0x0A41,0x0A42,0x0A47,0x0A48, - 0x0A4B,0x0A4C,0x0A4D,0x0A51,0x0A70,0x0A71,0x0A75,0x0A81, - 0x0A82,0x0ABC,0x0AC1,0x0AC2,0x0AC3,0x0AC4,0x0AC5,0x0AC7, - 0x0AC8,0x0ACD,0x0AE2,0x0AE3,0x0B01,0x0B3C,0x0B3F,0x0B41, - 0x0B42,0x0B43,0x0B44,0x0B4D,0x0B56,0x0B62,0x0B63,0x0B82, - 0x0BC0,0x0BCD,0x0C00,0x0C3E,0x0C3F,0x0C40,0x0C46,0x0C47, - 0x0C48,0x0C4A,0x0C4B,0x0C4C,0x0C4D,0x0C55,0x0C56,0x0C62, - 0x0C63,0x0C81,0x0CBC,0x0CBF,0x0CC6,0x0CCC,0x0CCD,0x0CE2, - 0x0CE3,0x0D01,0x0D41,0x0D42,0x0D43,0x0D44,0x0D4D,0x0D62, - 0x0D63,0x0DCA,0x0DD2,0x0DD3,0x0DD4,0x0DD6,0x0E31,0x0E34, - 0x0E35,0x0E36,0x0E37,0x0E38,0x0E39,0x0E3A,0x0E47,0x0E48, - 0x0E49,0x0E4A,0x0E4B,0x0E4C,0x0E4D,0x0E4E,0x0EB1,0x0EB4, - 0x0EB5,0x0EB6,0x0EB7,0x0EB8,0x0EB9,0x0EBB,0x0EBC,0x0EC8, - 0x0EC9,0x0ECA,0x0ECB,0x0ECC,0x0ECD,0x0F18,0x0F19,0x0F35, - 0x0F37,0x0F39,0x0F71,0x0F72,0x0F73,0x0F74,0x0F75,0x0F76, - 0x0F77,0x0F78,0x0F79,0x0F7A,0x0F7B,0x0F7C,0x0F7D,0x0F7E, - 0x0F80,0x0F81,0x0F82,0x0F83,0x0F84,0x0F86,0x0F87,0x0F8D, - 0x0F8E,0x0F8F,0x0F90,0x0F91,0x0F92,0x0F93,0x0F94,0x0F95, - 0x0F96,0x0F97,0x0F99,0x0F9A,0x0F9B,0x0F9C,0x0F9D,0x0F9E, - 0x0F9F,0x0FA0,0x0FA1,0x0FA2,0x0FA3,0x0FA4,0x0FA5,0x0FA6, - 0x0FA7,0x0FA8,0x0FA9,0x0FAA,0x0FAB,0x0FAC,0x0FAD,0x0FAE, - 0x0FAF,0x0FB0,0x0FB1,0x0FB2,0x0FB3,0x0FB4,0x0FB5,0x0FB6, - 0x0FB7,0x0FB8,0x0FB9,0x0FBA,0x0FBB,0x0FBC,0x0FC6,0x102D, - 0x102E,0x102F,0x1030,0x1032,0x1033,0x1034,0x1035,0x1036, - 0x1037,0x1039,0x103A,0x103D,0x103E,0x1058,0x1059,0x105E, - 0x105F,0x1060,0x1071,0x1072,0x1073,0x1074,0x1082,0x1085, - 0x1086,0x108D,0x109D,0x135D,0x135E,0x135F,0x1712,0x1713, - 0x1714,0x1732,0x1733,0x1734,0x1752,0x1753,0x1772,0x1773, - 0x17B4,0x17B5,0x17B7,0x17B8,0x17B9,0x17BA,0x17BB,0x17BC, - 0x17BD,0x17C6,0x17C9,0x17CA,0x17CB,0x17CC,0x17CD,0x17CE, - 0x17CF,0x17D0,0x17D1,0x17D2,0x17D3,0x17DD,0x180B,0x180C, - 0x180D,0x18A9,0x1920,0x1921,0x1922,0x1927,0x1928,0x1932, - 0x1939,0x193A,0x193B,0x1A17,0x1A18,0x1A1B,0x1A56,0x1A58, - 0x1A59,0x1A5A,0x1A5B,0x1A5C,0x1A5D,0x1A5E,0x1A60,0x1A62, - 0x1A65,0x1A66,0x1A67,0x1A68,0x1A69,0x1A6A,0x1A6B,0x1A6C, - 0x1A73,0x1A74,0x1A75,0x1A76,0x1A77,0x1A78,0x1A79,0x1A7A, - 0x1A7B,0x1A7C,0x1A7F,0x1AB0,0x1AB1,0x1AB2,0x1AB3,0x1AB4, - 0x1AB5,0x1AB6,0x1AB7,0x1AB8,0x1AB9,0x1ABA,0x1ABB,0x1ABC, - 0x1ABD,0x1B00,0x1B01,0x1B02,0x1B03,0x1B34,0x1B36,0x1B37, - 0x1B38,0x1B39,0x1B3A,0x1B3C,0x1B42,0x1B6B,0x1B6C,0x1B6D, - 0x1B6E,0x1B6F,0x1B70,0x1B71,0x1B72,0x1B73,0x1B80,0x1B81, - 0x1BA2,0x1BA3,0x1BA4,0x1BA5,0x1BA8,0x1BA9,0x1BAB,0x1BAC, - 0x1BAD,0x1BE6,0x1BE8,0x1BE9,0x1BED,0x1BEF,0x1BF0,0x1BF1, - 0x1C2C,0x1C2D,0x1C2E,0x1C2F,0x1C30,0x1C31,0x1C32,0x1C33, - 0x1C36,0x1C37,0x1CD0,0x1CD1,0x1CD2,0x1CD4,0x1CD5,0x1CD6, - 0x1CD7,0x1CD8,0x1CD9,0x1CDA,0x1CDB,0x1CDC,0x1CDD,0x1CDE, - 0x1CDF,0x1CE0,0x1CE2,0x1CE3,0x1CE4,0x1CE5,0x1CE6,0x1CE7, - 0x1CE8,0x1CED,0x1CF4,0x1CF8,0x1CF9,0x1DC0,0x1DC1,0x1DC2, - 0x1DC3,0x1DC4,0x1DC5,0x1DC6,0x1DC7,0x1DC8,0x1DC9,0x1DCA, - 0x1DCB,0x1DCC,0x1DCD,0x1DCE,0x1DCF,0x1DD0,0x1DD1,0x1DD2, - 0x1DD3,0x1DD4,0x1DD5,0x1DD6,0x1DD7,0x1DD8,0x1DD9,0x1DDA, - 0x1DDB,0x1DDC,0x1DDD,0x1DDE,0x1DDF,0x1DE0,0x1DE1,0x1DE2, - 0x1DE3,0x1DE4,0x1DE5,0x1DE6,0x1DE7,0x1DE8,0x1DE9,0x1DEA, - 0x1DEB,0x1DEC,0x1DED,0x1DEE,0x1DEF,0x1DF0,0x1DF1,0x1DF2, - 0x1DF3,0x1DF4,0x1DF5,0x1DFC,0x1DFD,0x1DFE,0x1DFF,0x20D0, - 0x20D1,0x20D2,0x20D3,0x20D4,0x20D5,0x20D6,0x20D7,0x20D8, - 0x20D9,0x20DA,0x20DB,0x20DC,0x20E1,0x20E5,0x20E6,0x20E7, - 0x20E8,0x20E9,0x20EA,0x20EB,0x20EC,0x20ED,0x20EE,0x20EF, - 0x20F0,0x2CEF,0x2CF0,0x2CF1,0x2D7F,0x2DE0,0x2DE1,0x2DE2, - 0x2DE3,0x2DE4,0x2DE5,0x2DE6,0x2DE7,0x2DE8,0x2DE9,0x2DEA, - 0x2DEB,0x2DEC,0x2DED,0x2DEE,0x2DEF,0x2DF0,0x2DF1,0x2DF2, - 0x2DF3,0x2DF4,0x2DF5,0x2DF6,0x2DF7,0x2DF8,0x2DF9,0x2DFA, - 0x2DFB,0x2DFC,0x2DFD,0x2DFE,0x2DFF,0x302A,0x302B,0x302C, - 0x302D,0x3099,0x309A,0xA66F,0xA674,0xA675,0xA676,0xA677, - 0xA678,0xA679,0xA67A,0xA67B,0xA67C,0xA67D,0xA69E,0xA69F, - 0xA6F0,0xA6F1,0xA802,0xA806,0xA80B,0xA825,0xA826,0xA8C4, - 0xA8E0,0xA8E1,0xA8E2,0xA8E3,0xA8E4,0xA8E5,0xA8E6,0xA8E7, - 0xA8E8,0xA8E9,0xA8EA,0xA8EB,0xA8EC,0xA8ED,0xA8EE,0xA8EF, - 0xA8F0,0xA8F1,0xA926,0xA927,0xA928,0xA929,0xA92A,0xA92B, - 0xA92C,0xA92D,0xA947,0xA948,0xA949,0xA94A,0xA94B,0xA94C, - 0xA94D,0xA94E,0xA94F,0xA950,0xA951,0xA980,0xA981,0xA982, - 0xA9B3,0xA9B6,0xA9B7,0xA9B8,0xA9B9,0xA9BC,0xA9E5,0xAA29, - 0xAA2A,0xAA2B,0xAA2C,0xAA2D,0xAA2E,0xAA31,0xAA32,0xAA35, - 0xAA36,0xAA43,0xAA4C,0xAA7C,0xAAB0,0xAAB2,0xAAB3,0xAAB4, - 0xAAB7,0xAAB8,0xAABE,0xAABF,0xAAC1,0xAAEC,0xAAED,0xAAF6, - 0xABE5,0xABE8,0xABED,0xFB1E,0xFE00,0xFE01,0xFE02,0xFE03, - 0xFE04,0xFE05,0xFE06,0xFE07,0xFE08,0xFE09,0xFE0A,0xFE0B, - 0xFE0C,0xFE0D,0xFE0E,0xFE0F,0xFE20,0xFE21,0xFE22,0xFE23, - 0xFE24,0xFE25,0xFE26,0xFE27,0xFE28,0xFE29,0xFE2A,0xFE2B, - 0xFE2C,0xFE2D,0xFE2E,0xFE2F, - 0x101FD,0x102E0,0x10376,0x10377,0x10378,0x10379,0x1037A,0x10A01, - 0x10A02,0x10A03,0x10A05,0x10A06,0x10A0C,0x10A0D,0x10A0E,0x10A0F, - 0x10A38,0x10A39,0x10A3A,0x10A3F,0x10AE5,0x10AE6,0x11001,0x11038, - 0x11039,0x1103A,0x1103B,0x1103C,0x1103D,0x1103E,0x1103F,0x11040, - 0x11041,0x11042,0x11043,0x11044,0x11045,0x11046,0x1107F,0x11080, - 0x11081,0x110B3,0x110B4,0x110B5,0x110B6,0x110B9,0x110BA,0x11100, - 0x11101,0x11102,0x11127,0x11128,0x11129,0x1112A,0x1112B,0x1112D, - 0x1112E,0x1112F,0x11130,0x11131,0x11132,0x11133,0x11134,0x11173, - 0x11180,0x11181,0x111B6,0x111B7,0x111B8,0x111B9,0x111BA,0x111BB, - 0x111BC,0x111BD,0x111BE,0x111CA,0x111CB,0x111CC,0x1122F,0x11230, - 0x11231,0x11234,0x11236,0x11237,0x112DF,0x112E3,0x112E4,0x112E5, - 0x112E6,0x112E7,0x112E8,0x112E9,0x112EA,0x11300,0x11301,0x1133C, - 0x11340,0x11366,0x11367,0x11368,0x11369,0x1136A,0x1136B,0x1136C, - 0x11370,0x11371,0x11372,0x11373,0x11374,0x114B3,0x114B4,0x114B5, - 0x114B6,0x114B7,0x114B8,0x114BA,0x114BF,0x114C0,0x114C2,0x114C3, - 0x115B2,0x115B3,0x115B4,0x115B5,0x115BC,0x115BD,0x115BF,0x115C0, - 0x115DC,0x115DD,0x11633,0x11634,0x11635,0x11636,0x11637,0x11638, - 0x11639,0x1163A,0x1163D,0x1163F,0x11640,0x116AB,0x116AD,0x116B0, - 0x116B1,0x116B2,0x116B3,0x116B4,0x116B5,0x116B7,0x1171D,0x1171E, - 0x1171F,0x11722,0x11723,0x11724,0x11725,0x11727,0x11728,0x11729, - 0x1172A,0x1172B,0x16AF0,0x16AF1,0x16AF2,0x16AF3,0x16AF4,0x16B30, - 0x16B31,0x16B32,0x16B33,0x16B34,0x16B35,0x16B36,0x16F8F,0x16F90, - 0x16F91,0x16F92,0x1BC9D,0x1BC9E,0x1D167,0x1D168,0x1D169,0x1D17B, - 0x1D17C,0x1D17D,0x1D17E,0x1D17F,0x1D180,0x1D181,0x1D182,0x1D185, - 0x1D186,0x1D187,0x1D188,0x1D189,0x1D18A,0x1D18B,0x1D1AA,0x1D1AB, - 0x1D1AC,0x1D1AD,0x1D242,0x1D243,0x1D244,0x1DA00,0x1DA01,0x1DA02, - 0x1DA03,0x1DA04,0x1DA05,0x1DA06,0x1DA07,0x1DA08,0x1DA09,0x1DA0A, - 0x1DA0B,0x1DA0C,0x1DA0D,0x1DA0E,0x1DA0F,0x1DA10,0x1DA11,0x1DA12, - 0x1DA13,0x1DA14,0x1DA15,0x1DA16,0x1DA17,0x1DA18,0x1DA19,0x1DA1A, - 0x1DA1B,0x1DA1C,0x1DA1D,0x1DA1E,0x1DA1F,0x1DA20,0x1DA21,0x1DA22, - 0x1DA23,0x1DA24,0x1DA25,0x1DA26,0x1DA27,0x1DA28,0x1DA29,0x1DA2A, - 0x1DA2B,0x1DA2C,0x1DA2D,0x1DA2E,0x1DA2F,0x1DA30,0x1DA31,0x1DA32, - 0x1DA33,0x1DA34,0x1DA35,0x1DA36,0x1DA3B,0x1DA3C,0x1DA3D,0x1DA3E, - 0x1DA3F,0x1DA40,0x1DA41,0x1DA42,0x1DA43,0x1DA44,0x1DA45,0x1DA46, - 0x1DA47,0x1DA48,0x1DA49,0x1DA4A,0x1DA4B,0x1DA4C,0x1DA4D,0x1DA4E, - 0x1DA4F,0x1DA50,0x1DA51,0x1DA52,0x1DA53,0x1DA54,0x1DA55,0x1DA56, - 0x1DA57,0x1DA58,0x1DA59,0x1DA5A,0x1DA5B,0x1DA5C,0x1DA5D,0x1DA5E, - 0x1DA5F,0x1DA60,0x1DA61,0x1DA62,0x1DA63,0x1DA64,0x1DA65,0x1DA66, - 0x1DA67,0x1DA68,0x1DA69,0x1DA6A,0x1DA6B,0x1DA6C,0x1DA75,0x1DA84, - 0x1DA9B,0x1DA9C,0x1DA9D,0x1DA9E,0x1DA9F,0x1DAA1,0x1DAA2,0x1DAA3, - 0x1DAA4,0x1DAA5,0x1DAA6,0x1DAA7,0x1DAA8,0x1DAA9,0x1DAAA,0x1DAAB, - 0x1DAAC,0x1DAAD,0x1DAAE,0x1DAAF,0x1E8D0,0x1E8D1,0x1E8D2,0x1E8D3, - 0x1E8D4,0x1E8D5,0x1E8D6,0xE0100,0xE0101,0xE0102,0xE0103,0xE0104, - 0xE0105,0xE0106,0xE0107,0xE0108,0xE0109,0xE010A,0xE010B,0xE010C, - 0xE010D,0xE010E,0xE010F,0xE0110,0xE0111,0xE0112,0xE0113,0xE0114, - 0xE0115,0xE0116,0xE0117,0xE0118,0xE0119,0xE011A,0xE011B,0xE011C, - 0xE011D,0xE011E,0xE011F,0xE0120,0xE0121,0xE0122,0xE0123,0xE0124, - 0xE0125,0xE0126,0xE0127,0xE0128,0xE0129,0xE012A,0xE012B,0xE012C, - 0xE012D,0xE012E,0xE012F,0xE0130,0xE0131,0xE0132,0xE0133,0xE0134, - 0xE0135,0xE0136,0xE0137,0xE0138,0xE0139,0xE013A,0xE013B,0xE013C, - 0xE013D,0xE013E,0xE013F,0xE0140,0xE0141,0xE0142,0xE0143,0xE0144, - 0xE0145,0xE0146,0xE0147,0xE0148,0xE0149,0xE014A,0xE014B,0xE014C, - 0xE014D,0xE014E,0xE014F,0xE0150,0xE0151,0xE0152,0xE0153,0xE0154, - 0xE0155,0xE0156,0xE0157,0xE0158,0xE0159,0xE015A,0xE015B,0xE015C, - 0xE015D,0xE015E,0xE015F,0xE0160,0xE0161,0xE0162,0xE0163,0xE0164, - 0xE0165,0xE0166,0xE0167,0xE0168,0xE0169,0xE016A,0xE016B,0xE016C, - 0xE016D,0xE016E,0xE016F,0xE0170,0xE0171,0xE0172,0xE0173,0xE0174, - 0xE0175,0xE0176,0xE0177,0xE0178,0xE0179,0xE017A,0xE017B,0xE017C, - 0xE017D,0xE017E,0xE017F,0xE0180,0xE0181,0xE0182,0xE0183,0xE0184, - 0xE0185,0xE0186,0xE0187,0xE0188,0xE0189,0xE018A,0xE018B,0xE018C, - 0xE018D,0xE018E,0xE018F,0xE0190,0xE0191,0xE0192,0xE0193,0xE0194, - 0xE0195,0xE0196,0xE0197,0xE0198,0xE0199,0xE019A,0xE019B,0xE019C, - 0xE019D,0xE019E,0xE019F,0xE01A0,0xE01A1,0xE01A2,0xE01A3,0xE01A4, - 0xE01A5,0xE01A6,0xE01A7,0xE01A8,0xE01A9,0xE01AA,0xE01AB,0xE01AC, - 0xE01AD,0xE01AE,0xE01AF,0xE01B0,0xE01B1,0xE01B2,0xE01B3,0xE01B4, - 0xE01B5,0xE01B6,0xE01B7,0xE01B8,0xE01B9,0xE01BA,0xE01BB,0xE01BC, - 0xE01BD,0xE01BE,0xE01BF,0xE01C0,0xE01C1,0xE01C2,0xE01C3,0xE01C4, - 0xE01C5,0xE01C6,0xE01C7,0xE01C8,0xE01C9,0xE01CA,0xE01CB,0xE01CC, - 0xE01CD,0xE01CE,0xE01CF,0xE01D0,0xE01D1,0xE01D2,0xE01D3,0xE01D4, - 0xE01D5,0xE01D6,0xE01D7,0xE01D8,0xE01D9,0xE01DA,0xE01DB,0xE01DC, - 0xE01DD,0xE01DE,0xE01DF,0xE01E0,0xE01E1,0xE01E2,0xE01E3,0xE01E4, - 0xE01E5,0xE01E6,0xE01E7,0xE01E8,0xE01E9,0xE01EA,0xE01EB,0xE01EC, - 0xE01ED,0xE01EE,0xE01EF, + 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, + 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, + 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F, + 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, 0x0346, 0x0347, + 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F, + 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F, + 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F, + 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0591, 0x0592, 0x0593, + 0x0594, 0x0595, 0x0596, 0x0597, 0x0598, 0x0599, 0x059A, 0x059B, + 0x059C, 0x059D, 0x059E, 0x059F, 0x05A0, 0x05A1, 0x05A2, 0x05A3, + 0x05A4, 0x05A5, 0x05A6, 0x05A7, 0x05A8, 0x05A9, 0x05AA, 0x05AB, + 0x05AC, 0x05AD, 0x05AE, 0x05AF, 0x05B0, 0x05B1, 0x05B2, 0x05B3, + 0x05B4, 0x05B5, 0x05B6, 0x05B7, 0x05B8, 0x05B9, 0x05BA, 0x05BB, + 0x05BC, 0x05BD, 0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, + 0x0610, 0x0611, 0x0612, 0x0613, 0x0614, 0x0615, 0x0616, 0x0617, + 0x0618, 0x0619, 0x061A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, + 0x0650, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0657, + 0x0658, 0x0659, 0x065A, 0x065B, 0x065C, 0x065D, 0x065E, 0x065F, + 0x0670, 0x06D6, 0x06D7, 0x06D8, 0x06D9, 0x06DA, 0x06DB, 0x06DC, + 0x06DF, 0x06E0, 0x06E1, 0x06E2, 0x06E3, 0x06E4, 0x06E7, 0x06E8, + 0x06EA, 0x06EB, 0x06EC, 0x06ED, 0x0711, 0x0730, 0x0731, 0x0732, + 0x0733, 0x0734, 0x0735, 0x0736, 0x0737, 0x0738, 0x0739, 0x073A, + 0x073B, 0x073C, 0x073D, 0x073E, 0x073F, 0x0740, 0x0741, 0x0742, + 0x0743, 0x0744, 0x0745, 0x0746, 0x0747, 0x0748, 0x0749, 0x074A, + 0x07A6, 0x07A7, 0x07A8, 0x07A9, 0x07AA, 0x07AB, 0x07AC, 0x07AD, + 0x07AE, 0x07AF, 0x07B0, 0x07EB, 0x07EC, 0x07ED, 0x07EE, 0x07EF, + 0x07F0, 0x07F1, 0x07F2, 0x07F3, 0x0816, 0x0817, 0x0818, 0x0819, + 0x081B, 0x081C, 0x081D, 0x081E, 0x081F, 0x0820, 0x0821, 0x0822, + 0x0823, 0x0825, 0x0826, 0x0827, 0x0829, 0x082A, 0x082B, 0x082C, + 0x082D, 0x0859, 0x085A, 0x085B, 0x08E3, 0x08E4, 0x08E5, 0x08E6, + 0x08E7, 0x08E8, 0x08E9, 0x08EA, 0x08EB, 0x08EC, 0x08ED, 0x08EE, + 0x08EF, 0x08F0, 0x08F1, 0x08F2, 0x08F3, 0x08F4, 0x08F5, 0x08F6, + 0x08F7, 0x08F8, 0x08F9, 0x08FA, 0x08FB, 0x08FC, 0x08FD, 0x08FE, + 0x08FF, 0x0900, 0x0901, 0x0902, 0x093A, 0x093C, 0x0941, 0x0942, + 0x0943, 0x0944, 0x0945, 0x0946, 0x0947, 0x0948, 0x094D, 0x0951, + 0x0952, 0x0953, 0x0954, 0x0955, 0x0956, 0x0957, 0x0962, 0x0963, + 0x0981, 0x09BC, 0x09C1, 0x09C2, 0x09C3, 0x09C4, 0x09CD, 0x09E2, + 0x09E3, 0x0A01, 0x0A02, 0x0A3C, 0x0A41, 0x0A42, 0x0A47, 0x0A48, + 0x0A4B, 0x0A4C, 0x0A4D, 0x0A51, 0x0A70, 0x0A71, 0x0A75, 0x0A81, + 0x0A82, 0x0ABC, 0x0AC1, 0x0AC2, 0x0AC3, 0x0AC4, 0x0AC5, 0x0AC7, + 0x0AC8, 0x0ACD, 0x0AE2, 0x0AE3, 0x0B01, 0x0B3C, 0x0B3F, 0x0B41, + 0x0B42, 0x0B43, 0x0B44, 0x0B4D, 0x0B56, 0x0B62, 0x0B63, 0x0B82, + 0x0BC0, 0x0BCD, 0x0C00, 0x0C3E, 0x0C3F, 0x0C40, 0x0C46, 0x0C47, + 0x0C48, 0x0C4A, 0x0C4B, 0x0C4C, 0x0C4D, 0x0C55, 0x0C56, 0x0C62, + 0x0C63, 0x0C81, 0x0CBC, 0x0CBF, 0x0CC6, 0x0CCC, 0x0CCD, 0x0CE2, + 0x0CE3, 0x0D01, 0x0D41, 0x0D42, 0x0D43, 0x0D44, 0x0D4D, 0x0D62, + 0x0D63, 0x0DCA, 0x0DD2, 0x0DD3, 0x0DD4, 0x0DD6, 0x0E31, 0x0E34, + 0x0E35, 0x0E36, 0x0E37, 0x0E38, 0x0E39, 0x0E3A, 0x0E47, 0x0E48, + 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0EB1, 0x0EB4, + 0x0EB5, 0x0EB6, 0x0EB7, 0x0EB8, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, + 0x0EC9, 0x0ECA, 0x0ECB, 0x0ECC, 0x0ECD, 0x0F18, 0x0F19, 0x0F35, + 0x0F37, 0x0F39, 0x0F71, 0x0F72, 0x0F73, 0x0F74, 0x0F75, 0x0F76, + 0x0F77, 0x0F78, 0x0F79, 0x0F7A, 0x0F7B, 0x0F7C, 0x0F7D, 0x0F7E, + 0x0F80, 0x0F81, 0x0F82, 0x0F83, 0x0F84, 0x0F86, 0x0F87, 0x0F8D, + 0x0F8E, 0x0F8F, 0x0F90, 0x0F91, 0x0F92, 0x0F93, 0x0F94, 0x0F95, + 0x0F96, 0x0F97, 0x0F99, 0x0F9A, 0x0F9B, 0x0F9C, 0x0F9D, 0x0F9E, + 0x0F9F, 0x0FA0, 0x0FA1, 0x0FA2, 0x0FA3, 0x0FA4, 0x0FA5, 0x0FA6, + 0x0FA7, 0x0FA8, 0x0FA9, 0x0FAA, 0x0FAB, 0x0FAC, 0x0FAD, 0x0FAE, + 0x0FAF, 0x0FB0, 0x0FB1, 0x0FB2, 0x0FB3, 0x0FB4, 0x0FB5, 0x0FB6, + 0x0FB7, 0x0FB8, 0x0FB9, 0x0FBA, 0x0FBB, 0x0FBC, 0x0FC6, 0x102D, + 0x102E, 0x102F, 0x1030, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, + 0x1037, 0x1039, 0x103A, 0x103D, 0x103E, 0x1058, 0x1059, 0x105E, + 0x105F, 0x1060, 0x1071, 0x1072, 0x1073, 0x1074, 0x1082, 0x1085, + 0x1086, 0x108D, 0x109D, 0x135D, 0x135E, 0x135F, 0x1712, 0x1713, + 0x1714, 0x1732, 0x1733, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, + 0x17B4, 0x17B5, 0x17B7, 0x17B8, 0x17B9, 0x17BA, 0x17BB, 0x17BC, + 0x17BD, 0x17C6, 0x17C9, 0x17CA, 0x17CB, 0x17CC, 0x17CD, 0x17CE, + 0x17CF, 0x17D0, 0x17D1, 0x17D2, 0x17D3, 0x17DD, 0x180B, 0x180C, + 0x180D, 0x18A9, 0x1920, 0x1921, 0x1922, 0x1927, 0x1928, 0x1932, + 0x1939, 0x193A, 0x193B, 0x1A17, 0x1A18, 0x1A1B, 0x1A56, 0x1A58, + 0x1A59, 0x1A5A, 0x1A5B, 0x1A5C, 0x1A5D, 0x1A5E, 0x1A60, 0x1A62, + 0x1A65, 0x1A66, 0x1A67, 0x1A68, 0x1A69, 0x1A6A, 0x1A6B, 0x1A6C, + 0x1A73, 0x1A74, 0x1A75, 0x1A76, 0x1A77, 0x1A78, 0x1A79, 0x1A7A, + 0x1A7B, 0x1A7C, 0x1A7F, 0x1AB0, 0x1AB1, 0x1AB2, 0x1AB3, 0x1AB4, + 0x1AB5, 0x1AB6, 0x1AB7, 0x1AB8, 0x1AB9, 0x1ABA, 0x1ABB, 0x1ABC, + 0x1ABD, 0x1B00, 0x1B01, 0x1B02, 0x1B03, 0x1B34, 0x1B36, 0x1B37, + 0x1B38, 0x1B39, 0x1B3A, 0x1B3C, 0x1B42, 0x1B6B, 0x1B6C, 0x1B6D, + 0x1B6E, 0x1B6F, 0x1B70, 0x1B71, 0x1B72, 0x1B73, 0x1B80, 0x1B81, + 0x1BA2, 0x1BA3, 0x1BA4, 0x1BA5, 0x1BA8, 0x1BA9, 0x1BAB, 0x1BAC, + 0x1BAD, 0x1BE6, 0x1BE8, 0x1BE9, 0x1BED, 0x1BEF, 0x1BF0, 0x1BF1, + 0x1C2C, 0x1C2D, 0x1C2E, 0x1C2F, 0x1C30, 0x1C31, 0x1C32, 0x1C33, + 0x1C36, 0x1C37, 0x1CD0, 0x1CD1, 0x1CD2, 0x1CD4, 0x1CD5, 0x1CD6, + 0x1CD7, 0x1CD8, 0x1CD9, 0x1CDA, 0x1CDB, 0x1CDC, 0x1CDD, 0x1CDE, + 0x1CDF, 0x1CE0, 0x1CE2, 0x1CE3, 0x1CE4, 0x1CE5, 0x1CE6, 0x1CE7, + 0x1CE8, 0x1CED, 0x1CF4, 0x1CF8, 0x1CF9, 0x1DC0, 0x1DC1, 0x1DC2, + 0x1DC3, 0x1DC4, 0x1DC5, 0x1DC6, 0x1DC7, 0x1DC8, 0x1DC9, 0x1DCA, + 0x1DCB, 0x1DCC, 0x1DCD, 0x1DCE, 0x1DCF, 0x1DD0, 0x1DD1, 0x1DD2, + 0x1DD3, 0x1DD4, 0x1DD5, 0x1DD6, 0x1DD7, 0x1DD8, 0x1DD9, 0x1DDA, + 0x1DDB, 0x1DDC, 0x1DDD, 0x1DDE, 0x1DDF, 0x1DE0, 0x1DE1, 0x1DE2, + 0x1DE3, 0x1DE4, 0x1DE5, 0x1DE6, 0x1DE7, 0x1DE8, 0x1DE9, 0x1DEA, + 0x1DEB, 0x1DEC, 0x1DED, 0x1DEE, 0x1DEF, 0x1DF0, 0x1DF1, 0x1DF2, + 0x1DF3, 0x1DF4, 0x1DF5, 0x1DFC, 0x1DFD, 0x1DFE, 0x1DFF, 0x20D0, + 0x20D1, 0x20D2, 0x20D3, 0x20D4, 0x20D5, 0x20D6, 0x20D7, 0x20D8, + 0x20D9, 0x20DA, 0x20DB, 0x20DC, 0x20E1, 0x20E5, 0x20E6, 0x20E7, + 0x20E8, 0x20E9, 0x20EA, 0x20EB, 0x20EC, 0x20ED, 0x20EE, 0x20EF, + 0x20F0, 0x2CEF, 0x2CF0, 0x2CF1, 0x2D7F, 0x2DE0, 0x2DE1, 0x2DE2, + 0x2DE3, 0x2DE4, 0x2DE5, 0x2DE6, 0x2DE7, 0x2DE8, 0x2DE9, 0x2DEA, + 0x2DEB, 0x2DEC, 0x2DED, 0x2DEE, 0x2DEF, 0x2DF0, 0x2DF1, 0x2DF2, + 0x2DF3, 0x2DF4, 0x2DF5, 0x2DF6, 0x2DF7, 0x2DF8, 0x2DF9, 0x2DFA, + 0x2DFB, 0x2DFC, 0x2DFD, 0x2DFE, 0x2DFF, 0x302A, 0x302B, 0x302C, + 0x302D, 0x3099, 0x309A, 0xA66F, 0xA674, 0xA675, 0xA676, 0xA677, + 0xA678, 0xA679, 0xA67A, 0xA67B, 0xA67C, 0xA67D, 0xA69E, 0xA69F, + 0xA6F0, 0xA6F1, 0xA802, 0xA806, 0xA80B, 0xA825, 0xA826, 0xA8C4, + 0xA8E0, 0xA8E1, 0xA8E2, 0xA8E3, 0xA8E4, 0xA8E5, 0xA8E6, 0xA8E7, + 0xA8E8, 0xA8E9, 0xA8EA, 0xA8EB, 0xA8EC, 0xA8ED, 0xA8EE, 0xA8EF, + 0xA8F0, 0xA8F1, 0xA926, 0xA927, 0xA928, 0xA929, 0xA92A, 0xA92B, + 0xA92C, 0xA92D, 0xA947, 0xA948, 0xA949, 0xA94A, 0xA94B, 0xA94C, + 0xA94D, 0xA94E, 0xA94F, 0xA950, 0xA951, 0xA980, 0xA981, 0xA982, + 0xA9B3, 0xA9B6, 0xA9B7, 0xA9B8, 0xA9B9, 0xA9BC, 0xA9E5, 0xAA29, + 0xAA2A, 0xAA2B, 0xAA2C, 0xAA2D, 0xAA2E, 0xAA31, 0xAA32, 0xAA35, + 0xAA36, 0xAA43, 0xAA4C, 0xAA7C, 0xAAB0, 0xAAB2, 0xAAB3, 0xAAB4, + 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, 0xAAC1, 0xAAEC, 0xAAED, 0xAAF6, + 0xABE5, 0xABE8, 0xABED, 0xFB1E, 0xFE00, 0xFE01, 0xFE02, 0xFE03, + 0xFE04, 0xFE05, 0xFE06, 0xFE07, 0xFE08, 0xFE09, 0xFE0A, 0xFE0B, + 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F, 0xFE20, 0xFE21, 0xFE22, 0xFE23, + 0xFE24, 0xFE25, 0xFE26, 0xFE27, 0xFE28, 0xFE29, 0xFE2A, 0xFE2B, + 0xFE2C, 0xFE2D, 0xFE2E, 0xFE2F, 0x101FD, 0x102E0, 0x10376, 0x10377, + 0x10378, 0x10379, 0x1037A, 0x10A01, 0x10A02, 0x10A03, 0x10A05, 0x10A06, + 0x10A0C, 0x10A0D, 0x10A0E, 0x10A0F, 0x10A38, 0x10A39, 0x10A3A, 0x10A3F, + 0x10AE5, 0x10AE6, 0x11001, 0x11038, 0x11039, 0x1103A, 0x1103B, 0x1103C, + 0x1103D, 0x1103E, 0x1103F, 0x11040, 0x11041, 0x11042, 0x11043, 0x11044, + 0x11045, 0x11046, 0x1107F, 0x11080, 0x11081, 0x110B3, 0x110B4, 0x110B5, + 0x110B6, 0x110B9, 0x110BA, 0x11100, 0x11101, 0x11102, 0x11127, 0x11128, + 0x11129, 0x1112A, 0x1112B, 0x1112D, 0x1112E, 0x1112F, 0x11130, 0x11131, + 0x11132, 0x11133, 0x11134, 0x11173, 0x11180, 0x11181, 0x111B6, 0x111B7, + 0x111B8, 0x111B9, 0x111BA, 0x111BB, 0x111BC, 0x111BD, 0x111BE, 0x111CA, + 0x111CB, 0x111CC, 0x1122F, 0x11230, 0x11231, 0x11234, 0x11236, 0x11237, + 0x112DF, 0x112E3, 0x112E4, 0x112E5, 0x112E6, 0x112E7, 0x112E8, 0x112E9, + 0x112EA, 0x11300, 0x11301, 0x1133C, 0x11340, 0x11366, 0x11367, 0x11368, + 0x11369, 0x1136A, 0x1136B, 0x1136C, 0x11370, 0x11371, 0x11372, 0x11373, + 0x11374, 0x114B3, 0x114B4, 0x114B5, 0x114B6, 0x114B7, 0x114B8, 0x114BA, + 0x114BF, 0x114C0, 0x114C2, 0x114C3, 0x115B2, 0x115B3, 0x115B4, 0x115B5, + 0x115BC, 0x115BD, 0x115BF, 0x115C0, 0x115DC, 0x115DD, 0x11633, 0x11634, + 0x11635, 0x11636, 0x11637, 0x11638, 0x11639, 0x1163A, 0x1163D, 0x1163F, + 0x11640, 0x116AB, 0x116AD, 0x116B0, 0x116B1, 0x116B2, 0x116B3, 0x116B4, + 0x116B5, 0x116B7, 0x1171D, 0x1171E, 0x1171F, 0x11722, 0x11723, 0x11724, + 0x11725, 0x11727, 0x11728, 0x11729, 0x1172A, 0x1172B, 0x16AF0, 0x16AF1, + 0x16AF2, 0x16AF3, 0x16AF4, 0x16B30, 0x16B31, 0x16B32, 0x16B33, 0x16B34, + 0x16B35, 0x16B36, 0x16F8F, 0x16F90, 0x16F91, 0x16F92, 0x1BC9D, 0x1BC9E, + 0x1D167, 0x1D168, 0x1D169, 0x1D17B, 0x1D17C, 0x1D17D, 0x1D17E, 0x1D17F, + 0x1D180, 0x1D181, 0x1D182, 0x1D185, 0x1D186, 0x1D187, 0x1D188, 0x1D189, + 0x1D18A, 0x1D18B, 0x1D1AA, 0x1D1AB, 0x1D1AC, 0x1D1AD, 0x1D242, 0x1D243, + 0x1D244, 0x1DA00, 0x1DA01, 0x1DA02, 0x1DA03, 0x1DA04, 0x1DA05, 0x1DA06, + 0x1DA07, 0x1DA08, 0x1DA09, 0x1DA0A, 0x1DA0B, 0x1DA0C, 0x1DA0D, 0x1DA0E, + 0x1DA0F, 0x1DA10, 0x1DA11, 0x1DA12, 0x1DA13, 0x1DA14, 0x1DA15, 0x1DA16, + 0x1DA17, 0x1DA18, 0x1DA19, 0x1DA1A, 0x1DA1B, 0x1DA1C, 0x1DA1D, 0x1DA1E, + 0x1DA1F, 0x1DA20, 0x1DA21, 0x1DA22, 0x1DA23, 0x1DA24, 0x1DA25, 0x1DA26, + 0x1DA27, 0x1DA28, 0x1DA29, 0x1DA2A, 0x1DA2B, 0x1DA2C, 0x1DA2D, 0x1DA2E, + 0x1DA2F, 0x1DA30, 0x1DA31, 0x1DA32, 0x1DA33, 0x1DA34, 0x1DA35, 0x1DA36, + 0x1DA3B, 0x1DA3C, 0x1DA3D, 0x1DA3E, 0x1DA3F, 0x1DA40, 0x1DA41, 0x1DA42, + 0x1DA43, 0x1DA44, 0x1DA45, 0x1DA46, 0x1DA47, 0x1DA48, 0x1DA49, 0x1DA4A, + 0x1DA4B, 0x1DA4C, 0x1DA4D, 0x1DA4E, 0x1DA4F, 0x1DA50, 0x1DA51, 0x1DA52, + 0x1DA53, 0x1DA54, 0x1DA55, 0x1DA56, 0x1DA57, 0x1DA58, 0x1DA59, 0x1DA5A, + 0x1DA5B, 0x1DA5C, 0x1DA5D, 0x1DA5E, 0x1DA5F, 0x1DA60, 0x1DA61, 0x1DA62, + 0x1DA63, 0x1DA64, 0x1DA65, 0x1DA66, 0x1DA67, 0x1DA68, 0x1DA69, 0x1DA6A, + 0x1DA6B, 0x1DA6C, 0x1DA75, 0x1DA84, 0x1DA9B, 0x1DA9C, 0x1DA9D, 0x1DA9E, + 0x1DA9F, 0x1DAA1, 0x1DAA2, 0x1DAA3, 0x1DAA4, 0x1DAA5, 0x1DAA6, 0x1DAA7, + 0x1DAA8, 0x1DAA9, 0x1DAAA, 0x1DAAB, 0x1DAAC, 0x1DAAD, 0x1DAAE, 0x1DAAF, + 0x1E8D0, 0x1E8D1, 0x1E8D2, 0x1E8D3, 0x1E8D4, 0x1E8D5, 0x1E8D6, 0xE0100, + 0xE0101, 0xE0102, 0xE0103, 0xE0104, 0xE0105, 0xE0106, 0xE0107, 0xE0108, + 0xE0109, 0xE010A, 0xE010B, 0xE010C, 0xE010D, 0xE010E, 0xE010F, 0xE0110, + 0xE0111, 0xE0112, 0xE0113, 0xE0114, 0xE0115, 0xE0116, 0xE0117, 0xE0118, + 0xE0119, 0xE011A, 0xE011B, 0xE011C, 0xE011D, 0xE011E, 0xE011F, 0xE0120, + 0xE0121, 0xE0122, 0xE0123, 0xE0124, 0xE0125, 0xE0126, 0xE0127, 0xE0128, + 0xE0129, 0xE012A, 0xE012B, 0xE012C, 0xE012D, 0xE012E, 0xE012F, 0xE0130, + 0xE0131, 0xE0132, 0xE0133, 0xE0134, 0xE0135, 0xE0136, 0xE0137, 0xE0138, + 0xE0139, 0xE013A, 0xE013B, 0xE013C, 0xE013D, 0xE013E, 0xE013F, 0xE0140, + 0xE0141, 0xE0142, 0xE0143, 0xE0144, 0xE0145, 0xE0146, 0xE0147, 0xE0148, + 0xE0149, 0xE014A, 0xE014B, 0xE014C, 0xE014D, 0xE014E, 0xE014F, 0xE0150, + 0xE0151, 0xE0152, 0xE0153, 0xE0154, 0xE0155, 0xE0156, 0xE0157, 0xE0158, + 0xE0159, 0xE015A, 0xE015B, 0xE015C, 0xE015D, 0xE015E, 0xE015F, 0xE0160, + 0xE0161, 0xE0162, 0xE0163, 0xE0164, 0xE0165, 0xE0166, 0xE0167, 0xE0168, + 0xE0169, 0xE016A, 0xE016B, 0xE016C, 0xE016D, 0xE016E, 0xE016F, 0xE0170, + 0xE0171, 0xE0172, 0xE0173, 0xE0174, 0xE0175, 0xE0176, 0xE0177, 0xE0178, + 0xE0179, 0xE017A, 0xE017B, 0xE017C, 0xE017D, 0xE017E, 0xE017F, 0xE0180, + 0xE0181, 0xE0182, 0xE0183, 0xE0184, 0xE0185, 0xE0186, 0xE0187, 0xE0188, + 0xE0189, 0xE018A, 0xE018B, 0xE018C, 0xE018D, 0xE018E, 0xE018F, 0xE0190, + 0xE0191, 0xE0192, 0xE0193, 0xE0194, 0xE0195, 0xE0196, 0xE0197, 0xE0198, + 0xE0199, 0xE019A, 0xE019B, 0xE019C, 0xE019D, 0xE019E, 0xE019F, 0xE01A0, + 0xE01A1, 0xE01A2, 0xE01A3, 0xE01A4, 0xE01A5, 0xE01A6, 0xE01A7, 0xE01A8, + 0xE01A9, 0xE01AA, 0xE01AB, 0xE01AC, 0xE01AD, 0xE01AE, 0xE01AF, 0xE01B0, + 0xE01B1, 0xE01B2, 0xE01B3, 0xE01B4, 0xE01B5, 0xE01B6, 0xE01B7, 0xE01B8, + 0xE01B9, 0xE01BA, 0xE01BB, 0xE01BC, 0xE01BD, 0xE01BE, 0xE01BF, 0xE01C0, + 0xE01C1, 0xE01C2, 0xE01C3, 0xE01C4, 0xE01C5, 0xE01C6, 0xE01C7, 0xE01C8, + 0xE01C9, 0xE01CA, 0xE01CB, 0xE01CC, 0xE01CD, 0xE01CE, 0xE01CF, 0xE01D0, + 0xE01D1, 0xE01D2, 0xE01D3, 0xE01D4, 0xE01D5, 0xE01D6, 0xE01D7, 0xE01D8, + 0xE01D9, 0xE01DA, 0xE01DB, 0xE01DC, 0xE01DD, 0xE01DE, 0xE01DF, 0xE01E0, + 0xE01E1, 0xE01E2, 0xE01E3, 0xE01E4, 0xE01E5, 0xE01E6, 0xE01E7, 0xE01E8, + 0xE01E9, 0xE01EA, 0xE01EB, 0xE01EC, 0xE01ED, 0xE01EE, 0xE01EF, }; -static int unicodeCombiningCharTableSize = sizeof(unicodeCombiningCharTable) / sizeof(unicodeCombiningCharTable[0]); +static int unicodeCombiningCharTableSize = + sizeof(unicodeCombiningCharTable) / sizeof(unicodeCombiningCharTable[0]); -inline int unicodeIsCombiningChar(unsigned long cp) -{ - int i; - for (i = 0; i < unicodeCombiningCharTableSize; i++) { - if (unicodeCombiningCharTable[i] == cp) { - return 1; - } +inline int unicodeIsCombiningChar(unsigned long cp) { + int i; + for (i = 0; i < unicodeCombiningCharTableSize; i++) { + if (unicodeCombiningCharTable[i] == cp) { + return 1; } - return 0; + } + return 0; } /* Get length of previous UTF8 character */ -inline int unicodePrevUTF8CharLen(char* buf, int pos) -{ - int end = pos--; - while (pos >= 0 && ((unsigned char)buf[pos] & 0xC0) == 0x80) { - pos--; - } - return end - pos; +inline int unicodePrevUTF8CharLen(char *buf, int pos) { + int end = pos--; + while (pos >= 0 && ((unsigned char)buf[pos] & 0xC0) == 0x80) { + pos--; + } + return end - pos; } /* Get length of previous UTF8 character */ -inline int unicodeUTF8CharLen(char* buf, int buf_len, int pos) -{ - if (pos == buf_len) { return 0; } - unsigned char ch = buf[pos]; - if (ch < 0x80) { return 1; } - else if (ch < 0xE0) { return 2; } - else if (ch < 0xF0) { return 3; } - else { return 4; } +inline int unicodeUTF8CharLen(char *buf, int buf_len, int pos) { + if (pos == buf_len) { + return 0; + } + unsigned char ch = buf[pos]; + if (ch < 0x80) { + return 1; + } else if (ch < 0xE0) { + return 2; + } else if (ch < 0xF0) { + return 3; + } else { + return 4; + } } /* Convert UTF8 to Unicode code point */ -inline int unicodeUTF8CharToCodePoint( - const char* buf, - int len, - int* cp) -{ - if (len) { - unsigned char byte = buf[0]; - if ((byte & 0x80) == 0) { - *cp = byte; - return 1; - } else if ((byte & 0xE0) == 0xC0) { - if (len >= 2) { - *cp = (((unsigned long)(buf[0] & 0x1F)) << 6) | - ((unsigned long)(buf[1] & 0x3F)); - return 2; - } - } else if ((byte & 0xF0) == 0xE0) { - if (len >= 3) { - *cp = (((unsigned long)(buf[0] & 0x0F)) << 12) | - (((unsigned long)(buf[1] & 0x3F)) << 6) | - ((unsigned long)(buf[2] & 0x3F)); - return 3; - } - } else if ((byte & 0xF8) == 0xF0) { - if (len >= 4) { - *cp = (((unsigned long)(buf[0] & 0x07)) << 18) | - (((unsigned long)(buf[1] & 0x3F)) << 12) | - (((unsigned long)(buf[2] & 0x3F)) << 6) | - ((unsigned long)(buf[3] & 0x3F)); - return 4; - } - } +inline int unicodeUTF8CharToCodePoint(const char *buf, int len, int *cp) { + if (len) { + unsigned char byte = buf[0]; + if ((byte & 0x80) == 0) { + *cp = byte; + return 1; + } else if ((byte & 0xE0) == 0xC0) { + if (len >= 2) { + *cp = (((unsigned long)(buf[0] & 0x1F)) << 6) | + ((unsigned long)(buf[1] & 0x3F)); + return 2; + } + } else if ((byte & 0xF0) == 0xE0) { + if (len >= 3) { + *cp = (((unsigned long)(buf[0] & 0x0F)) << 12) | + (((unsigned long)(buf[1] & 0x3F)) << 6) | + ((unsigned long)(buf[2] & 0x3F)); + return 3; + } + } else if ((byte & 0xF8) == 0xF0) { + if (len >= 4) { + *cp = (((unsigned long)(buf[0] & 0x07)) << 18) | + (((unsigned long)(buf[1] & 0x3F)) << 12) | + (((unsigned long)(buf[2] & 0x3F)) << 6) | + ((unsigned long)(buf[3] & 0x3F)); + return 4; + } } - return 0; + } + return 0; } /* Get length of grapheme */ -inline int unicodeGraphemeLen(char* buf, int buf_len, int pos) -{ - if (pos == buf_len) { - return 0; - } - int beg = pos; - pos += unicodeUTF8CharLen(buf, buf_len, pos); - while (pos < buf_len) { - int len = unicodeUTF8CharLen(buf, buf_len, pos); - int cp = 0; - unicodeUTF8CharToCodePoint(buf + pos, len, &cp); - if (!unicodeIsCombiningChar(cp)) { - return pos - beg; - } - pos += len; +inline int unicodeGraphemeLen(char *buf, int buf_len, int pos) { + if (pos == buf_len) { + return 0; + } + int beg = pos; + pos += unicodeUTF8CharLen(buf, buf_len, pos); + while (pos < buf_len) { + int len = unicodeUTF8CharLen(buf, buf_len, pos); + int cp = 0; + unicodeUTF8CharToCodePoint(buf + pos, len, &cp); + if (!unicodeIsCombiningChar(cp)) { + return pos - beg; } - return pos - beg; + pos += len; + } + return pos - beg; } /* Get length of previous grapheme */ -inline int unicodePrevGraphemeLen(char* buf, int pos) -{ - if (pos == 0) { - return 0; - } - int end = pos; - while (pos > 0) { - int len = unicodePrevUTF8CharLen(buf, pos); - pos -= len; - int cp = 0; - unicodeUTF8CharToCodePoint(buf + pos, len, &cp); - if (!unicodeIsCombiningChar(cp)) { - return end - pos; - } - } +inline int unicodePrevGraphemeLen(char *buf, int pos) { + if (pos == 0) { return 0; + } + int end = pos; + while (pos > 0) { + int len = unicodePrevUTF8CharLen(buf, pos); + pos -= len; + int cp = 0; + unicodeUTF8CharToCodePoint(buf + pos, len, &cp); + if (!unicodeIsCombiningChar(cp)) { + return end - pos; + } + } + return 0; } -inline int isAnsiEscape(const char* buf, int buf_len, int* len) -{ - if (buf_len > 2 && !memcmp("\033[", buf, 2)) { - int off = 2; - while (off < buf_len) { - switch (buf[off++]) { - case 'A': case 'B': case 'C': case 'D': - case 'E': case 'F': case 'G': case 'H': - case 'J': case 'K': case 'S': case 'T': - case 'f': case 'm': - *len = off; - return 1; - } - } +inline int isAnsiEscape(const char *buf, int buf_len, int *len) { + if (buf_len > 2 && !memcmp("\033[", buf, 2)) { + int off = 2; + while (off < buf_len) { + switch (buf[off++]) { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'J': + case 'K': + case 'S': + case 'T': + case 'f': + case 'm': + *len = off; + return 1; + } } - return 0; + } + return 0; } /* Get column position for the single line mode. */ -inline int unicodeColumnPos(const char* buf, int buf_len) -{ - int ret = 0; - - int off = 0; - while (off < buf_len) { - int len; - if (isAnsiEscape(buf + off, buf_len - off, &len)) { - off += len; - continue; - } - - int cp = 0; - len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); +inline int unicodeColumnPos(const char *buf, int buf_len) { + int ret = 0; + + int off = 0; + while (off < buf_len) { + int len; + if (isAnsiEscape(buf + off, buf_len - off, &len)) { + off += len; + continue; + } - if (!unicodeIsCombiningChar(cp)) { - ret += unicodeIsWideChar(cp) ? 2 : 1; - } + int cp = 0; + len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); - off += len; + if (!unicodeIsCombiningChar(cp)) { + ret += unicodeIsWideChar(cp) ? 2 : 1; } - return ret; + off += len; + } + + return ret; } /* Get column position for the multi line mode. */ -inline int unicodeColumnPosForMultiLine(char* buf, int buf_len, int pos, int cols, int ini_pos) -{ - int ret = 0; - int colwid = ini_pos; - - int off = 0; - while (off < buf_len) { - int cp = 0; - int len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); - - int wid = 0; - if (!unicodeIsCombiningChar(cp)) { - wid = unicodeIsWideChar(cp) ? 2 : 1; - } - - int dif = (int)(colwid + wid) - (int)cols; - if (dif > 0) { - ret += dif; - colwid = wid; - } else if (dif == 0) { - colwid = 0; - } else { - colwid += wid; - } +inline int unicodeColumnPosForMultiLine(char *buf, int buf_len, int pos, + int cols, int ini_pos) { + int ret = 0; + int colwid = ini_pos; + + int off = 0; + while (off < buf_len) { + int cp = 0; + int len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); + + int wid = 0; + if (!unicodeIsCombiningChar(cp)) { + wid = unicodeIsWideChar(cp) ? 2 : 1; + } - if (off >= pos) { - break; - } + int dif = (int)(colwid + wid) - (int)cols; + if (dif > 0) { + ret += dif; + colwid = wid; + } else if (dif == 0) { + colwid = 0; + } else { + colwid += wid; + } - off += len; - ret += wid; + if (off >= pos) { + break; } - return ret; + off += len; + ret += wid; + } + + return ret; } /* Read UTF8 character from file. */ -inline int unicodeReadUTF8Char(int fd, char* buf, int* cp) -{ - int nread = read(fd,&buf[0],1); +inline int unicodeReadUTF8Char(int fd, char *buf, int *cp) { + int nread = read(fd, &buf[0], 1); - if (nread <= 0) { return nread; } + if (nread <= 0) { + return nread; + } - unsigned char byte = buf[0]; + unsigned char byte = buf[0]; - if ((byte & 0x80) == 0) { - ; - } else if ((byte & 0xE0) == 0xC0) { - nread = read(fd,&buf[1],1); - if (nread <= 0) { return nread; } - } else if ((byte & 0xF0) == 0xE0) { - nread = read(fd,&buf[1],2); - if (nread <= 0) { return nread; } - } else if ((byte & 0xF8) == 0xF0) { - nread = read(fd,&buf[1],3); - if (nread <= 0) { return nread; } - } else { - return -1; + if ((byte & 0x80) == 0) { + ; + } else if ((byte & 0xE0) == 0xC0) { + nread = read(fd, &buf[1], 1); + if (nread <= 0) { + return nread; + } + } else if ((byte & 0xF0) == 0xE0) { + nread = read(fd, &buf[1], 2); + if (nread <= 0) { + return nread; + } + } else if ((byte & 0xF8) == 0xF0) { + nread = read(fd, &buf[1], 3); + if (nread <= 0) { + return nread; } + } else { + return -1; + } - return unicodeUTF8CharToCodePoint(buf, 4, cp); + return unicodeUTF8CharToCodePoint(buf, 4, cp); } /* ======================= Low level terminal handling ====================== */ /* Set if to use or not the multi line mode. */ -inline void SetMultiLine(bool ml) { - mlmode = ml; -} +inline void SetMultiLine(bool ml) { mlmode = ml; } /* Return true if the terminal name is in the list of terminals we know are * not able to understand basic escape sequences. */ inline bool isUnsupportedTerm(void) { #ifndef _WIN32 - char *term = getenv("TERM"); - int j; + char *term = getenv("TERM"); + int j; - if (term == NULL) return false; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term,unsupported_term[j])) return true; -#endif + if (term == NULL) return false; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term, unsupported_term[j])) + return true; +#endif + return false; } /* Raw mode: 1960 magic shit. */ inline bool enableRawMode(int fd) { #ifndef _WIN32 - struct termios raw; - - if (!isatty(STDIN_FILENO)) goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = true; - } - if (tcgetattr(fd,&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 */ - // NOTE: Multithreaded issue #20 (https://github.com/yhirose/cpp-linenoise/issues/20) - // 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(fd,TCSAFLUSH,&raw) < 0) goto fatal; - rawmode = true; + struct termios raw; + + if (!isatty(STDIN_FILENO)) + goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = true; + } + if (tcgetattr(fd, &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 */ + // NOTE: Multithreaded issue #20 + // (https://github.com/yhirose/cpp-linenoise/issues/20) 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(fd, TCSAFLUSH, &raw) < 0) + goto fatal; + rawmode = true; #else - if (!atexit_registered) { - /* Cleanup them at exit */ - atexit(linenoiseAtExit); - atexit_registered = true; - - /* Init windows console handles only once */ - hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut==INVALID_HANDLE_VALUE) goto fatal; - } + if (!atexit_registered) { + /* Cleanup them at exit */ + atexit(linenoiseAtExit); + atexit_registered = true; + + /* Init windows console handles only once */ + hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + goto fatal; + } + + DWORD consolemodeOut; + if (!GetConsoleMode(hOut, &consolemodeOut)) { + CloseHandle(hOut); + errno = ENOTTY; + return false; + }; - DWORD consolemodeOut; - if (!GetConsoleMode(hOut, &consolemodeOut)) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - }; - - hIn = GetStdHandle(STD_INPUT_HANDLE); - if (hIn == INVALID_HANDLE_VALUE) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - } + hIn = GetStdHandle(STD_INPUT_HANDLE); + if (hIn == INVALID_HANDLE_VALUE) { + CloseHandle(hOut); + errno = ENOTTY; + return false; + } - GetConsoleMode(hIn, &consolemodeIn); - /* Enable raw mode */ - SetConsoleMode(hIn, consolemodeIn & ~ENABLE_PROCESSED_INPUT); + GetConsoleMode(hIn, &consolemodeIn); + /* Enable raw mode */ + SetConsoleMode(hIn, consolemodeIn & ~ENABLE_PROCESSED_INPUT); - rawmode = true; + rawmode = true; #endif - return true; + return true; fatal: - errno = ENOTTY; - return false; + errno = ENOTTY; + return false; } inline void disableRawMode(int fd) { #ifdef _WIN32 - if (consolemodeIn) { - SetConsoleMode(hIn, consolemodeIn); - consolemodeIn = 0; - } - rawmode = false; + if (consolemodeIn) { + SetConsoleMode(hIn, consolemodeIn); + consolemodeIn = 0; + } + rawmode = false; #else - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) - rawmode = false; + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd, TCSAFLUSH, &orig_termios) != -1) + rawmode = false; #endif } @@ -1678,81 +1829,90 @@ inline void disableRawMode(int fd) { * and return it. On error -1 is returned, on success the position of the * cursor. */ inline int getCursorPosition(int ifd, int ofd) { - char buf[32]; - int cols, rows; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf)-1) { - if (read(ifd,buf+i,1) != 1) break; - if (buf[i] == 'R') break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') return -1; - if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; - return cols; + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) + return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf) - 1) { + if (read(ifd, buf + i, 1) != 1) + break; + if (buf[i] == 'R') + break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') + return -1; + if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) + return -1; + return cols; } /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ inline int getColumns(int ifd, int ofd) { #ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO b; + CONSOLE_SCREEN_BUFFER_INFO b; - if (!GetConsoleScreenBufferInfo(hOut, &b)) return 80; - return b.srWindow.Right - b.srWindow.Left; + if (!GetConsoleScreenBufferInfo(hOut, &b)) + return 80; + return b.srWindow.Right - b.srWindow.Left; #else - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int start, cols; - - /* Get the initial position so we can restore it later. */ - start = getCursorPosition(ifd,ofd); - if (start == -1) goto failed; - - /* Go to right margin and get position. */ - if (write(ofd,"\x1b[999C",6) != 6) goto failed; - cols = getCursorPosition(ifd,ofd); - if (cols == -1) goto failed; - - /* Restore position. */ - if (cols > start) { - char seq[32]; - snprintf(seq,32,"\x1b[%dD",cols-start); - if (write(ofd,seq,strlen(seq)) == -1) { - /* Can't recover... */ - } - } - return cols; - } else { - return ws.ws_col; + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd, ofd); + if (start == -1) + goto failed; + + /* Go to right margin and get position. */ + if (write(ofd, "\x1b[999C", 6) != 6) + goto failed; + cols = getCursorPosition(ifd, ofd); + if (cols == -1) + goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq, 32, "\x1b[%dD", cols - start); + if (write(ofd, seq, strlen(seq)) == -1) { + /* Can't recover... */ + } } + return cols; + } else { + return ws.ws_col; + } failed: - return 80; + return 80; #endif } /* Clear the screen. Used to handle ctrl+l */ inline void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { - /* nothing to do, just to avoid warning. */ - } + if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) <= 0) { + /* nothing to do, just to avoid warning. */ + } } /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ inline void linenoiseBeep(void) { - fprintf(stderr, "\x7"); - fflush(stderr); + fprintf(stderr, "\x7"); + fflush(stderr); } /* ============================== Completion ================================ */ @@ -1764,73 +1924,75 @@ inline void linenoiseBeep(void) { * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { - std::vector lc; - int nread = 0, nwritten; - *c = 0; - - completionCallback(ls->buf,lc); - if (lc.empty()) { - linenoiseBeep(); - } else { - int stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < static_cast(lc.size())) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = static_cast(lc[i].size()); - ls->buf = &lc[i][0]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - //nread = read(ls->ifd,&c,1); + std::vector lc; + int nread = 0, nwritten; + *c = 0; + + completionCallback(ls->buf, lc); + if (lc.empty()) { + linenoiseBeep(); + } else { + int stop = 0, i = 0; + + while (!stop) { + /* Show completion or original buffer */ + if (i < static_cast(lc.size())) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = static_cast(lc[i].size()); + ls->buf = &lc[i][0]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + // nread = read(ls->ifd,&c,1); #ifdef _WIN32 - nread = win32read(c); - if (nread == 1) { - cbuf[0] = *c; - } + nread = win32read(c); + if (nread == 1) { + cbuf[0] = *c; + } #else - nread = unicodeReadUTF8Char(ls->ifd,cbuf,c); + nread = unicodeReadUTF8Char(ls->ifd, cbuf, c); #endif - if (nread <= 0) { - *c = -1; - return nread; - } - - switch(*c) { - case 9: /* tab */ - i = (i+1) % (lc.size()+1); - if (i == static_cast(lc.size())) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < static_cast(lc.size())) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < static_cast(lc.size())) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",&lc[i][0]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } + if (nread <= 0) { + *c = -1; + return nread; + } + + switch (*c) { + case 9: /* tab */ + i = (i + 1) % (lc.size() + 1); + if (i == static_cast(lc.size())) + linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < static_cast(lc.size())) + refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < static_cast(lc.size())) { + nwritten = snprintf(ls->buf, ls->buflen, "%s", &lc[i][0]); + ls->len = ls->pos = nwritten; } + stop = 1; + break; + } } + } - return nread; + return nread; } /* Register a callback function to be called for tab-completion. */ inline void SetCompletionCallback(CompletionCallback fn) { - completionCallback = fn; + completionCallback = fn; } /* =========================== Line editing ================================= */ @@ -1840,37 +2002,39 @@ inline void SetCompletionCallback(CompletionCallback fn) { * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ inline void refreshSingleLine(struct linenoiseState *l) { - char seq[64]; - int pcolwid = unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); - int fd = l->ofd; - char *buf = l->buf; - int len = l->len; - int pos = l->pos; - std::string ab; - - while((pcolwid+unicodeColumnPos(buf, pos)) >= l->cols) { - int glen = unicodeGraphemeLen(buf, len, 0); - buf += glen; - len -= glen; - pos -= glen; - } - while (pcolwid+unicodeColumnPos(buf, len) > l->cols) { - len -= unicodePrevGraphemeLen(buf, len); - } - - /* Cursor to left edge */ - snprintf(seq,64,"\r"); - ab += seq; - /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(buf, len); - /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); - ab += seq; - /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(unicodeColumnPos(buf, pos)+pcolwid)); - ab += seq; - if (write(fd,ab.c_str(), static_cast(ab.length())) == -1) {} /* Can't recover from write error. */ + char seq[64]; + int pcolwid = + unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); + int fd = l->ofd; + char *buf = l->buf; + int len = l->len; + int pos = l->pos; + std::string ab; + + while ((pcolwid + unicodeColumnPos(buf, pos)) >= l->cols) { + int glen = unicodeGraphemeLen(buf, len, 0); + buf += glen; + len -= glen; + pos -= glen; + } + while (pcolwid + unicodeColumnPos(buf, len) > l->cols) { + len -= unicodePrevGraphemeLen(buf, len); + } + + /* Cursor to left edge */ + snprintf(seq, 64, "\r"); + ab += seq; + /* Write the prompt and the current buffer content */ + ab += l->prompt; + ab.append(buf, len); + /* Erase to right */ + snprintf(seq, 64, "\x1b[0K"); + ab += seq; + /* Move cursor to original position. */ + snprintf(seq, 64, "\r\x1b[%dC", (int)(unicodeColumnPos(buf, pos) + pcolwid)); + ab += seq; + if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { + } /* Can't recover from write error. */ } /* Multi line low level line refresh. @@ -1878,148 +2042,160 @@ inline void refreshSingleLine(struct linenoiseState *l) { * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ inline void refreshMultiLine(struct linenoiseState *l) { - char seq[64]; - int pcolwid = unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); - int colpos = unicodeColumnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcolwid); - int colpos2; /* cursor column position. */ - int rows = (pcolwid+colpos+l->cols-1)/l->cols; /* rows used by current buf. */ - int rpos = (pcolwid+l->oldcolpos+l->cols)/l->cols; /* cursor relative row. */ - int rpos2; /* rpos after refresh. */ - int col; /* column position, zero-based. */ - int old_rows = (int)l->maxrows; - int fd = l->ofd, j; - std::string ab; - - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; - - /* First step: clear all the lines used before. To do so start by - * going to the last row. */ - if (old_rows-rpos > 0) { - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); - ab += seq; - } - - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); - ab += seq; - } + char seq[64]; + int pcolwid = + unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); + int colpos = + unicodeColumnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcolwid); + int colpos2; /* cursor column position. */ + int rows = (pcolwid + colpos + l->cols - 1) / + l->cols; /* rows used by current buf. */ + int rpos = + (pcolwid + l->oldcolpos + l->cols) / l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* column position, zero-based. */ + int old_rows = (int)l->maxrows; + int fd = l->ofd, j; + std::string ab; + + /* Update maxrows if needed. */ + if (rows > (int)l->maxrows) + l->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + if (old_rows - rpos > 0) { + snprintf(seq, 64, "\x1b[%dB", old_rows - rpos); + ab += seq; + } - /* Clean the top line. */ - snprintf(seq,64,"\r\x1b[0K"); + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows - 1; j++) { + snprintf(seq, 64, "\r\x1b[0K\x1b[1A"); ab += seq; + } - /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(l->buf, l->len); + /* Clean the top line. */ + snprintf(seq, 64, "\r\x1b[0K"); + ab += seq; - /* Get text width to cursor position */ - colpos2 = unicodeColumnPosForMultiLine(l->buf, l->len, l->pos, l->cols, pcolwid); + /* Write the prompt and the current buffer content */ + ab += l->prompt; + ab.append(l->buf, l->len); - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (l->pos && - l->pos == l->len && - (colpos2+pcolwid) % l->cols == 0) - { - ab += "\n"; - snprintf(seq,64,"\r"); - ab += seq; - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } + /* Get text width to cursor position */ + colpos2 = + unicodeColumnPosForMultiLine(l->buf, l->len, l->pos, l->cols, pcolwid); - /* Move cursor to right position. */ - rpos2 = (pcolwid+colpos2+l->cols)/l->cols; /* current cursor relative row. */ - - /* Go up till we reach the expected position. */ - if (rows-rpos2 > 0) { - snprintf(seq,64,"\x1b[%dA", rows-rpos2); - ab += seq; - } - - /* Set column. */ - col = (pcolwid + colpos2) % l->cols; - if (col) - snprintf(seq,64,"\r\x1b[%dC", col); - else - snprintf(seq,64,"\r"); + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && l->pos == l->len && (colpos2 + pcolwid) % l->cols == 0) { + ab += "\n"; + snprintf(seq, 64, "\r"); + ab += seq; + rows++; + if (rows > (int)l->maxrows) + l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (pcolwid + colpos2 + l->cols) / + l->cols; /* current cursor relative row. */ + + /* Go up till we reach the expected position. */ + if (rows - rpos2 > 0) { + snprintf(seq, 64, "\x1b[%dA", rows - rpos2); ab += seq; + } - l->oldcolpos = colpos2; + /* Set column. */ + col = (pcolwid + colpos2) % l->cols; + if (col) + snprintf(seq, 64, "\r\x1b[%dC", col); + else + snprintf(seq, 64, "\r"); + ab += seq; - if (write(fd,ab.c_str(), static_cast(ab.length())) == -1) {} /* Can't recover from write error. */ + l->oldcolpos = colpos2; + + if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { + } /* Can't recover from write error. */ } /* Calls the two low level functions refreshSingleLine() or * refreshMultiLine() according to the selected mode. */ inline void refreshLine(struct linenoiseState *l) { - if (mlmode) - refreshMultiLine(l); - else - refreshSingleLine(l); + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); } /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ -inline int linenoiseEditInsert(struct linenoiseState *l, const char* cbuf, int clen) { - if (l->len < l->buflen) { - if (l->len == l->pos) { - memcpy(&l->buf[l->pos],cbuf,clen); - l->pos+=clen; - l->len+=clen;; - l->buf[l->len] = '\0'; - if ((!mlmode && unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length()))+unicodeColumnPos(l->buf,l->len) < l->cols) /* || mlmode */) { - /* Avoid a full update of the line in the - * trivial case. */ - if (write(l->ofd,cbuf,clen) == -1) return -1; - } else { - refreshLine(l); - } - } else { - memmove(l->buf+l->pos+clen,l->buf+l->pos,l->len-l->pos); - memcpy(&l->buf[l->pos],cbuf,clen); - l->pos+=clen; - l->len+=clen; - l->buf[l->len] = '\0'; - refreshLine(l); - } +inline int linenoiseEditInsert(struct linenoiseState *l, const char *cbuf, + int clen) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + memcpy(&l->buf[l->pos], cbuf, clen); + l->pos += clen; + l->len += clen; + ; + l->buf[l->len] = '\0'; + if ((!mlmode && unicodeColumnPos(l->prompt.c_str(), + static_cast(l->prompt.length())) + + unicodeColumnPos(l->buf, l->len) < + l->cols) /* || mlmode */) { + /* Avoid a full update of the line in the + * trivial case. */ + if (write(l->ofd, cbuf, clen) == -1) + return -1; + } else { + refreshLine(l); + } + } else { + memmove(l->buf + l->pos + clen, l->buf + l->pos, l->len - l->pos); + memcpy(&l->buf[l->pos], cbuf, clen); + l->pos += clen; + l->len += clen; + l->buf[l->len] = '\0'; + refreshLine(l); } - return 0; + } + return 0; } /* Move cursor on the left. */ inline void linenoiseEditMoveLeft(struct linenoiseState *l) { - if (l->pos > 0) { - l->pos -= unicodePrevGraphemeLen(l->buf, l->pos); - refreshLine(l); - } + if (l->pos > 0) { + l->pos -= unicodePrevGraphemeLen(l->buf, l->pos); + refreshLine(l); + } } /* Move cursor on the right. */ inline void linenoiseEditMoveRight(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos += unicodeGraphemeLen(l->buf, l->len, l->pos); - refreshLine(l); - } + if (l->pos != l->len) { + l->pos += unicodeGraphemeLen(l->buf, l->len, l->pos); + refreshLine(l); + } } /* Move cursor to the start of the line. */ inline void linenoiseEditMoveHome(struct linenoiseState *l) { - if (l->pos != 0) { - l->pos = 0; - refreshLine(l); - } + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } } /* Move cursor to the end of the line. */ inline void linenoiseEditMoveEnd(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos = l->len; - refreshLine(l); - } + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } } /* Substitute the currently edited line with the next or previous history @@ -2027,64 +2203,64 @@ inline void linenoiseEditMoveEnd(struct linenoiseState *l) { #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 inline void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { - if (history.size() > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - history[history.size() - 1 - l->history_index] = l->buf; - /* Show the new entry */ - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (l->history_index < 0) { - l->history_index = 0; - return; - } else if (l->history_index >= (int)history.size()) { - l->history_index = static_cast(history.size())-1; - return; - } - memset(l->buf, 0, l->buflen); - strcpy(l->buf,history[history.size() - 1 - l->history_index].c_str()); - l->len = l->pos = static_cast(strlen(l->buf)); - refreshLine(l); + if (history.size() > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + history[history.size() - 1 - l->history_index] = l->buf; + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= (int)history.size()) { + l->history_index = static_cast(history.size()) - 1; + return; } + memset(l->buf, 0, l->buflen); + strcpy(l->buf, history[history.size() - 1 - l->history_index].c_str()); + l->len = l->pos = static_cast(strlen(l->buf)); + refreshLine(l); + } } /* Delete the character at the right of the cursor without altering the cursor * position. Basically this is what happens with the "Delete" keyboard key. */ inline void linenoiseEditDelete(struct linenoiseState *l) { - if (l->len > 0 && l->pos < l->len) { - int glen = unicodeGraphemeLen(l->buf,l->len,l->pos); - memmove(l->buf+l->pos,l->buf+l->pos+glen,l->len-l->pos-glen); - l->len-=glen; - l->buf[l->len] = '\0'; - refreshLine(l); - } + if (l->len > 0 && l->pos < l->len) { + int glen = unicodeGraphemeLen(l->buf, l->len, l->pos); + memmove(l->buf + l->pos, l->buf + l->pos + glen, l->len - l->pos - glen); + l->len -= glen; + l->buf[l->len] = '\0'; + refreshLine(l); + } } /* Backspace implementation. */ inline void linenoiseEditBackspace(struct linenoiseState *l) { - if (l->pos > 0 && l->len > 0) { - int glen = unicodePrevGraphemeLen(l->buf,l->pos); - memmove(l->buf+l->pos-glen,l->buf+l->pos,l->len-l->pos); - l->pos-=glen; - l->len-=glen; - l->buf[l->len] = '\0'; - refreshLine(l); - } + if (l->pos > 0 && l->len > 0) { + int glen = unicodePrevGraphemeLen(l->buf, l->pos); + memmove(l->buf + l->pos - glen, l->buf + l->pos, l->len - l->pos); + l->pos -= glen; + l->len -= glen; + l->buf[l->len] = '\0'; + refreshLine(l); + } } /* Delete the previous word, maintaining the cursor at the start of the * current word. */ inline void linenoiseEditDeletePrevWord(struct linenoiseState *l) { - int old_pos = l->pos; - int diff; - - while (l->pos > 0 && l->buf[l->pos-1] == ' ') - l->pos--; - while (l->pos > 0 && l->buf[l->pos-1] != ' ') - l->pos--; - diff = old_pos - l->pos; - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); - l->len -= diff; - refreshLine(l); + int old_pos = l->pos; + int diff; + + while (l->pos > 0 && l->buf[l->pos - 1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos - 1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1); + l->len -= diff; + refreshLine(l); } /* This function is the core of the line editing capability of linenoise. @@ -2095,213 +2271,225 @@ inline void linenoiseEditDeletePrevWord(struct linenoiseState *l) { * when ctrl+d is typed. * * The function returns the length of the current buffer. */ -inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, const char *prompt) -{ - struct linenoiseState l; - - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.oldcolpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; - - /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.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. */ - AddHistory(""); - - if (write(l.ofd,prompt, static_cast(l.prompt.length())) == -1) return -1; - while(1) { - int c; - char cbuf[4]; - int nread; - char seq[3]; +inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, + const char *prompt) { + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.ifd = stdin_fd; + l.ofd = stdout_fd; + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.oldcolpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(stdin_fd, stdout_fd); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.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. */ + AddHistory(""); + + if (write(l.ofd, prompt, static_cast(l.prompt.length())) == -1) + return -1; + while (1) { + int c; + char cbuf[4]; + int nread; + char seq[3]; #ifdef _WIN32 - nread = win32read(&c); - if (nread == 1) { - cbuf[0] = c; - } + nread = win32read(&c); + if (nread == 1) { + cbuf[0] = c; + } #else - nread = unicodeReadUTF8Char(l.ifd,cbuf,&c); + nread = unicodeReadUTF8Char(l.ifd, cbuf, &c); #endif - if (nread <= 0) return (int)l.len; - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - nread = completeLine(&l,cbuf,&c); - /* Return on errors */ - if (c < 0) return l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } + if (nread <= 0) + return (int)l.len; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + nread = completeLine(&l, cbuf, &c); + /* Return on errors */ + if (c < 0) + return l.len; + /* Read next character when 0 */ + if (c == 0) + continue; + } - switch(c) { - case ENTER: /* enter */ - if (!history.empty()) history.pop_back(); - if (mlmode) linenoiseEditMoveEnd(&l); - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history.pop_back(); - return -1; - } + switch (c) { + case ENTER: /* enter */ + if (!history.empty()) + history.pop_back(); + if (mlmode) + linenoiseEditMoveEnd(&l); + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { + history.pop_back(); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + char aux = buf[l.pos - 1]; + buf[l.pos - 1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len - 1) + l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l.ifd, seq, 1) == -1) + break; + if (read(l.ifd, seq + 1, 1) == -1) + break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l.ifd, seq + 2, 1) == -1) break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - char aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); + if (seq[2] == '~') { + switch (seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ + } + } else { + switch (seq[1]) { + case 'A': /* Up */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); break; - case CTRL_N: /* ctrl-n */ + case 'B': /* Down */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { - switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch(seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - break; - default: - if (linenoiseEditInsert(&l,cbuf,nread)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); + case 'C': /* Right */ + linenoiseEditMoveRight(&l); break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ + case 'H': /* Home */ linenoiseEditMoveHome(&l); break; - case CTRL_E: /* ctrl+e, go to the end of the line */ + case 'F': /* End*/ linenoiseEditMoveEnd(&l); break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; } + } + break; + default: + if (linenoiseEditInsert(&l, cbuf, nread)) + return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; } - return l.len; + } + return l.len; } /* This function calls the line editing function linenoiseEdit() using * the STDIN file descriptor set in raw mode. */ -inline bool linenoiseRaw(const char *prompt, std::string& line) { - bool quit = false; +inline bool linenoiseRaw(const char *prompt, std::string &line) { + bool quit = false; + + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. */ + std::getline(std::cin, line); + } else { + /* Interactive editing. */ + if (enableRawMode(STDIN_FILENO) == false) { + return quit; + } - if (!isatty(STDIN_FILENO)) { - /* Not a tty: read from file / pipe. */ - std::getline(std::cin, line); + char buf[LINENOISE_MAX_LINE]; + auto count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, + LINENOISE_MAX_LINE, prompt); + if (count == -1) { + quit = true; } else { - /* Interactive editing. */ - if (enableRawMode(STDIN_FILENO) == false) { - return quit; - } - - char buf[LINENOISE_MAX_LINE]; - auto count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, LINENOISE_MAX_LINE, prompt); - if (count == -1) { - quit = true; - } else { - line.assign(buf, count); - } - - disableRawMode(STDIN_FILENO); - printf("\n"); + line.assign(buf, count); } - return quit; + + disableRawMode(STDIN_FILENO); + printf("\n"); + } + return quit; } /* The high level function that is the main API of the linenoise library. @@ -2309,34 +2497,32 @@ inline bool linenoiseRaw(const char *prompt, std::string& line) { * for a blacklist of stupid terminals, and later either calls the line * editing function or uses dummy fgets() so that you will be able to type * something even in the most desperate of the conditions. */ -inline bool Readline(const char *prompt, std::string& line) { - if (isUnsupportedTerm()) { - printf("%s",prompt); - fflush(stdout); - std::getline(std::cin, line); - return false; - } else { - return linenoiseRaw(prompt, line); - } +inline bool Readline(const char *prompt, std::string &line) { + if (isUnsupportedTerm()) { + printf("%s", prompt); + fflush(stdout); + std::getline(std::cin, line); + return false; + } else { + return linenoiseRaw(prompt, line); + } } -inline std::string Readline(const char *prompt, bool& quit) { - std::string line; - quit = Readline(prompt, line); - return line; +inline std::string Readline(const char *prompt, bool &quit) { + std::string line; + quit = Readline(prompt, line); + return line; } inline std::string Readline(const char *prompt) { - bool quit; // dummy - return Readline(prompt, quit); + bool quit; // dummy + return Readline(prompt, quit); } /* ================================ History ================================= */ /* At exit we'll try to fix the terminal to the initial conditions. */ -inline void linenoiseAtExit(void) { - disableRawMode(STDIN_FILENO); -} +inline void linenoiseAtExit(void) { disableRawMode(STDIN_FILENO); } /* This is the API call to add a new entry in the linenoise history. * It uses a fixed array of char pointers that are shifted (memmoved) @@ -2345,19 +2531,21 @@ inline void linenoiseAtExit(void) { * histories, but will work well for a few hundred of entries. * * Using a circular buffer is smarter, but a bit more complex to handle. */ -inline bool AddHistory(const char* line) { - if (history_max_len == 0) return false; +inline bool AddHistory(const char *line) { + if (history_max_len == 0) + return false; - /* Don't add duplicated lines. */ - if (!history.empty() && history.back() == line) return false; + /* Don't add duplicated lines. */ + if (!history.empty() && history.back() == line) + return false; - /* If we reached the max length, remove the older line. */ - if (history.size() == history_max_len) { - history.erase(history.begin()); - } - history.push_back(line); + /* If we reached the max length, remove the older line. */ + if (history.size() == history_max_len) { + history.erase(history.begin()); + } + history.push_back(line); - return true; + return true; } /* Set the maximum length for the history. This function can be called even @@ -2365,23 +2553,25 @@ inline bool AddHistory(const char* line) { * just the latest 'len' elements if the new history length value is smaller * than the amount of items already inside the history. */ inline bool SetHistoryMaxLen(size_t len) { - if (len < 1) return false; - history_max_len = len; - if (len < history.size()) { - history.resize(len); - } - return true; + if (len < 1) + return false; + history_max_len = len; + if (len < history.size()) { + history.resize(len); + } + return true; } /* Save the history in the specified file. On success *true* is returned * otherwise *false* is returned. */ -inline bool SaveHistory(const char* path) { - std::ofstream f(path); // TODO: need 'std::ios::binary'? - if (!f) return false; - for (const auto& h: history) { - f << h << std::endl; - } - return true; +inline bool SaveHistory(const char *path) { + std::ofstream f(path); // TODO: need 'std::ios::binary'? + if (!f) + return false; + for (const auto &h : history) { + f << h << std::endl; + } + return true; } /* Load the history from the specified file. If the file does not exist @@ -2389,19 +2579,18 @@ inline bool SaveHistory(const char* path) { * * If the file exists and the operation succeeded *true* is returned, otherwise * on error *false* is returned. */ -inline bool LoadHistory(const char* path) { - std::ifstream f(path); - if (!f) return false; - std::string line; - while (std::getline(f, line)) { - AddHistory(line.c_str()); - } - return true; +inline bool LoadHistory(const char *path) { + std::ifstream f(path); + if (!f) + return false; + std::string line; + while (std::getline(f, line)) { + AddHistory(line.c_str()); + } + return true; } -inline const std::vector& GetHistory() { - return history; -} +inline const std::vector &GetHistory() { return history; } } // namespace linenoise From 34b4986ec482c8529681c7557f17ed64f021e853 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:20:12 -0400 Subject: [PATCH 02/15] Reduce scope of variable --- linenoise.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index 3f6b5a5..e765e98 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -364,7 +364,6 @@ inline void SendSequence(LPCWSTR seq) { //----------------------------------------------------------------------------- inline void InterpretEscSeq(void) { - int i; WORD attribute; CONSOLE_SCREEN_BUFFER_INFO Info; CONSOLE_CURSOR_INFO CursInfo; @@ -391,7 +390,7 @@ inline void InterpretEscSeq(void) { case 'm': if (es_argc == 0) es_argv[es_argc++] = 0; - for (i = 0; i < es_argc; i++) { + for (int i = 0; i < es_argc; i++) { if (30 <= es_argv[i] && es_argv[i] <= 37) grm.foreground = es_argv[i] - 30; else if (40 <= es_argv[i] && es_argv[i] <= 47) From 99eab32c85879b9824d04877489821d78203ca05 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:33:50 -0400 Subject: [PATCH 03/15] Make a lot of the linenoise functions into methods on a class. --- linenoise.hpp | 535 +++++++++++++++++++++++++++----------------------- 1 file changed, 285 insertions(+), 250 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index e765e98..564009c 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -160,11 +160,76 @@ #include #include +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 + namespace linenoise { typedef std::function &)> CompletionCallback; +/* The linenoiseState structure represents the state during line editing, and + * provides methods by which user programs can act on that state. */ +class linenoiseState { +public: + void Initialize(int stdin_fd, int stdout_fd, int buflen, const char *prompt); + + void EnableMultiLine(bool); + + bool AddHistory(const char *line); + bool LoadHistory(const char *path); + bool SaveHistory(const char *path); + const std::vector &GetHistory() { return history; }; + bool SetHistoryMaxLen(size_t len); + + bool Readline(std::string &line); // Primary linenoise entry point + void RefreshLine(); // Restore current line (used after WipeLine) + + /* Register a callback function to be called for tab-completion. */ + void SetCompletionCallback(CompletionCallback fn) { + completionCallback = fn; + }; + + std::string prompt = std::string("> "); /* Prompt to display. */ + +private: + std::string Readline(bool &quit); + std::string Readline(); + + bool linenoiseRaw(std::string &line); + int linenoiseEditInsert(const char *cbuf, int clen); + void linenoiseEditDelete(); + void linenoiseEditBackspace(); + void linenoiseEditDeletePrevWord(); + int linenoiseEdit(); + void linenoiseEditHistoryNext(int dir); + void linenoiseEditMoveLeft(); + void linenoiseEditMoveRight(); + void linenoiseEditMoveHome(); + void linenoiseEditMoveEnd(); + void refreshSingleLine(); + void refreshMultiLine(); + int completeLine(char *cbuf, int *c); + + CompletionCallback completionCallback; + + int ifd = STDIN_FILENO; /* Terminal stdin file descriptor. */ + int ofd = STDOUT_FILENO; /* Terminal stdout file descriptor. */ + char *buf = wbuf; /* Edited line buffer. */ + int buflen = LINENOISE_MAX_LINE; /* Edited line buffer size. */ + int pos = 0; /* Current cursor position. */ + int oldcolpos = 0; /* Previous refresh cursor column position. */ + int len = 0; /* Current edited line length. */ + int cols = 0; /* Number of columns in terminal. */ + int maxrows = 0; /* Maximum num of rows used so far (multiline mode) */ + int history_index = -1; /* The history index we are currently editing. */ + char wbuf[LINENOISE_MAX_LINE] = {'\0'}; + std::string history_tmpbuf; + + size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; + std::vector history; +}; + #ifdef _WIN32 namespace ansi { @@ -1077,10 +1142,7 @@ inline int win32_write(int fd, const void *buffer, unsigned int count) { } #endif // _WIN32 -#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 -#define LINENOISE_MAX_LINE 4096 static const char *unsupported_term[] = {"dumb", "cons25", "emacs", NULL}; -static CompletionCallback completionCallback; #ifndef _WIN32 static struct termios orig_termios; /* In order to restore at exit.*/ @@ -1089,25 +1151,6 @@ static bool rawmode = false; /* For atexit() function to check if restore is needed*/ static bool mlmode = false; /* Multi line mode. Default is single line. */ static bool atexit_registered = false; /* Register atexit just 1 time. */ -static size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; -static std::vector history; - -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - int buflen; /* Edited line buffer size. */ - std::string prompt; /* Prompt to display. */ - int pos; /* Current cursor position. */ - int oldcolpos; /* Previous refresh cursor column position. */ - int len; /* Current edited line length. */ - int cols; /* Number of columns in terminal. */ - int maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ -}; enum KEY_ACTION { KEY_NULL = 0, /* NULL */ @@ -1132,8 +1175,6 @@ enum KEY_ACTION { }; void linenoiseAtExit(void); -bool AddHistory(const char *line); -void refreshLine(struct linenoiseState *l); /* ============================ UTF8 utilities ============================== */ @@ -1922,12 +1963,12 @@ inline void linenoiseBeep(void) { * * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ -inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { +int linenoiseState::completeLine(char *cbuf, int *c) { std::vector lc; int nread = 0, nwritten; *c = 0; - completionCallback(ls->buf, lc); + completionCallback(buf, lc); if (lc.empty()) { linenoiseBeep(); } else { @@ -1936,16 +1977,17 @@ inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { while (!stop) { /* Show completion or original buffer */ if (i < static_cast(lc.size())) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = static_cast(lc[i].size()); - ls->buf = &lc[i][0]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; + int old_len = len; + int old_pos = pos; + char *old_buf = buf; + len = pos = static_cast(lc[i].size()); + buf = &lc[i][0]; + RefreshLine(); + len = old_len; + pos = old_pos; + buf = old_buf; } else { - refreshLine(ls); + RefreshLine(); } // nread = read(ls->ifd,&c,1); @@ -1955,7 +1997,7 @@ inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { cbuf[0] = *c; } #else - nread = unicodeReadUTF8Char(ls->ifd, cbuf, c); + nread = unicodeReadUTF8Char(ifd, cbuf, c); #endif if (nread <= 0) { *c = -1; @@ -1971,14 +2013,14 @@ inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { case 27: /* escape */ /* Re-show original buffer */ if (i < static_cast(lc.size())) - refreshLine(ls); + RefreshLine(); stop = 1; break; default: /* Update buffer and return */ if (i < static_cast(lc.size())) { - nwritten = snprintf(ls->buf, ls->buflen, "%s", &lc[i][0]); - ls->len = ls->pos = nwritten; + nwritten = snprintf(buf, buflen, "%s", &lc[i][0]); + len = pos = nwritten; } stop = 1; break; @@ -1989,34 +2031,26 @@ inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { return nread; } -/* Register a callback function to be called for tab-completion. */ -inline void SetCompletionCallback(CompletionCallback fn) { - completionCallback = fn; -} - /* =========================== Line editing ================================= */ /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ -inline void refreshSingleLine(struct linenoiseState *l) { +void linenoiseState::refreshSingleLine() { char seq[64]; int pcolwid = - unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); - int fd = l->ofd; - char *buf = l->buf; - int len = l->len; - int pos = l->pos; + unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); + int fd = ofd; std::string ab; - while ((pcolwid + unicodeColumnPos(buf, pos)) >= l->cols) { + while ((pcolwid + unicodeColumnPos(buf, pos)) >= cols) { int glen = unicodeGraphemeLen(buf, len, 0); buf += glen; len -= glen; pos -= glen; } - while (pcolwid + unicodeColumnPos(buf, len) > l->cols) { + while (pcolwid + unicodeColumnPos(buf, len) > cols) { len -= unicodePrevGraphemeLen(buf, len); } @@ -2024,7 +2058,7 @@ inline void refreshSingleLine(struct linenoiseState *l) { snprintf(seq, 64, "\r"); ab += seq; /* Write the prompt and the current buffer content */ - ab += l->prompt; + ab += prompt; ab.append(buf, len); /* Erase to right */ snprintf(seq, 64, "\x1b[0K"); @@ -2040,26 +2074,24 @@ inline void refreshSingleLine(struct linenoiseState *l) { * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ -inline void refreshMultiLine(struct linenoiseState *l) { +void linenoiseState::refreshMultiLine() { char seq[64]; int pcolwid = - unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); - int colpos = - unicodeColumnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcolwid); + unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); + int colpos = unicodeColumnPosForMultiLine(buf, len, len, cols, pcolwid); int colpos2; /* cursor column position. */ - int rows = (pcolwid + colpos + l->cols - 1) / - l->cols; /* rows used by current buf. */ - int rpos = - (pcolwid + l->oldcolpos + l->cols) / l->cols; /* cursor relative row. */ - int rpos2; /* rpos after refresh. */ + int rows = + (pcolwid + colpos + cols - 1) / cols; /* rows used by current buf. */ + int rpos = (pcolwid + oldcolpos + cols) / cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ int col; /* column position, zero-based. */ - int old_rows = (int)l->maxrows; - int fd = l->ofd, j; + int old_rows = (int)maxrows; + int fd = ofd, j; std::string ab; /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) - l->maxrows = rows; + if (rows > (int)maxrows) + maxrows = rows; /* First step: clear all the lines used before. To do so start by * going to the last row. */ @@ -2079,27 +2111,25 @@ inline void refreshMultiLine(struct linenoiseState *l) { ab += seq; /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(l->buf, l->len); + ab += prompt; + ab.append(buf, len); /* Get text width to cursor position */ - colpos2 = - unicodeColumnPosForMultiLine(l->buf, l->len, l->pos, l->cols, pcolwid); + colpos2 = unicodeColumnPosForMultiLine(buf, len, pos, cols, pcolwid); /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the prompt to the first column. */ - if (l->pos && l->pos == l->len && (colpos2 + pcolwid) % l->cols == 0) { + if (pos && pos == len && (colpos2 + pcolwid) % cols == 0) { ab += "\n"; snprintf(seq, 64, "\r"); ab += seq; rows++; - if (rows > (int)l->maxrows) - l->maxrows = rows; + if (rows > (int)maxrows) + maxrows = rows; } /* Move cursor to right position. */ - rpos2 = (pcolwid + colpos2 + l->cols) / - l->cols; /* current cursor relative row. */ + rpos2 = (pcolwid + colpos2 + cols) / cols; /* current cursor relative row. */ /* Go up till we reach the expected position. */ if (rows - rpos2 > 0) { @@ -2108,92 +2138,93 @@ inline void refreshMultiLine(struct linenoiseState *l) { } /* Set column. */ - col = (pcolwid + colpos2) % l->cols; + col = (pcolwid + colpos2) % cols; if (col) snprintf(seq, 64, "\r\x1b[%dC", col); else snprintf(seq, 64, "\r"); ab += seq; - l->oldcolpos = colpos2; + oldcolpos = colpos2; if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { } /* Can't recover from write error. */ } /* Calls the two low level functions refreshSingleLine() or - * refreshMultiLine() according to the selected mode. */ -inline void refreshLine(struct linenoiseState *l) { + * refreshMultiLine() according to the selected mode. + * + * */ +void linenoiseState::RefreshLine() { if (mlmode) - refreshMultiLine(l); + refreshMultiLine(); else - refreshSingleLine(l); + refreshSingleLine(); } /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ -inline int linenoiseEditInsert(struct linenoiseState *l, const char *cbuf, - int clen) { - if (l->len < l->buflen) { - if (l->len == l->pos) { - memcpy(&l->buf[l->pos], cbuf, clen); - l->pos += clen; - l->len += clen; +int linenoiseState::linenoiseEditInsert(const char *cbuf, int clen) { + if (len < buflen) { + if (len == pos) { + memcpy(&buf[pos], cbuf, clen); + pos += clen; + len += clen; ; - l->buf[l->len] = '\0'; - if ((!mlmode && unicodeColumnPos(l->prompt.c_str(), - static_cast(l->prompt.length())) + - unicodeColumnPos(l->buf, l->len) < - l->cols) /* || mlmode */) { + buf[len] = '\0'; + if ((!mlmode && + unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())) + + unicodeColumnPos(buf, len) < + cols) /* || mlmode */) { /* Avoid a full update of the line in the * trivial case. */ - if (write(l->ofd, cbuf, clen) == -1) + if (write(ofd, cbuf, clen) == -1) return -1; } else { - refreshLine(l); + RefreshLine(); } } else { - memmove(l->buf + l->pos + clen, l->buf + l->pos, l->len - l->pos); - memcpy(&l->buf[l->pos], cbuf, clen); - l->pos += clen; - l->len += clen; - l->buf[l->len] = '\0'; - refreshLine(l); + memmove(buf + pos + clen, buf + pos, len - pos); + memcpy(&buf[pos], cbuf, clen); + pos += clen; + len += clen; + buf[len] = '\0'; + RefreshLine(); } } return 0; } /* Move cursor on the left. */ -inline void linenoiseEditMoveLeft(struct linenoiseState *l) { - if (l->pos > 0) { - l->pos -= unicodePrevGraphemeLen(l->buf, l->pos); - refreshLine(l); +void linenoiseState::linenoiseEditMoveLeft() { + if (pos > 0) { + pos -= unicodePrevGraphemeLen(buf, pos); + RefreshLine(); } } /* Move cursor on the right. */ -inline void linenoiseEditMoveRight(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos += unicodeGraphemeLen(l->buf, l->len, l->pos); - refreshLine(l); +void linenoiseState::linenoiseEditMoveRight() { + if (pos != len) { + pos += unicodeGraphemeLen(buf, len, pos); + RefreshLine(); } } /* Move cursor to the start of the line. */ -inline void linenoiseEditMoveHome(struct linenoiseState *l) { - if (l->pos != 0) { - l->pos = 0; - refreshLine(l); +void linenoiseState::linenoiseEditMoveHome() { + if (pos != 0) { + pos = 0; + RefreshLine(); } } /* Move cursor to the end of the line. */ -inline void linenoiseEditMoveEnd(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos = l->len; - refreshLine(l); +void linenoiseState::linenoiseEditMoveEnd() { + if (pos != len) { + pos = len; + RefreshLine(); } } @@ -2201,65 +2232,65 @@ inline void linenoiseEditMoveEnd(struct linenoiseState *l) { * entry as specified by 'dir'. */ #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 -inline void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { +void linenoiseState::linenoiseEditHistoryNext(int dir) { if (history.size() > 1) { /* Update the current history entry before to * overwrite it with the next one. */ - history[history.size() - 1 - l->history_index] = l->buf; + history[history.size() - 1 - history_index] = buf; /* Show the new entry */ - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (l->history_index < 0) { - l->history_index = 0; + history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (history_index < 0) { + history_index = 0; return; - } else if (l->history_index >= (int)history.size()) { - l->history_index = static_cast(history.size()) - 1; + } else if (history_index >= (int)history.size()) { + history_index = static_cast(history.size()) - 1; return; } - memset(l->buf, 0, l->buflen); - strcpy(l->buf, history[history.size() - 1 - l->history_index].c_str()); - l->len = l->pos = static_cast(strlen(l->buf)); - refreshLine(l); + memset(buf, 0, l->buflen); + strcpy(buf, history[history.size() - 1 - history_index].c_str()); + len = pos = static_cast(strlen(buf)); + refreshLine(); } } /* Delete the character at the right of the cursor without altering the cursor * position. Basically this is what happens with the "Delete" keyboard key. */ -inline void linenoiseEditDelete(struct linenoiseState *l) { - if (l->len > 0 && l->pos < l->len) { - int glen = unicodeGraphemeLen(l->buf, l->len, l->pos); - memmove(l->buf + l->pos, l->buf + l->pos + glen, l->len - l->pos - glen); - l->len -= glen; - l->buf[l->len] = '\0'; - refreshLine(l); +void linenoiseState::linenoiseEditDelete() { + if (len > 0 && pos < len) { + int glen = unicodeGraphemeLen(buf, len, pos); + memmove(buf + pos, buf + pos + glen, len - pos - glen); + len -= glen; + buf[len] = '\0'; + RefreshLine(); } } /* Backspace implementation. */ -inline void linenoiseEditBackspace(struct linenoiseState *l) { - if (l->pos > 0 && l->len > 0) { - int glen = unicodePrevGraphemeLen(l->buf, l->pos); - memmove(l->buf + l->pos - glen, l->buf + l->pos, l->len - l->pos); - l->pos -= glen; - l->len -= glen; - l->buf[l->len] = '\0'; - refreshLine(l); +void linenoiseState::linenoiseEditBackspace() { + if (pos > 0 && len > 0) { + int glen = unicodePrevGraphemeLen(buf, pos); + memmove(buf + pos - glen, buf + pos, len - pos); + pos -= glen; + len -= glen; + buf[len] = '\0'; + RefreshLine(); } } /* Delete the previous word, maintaining the cursor at the start of the * current word. */ -inline void linenoiseEditDeletePrevWord(struct linenoiseState *l) { - int old_pos = l->pos; +void linenoiseState::linenoiseEditDeletePrevWord() { + int old_pos = pos; int diff; - while (l->pos > 0 && l->buf[l->pos - 1] == ' ') - l->pos--; - while (l->pos > 0 && l->buf[l->pos - 1] != ' ') - l->pos--; - diff = old_pos - l->pos; - memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1); - l->len -= diff; - refreshLine(l); + while (pos > 0 && buf[pos - 1] == ' ') + pos--; + while (pos > 0 && buf[pos - 1] != ' ') + pos--; + diff = old_pos - pos; + memmove(buf + pos, buf + old_pos, len - old_pos + 1); + len -= diff; + RefreshLine(); } /* This function is the core of the line editing capability of linenoise. @@ -2270,32 +2301,15 @@ inline void linenoiseEditDeletePrevWord(struct linenoiseState *l) { * when ctrl+d is typed. * * The function returns the length of the current buffer. */ -inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, - const char *prompt) { - struct linenoiseState l; - - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.oldcolpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; - - /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ +int linenoiseState::linenoiseEdit() +//, int stdin_fd, int stdout_fd, char *buf, int buflen, const char *prompt) +{ /* The latest history entry is always our current buffer, that * initially is just an empty string. */ AddHistory(""); - if (write(l.ofd, prompt, static_cast(l.prompt.length())) == -1) + if (write(ofd, prompt.c_str(), static_cast(prompt.length())) == -1) return -1; while (1) { int c; @@ -2309,19 +2323,19 @@ inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, cbuf[0] = c; } #else - nread = unicodeReadUTF8Char(l.ifd, cbuf, &c); + nread = unicodeReadUTF8Char(ifd, cbuf, &c); #endif if (nread <= 0) - return (int)l.len; + return (int)len; /* Only autocomplete when the callback is set. It returns < 0 when * there was an error reading from fd. Otherwise it will return the * character that should be handled next. */ if (c == 9 && completionCallback != NULL) { - nread = completeLine(&l, cbuf, &c); + nread = completeLine(cbuf, &c); /* Return on errors */ if (c < 0) - return l.len; + return len; /* Read next character when 0 */ if (c == 0) continue; @@ -2332,87 +2346,87 @@ inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, if (!history.empty()) history.pop_back(); if (mlmode) - linenoiseEditMoveEnd(&l); - return (int)l.len; + linenoiseEditMoveEnd(); + return (int)len; case CTRL_C: /* ctrl-c */ errno = EAGAIN; return -1; case BACKSPACE: /* backspace */ case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); + linenoiseEditBackspace(); break; case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); + if (len > 0) { + linenoiseEditDelete(); } else { history.pop_back(); return -1; } break; case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - char aux = buf[l.pos - 1]; - buf[l.pos - 1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len - 1) - l.pos++; - refreshLine(&l); + if (pos > 0 && pos < len) { + char aux = wbuf[pos - 1]; + wbuf[pos - 1] = wbuf[pos]; + wbuf[pos] = aux; + if (pos != len - 1) + pos++; + RefreshLine(); } break; case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); + linenoiseEditMoveLeft(); break; case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); + linenoiseEditMoveRight(); break; case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + linenoiseEditHistoryNext(LINENOISE_HISTORY_PREV); break; case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + linenoiseEditHistoryNext(LINENOISE_HISTORY_NEXT); break; case ESC: /* escape sequence */ /* Read the next two bytes representing the escape sequence. * Use two calls to handle slow terminals returning the two * chars at different times. */ - if (read(l.ifd, seq, 1) == -1) + if (read(ifd, seq, 1) == -1) break; - if (read(l.ifd, seq + 1, 1) == -1) + if (read(ifd, seq + 1, 1) == -1) break; /* ESC [ sequences. */ if (seq[0] == '[') { if (seq[1] >= '0' && seq[1] <= '9') { /* Extended escape, read additional byte. */ - if (read(l.ifd, seq + 2, 1) == -1) + if (read(ifd, seq + 2, 1) == -1) break; if (seq[2] == '~') { switch (seq[1]) { case '3': /* Delete key. */ - linenoiseEditDelete(&l); + linenoiseEditDelete(); break; } } } else { switch (seq[1]) { case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + linenoiseEditHistoryNext(LINENOISE_HISTORY_PREV); break; case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + linenoiseEditHistoryNext(LINENOISE_HISTORY_NEXT); break; case 'C': /* Right */ - linenoiseEditMoveRight(&l); + linenoiseEditMoveRight(); break; case 'D': /* Left */ - linenoiseEditMoveLeft(&l); + linenoiseEditMoveLeft(); break; case 'H': /* Home */ - linenoiseEditMoveHome(&l); + linenoiseEditMoveHome(); break; case 'F': /* End*/ - linenoiseEditMoveEnd(&l); + linenoiseEditMoveEnd(); break; } } @@ -2422,49 +2436,49 @@ inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, else if (seq[0] == 'O') { switch (seq[1]) { case 'H': /* Home */ - linenoiseEditMoveHome(&l); + linenoiseEditMoveHome(); break; case 'F': /* End*/ - linenoiseEditMoveEnd(&l); + linenoiseEditMoveEnd(); break; } } break; default: - if (linenoiseEditInsert(&l, cbuf, nread)) + if (linenoiseEditInsert(cbuf, nread)) return -1; break; case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); + wbuf[0] = '\0'; + pos = len = 0; + RefreshLine(); break; case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); + wbuf[pos] = '\0'; + len = pos; + RefreshLine(); break; case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); + linenoiseEditMoveHome(); break; case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); + linenoiseEditMoveEnd(); break; case CTRL_L: /* ctrl+l, clear screen */ linenoiseClearScreen(); - refreshLine(&l); + RefreshLine(); break; case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); + linenoiseEditDeletePrevWord(); break; } } - return l.len; + return len; } /* This function calls the line editing function linenoiseEdit() using * the STDIN file descriptor set in raw mode. */ -inline bool linenoiseRaw(const char *prompt, std::string &line) { +bool linenoiseState::linenoiseRaw(std::string &line) { bool quit = false; if (!isatty(STDIN_FILENO)) { @@ -2476,9 +2490,14 @@ inline bool linenoiseRaw(const char *prompt, std::string &line) { return quit; } - char buf[LINENOISE_MAX_LINE]; - auto count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, - LINENOISE_MAX_LINE, prompt); + /* Buffer starts empty. Since we're potentially + * reusing the state, we need to reset these. */ + pos = 0; + len = 0; + buf[0] = '\0'; + wbuf[0] = '\0'; + + auto count = linenoiseEdit(); if (count == -1) { quit = true; } else { @@ -2491,31 +2510,49 @@ inline bool linenoiseRaw(const char *prompt, std::string &line) { return quit; } +void linenoiseState::Initialize(int stdin_fd, int stdout_fd, int i_buflen, + const char *prompt_str) { + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + ifd = stdin_fd; + ofd = stdout_fd; + buf = wbuf; + buflen = i_buflen; + prompt = std::string(prompt_str); + cols = getColumns(stdin_fd, stdout_fd); + + /* Buffer starts empty. */ + buf[0] = '\0'; + buflen--; /* Make sure there is always space for the nulterm */ +} + +void linenoiseState::EnableMultiLine(bool ml) { SetMultiLine(ml); } + /* The high level function that is the main API of the linenoise library. * This function checks if the terminal has basic capabilities, just checking * for a blacklist of stupid terminals, and later either calls the line * editing function or uses dummy fgets() so that you will be able to type * something even in the most desperate of the conditions. */ -inline bool Readline(const char *prompt, std::string &line) { +bool linenoiseState::Readline(std::string &line) { if (isUnsupportedTerm()) { - printf("%s", prompt); + printf("%s", prompt.c_str()); fflush(stdout); std::getline(std::cin, line); return false; } else { - return linenoiseRaw(prompt, line); + return linenoiseRaw(line); } } -inline std::string Readline(const char *prompt, bool &quit) { +std::string linenoiseState::Readline(bool &quit) { std::string line; - quit = Readline(prompt, line); + quit = Readline(line); return line; } -inline std::string Readline(const char *prompt) { +std::string linenoiseState::Readline() { bool quit; // dummy - return Readline(prompt, quit); + return Readline(quit); } /* ================================ History ================================= */ @@ -2530,7 +2567,7 @@ inline void linenoiseAtExit(void) { disableRawMode(STDIN_FILENO); } * histories, but will work well for a few hundred of entries. * * Using a circular buffer is smarter, but a bit more complex to handle. */ -inline bool AddHistory(const char *line) { +bool linenoiseState::AddHistory(const char *line) { if (history_max_len == 0) return false; @@ -2549,26 +2586,14 @@ inline bool AddHistory(const char *line) { /* Set the maximum length for the history. This function can be called even * if there is already some history, the function will make sure to retain - * just the latest 'len' elements if the new history length value is smaller + * just the latest 'hlen' elements if the new history length value is smaller * than the amount of items already inside the history. */ -inline bool SetHistoryMaxLen(size_t len) { - if (len < 1) - return false; - history_max_len = len; - if (len < history.size()) { - history.resize(len); - } - return true; -} - -/* Save the history in the specified file. On success *true* is returned - * otherwise *false* is returned. */ -inline bool SaveHistory(const char *path) { - std::ofstream f(path); // TODO: need 'std::ios::binary'? - if (!f) +bool linenoiseState::SetHistoryMaxLen(size_t hlen) { + if (hlen < 1) return false; - for (const auto &h : history) { - f << h << std::endl; + history_max_len = hlen; + if (hlen < history.size()) { + history.resize(hlen); } return true; } @@ -2578,7 +2603,7 @@ inline bool SaveHistory(const char *path) { * * If the file exists and the operation succeeded *true* is returned, otherwise * on error *false* is returned. */ -inline bool LoadHistory(const char *path) { +bool linenoiseState::LoadHistory(const char *path) { std::ifstream f(path); if (!f) return false; @@ -2589,7 +2614,17 @@ inline bool LoadHistory(const char *path) { return true; } -inline const std::vector &GetHistory() { return history; } +/* Save the history in the specified file. On success *true* is returned + * otherwise *false* is returned. */ +bool linenoiseState::SaveHistory(const char *path) { + std::ofstream f(path); // TODO: need 'std::ios::binary'? + if (!f) + return false; + for (const auto &h : history) { + f << h << std::endl; + } + return true; +} } // namespace linenoise From fcbd5b401f6bc7c3ccbbade8cebbb4dc5ac1e5d2 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:35:38 -0400 Subject: [PATCH 04/15] Add methods for line wiping and screen clearing. In this context, line wiping refers to temporarily removing the contents of the current line from the screen, but not otherwise changing the line editing state. Its use case is temporarily removing the prompt to allow other output to be written, with an intent to immediately restore it once that output is complete. --- linenoise.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/linenoise.hpp b/linenoise.hpp index 564009c..316748c 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -184,6 +184,9 @@ class linenoiseState { bool Readline(std::string &line); // Primary linenoise entry point void RefreshLine(); // Restore current line (used after WipeLine) + void + WipeLine(); // Temporarily removes line from screen - RefreshLine will restore + void ClearScreen(); // Clear terminal window /* Register a callback function to be called for tab-completion. */ void SetCompletionCallback(CompletionCallback fn) { @@ -1948,6 +1951,15 @@ inline void linenoiseClearScreen(void) { } } +/* Temporarily clear the line from the screen, + * without resetting its state. Used for + * temporarily clearing prompt and input while + * printing other content. */ +inline void linenoiseWipeLine() { + (void)write(STDOUT_FILENO, "\33[2K", 4); // Clear line + (void)write(STDOUT_FILENO, "\r", 1); // Move cursor to the left +} + /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ inline void linenoiseBeep(void) { @@ -2162,6 +2174,10 @@ void linenoiseState::RefreshLine() { refreshSingleLine(); } +void linenoiseState::WipeLine() { linenoiseWipeLine(); } + +void linenoiseState::ClearScreen() { linenoiseClearScreen(); } + /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ From 0740e8aba0e02daac455a6437dc078c108251524 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:38:31 -0400 Subject: [PATCH 05/15] Add a mutex to protect RefreshLine if multiple threads need to call it. --- linenoise.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/linenoise.hpp b/linenoise.hpp index 316748c..f8e61d2 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -153,6 +153,7 @@ #include #include #include +#include #include #include #include @@ -194,6 +195,7 @@ class linenoiseState { }; std::string prompt = std::string("> "); /* Prompt to display. */ + std::mutex r_mutex; private: std::string Readline(bool &quit); @@ -2166,12 +2168,17 @@ void linenoiseState::refreshMultiLine() { /* Calls the two low level functions refreshSingleLine() or * refreshMultiLine() according to the selected mode. * + * Note that we use this when recovering the line after writing + * console output from another thread, (i.e. independent of a + * Readline call) so we use a mutex for basic thread safety. * */ void linenoiseState::RefreshLine() { + r_mutex.lock(); if (mlmode) refreshMultiLine(); else refreshSingleLine(); + r_mutex.unlock(); } void linenoiseState::WipeLine() { linenoiseWipeLine(); } From a41f34231d3e8096832070a9b6acb14d1bf712db Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:40:01 -0400 Subject: [PATCH 06/15] Adjust how history and its up/down arrow browsing are handled. --- linenoise.hpp | 53 +++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index f8e61d2..be953ec 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -2256,24 +2256,33 @@ void linenoiseState::linenoiseEditMoveEnd() { #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 void linenoiseState::linenoiseEditHistoryNext(int dir) { - if (history.size() > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - history[history.size() - 1 - history_index] = buf; - /* Show the new entry */ - history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (history_index < 0) { - history_index = 0; - return; - } else if (history_index >= (int)history.size()) { - history_index = static_cast(history.size()) - 1; - return; - } - memset(buf, 0, l->buflen); - strcpy(buf, history[history.size() - 1 - history_index].c_str()); - len = pos = static_cast(strlen(buf)); - refreshLine(); + int history_size = static_cast(history.size()); + if (!history_size) + return; + /* Show the new entry */ + if (history_index == -1) { + history_tmpbuf = std::string(buf); + history_index = history.size() - 1; + } else { + if (history_index == history_size) + history_tmpbuf = std::string(buf); + history_index += (dir == LINENOISE_HISTORY_PREV) ? -1 : 1; + } + if (history_index < 0) + history_index = history_size; + if (history_index == history_size) { + memset(buf, 0, buflen); + strcpy(buf, history_tmpbuf.c_str()); + len = pos = static_cast(history_tmpbuf.size()); + RefreshLine(); + return; } + if (history_index > history_size) + history_index = 0; + memset(buf, 0, buflen); + strcpy(buf, history[history_index].c_str()); + len = pos = static_cast(strlen(buf)); + RefreshLine(); } /* Delete the character at the right of the cursor without altering the cursor @@ -2327,11 +2336,6 @@ void linenoiseState::linenoiseEditDeletePrevWord() { int linenoiseState::linenoiseEdit() //, int stdin_fd, int stdout_fd, char *buf, int buflen, const char *prompt) { - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - AddHistory(""); - if (write(ofd, prompt.c_str(), static_cast(prompt.length())) == -1) return -1; while (1) { @@ -2366,7 +2370,8 @@ int linenoiseState::linenoiseEdit() switch (c) { case ENTER: /* enter */ - if (!history.empty()) + history_index = -1; + if (history.size() == history_max_len) history.pop_back(); if (mlmode) linenoiseEditMoveEnd(); @@ -2593,6 +2598,8 @@ inline void linenoiseAtExit(void) { disableRawMode(STDIN_FILENO); } bool linenoiseState::AddHistory(const char *line) { if (history_max_len == 0) return false; + if (!line || !strlen(line)) + return false; /* Don't add duplicated lines. */ if (!history.empty() && history.back() == line) From ecb025d91ad66c28049e034e0f295f4c4702777a Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:16:49 -0400 Subject: [PATCH 07/15] Rework Initialize into constructor --- linenoise.hpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index be953ec..8407dc5 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -173,7 +173,7 @@ typedef std::function &)> * provides methods by which user programs can act on that state. */ class linenoiseState { public: - void Initialize(int stdin_fd, int stdout_fd, int buflen, const char *prompt); + linenoiseState(const char *prompt = NULL, int stdin_fd = STDIN_FILENO, int stdout_fd = STDOUT_FILENO); void EnableMultiLine(bool); @@ -2090,12 +2090,10 @@ void linenoiseState::refreshSingleLine() { * cursor position, and number of columns of the terminal. */ void linenoiseState::refreshMultiLine() { char seq[64]; - int pcolwid = - unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); + int pcolwid = unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); int colpos = unicodeColumnPosForMultiLine(buf, len, len, cols, pcolwid); int colpos2; /* cursor column position. */ - int rows = - (pcolwid + colpos + cols - 1) / cols; /* rows used by current buf. */ + int rows = (pcolwid + colpos + cols - 1) / cols; /* rows used by current buf. */ int rpos = (pcolwid + oldcolpos + cols) / cols; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ int col; /* column position, zero-based. */ @@ -2538,16 +2536,14 @@ bool linenoiseState::linenoiseRaw(std::string &line) { return quit; } -void linenoiseState::Initialize(int stdin_fd, int stdout_fd, int i_buflen, - const char *prompt_str) { +linenoiseState::linenoiseState(const char *prompt_str, int stdin_fd, int stdout_fd) { /* Populate the linenoise state that we pass to functions implementing * specific editing functionalities. */ ifd = stdin_fd; ofd = stdout_fd; buf = wbuf; - buflen = i_buflen; - prompt = std::string(prompt_str); - cols = getColumns(stdin_fd, stdout_fd); + prompt = (prompt_str) ? std::string(prompt_str) : std::string("> "); + cols = getColumns(ifd, ofd); /* Buffer starts empty. */ buf[0] = '\0'; From 5515ddcd70619b00566f46277de5ba1ca1d6da32 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:18:21 -0400 Subject: [PATCH 08/15] Update example --- example/example.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/example/example.cpp b/example/example.cpp index 7be6f71..2ee2c5d 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -7,14 +7,21 @@ int main(int argc, const char** argv) { const auto path = "history.txt"; +#ifdef _WIN32 + const char *prompt = "hello> "; +#else + const char *prompt = "\033[32mこんにちは\x1b[0m> "; +#endif + linenoise::linenoiseState l(prompt); + // Enable the multi-line mode - linenoise::SetMultiLine(true); + l.EnableMultiLine(true); // Set max length of the history - linenoise::SetHistoryMaxLen(4); + l.SetHistoryMaxLen(4); // Setup completion words every time when a user types - linenoise::SetCompletionCallback([](const char* editBuffer, std::vector& completions) { + l.SetCompletionCallback([](const char* editBuffer, std::vector& completions) { if (editBuffer[0] == 'h') { #ifdef _WIN32 completions.push_back("hello こんにちは"); @@ -27,15 +34,11 @@ int main(int argc, const char** argv) }); // Load history - linenoise::LoadHistory(path); + l.LoadHistory(path); while (true) { std::string line; -#ifdef _WIN32 - auto quit = linenoise::Readline("hello> ", line); -#else - auto quit = linenoise::Readline("\033[32mこんにちは\x1b[0m> ", line); -#endif + auto quit = l.Readline(line); if (quit) { break; @@ -44,10 +47,10 @@ int main(int argc, const char** argv) cout << "echo: '" << line << "'" << endl; // Add line to history - linenoise::AddHistory(line.c_str()); + l.AddHistory(line.c_str()); // Save history - linenoise::SaveHistory(path); + l.SaveHistory(path); } return 0; From 43f0ab68d42ccfe8d55aabe90103aac19f4bbc14 Mon Sep 17 00:00:00 2001 From: Fatima Waheed Date: Fri, 21 Jan 2022 15:47:36 +0100 Subject: [PATCH 09/15] Following fixes are made: - support unicode for windows - fixed line wrapping - Improved drawing by removing curso --- linenoise.hpp | 176 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 136 insertions(+), 40 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index 42db2e9..2e78734 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -320,13 +320,67 @@ inline void FlushBuffer(void) // Adds a character in the buffer. //----------------------------------------------------------------------------- -inline void PushBuffer(WCHAR c) +inline DWORD PushBuffer(LPCSTR buf, DWORD size) { - if (shifted && c >= FIRST_G1 && c <= LAST_G1) - c = G1[c - FIRST_G1]; - ChBuffer[nCharInBuffer] = c; - if (++nCharInBuffer == BUFFER_SIZE) - FlushBuffer(); + if (size < 1) + { + return 0; + } + + WCHAR wideChars[2]; + int wideCharCount; + if (shifted && *buf >= FIRST_G1 && *buf <= LAST_G1) + { + wideChars[0] = G1[*buf - FIRST_G1]; + wideCharCount = 1; + } + else + { + // Find the complete UTF-8 character + unsigned char byte = buf[0]; + DWORD utf8Size; + + if ((byte & 0x80) == 0) + { + utf8Size = 1; + } + else if ((byte & 0xE0) == 0xC0) + { + utf8Size = 2; + } + else if ((byte & 0xF0) == 0xE0) + { + utf8Size = 3; + } + else if ((byte & 0xF8) == 0xF0) + { + utf8Size = 4; + } + else + { + return 0; + } + + if (size < utf8Size) + { + return 0; + } + + wideCharCount = MultiByteToWideChar(CP_UTF8, 0, buf, utf8Size, wideChars, 2); + if (wideCharCount == 0) + { + return 0; + } + } + + for (int i = 0; i < wideCharCount; ++i) + { + ChBuffer[nCharInBuffer] = wideChars[i]; + if (++nCharInBuffer == BUFFER_SIZE) + FlushBuffer(); + } + + return wideCharCount; } //----------------------------------------------------------------------------- @@ -369,12 +423,11 @@ inline void SendSequence(LPCWSTR seq) // suffix = 'm' //----------------------------------------------------------------------------- -inline void InterpretEscSeq(void) +inline void InterpretEscSeq(PCONSOLE_CURSOR_INFO lpConsoleCursorInfo) { int i; WORD attribute; CONSOLE_SCREEN_BUFFER_INFO Info; - CONSOLE_CURSOR_INFO CursInfo; DWORD len, NumberOfCharsWritten; COORD Pos; SMALL_RECT Rect; @@ -386,9 +439,7 @@ inline void InterpretEscSeq(void) { if (es_argc == 1 && es_argv[0] == 25) { - GetConsoleCursorInfo(hConOut, &CursInfo); - CursInfo.bVisible = (suffix == 'h'); - SetConsoleCursorInfo(hConOut, &CursInfo); + lpConsoleCursorInfo->bVisible = (suffix == 'h'); return; } } @@ -826,6 +877,13 @@ inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumber state = 1; shifted = FALSE; } + + CONSOLE_CURSOR_INFO oldConsoleCursorInfo; + GetConsoleCursorInfo(hConOut, & oldConsoleCursorInfo); + + CONSOLE_CURSOR_INFO invisibleCursor { .dwSize = oldConsoleCursorInfo.dwSize, .bVisible = 0 }; + SetConsoleCursorInfo(hConOut, &invisibleCursor); + for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) { if (state == 1) @@ -833,7 +891,16 @@ inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumber if (*s == ESC) state = 2; else if (*s == SO) shifted = TRUE; else if (*s == SI) shifted = FALSE; - else PushBuffer(*s); + else + { + DWORD written = PushBuffer(s, nNumberOfBytesToWrite); + if (written > 0) + { + DWORD extraWritten = written - 1; + i -= extraWritten; + s += extraWritten; + } + } } else if (state == 2) { @@ -873,7 +940,7 @@ inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumber { es_argc = 0; suffix = *s; - InterpretEscSeq(); + InterpretEscSeq(&oldConsoleCursorInfo); state = 1; } } @@ -894,7 +961,7 @@ inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumber { es_argc++; suffix = *s; - InterpretEscSeq(); + InterpretEscSeq(&oldConsoleCursorInfo); state = 1; } } @@ -903,13 +970,13 @@ inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumber if (*s == BEL) { Pt_arg[Pt_len] = '\0'; - InterpretEscSeq(); + InterpretEscSeq(&oldConsoleCursorInfo); state = 1; } else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) { Pt_arg[--Pt_len] = '\0'; - InterpretEscSeq(); + InterpretEscSeq(&oldConsoleCursorInfo); state = 1; } else if (Pt_len < lenof(Pt_arg) - 1) @@ -922,6 +989,9 @@ inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumber } } FlushBuffer(); + + SetConsoleCursorInfo(hConOut, &oldConsoleCursorInfo); + if (lpNumberOfBytesWritten != NULL) *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; return (i == 0); @@ -933,20 +1003,20 @@ HANDLE hOut; HANDLE hIn; DWORD consolemodeIn = 0; -inline int win32read(int *c) { +inline int win32readW(WCHAR *c) { DWORD foo; INPUT_RECORD b; KEY_EVENT_RECORD e; BOOL altgr; while (1) { - if (!ReadConsoleInput(hIn, &b, 1, &foo)) return 0; + if (!ReadConsoleInputW(hIn, &b, 1, &foo)) return 0; if (!foo) return 0; if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { e = b.Event.KeyEvent; - *c = b.Event.KeyEvent.uChar.AsciiChar; + *c = b.Event.KeyEvent.uChar.UnicodeChar; altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); @@ -954,40 +1024,40 @@ inline int win32read(int *c) { /* Ctrl+Key */ switch (*c) { - case 'D': + case L'D': *c = 4; return 1; - case 'C': + case L'C': *c = 3; return 1; - case 'H': + case L'H': *c = 8; return 1; - case 'T': + case L'T': *c = 20; return 1; - case 'B': /* ctrl-b, left_arrow */ + case L'B': /* ctrl-b, left_arrow */ *c = 2; return 1; - case 'F': /* ctrl-f right_arrow*/ + case L'F': /* ctrl-f right_arrow*/ *c = 6; return 1; - case 'P': /* ctrl-p up_arrow*/ + case L'P': /* ctrl-p up_arrow*/ *c = 16; return 1; - case 'N': /* ctrl-n down_arrow*/ + case L'N': /* ctrl-n down_arrow*/ *c = 14; return 1; - case 'U': /* Ctrl+u, delete the whole line. */ + case L'U': /* Ctrl+u, delete the whole line. */ *c = 21; return 1; - case 'K': /* Ctrl+k, delete from current to end of line. */ + case L'K': /* Ctrl+k, delete from current to end of line. */ *c = 11; return 1; - case 'A': /* Ctrl+a, go to the start of the line */ + case L'A': /* Ctrl+a, go to the start of the line */ *c = 1; return 1; - case 'E': /* ctrl+e, go to the end of the line */ + case L'E': /* ctrl+e, go to the end of the line */ *c = 5; return 1; } @@ -1037,6 +1107,38 @@ inline int win32read(int *c) { return -1; /* Makes compiler happy */ } +inline int win32read(char *buf, int *c) +{ + WCHAR wideChars[2]; + int wideCharCount; + if (win32readW(wideChars) != 1) + { + return 0; + } + + // check for high surrogate + if (!IS_HIGH_SURROGATE(wideChars[0])) + { + *c = wideChars[0]; + wideCharCount = 1; + } + else + { + if (win32readW(wideChars + 1) != 1) + { + return 0; + } + + // combine the surrogates + *c = 0x10000 + (((wideChars[0] & 0x3ff) << 10) | (wideChars[1] & 0x3ff)); + wideCharCount = 2; + } + + auto count = WideCharToMultiByte(CP_UTF8, 0, wideChars, wideCharCount, buf, 4, nullptr, nullptr); + + return count; +} + inline int win32_write(int fd, const void *buffer, unsigned int count) { if (fd == _fileno(stdout)) { DWORD bytesWritten = 0; @@ -1706,7 +1808,7 @@ inline int getColumns(int ifd, int ofd) { CONSOLE_SCREEN_BUFFER_INFO b; if (!GetConsoleScreenBufferInfo(hOut, &b)) return 80; - return b.srWindow.Right - b.srWindow.Left; + return (b.srWindow.Right - b.srWindow.Left) + 1; #else struct winsize ws; @@ -1791,10 +1893,7 @@ inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { //nread = read(ls->ifd,&c,1); #ifdef _WIN32 - nread = win32read(c); - if (nread == 1) { - cbuf[0] = *c; - } + nread = win32read(cbuf, c); #else nread = unicodeReadUTF8Char(ls->ifd,cbuf,c); #endif @@ -2128,10 +2227,7 @@ inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, con char seq[3]; #ifdef _WIN32 - nread = win32read(&c); - if (nread == 1) { - cbuf[0] = c; - } + nread = win32read(cbuf, &c); #else nread = unicodeReadUTF8Char(l.ifd,cbuf,&c); #endif From 593e38d713be23ba7800a5dde9fd10b1acb31947 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:59:40 -0400 Subject: [PATCH 10/15] Update README for fork's API --- README.md | 46 +++++++++++----------------------------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 872be3b..2647cf4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ cpp-linenoise Multi-platform (Unix, Windows) C++ header-only linenoise-based readline library. -This library gathered code from following excellent libraries, clean it up, and put it into a C++ header file for convenience. +This version of cpp-linenoise is derived from https://github.com/yhirose/cpp-linenoise, +which is in turn assembled from the following libraries: * `linenoise.h` and `linenoise.c` ([antirez/linenoise](https://github.com/antirez/linenoise)) * `ANSI.c` ([adoxa/ansicon](https://github.com/adoxa/ansicon)) @@ -21,6 +22,8 @@ Usage const auto path = "history.txt"; +linenoise::linenoiseState l("hello> "); + // Setup completion words every time when a user types linenoise::SetCompletionCallback([](const char* editBuffer, std::vector& completions) { if (editBuffer[0] == 'h') { @@ -30,18 +33,18 @@ linenoise::SetCompletionCallback([](const char* editBuffer, std::vector ", line); + auto quit = l.Readline(line); if (quit) { break; @@ -50,44 +53,17 @@ while (true) { cout << "echo: '" << line << "'" << endl; // Add text to history - linenoise::AddHistory(line.c_str()); + lAddHistory(line.c_str()); } // Save history -linenoise::SaveHistory(path); +l.SaveHistory(path); ``` API --- -```c++ -namespace linenoise; - -std::string Readline(const char* prompt); - -void SetMultiLine(bool multiLineMode); - -typedef std::function& completions)> CompletionCallback; - -void SetCompletionCallback(CompletionCallback fn); - -bool SetHistoryMaxLen(size_t len); - -bool LoadHistory(const char* path); - -bool SaveHistory(const char* path); - -bool AddHistory(const char* line); - -const std::vector& GetHistory(); -``` - -Tested compilers ----------------- - - * Visual Studio 2015 - * Clang 3.5 - * GCC 6.3.1 +The public methods on the linenoiseState class are considered the public API. License ------- From 3cb02ca8b32cffd43ae28d75ab9438643fa952aa Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 20:00:27 -0400 Subject: [PATCH 11/15] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2647cf4..992e469 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ while (true) { cout << "echo: '" << line << "'" << endl; // Add text to history - lAddHistory(line.c_str()); + l.AddHistory(line.c_str()); } // Save history From 9743ae108815370260f8e76ae9902fac3da9253e Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 20:48:12 -0400 Subject: [PATCH 12/15] We don't want to hang if we can't read anything from stdin --- linenoise.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/linenoise.hpp b/linenoise.hpp index 72b8b8a..cb5893a 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -2582,8 +2582,15 @@ bool linenoiseState::linenoiseRaw(std::string &line) { bool quit = false; if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. */ - std::getline(std::cin, line); + int c; + while ((c = getc(stdin)) != EOF) + line += c; + + if (!line.length()) + quit = true; + } else { /* Interactive editing. */ if (enableRawMode(STDIN_FILENO) == false) { From 845e3006db77386bd0f15d0a394074f49ba48003 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:03:35 -0400 Subject: [PATCH 13/15] missed a conversion --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 992e469..8c8e01b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ const auto path = "history.txt"; linenoise::linenoiseState l("hello> "); // Setup completion words every time when a user types -linenoise::SetCompletionCallback([](const char* editBuffer, std::vector& completions) { +l.SetCompletionCallback([](const char* editBuffer, std::vector& completions) { if (editBuffer[0] == 'h') { completions.push_back("hello"); completions.push_back("hello there"); From aac8af6f3d9d74bab4c45eb16076406361421135 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:10:25 -0400 Subject: [PATCH 14/15] Make enable/disable pair, rather than bool arg --- README.md | 2 +- linenoise.hpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c8e01b..00e2a03 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ l.SetCompletionCallback([](const char* editBuffer, std::vector& com }); // Enable the multi-line mode -l.EnableMultiLine(true); +l.EnableMultiLine(); // Set max length of the history l.SetHistoryMaxLen(4); diff --git a/linenoise.hpp b/linenoise.hpp index cb5893a..d61b9f6 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -175,7 +175,8 @@ class linenoiseState { public: linenoiseState(const char *prompt = NULL, int stdin_fd = STDIN_FILENO, int stdout_fd = STDOUT_FILENO); - void EnableMultiLine(bool); + void EnableMultiLine(); + void DisableMultiLine(); bool AddHistory(const char *line); bool LoadHistory(const char *path); @@ -2631,7 +2632,8 @@ linenoiseState::linenoiseState(const char *prompt_str, int stdin_fd, int stdout_ buflen--; /* Make sure there is always space for the nulterm */ } -void linenoiseState::EnableMultiLine(bool ml) { SetMultiLine(ml); } +void linenoiseState::EnableMultiLine() { SetMultiLine(true); } +void linenoiseState::DisableMultiLine() { SetMultiLine(false); } /* The high level function that is the main API of the linenoise library. * This function checks if the terminal has basic capabilities, just checking From 03b1cf8b0a88afa6803019064c15c0401fc864a2 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:42:12 -0500 Subject: [PATCH 15/15] Match where BRL-CAD copy wound up --- linenoise.hpp | 3568 +++++++++++++++++++++++++------------------------ 1 file changed, 1804 insertions(+), 1764 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index d61b9f6..20b9d6a 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -148,7 +148,8 @@ #pragma warning(push) #pragma warning(disable : 4996) #endif -#include +#include +#include #include #include #include @@ -172,68 +173,69 @@ typedef std::function &)> /* The linenoiseState structure represents the state during line editing, and * provides methods by which user programs can act on that state. */ class linenoiseState { -public: - linenoiseState(const char *prompt = NULL, int stdin_fd = STDIN_FILENO, int stdout_fd = STDOUT_FILENO); - - void EnableMultiLine(); - void DisableMultiLine(); - - bool AddHistory(const char *line); - bool LoadHistory(const char *path); - bool SaveHistory(const char *path); - const std::vector &GetHistory() { return history; }; - bool SetHistoryMaxLen(size_t len); - - bool Readline(std::string &line); // Primary linenoise entry point - void RefreshLine(); // Restore current line (used after WipeLine) - void - WipeLine(); // Temporarily removes line from screen - RefreshLine will restore - void ClearScreen(); // Clear terminal window - - /* Register a callback function to be called for tab-completion. */ - void SetCompletionCallback(CompletionCallback fn) { - completionCallback = fn; - }; - - std::string prompt = std::string("> "); /* Prompt to display. */ - std::mutex r_mutex; - -private: - std::string Readline(bool &quit); - std::string Readline(); - - bool linenoiseRaw(std::string &line); - int linenoiseEditInsert(const char *cbuf, int clen); - void linenoiseEditDelete(); - void linenoiseEditBackspace(); - void linenoiseEditDeletePrevWord(); - int linenoiseEdit(); - void linenoiseEditHistoryNext(int dir); - void linenoiseEditMoveLeft(); - void linenoiseEditMoveRight(); - void linenoiseEditMoveHome(); - void linenoiseEditMoveEnd(); - void refreshSingleLine(); - void refreshMultiLine(); - int completeLine(char *cbuf, int *c); - - CompletionCallback completionCallback; - - int ifd = STDIN_FILENO; /* Terminal stdin file descriptor. */ - int ofd = STDOUT_FILENO; /* Terminal stdout file descriptor. */ - char *buf = wbuf; /* Edited line buffer. */ - int buflen = LINENOISE_MAX_LINE; /* Edited line buffer size. */ - int pos = 0; /* Current cursor position. */ - int oldcolpos = 0; /* Previous refresh cursor column position. */ - int len = 0; /* Current edited line length. */ - int cols = 0; /* Number of columns in terminal. */ - int maxrows = 0; /* Maximum num of rows used so far (multiline mode) */ - int history_index = -1; /* The history index we are currently editing. */ - char wbuf[LINENOISE_MAX_LINE] = {'\0'}; - std::string history_tmpbuf; - - size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; - std::vector history; + public: + linenoiseState(const char *prompt = NULL, int stdin_fd = STDIN_FILENO, + int stdout_fd = STDOUT_FILENO); + + void EnableMultiLine(); + void DisableMultiLine(); + + bool AddHistory(const char *line); + bool LoadHistory(const char *path); + bool SaveHistory(const char *path); + const std::vector &GetHistory() { return history; }; + bool SetHistoryMaxLen(size_t len); + + bool Readline(std::string &line); // Primary linenoise entry point + void RefreshLine(); // Restore current line (used after WipeLine) + void + WipeLine(); // Temporarily removes line from screen - RefreshLine will restore + void ClearScreen(); // Clear terminal window + + /* Register a callback function to be called for tab-completion. */ + void SetCompletionCallback(CompletionCallback fn) { + completionCallback = fn; + }; + + std::string prompt = std::string("> "); /* Prompt to display. */ + std::mutex r_mutex; + + private: + std::string Readline(bool &quit); + std::string Readline(); + + bool linenoiseRaw(std::string &line); + int linenoiseEditInsert(const char *cbuf, int clen); + void linenoiseEditDelete(); + void linenoiseEditBackspace(); + void linenoiseEditDeletePrevWord(); + int linenoiseEdit(); + void linenoiseEditHistoryNext(int dir); + void linenoiseEditMoveLeft(); + void linenoiseEditMoveRight(); + void linenoiseEditMoveHome(); + void linenoiseEditMoveEnd(); + void refreshSingleLine(); + void refreshMultiLine(); + int completeLine(char *cbuf, int *c); + + CompletionCallback completionCallback; + + int ifd = STDIN_FILENO; /* Terminal stdin file descriptor. */ + int ofd = STDOUT_FILENO; /* Terminal stdout file descriptor. */ + char *buf = wbuf; /* Edited line buffer. */ + int buflen = LINENOISE_MAX_LINE; /* Edited line buffer size. */ + int pos = 0; /* Current cursor position. */ + int oldcolpos = 0; /* Previous refresh cursor column position. */ + int len = 0; /* Current edited line length. */ + int lcols = -1; /* Number of columns in terminal. */ + int maxrows = 0; /* Maximum num of rows used so far (multiline mode) */ + int history_index = -1; /* The history index we are currently editing. */ + char wbuf[LINENOISE_MAX_LINE] = {'\0'}; + std::string history_tmpbuf; + + size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; + std::vector history; }; #ifdef _WIN32 @@ -243,13 +245,13 @@ namespace ansi { #define lenof(array) (sizeof(array) / sizeof(*(array))) typedef struct { - BYTE foreground; // ANSI base color (0 to 7; add 30) - BYTE background; // ANSI base color (0 to 7; add 40) - BYTE bold; // console FOREGROUND_INTENSITY bit - BYTE underline; // console BACKGROUND_INTENSITY bit - BYTE rvideo; // swap foreground/bold & background/underline - BYTE concealed; // set foreground/bold to background/underline - BYTE reverse; // swap console foreground & background attributes + BYTE foreground; // ANSI base color (0 to 7; add 30) + BYTE background; // ANSI base color (0 to 7; add 40) + BYTE bold; // console FOREGROUND_INTENSITY bit + BYTE underline; // console BACKGROUND_INTENSITY bit + BYTE rvideo; // swap foreground/bold & background/underline + BYTE concealed; // set foreground/bold to background/underline + BYTE reverse; // swap console foreground & background attributes } GRM, *PGRM; // Graphic Rendition Mode inline bool is_digit(char c) { return '0' <= c && c <= '9'; } @@ -347,15 +349,15 @@ const BYTE backgroundcolor[8] = { }; const BYTE attr2ansi[8] = // map console attribute to ANSI number - { - 0, // black - 4, // blue - 2, // green - 6, // cyan - 1, // red - 5, // magenta - 3, // yellow - 7 // white +{ + 0, // black + 4, // blue + 2, // green + 6, // cyan + 1, // red + 5, // magenta + 3, // yellow + 7 // white }; GRM grm; @@ -376,11 +378,11 @@ WCHAR ChBuffer[BUFFER_SIZE]; //----------------------------------------------------------------------------- inline void FlushBuffer(void) { - DWORD nWritten; - if (nCharInBuffer <= 0) - return; - WriteConsoleW(hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL); - nCharInBuffer = 0; + DWORD nWritten; + if (nCharInBuffer <= 0) + return; + WriteConsoleW(hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL); + nCharInBuffer = 0; } //----------------------------------------------------------------------------- @@ -389,50 +391,50 @@ inline void FlushBuffer(void) { //----------------------------------------------------------------------------- inline DWORD PushBuffer(LPCSTR buf, DWORD size) { - if (size < 1) { - return 0; - } - - WCHAR wideChars[2]; - int wideCharCount; - if (shifted && *buf >= FIRST_G1 && *buf <= LAST_G1) { - wideChars[0] = G1[*buf - FIRST_G1]; - wideCharCount = 1; - } else { - // Find the complete UTF-8 character - unsigned char byte = buf[0]; - DWORD utf8Size; - - if ((byte & 0x80) == 0) { - utf8Size = 1; - } else if ((byte & 0xE0) == 0xC0) { - utf8Size = 2; - } else if ((byte & 0xF0) == 0xE0) { - utf8Size = 3; - } else if ((byte & 0xF8) == 0xF0) { - utf8Size = 4; - } else { - return 0; + if (size < 1) { + return 0; } - if (size < utf8Size) { - return 0; + WCHAR wideChars[2]; + int wideCharCount; + if (shifted && *buf >= FIRST_G1 && *buf <= LAST_G1) { + wideChars[0] = G1[*buf - FIRST_G1]; + wideCharCount = 1; + } else { + // Find the complete UTF-8 character + unsigned char byte = buf[0]; + DWORD utf8Size; + + if ((byte & 0x80) == 0) { + utf8Size = 1; + } else if ((byte & 0xE0) == 0xC0) { + utf8Size = 2; + } else if ((byte & 0xF0) == 0xE0) { + utf8Size = 3; + } else if ((byte & 0xF8) == 0xF0) { + utf8Size = 4; + } else { + return 0; + } + + if (size < utf8Size) { + return 0; + } + + wideCharCount = + MultiByteToWideChar(CP_UTF8, 0, buf, utf8Size, wideChars, 2); + if (wideCharCount == 0) { + return 0; + } } - wideCharCount = - MultiByteToWideChar(CP_UTF8, 0, buf, utf8Size, wideChars, 2); - if (wideCharCount == 0) { - return 0; + for (int i = 0; i < wideCharCount; ++i) { + ChBuffer[nCharInBuffer] = wideChars[i]; + if (++nCharInBuffer == BUFFER_SIZE) + FlushBuffer(); } - } - for (int i = 0; i < wideCharCount; ++i) { - ChBuffer[nCharInBuffer] = wideChars[i]; - if (++nCharInBuffer == BUFFER_SIZE) - FlushBuffer(); - } - - return wideCharCount; + return wideCharCount; } //----------------------------------------------------------------------------- @@ -441,20 +443,20 @@ inline DWORD PushBuffer(LPCSTR buf, DWORD size) { //----------------------------------------------------------------------------- inline void SendSequence(LPCWSTR seq) { - DWORD out; - INPUT_RECORD in; - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - - in.EventType = KEY_EVENT; - in.Event.KeyEvent.bKeyDown = TRUE; - in.Event.KeyEvent.wRepeatCount = 1; - in.Event.KeyEvent.wVirtualKeyCode = 0; - in.Event.KeyEvent.wVirtualScanCode = 0; - in.Event.KeyEvent.dwControlKeyState = 0; - for (; *seq; ++seq) { - in.Event.KeyEvent.uChar.UnicodeChar = *seq; - WriteConsoleInput(hStdIn, &in, 1, &out); - } + DWORD out; + INPUT_RECORD in; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + + in.EventType = KEY_EVENT; + in.Event.KeyEvent.bKeyDown = TRUE; + in.Event.KeyEvent.wRepeatCount = 1; + in.Event.KeyEvent.wVirtualKeyCode = 0; + in.Event.KeyEvent.wVirtualScanCode = 0; + in.Event.KeyEvent.dwControlKeyState = 0; + for (; *seq; ++seq) { + in.Event.KeyEvent.uChar.UnicodeChar = *seq; + WriteConsoleInput(hStdIn, &in, 1, &out); + } } // ========== Print functions @@ -474,473 +476,473 @@ inline void SendSequence(LPCWSTR seq) { //----------------------------------------------------------------------------- inline void InterpretEscSeq(PCONSOLE_CURSOR_INFO lpConsoleCursorInfo) { - WORD attribute; - CONSOLE_SCREEN_BUFFER_INFO Info; - DWORD len, NumberOfCharsWritten; - COORD Pos; - SMALL_RECT Rect; - CHAR_INFO CharInfo; - - if (prefix == '[') { - if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) { - if (es_argc == 1 && es_argv[0] == 25) { - lpConsoleCursorInfo->bVisible = (suffix == 'h'); - return; - } - } - // Ignore any other \e[? or \e[> sequences. - if (prefix2 != 0) - return; - - GetConsoleScreenBufferInfo(hConOut, &Info); - switch (suffix) { - case 'm': - if (es_argc == 0) - es_argv[es_argc++] = 0; - for (int i = 0; i < es_argc; i++) { - if (30 <= es_argv[i] && es_argv[i] <= 37) - grm.foreground = es_argv[i] - 30; - else if (40 <= es_argv[i] && es_argv[i] <= 47) - grm.background = es_argv[i] - 40; - else - switch (es_argv[i]) { - case 0: - case 39: - case 49: { - WCHAR def[4]; - int a; - *def = '7'; - def[1] = '\0'; - GetEnvironmentVariableW(L"ANSICON_DEF", def, lenof(def)); - a = wcstol(def, NULL, 16); - grm.reverse = FALSE; - if (a < 0) { - grm.reverse = TRUE; - a = -a; - } - if (es_argv[i] != 49) - grm.foreground = attr2ansi[a & 7]; - if (es_argv[i] != 39) - grm.background = attr2ansi[(a >> 4) & 7]; - if (es_argv[i] == 0) { - if (es_argc == 1) { - grm.bold = a & FOREGROUND_INTENSITY; - grm.underline = a & BACKGROUND_INTENSITY; - } else { - grm.bold = 0; - grm.underline = 0; - } - grm.rvideo = 0; - grm.concealed = 0; - } - } break; - - case 1: - grm.bold = FOREGROUND_INTENSITY; - break; - case 5: // blink - case 4: - grm.underline = BACKGROUND_INTENSITY; - break; - case 7: - grm.rvideo = 1; - break; - case 8: - grm.concealed = 1; - break; - case 21: // oops, this actually turns on double underline - case 22: - grm.bold = 0; - break; - case 25: - case 24: - grm.underline = 0; - break; - case 27: - grm.rvideo = 0; - break; - case 28: - grm.concealed = 0; - break; - } - } - if (grm.concealed) { - if (grm.rvideo) { - attribute = - foregroundcolor[grm.foreground] | backgroundcolor[grm.foreground]; - if (grm.bold) - attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } else { - attribute = - foregroundcolor[grm.background] | backgroundcolor[grm.background]; - if (grm.underline) - attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - } else if (grm.rvideo) { - attribute = - foregroundcolor[grm.background] | backgroundcolor[grm.foreground]; - if (grm.bold) - attribute |= BACKGROUND_INTENSITY; - if (grm.underline) - attribute |= FOREGROUND_INTENSITY; - } else - attribute = foregroundcolor[grm.foreground] | grm.bold | - backgroundcolor[grm.background] | grm.underline; - if (grm.reverse) - attribute = ((attribute >> 4) & 15) | ((attribute & 15) << 4); - SetConsoleTextAttribute(hConOut, attribute); - return; - - case 'J': - if (es_argc == 0) - es_argv[es_argc++] = 0; // ESC[J == ESC[0J - if (es_argc != 1) - return; - switch (es_argv[0]) { - case 0: // ESC[0J erase from cursor to end of display - len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X + - Info.dwSize.X - Info.dwCursorPosition.X - 1; - FillConsoleOutputCharacter(hConOut, ' ', len, Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1J erase from start to cursor. - Pos.X = 0; - Pos.Y = 0; - len = Info.dwCursorPosition.Y * Info.dwSize.X + - Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2J Clear screen and home cursor - Pos.X = 0; - Pos.Y = 0; - len = Info.dwSize.X * Info.dwSize.Y; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - SetConsoleCursorPosition(hConOut, Pos); - return; - - default: - return; - } - - case 'K': - if (es_argc == 0) - es_argv[es_argc++] = 0; // ESC[K == ESC[0K - if (es_argc != 1) - return; - switch (es_argv[0]) { - case 0: // ESC[0K Clear to end of line - len = Info.dwSize.X - Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1K Clear from start of line to cursor - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', Info.dwCursorPosition.X + 1, - Pos, &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2K Clear whole line. - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', Info.dwSize.X, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, Info.dwSize.X, - Pos, &NumberOfCharsWritten); - return; - - default: - return; - } - - case 'X': // ESC[#X Erase # characters. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[X == ESC[1X - if (es_argc != 1) - return; - FillConsoleOutputCharacter(hConOut, ' ', es_argv[0], - Info.dwCursorPosition, &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, es_argv[0], - Info.dwCursorPosition, &NumberOfCharsWritten); - return; - - case 'L': // ESC[#L Insert # blank lines. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[L == ESC[1L - if (es_argc != 1) - return; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'M': // ESC[#M Delete # lines. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[M == ESC[1M - if (es_argc != 1) - return; - if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) - es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'P': // ESC[#P Delete # characters. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[P == ESC[1P - if (es_argc != 1) - return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X + es_argv[0]; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Info.dwCursorPosition, - &CharInfo); - return; - - case '@': // ESC[#@ Insert # blank characters. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ - if (es_argc != 1) - return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1 - es_argv[0]; - Rect.Bottom = Info.dwCursorPosition.Y; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'k': // ESC[#k - case 'A': // ESC[#A Moves cursor up # lines - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[A == ESC[1A - if (es_argc != 1) - return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) - Pos.Y = 0; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'e': // ESC[#e - case 'B': // ESC[#B Moves cursor down # lines - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[B == ESC[1B - if (es_argc != 1) - return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) - Pos.Y = Info.dwSize.Y - 1; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'a': // ESC[#a - case 'C': // ESC[#C Moves cursor forward # spaces - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[C == ESC[1C - if (es_argc != 1) - return; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - if (Pos.X >= Info.dwSize.X) - Pos.X = Info.dwSize.X - 1; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'j': // ESC[#j - case 'D': // ESC[#D Moves cursor back # spaces - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[D == ESC[1D - if (es_argc != 1) - return; - Pos.X = Info.dwCursorPosition.X - es_argv[0]; - if (Pos.X < 0) - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'E': // ESC[#E Moves cursor down # lines, column 1. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[E == ESC[1E - if (es_argc != 1) - return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) - Pos.Y = Info.dwSize.Y - 1; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'F': // ESC[#F Moves cursor up # lines, column 1. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[F == ESC[1F - if (es_argc != 1) - return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) - Pos.Y = 0; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case '`': // ESC[#` - case 'G': // ESC[#G Moves cursor column # in current row. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[G == ESC[1G - if (es_argc != 1) - return; - Pos.X = es_argv[0] - 1; - if (Pos.X >= Info.dwSize.X) - Pos.X = Info.dwSize.X - 1; - if (Pos.X < 0) - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'd': // ESC[#d Moves cursor row #, current column. - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[d == ESC[1d - if (es_argc != 1) - return; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) - Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) - Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'f': // ESC[#;#f - case 'H': // ESC[#;#H Moves cursor to line #, column # - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H - if (es_argc == 1) - es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H - if (es_argc > 2) - return; - Pos.X = es_argv[1] - 1; - if (Pos.X < 0) - Pos.X = 0; - if (Pos.X >= Info.dwSize.X) - Pos.X = Info.dwSize.X - 1; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) - Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) - Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 's': // ESC[s Saves cursor position for recall later - if (es_argc != 0) - return; - SavePos = Info.dwCursorPosition; - return; - - case 'u': // ESC[u Return to saved cursor position - if (es_argc != 0) - return; - SetConsoleCursorPosition(hConOut, SavePos); - return; - - case 'n': // ESC[#n Device status report - if (es_argc != 1) - return; // ESC[n == ESC[0n -> ignored - switch (es_argv[0]) { - case 5: // ESC[5n Report status - SendSequence(L"\33[0n"); // "OK" - return; - - case 6: // ESC[6n Report cursor position - { - WCHAR buf[32]; - swprintf(buf, 32, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1, - Info.dwCursorPosition.X + 1); - SendSequence(buf); - } - return; - - default: - return; - } - - case 't': // ESC[#t Window manipulation - if (es_argc != 1) - return; - if (es_argv[0] == 21) // ESC[21t Report xterm window's title - { - WCHAR buf[MAX_PATH * 2]; - len = GetConsoleTitleW(buf + 3, lenof(buf) - 3 - 2); - // Too bad if it's too big or fails. - buf[0] = ESC; - buf[1] = ']'; - buf[2] = 'l'; - buf[3 + len] = ESC; - buf[3 + len + 1] = '\\'; - buf[3 + len + 2] = '\0'; - SendSequence(buf); - } - return; - - default: - return; - } - } else // (prefix == ']') - { - // Ignore any \e]? or \e]> sequences. - if (prefix2 != 0) - return; - - if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST + WORD attribute; + CONSOLE_SCREEN_BUFFER_INFO Info; + DWORD len, NumberOfCharsWritten; + COORD Pos; + SMALL_RECT Rect; + CHAR_INFO CharInfo; + + if (prefix == '[') { + if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) { + if (es_argc == 1 && es_argv[0] == 25) { + lpConsoleCursorInfo->bVisible = (suffix == 'h'); + return; + } + } + // Ignore any other \e[? or \e[> sequences. + if (prefix2 != 0) + return; + + GetConsoleScreenBufferInfo(hConOut, &Info); + switch (suffix) { + case 'm': + if (es_argc == 0) + es_argv[es_argc++] = 0; + for (int i = 0; i < es_argc; i++) { + if (30 <= es_argv[i] && es_argv[i] <= 37) + grm.foreground = es_argv[i] - 30; + else if (40 <= es_argv[i] && es_argv[i] <= 47) + grm.background = es_argv[i] - 40; + else + switch (es_argv[i]) { + case 0: + case 39: + case 49: { + WCHAR def[4]; + int a; + *def = '7'; + def[1] = '\0'; + GetEnvironmentVariableW(L"ANSICON_DEF", def, lenof(def)); + a = wcstol(def, NULL, 16); + grm.reverse = FALSE; + if (a < 0) { + grm.reverse = TRUE; + a = -a; + } + if (es_argv[i] != 49) + grm.foreground = attr2ansi[a & 7]; + if (es_argv[i] != 39) + grm.background = attr2ansi[(a >> 4) & 7]; + if (es_argv[i] == 0) { + if (es_argc == 1) { + grm.bold = a & FOREGROUND_INTENSITY; + grm.underline = a & BACKGROUND_INTENSITY; + } else { + grm.bold = 0; + grm.underline = 0; + } + grm.rvideo = 0; + grm.concealed = 0; + } + } break; + + case 1: + grm.bold = FOREGROUND_INTENSITY; + break; + case 5: // blink + case 4: + grm.underline = BACKGROUND_INTENSITY; + break; + case 7: + grm.rvideo = 1; + break; + case 8: + grm.concealed = 1; + break; + case 21: // oops, this actually turns on double underline + case 22: + grm.bold = 0; + break; + case 25: + case 24: + grm.underline = 0; + break; + case 27: + grm.rvideo = 0; + break; + case 28: + grm.concealed = 0; + break; + } + } + if (grm.concealed) { + if (grm.rvideo) { + attribute = + foregroundcolor[grm.foreground] | backgroundcolor[grm.foreground]; + if (grm.bold) + attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } else { + attribute = + foregroundcolor[grm.background] | backgroundcolor[grm.background]; + if (grm.underline) + attribute |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + } else if (grm.rvideo) { + attribute = + foregroundcolor[grm.background] | backgroundcolor[grm.foreground]; + if (grm.bold) + attribute |= BACKGROUND_INTENSITY; + if (grm.underline) + attribute |= FOREGROUND_INTENSITY; + } else + attribute = foregroundcolor[grm.foreground] | grm.bold | + backgroundcolor[grm.background] | grm.underline; + if (grm.reverse) + attribute = ((attribute >> 4) & 15) | ((attribute & 15) << 4); + SetConsoleTextAttribute(hConOut, attribute); + return; + + case 'J': + if (es_argc == 0) + es_argv[es_argc++] = 0; // ESC[J == ESC[0J + if (es_argc != 1) + return; + switch (es_argv[0]) { + case 0: // ESC[0J erase from cursor to end of display + len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X + + Info.dwSize.X - Info.dwCursorPosition.X - 1; + FillConsoleOutputCharacter(hConOut, ' ', len, Info.dwCursorPosition, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, + Info.dwCursorPosition, + &NumberOfCharsWritten); + return; + + case 1: // ESC[1J erase from start to cursor. + Pos.X = 0; + Pos.Y = 0; + len = Info.dwCursorPosition.Y * Info.dwSize.X + + Info.dwCursorPosition.X + 1; + FillConsoleOutputCharacter(hConOut, ' ', len, Pos, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, + &NumberOfCharsWritten); + return; + + case 2: // ESC[2J Clear screen and home cursor + Pos.X = 0; + Pos.Y = 0; + len = Info.dwSize.X * Info.dwSize.Y; + FillConsoleOutputCharacter(hConOut, ' ', len, Pos, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, + &NumberOfCharsWritten); + SetConsoleCursorPosition(hConOut, Pos); + return; + + default: + return; + } + + case 'K': + if (es_argc == 0) + es_argv[es_argc++] = 0; // ESC[K == ESC[0K + if (es_argc != 1) + return; + switch (es_argv[0]) { + case 0: // ESC[0K Clear to end of line + len = Info.dwSize.X - Info.dwCursorPosition.X + 1; + FillConsoleOutputCharacter(hConOut, ' ', len, Info.dwCursorPosition, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, + Info.dwCursorPosition, + &NumberOfCharsWritten); + return; + + case 1: // ESC[1K Clear from start of line to cursor + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + FillConsoleOutputCharacter(hConOut, ' ', Info.dwCursorPosition.X + 1, + Pos, &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, + Info.dwCursorPosition.X + 1, Pos, + &NumberOfCharsWritten); + return; + + case 2: // ESC[2K Clear whole line. + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + FillConsoleOutputCharacter(hConOut, ' ', Info.dwSize.X, Pos, + &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, Info.dwSize.X, + Pos, &NumberOfCharsWritten); + return; + + default: + return; + } + + case 'X': // ESC[#X Erase # characters. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[X == ESC[1X + if (es_argc != 1) + return; + FillConsoleOutputCharacter(hConOut, ' ', es_argv[0], + Info.dwCursorPosition, &NumberOfCharsWritten); + FillConsoleOutputAttribute(hConOut, Info.wAttributes, es_argv[0], + Info.dwCursorPosition, &NumberOfCharsWritten); + return; + + case 'L': // ESC[#L Insert # blank lines. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[L == ESC[1L + if (es_argc != 1) + return; + Rect.Left = 0; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwSize.Y - 1; + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); + return; + + case 'M': // ESC[#M Delete # lines. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[M == ESC[1M + if (es_argc != 1) + return; + if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) + es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; + Rect.Left = 0; + Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwSize.Y - 1; + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); + return; + + case 'P': // ESC[#P Delete # characters. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[P == ESC[1P + if (es_argc != 1) + return; + if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) + es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; + Rect.Left = Info.dwCursorPosition.X + es_argv[0]; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Info.dwCursorPosition, + &CharInfo); + return; + + case '@': // ESC[#@ Insert # blank characters. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ + if (es_argc != 1) + return; + if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) + es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; + Rect.Left = Info.dwCursorPosition.X; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1 - es_argv[0]; + Rect.Bottom = Info.dwCursorPosition.Y; + Pos.X = Info.dwCursorPosition.X + es_argv[0]; + Pos.Y = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); + return; + + case 'k': // ESC[#k + case 'A': // ESC[#A Moves cursor up # lines + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[A == ESC[1A + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; + if (Pos.Y < 0) + Pos.Y = 0; + Pos.X = Info.dwCursorPosition.X; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'e': // ESC[#e + case 'B': // ESC[#B Moves cursor down # lines + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[B == ESC[1B + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + Pos.X = Info.dwCursorPosition.X; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'a': // ESC[#a + case 'C': // ESC[#C Moves cursor forward # spaces + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[C == ESC[1C + if (es_argc != 1) + return; + Pos.X = Info.dwCursorPosition.X + es_argv[0]; + if (Pos.X >= Info.dwSize.X) + Pos.X = Info.dwSize.X - 1; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'j': // ESC[#j + case 'D': // ESC[#D Moves cursor back # spaces + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[D == ESC[1D + if (es_argc != 1) + return; + Pos.X = Info.dwCursorPosition.X - es_argv[0]; + if (Pos.X < 0) + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'E': // ESC[#E Moves cursor down # lines, column 1. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[E == ESC[1E + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + Pos.X = 0; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'F': // ESC[#F Moves cursor up # lines, column 1. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[F == ESC[1F + if (es_argc != 1) + return; + Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; + if (Pos.Y < 0) + Pos.Y = 0; + Pos.X = 0; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case '`': // ESC[#` + case 'G': // ESC[#G Moves cursor column # in current row. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[G == ESC[1G + if (es_argc != 1) + return; + Pos.X = es_argv[0] - 1; + if (Pos.X >= Info.dwSize.X) + Pos.X = Info.dwSize.X - 1; + if (Pos.X < 0) + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'd': // ESC[#d Moves cursor row #, current column. + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[d == ESC[1d + if (es_argc != 1) + return; + Pos.Y = es_argv[0] - 1; + if (Pos.Y < 0) + Pos.Y = 0; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 'f': // ESC[#;#f + case 'H': // ESC[#;#H Moves cursor to line #, column # + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H + if (es_argc == 1) + es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H + if (es_argc > 2) + return; + Pos.X = es_argv[1] - 1; + if (Pos.X < 0) + Pos.X = 0; + if (Pos.X >= Info.dwSize.X) + Pos.X = Info.dwSize.X - 1; + Pos.Y = es_argv[0] - 1; + if (Pos.Y < 0) + Pos.Y = 0; + if (Pos.Y >= Info.dwSize.Y) + Pos.Y = Info.dwSize.Y - 1; + SetConsoleCursorPosition(hConOut, Pos); + return; + + case 's': // ESC[s Saves cursor position for recall later + if (es_argc != 0) + return; + SavePos = Info.dwCursorPosition; + return; + + case 'u': // ESC[u Return to saved cursor position + if (es_argc != 0) + return; + SetConsoleCursorPosition(hConOut, SavePos); + return; + + case 'n': // ESC[#n Device status report + if (es_argc != 1) + return; // ESC[n == ESC[0n -> ignored + switch (es_argv[0]) { + case 5: // ESC[5n Report status + SendSequence(L"\33[0n"); // "OK" + return; + + case 6: // ESC[6n Report cursor position + { + WCHAR buf[32]; + swprintf(buf, 32, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1, + Info.dwCursorPosition.X + 1); + SendSequence(buf); + } + return; + + default: + return; + } + + case 't': // ESC[#t Window manipulation + if (es_argc != 1) + return; + if (es_argv[0] == 21) // ESC[21t Report xterm window's title + { + WCHAR buf[MAX_PATH * 2]; + len = GetConsoleTitleW(buf + 3, lenof(buf) - 3 - 2); + // Too bad if it's too big or fails. + buf[0] = ESC; + buf[1] = ']'; + buf[2] = 'l'; + buf[3 + len] = ESC; + buf[3 + len + 1] = '\\'; + buf[3 + len + 2] = '\0'; + SendSequence(buf); + } + return; + + default: + return; + } + } else // (prefix == ']') { - SetConsoleTitleW(Pt_arg); + // Ignore any \e]? or \e]> sequences. + if (prefix2 != 0) + return; + + if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST + { + SetConsoleTitleW(Pt_arg); + } } - } } //----------------------------------------------------------------------------- @@ -953,109 +955,110 @@ inline void InterpretEscSeq(PCONSOLE_CURSOR_INFO lpConsoleCursorInfo) { //----------------------------------------------------------------------------- inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, - DWORD nNumberOfBytesToWrite, - LPDWORD lpNumberOfBytesWritten) { - DWORD i; - LPCSTR s; - - if (hDev != hConOut) // reinit if device has changed - { - hConOut = hDev; - state = 1; - shifted = FALSE; - } - - CONSOLE_CURSOR_INFO oldConsoleCursorInfo; - GetConsoleCursorInfo(hConOut, &oldConsoleCursorInfo); - - CONSOLE_CURSOR_INFO invisibleCursor{.dwSize = oldConsoleCursorInfo.dwSize, - .bVisible = 0}; - SetConsoleCursorInfo(hConOut, &invisibleCursor); - - for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) { - if (state == 1) { - if (*s == ESC) - state = 2; - else if (*s == SO) - shifted = TRUE; - else if (*s == SI) - shifted = FALSE; - else { - DWORD written = PushBuffer(s, nNumberOfBytesToWrite); - if (written > 0) { - DWORD extraWritten = written - 1; - i -= extraWritten; - s += extraWritten; - } - } - } else if (state == 2) { - if (*s == ESC) - ; // \e\e...\e == \e - else if ((*s == '[') || (*s == ']')) { - FlushBuffer(); - prefix = *s; - prefix2 = 0; - state = 3; - Pt_len = 0; - *Pt_arg = '\0'; - } else if (*s == ')' || *s == '(') - state = 6; - else - state = 1; - } else if (state == 3) { - if (is_digit(*s)) { - es_argc = 0; - es_argv[0] = *s - '0'; - state = 4; - } else if (*s == ';') { - es_argc = 1; - es_argv[0] = 0; - es_argv[1] = 0; - state = 4; - } else if (*s == '?' || *s == '>') { - prefix2 = *s; - } else { - es_argc = 0; - suffix = *s; - InterpretEscSeq(&oldConsoleCursorInfo); - state = 1; - } - } else if (state == 4) { - if (is_digit(*s)) { - es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); - } else if (*s == ';') { - if (es_argc < MAX_ARG - 1) - es_argc++; - es_argv[es_argc] = 0; - if (prefix == ']') - state = 5; - } else { - es_argc++; - suffix = *s; - InterpretEscSeq(&oldConsoleCursorInfo); - state = 1; - } - } else if (state == 5) { - if (*s == BEL) { - Pt_arg[Pt_len] = '\0'; - InterpretEscSeq(&oldConsoleCursorInfo); - state = 1; - } else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) { - Pt_arg[--Pt_len] = '\0'; - InterpretEscSeq(&oldConsoleCursorInfo); - state = 1; - } else if (Pt_len < lenof(Pt_arg) - 1) - Pt_arg[Pt_len++] = *s; - } else if (state == 6) { - // Ignore it (ESC ) 0 is implicit; nothing else is supported). - state = 1; + DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten) { + DWORD i; + LPCSTR s; + + if (hDev != hConOut) // reinit if device has changed + { + hConOut = hDev; + state = 1; + shifted = FALSE; + } + + CONSOLE_CURSOR_INFO oldConsoleCursorInfo; + GetConsoleCursorInfo(hConOut, &oldConsoleCursorInfo); + + CONSOLE_CURSOR_INFO invisibleCursor; + invisibleCursor.dwSize = oldConsoleCursorInfo.dwSize; + invisibleCursor.bVisible = 0; + SetConsoleCursorInfo(hConOut, &invisibleCursor); + + for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) { + if (state == 1) { + if (*s == ESC) + state = 2; + else if (*s == SO) + shifted = TRUE; + else if (*s == SI) + shifted = FALSE; + else { + DWORD written = PushBuffer(s, nNumberOfBytesToWrite); + if (written > 0) { + DWORD extraWritten = written - 1; + i -= extraWritten; + s += extraWritten; + } + } + } else if (state == 2) { + if (*s == ESC) + ; // \e\e...\e == \e + else if ((*s == '[') || (*s == ']')) { + FlushBuffer(); + prefix = *s; + prefix2 = 0; + state = 3; + Pt_len = 0; + *Pt_arg = '\0'; + } else if (*s == ')' || *s == '(') + state = 6; + else + state = 1; + } else if (state == 3) { + if (is_digit(*s)) { + es_argc = 0; + es_argv[0] = *s - '0'; + state = 4; + } else if (*s == ';') { + es_argc = 1; + es_argv[0] = 0; + es_argv[1] = 0; + state = 4; + } else if (*s == '?' || *s == '>') { + prefix2 = *s; + } else { + es_argc = 0; + suffix = *s; + InterpretEscSeq(&oldConsoleCursorInfo); + state = 1; + } + } else if (state == 4) { + if (is_digit(*s)) { + es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); + } else if (*s == ';') { + if (es_argc < MAX_ARG - 1) + es_argc++; + es_argv[es_argc] = 0; + if (prefix == ']') + state = 5; + } else { + es_argc++; + suffix = *s; + InterpretEscSeq(&oldConsoleCursorInfo); + state = 1; + } + } else if (state == 5) { + if (*s == BEL) { + Pt_arg[Pt_len] = '\0'; + InterpretEscSeq(&oldConsoleCursorInfo); + state = 1; + } else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) { + Pt_arg[--Pt_len] = '\0'; + InterpretEscSeq(&oldConsoleCursorInfo); + state = 1; + } else if (Pt_len < lenof(Pt_arg) - 1) + Pt_arg[Pt_len++] = *s; + } else if (state == 6) { + // Ignore it (ESC ) 0 is implicit; nothing else is supported). + state = 1; + } } - } - FlushBuffer(); - SetConsoleCursorInfo(hConOut, &oldConsoleCursorInfo); - if (lpNumberOfBytesWritten != NULL) - *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; - return (i == 0); + FlushBuffer(); + SetConsoleCursorInfo(hConOut, &oldConsoleCursorInfo); + if (lpNumberOfBytesWritten != NULL) + *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; + return (i == 0); } } // namespace ansi @@ -1065,164 +1068,164 @@ HANDLE hIn; DWORD consolemodeIn = 0; inline int win32readW(WCHAR *c) { - DWORD foo; - INPUT_RECORD b; - KEY_EVENT_RECORD e; - BOOL altgr; - - while (1) { - if (!ReadConsoleInputW(hIn, &b, 1, &foo)) - return 0; - if (!foo) - return 0; - - if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { - - e = b.Event.KeyEvent; - *c = b.Event.KeyEvent.uChar.UnicodeChar; - - altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); - - if (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && - !altgr) { - - /* Ctrl+Key */ - switch (*c) { - case L'D': - *c = 4; - return 1; - case L'C': - *c = 3; - return 1; - case L'H': - *c = 8; - return 1; - case L'T': - *c = 20; - return 1; - case L'B': /* ctrl-b, left_arrow */ - *c = 2; - return 1; - case L'F': /* ctrl-f right_arrow*/ - *c = 6; - return 1; - case L'P': /* ctrl-p up_arrow*/ - *c = 16; - return 1; - case L'N': /* ctrl-n down_arrow*/ - *c = 14; - return 1; - case L'U': /* Ctrl+u, delete the whole line. */ - *c = 21; - return 1; - case L'K': /* Ctrl+k, delete from current to end of line. */ - *c = 11; - return 1; - case L'A': /* Ctrl+a, go to the start of the line */ - *c = 1; - return 1; - case L'E': /* ctrl+e, go to the end of the line */ - *c = 5; - return 1; - } - - /* Other Ctrl+KEYs ignored */ - } else { - - switch (e.wVirtualKeyCode) { - - case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ - *c = 3; - return 1; - case VK_RETURN: /* enter */ - *c = 13; - return 1; - case VK_LEFT: /* left */ - *c = 2; - return 1; - case VK_RIGHT: /* right */ - *c = 6; - return 1; - case VK_UP: /* up */ - *c = 16; - return 1; - case VK_DOWN: /* down */ - *c = 14; - return 1; - case VK_HOME: - *c = 1; - return 1; - case VK_END: - *c = 5; - return 1; - case VK_BACK: - *c = 8; - return 1; - case VK_DELETE: - *c = 4; /* same as Ctrl+D above */ - return 1; - default: - if (*c) - return 1; - } - } + DWORD foo; + INPUT_RECORD b; + KEY_EVENT_RECORD e; + BOOL altgr; + + while (1) { + if (!ReadConsoleInputW(hIn, &b, 1, &foo)) + return 0; + if (!foo) + return 0; + + if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { + + e = b.Event.KeyEvent; + *c = b.Event.KeyEvent.uChar.UnicodeChar; + + altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + + if (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && + !altgr) { + + /* Ctrl+Key */ + switch (*c) { + case L'D': + *c = 4; + return 1; + case L'C': + *c = 3; + return 1; + case L'H': + *c = 8; + return 1; + case L'T': + *c = 20; + return 1; + case L'B': /* ctrl-b, left_arrow */ + *c = 2; + return 1; + case L'F': /* ctrl-f right_arrow*/ + *c = 6; + return 1; + case L'P': /* ctrl-p up_arrow*/ + *c = 16; + return 1; + case L'N': /* ctrl-n down_arrow*/ + *c = 14; + return 1; + case L'U': /* Ctrl+u, delete the whole line. */ + *c = 21; + return 1; + case L'K': /* Ctrl+k, delete from current to end of line. */ + *c = 11; + return 1; + case L'A': /* Ctrl+a, go to the start of the line */ + *c = 1; + return 1; + case L'E': /* ctrl+e, go to the end of the line */ + *c = 5; + return 1; + } + + /* Other Ctrl+KEYs ignored */ + } else { + + switch (e.wVirtualKeyCode) { + + case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ + *c = 3; + return 1; + case VK_RETURN: /* enter */ + *c = 13; + return 1; + case VK_LEFT: /* left */ + *c = 2; + return 1; + case VK_RIGHT: /* right */ + *c = 6; + return 1; + case VK_UP: /* up */ + *c = 16; + return 1; + case VK_DOWN: /* down */ + *c = 14; + return 1; + case VK_HOME: + *c = 1; + return 1; + case VK_END: + *c = 5; + return 1; + case VK_BACK: + *c = 8; + return 1; + case VK_DELETE: + *c = 4; /* same as Ctrl+D above */ + return 1; + default: + if (*c) + return 1; + } + } + } } - } - return -1; /* Makes compiler happy */ + return -1; /* Makes compiler happy */ } inline int win32read(char *buf, int *c) { - WCHAR wideChars[2]; - int wideCharCount; - if (win32readW(wideChars) != 1) { - return 0; - } - - // check for high surrogate - if (!IS_HIGH_SURROGATE(wideChars[0])) { - *c = wideChars[0]; - wideCharCount = 1; - } else { - if (win32readW(wideChars + 1) != 1) { - return 0; + WCHAR wideChars[2]; + int wideCharCount; + if (win32readW(wideChars) != 1) { + return 0; } - // combine the surrogates - *c = 0x10000 + (((wideChars[0] & 0x3ff) << 10) | (wideChars[1] & 0x3ff)); - wideCharCount = 2; - } + // check for high surrogate + if (!IS_HIGH_SURROGATE(wideChars[0])) { + *c = wideChars[0]; + wideCharCount = 1; + } else { + if (win32readW(wideChars + 1) != 1) { + return 0; + } - auto count = WideCharToMultiByte(CP_UTF8, 0, wideChars, wideCharCount, buf, 4, - nullptr, nullptr); + // combine the surrogates + *c = 0x10000 + (((wideChars[0] & 0x3ff) << 10) | (wideChars[1] & 0x3ff)); + wideCharCount = 2; + } + + auto count = WideCharToMultiByte(CP_UTF8, 0, wideChars, wideCharCount, buf, 4, + nullptr, nullptr); - return count; + return count; } inline int win32_write(int fd, const void *buffer, unsigned int count) { - if (fd == _fileno(stdout)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_OUTPUT_HANDLE), - buffer, (DWORD)count, - &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } - } else if (fd == _fileno(stderr)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_ERROR_HANDLE), - buffer, (DWORD)count, - &bytesWritten)) { - return (int)bytesWritten; + if (fd == _fileno(stdout)) { + DWORD bytesWritten = 0; + if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_OUTPUT_HANDLE), + buffer, (DWORD)count, + &bytesWritten)) { + return (int)bytesWritten; + } else { + errno = GetLastError(); + return 0; + } + } else if (fd == _fileno(stderr)) { + DWORD bytesWritten = 0; + if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_ERROR_HANDLE), + buffer, (DWORD)count, + &bytesWritten)) { + return (int)bytesWritten; + } else { + errno = GetLastError(); + return 0; + } } else { - errno = GetLastError(); - return 0; + return _write(fd, buffer, count); } - } else { - return _write(fd, buffer, count); - } } #endif // _WIN32 @@ -1232,30 +1235,30 @@ static const char *unsupported_term[] = {"dumb", "cons25", "emacs", NULL}; static struct termios orig_termios; /* In order to restore at exit.*/ #endif static bool rawmode = - false; /* For atexit() function to check if restore is needed*/ +false; /* For atexit() function to check if restore is needed*/ static bool mlmode = false; /* Multi line mode. Default is single line. */ static bool atexit_registered = false; /* Register atexit just 1 time. */ enum KEY_ACTION { - KEY_NULL = 0, /* NULL */ - CTRL_A = 1, /* Ctrl+a */ - CTRL_B = 2, /* Ctrl-b */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_E = 5, /* Ctrl-e */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_K = 11, /* Ctrl+k */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_N = 14, /* Ctrl-n */ - CTRL_P = 16, /* Ctrl-p */ - CTRL_T = 20, /* Ctrl-t */ - CTRL_U = 21, /* Ctrl+u */ - CTRL_W = 23, /* Ctrl+w */ - ESC = 27, /* Escape */ - BACKSPACE = 127 /* Backspace */ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ }; void linenoiseAtExit(void); @@ -1266,138 +1269,138 @@ static unsigned long unicodeWideCharTable[][2] = { {0x1100, 0x115F}, {0x2329, 0x232A}, { - 0x2E80, - 0x2E99, + 0x2E80, + 0x2E99, }, { - 0x2E9B, - 0x2EF3, + 0x2E9B, + 0x2EF3, }, { - 0x2F00, - 0x2FD5, + 0x2F00, + 0x2FD5, }, { - 0x2FF0, - 0x2FFB, + 0x2FF0, + 0x2FFB, }, { - 0x3000, - 0x303E, + 0x3000, + 0x303E, }, { - 0x3041, - 0x3096, + 0x3041, + 0x3096, }, { - 0x3099, - 0x30FF, + 0x3099, + 0x30FF, }, { - 0x3105, - 0x312D, + 0x3105, + 0x312D, }, { - 0x3131, - 0x318E, + 0x3131, + 0x318E, }, { - 0x3190, - 0x31BA, + 0x3190, + 0x31BA, }, { - 0x31C0, - 0x31E3, + 0x31C0, + 0x31E3, }, { - 0x31F0, - 0x321E, + 0x31F0, + 0x321E, }, { - 0x3220, - 0x3247, + 0x3220, + 0x3247, }, { - 0x3250, - 0x4DBF, + 0x3250, + 0x4DBF, }, { - 0x4E00, - 0xA48C, + 0x4E00, + 0xA48C, }, { - 0xA490, - 0xA4C6, + 0xA490, + 0xA4C6, }, { - 0xA960, - 0xA97C, + 0xA960, + 0xA97C, }, { - 0xAC00, - 0xD7A3, + 0xAC00, + 0xD7A3, }, { - 0xF900, - 0xFAFF, + 0xF900, + 0xFAFF, }, { - 0xFE10, - 0xFE19, + 0xFE10, + 0xFE19, }, { - 0xFE30, - 0xFE52, + 0xFE30, + 0xFE52, }, { - 0xFE54, - 0xFE66, + 0xFE54, + 0xFE66, }, { - 0xFE68, - 0xFE6B, + 0xFE68, + 0xFE6B, }, { - 0xFF01, - 0xFFE6, + 0xFF01, + 0xFFE6, }, { - 0x1B000, - 0x1B001, + 0x1B000, + 0x1B001, }, { - 0x1F200, - 0x1F202, + 0x1F200, + 0x1F202, }, { - 0x1F210, - 0x1F23A, + 0x1F210, + 0x1F23A, }, { - 0x1F240, - 0x1F248, + 0x1F240, + 0x1F248, }, { - 0x1F250, - 0x1F251, + 0x1F250, + 0x1F251, }, { - 0x20000, - 0x3FFFD, + 0x20000, + 0x3FFFD, }, }; static int unicodeWideCharTableSize = - sizeof(unicodeWideCharTable) / sizeof(unicodeWideCharTable[0]); +sizeof(unicodeWideCharTable) / sizeof(unicodeWideCharTable[0]); static int unicodeIsWideChar(unsigned long cp) { - int i; - for (i = 0; i < unicodeWideCharTableSize; i++) { - if (unicodeWideCharTable[i][0] <= cp && cp <= unicodeWideCharTable[i][1]) { - return 1; + int i; + for (i = 0; i < unicodeWideCharTableSize; i++) { + if (unicodeWideCharTable[i][0] <= cp && cp <= unicodeWideCharTable[i][1]) { + return 1; + } } - } - return 0; + return 0; } static unsigned long unicodeCombiningCharTable[] = { @@ -1600,243 +1603,243 @@ static unsigned long unicodeCombiningCharTable[] = { }; static int unicodeCombiningCharTableSize = - sizeof(unicodeCombiningCharTable) / sizeof(unicodeCombiningCharTable[0]); +sizeof(unicodeCombiningCharTable) / sizeof(unicodeCombiningCharTable[0]); inline int unicodeIsCombiningChar(unsigned long cp) { - int i; - for (i = 0; i < unicodeCombiningCharTableSize; i++) { - if (unicodeCombiningCharTable[i] == cp) { - return 1; + int i; + for (i = 0; i < unicodeCombiningCharTableSize; i++) { + if (unicodeCombiningCharTable[i] == cp) { + return 1; + } } - } - return 0; + return 0; } /* Get length of previous UTF8 character - */ +*/ inline int unicodePrevUTF8CharLen(char *buf, int pos) { - int end = pos--; - while (pos >= 0 && ((unsigned char)buf[pos] & 0xC0) == 0x80) { - pos--; - } - return end - pos; + int end = pos--; + while (pos >= 0 && ((unsigned char)buf[pos] & 0xC0) == 0x80) { + pos--; + } + return end - pos; } /* Get length of previous UTF8 character - */ +*/ inline int unicodeUTF8CharLen(char *buf, int buf_len, int pos) { - if (pos == buf_len) { - return 0; - } - unsigned char ch = buf[pos]; - if (ch < 0x80) { - return 1; - } else if (ch < 0xE0) { - return 2; - } else if (ch < 0xF0) { - return 3; - } else { - return 4; - } + if (pos == buf_len) { + return 0; + } + unsigned char ch = buf[pos]; + if (ch < 0x80) { + return 1; + } else if (ch < 0xE0) { + return 2; + } else if (ch < 0xF0) { + return 3; + } else { + return 4; + } } /* Convert UTF8 to Unicode code point - */ +*/ inline int unicodeUTF8CharToCodePoint(const char *buf, int len, int *cp) { - if (len) { - unsigned char byte = buf[0]; - if ((byte & 0x80) == 0) { - *cp = byte; - return 1; - } else if ((byte & 0xE0) == 0xC0) { - if (len >= 2) { - *cp = (((unsigned long)(buf[0] & 0x1F)) << 6) | - ((unsigned long)(buf[1] & 0x3F)); - return 2; - } - } else if ((byte & 0xF0) == 0xE0) { - if (len >= 3) { - *cp = (((unsigned long)(buf[0] & 0x0F)) << 12) | - (((unsigned long)(buf[1] & 0x3F)) << 6) | - ((unsigned long)(buf[2] & 0x3F)); - return 3; - } - } else if ((byte & 0xF8) == 0xF0) { - if (len >= 4) { - *cp = (((unsigned long)(buf[0] & 0x07)) << 18) | - (((unsigned long)(buf[1] & 0x3F)) << 12) | - (((unsigned long)(buf[2] & 0x3F)) << 6) | - ((unsigned long)(buf[3] & 0x3F)); - return 4; - } + if (len) { + unsigned char byte = buf[0]; + if ((byte & 0x80) == 0) { + *cp = byte; + return 1; + } else if ((byte & 0xE0) == 0xC0) { + if (len >= 2) { + *cp = (((unsigned long)(buf[0] & 0x1F)) << 6) | + ((unsigned long)(buf[1] & 0x3F)); + return 2; + } + } else if ((byte & 0xF0) == 0xE0) { + if (len >= 3) { + *cp = (((unsigned long)(buf[0] & 0x0F)) << 12) | + (((unsigned long)(buf[1] & 0x3F)) << 6) | + ((unsigned long)(buf[2] & 0x3F)); + return 3; + } + } else if ((byte & 0xF8) == 0xF0) { + if (len >= 4) { + *cp = (((unsigned long)(buf[0] & 0x07)) << 18) | + (((unsigned long)(buf[1] & 0x3F)) << 12) | + (((unsigned long)(buf[2] & 0x3F)) << 6) | + ((unsigned long)(buf[3] & 0x3F)); + return 4; + } + } } - } - return 0; + return 0; } /* Get length of grapheme - */ +*/ inline int unicodeGraphemeLen(char *buf, int buf_len, int pos) { - if (pos == buf_len) { - return 0; - } - int beg = pos; - pos += unicodeUTF8CharLen(buf, buf_len, pos); - while (pos < buf_len) { - int len = unicodeUTF8CharLen(buf, buf_len, pos); - int cp = 0; - unicodeUTF8CharToCodePoint(buf + pos, len, &cp); - if (!unicodeIsCombiningChar(cp)) { - return pos - beg; + if (pos == buf_len) { + return 0; + } + int beg = pos; + pos += unicodeUTF8CharLen(buf, buf_len, pos); + while (pos < buf_len) { + int len = unicodeUTF8CharLen(buf, buf_len, pos); + int cp = 0; + unicodeUTF8CharToCodePoint(buf + pos, len, &cp); + if (!unicodeIsCombiningChar(cp)) { + return pos - beg; + } + pos += len; } - pos += len; - } - return pos - beg; + return pos - beg; } /* Get length of previous grapheme - */ +*/ inline int unicodePrevGraphemeLen(char *buf, int pos) { - if (pos == 0) { - return 0; - } - int end = pos; - while (pos > 0) { - int len = unicodePrevUTF8CharLen(buf, pos); - pos -= len; - int cp = 0; - unicodeUTF8CharToCodePoint(buf + pos, len, &cp); - if (!unicodeIsCombiningChar(cp)) { - return end - pos; + if (pos == 0) { + return 0; + } + int end = pos; + while (pos > 0) { + int len = unicodePrevUTF8CharLen(buf, pos); + pos -= len; + int cp = 0; + unicodeUTF8CharToCodePoint(buf + pos, len, &cp); + if (!unicodeIsCombiningChar(cp)) { + return end - pos; + } } - } - return 0; + return 0; } inline int isAnsiEscape(const char *buf, int buf_len, int *len) { - if (buf_len > 2 && !memcmp("\033[", buf, 2)) { - int off = 2; - while (off < buf_len) { - switch (buf[off++]) { - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - case 'G': - case 'H': - case 'J': - case 'K': - case 'S': - case 'T': - case 'f': - case 'm': - *len = off; - return 1; - } + if (buf_len > 2 && !memcmp("\033[", buf, 2)) { + int off = 2; + while (off < buf_len) { + switch (buf[off++]) { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'J': + case 'K': + case 'S': + case 'T': + case 'f': + case 'm': + *len = off; + return 1; + } + } } - } - return 0; + return 0; } /* Get column position for the single line mode. - */ +*/ inline int unicodeColumnPos(const char *buf, int buf_len) { - int ret = 0; - - int off = 0; - while (off < buf_len) { - int len; - if (isAnsiEscape(buf + off, buf_len - off, &len)) { - off += len; - continue; - } + int ret = 0; + + int off = 0; + while (off < buf_len) { + int len; + if (isAnsiEscape(buf + off, buf_len - off, &len)) { + off += len; + continue; + } - int cp = 0; - len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); + int cp = 0; + len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); - if (!unicodeIsCombiningChar(cp)) { - ret += unicodeIsWideChar(cp) ? 2 : 1; - } + if (!unicodeIsCombiningChar(cp)) { + ret += unicodeIsWideChar(cp) ? 2 : 1; + } - off += len; - } + off += len; + } - return ret; + return ret; } /* Get column position for the multi line mode. - */ +*/ inline int unicodeColumnPosForMultiLine(char *buf, int buf_len, int pos, - int cols, int ini_pos) { - int ret = 0; - int colwid = ini_pos; - - int off = 0; - while (off < buf_len) { - int cp = 0; - int len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); - - int wid = 0; - if (!unicodeIsCombiningChar(cp)) { - wid = unicodeIsWideChar(cp) ? 2 : 1; - } + int cols, int ini_pos) { + int ret = 0; + int colwid = ini_pos; - int dif = (int)(colwid + wid) - (int)cols; - if (dif > 0) { - ret += dif; - colwid = wid; - } else if (dif == 0) { - colwid = 0; - } else { - colwid += wid; - } - - if (off >= pos) { - break; + int off = 0; + while (off < buf_len) { + int cp = 0; + int len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); + + int wid = 0; + if (!unicodeIsCombiningChar(cp)) { + wid = unicodeIsWideChar(cp) ? 2 : 1; + } + + int dif = (int)(colwid + wid) - (int)cols; + if (dif > 0) { + ret += dif; + colwid = wid; + } else if (dif == 0) { + colwid = 0; + } else { + colwid += wid; + } + + if (off >= pos) { + break; + } + + off += len; + ret += wid; } - off += len; - ret += wid; - } - - return ret; + return ret; } /* Read UTF8 character from file. - */ +*/ inline int unicodeReadUTF8Char(int fd, char *buf, int *cp) { - int nread = read(fd, &buf[0], 1); + int nread = read(fd, &buf[0], 1); - if (nread <= 0) { - return nread; - } - - unsigned char byte = buf[0]; - - if ((byte & 0x80) == 0) { - ; - } else if ((byte & 0xE0) == 0xC0) { - nread = read(fd, &buf[1], 1); if (nread <= 0) { - return nread; + return nread; } - } else if ((byte & 0xF0) == 0xE0) { - nread = read(fd, &buf[1], 2); - if (nread <= 0) { - return nread; - } - } else if ((byte & 0xF8) == 0xF0) { - nread = read(fd, &buf[1], 3); - if (nread <= 0) { - return nread; + + unsigned char byte = buf[0]; + + if ((byte & 0x80) == 0) { + ; + } else if ((byte & 0xE0) == 0xC0) { + nread = read(fd, &buf[1], 1); + if (nread <= 0) { + return nread; + } + } else if ((byte & 0xF0) == 0xE0) { + nread = read(fd, &buf[1], 2); + if (nread <= 0) { + return nread; + } + } else if ((byte & 0xF8) == 0xF0) { + nread = read(fd, &buf[1], 3); + if (nread <= 0) { + return nread; + } + } else { + return -1; } - } else { - return -1; - } - return unicodeUTF8CharToCodePoint(buf, 4, cp); + return unicodeUTF8CharToCodePoint(buf, 4, cp); } /* ======================= Low level terminal handling ====================== */ @@ -1848,104 +1851,111 @@ inline void SetMultiLine(bool ml) { mlmode = ml; } * not able to understand basic escape sequences. */ inline bool isUnsupportedTerm(void) { #ifndef _WIN32 - char *term = getenv("TERM"); - int j; - - if (term == NULL) - return false; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term, unsupported_term[j])) - return true; + char *term = getenv("TERM"); + int j; + + if (term == NULL) + return false; + std::string sterm(term); + // https://stackoverflow.com/a/313990 + std::transform(sterm.begin(), sterm.end(), sterm.begin(), [](unsigned char c){ return std::tolower(c); }); + for (j = 0; unsupported_term[j]; j++) { + std::string uterm(unsupported_term[j]); + // https://stackoverflow.com/a/313990 + std::transform(uterm.begin(), uterm.end(), uterm.begin(), [](unsigned char c){ return std::tolower(c); }); + if (uterm == uterm) + return true; + } #endif - return false; + return false; } /* Raw mode: 1960 magic shit. */ inline bool enableRawMode(int fd) { #ifndef _WIN32 - struct termios raw; - - if (!isatty(STDIN_FILENO)) - goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = true; - } - if (tcgetattr(fd, &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 */ - // NOTE: Multithreaded issue #20 - // (https://github.com/yhirose/cpp-linenoise/issues/20) 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(fd, TCSAFLUSH, &raw) < 0) - goto fatal; - rawmode = true; + struct termios raw; + + if (!isatty(STDIN_FILENO)) + goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = true; + } + if (tcgetattr(fd, &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 */ + // NOTE: Multithreaded issue #20 + // (https://github.com/yhirose/cpp-linenoise/issues/20) 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(fd, TCSAFLUSH, &raw) < 0) + goto fatal; + rawmode = true; #else - if (!atexit_registered) { - /* Cleanup them at exit */ - atexit(linenoiseAtExit); - atexit_registered = true; - - /* Init windows console handles only once */ - hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut == INVALID_HANDLE_VALUE) - goto fatal; - } - - DWORD consolemodeOut; - if (!GetConsoleMode(hOut, &consolemodeOut)) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - }; + if (!atexit_registered) { + /* Cleanup them at exit */ + atexit(linenoiseAtExit); + atexit_registered = true; + + /* Init windows console handles only once */ + hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + goto fatal; + } - hIn = GetStdHandle(STD_INPUT_HANDLE); - if (hIn == INVALID_HANDLE_VALUE) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - } + DWORD consolemodeOut; + if (!GetConsoleMode(hOut, &consolemodeOut)) { + CloseHandle(hOut); + errno = ENOTTY; + return false; + }; + + hIn = GetStdHandle(STD_INPUT_HANDLE); + if (hIn == INVALID_HANDLE_VALUE) { + CloseHandle(hOut); + errno = ENOTTY; + return false; + } - GetConsoleMode(hIn, &consolemodeIn); - /* Enable raw mode */ - SetConsoleMode(hIn, consolemodeIn & ~ENABLE_PROCESSED_INPUT); + GetConsoleMode(hIn, &consolemodeIn); + /* Enable raw mode */ + SetConsoleMode(hIn, consolemodeIn & ~ENABLE_PROCESSED_INPUT); - rawmode = true; + rawmode = true; #endif - return true; + return true; fatal: - errno = ENOTTY; - return false; + errno = ENOTTY; + return false; } inline void disableRawMode(int fd) { #ifdef _WIN32 - if (consolemodeIn) { - SetConsoleMode(hIn, consolemodeIn); - consolemodeIn = 0; - } - rawmode = false; -#else - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(fd, TCSAFLUSH, &orig_termios) != -1) + if (consolemodeIn) { + SetConsoleMode(hIn, consolemodeIn); + consolemodeIn = 0; + } rawmode = false; +#else + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd, TCSAFLUSH, &orig_termios) != -1) + rawmode = false; #endif } @@ -1953,83 +1963,83 @@ inline void disableRawMode(int fd) { * and return it. On error -1 is returned, on success the position of the * cursor. */ inline int getCursorPosition(int ifd, int ofd) { - char buf[32]; - int cols, rows; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) - return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf) - 1) { - if (read(ifd, buf + i, 1) != 1) - break; - if (buf[i] == 'R') - break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') - return -1; - if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) - return -1; - return cols; + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) + return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf) - 1) { + if (read(ifd, buf + i, 1) != 1) + break; + if (buf[i] == 'R') + break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') + return -1; + if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) + return -1; + return cols; } /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ inline int getColumns(int ifd, int ofd) { #ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO b; + CONSOLE_SCREEN_BUFFER_INFO b; - if (!GetConsoleScreenBufferInfo(hOut, &b)) - return 80; - return (b.srWindow.Right - b.srWindow.Left) + 1; + if (!GetConsoleScreenBufferInfo(hOut, &b)) + return 80; + return (b.srWindow.Right - b.srWindow.Left) + 1; #else - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int start, cols; - - /* Get the initial position so we can restore it later. */ - start = getCursorPosition(ifd, ofd); - if (start == -1) - goto failed; - - /* Go to right margin and get position. */ - if (write(ofd, "\x1b[999C", 6) != 6) - goto failed; - cols = getCursorPosition(ifd, ofd); - if (cols == -1) - goto failed; - - /* Restore position. */ - if (cols > start) { - char seq[32]; - snprintf(seq, 32, "\x1b[%dD", cols - start); - if (write(ofd, seq, strlen(seq)) == -1) { - /* Can't recover... */ - } + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd, ofd); + if (start == -1) + goto failed; + + /* Go to right margin and get position. */ + if (write(ofd, "\x1b[999C", 6) != 6) + goto failed; + cols = getCursorPosition(ifd, ofd); + if (cols == -1) + goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq, 32, "\x1b[%dD", cols - start); + if (write(ofd, seq, strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; } - return cols; - } else { - return ws.ws_col; - } failed: - return 80; + return 80; #endif } /* Clear the screen. Used to handle ctrl+l */ inline void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) <= 0) { - /* nothing to do, just to avoid warning. */ - } + if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) <= 0) { + /* nothing to do, just to avoid warning. */ + } } /* Temporarily clear the line from the screen, @@ -2037,15 +2047,21 @@ inline void linenoiseClearScreen(void) { * temporarily clearing prompt and input while * printing other content. */ inline void linenoiseWipeLine() { - (void)write(STDOUT_FILENO, "\33[2K", 4); // Clear line - (void)write(STDOUT_FILENO, "\r", 1); // Move cursor to the left + // Clear line + if (write(STDOUT_FILENO, "\33[2K", 4) < 0) { + /* nothing to do, just to avoid warning. */ + } + // Move cursor to the left + if (write(STDOUT_FILENO, "\r", 1) < 0) { + /* nothing to do, just to avoid warning. */ + } } /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ inline void linenoiseBeep(void) { - fprintf(stderr, "\x7"); - fflush(stderr); + fprintf(stderr, "\x7"); + fflush(stderr); } /* ============================== Completion ================================ */ @@ -2057,68 +2073,68 @@ inline void linenoiseBeep(void) { * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ int linenoiseState::completeLine(char *cbuf, int *c) { - std::vector lc; - int nread = 0, nwritten; - *c = 0; - - completionCallback(buf, lc); - if (lc.empty()) { - linenoiseBeep(); - } else { - int stop = 0, i = 0; - - while (!stop) { - /* Show completion or original buffer */ - if (i < static_cast(lc.size())) { - int old_len = len; - int old_pos = pos; - char *old_buf = buf; - len = pos = static_cast(lc[i].size()); - buf = &lc[i][0]; - RefreshLine(); - len = old_len; - pos = old_pos; - buf = old_buf; - } else { - RefreshLine(); - } - - // nread = read(ls->ifd,&c,1); + std::vector lc; + int nread = 0, nwritten; + *c = 0; + + completionCallback(buf, lc); + if (lc.empty()) { + linenoiseBeep(); + } else { + int stop = 0, i = 0; + + while (!stop) { + /* Show completion or original buffer */ + if (i < static_cast(lc.size())) { + int old_len = len; + int old_pos = pos; + char *old_buf = buf; + len = pos = static_cast(lc[i].size()); + buf = &lc[i][0]; + RefreshLine(); + len = old_len; + pos = old_pos; + buf = old_buf; + } else { + RefreshLine(); + } + + // nread = read(ls->ifd,&c,1); #ifdef _WIN32 - nread = win32read(cbuf, c); + nread = win32read(cbuf, c); #else - nread = unicodeReadUTF8Char(ifd, cbuf, c); + nread = unicodeReadUTF8Char(ifd, cbuf, c); #endif - if (nread <= 0) { - *c = -1; - return nread; - } - - switch (*c) { - case 9: /* tab */ - i = (i + 1) % (lc.size() + 1); - if (i == static_cast(lc.size())) - linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < static_cast(lc.size())) - RefreshLine(); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < static_cast(lc.size())) { - nwritten = snprintf(buf, buflen, "%s", &lc[i][0]); - len = pos = nwritten; - } - stop = 1; - break; - } + if (nread <= 0) { + *c = -1; + return nread; + } + + switch (*c) { + case 9: /* tab */ + i = (i + 1) % (lc.size() + 1); + if (i == static_cast(lc.size())) + linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < static_cast(lc.size())) + RefreshLine(); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < static_cast(lc.size())) { + nwritten = snprintf(buf, buflen, "%s", &lc[i][0]); + len = pos = nwritten; + } + stop = 1; + break; + } + } } - } - return nread; + return nread; } /* =========================== Line editing ================================= */ @@ -2128,36 +2144,41 @@ int linenoiseState::completeLine(char *cbuf, int *c) { * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ void linenoiseState::refreshSingleLine() { - char seq[64]; - int pcolwid = - unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); - int fd = ofd; - std::string ab; - - while ((pcolwid + unicodeColumnPos(buf, pos)) >= cols) { - int glen = unicodeGraphemeLen(buf, len, 0); - buf += glen; - len -= glen; - pos -= glen; - } - while (pcolwid + unicodeColumnPos(buf, len) > cols) { - len -= unicodePrevGraphemeLen(buf, len); - } - - /* Cursor to left edge */ - snprintf(seq, 64, "\r"); - ab += seq; - /* Write the prompt and the current buffer content */ - ab += prompt; - ab.append(buf, len); - /* Erase to right */ - snprintf(seq, 64, "\x1b[0K"); - ab += seq; - /* Move cursor to original position. */ - snprintf(seq, 64, "\r\x1b[%dC", (int)(unicodeColumnPos(buf, pos) + pcolwid)); - ab += seq; - if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { - } /* Can't recover from write error. */ + char seq[64]; + int pcolwid = + unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); + int fd = ofd; + std::string ab; + // TODO - right now this is just delayed initialization + // of lcols, but might be worth checking if we should update + // the column count every time. + if (lcols <= 0) + lcols = getColumns(ifd, ofd); + + while ((pcolwid + unicodeColumnPos(buf, pos)) >= lcols) { + int glen = unicodeGraphemeLen(buf, len, 0); + buf += glen; + len -= glen; + pos -= glen; + } + while (pcolwid + unicodeColumnPos(buf, len) > lcols) { + len -= unicodePrevGraphemeLen(buf, len); + } + + /* Cursor to left edge */ + snprintf(seq, 64, "\r"); + ab += seq; + /* Write the prompt and the current buffer content */ + ab += prompt; + ab.append(buf, len); + /* Erase to right */ + snprintf(seq, 64, "\x1b[0K"); + ab += seq; + /* Move cursor to original position. */ + snprintf(seq, 64, "\r\x1b[%dC", (int)(unicodeColumnPos(buf, pos) + pcolwid)); + ab += seq; + if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { + } /* Can't recover from write error. */ } /* Multi line low level line refresh. @@ -2165,80 +2186,83 @@ void linenoiseState::refreshSingleLine() { * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ void linenoiseState::refreshMultiLine() { - char seq[64]; - int pcolwid = - unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); - int colpos = unicodeColumnPosForMultiLine(buf, len, len, cols, pcolwid); - int colpos2; /* cursor column position. */ - int rows = - (pcolwid + colpos + cols - 1) / cols; /* rows used by current buf. */ - int rpos = (pcolwid + oldcolpos + cols) / cols; /* cursor relative row. */ - int rpos2; /* rpos after refresh. */ - int col; /* column position, zero-based. */ - int old_rows = (int)maxrows; - int fd = ofd, j; - std::string ab; - - /* Update maxrows if needed. */ - if (rows > (int)maxrows) - maxrows = rows; - - /* First step: clear all the lines used before. To do so start by - * going to the last row. */ - if (old_rows - rpos > 0) { - snprintf(seq, 64, "\x1b[%dB", old_rows - rpos); - ab += seq; - } + if (lcols <= 0) + lcols = getColumns(ifd, ofd); + + char seq[64]; + int pcolwid = + unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())); + int colpos = unicodeColumnPosForMultiLine(buf, len, len, lcols, pcolwid); + int colpos2; /* cursor column position. */ + int rows = + (pcolwid + colpos + lcols - 1) / lcols; /* rows used by current buf. */ + int rpos = (pcolwid + oldcolpos + lcols) / lcols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* column position, zero-based. */ + int old_rows = (int)maxrows; + int fd = ofd, j; + std::string ab; + + /* Update maxrows if needed. */ + if (rows > (int)maxrows) + maxrows = rows; - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows - 1; j++) { - snprintf(seq, 64, "\r\x1b[0K\x1b[1A"); - ab += seq; - } + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + if (old_rows - rpos > 0) { + snprintf(seq, 64, "\x1b[%dB", old_rows - rpos); + ab += seq; + } - /* Clean the top line. */ - snprintf(seq, 64, "\r\x1b[0K"); - ab += seq; + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows - 1; j++) { + snprintf(seq, 64, "\r\x1b[0K\x1b[1A"); + ab += seq; + } - /* Write the prompt and the current buffer content */ - ab += prompt; - ab.append(buf, len); + /* Clean the top line. */ + snprintf(seq, 64, "\r\x1b[0K"); + ab += seq; - /* Get text width to cursor position */ - colpos2 = unicodeColumnPosForMultiLine(buf, len, pos, cols, pcolwid); + /* Write the prompt and the current buffer content */ + ab += prompt; + ab.append(buf, len); + + /* Get text width to cursor position */ + colpos2 = unicodeColumnPosForMultiLine(buf, len, pos, lcols, pcolwid); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (pos && pos == len && (colpos2 + pcolwid) % lcols == 0) { + ab += "\n"; + snprintf(seq, 64, "\r"); + ab += seq; + rows++; + if (rows > (int)maxrows) + maxrows = rows; + } - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (pos && pos == len && (colpos2 + pcolwid) % cols == 0) { - ab += "\n"; - snprintf(seq, 64, "\r"); - ab += seq; - rows++; - if (rows > (int)maxrows) - maxrows = rows; - } + /* Move cursor to right position. */ + rpos2 = (pcolwid + colpos2 + lcols) / lcols; /* current cursor relative row. */ - /* Move cursor to right position. */ - rpos2 = (pcolwid + colpos2 + cols) / cols; /* current cursor relative row. */ + /* Go up till we reach the expected position. */ + if (rows - rpos2 > 0) { + snprintf(seq, 64, "\x1b[%dA", rows - rpos2); + ab += seq; + } - /* Go up till we reach the expected position. */ - if (rows - rpos2 > 0) { - snprintf(seq, 64, "\x1b[%dA", rows - rpos2); + /* Set column. */ + col = (pcolwid + colpos2) % lcols; + if (col) + snprintf(seq, 64, "\r\x1b[%dC", col); + else + snprintf(seq, 64, "\r"); ab += seq; - } - - /* Set column. */ - col = (pcolwid + colpos2) % cols; - if (col) - snprintf(seq, 64, "\r\x1b[%dC", col); - else - snprintf(seq, 64, "\r"); - ab += seq; - oldcolpos = colpos2; + oldcolpos = colpos2; - if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { - } /* Can't recover from write error. */ + if (write(fd, ab.c_str(), static_cast(ab.length())) == -1) { + } /* Can't recover from write error. */ } /* Calls the two low level functions refreshSingleLine() or @@ -2249,12 +2273,12 @@ void linenoiseState::refreshMultiLine() { * Readline call) so we use a mutex for basic thread safety. * */ void linenoiseState::RefreshLine() { - r_mutex.lock(); - if (mlmode) - refreshMultiLine(); - else - refreshSingleLine(); - r_mutex.unlock(); + r_mutex.lock(); + if (mlmode) + refreshMultiLine(); + else + refreshSingleLine(); + r_mutex.unlock(); } void linenoiseState::WipeLine() { linenoiseWipeLine(); } @@ -2265,66 +2289,68 @@ void linenoiseState::ClearScreen() { linenoiseClearScreen(); } * * On error writing to the terminal -1 is returned, otherwise 0. */ int linenoiseState::linenoiseEditInsert(const char *cbuf, int clen) { - if (len < buflen) { - if (len == pos) { - memcpy(&buf[pos], cbuf, clen); - pos += clen; - len += clen; - ; - buf[len] = '\0'; - if ((!mlmode && - unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())) + - unicodeColumnPos(buf, len) < - cols) /* || mlmode */) { - /* Avoid a full update of the line in the - * trivial case. */ - if (write(ofd, cbuf, clen) == -1) - return -1; - } else { - RefreshLine(); - } - } else { - memmove(buf + pos + clen, buf + pos, len - pos); - memcpy(&buf[pos], cbuf, clen); - pos += clen; - len += clen; - buf[len] = '\0'; - RefreshLine(); + if (lcols <= 0) + lcols = getColumns(ifd, ofd); + if (len < buflen) { + if (len == pos) { + memcpy(&buf[pos], cbuf, clen); + pos += clen; + len += clen; + ; + buf[len] = '\0'; + if ((!mlmode && + unicodeColumnPos(prompt.c_str(), static_cast(prompt.length())) + + unicodeColumnPos(buf, len) < + lcols) /* || mlmode */) { + /* Avoid a full update of the line in the + * trivial case. */ + if (write(ofd, cbuf, clen) == -1) + return -1; + } else { + RefreshLine(); + } + } else { + memmove(buf + pos + clen, buf + pos, len - pos); + memcpy(&buf[pos], cbuf, clen); + pos += clen; + len += clen; + buf[len] = '\0'; + RefreshLine(); + } } - } - return 0; + return 0; } /* Move cursor on the left. */ void linenoiseState::linenoiseEditMoveLeft() { - if (pos > 0) { - pos -= unicodePrevGraphemeLen(buf, pos); - RefreshLine(); - } + if (pos > 0) { + pos -= unicodePrevGraphemeLen(buf, pos); + RefreshLine(); + } } /* Move cursor on the right. */ void linenoiseState::linenoiseEditMoveRight() { - if (pos != len) { - pos += unicodeGraphemeLen(buf, len, pos); - RefreshLine(); - } + if (pos != len) { + pos += unicodeGraphemeLen(buf, len, pos); + RefreshLine(); + } } /* Move cursor to the start of the line. */ void linenoiseState::linenoiseEditMoveHome() { - if (pos != 0) { - pos = 0; - RefreshLine(); - } + if (pos != 0) { + pos = 0; + RefreshLine(); + } } /* Move cursor to the end of the line. */ void linenoiseState::linenoiseEditMoveEnd() { - if (pos != len) { - pos = len; - RefreshLine(); - } + if (pos != len) { + pos = len; + RefreshLine(); + } } /* Substitute the currently edited line with the next or previous history @@ -2332,73 +2358,73 @@ void linenoiseState::linenoiseEditMoveEnd() { #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 void linenoiseState::linenoiseEditHistoryNext(int dir) { - int history_size = static_cast(history.size()); - if (!history_size) - return; - /* Show the new entry */ - if (history_index == -1) { - history_tmpbuf = std::string(buf); - history_index = history.size() - 1; - } else { - if (history_index == history_size) - history_tmpbuf = std::string(buf); - history_index += (dir == LINENOISE_HISTORY_PREV) ? -1 : 1; - } - if (history_index < 0) - history_index = history_size; - if (history_index == history_size) { + int history_size = static_cast(history.size()); + if (!history_size) + return; + /* Show the new entry */ + if (history_index == -1) { + history_tmpbuf = std::string(buf); + history_index = history.size() - 1; + } else { + if (history_index == history_size) + history_tmpbuf = std::string(buf); + history_index += (dir == LINENOISE_HISTORY_PREV) ? -1 : 1; + } + if (history_index < 0) + history_index = history_size; + if (history_index == history_size) { + memset(buf, 0, buflen); + strcpy(buf, history_tmpbuf.c_str()); + len = pos = static_cast(history_tmpbuf.size()); + RefreshLine(); + return; + } + if (history_index > history_size) + history_index = 0; memset(buf, 0, buflen); - strcpy(buf, history_tmpbuf.c_str()); - len = pos = static_cast(history_tmpbuf.size()); + strcpy(buf, history[history_index].c_str()); + len = pos = static_cast(strlen(buf)); RefreshLine(); - return; - } - if (history_index > history_size) - history_index = 0; - memset(buf, 0, buflen); - strcpy(buf, history[history_index].c_str()); - len = pos = static_cast(strlen(buf)); - RefreshLine(); } /* Delete the character at the right of the cursor without altering the cursor * position. Basically this is what happens with the "Delete" keyboard key. */ void linenoiseState::linenoiseEditDelete() { - if (len > 0 && pos < len) { - int glen = unicodeGraphemeLen(buf, len, pos); - memmove(buf + pos, buf + pos + glen, len - pos - glen); - len -= glen; - buf[len] = '\0'; - RefreshLine(); - } + if (len > 0 && pos < len) { + int glen = unicodeGraphemeLen(buf, len, pos); + memmove(buf + pos, buf + pos + glen, len - pos - glen); + len -= glen; + buf[len] = '\0'; + RefreshLine(); + } } /* Backspace implementation. */ void linenoiseState::linenoiseEditBackspace() { - if (pos > 0 && len > 0) { - int glen = unicodePrevGraphemeLen(buf, pos); - memmove(buf + pos - glen, buf + pos, len - pos); - pos -= glen; - len -= glen; - buf[len] = '\0'; - RefreshLine(); - } + if (pos > 0 && len > 0) { + int glen = unicodePrevGraphemeLen(buf, pos); + memmove(buf + pos - glen, buf + pos, len - pos); + pos -= glen; + len -= glen; + buf[len] = '\0'; + RefreshLine(); + } } /* Delete the previous word, maintaining the cursor at the start of the * current word. */ void linenoiseState::linenoiseEditDeletePrevWord() { - int old_pos = pos; - int diff; - - while (pos > 0 && buf[pos - 1] == ' ') - pos--; - while (pos > 0 && buf[pos - 1] != ' ') - pos--; - diff = old_pos - pos; - memmove(buf + pos, buf + old_pos, len - old_pos + 1); - len -= diff; - RefreshLine(); + int old_pos = pos; + int diff; + + while (pos > 0 && buf[pos - 1] == ' ') + pos--; + while (pos > 0 && buf[pos - 1] != ' ') + pos--; + diff = old_pos - pos; + memmove(buf + pos, buf + old_pos, len - old_pos + 1); + len -= diff; + RefreshLine(); } /* This function is the core of the line editing capability of linenoise. @@ -2410,226 +2436,231 @@ void linenoiseState::linenoiseEditDeletePrevWord() { * * The function returns the length of the current buffer. */ int linenoiseState::linenoiseEdit() -//, int stdin_fd, int stdout_fd, char *buf, int buflen, const char *prompt) + //, int stdin_fd, int stdout_fd, char *buf, int buflen, const char *prompt) { - if (write(ofd, prompt.c_str(), static_cast(prompt.length())) == -1) - return -1; - while (1) { - int c; - char cbuf[4]; - int nread; - char seq[3]; + if (write(ofd, prompt.c_str(), static_cast(prompt.length())) == -1) + return -1; + while (1) { + int c; + char cbuf[4]; + int nread; + char seq[3]; #ifdef _WIN32 - nread = win32read(cbuf, &c); + nread = win32read(cbuf, &c); #else - nread = unicodeReadUTF8Char(ifd, cbuf, &c); + nread = unicodeReadUTF8Char(ifd, cbuf, &c); #endif - if (nread <= 0) - return (int)len; - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - nread = completeLine(cbuf, &c); - /* Return on errors */ - if (c < 0) - return len; - /* Read next character when 0 */ - if (c == 0) - continue; - } - - switch (c) { - case ENTER: /* enter */ - history_index = -1; - if (history.size() == history_max_len) - history.pop_back(); - if (mlmode) - linenoiseEditMoveEnd(); - return (int)len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (len > 0) { - linenoiseEditDelete(); - } else { - history.pop_back(); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (pos > 0 && pos < len) { - char aux = wbuf[pos - 1]; - wbuf[pos - 1] = wbuf[pos]; - wbuf[pos] = aux; - if (pos != len - 1) - pos++; - RefreshLine(); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(ifd, seq, 1) == -1) - break; - if (read(ifd, seq + 1, 1) == -1) - break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(ifd, seq + 2, 1) == -1) - break; - if (seq[2] == '~') { - switch (seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(); - break; - } - } - } else { - switch (seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(); - break; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch (seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(); - break; - } - } - break; - default: - if (linenoiseEditInsert(cbuf, nread)) - return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - wbuf[0] = '\0'; - pos = len = 0; - RefreshLine(); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - wbuf[pos] = '\0'; - len = pos; - RefreshLine(); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - RefreshLine(); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(); - break; + if (nread <= 0) + return (int)len; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + nread = completeLine(cbuf, &c); + /* Return on errors */ + if (c < 0) + return len; + /* Read next character when 0 */ + if (c == 0) + continue; + } + + switch (c) { + case ENTER: /* enter */ + history_index = -1; + if (history.size() == history_max_len) + history.pop_back(); + if (mlmode) + linenoiseEditMoveEnd(); + return (int)len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (len > 0) { + linenoiseEditDelete(); + } else { + history.pop_back(); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (pos > 0 && pos < len) { + char aux = wbuf[pos - 1]; + wbuf[pos - 1] = wbuf[pos]; + wbuf[pos] = aux; + if (pos != len - 1) + pos++; + RefreshLine(); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(ifd, seq, 1) == -1) + break; + if (read(ifd, seq + 1, 1) == -1) + break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(ifd, seq + 2, 1) == -1) + break; + if (seq[2] == '~') { + switch (seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(); + break; + } + } + } else { + switch (seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(); + break; + } + } + break; + default: + if (linenoiseEditInsert(cbuf, nread)) + return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + wbuf[0] = '\0'; + pos = len = 0; + RefreshLine(); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + wbuf[pos] = '\0'; + len = pos; + RefreshLine(); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + RefreshLine(); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(); + break; + } } - } - return len; + return len; } /* This function calls the line editing function linenoiseEdit() using * the STDIN file descriptor set in raw mode. */ bool linenoiseState::linenoiseRaw(std::string &line) { - bool quit = false; + bool quit = false; - if (!isatty(STDIN_FILENO)) { + if (!isatty(STDIN_FILENO)) { - /* Not a tty: read from file / pipe. */ - int c; - while ((c = getc(stdin)) != EOF) - line += c; + /* Not a tty: read from file / pipe. */ + int c; + while ((c = getc(stdin)) != EOF) { + if (c == '\r') // CRLF -> LF + continue; + if (c == '\n' || c == '\r') // send command + break; - if (!line.length()) - quit = true; - - } else { - /* Interactive editing. */ - if (enableRawMode(STDIN_FILENO) == false) { - return quit; - } + line += c; + } + if (!line.length()) + quit = true; - /* Buffer starts empty. Since we're potentially - * reusing the state, we need to reset these. */ - pos = 0; - len = 0; - buf[0] = '\0'; - wbuf[0] = '\0'; - - auto count = linenoiseEdit(); - if (count == -1) { - quit = true; } else { - line.assign(buf, count); + /* Interactive editing. */ + if (enableRawMode(STDIN_FILENO) == false) { + return quit; + } + + /* Buffer starts empty. Since we're potentially + * reusing the state, we need to reset these. */ + pos = 0; + len = 0; + buf[0] = '\0'; + wbuf[0] = '\0'; + + auto count = linenoiseEdit(); + if (count == -1) { + quit = true; + } else { + line.assign(buf, count); + } + + disableRawMode(STDIN_FILENO); + printf("\n"); } - - disableRawMode(STDIN_FILENO); - printf("\n"); - } - return quit; + return quit; } -linenoiseState::linenoiseState(const char *prompt_str, int stdin_fd, int stdout_fd) { - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - ifd = stdin_fd; - ofd = stdout_fd; - buf = wbuf; - prompt = (prompt_str) ? std::string(prompt_str) : std::string("> "); - cols = getColumns(ifd, ofd); - - /* Buffer starts empty. */ - buf[0] = '\0'; - buflen--; /* Make sure there is always space for the nulterm */ +linenoiseState::linenoiseState(const char *prompt_str, int stdin_fd, + int stdout_fd) { + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + ifd = stdin_fd; + ofd = stdout_fd; + buf = wbuf; + prompt = (prompt_str) ? std::string(prompt_str) : std::string("> "); + + /* Buffer starts empty. */ + buf[0] = '\0'; + buflen--; /* Make sure there is always space for the nulterm */ } void linenoiseState::EnableMultiLine() { SetMultiLine(true); } @@ -2641,25 +2672,25 @@ void linenoiseState::DisableMultiLine() { SetMultiLine(false); } * editing function or uses dummy fgets() so that you will be able to type * something even in the most desperate of the conditions. */ bool linenoiseState::Readline(std::string &line) { - if (isUnsupportedTerm()) { - printf("%s", prompt.c_str()); - fflush(stdout); - std::getline(std::cin, line); - return false; - } else { - return linenoiseRaw(line); - } + if (isUnsupportedTerm()) { + printf("%s", prompt.c_str()); + fflush(stdout); + std::getline(std::cin, line); + return false; + } else { + return linenoiseRaw(line); + } } std::string linenoiseState::Readline(bool &quit) { - std::string line; - quit = Readline(line); - return line; + std::string line; + quit = Readline(line); + return line; } std::string linenoiseState::Readline() { - bool quit; // dummy - return Readline(quit); + bool quit; // dummy + return Readline(quit); } /* ================================ History ================================= */ @@ -2675,22 +2706,22 @@ inline void linenoiseAtExit(void) { disableRawMode(STDIN_FILENO); } * * Using a circular buffer is smarter, but a bit more complex to handle. */ bool linenoiseState::AddHistory(const char *line) { - if (history_max_len == 0) - return false; - if (!line || !strlen(line)) - return false; - - /* Don't add duplicated lines. */ - if (!history.empty() && history.back() == line) - return false; - - /* If we reached the max length, remove the older line. */ - if (history.size() == history_max_len) { - history.erase(history.begin()); - } - history.push_back(line); + if (history_max_len == 0) + return false; + if (!line || !strlen(line)) + return false; + + /* Don't add duplicated lines. */ + if (!history.empty() && history.back() == line) + return false; + + /* If we reached the max length, remove the older line. */ + if (history.size() == history_max_len) { + history.erase(history.begin()); + } + history.push_back(line); - return true; + return true; } /* Set the maximum length for the history. This function can be called even @@ -2698,13 +2729,13 @@ bool linenoiseState::AddHistory(const char *line) { * just the latest 'hlen' elements if the new history length value is smaller * than the amount of items already inside the history. */ bool linenoiseState::SetHistoryMaxLen(size_t hlen) { - if (hlen < 1) - return false; - history_max_len = hlen; - if (hlen < history.size()) { - history.resize(hlen); - } - return true; + if (hlen < 1) + return false; + history_max_len = hlen; + if (hlen < history.size()) { + history.resize(hlen); + } + return true; } /* Load the history from the specified file. If the file does not exist @@ -2713,26 +2744,26 @@ bool linenoiseState::SetHistoryMaxLen(size_t hlen) { * If the file exists and the operation succeeded *true* is returned, otherwise * on error *false* is returned. */ bool linenoiseState::LoadHistory(const char *path) { - std::ifstream f(path); - if (!f) - return false; - std::string line; - while (std::getline(f, line)) { - AddHistory(line.c_str()); - } - return true; + std::ifstream f(path); + if (!f) + return false; + std::string line; + while (std::getline(f, line)) { + AddHistory(line.c_str()); + } + return true; } /* Save the history in the specified file. On success *true* is returned * otherwise *false* is returned. */ bool linenoiseState::SaveHistory(const char *path) { - std::ofstream f(path); // TODO: need 'std::ios::binary'? - if (!f) - return false; - for (const auto &h : history) { - f << h << std::endl; - } - return true; + std::ofstream f(path); // TODO: need 'std::ios::binary'? + if (!f) + return false; + for (const auto &h : history) { + f << h << std::endl; + } + return true; } } // namespace linenoise @@ -2745,3 +2776,12 @@ bool linenoiseState::SaveHistory(const char *path) { #endif #endif /* __LINENOISE_HPP */ + +// Local Variables: +// tab-width: 8 +// mode: C++ +// c-basic-offset: 4 +// indent-tabs-mode: t +// c-file-style: "stroustrup" +// End: +// ex: shiftwidth=4 tabstop=8 cino=N-s