mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into docs/registerMarker-tip
This commit is contained in:
+1
-1
@@ -95,7 +95,7 @@
|
||||
"jsdom": "^18.0.1",
|
||||
"mocha": "^10.1.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-pty": "1.1.0-beta19",
|
||||
"node-pty": "^1.1.0-beta31",
|
||||
"nyc": "^15.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
|
||||
@@ -532,7 +532,13 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this.textarea!.focus();
|
||||
this.textarea!.select();
|
||||
}));
|
||||
this._register(this._onScroll.event(() => this._selectionService!.refresh()));
|
||||
this._register(Event.any(
|
||||
this._onScroll.event,
|
||||
this._inputHandler.onScroll
|
||||
)(() => {
|
||||
this._selectionService!.refresh();
|
||||
this._viewport?.queueSync();
|
||||
}));
|
||||
|
||||
this._register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
|
||||
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
|
||||
|
||||
@@ -93,8 +93,8 @@ export class Viewport extends Disposable {
|
||||
].join('\n');
|
||||
}));
|
||||
|
||||
this._register(this._bufferService.onResize(() => this._queueSync()));
|
||||
this._register(this._bufferService.buffers.onBufferActivate(() => this._queueSync()));
|
||||
this._register(this._bufferService.onResize(() => this.queueSync()));
|
||||
this._register(this._bufferService.buffers.onBufferActivate(() => this.queueSync()));
|
||||
this._register(this._bufferService.onScroll(() => this._sync()));
|
||||
|
||||
this._register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
|
||||
@@ -126,7 +126,7 @@ export class Viewport extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
private _queueSync(ydisp?: number): void {
|
||||
public queueSync(ydisp?: number): void {
|
||||
// Update state
|
||||
if (ydisp !== undefined) {
|
||||
this._latestYDisp = ydisp;
|
||||
|
||||
@@ -438,6 +438,37 @@ describe('InputHandler', () => {
|
||||
inputHandler.eraseInLine(Params.fromArray([2]));
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
|
||||
});
|
||||
it('ED2 with scrollOnEraseInDisplay turned on', async () => {
|
||||
const inputHandler = new TestInputHandler(
|
||||
bufferService,
|
||||
new MockCharsetService(),
|
||||
new MockCoreService(),
|
||||
new MockLogService(),
|
||||
new MockOptionsService({ scrollOnEraseInDisplay: true }),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
const aLine = Array(bufferService.cols + 1).join('a');
|
||||
// add 2 full lines of text.
|
||||
await inputHandler.parseP(aLine);
|
||||
await inputHandler.parseP(aLine);
|
||||
|
||||
inputHandler.eraseInDisplay(Params.fromArray([2]));
|
||||
// those 2 lines should have been pushed to scrollback.
|
||||
assert.equal(bufferService.rows + 2, bufferService.buffer.lines.length);
|
||||
assert.equal(bufferService.buffer.ybase, 2);
|
||||
assert.equal(bufferService.buffer.lines.get(0)?.translateToString(), aLine);
|
||||
assert.equal(bufferService.buffer.lines.get(1)?.translateToString(), aLine);
|
||||
|
||||
// Move to last line and add more text.
|
||||
bufferService.buffer.y = bufferService.rows - 1;
|
||||
bufferService.buffer.x = 0;
|
||||
await inputHandler.parseP(aLine);
|
||||
inputHandler.eraseInDisplay(Params.fromArray([2]));
|
||||
// Screen should have been scrolled by a full screen size.
|
||||
assert.equal(bufferService.rows * 2 + 2, bufferService.buffer.lines.length);
|
||||
});
|
||||
it('eraseInDisplay', async () => {
|
||||
const bufferService = new MockBufferService(80, 7);
|
||||
const inputHandler = new TestInputHandler(
|
||||
|
||||
@@ -1220,12 +1220,27 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._dirtyRowTracker.markDirty(0);
|
||||
break;
|
||||
case 2:
|
||||
j = this._bufferService.rows;
|
||||
this._dirtyRowTracker.markDirty(j - 1);
|
||||
while (j--) {
|
||||
this._resetBufferLine(j, respectProtect);
|
||||
if (this._optionsService.rawOptions.scrollOnEraseInDisplay) {
|
||||
j = this._bufferService.rows;
|
||||
this._dirtyRowTracker.markRangeDirty(0, j - 1);
|
||||
while (j--) {
|
||||
const currentLine = this._activeBuffer.lines.get(this._activeBuffer.ybase + j);
|
||||
if (currentLine?.getTrimmedLength()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; j >= 0; j--) {
|
||||
this._bufferService.scroll(this._eraseAttrData());
|
||||
}
|
||||
}
|
||||
else {
|
||||
j = this._bufferService.rows;
|
||||
this._dirtyRowTracker.markDirty(j - 1);
|
||||
while (j--) {
|
||||
this._resetBufferLine(j, respectProtect);
|
||||
}
|
||||
this._dirtyRowTracker.markDirty(0);
|
||||
}
|
||||
this._dirtyRowTracker.markDirty(0);
|
||||
break;
|
||||
case 3:
|
||||
// Clear scrollback (everything not in viewport)
|
||||
|
||||
@@ -320,7 +320,37 @@ export class Buffer implements IBuffer {
|
||||
if (toRemove.length > 0) {
|
||||
const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);
|
||||
reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);
|
||||
|
||||
// For conpty, it has its own copy of the buffer _without scrollback_ internally. Its behavior
|
||||
// when reflowing larger is to insert empty lines at the bottom of the buffer as when lines
|
||||
// unwrap conpty's view cannot pull scrollback down, so it adds empty lines at the end.
|
||||
let removedInViewport = 0;
|
||||
const isWindowsMode = this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined;
|
||||
if (isWindowsMode) {
|
||||
for (let i = (toRemove.length / 2) - 1; i >= 0; i--) {
|
||||
if (toRemove[i * 2 + 0] > this.ybase + removedInViewport) {
|
||||
removedInViewport += toRemove[i * 2 + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);
|
||||
|
||||
// Apply empty lines for any removed in viewport for conpty.
|
||||
if (isWindowsMode) {
|
||||
if (removedInViewport > 0) {
|
||||
for (let i = 0; i < removedInViewport; i++) {
|
||||
// Just add the new missing rows on Windows as conpty reprints the screen with it's
|
||||
// view of the world. Once a line enters scrollback for conpty it remains there
|
||||
this.lines.push(new BufferLine(newCols, this.getNullCell(DEFAULT_ATTR_DATA)));
|
||||
}
|
||||
if (this.ybase === this.ydisp) {
|
||||
this.ydisp += removedInViewport;
|
||||
}
|
||||
this.ybase += removedInViewport;
|
||||
this.y -= removedInViewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +382,7 @@ export class Buffer implements IBuffer {
|
||||
const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
|
||||
// Gather all BufferLines that need to be inserted into the Buffer here so that they can be
|
||||
// batched up and only committed once
|
||||
const toInsert = [];
|
||||
const toInsert: { start: number, newLines: IBufferLine[] }[] = [];
|
||||
let countToInsert = 0;
|
||||
// Go backwards as many lines may be trimmed and this will avoid considering them
|
||||
for (let y = this.lines.length - 1; y >= 0; y--) {
|
||||
@@ -467,6 +497,20 @@ export class Buffer implements IBuffer {
|
||||
this.savedY = Math.min(this.savedY + linesToAdd, this.ybase + newRows - 1);
|
||||
}
|
||||
|
||||
// For conpty, it has its own copy of the buffer _without scrollback_ internally. Its behavior
|
||||
// when reflowing smaller is to reflow all lines inside the viewport, and removing empty or
|
||||
// whitespace only lines from the bottom, until non-whitespace is hit in order to prevent
|
||||
// content from being pushed into the scrollback.
|
||||
let addedInViewport = 0;
|
||||
const isWindowsMode = this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined;
|
||||
if (isWindowsMode) {
|
||||
for (let i = toInsert.length - 1; i >= 0; i--) {
|
||||
if (toInsert[i].start > this.ybase + addedInViewport) {
|
||||
addedInViewport += toInsert[i].newLines.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rearrange lines in the buffer if there are any insertions, this is done at the end rather
|
||||
// than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
|
||||
// costly calls to CircularList.splice.
|
||||
@@ -520,6 +564,35 @@ export class Buffer implements IBuffer {
|
||||
this.lines.onTrimEmitter.fire(amountToTrim);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply empty lines to remove calculated earlier for conpty.
|
||||
if (isWindowsMode) {
|
||||
if (addedInViewport > 0) {
|
||||
let emptyLinesAtBottom = 0;
|
||||
for (let i = this.lines.length - 1; i >= this.ybase + this.y; i--) {
|
||||
const line = this.lines.get(i) as BufferLine;
|
||||
if (line.isWrapped || line.getTrimmedLength() > 0) {
|
||||
break;
|
||||
}
|
||||
emptyLinesAtBottom++;
|
||||
}
|
||||
const emptyLinesToRemove = Math.min(addedInViewport, emptyLinesAtBottom);
|
||||
if (emptyLinesToRemove > 0) {
|
||||
for (let i = 0; i < emptyLinesToRemove; i++) {
|
||||
this.lines.pop();
|
||||
}
|
||||
if (this.ybase === this.ydisp) {
|
||||
this.ydisp -= emptyLinesToRemove;
|
||||
}
|
||||
this.ybase -= emptyLinesToRemove;
|
||||
this.y += emptyLinesToRemove;
|
||||
this.lines.onDeleteEmitter.fire({
|
||||
index: this.lines.length - emptyLinesToRemove,
|
||||
amount: emptyLinesToRemove
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
logLevel: 'info',
|
||||
logger: null,
|
||||
scrollback: 1000,
|
||||
scrollOnEraseInDisplay: false,
|
||||
scrollOnUserInput: true,
|
||||
scrollSensitivity: 1,
|
||||
screenReaderMode: false,
|
||||
|
||||
@@ -254,6 +254,7 @@ export interface ITerminalOptions {
|
||||
windowOptions?: IWindowOptions;
|
||||
wordSeparator?: string;
|
||||
overviewRuler?: IOverviewRulerOptions;
|
||||
scrollOnEraseInDisplay?: boolean;
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
|
||||
Vendored
+7
@@ -186,6 +186,13 @@ declare module '@xterm/headless' {
|
||||
*/
|
||||
scrollback?: number;
|
||||
|
||||
/**
|
||||
* If enabled the Erase in Display All (ED2) escape sequence will push
|
||||
* erased text to scrollback, instead of clearing only the viewport portion.
|
||||
* This emulates PuTTY's default clear screen behavior.
|
||||
*/
|
||||
scrollOnEraseInDisplay?: boolean;
|
||||
|
||||
/**
|
||||
* The scrolling speed multiplier used for adjusting normal scrolling speed.
|
||||
*/
|
||||
|
||||
Vendored
+7
@@ -257,6 +257,13 @@ declare module '@xterm/xterm' {
|
||||
*/
|
||||
scrollback?: number;
|
||||
|
||||
/**
|
||||
* If enabled the Erase in Display All (ED2) escape sequence will push
|
||||
* erased text to scrollback, instead of clearing only the viewport portion.
|
||||
* This emulates PuTTY's default clear screen behavior.
|
||||
*/
|
||||
scrollOnEraseInDisplay?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to scroll to the bottom whenever there is some user input. The
|
||||
* default is true.
|
||||
|
||||
@@ -3296,10 +3296,10 @@ node-preload@^0.2.1:
|
||||
dependencies:
|
||||
process-on-spawn "^1.0.0"
|
||||
|
||||
node-pty@1.1.0-beta19:
|
||||
version "1.1.0-beta19"
|
||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta19.tgz#a74dc04429903c5ac49ee81a15a24590da67d4f3"
|
||||
integrity sha512-/p4Zu56EYDdXjjaLWzrIlFyrBnND11LQGP0/L6GEVGURfCNkAlHc3Twg/2I4NPxghimHXgvDlwp7Z2GtvDIh8A==
|
||||
node-pty@^1.1.0-beta31:
|
||||
version "1.1.0-beta9"
|
||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d"
|
||||
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==
|
||||
dependencies:
|
||||
node-addon-api "^7.1.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user