Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ describe("hasChanged", () => {
it("returns true when is_fullscreen changes even if screen is the same", () => {
expect(hasChanged(base, { screen: "hello", title: "", is_fullscreen: true })).toBe(true);
});

it("returns true when cursor position changes even if screen text is the same", () => {
const before = { screen: "hello", title: "", is_fullscreen: false, cursor: { x: 0, y: 0 } };
const current = { screen: "hello", title: "", is_fullscreen: false, cursor: { x: 5, y: 0 } };
expect(hasChanged(before, current)).toBe(true);
});

it("returns true when cursor row changes", () => {
const before = { screen: "hello", title: "", is_fullscreen: false, cursor: { x: 0, y: 0 } };
const current = { screen: "hello", title: "", is_fullscreen: false, cursor: { x: 0, y: 1 } };
expect(hasChanged(before, current)).toBe(true);
});

it("returns false when cursor position is the same", () => {
const before = { screen: "hello", title: "", is_fullscreen: false, cursor: { x: 3, y: 2 } };
const current = { screen: "hello", title: "", is_fullscreen: false, cursor: { x: 3, y: 2 } };
expect(hasChanged(before, current)).toBe(false);
});

it("ignores cursor when cursor is not provided (backward compatible)", () => {
expect(hasChanged(base, { screen: "hello", title: "", is_fullscreen: false })).toBe(false);
});
});

describe("Session", () => {
Expand Down
21 changes: 15 additions & 6 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ export function extractIsFullscreen(bufferNamespace: { active: { type: string }

/** Check if any observable state has changed between two snapshots. */
export function hasChanged(
before: { screen: string; title: string; is_fullscreen: boolean },
current: { screen: string; title: string; is_fullscreen: boolean }
before: { screen: string; title: string; is_fullscreen: boolean; cursor?: { x: number; y: number } },
current: { screen: string; title: string; is_fullscreen: boolean; cursor?: { x: number; y: number } }
): boolean {
return (
if (
current.screen !== before.screen ||
current.title !== before.title ||
current.is_fullscreen !== before.is_fullscreen
);
) return true;

if (before.cursor && current.cursor) {
if (current.cursor.x !== before.cursor.x || current.cursor.y !== before.cursor.y) return true;
}

return false;
}

// Special key name → escape sequence mapping
Expand Down Expand Up @@ -213,6 +219,8 @@ export class Session {
const beforeScreen = this.lastSnapshot;
const beforeTitle = this._title;
const beforeFullscreen = this._isFullscreen;
const buf0 = this.terminal.buffer.active;
const beforeCursor = { x: buf0.cursorX, y: buf0.cursorY };

if (this._status === "exited") {
return this.snapshot();
Expand Down Expand Up @@ -253,9 +261,10 @@ export class Session {
if (new RegExp(text).test(currentScreen)) { done(); return; }
} else {
// Change mode: resolve when any observable state differs from before AND has been idle
const currentCursor = { x: buf.cursorX, y: buf.cursorY };
if (hasChanged(
{ screen: beforeScreen, title: beforeTitle, is_fullscreen: beforeFullscreen },
{ screen: currentScreen, title: this._title, is_fullscreen: this._isFullscreen }
{ screen: beforeScreen, title: beforeTitle, is_fullscreen: beforeFullscreen, cursor: beforeCursor },
{ screen: currentScreen, title: this._title, is_fullscreen: this._isFullscreen, cursor: currentCursor }
)) {
if (idleTimer) clearTimeout(idleTimer);
idleTimer = setTimeout(done, 100);
Expand Down
Loading