Skip to content

feat(ivrix): text-direction shortcut + ASCII quotes on Hebrew layout - #5

Merged
DananzMolt merged 6 commits into
ivrix-prompt-selection-edit-appfrom
ivrix-rtl-shortcut-quotes
Jul 31, 2026
Merged

feat(ivrix): text-direction shortcut + ASCII quotes on Hebrew layout#5
DananzMolt merged 6 commits into
ivrix-prompt-selection-edit-appfrom
ivrix-rtl-shortcut-quotes

Conversation

@DananzMolt

Copy link
Copy Markdown
Owner

Two Ivrix typing-ergonomics changes, one commit each.

1. Toggle text direction from a keyboard shortcut

The RTL/LTR control in the titlebar was the only way to flip the terminal print direction.

  • New toggleTextDirection shortcut action, default Ctrl+Cmd+H, plus a View menu item.
  • All four entrypoints (titlebar control, toolbar segmented control, menu item, shortcut) now flip through one path, TerminalTextDirectionSettings.toggleDirection(), instead of each site computing the next direction itself.
  • Ctrl+Cmd+H is free of AppKit reservations and of every cmux default. Cmd+H alone is Hide Application, so the Ctrl variant does not shadow it.
  • Shortcut policy: in KeyboardShortcutSettings, editable in Settings > Keyboard Shortcuts, settable as shortcuts.bindings.toggleTextDirection in ~/.config/cmux/cmux.json, documented in web/data/cmux-shortcuts.ts, web/data/cmux.schema.json, and skills/cmux-settings/references/shortcut-actions.md.

2. ASCII quotes when typing on a Hebrew layout

Hebrew layouts put GERESH (U+05F3) and GERSHAYIM (U+05F4) on the apostrophe and quote keys, so echo "hi" typed in Hebrew arrives as echo ״hi״ and the shell never sees a quote.

  • Rewrites those two scalars to ASCII ' and " on both typed-text paths: the insertText accumulator and the textForKeyEvent fallback.
  • Gated on keyTextAccumulator being non-nil, so it only applies to live keystrokes. Paste, dictation, and programmatic NSTextInputClient callers keep their text verbatim.
  • normalized scans for the two scalars before reading UserDefaults, so ordinary typing pays one pass over a one-scalar string and nothing else (this is the keystroke path).
  • On by default. Settings > Terminal > ASCII Quotes on Hebrew Layout turns it off for typing Hebrew acronyms such as צה״ל, which need the real gershayim. Also settable as terminal.hebrewAsciiQuotes in ~/.config/cmux/cmux.json.

Verification

  • xcodebuild -scheme cmux -configuration Debug — BUILD SUCCEEDED.
  • xcodebuild test -scheme cmux-unit -only-testing:cmuxTests/HebrewAsciiQuotesTests -only-testing:cmuxTests/TerminalTextDirectionToggleTests — 8 tests in 2 suites passed.
  • swift test --filter SettingsRowAnchorResolution and --filter SettingsSearchIndexTests in Packages/macOS/CmuxSettingsUI — passed (104 row-path cases + 16 index tests).
  • ./scripts/lint-pbxproj-test-wiring.sh — ok (591 test files); both new Swift files are wired into project.pbxproj.
  • ./scripts/reload.sh --tag ivrix-rtl-shortcut — succeeded.

Localization audit

