Files
fish-shell/tests/checks/tmux-abbr.fish

59 lines
1.7 KiB
Fish
Raw Permalink Normal View History

#RUN: %fish %s
#REQUIRES: command -v tmux
isolated-tmux-start -C '
set -g fish_autosuggestion_enabled 0
function abbr-test
end
abbr -g abbr-test "abbr-test [expanded]"
'
if { command -v less && ! less --version 2>&1 | grep BusyBox } >/dev/null
isolated-tmux send-keys "abbr --help | sed 1q | less" Enter
tmux-sleep
isolated-tmux capture-pane -p
isolated-tmux send-keys q
else
echo '(END)'
echo 'prompt 0> abbr --help | sed 1q | less'
echo 'Documentation for abbr'
end
Pass job group down to builtin_print_help Repro: $ abbr -h | less fish: Job 1, 'abbr --help | less' has stopped Bisects to 5ece9bec6c6 (__fish_print_help: use man as-is, 2025-08-29). Root cause is similar to https://github.com/fish-shell/fish-shell/issues/8699#issuecomment-1030762748 except it's specific to how __fish_print_help is invoked, since "__fish_print_help abbr | less" works fine. Fix this by letting __fish_print_help access the terminal. Fixes #12612 Solved by Claude in a single prompt: --- ▐▛███▜▌ Claude Code v2.1.92 ▝▜█████▛▘ Sonnet 4.6 ▘▘ ▝▝ ~/git/fish-shell ❯ "abbr -h | less" gets a signal, resulting in the message "fish: Job 1, 'abbr -h | less' has stopped" whereas "__fish_print_help abbr | less" works just fine, even though "abbr -h" should run exactly that. Why this difference? [...] ● Now I can give you a clear answer. --- The difference comes down to job group inheritance when man is spawned. The deferred execution order In abbr -h | less, fish uses the "deferred process" optimization: since abbr is a non-external process piping into an external one, fish launches less first, transfers the terminal to less's process group, then runs the deferred abbr builtin. By the time abbr's help function spawns man, the terminal already belongs to less's pgroup. Same thing happens with __fish_print_help abbr | less — the outer less is launched first and gets the terminal. Where the paths diverge Function case (__fish_print_help abbr | less): get_performer_for_function (exec.rs:1050) captures job.group.clone() and passes it to eval_node. So when command man runs inside the function, it's given the outer pipeline's job group — the same one that owns the terminal. man's inner less pager joins that group and can read from the terminal freely. ✓ Builtin case (abbr -h | less): builtin_print_help calls parser.eval(&cmd, streams.io_chain) (shared.rs:652). parser.eval is defined as: pub fn eval(&self, cmd: &wstr, io: &IoChain) -> EvalRes { self.eval_with(cmd, io, None, BlockType::top, false) // ^^^^ job_group = None } job_group = None means man creates a brand-new job group with no terminal access. When man spawns its less pager, the pager tries to read keyboard input from the terminal, but the terminal belongs to the outer less's process group. The pager receives SIGTTIN → stops → the whole job is reported as stopped. The fix builtin_print_help should pass streams.job_group to the eval call (which is already populated at exec.rs:1192). Changing line 652 from: let res = parser.eval(&cmd, streams.io_chain); to: let res = parser.eval_with(&cmd, streams.io_chain, streams.job_group.as_ref(), BlockType::top, false); would give man the outer job's job group, letting its pager access the terminal just like the function case. ✻ Sautéed for 13m 0s
2026-04-07 19:31:35 +08:00
# CHECK: (END)
# CHECK: prompt 0> abbr --help | sed 1q | less
# CHECK: Documentation for abbr
# Expand abbreviations on space.
isolated-tmux send-keys abbr-test Space arg1 Enter
tmux-sleep
# CHECK: prompt {{\d+}}> abbr-test [expanded] arg1
# Expand abbreviations at the cursor when executing.
isolated-tmux send-keys abbr-test Enter
tmux-sleep
# CHECK: prompt {{\d+}}> abbr-test [expanded]
# Use Control+Z right after abbreviation expansion, to keep going without expanding.
isolated-tmux send-keys abbr-test Space C-z arg2 Enter
tmux-sleep
# CHECK: prompt {{\d+}}> abbr-test arg2
# Same with a redundant space; it does not expand abbreviations.
isolated-tmux send-keys C-u abbr-test Space C-z Space arg2 Enter
tmux-sleep
# CHECK: prompt {{\d+}}> abbr-test arg2
# Or use Control+Space to the same effect.
isolated-tmux send-keys abbr-test C-Space arg3 Enter
tmux-sleep
# CHECK: prompt {{\d+}}> abbr-test arg3
2025-01-19 21:34:42 +01:00
# Do not expand abbreviation if the cursor is not at the command, even if it's just white space.
# This makes the behavior more consistent with the above two scenarios.
isolated-tmux send-keys abbr-test C-Space Enter
tmux-sleep
# CHECK: prompt {{\d+}}> abbr-test
# CHECK: prompt {{\d+}}>
isolated-tmux capture-pane -p