// 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. // #include "ConsoleLine.h" #include #include "../shared/WinptyAssert.h" 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(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(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(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; }