Skip to content

feat(render): draw the cursor in screenshots - #86

Closed
Ayman Bagabas (aymanbagabas) wants to merge 3 commits into
microsoft:feat/osc-colorsfrom
aymanbagabas:feat/cursor-render
Closed

feat(render): draw the cursor in screenshots#86
Ayman Bagabas (aymanbagabas) wants to merge 3 commits into
microsoft:feat/osc-colorsfrom
aymanbagabas:feat/cursor-render

Conversation

@aymanbagabas

@aymanbagabas Ayman Bagabas (aymanbagabas) commented Aug 4, 2026

Copy link
Copy Markdown
Member

Third of a three PR stack: #84#85#86. Only the last commit is new here; a cross-repo PR cannot be based on a fork branch, so GitHub shows the whole stack. Review feat(render): draw the cursor in screenshots.

A screenshot showed the grid but never showed where the terminal was about to write. You could not tell an editor's caret position, whether a program had hidden the cursor, or which mode it was in.

Before / after

The same session, screenshotted by the same command. The cursor is parked mid-word on The:

before after
before after

The emulator now reports visibility (DECTCEM, CSI ?25 h and l) and shape (DECSCUSR, CSI Ps SP q). The renderer draws the cursor after the text pass: a block fills the cell, an underline sits on its bottom edge, a bar on its left.

Visibility matters as much as position. Full-screen programs hide the cursor while repainting, so a screenshot that ignored DECTCEM would show one parked wherever the last write happened to land.

The cursor never hides content

A block redraws the character beneath it in the cell background, the way a terminal does. The redraw goes through the same path as the text pass, which keeps right two cases a naive redraw gets wrong:

case naive redraw here
double-width () block covers the left half, glyph squashed into one cell block spans both cells, glyph drawn at full width
nerd font glyph emitted as text the font has no glyph for, so the block swallows it redrawn as the same vector glyph
double-width, covered across both cells nerd font glyph, redrawn as a glyph
wide nerd

The cursor follows a color a program sets

OSC 12 sets the cursor color and OSC 112 resets it, both from #85. The cursor had nothing to paint until this PR, so this is where they become visible. One session, screenshotted after each step, cursor parked on the q:

default OSC 12 sets #ff8700 OSC 112 resets
default orange reset

The default cursor color follows the profile foreground, so the block is the same grey as the text. The q stays legible in all three.

Notes for review

  • Blink is deliberately not represented. A screenshot is a single moment, and a blinking cursor is drawn in the half of the cycle where it is visible.
  • Emulator::cursor is relative to the visible screen, so screenshot --full offsets it past the scrollback above.
  • Alacritty's HollowBlock (what it draws for an unfocused window) maps to Block, since a headless terminal has no notion of focus. Its Hidden shape means the same as the mode being off.
  • 2 conformance cases, which run against every backend, cover hide/show and the three shapes. 6 renderer tests cover each shape's rectangle, the readable character, the double-width span, the vector glyph redraw, a hidden or out-of-range cursor, and the runtime color.

A screenshot showed the grid but never where the terminal was about to
write, so a reader could not tell an editor's caret position, whether a
program had hidden the cursor, or which mode it was in.

The emulator now reports visibility (`DECTCEM`) and shape (`DECSCUSR`),
and the renderer draws the cursor after the text pass: a block fills the
cell, an underline sits on its bottom edge, and a bar on its left.

A block redraws the character beneath it in the cell's background color,
so it stays readable rather than being swallowed. The redraw goes
through the same path as the text pass, which keeps two cases right that
a naive redraw gets wrong: a double-width character is covered across
both of its cells instead of being clipped and squashed into one, and a
nerd font glyph comes back as a vector glyph rather than as a character
the text font has no glyph for.

Blink is deliberately not represented; a screenshot is a single moment,
and a blinking cursor is drawn in the half of the cycle where it shows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
@aymanbagabas

Copy link
Copy Markdown
Member Author

cpendery (@cpendery) third of the stack, on top of #84#85. Only the last commit is new here. Please review #84 and #85 first.

Brings main's move of the request types out of the core crate down the
stack. The screenshot keeps main's signature and error handling, and holds
the state guard across both the emulator borrow and the cursor lookup so a
single lock covers the whole render.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
@aymanbagabas
Ayman Bagabas (aymanbagabas) changed the base branch from main to feat/osc-colors August 5, 2026 17:49
@aymanbagabas

Copy link
Copy Markdown
Member Author

Superseded: reopening this from a branch in microsoft/shell-use rather than my fork, so the three can be linked as a real stack. GitHub does not allow a pull request from a fork to join a stack, and the head repository of an existing PR cannot be changed.

Same commits, same branch name; the replacement is linked below.

@aymanbagabas

Copy link
Copy Markdown
Member Author

Reopened as #96, based on #95 so the diff shows only this layer.

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.

1 participant