Change:
Testing So I was testing the terminal sticky scroll and I noticed that the sticky code block disappears in an abrupt manner when the beginning (not the end) of the multiline terminal command, reaches the end of the scope. To give more context about what I mean, I the GIF below. I was sort of expecting the code block to start disappearing when the line reached the end of the output for that command. I noticed that the code block disappeared however when the line reached the end of the output for that run. I don't think this is a problem per say, it's a different manner of animating sticky scroll. I was just wondering if this is intended? I think the potential issue with this approach is that the sticky scroll block is hiding the next terminal command and its output while you are scrolling until it disappears. In the editor, sticky scroll starts disappearing progressively when the bottom of the last block/line touches the end of the scope. Perhaps we could explore something similar?
This is actually a bug, it's meant to scroll up as in the editor and was doing that at some point. Note that it's not as smooth as the editor since there's no "partial line scrolling" in the terminal
Repro that demonstrates more clearly what's happening: Run: Run: Run: when scrolling, sticky scroll from a command should never obscure the command below it like this: !
I see the prompt line sort of being obscured here

Discussion:
import { importAMDNodeModule } from 'vs/amdX'; import { $, addDisposableListener, addStandardDisposableListener, getWindow } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; <del> import { debounce, memoize, throttle } from 'vs/base/common/decorators'; </del> <ins> import { memoize, throttle } from 'vs/base/common/decorators'; </ins> import { Event } from 'vs/base/common/event'; import { Disposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { removeAnsiEscapeCodes } from 'vs/base/common/strings';

if (command && this._currentStickyCommand !== command) { this._throttledRefresh(); } else { <del> this._debouncedRefresh(); </del> <ins> // If it's the same command, do not throttle as the sticky scroll overlay height may // need to be adjusted. This would cause a flicker if throttled. this._refreshNow(); </ins> } } <del> @debounce(20) private _debouncedRefresh(): void { this._throttledRefresh(); } </del> @throttle(0) private _throttledRefresh(): void { <ins> this._refreshNow(); } private _refreshNow(): void { </ins> const command = this._commandDetection.getCommandForLine(this._xterm.raw.buffer.active.viewportY); // The command from viewportY + 1 is used because this one will not be obscured by sticky

} } <del> // Clear attrs, reset cursor position, clear right </del> <ins> // Get the line content of the command from the terminal </ins> const content = this._serializeAddon.serialize({ range: { start: stickyScrollLineStart + rowOffset,

// Write content if it differs if (content && this._currentContent !== content) { this._stickyScrollOverlay.resize(this._stickyScrollOverlay.cols, stickyScrollLineCount); <ins> // Clear attrs, reset cursor position, clear right </ins> this._stickyScrollOverlay.write('x1b[0mx1b[Hx1b[2J'); this._stickyScrollOverlay.write(content); this._currentContent = content;

const termBox = xterm.element.getBoundingClientRect(); const rowHeight = termBox.height / xterm.rows; const overlayHeight = stickyScrollLineCount * rowHeight; <del> this._element.style.bottom = `${termBox.height - overlayHeight + 1}px`; </del> <ins> // Adjust sticky scroll content if it would below the end of the command, obscuring the // following command. let endMarkerOffset = 0; if (!isPartialCommand && command.endMarker && command.endMarker.line !== -1) { if (buffer.viewportY + stickyScrollLineCount > command.endMarker.line) { const diff = buffer.viewportY + stickyScrollLineCount - command.endMarker.line; endMarkerOffset = diff * rowHeight; } } this._element.style.bottom = `${termBox.height - overlayHeight + 1 + endMarkerOffset}px`; </ins> } } else { this._setVisible(false);