As of 7640e95bd7 (Create user config file/directories only on
first startup again, 2025-12-29) we interpret absence of the
__fish_initialized variable as "this is the first run, so create
~/.config/fish/conf.d etc.".
As reported in #11226, the presence of this universal variable causes
friction to users who track ~/.config/fish/fish_variables in version
control.
Also, __fish_initialized is the only universal variable we create
by default.
Use another way to detect the first run: since we already create
~/.config/fish on every run, assume that a successful mkdir() means
we should also create the subdirectories.
This has false negatives (if the user already created the directory)
and false positives (if the user doesn't want ~/.config/fish to exist)
but at least the latter should not really matter because historically
we always created it, at least for ~/.config/fish/fish_variables.
Alternatively, we could create a file at ~/.cache/fish/first-run-done.
But let's not add more state unless there's a good reason.
Gets rid of another use of XDG_CACHE_HOME; this gets us closer to
a single source of truth. Not super important right now, but will
be helpful if we allow users to override XDG_CACHE_HOME in future.
I guess we could also remove it from create_manpage_completions.py,
that's unnecessary breakage but probably fine since this is not
public-facing.
Commit 77471c500e (path: use `std::io::Error` instead of raw ints,
2026-01-21) inadvertently prints the "Unable to locate config directory"
error when the *data* dir is inaccessible, rather than when the *config*
dir is inaccessible.
Fix that. While at it, rename variables to avoid this issue.
These don't really belong in config.fish. Put them in function files,
like similar wrappers. The redundancy is ugly but if it's a problem
we can use code generation to fix that.
Make abbreviations with `--position anywhere` appear in the completion
menu when tabbing in argument position (e.g. `cat foo.log pg<TAB>`
shows pgr, pgrv, pjq, etc.), not just in command position.
Lift "do_file" into an enum, so we don't have to check "is_redirection"
twice, and can express more accurately the reason for not completing
abbreviations after redirection.
Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>
Closes#12764
This loop iterates over all bindings and crashes if a mode filter
argument is given for a mode that has no binding. Fix this by
continuing the loop earlier.
Fixes 11d6e92cb5 (Show file location when querying bindings with bind, 2025-12-28)
Fixes#12798
On Windows, "chmod 000 .; cd ." doesn't fail, so disable this
test case on Windows. On the Windows CI runner, "uname" prints
"MINGW64_NT-10.0-26100" so we can't use "__fish_is_cygwin".
Simplify these and move the sanitization closer to the point of output.
Stop worrying about control characters beyond what PCRE2 reports in
"[[:cntrl:]]" for clarity.
Strip control characters from VCS branch and state strings before writing them in prompts.
Also route the informative and minimalist sample prompts through prompt_pwd instead of printing PWD directly.
Doing
firefox --pro TAB TAB TAB
results in
firefox --profile --ProfileManager
git-bisect points to 3546ffa3ef (reader
handle_completions(): remove dead filtering code, 2026-01-02)
but that regression has already been fixed by 85e76ba356
(Fix option substr completions not being filtered out, 2026-04-16).
However in between those two commits, the above case has also been
broken by 2f6b1eaaf9 (reader handle_completions(): don't consider
odd replacing completions for common prefix, 2026-01-02)
The first TAB inserts "--profile ", including the trailing space.
However it also shows the completion pager, which means that subsequent
TABs will insert after the space.
The trailing space does not make sense unless we navigate the pager.
Remove it in all cases, to fix this smartcase scenario.
Alternatively, we could start navigating the pager in this case
(and keep the trailing space), but that's probably too inconsistent.
git status --porcelain -z outputs the rename source filename as a
separate NUL-delimited entry after the status line. When the source
filename starts with [ACDMRTU], the informative prompt miscounts it
as an additional staged change.
Part of #12754
When we push a new reader for builtin read, we use the default
CursorSelectionMode::Exclusive, which is wrong in Vi mode.
Add a haphazard fix for that.
This is very ugly, we should improve this.
Closes#12724
This removes the need to double-escape the values on the command line
(once for the command line parser, another for the option handling)
This also brings it in line with the implicit case (`complete cmd ...`)
Fixes#12712Closes#12718
During our one-time migration away from universal variables,
we create ~/.config/fish/conf.d/fish_frozen_theme.fish
if we think that the current theme is different from the default.
The default uvar-backed theme had changed over time, but existing
installations would not be upgraded. Because of this, we have
a heuristic that assumes that values coinciding with historical
default values also stem from a default. Some historical values are
missing. Add them.
There are more left, see 03b23dd1b6 (Update default colors,
2022-01-27).
Fixes#12725
If a user passes "-i" when running a script, they ought to expect
weird behavior i.e. fish might run the user's interactive-only
configuration which might print things to TTY etc. But at least
for our part of the configuration, we can avoid depending on the
user-settable interactive bit.
__fish_config_interactive is already only called when we paint the
first prompt, either for a prompt (which implies we're an interactive
shell) or for builtin read (which does not imply anything about the
interactivity of the shell).
Only print greetings when not in interactive read. Notably, "status
is-interactive-read" is not overridable by the user.
This helps us get rid of more "status is-interactive" switches.
This command
cd $(mktemp -d)
mkdir a "a b"
complete -C": "
prints
a b/
a/
which is wrong ordering.
Usually the trailing slash should not be compared.
Fix this by always sorting slashes first. Not sure if this is correct
for middle slashes but I couldn't find a case where it matters.
Closes#12695
Rewrite the PO file handling logic in Rust and make it available via an
xtask. Replaces the
`build_tools/{update_translations,fish_xgettext}.fish` scripts.
Main benefits:
- Better ergonomics
- Better error handling
- Eliminates the need for a fish executable for updating PO files,
which is particularly useful in CI
- Improved performance, mainly due to concurrent threads working on the
PO files in parallel
The behavior is mostly unchanged, with the minor exception that section
headers for empty sections are now omitted in PO files.
The interface for invoking the tooling is quite different. Instead of
working with flags, `cargo xtask gettext` has 3 subcommands:
- `update` modifies the PO files to match the current sources
- `check` is like update, but instead of modifying the PO files, it
shows diffs between the current version of the PO files and what they
would look like after updating. When there is a difference, the xtask
exits non-zero, making it useful for checks to detect outdated PO
files.
- `new` creates a new PO file for the given language.
Both the `update` and `check` command take any number of file paths to
specify the PO files to consider. If none are specified, all files in
`localization/po/` are considered.
Extracting gettext messages from Rust still requires compiling with the
`gettext-extract` feature active. In situations where compilation is
needed for other purposes as well, it can make sense to only build once
and then tell the gettext xtask about the directory into which the
messages have been extracted. This can be done via the
`--rust-extraction-dir` flag. If we stop having gettext messages in
Rust, this logic can be removed.
Closes#12676
Commit 3534c07584 (Adopt the new AST in parse_execution, 2020-07-03)
added to parse_execution_context_t::run_job_conjunction an early
return when any job in a job conjunction fails to launch. This causes
"nosuchcommand || echo hello" to not execute the continuation.
Fix this by restoring the previous behavior.
Fixes#12654
We define colors in noninteractive shells for historical reasons
(because colors used to be universal variables).
The other potential reason is to get regular syntax highlighting for
commands like:
fish -c 'read --shell'
but if anyone actually uses that they can probably load a theme
explicitly.
Stop defining colors in noninteractive shells. It's usually not
a good idea to make them behave differently from interactive ones,
but color seems only relevant for interactive shells?
Let's see if anyone complains.. we may end up reverting this if people
want to use noninteractive fish to query colors.. but I'm not sure
why that would be necessary.
Closes#12673
"commandline -f repaint" might be triggered for various reasons;
since this sets "last_cmd", it will reset some UI states, notably
pager selection:
1. press tab
2. trigger repaint
3. press tab
The repaint prevents us from selecting the first candidate.
Work around this by ignoring repaint events for the last_cmd logic.
Fixes#12683
As seen in
https://github.com/fish-shell/fish-shell/actions/runs/24944417077/job/73043241890?pr=12171
Failure:
The CHECK on line 12 wants:
prompt 1> source -
which failed to match line stdout:3:
source -
Context:
prompt 0> source
source: missing filename argument or input redirection
source - <= no check matches this, previous check on line 11
prompt 1> source -
prompt 1>
Installing a program like sway to /usr/local installs fish
completions to /usr/local/share/fish/vendor_completions.d/sway.fish.
When $XDG_DATA_DIRS is empty, these will typically not
be picked up.
(Since "__extra_completionsdir" is usually
"/usr/share/fish/vendor_completions.d/", this issue typically only
affects "/usr/share", not "/usr".)
Fix this by using the correct fallback value for XDG_DATA_DIRS.
Fixes#11349Closes#12656
Most notably:
- Unlike MSYS, Cygwin seems to always properly handle symlinks (at least
in common scenarios)
- With ACL, "x" permission also requires "r" do to anything, be it files
or directories
Closes#12642
Intermittent test failure suggests that kill(3p) returns before the
signal is delivered. Fix the failure by waiting until the signal
has been delivered before continuing the test.
Fixes#12635
Commit 3546ffa3ef (reader handle_completions(): remove dead filtering code,
2026-01-02) gives a proof of correctness that still makes sense;
The first lemma ("if will_replace_token") is trivially true, so no need to
assert it.
The second lemma ("if !will_replace_token") is violated in some edge cases:
we claim that given a token "-c", the option completion "--clip" is an exact match,
which is not true, it's a substring match.
Fix that, asserting the claim.
If we get to this code path, we'll only get completions for user
names, so technically the full StringFuzzyMatch with its ranking of
samecase/smartcase/icase (only showing the best) might be overkill,
but it seems like a good idea to treat this the same way as other
completions.
The occasion for this commit is to correct a wrong
StringFuzzyMatch::exact_match() in the icase branch; which will be
important for a following commit. Add a test for that.
If a directory has a control sequence in it, then prompt_pwd (used in
the default prompt) would emit it to the console, which could cause
the terminal to interpret the escape sequence.
Strip control sequences from within prompt_pwd, in the same way as
we do in __fish_paste.fish, to sanitize it.
Closes#12629
The main changes are:
- disabling some checks related to POSIX file permissions when a filesystem is
mounted with "noacl" (default on MSYS2)
- disabling some checks related to symlinks when using fake ones (file copy)
Windows with acl hasn't been tested because 1) Cygwin itself does not have any
Rust package yet to compile fish, and 2) MSYS2 defaults to `noacl`
Part of #12171
- Use the different strings for different checks to more easily narrow down
where a failure happens
- Move CHECK comments outside a `if...else...end` to avoid giving the impression
that the check only runs in the `if` case.
Part of #12171
Previously, SIGTERM immediately re-raised with SIG_DFL, killing
fish without saving history. SIGHUP deferred via a flag but never
re-raised, so the parent saw a normal exit instead of signal death.
Unify both signals: the handler stores the signal number in a single
AtomicI32, the reader loop exits normally, throwing_main() saves
history and re-raises with SIG_DFL so the parent sees WIFSIGNALED.
Fixes#10300Closes#12615