installed pty
[VSoRC/.git] / node_modules / node-pty / deps / winpty / src / agent / ConsoleLine.cc
1 // Copyright (c) 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 //
22 // ConsoleLine
23 //
24 // This data structure keep tracks of the previous CHAR_INFO content of an
25 // output line and determines when a line has changed.  Detecting line changes
26 // is made complicated by terminal resizing.
27 //
28
29 #include "ConsoleLine.h"
30
31 #include <algorithm>
32
33 #include "../shared/WinptyAssert.h"
34
35 static CHAR_INFO blankChar(WORD attributes)
36 {
37     // N.B.: As long as we write to UnicodeChar rather than AsciiChar, there
38     // are no padding bytes that could contain uninitialized bytes.  This fact
39     // is important for efficient comparison.
40     CHAR_INFO ret;
41     ret.Attributes = attributes;
42     ret.Char.UnicodeChar = L' ';
43     return ret;
44 }
45
46 static bool isLineBlank(const CHAR_INFO *line, int length, WORD attributes)
47 {
48     for (int col = 0; col < length; ++col) {
49         if (line[col].Attributes != attributes ||
50                 line[col].Char.UnicodeChar != L' ') {
51             return false;
52         }
53     }
54     return true;
55 }
56
57 static inline bool areLinesEqual(
58     const CHAR_INFO *line1,
59     const CHAR_INFO *line2,
60     int length)
61 {
62     return memcmp(line1, line2, sizeof(CHAR_INFO) * length) == 0;
63 }
64
65 ConsoleLine::ConsoleLine() : m_prevLength(0)
66 {
67 }
68
69 void ConsoleLine::reset()
70 {
71     m_prevLength = 0;
72     m_prevData.clear();
73 }
74
75 // Determines whether the given line is sufficiently different from the
76 // previously seen line as to justify reoutputting the line.  The function
77 // also sets the `ConsoleLine` to the given line, exactly as if `setLine` had
78 // been called.
79 bool ConsoleLine::detectChangeAndSetLine(const CHAR_INFO *const line, const int newLength)
80 {
81     ASSERT(newLength >= 1);
82     ASSERT(m_prevLength <= static_cast<int>(m_prevData.size()));
83
84     if (newLength == m_prevLength) {
85         bool equalLines = areLinesEqual(m_prevData.data(), line, newLength);
86         if (!equalLines) {
87             setLine(line, newLength);
88         }
89         return !equalLines;
90     } else {
91         if (m_prevLength == 0) {
92             setLine(line, newLength);
93             return true;
94         }
95
96         ASSERT(m_prevLength >= 1);
97         const WORD prevBlank = m_prevData[m_prevLength - 1].Attributes;
98         const WORD newBlank = line[newLength - 1].Attributes;
99
100         bool equalLines = false;
101         if (newLength < m_prevLength) {
102             // The line has become shorter.  The lines are equal if the common
103             // part is equal, and if the newly truncated characters were blank.
104             equalLines =
105                 areLinesEqual(m_prevData.data(), line, newLength) &&
106                 isLineBlank(m_prevData.data() + newLength,
107                             m_prevLength - newLength,
108                             newBlank);
109         } else {
110             //
111             // The line has become longer.  The lines are equal if the common
112             // part is equal, and if both the extra characters and any
113             // potentially reexposed characters are blank.
114             //
115             // Two of the most relevant terminals for winpty--mintty and
116             // jediterm--don't (currently) erase the obscured content when a
117             // line is cleared, so we should anticipate its existence when
118             // making a terminal wider and reoutput the line.  See:
119             //
120             //  * https://github.com/mintty/mintty/issues/480
121             //  * https://github.com/JetBrains/jediterm/issues/118
122             //
123             ASSERT(newLength > m_prevLength);
124             equalLines =
125                 areLinesEqual(m_prevData.data(), line, m_prevLength) &&
126                 isLineBlank(m_prevData.data() + m_prevLength,
127                             std::min<int>(m_prevData.size(), newLength) - m_prevLength,
128                             prevBlank) &&
129                 isLineBlank(line + m_prevLength,
130                             newLength - m_prevLength,
131                             prevBlank);
132         }
133         setLine(line, newLength);
134         return !equalLines;
135     }
136 }
137
138 void ConsoleLine::setLine(const CHAR_INFO *const line, const int newLength)
139 {
140     if (static_cast<int>(m_prevData.size()) < newLength) {
141         m_prevData.resize(newLength);
142     }
143     memcpy(m_prevData.data(), line, sizeof(CHAR_INFO) * newLength);
144     m_prevLength = newLength;
145 }
146
147 void ConsoleLine::blank(WORD attributes)
148 {
149     m_prevData.resize(1);
150     m_prevData[0] = blankChar(attributes);
151     m_prevLength = 1;
152 }