bug: builtin pager ignores Shift keys in Ghostty
Description
When using Sapling's internal pager (pager=internal:streampager, the default for sl log), any key that requires Shift does not work when running outside of tmux in Ghostty.app (and cmux.app which uses libghostty).
Observed failures:
G(Shift+g) to go to bottom of pager does nothing- Typing
@(Shift+2 on US layout) in search prompt (/) results in empty input - Searching for any capital letter (e.g.
Foo) does not insert the capital - All shifted punctuation / symbols affected
Inside tmux it works (tmux re-encodes keys as plain chars), and inside Terminal.app it works. Only Ghostty (and libghostty-based terms) outside tmux reproduces.
Repro steps (Ghostty, no tmux)
cd ~/code/github.com/facebook/sapling && sl log -l 5000 --pager=always- Press lowercase
g-> goes to top (works) - Press uppercase
G(Shift+g) -> should go to bottom, stays at top - Press
/to search - Type
@(Shift+2) -> prompt stays empty, should show @ - Type
ABC-> nothing inserted or only lowercase
Repro with hex injection (works inside tmux too, to avoid needing Ghostty)
Ghostty with modifyOtherKeys=2 enabled (which Sapling enables via termwiz ESC[>4;2m in set_raw_mode) encodes shifted keys as xterm modifyOtherKeys:
- Shift+G:
ESC[27;2;71~hex1b 5b 32 37 3b 32 3b 37 31 7e - Shift+@:
ESC[27;2;64~hex1b 5b 32 37 3b 32 3b 36 34 7e
tmux new -d -s repro -x 200 -y 20
tmux send-keys -t repro "sl log -l 5000 --pager=always" C-m; sleep 2
tmux send-keys -t repro -l "g"; sleep 0.5 # top works
tmux send-keys -t repro -H '1b5b32373b323b37317e'; sleep 0.8 # Shift+G Ghostty
# expected bottom, actual stays at top -> bug
tmux send-keys -t repro -l "/"; sleep 0.3
tmux send-keys -t repro -H '1b5b32373b323b36347e'; sleep 0.6 # @ shift
# expected Search: @, actual empty
tmux send-keys -t repro "q"; tmux kill-session -t reproTechnical overview
Sapling's pager enables xterm modifyOtherKeys and Ghostty sends Shift+G as ESC[27;2;71~ which termwiz decodes as Char('G')+SHIFT. The pager's keymap expects Char('G')+NONE and prompt only accepts NONE, so the exact modifier check fails. tmux hides this by re-encoding to plain G.
Disclosure: Written by Meta Muse Spark 1.1
Source: facebook/sapling