Changed user-facing surfaces: one shortcut label, one View menu item, one Settings row title with two subtitle variants, one cmux.json schema description, two skill reference docs.

  • Resources/Localizable.xcstrings: 5 new keys, each with real translations for all 20 supported locales (ar, bs, da, de, en, es, fr, it, ja, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant). No needs_review English fallbacks. Verified additive: 625 insertions, 0 deletions.
  • web/messages/*.json: docs.configuration.schemaDescriptions.terminal.hebrewAsciiQuotes added to all 20 catalogs with real translations. Verified additive: 1 insertion, 0 deletions per file.
  • web/data/cmux-shortcuts.ts: en + ja, matching that file's shape.
  • Not localized, matching the existing convention in those files: CuratedSettingEntry+Default.swift titles (the whole upstream table is bare English) and the skills/cmux-settings/references/*.md docs (English-only).

Notes

  • Base branch is ivrix-prompt-selection-edit-app (Ivrix trunk, tip 0808e7c0a4 "release: Ivrix 1.1.2"). origin/main is the upstream cmux mirror and does not contain the RTL/LTR control at all.
  • terminal.hebrewFont predates this PR and is still missing from the search index and rowConfigPaths; not touched here. The new terminal.hebrewAsciiQuotes setting does get both.

https://claude.ai/code/session_01TPRwsvsc1t88Wg5LaJ3rXb

The RTL/LTR control in the titlebar was the only way to flip the terminal
print direction. Add a `toggleTextDirection` shortcut action, bound to
Ctrl+Cmd+H by default, plus a matching View menu item.

All four entrypoints (titlebar control, toolbar segmented control, menu
item, shortcut) now flip through one path,
`TerminalTextDirectionSettings.toggleDirection()`, instead of each site
computing the next direction itself.

Ctrl+Cmd+H is free of AppKit reservations and of every cmux default; Cmd+H
alone is Hide Application, so the Ctrl variant does not shadow it.

Per the shortcut policy the action is in `KeyboardShortcutSettings`,
editable in Settings > Keyboard Shortcuts, settable as
`shortcuts.bindings.toggleTextDirection` in `~/.config/cmux/cmux.json`, and
documented in the shortcut docs and the cmux-settings skill reference.
Localized across all 20 supported locales.
Hebrew layouts put HEBREW PUNCTUATION GERESH (U+05F3) and GERSHAYIM
(U+05F4) on the apostrophe and quote keys, so `echo "hi"` typed in Hebrew
arrives as `echo ״hi״` and the shell never sees a quote.

Rewrite those two scalars to ASCII `'` and `"` on the way into the
terminal, on both typed-text paths: the `insertText` accumulator and the
`textForKeyEvent` fallback. The rewrite is gated on `keyTextAccumulator`
being non-nil, so it only applies to live keystrokes — paste, dictation,
and programmatic NSTextInputClient callers keep their text verbatim.

`normalized` scans for the two scalars before reading UserDefaults, so
ordinary typing pays one pass over a one-scalar string and nothing else.

On by default, since shell quoting is the common case. Settings > Terminal
> ASCII Quotes on Hebrew Layout turns it off for typing Hebrew acronyms
such as צה״ל, which need the real gershayim; also settable as
`terminal.hebrewAsciiQuotes` in `~/.config/cmux/cmux.json`. Localized
across all 20 supported locales.

Also adds the direction-toggle coverage for the shared
`TerminalTextDirectionSettings.toggleDirection()` path added in the
previous commit.
The titlebar RTL/LTR button's hover tooltip named only the current
direction, so the new Ctrl+Cmd+H binding was undiscoverable from the
control it drives. Route it through `Action.tooltip(_:)`, the same helper
the sidebar, notifications, and focus-history buttons already use, so the
tooltip reads "Right-to-left (⌃⌘H)" and follows a rebind.
Selection editing at a prompt no longer refuses on the sole grounds that an
application owns the screen. It now requires what it actually needs: cells
marked as input by OSC 133 `B`.

No behavior change today. Claude Code, and every other full-screen
application I know of, emits no OSC 133, so their composers are still
copy-only. This makes Ivrix ready for the ones that mark, and turns the
open question into an upstream ask rather than a terminal-side guess about
where somebody else's input box begins.
Keeps the terminal-side half and the upstream request that unblocks it in
one place, so the next person to touch selection editing can see why it
stops at the composer of a full-screen application.
@DananzMolt

Copy link
Copy Markdown
Owner Author

Added the selection-editing fix (option B from the investigation) to this branch.

Diagnosis. Selecting text and typing over it worked at a shell prompt in both English and Hebrew, but did nothing in Claude Code. I captured Claude Code's startup bytes in a pty rather than assume: it enters the alternate screen (CSI ?1049h) and emits no OSC 133. Two guards refused, either one fatal:

  1. Surface.zig called Terminal.cursorIsAtPrompt(), which returns false unconditionally on the alternate screen (Terminal.zig:1903).
  2. Even without that, the feature requires semantic_content == .input on the cursor cell and both selection ends — marks that only OSC 133 B produces.

Fix. Gate on the marking instead of on the screen. cursorIsAtPromptInput is cursorIsAtPrompt minus the alternate-screen veto; every other condition is untouched. An application that emits no marks is still refused one guard later, because no cell of the composer it drew is .input.

Deliberately not done: inferring the composer region from box-drawing characters. A misidentification issues deletes into the user's real input, which is the failure the original commit refused to risk.

This is inert today. Claude Code emits no marks, so nothing changes for it until it does. docs/claude-code-osc133-request.md is the drafted upstream ask that would unblock it — not yet filed.

Verification

  • zig build test -Dtest-filter="prompt input gate" — 3 new tests pass. Confirmed they actually execute by comparing counts against a no-match filter (72 baseline, 75 with the filter, 73 with just the alt-screen test's exact name).
  • One test walks a whole prompt lifecycle asserting cursorIsAtPromptInput and cursorIsAtPrompt agree at every step on the primary screen, so the accepted set there is provably unchanged.
  • Submodule commit f3f6bd8 pushed to DananzMolt/ghostty ivrix-prompt-selection-edit and verified an ancestor of the remote branch before the pointer bump.

Also drops a stale line from the selection-edit-at-prompt config docs claiming RTL rows are disabled — they were re-enabled when logicalViewportPoint started inverting the row's bidi order on the way in.

Note this needs a GhosttyKit rebuild from the new pin before release; the tagged dogfood build still carries the old xcframework, which is harmless since the change is inert.

@DananzMolt
DananzMolt merged commit 5bdaca0 into ivrix-prompt-selection-edit-app Jul 31, 2026
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