| // Copyright (c) 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. | |
| // | |
| // ConsoleLine | |
| // | |
| // This data structure keep tracks of the previous CHAR_INFO content of an | |
| // output line and determines when a line has changed. Detecting line changes | |
| // is made complicated by terminal resizing. | |
| // | |
| static CHAR_INFO blankChar(WORD attributes) | |
| { | |
| // N.B.: As long as we write to UnicodeChar rather than AsciiChar, there | |
| // are no padding bytes that could contain uninitialized bytes. This fact | |
| // is important for efficient comparison. | |
| CHAR_INFO ret; | |
| ret.Attributes = attributes; | |
| ret.Char.UnicodeChar = L' '; | |
| return ret; | |
| } | |
| static bool isLineBlank(const CHAR_INFO *line, int length, WORD attributes) | |
| { | |
| for (int col = 0; col < length; ++col) { | |
| if (line[col].Attributes != attributes || | |
| line[col].Char.UnicodeChar != L' ') { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| static inline bool areLinesEqual( | |
| const CHAR_INFO *line1, | |
| const CHAR_INFO *line2, | |
| int length) | |
| { | |
| return memcmp(line1, line2, sizeof(CHAR_INFO) * length) == 0; | |
| } | |
| ConsoleLine::ConsoleLine() : m_prevLength(0) | |
| { | |
| } | |
| void ConsoleLine::reset() | |
| { | |
| m_prevLength = 0; | |
| m_prevData.clear(); | |
| } | |
| // Determines whether the given line is sufficiently different from the | |
| // previously seen line as to justify reoutputting the line. The function | |
| // also sets the `ConsoleLine` to the given line, exactly as if `setLine` had | |
| // been called. | |
| bool ConsoleLine::detectChangeAndSetLine(const CHAR_INFO *const line, const int newLength) | |
| { | |
| ASSERT(newLength >= 1); | |
| ASSERT(m_prevLength <= static_cast<int>(m_prevData.size())); | |
| if (newLength == m_prevLength) { | |
| bool equalLines = areLinesEqual(m_prevData.data(), line, newLength); | |
| if (!equalLines) { | |
| setLine(line, newLength); | |
| } | |
| return !equalLines; | |
| } else { | |
| if (m_prevLength == 0) { | |
| setLine(line, newLength); | |
| return true; | |
| } | |
| ASSERT(m_prevLength >= 1); | |
| const WORD prevBlank = m_prevData[m_prevLength - 1].Attributes; | |
| const WORD newBlank = line[newLength - 1].Attributes; | |
| bool equalLines = false; | |
| if (newLength < m_prevLength) { | |
| // The line has become shorter. The lines are equal if the common | |
| // part is equal, and if the newly truncated characters were blank. | |
| equalLines = | |
| areLinesEqual(m_prevData.data(), line, newLength) && | |
| isLineBlank(m_prevData.data() + newLength, | |
| m_prevLength - newLength, | |
| newBlank); | |
| } else { | |
| // | |
| // The line has become longer. The lines are equal if the common | |
| // part is equal, and if both the extra characters and any | |
| // potentially reexposed characters are blank. | |
| // | |
| // Two of the most relevant terminals for winpty--mintty and | |
| // jediterm--don't (currently) erase the obscured content when a | |
| // line is cleared, so we should anticipate its existence when | |
| // making a terminal wider and reoutput the line. See: | |
| // | |
| // * https://github.com/mintty/mintty/issues/480 | |
| // * https://github.com/JetBrains/jediterm/issues/118 | |
| // | |
| ASSERT(newLength > m_prevLength); | |
| equalLines = | |
| areLinesEqual(m_prevData.data(), line, m_prevLength) && | |
| isLineBlank(m_prevData.data() + m_prevLength, | |
| std::min<int>(m_prevData.size(), newLength) - m_prevLength, | |
| prevBlank) && | |
| isLineBlank(line + m_prevLength, | |
| newLength - m_prevLength, | |
| prevBlank); | |
| } | |
| setLine(line, newLength); | |
| return !equalLines; | |
| } | |
| } | |
| void ConsoleLine::setLine(const CHAR_INFO *const line, const int newLength) | |
| { | |
| if (static_cast<int>(m_prevData.size()) < newLength) { | |
| m_prevData.resize(newLength); | |
| } | |
| memcpy(m_prevData.data(), line, sizeof(CHAR_INFO) * newLength); | |
| m_prevLength = newLength; | |
| } | |
| void ConsoleLine::blank(WORD attributes) | |
| { | |
| m_prevData.resize(1); | |
| m_prevData[0] = blankChar(attributes); | |
| m_prevLength = 1; | |
| } | |