Compare commits

...
Author SHA1 Message Date
Cursor AgentandDaniel Imms e331541fd1 Align browser registerMarker alt-buffer guard with headless
Block registerMarker on the alternate buffer in CoreBrowserTerminal,
matching headless addMarker behavior. Update public typings to
IMarker | undefined and add regression tests.

Fixes #5937

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:22:33 +00:00
8 changed files with 46 additions and 15 deletions
@@ -131,6 +131,9 @@ export class DecorationManager extends Disposable {
const decorations: IDecoration[] = [];
for (const range of decorationRanges) {
const marker = this._terminal.registerMarker(range[0]);
if (!marker) {
continue;
}
const decoration = this._terminal.registerDecoration({
marker,
x: range[1],
+10 -10
View File
@@ -753,7 +753,7 @@ async function loadTestLongLines(term: Terminal, addons: AddonCollection): Promi
function addDecoration(term: Terminal, dim: number = 1): void {
term.options.scrollbar = { ...(term.options.scrollbar ?? {}), width: 14, overviewRuler: term.options.scrollbar?.overviewRuler ?? {} };
const marker = term.registerMarker(1);
const marker = term.registerMarker(1)!;
const decoration = term.registerDecoration({
marker,
height: dim,
@@ -770,14 +770,14 @@ function addDecoration(term: Terminal, dim: number = 1): void {
function addOverviewRuler(term: Terminal): void {
term.options.scrollbar = { ...(term.options.scrollbar ?? {}), width: 14, overviewRuler: term.options.scrollbar?.overviewRuler ?? {} };
term.registerDecoration({ marker: term.registerMarker(1), overviewRulerOptions: { color: '#ef2929' } });
term.registerDecoration({ marker: term.registerMarker(3), overviewRulerOptions: { color: '#8ae234' } });
term.registerDecoration({ marker: term.registerMarker(5), overviewRulerOptions: { color: '#729fcf' } });
term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' } });
term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' } });
term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' } });
term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' } });
term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' } });
term.registerDecoration({ marker: term.registerMarker(1)!, overviewRulerOptions: { color: '#ef2929' } });
term.registerDecoration({ marker: term.registerMarker(3)!, overviewRulerOptions: { color: '#8ae234' } });
term.registerDecoration({ marker: term.registerMarker(5)!, overviewRulerOptions: { color: '#729fcf' } });
term.registerDecoration({ marker: term.registerMarker(7)!, overviewRulerOptions: { color: '#ef2929', position: 'left' } });
term.registerDecoration({ marker: term.registerMarker(7)!, overviewRulerOptions: { color: '#8ae234', position: 'center' } });
term.registerDecoration({ marker: term.registerMarker(7)!, overviewRulerOptions: { color: '#729fcf', position: 'right' } });
term.registerDecoration({ marker: term.registerMarker(10)!, overviewRulerOptions: { color: '#8ae234', position: 'center' } });
term.registerDecoration({ marker: term.registerMarker(10)!, overviewRulerOptions: { color: '#ffffff80', position: 'full' } });
}
let decorationStressTestDecorations: IDisposable[] | undefined;
@@ -795,7 +795,7 @@ function decorationStressTest(term: Terminal): void {
for (let y = 0; y < term.buffer.active.length; y++) {
const cursorOffsetY = y - cursorY;
const decoration = term.registerDecoration({
marker: term.registerMarker(cursorOffsetY),
marker: term.registerMarker(cursorOffsetY)!,
x,
width: 4,
backgroundColor: '#FF0000',
+6 -1
View File
@@ -768,7 +768,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
return this.buffer.markers;
}
public registerMarker(cursorYOffset: number): IMarker {
public registerMarker(cursorYOffset: number): IMarker | undefined {
// Disallow markers on the alt buffer
if (this.buffer !== this.buffers.normal) {
return;
}
return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
}
+10
View File
@@ -1146,6 +1146,16 @@ describe('Terminal', () => {
assert.equal(termConverting.buffer.lines.get(1)!.translateToString(true), 'World');
});
describe('registerMarker', () => {
it('should return undefined on the alt buffer', async () => {
assert.isDefined(term.registerMarker(0));
await term.writeP('\x1b[?47h');
assert.isUndefined(term.registerMarker(0));
await term.writeP('\x1b[?47l');
assert.isDefined(term.registerMarker(0));
});
});
// FIXME: move to common/CoreTerminal.test once the trimming is moved over
describe('marker lifecycle', () => {
// create a 10x5 terminal with markers on every line
+1 -1
View File
@@ -166,7 +166,7 @@ export class Terminal extends Disposable implements ITerminalApi {
public deregisterCharacterJoiner(joinerId: number): void {
this._core.deregisterCharacterJoiner(joinerId);
}
public registerMarker(cursorYOffset: number = 0): IMarker {
public registerMarker(cursorYOffset: number = 0): IMarker | undefined {
this._verifyIntegers(cursorYOffset);
return this._core.registerMarker(cursorYOffset);
}
+12
View File
@@ -419,6 +419,18 @@ describe('Headless API Tests', function (): void {
strictEqual(term.buffer.normal.getLine(0)!.translateToString(), 'norm ');
strictEqual(term.buffer.alternate.getLine(0), undefined);
});
it('registerMarker returns undefined on the alt buffer', async () => {
term = new Terminal({ cols: 5, allowProposedApi: true });
strictEqual(term.buffer.active.type, 'normal');
strictEqual(term.registerMarker(0)!.line, 0);
await writeSync('\x1b[?47h');
strictEqual(term.buffer.active.type, 'alternate');
strictEqual(term.registerMarker(0), undefined);
await writeSync('\x1b[?47l');
strictEqual(term.buffer.active.type, 'normal');
strictEqual(term.registerMarker(0)!.line, 0);
});
});
describe('modes', () => {
+1 -1
View File
@@ -284,7 +284,7 @@ export class TerminalProxy implements ITerminalProxyCustomMethods, PlaywrightApi
}
public async input(data: string, wasUserInput: boolean = true): Promise<void> { return this.evaluate(([term]) => term.input(data, wasUserInput)); }
public async resize(cols: number, rows: number): Promise<void> { return this._page.evaluate(([term, cols, rows]) => term.resize(cols, rows), [await this.getHandle(), cols, rows] as const); }
public async registerMarker(y?: number | undefined): Promise<IMarker> { return this._page.evaluate(([term, y]) => term.registerMarker(y), [await this.getHandle(), y] as const); }
public async registerMarker(y?: number | undefined): Promise<IMarker | undefined> { return this._page.evaluate(([term, y]) => term.registerMarker(y), [await this.getHandle(), y] as const); }
public async registerDecoration(decorationOptions: IDecorationOptions): Promise<IDecoration | undefined> { return this._page.evaluate(([term, decorationOptions]) => term.registerDecoration(decorationOptions), [await this.getHandle(), decorationOptions] as const); }
public async clearTextureAtlas(): Promise<void> { return this.evaluate(([term]) => term.clearTextureAtlas()); }
// #endregion
+3 -2
View File
@@ -1257,11 +1257,12 @@ declare module '@xterm/xterm' {
deregisterCharacterJoiner(joinerId: number): void;
/**
* Adds a marker to the normal buffer and returns it.
* Adds a marker to the normal buffer and returns it. If the alt buffer is
* active, undefined is returned.
* @param cursorYOffset The y position offset of the marker from the cursor.
* @returns The new marker or undefined.
*/
registerMarker(cursorYOffset?: number): IMarker;
registerMarker(cursorYOffset?: number): IMarker | undefined;
/**
* Registers a decoration to the terminal.