Skip to content

fix IME position when using claude code on MacOS - #12957

Open
acarl005 wants to merge 4 commits into
masterfrom
andy/fix-ime-claude-code-macos
Open

fix IME position when using claude code on MacOS#12957
acarl005 wants to merge 4 commits into
masterfrom
andy/fix-ime-claude-code-macos

Conversation

@acarl005

@acarl005 acarl005 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

Fixes #9145

Testing

To repro the bug. Open claude CLI on MacOS and then open the IME. The IME will be in the top-left corner of the grid, i.e. position (0, 0).

On this branch, the position is correct:

Screenshot 2026-06-23 at 11 22 59 AM

Description

The bug is due to the cached cursor position (in PaintContext::position_cache being incorrect). The IME uses the cached cursor position to decide where to render. The bug was occurring because the position was stale at (0, 0).

When you run claude, it tries to prevent visual flickering using the synchronized update protocol. It emits escape codes like this:

# on one "frame" where "frame" is a step in claude's update cycle that outputs text to the terminal model. not WarpUI's idea of a "frame" or a "rendered scene"

\e[?2026h    ← begin sync
\e[?25l      ← hide cursor
print a bunch of output, the "frame" content
\e[?25h      ← show cursor
\e[?2026l    ← end sync / render 

# next "frame"

\e[?2026h    ← begin sync
\e[?25l      ← hide cursor
print a bunch of output, the "frame" content
\e[?25h      ← show cursor
\e[?2026l    ← end sync / render 

This is from inspecting PTY recordings BTW

Screenshot 2026-06-23 at 2 06 57 PM

It wraps each "frame" in synchronized output as well as hiding and then showing the cursor. I presume this is to ensure the cursor doesn't jump around at all during the update cycle. They want to maintain the user's idea of the cursor position even though updating the screen means the actual cursor needs to fully traverse the grid to update each cell. Hiding it during the update ensures that the user won't notice that.

So why does this go wrong?

We do implement this sync output protocol. However, we do not actually paint the scene (Warp's idea of a frame) synchronously. We send a wakeup event, but wakeup events are buffered and coalesced, i.e. throttled. When \e[?2026l (end sync) is received, we correctly send a wakeup event and the cursor is SHOWN. But soon the \e[?25l (hide cursor) for the next update comes in and gets applied to the terminal model and coalesces with the last wakeup event. Now we get a deferred render where cursor is HIDDEN.

Finally rendering the scene happens and BlockListElement::draw_block is called. It checks TermMode::SHOW_CURSOR. If that is on, it calls fn render_cursor which is the function responsible for setting the cached cursor position which the IME depends on. If not, no position is cached and the IME will not have an up-to-date position.

I think in an ideal world,

@cla-bot cla-bot Bot added the cla-signed label Jun 23, 2026
@oz-for-oss

oz-for-oss Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@acarl005

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR keeps the terminal cursor position cache updated when block-grid cursors are hidden via SHOW_CURSOR, so macOS IME candidate windows can anchor to the active cursor location for Claude Code-style TUIs. It also refactors editor cursor caching to reuse the same computed rect.

Concerns

  • No blocking correctness, security, or spec-alignment concerns found in the changed lines.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@acarl005

Copy link
Copy Markdown
Contributor Author

/oz-review

@oz-for-oss

oz-for-oss Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@acarl005

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR factors terminal cursor geometry so hidden cursors can keep terminal_view:cursor_* position cache entries current without painting, and uses that cache path for active command/output grids when TermMode::SHOW_CURSOR is false. It also simplifies editor cursor rect reuse.

Concerns

  • No blocking correctness or security concerns found in the changed lines.
  • The PR includes visual evidence for the user-facing IME positioning change.
  • No approved or repository spec context was provided for implementation drift review.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CJK IME conversion candidates popup is misaligned from cursor/input line

1 participant