X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FTerminal.cc;fp=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FTerminal.cc;h=afa0a362600b5159ab33790090ebd586b76fca2c;hp=0000000000000000000000000000000000000000;hb=e79e4a5a87f3e84f7c1777f10a954453a69bf540;hpb=4339da12467b75fb8b6ca831f4bf0081c485ed2c diff --git a/node_modules/node-pty/deps/winpty/src/agent/Terminal.cc b/node_modules/node-pty/deps/winpty/src/agent/Terminal.cc new file mode 100644 index 0000000..afa0a36 --- /dev/null +++ b/node_modules/node-pty/deps/winpty/src/agent/Terminal.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Terminal.h" + +#include +#include +#include + +#include + +#include "NamedPipe.h" +#include "UnicodeEncoding.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +#define CSI "\x1b[" + +// Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and +// COMMON_LVB_TRAILING_BYTE. +const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100; +const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200; +const int WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000; +const int WINPTY_COMMON_LVB_UNDERSCORE = 0x8000; + +const int COLOR_ATTRIBUTE_MASK = + FOREGROUND_BLUE | + FOREGROUND_GREEN | + FOREGROUND_RED | + FOREGROUND_INTENSITY | + BACKGROUND_BLUE | + BACKGROUND_GREEN | + BACKGROUND_RED | + BACKGROUND_INTENSITY | + WINPTY_COMMON_LVB_REVERSE_VIDEO | + WINPTY_COMMON_LVB_UNDERSCORE; + +const int FLAG_RED = 1; +const int FLAG_GREEN = 2; +const int FLAG_BLUE = 4; +const int FLAG_BRIGHT = 8; + +const int BLACK = 0; +const int DKGRAY = BLACK | FLAG_BRIGHT; +const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE; +const int WHITE = LTGRAY | FLAG_BRIGHT; + +// SGR parameters (Select Graphic Rendition) +const int SGR_FORE = 30; +const int SGR_FORE_HI = 90; +const int SGR_BACK = 40; +const int SGR_BACK_HI = 100; + +namespace { + +static void outUInt(std::string &out, unsigned int n) +{ + char buf[32]; + char *pbuf = &buf[32]; + *(--pbuf) = '\0'; + do { + *(--pbuf) = '0' + n % 10; + n /= 10; + } while (n != 0); + out.append(pbuf); +} + +static void outputSetColorSgrParams(std::string &out, bool isFore, int color) +{ + out.push_back(';'); + const int sgrBase = isFore ? SGR_FORE : SGR_BACK; + if (color & FLAG_BRIGHT) { + // Some terminals don't support the 9X/10X "intensive" color parameters + // (e.g. the Eclipse TM terminal as of this writing). Those terminals + // will quietly ignore a 9X/10X code, and the other terminals will + // ignore a 3X/4X code if it's followed by a 9X/10X code. Therefore, + // output a 3X/4X code as a fallback, then override it. + const int colorBase = color & ~FLAG_BRIGHT; + outUInt(out, sgrBase + colorBase); + out.push_back(';'); + outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase); + } else { + outUInt(out, sgrBase + color); + } +} + +static void outputSetColor(std::string &out, int color) +{ + int fore = 0; + int back = 0; + if (color & FOREGROUND_RED) fore |= FLAG_RED; + if (color & FOREGROUND_GREEN) fore |= FLAG_GREEN; + if (color & FOREGROUND_BLUE) fore |= FLAG_BLUE; + if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT; + if (color & BACKGROUND_RED) back |= FLAG_RED; + if (color & BACKGROUND_GREEN) back |= FLAG_GREEN; + if (color & BACKGROUND_BLUE) back |= FLAG_BLUE; + if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT; + + if (color & WINPTY_COMMON_LVB_REVERSE_VIDEO) { + // n.b.: The COMMON_LVB_REVERSE_VIDEO flag also swaps + // FOREGROUND_INTENSITY and BACKGROUND_INTENSITY. Tested on + // Windows 10 v14393. + std::swap(fore, back); + } + + // Translate the fore/back colors into terminal escape codes using + // a heuristic that works OK with common white-on-black or + // black-on-white color schemes. We don't know which color scheme + // the terminal is using. It is ugly to force white-on-black text + // on a black-on-white terminal, and it's even ugly to force the + // matching scheme. It's probably relevant that the default + // fore/back terminal colors frequently do not match any of the 16 + // palette colors. + + // Typical default terminal color schemes (according to palette, + // when possible): + // - mintty: LtGray-on-Black(A) + // - putty: LtGray-on-Black(A) + // - xterm: LtGray-on-Black(A) + // - Konsole: LtGray-on-Black(A) + // - JediTerm/JetBrains: Black-on-White(B) + // - rxvt: Black-on-White(B) + + // If the background is the default color (black), then it will + // map to Black(A) or White(B). If we translate White to White, + // then a Black background and a White background in the console + // are both White with (B). Therefore, we should translate White + // using SGR 7 (Invert). The typical finished mapping table for + // background grayscale colors is: + // + // (A) White => LtGray(fore) + // (A) Black => Black(back) + // (A) LtGray => LtGray + // (A) DkGray => DkGray + // + // (B) White => Black(fore) + // (B) Black => White(back) + // (B) LtGray => LtGray + // (B) DkGray => DkGray + // + + out.append(CSI "0"); + if (back == BLACK) { + if (fore == LTGRAY) { + // The "default" foreground color. Use the terminal's + // default colors. + } else if (fore == WHITE) { + // Sending the literal color white would behave poorly if + // the terminal were black-on-white. Sending Bold is not + // guaranteed to alter the color, but it will make the text + // visually distinct, so do that instead. + out.append(";1"); + } else if (fore == DKGRAY) { + // Set the foreground color to DkGray(90) with a fallback + // of LtGray(37) for terminals that don't handle the 9X SGR + // parameters (e.g. Eclipse's TM Terminal as of this + // writing). + out.append(";37;90"); + } else { + outputSetColorSgrParams(out, true, fore); + } + } else if (back == WHITE) { + // Set the background color using Invert on the default + // foreground color, and set the foreground color by setting a + // background color. + + // Use the terminal's inverted colors. + out.append(";7"); + if (fore == LTGRAY || fore == BLACK) { + // We're likely mapping Console White to terminal LtGray or + // Black. If they are the Console foreground color, then + // don't set a terminal foreground color to avoid creating + // invisible text. + } else { + outputSetColorSgrParams(out, false, fore); + } + } else { + // Set the foreground and background to match exactly that in + // the Windows console. + outputSetColorSgrParams(out, true, fore); + outputSetColorSgrParams(out, false, back); + } + if (fore == back) { + // The foreground and background colors are exactly equal, so + // attempt to hide the text using the Conceal SGR parameter, + // which some terminals support. + out.append(";8"); + } + if (color & WINPTY_COMMON_LVB_UNDERSCORE) { + out.append(";4"); + } + out.push_back('m'); +} + +static inline unsigned int fixSpecialCharacters(unsigned int ch) +{ + if (ch <= 0x1b) { + switch (ch) { + // The Windows Console has a popup window (e.g. that appears with + // F7) that is sometimes bordered with box-drawing characters. + // With the Japanese and Korean system locales (CP932 and CP949), + // the UnicodeChar values for the box-drawing characters are 1 + // through 6. Detect this and map the values to the correct + // Unicode values. + // + // N.B. In the English locale, the UnicodeChar values are correct, + // and they identify single-line characters rather than + // double-line. In the Chinese Simplified and Traditional locales, + // the popups use ASCII characters instead. + case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT + case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT + case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT + case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT + case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL + case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL + + // Convert an escape character to some other character. This + // conversion only applies to console cells containing an escape + // character. In newer versions of Windows 10 (e.g. 10.0.10586), + // the non-legacy console recognizes escape sequences in + // WriteConsole and interprets them without writing them to the + // cells of the screen buffer. In that case, the conversion here + // does not apply. + case 0x1b: return '?'; + } + } + return ch; +} + +static inline bool isFullWidthCharacter(const CHAR_INFO *data, int width) +{ + if (width < 2) { + return false; + } + return + (data[0].Attributes & WINPTY_COMMON_LVB_LEADING_BYTE) && + (data[1].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) && + data[0].Char.UnicodeChar == data[1].Char.UnicodeChar; +} + +// Scan to find a single Unicode Scalar Value. Full-width characters occupy +// two console cells, and this code also tries to handle UTF-16 surrogate +// pairs. +// +// Windows expands at least some wide characters outside the Basic +// Multilingual Plane into four cells, such as U+20000: +// 1. 0xD840, attr=0x107 +// 2. 0xD840, attr=0x207 +// 3. 0xDC00, attr=0x107 +// 4. 0xDC00, attr=0x207 +// Even in the Traditional Chinese locale on Windows 10, this text is rendered +// as two boxes, but if those boxes are copied-and-pasted, the character is +// copied correctly. +static inline void scanUnicodeScalarValue( + const CHAR_INFO *data, int width, + int &outCellCount, unsigned int &outCharValue) +{ + ASSERT(width >= 1); + + const int w1 = isFullWidthCharacter(data, width) ? 2 : 1; + const wchar_t c1 = data[0].Char.UnicodeChar; + + if ((c1 & 0xF800) == 0xD800) { + // The first cell is either a leading or trailing surrogate pair. + if ((c1 & 0xFC00) != 0xD800 || + width <= w1 || + ((data[w1].Char.UnicodeChar & 0xFC00) != 0xDC00)) { + // Invalid surrogate pair + outCellCount = w1; + outCharValue = '?'; + } else { + // Valid surrogate pair + outCellCount = w1 + (isFullWidthCharacter(&data[w1], width - w1) ? 2 : 1); + outCharValue = decodeSurrogatePair(c1, data[w1].Char.UnicodeChar); + } + } else { + outCellCount = w1; + outCharValue = c1; + } +} + +} // anonymous namespace + +void Terminal::reset(SendClearFlag sendClearFirst, int64_t newLine) +{ + if (sendClearFirst == SendClear && !m_plainMode) { + // 0m ==> reset SGR parameters + // 1;1H ==> move cursor to top-left position + // 2J ==> clear the entire screen + m_output.write(CSI "0m" CSI "1;1H" CSI "2J"); + } + m_remoteLine = newLine; + m_remoteColumn = 0; + m_lineData.clear(); + m_cursorHidden = false; + m_remoteColor = -1; +} + +void Terminal::sendLine(int64_t line, const CHAR_INFO *lineData, int width, + int cursorColumn) +{ + ASSERT(width >= 1); + + moveTerminalToLine(line); + + // If possible, see if we can append to what we've already output for this + // line. + if (m_lineDataValid) { + ASSERT(m_lineData.size() == static_cast(m_remoteColumn)); + if (m_remoteColumn > 0) { + // In normal mode, if m_lineData.size() equals `width`, then we + // will have trouble outputing the "erase rest of line" command, + // which must be output before reaching the end of the line. In + // plain mode, we don't output that command, so we're OK with a + // full line. + bool okWidth = false; + if (m_plainMode) { + okWidth = static_cast(width) >= m_lineData.size(); + } else { + okWidth = static_cast(width) > m_lineData.size(); + } + if (!okWidth || + memcmp(m_lineData.data(), lineData, + sizeof(CHAR_INFO) * m_lineData.size()) != 0) { + m_lineDataValid = false; + } + } + } + if (!m_lineDataValid) { + // We can't reuse, so we must reset this line. + hideTerminalCursor(); + if (m_plainMode) { + // We can't backtrack, so repeat this line. + m_output.write("\r\n"); + } else { + m_output.write("\r"); + } + m_lineDataValid = true; + m_lineData.clear(); + m_remoteColumn = 0; + } + + std::string &termLine = m_termLineWorkingBuffer; + termLine.clear(); + size_t trimmedLineLength = 0; + int trimmedCellCount = m_lineData.size(); + bool alreadyErasedLine = false; + + int cellCount = 1; + for (int i = m_lineData.size(); i < width; i += cellCount) { + if (m_outputColor) { + int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK; + if (color != m_remoteColor) { + outputSetColor(termLine, color); + trimmedLineLength = termLine.size(); + m_remoteColor = color; + + // All the cells just up to this color change will be output. + trimmedCellCount = i; + } + } + unsigned int ch; + scanUnicodeScalarValue(&lineData[i], width - i, cellCount, ch); + if (ch == ' ') { + // Tentatively add this space character. We'll only output it if + // we see something interesting after it. + termLine.push_back(' '); + } else { + if (i + cellCount == width) { + // We'd like to erase the line after outputting all non-blank + // characters, but this doesn't work if the last cell in the + // line is non-blank. At the point, the cursor is positioned + // just past the end of the line, but in many terminals, + // issuing a CSI 0K at that point also erases the last cell in + // the line. Work around this behavior by issuing the erase + // one character early in that case. + if (!m_plainMode) { + termLine.append(CSI "0K"); // Erase from cursor to EOL + } + alreadyErasedLine = true; + } + ch = fixSpecialCharacters(ch); + char enc[4]; + int enclen = encodeUtf8(enc, ch); + if (enclen == 0) { + enc[0] = '?'; + enclen = 1; + } + termLine.append(enc, enclen); + trimmedLineLength = termLine.size(); + + // All the cells up to and including this cell will be output. + trimmedCellCount = i + cellCount; + } + } + + if (cursorColumn != -1 && trimmedCellCount > cursorColumn) { + // The line content would run past the cursor, so hide it before we + // output. + hideTerminalCursor(); + } + + m_output.write(termLine.data(), trimmedLineLength); + if (!alreadyErasedLine && !m_plainMode) { + m_output.write(CSI "0K"); // Erase from cursor to EOL + } + + ASSERT(trimmedCellCount <= width); + m_lineData.insert(m_lineData.end(), + &lineData[m_lineData.size()], + &lineData[trimmedCellCount]); + m_remoteColumn = trimmedCellCount; +} + +void Terminal::showTerminalCursor(int column, int64_t line) +{ + moveTerminalToLine(line); + if (!m_plainMode) { + if (m_remoteColumn != column) { + char buffer[32]; + winpty_snprintf(buffer, CSI "%dG", column + 1); + m_output.write(buffer); + m_lineDataValid = (column == 0); + m_lineData.clear(); + m_remoteColumn = column; + } + if (m_cursorHidden) { + m_output.write(CSI "?25h"); + m_cursorHidden = false; + } + } +} + +void Terminal::hideTerminalCursor() +{ + if (!m_plainMode) { + if (m_cursorHidden) { + return; + } + m_output.write(CSI "?25l"); + m_cursorHidden = true; + } +} + +void Terminal::moveTerminalToLine(int64_t line) +{ + if (line == m_remoteLine) { + return; + } + + // Do not use CPL or CNL. Konsole 2.5.4 does not support Cursor Previous + // Line (CPL) -- there are "Undecodable sequence" errors. gnome-terminal + // 2.32.0 does handle it. Cursor Next Line (CNL) does nothing if the + // cursor is on the last line already. + + hideTerminalCursor(); + + if (line < m_remoteLine) { + if (m_plainMode) { + // We can't backtrack, so instead repeat the lines again. + m_output.write("\r\n"); + m_remoteLine = line; + } else { + // Backtrack and overwrite previous lines. + // CUrsor Up (CUU) + char buffer[32]; + winpty_snprintf(buffer, "\r" CSI "%uA", + static_cast(m_remoteLine - line)); + m_output.write(buffer); + m_remoteLine = line; + } + } else if (line > m_remoteLine) { + while (line > m_remoteLine) { + m_output.write("\r\n"); + m_remoteLine++; + } + } + + m_lineDataValid = true; + m_lineData.clear(); + m_remoteColumn = 0; +} + +void Terminal::enableMouseMode(bool enabled) +{ + if (m_mouseModeEnabled == enabled || m_plainMode) { + return; + } + m_mouseModeEnabled = enabled; + if (enabled) { + // Start by disabling UTF-8 coordinate mode (1005), just in case we + // have a terminal that does not support 1006/1015 modes, and 1005 + // happens to be enabled. The UTF-8 coordinates can't be unambiguously + // decoded. + // + // Enable basic mouse support first (1000), then try to switch to + // button-move mode (1002), then try full mouse-move mode (1003). + // Terminals that don't support a mode will be stuck at the highest + // mode they do support. + // + // Enable encoding mode 1015 first, then try to switch to 1006. On + // some terminals, both modes will be enabled, but 1006 will have + // priority. On other terminals, 1006 wins because it's listed last. + // + // See misc/MouseInputNotes.txt for details. + m_output.write( + CSI "?1005l" + CSI "?1000h" CSI "?1002h" CSI "?1003h" CSI "?1015h" CSI "?1006h"); + } else { + // Resetting both encoding modes (1006 and 1015) is necessary, but + // apparently we only need to use reset on one of the 100[023] modes. + // Doing both doesn't hurt. + m_output.write( + CSI "?1006l" CSI "?1015l" CSI "?1003l" CSI "?1002l" CSI "?1000l"); + } +}