2024-04-12 12:19:32 +02:00
|
|
|
#RUN: %fish %s
|
2021-11-14 08:34:45 +01:00
|
|
|
#REQUIRES: command -v tmux
|
|
|
|
|
|
2025-10-16 23:00:16 +03:00
|
|
|
isolated-tmux-start -C '
|
2021-11-14 08:34:45 +01:00
|
|
|
set -g fish_autosuggestion_enabled 0
|
|
|
|
|
function abbr-test
|
|
|
|
|
end
|
|
|
|
|
abbr -g abbr-test "abbr-test [expanded]"
|
|
|
|
|
'
|
|
|
|
|
|
2026-04-11 16:23:10 +08:00
|
|
|
if { command -v less && ! less --version 2>&1 | grep BusyBox } >/dev/null
|
2026-04-08 14:00:56 +08:00
|
|
|
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
|
|
|
|
|
|
2021-11-14 08:34:45 +01:00
|
|
|
# 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
|
|
|
|
|
|
2024-05-03 08:30:27 +02:00
|
|
|
# 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
|
|
|
|
|
|
2025-03-31 20:30:09 +02:00
|
|
|
# Or use Control+Space to the same effect.
|
2021-11-14 08:34:45 +01:00
|
|
|
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.
|
2021-11-14 08:34:45 +01:00
|
|
|
# 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
|