Skip to content

Commit 6341dac

Browse files
authored
Highlight changelog row under the mouse cursor (#50)
2 parents 7ecff13 + b80a398 commit 6341dac

1 file changed

Lines changed: 29 additions & 4 deletions

File tree

website/src/lib/changelog-runner.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ export class ChangelogRunner implements InteractiveProgram {
7878
private selectedIndex = 0;
7979
private listOffset = 0;
8080
private detailOffset = 0;
81+
private hoverIndex: number | null = null;
82+
private lastMousePos: { col: number; row: number } | null = null;
8183
private resizeUnsub: (() => void) | null = null;
8284
private disposed = false;
8385
private detailCache: { index: number; width: number; lines: string[] } | null = null;
@@ -161,6 +163,7 @@ export class ChangelogRunner implements InteractiveProgram {
161163
}
162164

163165
private handleMouse(button: number, col: number, row: number, finalByte: string): void {
166+
this.lastMousePos = { col, row };
164167
// Wheel scroll routes to whichever column the cursor is over so each
165168
// side scrolls independently.
166169
if (button === WHEEL_UP) {
@@ -173,11 +176,17 @@ export class ChangelogRunner implements InteractiveProgram {
173176
else this.scrollDetail(1);
174177
return;
175178
}
179+
// Motion (no button held). 1003h reports these as button code 35:
180+
// bit 5 (motion) | code 3 (no button). Highlight the list row under
181+
// the cursor so the user can see where a click would land.
182+
if (button === 35 && finalByte === "M") {
183+
if (this.listIndexAt(col, row) !== this.hoverIndex) this.render();
184+
return;
185+
}
176186
// Left-button press on the version list selects that release.
177-
if (button === 0 && finalByte === "M" && col < LIST_WIDTH) {
178-
const bodyTop = HEADER_ROWS;
179-
const idx = this.listOffset + (row - bodyTop);
180-
if (idx >= 0 && idx < RELEASES.length) {
187+
if (button === 0 && finalByte === "M") {
188+
const idx = this.listIndexAt(col, row);
189+
if (idx !== null) {
181190
this.selectedIndex = idx;
182191
this.detailOffset = 0;
183192
this.ensureSelectionVisible();
@@ -186,6 +195,14 @@ export class ChangelogRunner implements InteractiveProgram {
186195
}
187196
}
188197

198+
private listIndexAt(col: number, row: number): number | null {
199+
if (col >= LIST_WIDTH) return null;
200+
const r = row - HEADER_ROWS;
201+
if (r < 0 || r >= this.bodyHeight()) return null;
202+
const idx = this.listOffset + r;
203+
return idx >= 0 && idx < RELEASES.length ? idx : null;
204+
}
205+
189206
private moveSelection(delta: number): void {
190207
const next = Math.max(0, Math.min(RELEASES.length - 1, this.selectedIndex + delta));
191208
if (next === this.selectedIndex) return;
@@ -280,6 +297,12 @@ export class ChangelogRunner implements InteractiveProgram {
280297
private render(): void {
281298
if (this.disposed) return;
282299
this.lastSize = { ...this.size };
300+
// Re-sync hover from the cursor's last position so wheel-scrolls and
301+
// keyboard jumps move the highlight to whatever release is now under
302+
// the mouse.
303+
this.hoverIndex = this.lastMousePos
304+
? this.listIndexAt(this.lastMousePos.col, this.lastMousePos.row)
305+
: null;
283306
const bodyH = this.bodyHeight();
284307
const detailLines = this.getDetailLines();
285308

@@ -296,6 +319,8 @@ export class ChangelogRunner implements InteractiveProgram {
296319
const padded = label.length > LIST_WIDTH - 2 ? label.slice(0, LIST_WIDTH - 2) : label.padEnd(LIST_WIDTH - 2);
297320
if (idx === this.selectedIndex) {
298321
leftCell = `${fg(36)}${RESET} ${BOLD}${padded}${RESET}`;
322+
} else if (idx === this.hoverIndex) {
323+
leftCell = `${fg(36)}${RESET} ${padded}`;
299324
} else {
300325
leftCell = ` ${padded}`;
301326
}

0 commit comments

Comments
 (0)