installed pty
[VSoRC/.git] / node_modules / node-pty / deps / winpty / src / agent / Terminal.cc
1 // Copyright (c) 2011-2015 Ryan Prichard
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 // IN THE SOFTWARE.
20
21 #include "Terminal.h"
22
23 #include <windows.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include <string>
28
29 #include "NamedPipe.h"
30 #include "UnicodeEncoding.h"
31 #include "../shared/DebugClient.h"
32 #include "../shared/WinptyAssert.h"
33 #include "../shared/winpty_snprintf.h"
34
35 #define CSI "\x1b["
36
37 // Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and
38 // COMMON_LVB_TRAILING_BYTE.
39 const int WINPTY_COMMON_LVB_LEADING_BYTE  = 0x100;
40 const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200;
41 const int WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000;
42 const int WINPTY_COMMON_LVB_UNDERSCORE    = 0x8000;
43
44 const int COLOR_ATTRIBUTE_MASK =
45         FOREGROUND_BLUE |
46         FOREGROUND_GREEN |
47         FOREGROUND_RED |
48         FOREGROUND_INTENSITY |
49         BACKGROUND_BLUE |
50         BACKGROUND_GREEN |
51         BACKGROUND_RED |
52         BACKGROUND_INTENSITY |
53         WINPTY_COMMON_LVB_REVERSE_VIDEO |
54         WINPTY_COMMON_LVB_UNDERSCORE;
55
56 const int FLAG_RED    = 1;
57 const int FLAG_GREEN  = 2;
58 const int FLAG_BLUE   = 4;
59 const int FLAG_BRIGHT = 8;
60
61 const int BLACK  = 0;
62 const int DKGRAY = BLACK | FLAG_BRIGHT;
63 const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE;
64 const int WHITE  = LTGRAY | FLAG_BRIGHT;
65
66 // SGR parameters (Select Graphic Rendition)
67 const int SGR_FORE = 30;
68 const int SGR_FORE_HI = 90;
69 const int SGR_BACK = 40;
70 const int SGR_BACK_HI = 100;
71
72 namespace {
73
74 static void outUInt(std::string &out, unsigned int n)
75 {
76     char buf[32];
77     char *pbuf = &buf[32];
78     *(--pbuf) = '\0';
79     do {
80         *(--pbuf) = '0' + n % 10;
81         n /= 10;
82     } while (n != 0);
83     out.append(pbuf);
84 }
85
86 static void outputSetColorSgrParams(std::string &out, bool isFore, int color)
87 {
88     out.push_back(';');
89     const int sgrBase = isFore ? SGR_FORE : SGR_BACK;
90     if (color & FLAG_BRIGHT) {
91         // Some terminals don't support the 9X/10X "intensive" color parameters
92         // (e.g. the Eclipse TM terminal as of this writing).  Those terminals
93         // will quietly ignore a 9X/10X code, and the other terminals will
94         // ignore a 3X/4X code if it's followed by a 9X/10X code.  Therefore,
95         // output a 3X/4X code as a fallback, then override it.
96         const int colorBase = color & ~FLAG_BRIGHT;
97         outUInt(out, sgrBase + colorBase);
98         out.push_back(';');
99         outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase);
100     } else {
101         outUInt(out, sgrBase + color);
102     }
103 }
104
105 static void outputSetColor(std::string &out, int color)
106 {
107     int fore = 0;
108     int back = 0;
109     if (color & FOREGROUND_RED)       fore |= FLAG_RED;
110     if (color & FOREGROUND_GREEN)     fore |= FLAG_GREEN;
111     if (color & FOREGROUND_BLUE)      fore |= FLAG_BLUE;
112     if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT;
113     if (color & BACKGROUND_RED)       back |= FLAG_RED;
114     if (color & BACKGROUND_GREEN)     back |= FLAG_GREEN;
115     if (color & BACKGROUND_BLUE)      back |= FLAG_BLUE;
116     if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT;
117
118     if (color & WINPTY_COMMON_LVB_REVERSE_VIDEO) {
119         // n.b.: The COMMON_LVB_REVERSE_VIDEO flag also swaps
120         // FOREGROUND_INTENSITY and BACKGROUND_INTENSITY.  Tested on
121         // Windows 10 v14393.
122         std::swap(fore, back);
123     }
124
125     // Translate the fore/back colors into terminal escape codes using
126     // a heuristic that works OK with common white-on-black or
127     // black-on-white color schemes.  We don't know which color scheme
128     // the terminal is using.  It is ugly to force white-on-black text
129     // on a black-on-white terminal, and it's even ugly to force the
130     // matching scheme.  It's probably relevant that the default
131     // fore/back terminal colors frequently do not match any of the 16
132     // palette colors.
133
134     // Typical default terminal color schemes (according to palette,
135     // when possible):
136     //  - mintty:               LtGray-on-Black(A)
137     //  - putty:                LtGray-on-Black(A)
138     //  - xterm:                LtGray-on-Black(A)
139     //  - Konsole:              LtGray-on-Black(A)
140     //  - JediTerm/JetBrains:   Black-on-White(B)
141     //  - rxvt:                 Black-on-White(B)
142
143     // If the background is the default color (black), then it will
144     // map to Black(A) or White(B).  If we translate White to White,
145     // then a Black background and a White background in the console
146     // are both White with (B).  Therefore, we should translate White
147     // using SGR 7 (Invert).  The typical finished mapping table for
148     // background grayscale colors is:
149     //
150     //  (A) White => LtGray(fore)
151     //  (A) Black => Black(back)
152     //  (A) LtGray => LtGray
153     //  (A) DkGray => DkGray
154     //
155     //  (B) White => Black(fore)
156     //  (B) Black => White(back)
157     //  (B) LtGray => LtGray
158     //  (B) DkGray => DkGray
159     //
160
161     out.append(CSI "0");
162     if (back == BLACK) {
163         if (fore == LTGRAY) {
164             // The "default" foreground color.  Use the terminal's
165             // default colors.
166         } else if (fore == WHITE) {
167             // Sending the literal color white would behave poorly if
168             // the terminal were black-on-white.  Sending Bold is not
169             // guaranteed to alter the color, but it will make the text
170             // visually distinct, so do that instead.
171             out.append(";1");
172         } else if (fore == DKGRAY) {
173             // Set the foreground color to DkGray(90) with a fallback
174             // of LtGray(37) for terminals that don't handle the 9X SGR
175             // parameters (e.g. Eclipse's TM Terminal as of this
176             // writing).
177             out.append(";37;90");
178         } else {
179             outputSetColorSgrParams(out, true, fore);
180         }
181     } else if (back == WHITE) {
182         // Set the background color using Invert on the default
183         // foreground color, and set the foreground color by setting a
184         // background color.
185
186         // Use the terminal's inverted colors.
187         out.append(";7");
188         if (fore == LTGRAY || fore == BLACK) {
189             // We're likely mapping Console White to terminal LtGray or
190             // Black.  If they are the Console foreground color, then
191             // don't set a terminal foreground color to avoid creating
192             // invisible text.
193         } else {
194             outputSetColorSgrParams(out, false, fore);
195         }
196     } else {
197         // Set the foreground and background to match exactly that in
198         // the Windows console.
199         outputSetColorSgrParams(out, true, fore);
200         outputSetColorSgrParams(out, false, back);
201     }
202     if (fore == back) {
203         // The foreground and background colors are exactly equal, so
204         // attempt to hide the text using the Conceal SGR parameter,
205         // which some terminals support.
206         out.append(";8");
207     }
208     if (color & WINPTY_COMMON_LVB_UNDERSCORE) {
209         out.append(";4");
210     }
211     out.push_back('m');
212 }
213
214 static inline unsigned int fixSpecialCharacters(unsigned int ch)
215 {
216     if (ch <= 0x1b) {
217         switch (ch) {
218             // The Windows Console has a popup window (e.g. that appears with
219             // F7) that is sometimes bordered with box-drawing characters.
220             // With the Japanese and Korean system locales (CP932 and CP949),
221             // the UnicodeChar values for the box-drawing characters are 1
222             // through 6.  Detect this and map the values to the correct
223             // Unicode values.
224             //
225             // N.B. In the English locale, the UnicodeChar values are correct,
226             // and they identify single-line characters rather than
227             // double-line.  In the Chinese Simplified and Traditional locales,
228             // the popups use ASCII characters instead.
229             case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT
230             case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT
231             case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT
232             case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT
233             case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL
234             case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL
235
236             // Convert an escape character to some other character.  This
237             // conversion only applies to console cells containing an escape
238             // character.  In newer versions of Windows 10 (e.g. 10.0.10586),
239             // the non-legacy console recognizes escape sequences in
240             // WriteConsole and interprets them without writing them to the
241             // cells of the screen buffer.  In that case, the conversion here
242             // does not apply.
243             case 0x1b: return '?';
244         }
245     }
246     return ch;
247 }
248
249 static inline bool isFullWidthCharacter(const CHAR_INFO *data, int width)
250 {
251     if (width < 2) {
252         return false;
253     }
254     return
255         (data[0].Attributes & WINPTY_COMMON_LVB_LEADING_BYTE) &&
256         (data[1].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) &&
257         data[0].Char.UnicodeChar == data[1].Char.UnicodeChar;
258 }
259
260 // Scan to find a single Unicode Scalar Value.  Full-width characters occupy
261 // two console cells, and this code also tries to handle UTF-16 surrogate
262 // pairs.
263 //
264 // Windows expands at least some wide characters outside the Basic
265 // Multilingual Plane into four cells, such as U+20000:
266 //   1. 0xD840, attr=0x107
267 //   2. 0xD840, attr=0x207
268 //   3. 0xDC00, attr=0x107
269 //   4. 0xDC00, attr=0x207
270 // Even in the Traditional Chinese locale on Windows 10, this text is rendered
271 // as two boxes, but if those boxes are copied-and-pasted, the character is
272 // copied correctly.
273 static inline void scanUnicodeScalarValue(
274     const CHAR_INFO *data, int width,
275     int &outCellCount, unsigned int &outCharValue)
276 {
277     ASSERT(width >= 1);
278
279     const int w1 = isFullWidthCharacter(data, width) ? 2 : 1;
280     const wchar_t c1 = data[0].Char.UnicodeChar;
281
282     if ((c1 & 0xF800) == 0xD800) {
283         // The first cell is either a leading or trailing surrogate pair.
284         if ((c1 & 0xFC00) != 0xD800 ||
285                 width <= w1 ||
286                 ((data[w1].Char.UnicodeChar & 0xFC00) != 0xDC00)) {
287             // Invalid surrogate pair
288             outCellCount = w1;
289             outCharValue = '?';
290         } else {
291             // Valid surrogate pair
292             outCellCount = w1 + (isFullWidthCharacter(&data[w1], width - w1) ? 2 : 1);
293             outCharValue = decodeSurrogatePair(c1, data[w1].Char.UnicodeChar);
294         }
295     } else {
296         outCellCount = w1;
297         outCharValue = c1;
298     }
299 }
300
301 } // anonymous namespace
302
303 void Terminal::reset(SendClearFlag sendClearFirst, int64_t newLine)
304 {
305     if (sendClearFirst == SendClear && !m_plainMode) {
306         // 0m   ==> reset SGR parameters
307         // 1;1H ==> move cursor to top-left position
308         // 2J   ==> clear the entire screen
309         m_output.write(CSI "0m" CSI "1;1H" CSI "2J");
310     }
311     m_remoteLine = newLine;
312     m_remoteColumn = 0;
313     m_lineData.clear();
314     m_cursorHidden = false;
315     m_remoteColor = -1;
316 }
317
318 void Terminal::sendLine(int64_t line, const CHAR_INFO *lineData, int width,
319                         int cursorColumn)
320 {
321     ASSERT(width >= 1);
322
323     moveTerminalToLine(line);
324
325     // If possible, see if we can append to what we've already output for this
326     // line.
327     if (m_lineDataValid) {
328         ASSERT(m_lineData.size() == static_cast<size_t>(m_remoteColumn));
329         if (m_remoteColumn > 0) {
330             // In normal mode, if m_lineData.size() equals `width`, then we
331             // will have trouble outputing the "erase rest of line" command,
332             // which must be output before reaching the end of the line.  In
333             // plain mode, we don't output that command, so we're OK with a
334             // full line.
335             bool okWidth = false;
336             if (m_plainMode) {
337                 okWidth = static_cast<size_t>(width) >= m_lineData.size();
338             } else {
339                 okWidth = static_cast<size_t>(width) > m_lineData.size();
340             }
341             if (!okWidth ||
342                     memcmp(m_lineData.data(), lineData,
343                            sizeof(CHAR_INFO) * m_lineData.size()) != 0) {
344                 m_lineDataValid = false;
345             }
346         }
347     }
348     if (!m_lineDataValid) {
349         // We can't reuse, so we must reset this line.
350         hideTerminalCursor();
351         if (m_plainMode) {
352             // We can't backtrack, so repeat this line.
353             m_output.write("\r\n");
354         } else {
355             m_output.write("\r");
356         }
357         m_lineDataValid = true;
358         m_lineData.clear();
359         m_remoteColumn = 0;
360     }
361
362     std::string &termLine = m_termLineWorkingBuffer;
363     termLine.clear();
364     size_t trimmedLineLength = 0;
365     int trimmedCellCount = m_lineData.size();
366     bool alreadyErasedLine = false;
367
368     int cellCount = 1;
369     for (int i = m_lineData.size(); i < width; i += cellCount) {
370         if (m_outputColor) {
371             int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK;
372             if (color != m_remoteColor) {
373                 outputSetColor(termLine, color);
374                 trimmedLineLength = termLine.size();
375                 m_remoteColor = color;
376
377                 // All the cells just up to this color change will be output.
378                 trimmedCellCount = i;
379             }
380         }
381         unsigned int ch;
382         scanUnicodeScalarValue(&lineData[i], width - i, cellCount, ch);
383         if (ch == ' ') {
384             // Tentatively add this space character.  We'll only output it if
385             // we see something interesting after it.
386             termLine.push_back(' ');
387         } else {
388             if (i + cellCount == width) {
389                 // We'd like to erase the line after outputting all non-blank
390                 // characters, but this doesn't work if the last cell in the
391                 // line is non-blank.  At the point, the cursor is positioned
392                 // just past the end of the line, but in many terminals,
393                 // issuing a CSI 0K at that point also erases the last cell in
394                 // the line.  Work around this behavior by issuing the erase
395                 // one character early in that case.
396                 if (!m_plainMode) {
397                     termLine.append(CSI "0K"); // Erase from cursor to EOL
398                 }
399                 alreadyErasedLine = true;
400             }
401             ch = fixSpecialCharacters(ch);
402             char enc[4];
403             int enclen = encodeUtf8(enc, ch);
404             if (enclen == 0) {
405                 enc[0] = '?';
406                 enclen = 1;
407             }
408             termLine.append(enc, enclen);
409             trimmedLineLength = termLine.size();
410
411             // All the cells up to and including this cell will be output.
412             trimmedCellCount = i + cellCount;
413         }
414     }
415
416     if (cursorColumn != -1 && trimmedCellCount > cursorColumn) {
417         // The line content would run past the cursor, so hide it before we
418         // output.
419         hideTerminalCursor();
420     }
421
422     m_output.write(termLine.data(), trimmedLineLength);
423     if (!alreadyErasedLine && !m_plainMode) {
424         m_output.write(CSI "0K"); // Erase from cursor to EOL
425     }
426
427     ASSERT(trimmedCellCount <= width);
428     m_lineData.insert(m_lineData.end(),
429                       &lineData[m_lineData.size()],
430                       &lineData[trimmedCellCount]);
431     m_remoteColumn = trimmedCellCount;
432 }
433
434 void Terminal::showTerminalCursor(int column, int64_t line)
435 {
436     moveTerminalToLine(line);
437     if (!m_plainMode) {
438         if (m_remoteColumn != column) {
439             char buffer[32];
440             winpty_snprintf(buffer, CSI "%dG", column + 1);
441             m_output.write(buffer);
442             m_lineDataValid = (column == 0);
443             m_lineData.clear();
444             m_remoteColumn = column;
445         }
446         if (m_cursorHidden) {
447             m_output.write(CSI "?25h");
448             m_cursorHidden = false;
449         }
450     }
451 }
452
453 void Terminal::hideTerminalCursor()
454 {
455     if (!m_plainMode) {
456         if (m_cursorHidden) {
457             return;
458         }
459         m_output.write(CSI "?25l");
460         m_cursorHidden = true;
461     }
462 }
463
464 void Terminal::moveTerminalToLine(int64_t line)
465 {
466     if (line == m_remoteLine) {
467         return;
468     }
469
470     // Do not use CPL or CNL.  Konsole 2.5.4 does not support Cursor Previous
471     // Line (CPL) -- there are "Undecodable sequence" errors.  gnome-terminal
472     // 2.32.0 does handle it.  Cursor Next Line (CNL) does nothing if the
473     // cursor is on the last line already.
474
475     hideTerminalCursor();
476
477     if (line < m_remoteLine) {
478         if (m_plainMode) {
479             // We can't backtrack, so instead repeat the lines again.
480             m_output.write("\r\n");
481             m_remoteLine = line;
482         } else {
483             // Backtrack and overwrite previous lines.
484             // CUrsor Up (CUU)
485             char buffer[32];
486             winpty_snprintf(buffer, "\r" CSI "%uA",
487                 static_cast<unsigned int>(m_remoteLine - line));
488             m_output.write(buffer);
489             m_remoteLine = line;
490         }
491     } else if (line > m_remoteLine) {
492         while (line > m_remoteLine) {
493             m_output.write("\r\n");
494             m_remoteLine++;
495         }
496     }
497
498     m_lineDataValid = true;
499     m_lineData.clear();
500     m_remoteColumn = 0;
501 }
502
503 void Terminal::enableMouseMode(bool enabled)
504 {
505     if (m_mouseModeEnabled == enabled || m_plainMode) {
506         return;
507     }
508     m_mouseModeEnabled = enabled;
509     if (enabled) {
510         // Start by disabling UTF-8 coordinate mode (1005), just in case we
511         // have a terminal that does not support 1006/1015 modes, and 1005
512         // happens to be enabled.  The UTF-8 coordinates can't be unambiguously
513         // decoded.
514         //
515         // Enable basic mouse support first (1000), then try to switch to
516         // button-move mode (1002), then try full mouse-move mode (1003).
517         // Terminals that don't support a mode will be stuck at the highest
518         // mode they do support.
519         //
520         // Enable encoding mode 1015 first, then try to switch to 1006.  On
521         // some terminals, both modes will be enabled, but 1006 will have
522         // priority.  On other terminals, 1006 wins because it's listed last.
523         //
524         // See misc/MouseInputNotes.txt for details.
525         m_output.write(
526             CSI "?1005l"
527             CSI "?1000h" CSI "?1002h" CSI "?1003h" CSI "?1015h" CSI "?1006h");
528     } else {
529         // Resetting both encoding modes (1006 and 1015) is necessary, but
530         // apparently we only need to use reset on one of the 100[023] modes.
531         // Doing both doesn't hurt.
532         m_output.write(
533             CSI "?1006l" CSI "?1015l" CSI "?1003l" CSI "?1002l" CSI "?1000l");
534     }
535 }