in src/agent/Scraper.cc [476:648]
bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
bool consoleCursorVisible,
bool tentative)
{
const Coord cursor = info.cursorPosition();
const SmallRect windowRect = info.windowRect();
if (m_syncRow != -1) {
// If a synchronizing marker was placed into the history, look for it
// and adjust the scroll count.
const int markerRow = findSyncMarker();
if (markerRow == -1) {
if (tentative) {
// I *think* it's possible to keep going, but it's simple to
// bail out.
return false;
}
// Something has happened. Reset the terminal.
trace("Sync marker has disappeared -- resetting the terminal"
" (m_syncCounter=%u)",
m_syncCounter);
resetConsoleTracking(Terminal::SendClear, windowRect.top());
} else if (markerRow != m_syncRow) {
ASSERT(markerRow < m_syncRow);
m_scrolledCount += (m_syncRow - markerRow);
m_syncRow = markerRow;
// If the buffer has scrolled, then the entire window is dirty.
markEntireWindowDirty(windowRect);
}
}
// Creating a new sync row requires clearing part of the console buffer, so
// avoid doing it if there's already a sync row that's good enough.
const int newSyncRow =
static_cast<int>(windowRect.top()) - SYNC_MARKER_LEN - SYNC_MARKER_MARGIN;
const bool shouldCreateSyncRow =
newSyncRow >= m_syncRow + SYNC_MARKER_LEN + SYNC_MARKER_MARGIN;
if (tentative && shouldCreateSyncRow) {
// It's difficult even in principle to put down a new marker if the
// console can scroll an arbitrarily amount while we're writing.
return false;
}
// Update the dirty line count:
// - If the window has moved, the entire window is dirty.
// - Everything up to the cursor is dirty.
// - All lines above the window are dirty.
// - Any non-blank lines are dirty.
if (m_dirtyWindowTop != -1) {
if (windowRect.top() > m_dirtyWindowTop) {
// The window has moved down, presumably as a result of scrolling.
markEntireWindowDirty(windowRect);
} else if (windowRect.top() < m_dirtyWindowTop) {
if (tentative) {
// I *think* it's possible to keep going, but it's simple to
// bail out.
return false;
}
// The window has moved upward. This is generally not expected to
// happen, but the CMD/PowerShell CLS command will move the window
// to the top as part of clearing everything else in the console.
trace("Window moved upward -- resetting the terminal"
" (m_syncCounter=%u)",
m_syncCounter);
resetConsoleTracking(Terminal::SendClear, windowRect.top());
}
}
m_dirtyWindowTop = windowRect.top();
m_dirtyLineCount = std::max(m_dirtyLineCount, cursor.Y + 1);
m_dirtyLineCount = std::max(m_dirtyLineCount, (int)windowRect.top());
// There will be at least one dirty line, because there is a cursor.
ASSERT(m_dirtyLineCount >= 1);
// The first line to scrape, in virtual line coordinates.
const int64_t firstVirtLine = std::min(m_scrapedLineCount,
windowRect.top() + m_scrolledCount);
// Read all the data we will need from the console. Start reading with the
// first line to scrape, but adjust the the read area upward to account for
// scanForDirtyLines' need to read the previous attribute. Read to the
// bottom of the window. (It's not clear to me whether the
// m_dirtyLineCount adjustment here is strictly necessary. It isn't
// necessary so long as the cursor is inside the current window.)
const int firstReadLine = std::min<int>(firstVirtLine - m_scrolledCount,
m_dirtyLineCount - 1);
const int stopReadLine = std::max(windowRect.top() + windowRect.height(),
m_dirtyLineCount);
ASSERT(firstReadLine >= 0 && stopReadLine > firstReadLine);
largeConsoleRead(m_readBuffer,
*m_consoleBuffer,
SmallRect(0, firstReadLine,
std::min<SHORT>(info.bufferSize().X,
MAX_CONSOLE_WIDTH),
stopReadLine - firstReadLine),
attributesMask());
// If we're scraping the buffer without freezing it, we have to query the
// buffer position data separately from the buffer content, so the two
// could easily be out-of-sync. If they *are* out-of-sync, abort the
// scrape operation and restart it frozen. (We may have updated the
// dirty-line high-water-mark, but that should be OK.)
if (tentative) {
const auto infoCheck = m_consoleBuffer->bufferInfo();
if (info.bufferSize() != infoCheck.bufferSize() ||
info.windowRect() != infoCheck.windowRect() ||
info.cursorPosition() != infoCheck.cursorPosition()) {
return false;
}
if (m_syncRow != -1 && m_syncRow != findSyncMarker()) {
return false;
}
}
if (shouldCreateSyncRow) {
ASSERT(!tentative);
createSyncMarker(newSyncRow);
}
// At this point, we're finished interacting (reading or writing) the
// console, and we just need to convert our collected data into terminal
// output.
scanForDirtyLines(windowRect);
// Note that it's possible for all the lines on the current window to
// be non-dirty.
// The line to stop scraping at, in virtual line coordinates.
const int64_t stopVirtLine =
std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) +
m_scrolledCount;
const bool showTerminalCursor =
consoleCursorVisible && windowRect.contains(cursor);
const int64_t cursorLine = !showTerminalCursor ? -1 : cursor.Y + m_scrolledCount;
const int cursorColumn = !showTerminalCursor ? -1 : cursor.X;
if (!showTerminalCursor) {
m_terminal->hideTerminalCursor();
}
bool sawModifiedLine = false;
const int w = m_readBuffer.rect().width();
for (int64_t line = firstVirtLine; line < stopVirtLine; ++line) {
const CHAR_INFO *curLine =
m_readBuffer.lineData(line - m_scrolledCount);
ConsoleLine &bufLine = m_bufferData[line % BUFFER_LINE_COUNT];
if (line > m_maxBufferedLine) {
m_maxBufferedLine = line;
sawModifiedLine = true;
}
if (sawModifiedLine) {
bufLine.setLine(curLine, w);
} else {
sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w);
}
if (sawModifiedLine) {
const int lineCursorColumn =
line == cursorLine ? cursorColumn : -1;
m_terminal->sendLine(line, curLine, w, lineCursorColumn);
}
}
m_scrapedLineCount = windowRect.top() + m_scrolledCount;
if (showTerminalCursor) {
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
}
return true;
}