Skip to content

Support HVP cursor positioning - #34

Open
rezigned wants to merge 2 commits into
doy:mainfrom
rezigned:fix/hvp-cursor-position
Open

Support HVP cursor positioning#34
rezigned wants to merge 2 commits into
doy:mainfrom
rezigned:fix/hvp-cursor-position

Conversation

@rezigned

Copy link
Copy Markdown
Contributor

Support HVP cursor positioning

Summary

  • Handle CSI Ps ; Ps f (HVP) as an alias of CSI Ps ; Ps H (CUP).
  • Preserve the existing one-based row and column defaults through canonicalize_params_2.
  • Add regression coverage for explicit HVP coordinates and the default home position.

Problem

HVP is the ANSI horizontal and vertical position command. It has the same cursor-positioning behavior as CUP, but the parser only dispatched the H final byte. Applications that use f for absolute cursor movement therefore had their cursor commands ignored.

This is visible with btop, which uses HVP throughout its redraw output. Ignoring those commands causes drawing payloads to be written sequentially and wrap across rows instead of appearing at their requested coordinates.

Fix

Dispatch both final bytes to the existing CUP implementation:

'H' | 'f' => {
    self.screen.cup(canonicalize_params_2(params, 1, 1))
}

No new screen behavior or parser state is introduced.

Verification

cargo fmt --check
cargo test --test csi horizontal_vertical_position
cargo test

Results:

  • Focused HVP regression: 1 passed
  • Full vt100 suite: 71 passed, 4 ignored
  • Downstream upmd suite using the pinned fork revision: 159 passed, 1 ignored
  • Manual downstream check: btop renders at the correct cursor coordinates

@rezigned
rezigned force-pushed the fix/hvp-cursor-position branch from 8a35571 to a58cbf7 Compare July 12, 2026 06:15
@rezigned
rezigned force-pushed the fix/hvp-cursor-position branch from 9baf1fb to e984df3 Compare July 12, 2026 06:45
Comment thread tests/csi.rs
Comment on lines +8 to +18
#[test]
fn horizontal_vertical_position() {
let mut vt = vt100::Parser::new(5, 10, 0);

vt.process(b"\x1b[3;7fX");
assert_eq!(vt.screen().cell(2, 6).unwrap().contents(), "X");
assert_eq!(vt.screen().cursor_position(), (2, 7));

vt.process(b"\x1b[fY");
assert_eq!(vt.screen().cell(0, 0).unwrap().contents(), "Y");
}

@rramphal rramphal Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional additions — zero parameters, clamping, and explicit CUP/HVP equivalence. Verified against the 5x10 grid.

Suggested change
#[test]
fn horizontal_vertical_position() {
let mut vt = vt100::Parser::new(5, 10, 0);
vt.process(b"\x1b[3;7fX");
assert_eq!(vt.screen().cell(2, 6).unwrap().contents(), "X");
assert_eq!(vt.screen().cursor_position(), (2, 7));
vt.process(b"\x1b[fY");
assert_eq!(vt.screen().cell(0, 0).unwrap().contents(), "Y");
}
#[test]
fn horizontal_vertical_position() {
let mut vt = vt100::Parser::new(5, 10, 0);
vt.process(b"\x1b[3;7fX");
assert_eq!(vt.screen().cell(2, 6).unwrap().contents(), "X");
assert_eq!(vt.screen().cursor_position(), (2, 7));
vt.process(b"\x1b[fY");
assert_eq!(vt.screen().cell(0, 0).unwrap().contents(), "Y");
vt.process(b"\x1b[0;0fZ");
assert_eq!(vt.screen().cell(0, 0).unwrap().contents(), "Z");
vt.process(b"\x1b[99;99f");
assert_eq!(vt.screen().cursor_position(), (4, 9));
let mut cup = vt100::Parser::new(5, 10, 0);
cup.process(b"\x1b[3;7H");
let mut hvp = vt100::Parser::new(5, 10, 0);
hvp.process(b"\x1b[3;7f");
assert_eq!(
cup.screen().cursor_position(),
hvp.screen().cursor_position()
);
}

Keeps their grid size, test name and existing four assertions untouched — it only appends. Extensions get accepted far more readily than rewrites.

Every assertion was verified empirically against the patched crate at 5x10:

