Not reexporting means that imports have to change to directly import
from `fish_common`. This makes it easier to see which dependencies on
`src/common.rs` actually remain, which helps with identifying candidates
for extraction.
While at it, group some imports.
Part of #12625
Removing this dependency allows extracting the `fish_reserved_codepoint`
function, and other code depending on it in subsequent commits.
Part of #12625
Reduce verbosity of const definitions. Define a dedicated const for the
base of the special key encoding range. This range is 256 bytes wide, so
by defining consts via an u8 offset from the base, we can guarantee that
the consts fall into the allocated range. Ideally, we would also check
for collisions, but Rust's const capabilities don't allow for that as
far as I'm aware.
Having `SPECIAL_KEY_ENCODE_BASE` in the `rust-widestring` crate allows
getting rid of the dependency on `key::Backspace` in the
`fish_reserved_codepoint` function, which unblocks code extraction.
Part of #12625
While the function is only used to decode single codepoints, nothing in
its implementation limits it to single codepoints, so the name
`decode_one_codepoint_utf8` is misleading. Change it to the simpler and
more accurate `decode_utf8`. Add a doc comment to describe the
function's behavior.
Part of #12625
To homogenize error reporting format, use a new Error struct. Currently this
is used for builtins and ensuring a common cmd/subcmd prefix.
Part of #12556
Repro:
$ abbr -h | less
fish: Job 1, 'abbr --help | less' has stopped
Bisects to 5ece9bec6c (__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
Another step in splitting up the main library crate.
Note that this change requires removing the `#[cfg(test)]` annotations
around the `LOCAL_OVERRIDE_STACK` code, because otherwise the code would
be removed in test builds for other packages, making the `#[cfg(test)]`
functions unusable from other packages, and functions with such feature
gates in their body would have the code guarded by these gates removed
in test builds for tests in other packages.
Closes#12494
Having a public function named `test` is quite unspecific. Exporting it
both as `test` and `feature_test` results in inconsistent usage. Fix
this by renaming the function to `feature_test` and removing the alias.
Part of #12494
Several features of fish can be toggled at runtime (in practice at
startup). To keep track of the active features, `FEATURES`, an array of
`AtomicBool` is used. This can safely be shared across threads without
requiring locks.
Some of our tests override certain features to test behavior with a
specific value of the feature. Prior to this commit, they did this by
using thread-local versions of `FEATURES` instead of the process-wide
version used in non-test builds. This approach has two downsides:
- It does not allow nested overrides.
- It prevents using the code across package boundaries.
The former is a fairly minor issue, since I don't think we need nested
overrides. The latter prevents splitting up our large library crate,
since `#[cfg(test)]`-guarded code can only be used within a single
package.
To resolve these issues, a new approach to feature overrides in
tests is introduced in this commit: Instead of having a thread-local
version of `FEATURES`, all code, whether test or not, uses the
process-wide `FEATURES`. For non-test code, there is no change. For test
code, `FEATURES` is now also used. To override features in tests, a new
`with_overridden_feature` function is added, which replaces
`scoped_test` and `set`. It works by maintaining a thread-local stack of
feature overrides (`LOCAL_OVERRIDE_STACK`). The overridden `FeatureFlag`
and its new value are pushed to the stack, then the code for which the
override should be active is run, and finally the stack is popped again.
Feature tests now have to scan the stack for the first appearance of the
`FeatureFlag`, or use the value in `FEATURES` if the stack does not
contain the `FeatureFlag`. In most cases, the stack will be empty or
contain very few elements, so scanning it should not take long. For now,
it's only active in test code, so non-test code is unaffected. The plan
is to change this when the feature flag code is extracted from the main
library crate. This would slightly slow down feature tests in non-test
code, but there the stack will always be empty, since we only override
features in tests.
Part of #12494
To homogenize error reporting format, use a new Error struct. Currently this
is used for builtins and ensuring a common cmd/subcmd prefix.
Part of #12556
With a few exceptions, only one test is added for a given message, even
when there are multiple ways to trigger the same message (e.g. different
invalid option combinations, or triggered in shared functions such as
`builtin_unknown_option`)
Includes a few very minor fixes, such as missing a newline, or using the
wrong var name.
Closes#12603
- fix wrong pattern used in `string replace` error message
- replace unreachable error with `unreachable!` in `string`
- fix cmd being used in place of subcmd
Part of #12603
Previously executing `set -a` or `set -p` would just list all the
variables, which does not make sense since the user specifically ask
for an action (append/prepend).
Update the help page synopsis
Part of #12603
If all of
$PREFIX/bin/fish
$PREFIX/share/fish
$PREFIX/etc/fish
exist, then fish assumes it's in a relocatable directory tree.
This is used by homebrew (PREFIX=/usr/local) and maybe also nix(?).
Other Linux distros prefer to use /etc/fish instead of $PREFIX/etc/fish
[1]. To do so, they need to pass -DCMAKE_INSTALL_SYSCONFDIR=/etc.
The relocatable tree logic assumes default data and sysconf dirs
(relative to a potentially relocatable prefix). If the user changes
any of those, and the relocatable tree logic happens to kick in,
that'd overrule user preference, which is surprising.
So a non-default data or sysconf path is a strong enough signal that
we want to disable the relocatable tree logic. Do that.
Closes#10748
[1]: ff2f69cd56/PKGBUILD (L43)
test_history_path_detection calls add_pending_with_file_detection(),
which spawns a thread pool task via ThreadPool::perform(). This
requires threads::init() to have been called, otherwise
assert_is_background_thread() panics.
Add the missing test_init() call, matching other tests that use
subsystems requiring initialization.
Closes#12604
- Remove unreachable error message in `handle_env_return()`
While we could have put an empty block in `handle_env_return()` and
removed the condition on `NotFound` in `erase()`, we prefered to use
`unreachable!` in case `handle_env_return()` gets called in new scenarios
in the future
- Make reachable the error message when asking to show a slice
Part of #12603
With empty argument, `realpath` skips all processing, so the error
message, based on `errno`, was unrelated and changed depending on what
failed before. E.g:
```
$ builtin realpath "" /tmp "" /no-exist ""
builtin realpath: : Resource temporarily unavailable
/tmp
builtin realpath: : Invalid argument
/dont-exist
builtin realpath: : No such file or directory
```
Part of #12603
`to_stdout` is set to `true` if and only if `argv` is not empty.
- `argv` length and `to_stdout` are redundant, so we can remove `to_stdout`
- some tests in `validate_read_args` are necessarily false
Part of #12603
The previous handling was unnecessarily complex and had a bug introduced
by porting from C++ to Rust: The substrings `\0x0B` and `\0x0C` in Rust
mean `\0` (the NUL character) followed by the regular characters `0B`
and `0C`, respectively, so feature names starting or ending with these
characters would have these characters stripped away.
Replace this handling by built-in functionality, and simplify some
syntax. We now trim all whitespace, instead of just certain ASCII
characters, but I think there is no reason to limit trimming to ASCII.
Closes#12592
Return None rather than -1 for nonprintables. We probably still
differ from wcwidth which is bad (given we use the same name), but
hopefully not in a way that matters.
Fixes 146384abc6 (Stop using wcwidth entirely, 2026-03-15).
wcwidth isn't a great idea - it returns "-1" for anything it doesn't
know and non-printables, which can easily break text.
It is also unclear that it would be accurate to the system console,
and that's a minority use-case over using ssh to access older systems.
Additionally, it means we use one less function from libc and
simplifies the code.
Closes#12562
"Emoji width" refers to the width of emoji codepoints. Since Unicode
9, they're classified as "wide" according to
TR11 (https://www.unicode.org/reports/tr11/).
Unicode 9 was released in 2016, and this slowly percolated into C
libraries and terminals. Glibc updated its default in 2.26, released
in August 2017.
Until now, we'd guess support for unicode 9 by checking the system
wcwidth function for an emoji - if it returned 2, we'd set our emoji
width to 2 as well.
However, that's a problem in the common case of using ssh to connect
to an old server - modern desktop OS, old server LTS OS, boom.
So now we instead just figure you've got a system that's *displaying*
the emoji that has been updated in the last 9 years.
In effect we're putting the burden on those who run old RHEL et al as
their client OS. They need to set $fish_emoji_width to 1.
Fixes#12500
Part of #12562
Commit 7ef4e7dfe7 (Time out terminal queries after a while,
2025-09-21) though that "2 seconds ought to be enough for anyone".
But that's not true in practice: when rebooting a macOS system, it
can take longer. Let's see if 10 seconds is enough. It should be fine
to have such a high timeout since this shouldn't happen in other cases.
Closes#12571
Add support for the SHELL_PROMPT_PREFIX, SHELL_PROMPT_SUFFIX, and
SHELL_WELCOME environment variables as standardized by systemd v257.
SHELL_PROMPT_PREFIX and SHELL_PROMPT_SUFFIX are automatically prepended
and appended to the left prompt at the shell level, so all prompts
(default, custom, and sample) pick them up without modification.
SHELL_WELCOME is displayed after the greeting when an interactive shell
starts.
These variables provide a standard interface for tools like systemd's
run0 to communicate session context to the shell.
Fixes https://github.com/fish-shell/fish-shell/issues/10924Closes#12570
The `forward-word` readline command on "a-a-a" is wrong (jumps to
"a"); on "aa-aa-aa" it's right (jumps to "-"); that's a regression
from bbb2f0de8d (feat(vi-mode): make word movements vi-compliant,
2026-01-10).
The is_word_end check for ForwardWordEmacs only tests for blank
(whitespace) after the current char. In the Punctuation style, words
also end before punctuation.
Fix this.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
See https://github.com/fish-shell/fish-shell/issues/12543#issuecomment-4125223455
`set_color normal` is too ambiguous and easily misinterpreted since
it actually reset all colors and modes instead of resetting just
the foreground color as one without prior knowledge might expect.
Closes#12548
Repro (with default prompt):
$ HOME=$PWD target/debug/fish -C '
function sleep_func; sleep 1; false; end
commandline "sleep 2 | sleep 3 | sleep 4 | sleep_func"
'
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
johannes@e15 ~> sleep 2 | sleep 3 | sleep 4 | sleep_func
^Zfish: Job 1, 'sleep 2 | sleep 3 | sleep 4 | s…' has stopped
johannes@e15 ~ [0|SIGTSTP|SIGTSTP|1]>
I'm not sure why the first sleep is not reported as stopped.
Co-authored-by: Lieuwe Rooijakkers <lieuwerooijakkers@gmail.com>
Fixes issue #12301Closes#12550