diff --git a/addons/addon-serialize/src/SerializeAddon.test.ts b/addons/addon-serialize/src/SerializeAddon.test.ts index ac21cc63..b0718ad3 100644 --- a/addons/addon-serialize/src/SerializeAddon.test.ts +++ b/addons/addon-serialize/src/SerializeAddon.test.ts @@ -153,6 +153,43 @@ describe('SerializeAddon', () => { assert.ok(result.includes('58:5:46'), result); }); }); + + describe('scroll region', () => { + let scrollTerminal: Terminal; + let scrollAddon: SerializeAddon; + + beforeEach(() => { + scrollTerminal = new Terminal({ cols: 10, rows: 5, allowProposedApi: true }); + scrollAddon = new SerializeAddon(); + scrollTerminal.loadAddon(scrollAddon); + }); + + it('should serialize scroll region when margins are set', async () => { + await writeP(scrollTerminal, '\x1b[2;4r'); + const buffer = (scrollTerminal as any)._core.buffer; + assert.equal(buffer.scrollTop, 1, 'scrollTop should be 1'); + assert.equal(buffer.scrollBottom, 3, 'scrollBottom should be 3'); + const result = scrollAddon.serialize(); + assert.ok(result.includes('\x1b[2;4r'), result); + }); + + it('should not serialize scroll region when excludeModes is true', async () => { + await writeP(scrollTerminal, '\x1b[2;4r'); + const result = scrollAddon.serialize({ excludeModes: true }); + assert.ok(!result.includes('\x1b[2;4r'), result); + }); + + it('should restore scroll region correctly when deserialized', async () => { + await writeP(scrollTerminal, '\x1b[2;4r'); + const serialized = scrollAddon.serialize(); + const terminal2 = new Terminal({ cols: 10, rows: 5, allowProposedApi: true }); + terminal2.loadAddon(new SerializeAddon()); + await writeP(terminal2, serialized); + const buffer = (terminal2 as any)._core.buffer; + assert.equal(buffer.scrollTop, 1); + assert.equal(buffer.scrollBottom, 3); + }); + }); }); describe('html', () => { diff --git a/addons/addon-serialize/src/SerializeAddon.ts b/addons/addon-serialize/src/SerializeAddon.ts index 0f37d34e..35baeab7 100644 --- a/addons/addon-serialize/src/SerializeAddon.ts +++ b/addons/addon-serialize/src/SerializeAddon.ts @@ -513,6 +513,25 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi { return ''; } + /** + * Serializes the scroll region (DECSTBM) if it's not set to the full terminal size. + * Uses internal API access since scroll region is not exposed in the public API. + */ + private _serializeScrollRegion(terminal: Terminal): string { + // HACK: Internal API access since scroll region is not exposed in the public API + const buffer = (terminal as any)._core.buffer; + const scrollTop: number = buffer.scrollTop; + const scrollBottom: number = buffer.scrollBottom; + + // Only serialize if scroll region is not the default (full terminal size) + if (scrollTop !== 0 || scrollBottom !== terminal.rows - 1) { + // DECSTBM uses 1-based indices: CSI Ps ; Ps r + return `\x1b[${scrollTop + 1};${scrollBottom + 1}r`; + } + + return ''; + } + private _serializeModes(terminal: Terminal): string { let content = ''; const modes = terminal.modes; @@ -562,9 +581,10 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi { } } - // Modes + // Modes and scroll region if (!options?.excludeModes) { content += this._serializeModes(this._terminal); + content += this._serializeScrollRegion(this._terminal); } return content;