ESC[3;7fX   cell(2,6)="X"  cursor=(2,7)
ESC[fY      cell(0,0)="Y"
ESC[0;0fZ   cell(0,0)="Z"
ESC[99;99f  cursor=(4,9)          clamps
equivalence cup=(2,6) hvp=(2,6)

@rramphal

Copy link
Copy Markdown

Thanks for this. I hit the same bug from the other direction — btop's box layout collapsing into a single wrapped stream inside a terminal built on this crate — and independently arrived at the identical fix, so this looks right to me. Routing f to the existing cup() also inherits its Origin Mode handling, which is where hand-rolled HVP implementations tend to drift from CUP.

A few notes that might help it land:

Related threads. #26 (the issue), this PR, and #36 (another HVP fix) don't currently reference each other, so none of them is discoverable from the others. Cross-linking would save the next person the rediscovery.

CHANGELOG. The repo looks like it expects changes to carry their own entry — 7076e0f (the #13 fix) and 062f9de (from an outside contributor) both added one in the same commit as the code. Something like:

## [Unreleased]

### Fixed

* Handle HVP (`CSI f`), which is functionally identical to CUP (`CSI H`).
  It was previously dispatched as an unhandled sequence, silently dropping
  every cursor movement made by applications that emit it. (#26)

Test coverage. Left a suggestion on the test adding zero-parameter handling, out-of-range clamping, and explicit CUP/HVP equivalence. All assertions verified against your 5x10 grid.

Optional. The examples/generate_fixture.rs change is unrelated to HVP — might be worth splitting so this stays a pure two-file fix. Entirely your call; just thinking about giving a reviewer as little as possible to weigh.

Assertions verified by running them against the patched crate rather than by inspection.

@rezigned

rezigned commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for this. I hit the same bug from the other direction — btop's box layout collapsing into a single wrapped stream inside a terminal built on this crate — and independently arrived at the identical fix, so this looks right to me. Routing f to the existing cup() also inherits its Origin Mode handling, which is where hand-rolled HVP implementations tend to drift from CUP.

A few notes that might help it land:

Related threads. #26 (the issue), this PR, and #36 (another HVP fix) don't currently reference each other, so none of them is discoverable from the others. Cross-linking would save the next person the rediscovery.

CHANGELOG. The repo looks like it expects changes to carry their own entry — 7076e0f (the #13 fix) and 062f9de (from an outside contributor) both added one in the same commit as the code. Something like:

## [Unreleased]

### Fixed

* Handle HVP (`CSI f`), which is functionally identical to CUP (`CSI H`).
  It was previously dispatched as an unhandled sequence, silently dropping
  every cursor movement made by applications that emit it. (#26)

Test coverage. Left a suggestion on the test adding zero-parameter handling, out-of-range clamping, and explicit CUP/HVP equivalence. All assertions verified against your 5x10 grid.

Optional. The examples/generate_fixture.rs change is unrelated to HVP — might be worth splitting so this stays a pure two-file fix. Entirely your call; just thinking about giving a reviewer as little as possible to weigh.

Assertions verified by running them against the patched crate rather than by inspection.

Thanks! The thing is, we don't know when our PR will get merged 😆 (e.g. #11 )

0chroma added a commit to gominimal/minimal that referenced this pull request Aug 21, 2026
…1240)

The daemon's terminal model never applied CSI 'f' (HVP), the ECMA-48
alias of CUP that btop uses for all of its cursor positioning: vt100's
dispatch has no 'f' arm, so the sequence fell to unhandled_csi and the
output applied as one continuous append. The corrupted model surfaced
both in the dash Preview snapshot and on session re-attach, while real
terminals (which implement HVP) rendered btop fine.

No vt100-lineage crate ships the fix: doy/vt100 is dormant (one commit
since July 2025, two duplicate HVP PRs open since then) and the
ChrisTitusTech packaging fork adds nothing we use while inheriting the
same gap. Switch the dependency back to upstream vt100 0.16.2 and patch
it against gominimal/vt100-rust's minimal-patches branch, which carries
rezigned's fix from doy/vt100-rust#34 (cherry-picked with authorship,
drive-by cleanup dropped). A regression test in session_host asserts
both explicit and default-parameter HVP positioning.

Refs: #1197
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants