mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-06 14:30:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed884c82d9 | ||
|
|
b7d4e73815 |
No files matched your search
@@ -33,7 +33,7 @@ const createFakeTerm = (cols = 80) => {
|
||||
onRender: (handler: (element: FakeElement) => void) => { dispose: () => void };
|
||||
onDispose: (handler: Handler) => { dispose: () => void };
|
||||
}> = [];
|
||||
const markers: Array<{ line: number; disposed: boolean }> = [];
|
||||
const markers: Array<{ line: number; disposed: boolean; dispose: () => void }> = [];
|
||||
|
||||
const term = {
|
||||
cols,
|
||||
@@ -477,6 +477,84 @@ test('CursorLineHighlighter restores after the terminal resets its decorations',
|
||||
highlighter.dispose();
|
||||
});
|
||||
|
||||
test('CursorLineHighlighter self-heals when xterm disposes its decorations without a write', () => {
|
||||
const term = createFakeTerm(80);
|
||||
const highlighter = new CursorLineHighlighter(term as never);
|
||||
highlighter.setEnabled(true);
|
||||
assert.equal(term.decorations.at(-1)?.disposed, false);
|
||||
|
||||
// xterm-side disposal (decoration service reset / term.reset): no cursor
|
||||
// move and no write follow, only the next render frame.
|
||||
term.resetDecorations();
|
||||
assert.equal(term.decorations[0]?.disposed, true);
|
||||
|
||||
term.render();
|
||||
assert.equal(term.decorations.at(-1)?.disposed, false);
|
||||
assert.equal(term.decorations.at(-1)?.options.width, 80);
|
||||
highlighter.dispose();
|
||||
});
|
||||
|
||||
test('CursorLineHighlighter recreates after a buffer clear wipes markers and decorations', () => {
|
||||
const term = createFakeTerm(80);
|
||||
const highlighter = new CursorLineHighlighter(term as never);
|
||||
highlighter.setEnabled(true);
|
||||
assert.equal(term.markers.at(-1)?.disposed, false);
|
||||
|
||||
// term.clear() disposes every marker; decorations follow their markers.
|
||||
for (const marker of term.markers) marker.dispose();
|
||||
term.resetDecorations();
|
||||
assert.equal(term.decorations.at(-1)?.disposed, true);
|
||||
|
||||
term.render();
|
||||
assert.equal(term.decorations.at(-1)?.disposed, false);
|
||||
assert.equal(term.markers.at(-1)?.line, 0);
|
||||
highlighter.dispose();
|
||||
});
|
||||
|
||||
test('CursorLineHighlighter does not duplicate decorations on redundant renders', () => {
|
||||
const term = createFakeTerm(80);
|
||||
const highlighter = new CursorLineHighlighter(term as never);
|
||||
highlighter.setEnabled(true);
|
||||
const aliveBefore = term.decorations.filter((decoration) => !decoration.disposed).length;
|
||||
|
||||
term.render();
|
||||
term.render();
|
||||
assert.equal(
|
||||
term.decorations.filter((decoration) => !decoration.disposed).length,
|
||||
aliveBefore,
|
||||
);
|
||||
highlighter.dispose();
|
||||
});
|
||||
|
||||
test('CursorLineHighlighter does not churn markers when a fully colored row has no decorations', () => {
|
||||
const cols = 8;
|
||||
const term = createFakeTerm(cols);
|
||||
// Every cell has an explicit ANSI background and the row is full, so both
|
||||
// highlight ranges and the blank tail are legitimately empty.
|
||||
term.setColoredBackgrounds(Array.from({ length: cols }, (_, column) => column));
|
||||
const highlighter = new CursorLineHighlighter(term as never);
|
||||
highlighter.setEnabled(true);
|
||||
|
||||
assert.equal(term.decorations.length, 0);
|
||||
assert.equal(term.markers.length, 1);
|
||||
const marker = term.markers[0];
|
||||
assert.ok(marker);
|
||||
assert.equal(marker.disposed, false);
|
||||
assert.equal(marker.line, 0);
|
||||
const aliveBefore = term.markers.filter((candidate) => !candidate.disposed).length;
|
||||
|
||||
term.render();
|
||||
term.render();
|
||||
term.render();
|
||||
|
||||
assert.equal(term.markers.length, 1);
|
||||
assert.equal(term.markers.filter((candidate) => !candidate.disposed).length, aliveBefore);
|
||||
assert.equal(marker.disposed, false);
|
||||
assert.equal(marker.line, 0);
|
||||
assert.equal(term.decorations.length, 0);
|
||||
highlighter.dispose();
|
||||
});
|
||||
|
||||
test('CursorLineHighlighter clears in the alternate buffer and restores in normal buffer', () => {
|
||||
const term = createFakeTerm(80);
|
||||
const highlighter = new CursorLineHighlighter(term as never);
|
||||
|
||||
@@ -37,6 +37,8 @@ export class CursorLineHighlighter implements IDisposable {
|
||||
private activeColor: string | null = null;
|
||||
private activeRanges: HighlightRange[] = [];
|
||||
private activeTailRanges: HighlightRange[] = [];
|
||||
/** False once any tracked decoration has been disposed out from under us. */
|
||||
private decorationsAlive = false;
|
||||
private readonly disposables: IDisposable[] = [];
|
||||
private pendingRefresh = false;
|
||||
private disposed = false;
|
||||
@@ -46,10 +48,16 @@ export class CursorLineHighlighter implements IDisposable {
|
||||
this.term.onCursorMove(() => this.markPendingRefresh()),
|
||||
this.term.onWriteParsed(() => this.markPendingRefresh()),
|
||||
this.term.onRender(() => {
|
||||
if (this.pendingRefresh) {
|
||||
this.pendingRefresh = false;
|
||||
this.refresh();
|
||||
}
|
||||
const pending = this.pendingRefresh;
|
||||
this.pendingRefresh = false;
|
||||
if (this.disposed || !this.enabled) return;
|
||||
// Writes and cursor moves defer to render, but xterm can also dispose
|
||||
// or clear our decorations out from under us (term.clear/reset,
|
||||
// decoration service resets, marker reindexing) without any further
|
||||
// cursor move or write. Verify the tracked set on every render so the
|
||||
// highlight can self-heal at the cursor line instead of staying
|
||||
// anchored to stale bookkeeping (wrong/missing line).
|
||||
if (pending || !this.isTrackingIntact()) this.refresh();
|
||||
}),
|
||||
this.term.onSelectionChange(() => this.refresh({ force: true })),
|
||||
this.term.onResize(() => this.refresh({ force: true })),
|
||||
@@ -111,6 +119,7 @@ export class CursorLineHighlighter implements IDisposable {
|
||||
color === this.activeColor &&
|
||||
rangesEqual(ranges, this.activeRanges) &&
|
||||
rangesEqual(tailRanges, this.activeTailRanges) &&
|
||||
this.decorationsAlive &&
|
||||
this.marker &&
|
||||
!this.marker.isDisposed &&
|
||||
this.marker.line === absoluteLine
|
||||
@@ -160,13 +169,18 @@ export class CursorLineHighlighter implements IDisposable {
|
||||
|
||||
this.marker = marker;
|
||||
this.decorations = decorations;
|
||||
// An empty set is valid when the whole row already has ANSI backgrounds or
|
||||
// inverse cells. Tracking stays intact so later frames do not churn markers.
|
||||
this.decorationsAlive = true;
|
||||
this.decorationDisposeListeners = decorations.map((decoration) =>
|
||||
decoration.onDispose(() => {
|
||||
if (this.decorations.includes(decoration)) {
|
||||
this.decorationsAlive = false;
|
||||
this.activeLine = null;
|
||||
this.activeCols = null;
|
||||
this.activeColor = null;
|
||||
this.activeRanges = [];
|
||||
this.activeTailRanges = [];
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -244,6 +258,7 @@ export class CursorLineHighlighter implements IDisposable {
|
||||
this.marker = null;
|
||||
this.decorations = [];
|
||||
this.decorationDisposeListeners = [];
|
||||
this.decorationsAlive = false;
|
||||
this.activeLine = null;
|
||||
this.activeCols = null;
|
||||
this.activeColor = null;
|
||||
@@ -251,6 +266,19 @@ export class CursorLineHighlighter implements IDisposable {
|
||||
this.activeTailRanges = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cheap render-time check that the tracked marker and decorations are still
|
||||
* owned by xterm. xterm disposes highlight decorations on buffer clears,
|
||||
* resets, and marker reindexing without emitting cursor-move/write events,
|
||||
* so a stale tracking state must be recreated before the next frame paints.
|
||||
*/
|
||||
private isTrackingIntact(): boolean {
|
||||
if (this.marker === null || this.marker.isDisposed) {
|
||||
return this.marker === null && this.decorations.length === 0;
|
||||
}
|
||||
return this.decorationsAlive;
|
||||
}
|
||||
|
||||
private clearOwned(
|
||||
marker: IMarker | null,
|
||||
decorations: IDecoration[],
|
||||
|
||||
Reference in new issue
Block a user