153 lines
5.3 KiB
Plaintext
153 lines
5.3 KiB
Plaintext
// 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 <algorithm>
|
|
|
|
#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<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;
|
|
}
|