Compare commits

..

395 Commits
4.3.0 ... 4.5.0

Author SHA1 Message Date
Johannes Altmanninger
3478f78a05 Release 4.5.0
Created by ./build_tools/release.sh 4.5.0
2026-02-17 11:32:33 +11:00
Johannes Altmanninger
19cedb01bc changelog 2026-02-17 11:31:17 +11:00
xtqqczze
cb24c4a863 rust: use const_locks feature
Closes #12454
2026-02-17 11:28:58 +11:00
xtqqczze
93478e7c51 simplify serialize_with_vars
Closes #12453
2026-02-17 11:28:58 +11:00
xtqqczze
4eac5f4d9d clippy: fix unused_trait_names lint
https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names

Closes #12450
2026-02-17 11:28:58 +11:00
xtqqczze
e76370b3b7 clippy: fix str_to_string lint
https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string

Closes #12449
2026-02-17 11:13:34 +11:00
Daniel Rainer
1e3153c3fb unicode: fix history search cursor positioning
The cursor position calculation did not correctly account for the width
of Unicode text, resulting in the cursor being placed to far left in
scenarios with characters taking up 2 cells, such as in Chinese text.

Fix this by combining the entire line into a string and computing the
length of the resulting string using the `unicode-width` crate.

This is the first code in the main crate making use of `unicode-width`.
Eventually, we'll probably want to use it in more places, to get better
and consistent results.

Fixes #12444

Closes #12446
2026-02-17 11:13:25 +11:00
Peter Ammon
d0a95e4fde Fix some dumb clipply 2026-02-15 11:25:40 -08:00
Peter Ammon
7174ebbb4b Slightly refactor history tests
This introduces a create_test_history helper function; it looks sort of
silly now but will be useful for upcoming history improvements.
2026-02-15 11:09:14 -08:00
Peter Ammon
e81cec1633 Fix kill-word-vi on macOS/BSDs
`seq 0` outputs nothing on Linux but `1 0` on macOS and BSDs, breaking
this word motion. Fix it by not running `seq 0`.
2026-02-14 12:06:51 -08:00
Johannes Altmanninger
02c04550fd Fix Vi mode cw deleting trailing whitespace
Fixes 38e633d49b (fish_vi_key_bindings: add support for count,
2025-12-16).

Fixes #12443
2026-02-12 14:11:26 +11:00
David Adam
b0bfc7758a CHANGELOG: editing 2026-02-11 16:41:00 +08:00
Weixie Cui
cbf21b13d7 fix: unwrap may cause panic
Closes #12441
2026-02-11 14:49:54 +11:00
Aman Verma
14f3c3e13e completions: use upstream completions for dua
Our completions had gone out-of-date. They didn't support invocations
like `dua path/to/folder`, instead requiring a subcommand.

Closes #12438
2026-02-11 14:46:01 +11:00
Johannes Altmanninger
05ea6e9e66 Remove unused "Output" trait
Continue incidental cleanup started in earlier commit (Reduce the
number of escape sequences for text styles, 2026-02-06).
2026-02-11 14:37:38 +11:00
Nahor
74d2e6d5d8 Rip support for terminfo database
The terminfo database hasn't been used by default since 4.1.0
(see #11344, #11345)

Closes #12439
2026-02-11 14:37:38 +11:00
Nahor
1b8f9c2b03 Initialize Features using a loop
This removes the need to update `Features::new()` every time a feature
is added or removed

Part of #12439
2026-02-11 14:37:38 +11:00
Johannes Altmanninger
491158dfad Output reset_text_face: minor simplification 2026-02-11 14:37:38 +11:00
Johannes Altmanninger
7fa08e31c6 Tweak naming for outputter buffer consumer
Rust idiomatic naming is "take" for "move", also Rust might actually
move the object to a different memory adress, which is fine because
all Rust objects are trivially relocatable.
2026-02-11 14:22:14 +11:00
Nahor
7ac9ce7ffb Reduce the number of escape sequences for text styles
When setting graphics attributes (SGR), combine them into a single
escape sequence to reduce the length of the string and make it slightly
easier to read by people when needed.

Some terminal/parser[^1] may have a cap on the number of parameters, so
we limit the number to 16.

[^1]: https://vt100.net/emu/dec_ansi_parser: "There is no limit to the
number of characters in a parameter string, although a maximum of 16
parameters need be stored. If more than 16 parameters arrive, all
the extra parameters are silently ignored.""

Closes #12429
2026-02-11 14:22:14 +11:00
Johannes Altmanninger
197779697d Remove unused Output implementations
I think the OutputStream was used only in old fish_indent code.
The BufferedOutputter seems obsolete, maybe because DerefMut covers
this.
2026-02-11 14:22:14 +11:00
Weixie Cui
96fabe4b29 fix: update 2026-02-10 14:21:04 +01:00
David Adam
4a4d35b625 completions/dput: add new completions 2026-02-10 16:02:31 +08:00
David Adam
92739a06e2 completions/dscacheutil: correct description 2026-02-10 15:46:53 +08:00
David Adam
244e9586ca completions/chsh: remove stray whitespace 2026-02-10 15:44:29 +08:00
David Adam
13a2ccae66 release.sh: add a couple of explanatory comments 2026-02-10 14:29:16 +08:00
David Adam
ed34845b10 Debian packaging: don't link the .cargo directory from vendor tarball
The main tarball did not contain a .cargo directory, but it does now,
and this symlink ends up in the wrong place and is not needed.
2026-02-10 13:07:01 +08:00
Daniel Rainer
74b104c9f6 cleanup: delete unmaintained Dockerfile
This Dockerfile has been broken for quite a while now, at least since
Rust is required for building fish. No one seems to have complained
about it being broken, so there is no point in keeping it around. The
`docker` directory contains several Dockerfiles which could be used
instead.

https://github.com/fish-shell/fish-shell/pull/12408#discussion_r2770432433

Closes #12435
2026-02-09 12:16:52 +11:00
Francisco Giordano
1f8cdf85b6 Fix ctrl-l interference with history search
Closes #12436
2026-02-09 12:16:33 +11:00
David Adam
dc02a8ce35 CI: run apt-get update before any apt install
Try to avoid surprises down the track by doing this across the board.
2026-02-09 00:04:40 +08:00
Pothi Kalimuthu
65e726324a Fix the link to GitHub repo 2026-02-08 23:38:46 +08:00
Johannes Altmanninger
fe3c42af9e Work around github actions python failure
github actions runners have python 3.12, so the upgrade to debian
stable's 3.13 broke things:

	+ env UV_PYTHON=python uv --no-managed-python lock --check --exclude-newer=2026-02-01T13:00:00Z
	Using CPython 3.12.3 interpreter at: /usr/bin/python
	error: The requested interpreter resolved to Python 3.12.3, which is incompatible with the project's Python requirement: `>=3.13` (from `project.requires-python`)
	Error: Process completed with exit code 2.

Steps:
1. edit pyproject.toml and
2. uv lock --upgrade --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"

In future we should maybe use managed python?
2026-02-08 14:03:53 +11:00
Johannes Altmanninger
28c7e7173f Fix fish_indent_interrupt CI workaround 2026-02-08 14:01:45 +11:00
Johannes Altmanninger
a897a26daa Test Vi mode dfX
Tests d25965afba (Vi mode: hack in support for df and friends,
2026-02-06).
2026-02-08 13:16:07 +11:00
Johannes Altmanninger
ffce214362 Test fish_vi_key_bindings argument
Tests 6d01138797 (Fix vi key bindings regression when called with
argument, 2026-02-04).
2026-02-08 13:16:07 +11:00
Liam Snow
bdb8e4847f dir_iter: handle missing d_type on illumos
Closes #12410
Fixes #11099
2026-02-08 13:16:07 +11:00
Liam Snow
9750d8c3ad path: return Unknown for remoteness check on illumos
Part of #12410
2026-02-08 13:12:41 +11:00
Johannes Altmanninger
dadec16661 path_remoteness: use cfg_if 2026-02-08 13:12:41 +11:00
Liam Snow
b5cd2763db spawn: POSIX_SPAWN flags are not i32 on illumos
Part of #12410
2026-02-08 13:12:41 +11:00
Liam Snow
a08dd70354 env: define _CS_PATH const for illumos
Part of #12410
2026-02-08 13:12:41 +11:00
Liam Snow
5b39d1fc6a ulimit: add illumos to platforms missing MEMLOCK/NPROC limits
Part of #12410
2026-02-08 13:12:41 +11:00
Nahor
38513de954 Remove duplicated code introduced in commit 289057f
Closes #12432
2026-02-08 13:12:41 +11:00
Johannes Altmanninger
cc1ae25c3a Fix kill-a-word kill-inner-word bounds checks if at end of command line
These commands are meant to be used in Vi mode when the cursor is on
a valid character, so there's not much reason to try to make them do
something when the cursor is past-end.  Do nothing, like we already
do for the empty commandline.

Reported in #12430
2026-02-08 13:12:41 +11:00
Johannes Altmanninger
002fa0e791 Fix cursor position after accepting Vi mode autosuggestion
Closes #12430
2026-02-08 13:12:41 +11:00
Jack Pickle
0589de7523 fix git stash completions not working after flags
update __fish_git_stash_not_using_subcommand check for actual subcommands
instead of treating any word after 'stash' as a subcommand.

stay dry by adding__fish_git_stash_is_push helper that matches both implicit and explicit push.

fixes #11307

Closes #12421
2026-02-08 13:12:41 +11:00
Johannes Altmanninger
4e7e0139fd Update to sphinx 9.1
sphinx==9.1.0 depends on Python>=3.12,
so change our pinning policy to fit.
Note we still support Python 3.9 in user-facing code.

Steps:
1. edit updatecli.d/python.yml
2. remove bad "uv lock" from build_tools/update-dependencies.sh
   (didn't respect exclude-newer)
3. updatecli apply --config updatecli.d/python.yml
4. uv lock --upgrade --exclude-newer="$(date --date='7 days ago' --iso-8601)"
2026-02-08 13:12:41 +11:00
Johannes Altmanninger
c044e1d433 update-dependencies.sh: update and pin 3rd party github workflows 2026-02-08 13:12:41 +11:00
Johannes Altmanninger
fb0edf564e Add AGENTS.md
LLM-generated contributions tend to produce too many redundant
comments. Fix that.  This doesn't work OOTB for Claude, but it's easy
to tell it to respect AGENTS.md..
2026-02-08 13:12:41 +11:00
Salman Muin Kayser Chishti
969ce68d5d Upgrade GitHub Actions for Node 24 compatibility
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>

Closes #12422
2026-02-08 12:59:26 +11:00
Salman Muin Kayser Chishti
6ef2e04518 Upgrade GitHub Actions to latest versions
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>

Closes #12423
2026-02-08 12:59:26 +11:00
Johannes Altmanninger
bc84fe9407 tests/pexpects/fish_indent_interrupt: skip in CI
This fails intermittently in CI. Disable it.  We disable a lot of
other tests as well which is why we run tests on developer machines
before pushing to master.

See #12351
2026-02-08 12:59:26 +11:00
Peter Ammon
38e8416da5 test_history_path_detection to adopt custom history directories
Simplifies this test by making it no longer serial.
2026-02-07 12:29:54 -08:00
Peter Ammon
cf0e07aecd test_history_formats to adopt custom history directories
Simplifies this test by making it no longer serial.
2026-02-07 12:23:13 -08:00
Peter Ammon
8e014bbf97 test_history_formats to adopt custom history directories
Simplifies this test by making it no longer serial.
2026-02-07 12:20:11 -08:00
Peter Ammon
f9013ad7a6 Allow histories to be created outside of the default path
Currently history files are written to the "data directory"
(XDG_DATA_HOME). This is awkward in testing since we have to put files
into this directory.

Allow histories to have their own directory, so that they don't
interfere with other files. This will help simplify some tests.

Adopt this in some (but not all) history tests.
2026-02-07 12:20:09 -08:00
Johannes Altmanninger
8d2d50573a changelog 2026-02-06 15:38:50 +11:00
Johannes Altmanninger
d25965afba Vi mode: hack in support for df and friends
Commit 38e633d49b (fish_vi_key_bindings: add support for count,
2025-12-16) introduced an operator mode which kind of makes a lot of
sense for today's fish.  If we end up needing more flexibility and
tighter integration, we might want to move some of this into core.

Unfortunately the change is at odds with our cursed forward-jump
implementation.  The forward-jump special input function works by
magically reading the next key from stdin, which causes problems when
we are executing a script:

	commandline -f begin-selection
	commandline -f forward-jump
	commandline -f end-selection

here end-selection will be executed immediately
and forward-jump fails to wait for a keystroke.

We should get rid of forward-jump implementation.

For now, replace only the broken thing with a dedicated bind mode
for each of f/F/t/T.

Fixes #12417
2026-02-06 15:38:50 +11:00
Johannes Altmanninger
6f895935a9 bind.rst: update meaning of --silent option
We can probably even remove this, since the remaining behavior doesn't
seem useful.
2026-02-06 15:19:49 +11:00
Johannes Altmanninger
70ebc969f9 fish_vi_key_bindings: remove unused "bind -s" option
Commit 46d1334f95 (Silence bind errors in default key bindings,
2017-10-03) worked around errors arising from "bind -k".
We no longer use that, so remove that.
2026-02-06 15:19:49 +11:00
Simon Olofsson
6d01138797 Fix vi key bindings regression when called with argument
Commit bbb2f0de8d added a ctrl-right binding to override the shared
binding with forward-word-vi for vi-compliance. However, it incorrectly
passed $argv which caused the error:
  "bind: cannot parse key 'default'"

when calling fish_vi_key_bindings with a mode argument like:
  fish_vi_key_bindings "default"

Fix that.

Co-Authored-By: Johannes Altmanninger <aclopte@gmail.com>

Closes #12413
2026-02-06 12:40:53 +11:00
Daniel Rainer
779f1371a1 l10n: localize colon followed by content
Some languages have different conventions regarding colons. In order to
handle this better in cases with non-constant strings, as is the case in
`describe_with_prefix`, use localization to figure out how colons should
be localized.

This approach fixes the extra whitespace inserted after Chinese colons.
See #12405.

Closes #12414
2026-02-06 12:05:03 +11:00
Daniel Rainer
5d24a846e3 cleanup: simplify pushing to WString
Part of #12414
2026-02-06 12:05:02 +11:00
madblobfish
c4f1c25a87 completion/rfkill: add completions for toggle command
"toggle" command was not considered in the completions

Closes #12412
2026-02-06 12:05:02 +11:00
Daniel Rainer
7d754b2865 ci: update before apt install
GitHub does not consistently provide images with up-to-date package
lists, causing `apt install` failures.
Work around this by updating the package list.
https://github.com/actions/runner-images/issues/13636
https://github.com/actions/runner-images/issues/12599

Closes #12418
2026-02-06 11:42:30 +11:00
Johannes Altmanninger
232e38f105 Fix rst syntax in changelog 2026-02-06 11:41:23 +11:00
Tristan Partin
725afc71fd Fix fish_mode_prompt formatting error
"variable" was in the formatted portion of the text when it should not
have been.

Signed-off-by: Tristan Partin <tristan@partin.io>
2026-02-05 07:14:21 +01:00
David Adam
6d60eaf04e CMake: use cargo variable to call cargo
Fixes the build where a custom cargo binary is passed in.
2026-02-03 11:10:34 +08:00
Johannes Altmanninger
7c1d8cf4f1 start new cycle
Created by ./build_tools/release.sh 4.4.0
2026-02-03 12:39:46 +11:00
Johannes Altmanninger
e1a0df68fc Release 4.4.0
Created by ./build_tools/release.sh 4.4.0
2026-02-03 12:11:51 +11:00
Johannes Altmanninger
14832117b6 release.sh: regression fixes
The error check in make_tarball.sh gives a false negative when building
from a release tag.
2026-02-03 12:10:40 +11:00
Johannes Altmanninger
56deaafb34 Remove extra newline in error log
Introduced in 149594f974 (Initial revision, 2005-09-20).
2026-02-03 11:58:52 +11:00
Johannes Altmanninger
b8b36f3293 de.po: fix typo 2026-02-03 11:58:52 +11:00
Daniel Rainer
a9ab708494 gettext: enforce trimmed msgids
Now that we have trimmed our msgids, add an assertion to ensure that
they stay trimmed. Note that we don't check msgstrs. We could do so when
building the maps which get put into the executable, but there we also
include messages originating from fish scripts, and there we don't
enforce trimmed messages, so limiting the checks to only messages
originating from the Rust code there would not be trivial.

Closes #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
a33363902c gettext: don't put tabs into localizable strings
This allows the strings to be simpler, and keeps
localization-independent formatting out of localizable strings.

Tab-based formatting is brittle, and should probably be reworked.

This is also the final piece to have no more leading or trailing
whitespace in our localizable strings in the Rust code.

Part of #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
3ca3d10e28 gettext: simplify localizable string
Only the first line needs to be localized, so don't include the rest for
localization.

Part of #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
05c236481f gettext: remove leading spaces
Now, non of the localizable strings in Rust have any more leading or
trailing spaces, making it easier to use them with Fluent. Again, there
are some slight issues with Chinese translations, which now have
additional whitespace.

Part of #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
25e2bdb7de gettext: remove trailing spaces
Another step towards trimming the localizable strings. Fix
inconsistencies in some of the translations. Translations for zh_CN are
not entirely consistent between using ASCII colons and `:`. If the
latter is used, which also happens for zh_TW, no trailing space is
present in the translation even if it is present in the msgid. This
means that the new code will show excessive whitespace after these
colons, since a regular space is inserted outside of the localization
code. While this might not be pretty, I don't think it really breaks
anything, and not having to deal with trailing whitespace simplifies
working with Fluent.

Part of #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
12bd4cf9e9 l10n: don't localize PID
This simplifies our table formatting. Since none of our translations
modify the string `PID`, it seems reasonable to assume that the term
does not benefit from localization.

Part of #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
0d79681070 gettext: remove remaining trailing newlines
Complete the work started in
e78e3f16e (gettext: remove trailing newlines, 2026-01-30)

Now, there are no remaining trailing newlines in the localizable strings
in our Rust sources. A bit more work is still needed to get rid of a few
leading and trailing spaces, the goal being that for all localizable
strings `s` in our Rust sources, `s == s.trim()`.

Includes a bit of drive-by refactoring.

Part of #12405
2026-02-03 11:58:52 +11:00
Daniel Rainer
47d7f52149 editorconfig: use indent_size=2 for Vagrantfiles
This matches the existing formatting of the files.

Part of #12408
2026-02-03 11:26:59 +11:00
Daniel Rainer
000a5cc80a format: add final newlines to files
Part of #12408
2026-02-03 11:26:59 +11:00
Daniel Rainer
66f51f5166 editorconfig: remove special handling for *.in
The file we have whose names end in `.in` are not Makefiles, so there is
no reason to deviate from our default formatting for them.

Part of #12408
2026-02-03 11:26:59 +11:00
Daniel Rainer
7360deee74 format: remove trailing whitespace
Part of #12408
2026-02-03 11:26:59 +11:00
Daniel Rainer
e0916e793b format: don't use tabs for indentation
This is done in accordance with our editorconfig file.

Part of #12408
2026-02-03 11:26:59 +11:00
Daniel Rainer
f43a79bc13 editorconfig: set indent_style=tab for makefiles
Replace mixed indentation in GNUmakefile used for alignment by a single
tab increase in indentation

Part of #12408
2026-02-03 11:26:59 +11:00
Daniel Rainer
fb56a6a54d editorconfig: set YAML indent_size to 2
This matches what we're already using, except for a few inconsistencies.

Part of #12408
2026-02-03 11:26:59 +11:00
Timen Zandbergen
f6936f222a Add strikethrough TextStyling
Closes #12369
2026-02-03 11:26:59 +11:00
Johannes Altmanninger
553aa40b34 Fix crash in Vi's "dge" due to misplaced cursor
kill-selection does not respect fish_cursor_end_mode; fix that.
2026-02-02 12:49:39 +11:00
Johannes Altmanninger
ac9c339e5a Vi bindings: remove unused special input functions
These were only needed before the new approach from 38e633d49b
(fish_vi_key_bindings: add support for count, 2025-12-16).
2026-02-02 12:33:50 +11:00
Johannes Altmanninger
0ae3b33f2f tarball: remove unused git-archive knob
The problem introduced by 081c469f6f (tarball: include
.cargo/config.toml again, 2026-01-13) seems solved by 09d8570922
(debian packaging: generate patches automatically, 2026-02-01),
so the argument to make_tarball.sh is no longer needed.
2026-02-02 12:22:45 +11:00
Johannes Altmanninger
6dbe5637ef redirect_tty_after_sighup: minor simplification 2026-02-02 11:25:03 +11:00
David Adam
09d8570922 debian packaging: generate patches automatically
The vendor tarball drops a new version of .cargo/config into place,
which the Debian toolchain does not like (as it is an unexpected
modification of the original tarball). Tell dpkg-source to generate a
patch automatically, as trying to do it in fish's packaging scripts is
brittle.
2026-02-01 22:59:12 +08:00
Johannes Altmanninger
4ab433418e Update changelog 2026-02-01 20:13:13 +11:00
Johannes Altmanninger
73e3b47ebd tarball: capture Git version
Commit 92dae88f62 (tarball: remove redundant "version" file,
2026-01-12) committed to using Cargo.toml as single source of truth
for our version string.

This works fine for tarballs built from a tag, but those built from
an arbitrary Git commit will show a misleading version ("4.3.3"
instead of something like "4.3.3-190-g460081725b5-dirty").

Fix this by copying the Git version to the tarball's Cargo.{toml,lock}.

It's not clear if we really need this (it's only for nightly builds)
so we might end up reverting this.

Ref: https://matrix.to/#/!YLTeaulxSDauOOxBoR:matrix.org/$BdRagDGCV-8yVjBs0i3QyWUdBK820vTmjuSBqgpsuJY

Note that OBS builds are probably still broken from 081c469f6f
(tarball: include .cargo/config.toml again, 2026-01-13); see
https://github.com/fish-shell/fish-shell/pull/12292#discussion_r2694000477
2026-02-01 20:13:13 +11:00
Johannes Altmanninger
bb7dce941a termios: use nix wrapper 2026-02-01 20:13:13 +11:00
Johannes Altmanninger
55e9255264 termios: remove repeated assignment to global 2026-02-01 19:58:51 +11:00
Johannes Altmanninger
cf53ef02c4 termios: simplify set-operations for toggling flow control 2026-02-01 19:58:23 +11:00
Johannes Altmanninger
c8ea418154 Update Cargo dependencies 2026-02-01 12:14:36 +11:00
Peter Ammon
44920b4be8 Pre-allocate certain string capacities
Reduce some allocation churn.
2026-01-31 10:51:33 -08:00
Tiago de Paula
b980e50acf __fish_complete_suffix: Remove –foo= from token
Otherwise we get --foo=--foo= suggested.

Fixes #12374. Related to #9538.

Closes #12400
2026-01-31 14:12:50 +11:00
Daniel Rainer
e78e3f16e1 gettext: remove trailing newlines
Remove trailing newlines from msgids. Newlines do not need to be
localized, so translators should not have to care about them.

In addition to simplifying the jobs of translators using gettext, not
having trailing newlines also makes it easier to port localizations to
Fluent.

Closes #12399
2026-01-31 14:12:50 +11:00
Johannes Altmanninger
714c2f7373 builtin commandline: minor refactoring 2026-01-31 14:12:50 +11:00
Johannes Altmanninger
f1036beb1f Use std::iter::once 2026-01-31 14:12:50 +11:00
Daniel Rainer
bd4e55b149 io: delete unused append_narrow function
Closes #12396
2026-01-31 14:12:50 +11:00
Daniel Rainer
22e5e63a65 io: remove append_char
Its functionality is subsumed by `append`. In some cases, the call can
be omitted and replaced by an `appendln`.

Part of #12396
2026-01-31 14:12:49 +11:00
Daniel Rainer
414dba994f io: accept intoCharIter in more functions
This makes them more general than the previous versions which expected
`&wstr`. It comes at the cost of additional eager calls to `chars()`.

To implement `appendln` without having to call `append` twice, implement
`IntoCharIter` for chained iterators whose elements are both the kind of
iterator specified by `IntoCharIter`.

Because `IntoCharIter` is not implemented for owned types to avoid
allocations, some call sites which used to pass `WString` need to be
updated to pass references instead, which constitutes the bulk of the
changes in this commit.

Part of #12396
2026-01-31 14:12:49 +11:00
xtqqczze
934def3b75 clippy fix semicolon_if_nothing_returned lint
https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned

Closes #12395
2026-01-31 14:12:49 +11:00
JANMESH ARUN SHEWALE
74c2837d87 dnf: support subcommand aliases in completions
Closes #12393
2026-01-31 14:12:49 +11:00
xtqqczze
40c480c2cf clippy: fix nightly while_let_loop lint
https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop

Closes #12391
2026-01-31 14:12:49 +11:00
Johannes Altmanninger
ad82c7ebf1 fish_update_completions: fix escaping of | and ~
Fixes #12345
2026-01-31 14:12:49 +11:00
xtqqczze
86b126628c fix: update SourceLineCache to use NonNull for last_source pointer
Closes #12401
2026-01-31 14:12:49 +11:00
Johannes Altmanninger
24ee3afdae Address MSRV clippy lint 2026-01-31 14:12:49 +11:00
Peter Ammon
08a9f5e683 Remove LineCounter
LineCounter was a funny type which aided in eagerly computing line
numbers. It's no longer needed as we lazily compute lines, so remove it.
2026-01-30 09:30:57 -08:00
Peter Ammon
ffb09a429d Lazily compute line numbers
Prior to this commit, the line number of each block (say, begin, or
function call, etc) was computed eagerly and stored in the block. However
this line number is only used in rare cases, when printing backtraces.

Instead of storing the line number, store the node in the abstract syntax
tree along with the (shared) source text, and only compute the line numbers
when a backtrace is required.

This is about a ~4% improvement on seq_echo benchmark.
2026-01-30 09:27:17 -08:00
Johannes Altmanninger
87048330b7 Update to Rust 1.93 2026-01-30 15:34:43 +11:00
andreacfromtheapp
c2f7c5fbeb feat: add catpuccin themes
Apologies about the unsolicited PR. I hope this helps.

Adding Catppuccin themes from: https://github.com/catppuccin/fish

They note:

Q: Where's the Latte theme?
A: All three themes contain Latte as the light variant. Install any of
them, and then set your system or terminal theme to light mode.

Not sure about Fish Shell policy to keep them up to date here, I hope
this is not a nuissance.

Closes #12299
2026-01-30 15:34:43 +11:00
Tiago de Paula
4fde7e4f2f completion/quilt: list predefined spec filters
Closes #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
3369cf9a70 completion/quilt: complete grep
Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
ff5e83cd42 completion/quilt: complete diff context sizes
Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
193a564c8f completion/quilt: add patch name suggestions
Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
296e2369d7 completion/quilt: handle refresh -z[name]
Avoid suggestions like `-zh`, `-z=[name]` and `-z [name]`.

Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
16cb7cc550 completion/quilt: add switches for 0.69
Add missing switch options for quilt, synced with the latest version.

Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
c86b8c8130 completion/quilt: make subcommands exclusive
Includes the missing `revert` command.

Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
1b1badfd02 completion/quilt: split commands
Part of #12370
2026-01-30 15:34:43 +11:00
Tiago de Paula
b7f990fe4a completion/quilt: update descriptions
Part of #12370
2026-01-30 15:34:43 +11:00
Razzi Abuissa
e639efb77c completions/git: add some completions for git rev-parse
I took the verbiage mostly from `man git-rev-parse` (slightly shortened
since space is at a premium for completions):

       --is-inside-git-dir
           When the current working directory is below the repository directory print "true", otherwise "false".

       --is-inside-work-tree
           When the current working directory is inside the work tree of the repository print "true", otherwise "false".

Closes #12382
2026-01-30 15:34:43 +11:00
Luc J. Bourhis
41c2f8b9e0 completions/pandoc: deprecate --self-contained, add replacements
Closes #12385
2026-01-30 15:34:43 +11:00
Johannes Altmanninger
78b33eb47b changelog 2026-01-30 15:34:43 +11:00
xtqqczze
7e1164762c clippy fix nightly map_unwrap_or lint
https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or

Closes #12376
2026-01-30 15:34:43 +11:00
Tiago de Paula
8a9203e8e3 completion/pacman: suggest absolute paths for -F
Closes #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
99cae405ca completion/pacman: completions for -S $repo/$package
Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
f11c9c5105 completion/pacman: fix argument order
- fixed argument order for `-Sg`, `-Sl`, `-Qg`, `-Qp` and `-Qo`
- removed completions for `-Dk`
- added "Package Group" completions for `-Q` and `-R`
- remove package completions for `-F `

Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
f8f98d2581 completion/pacman: dump options for --arch and -U
Add lists with most known archictures for Arch Linux Ports and
all possible options for PKGEXT. Also simplified option list
for `--color` and improved completions for `--config`.

Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
e7648662ae completion/pacman: list only installed groups for -Qg
Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
69c4021646 completion/pacman: update descriptions
Some description were slightly off from what they currently do.
Also added `disable-sandbox*` options for Pacman 7.

Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
d0084cb9df completion/pacman: enforce consistent style
Keep the same argument order and string quoting across the file.
Also avoid variables for simple function calls.

Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
5f59582e63 completion/pacman: use function to check main operation
Should be simpler and safer than double expansion of a loop variable.

Part of #12347
2026-01-30 15:34:43 +11:00
Tiago de Paula
c89c516467 completion/pacman: sort options like the man page
Should hopefully be easier to update options it in the future.

Part of #12347
2026-01-30 15:34:43 +11:00
Johannes Altmanninger
40bf0e0ce9 tests/man: skip if documentation has not been built
Closes #12291
2026-01-30 15:34:43 +11:00
xtqqczze
07e3e924c3 build(deps): bump actions/checkout to 6 2026-01-30 08:06:16 +08:00
Johannes Altmanninger
f2bb5b5d7f Work around intermittent test_complete failure
test_complete and test_history_races both have the #[serial]
annotations.  Still, "cargo test" sometimes fails with

   thread 'complete::tests::test_complete' (370134) panicked at             │
   src/complete.rs:2814:13:                                                 │
   assertion `left == right` failed                                         │
     left: [("TTestWithColon", false), ("history-races-test-balloon",       │
   true), ("test/", true)]                                                  │
    right: [("TTestWithColon", false), ("test/", true)]                     │

I don't understand why this happens (filesystem race condition?)
but let's fix it by having "test_complete" ignore files from other
tests.

Fixes #12184
2026-01-27 22:10:05 +11:00
Johannes Altmanninger
3bd6771ae7 screen: fix test name 2026-01-27 22:06:52 +11:00
Johannes Altmanninger
527176ca97 test_driver: reduce default concurrency to reduce flakiness
Unlimited concurrency frequently makes system tests fail when when run
as "tests/test_driver.py" or "cargo xtask check".  Add a workaround
until we fix them.

Use $(nproc), which is already the default for RUST_TEST_THREADS
and CARGO_BUILD_JOBS, and it works pretty reliably on my laptop.
It's possible that we can increase it a bit to make it faster,
but until the tests are improved, we can't increase it much without
risking failures.

Ref: https://github.com/fish-shell/fish-shell/pull/12292#discussion_r2713370474

Tracking issue: #11815
2026-01-27 22:06:52 +11:00
Daniel Rainer
0cd8d64607 nix: get user info without direct libc calls
The `get_home` function has no users, so delete it instead of rewriting
it.

Closes #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
68e408b930 nix: use gethostname
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
74b03de449 nix: use killpg
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
ac1c77d499 wutil: use std file utils instead of libc in test
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
c7aaf4249a wutil: use std::fs::rename
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
11dda63f7b wutil: use std::fs::read_link
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
04b799c0c5 nix: use access
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
44f9363e39 wutil: libc::unlink -> std::fs::remove_file
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
43513d3fca sleep: use stdlib instead of libc::usleep
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
e41f2f6588 nix: use fchdir
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
17d870a842 nix: allow converting fish Pid to nix Pid
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
ab02558181 nix: use waitpid for disowned pids
Use the `nix::sys::wait::waitpid` function instead of libc's `waitpid`
for reaping disowned PIDs.
Note that this commit changes behavior. Previously, any updates received
for the PID were interpreted as the process having exited. However,
`waitpid` also indicates that processes were stopped or continued, or,
on Linux, that a ptrace event occurred. If the process has not exited,
it cannot be reaped, so I think it should be kept in the list of
disowned PIDs.

Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
fa1a128aeb nix: use getpgrp wrapper
Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
1f0a7e7697 nix: use wrappers for geteuid and getegid
The nix crate offers thin wrappers around these functions which allow us
to get rid of our own libc wrappers, reducing the amount of code marked
`unsafe`.

Part of #12380
2026-01-27 21:50:54 +11:00
Daniel Rainer
cce788388f encoding: remove or annotate WString::from_str
We want to discourage direct conversion from regular Rust strings to
`WString`, since our `WString`s are assumed to use the PUA encoding
scheme. If the input string contains certain PUA codepoints and the
resulting `WString` is decoded, it would not result in the same bytes as
the UTF-8 encoding of the input string. To avoid this, use
`str2wcstring`.

There are two remaining usages of `WString::from_str` which have been
annotated to indicate why they are there.
2026-01-27 11:14:04 +01:00
Daniel Rainer
a93c24b084 refactor: improve widestring conversion utils
- Don't use `WString::from_str` for `str`s which are available at
  compile-time. Use `L!(input).to_owned()` instead. The reason for this
  is that `WString::from_str` can cause problems if the input contains
  PUA bytes which we use for our custom encoding scheme. In such cases,
  `bytes2wcstring` should be used, to avoid problems when decoding the
  `WString`. Removing harmless usages of `WString::from_str` allows us
  to focus on the potentially dangerous ones which don't convert
  `str`'s that are compiled into the binary.
- Make `cstr2wcstring` actually take `CStr` as its input. The former
  version was only used in one place, and the conversion to `CStr`
  should happen there, where it can be checked that the conversion makes
  sense and is safe. The new version is used in
  `src/env/environmant.rs`, to avoid `to_bytes()` calls cluttering the
  code there.
- Add `osstr2wcstring` function. This function also works for `Path`.
  Now, these types can be converted to widestrings with much less
  syntactic clutter.
2026-01-27 11:14:04 +01:00
Johannes Altmanninger
6a2f531f9b try_apply_edit_to_autosuggestion: update icase matched codepoint counter
This function mutates the autosuggestion's search_string_range without
updating the number of matched codepoints accordingly, fix that.
Fixes 78f4541116 (reader: fix try_apply_edit_to_autosuggestion false
positive, 2026-01-22).

Fixes #12377
2026-01-26 02:22:48 +11:00
Johannes Altmanninger
eda1694581 try_apply_edit_to_autosuggestion: new test 2026-01-26 02:22:48 +11:00
Johannes Altmanninger
1854d03b95 wildcard: remove unneeded lock for local variable 2026-01-26 02:22:48 +11:00
xtqqczze
2180777f73 use nix::sys::signal::SigSet
Closes #12335
2026-01-25 03:40:12 +01:00
xtqqczze
f2fc779c94 Update feature(let_chains) TODO
Closes #12318
2026-01-25 03:40:12 +01:00
xtqqczze
3a024b6d8f Inline LazyLock helpers
Closes #12333
2026-01-25 03:40:12 +01:00
Next Alone
33d9957bcb completions: add claude command completions
Add tab completion support for claude CLI tool, including:
  - Top-level commands (doctor, install, mcp, plugin, setup-token, update)
  - Global options for model, agent, system prompt configuration
  - Tool and permission management options
  - MCP server configuration
  - IDE and Chrome integration settings
  - Output format and session management options
  - Sub-command specific help

Closes #12361
2026-01-25 03:40:12 +01:00
Daniel Rainer
28c0a8bc81 path: de-duplicate getter logic
Also remove comments which were already obsolete before these changes.

Closes #12365
2026-01-25 03:40:12 +01:00
Daniel Rainer
77471c500e path: use std::io::Error instead of raw ints
The functions we use already give us errors from `std`, so we might as
well use them, instead of converting them to raw libc errors.

Part of #12365
2026-01-25 03:40:12 +01:00
Johannes Altmanninger
98eaef2778 reader: remove obsolete workaround
We no longer use libc's locale-aware tolower etc.
2026-01-25 03:40:12 +01:00
Johannes Altmanninger
78f4541116 reader: fix try_apply_edit_to_autosuggestion false positive
Given command line ": i" and suggestion ": İnstall" whose lowercase
mapping is ": i\u{307}nstall", most of our code assumes that typing
"n" invalidates the autosuggestion.

This doesn't happen because try_apply_edit_to_autosuggestion thinks
that "i" has fully matched the suggestion's "İ".

Fix this inconsistency by recording the exact number of lowercase
characters already matched in the suggestion; then we only need to
compare the rest.

This allows us to restore an important invariant; this reverts
1d2a5997cc (Remove broken assert, 2026-01-21).
2026-01-25 03:40:12 +01:00
Johannes Altmanninger
2a3fe73a6d reader: unit-test try_apply_edit_to_autosuggestion 2026-01-25 03:38:11 +01:00
Johannes Altmanninger
9c3cb154d3 Revert "reader: Remove two more case-insensitive asserts"
See following commits.
This reverts commit 0778919e7f.
2026-01-25 03:38:11 +01:00
Johannes Altmanninger
4e68a36141 terminal: name some enum fields 2026-01-25 03:19:22 +01:00
Daniel Rainer
b62fa06753 asan: remove redundant handling
The special exit handling when running with address sanitization no
longer seems necessary. Our tests all pass without it.

Similarly, the leak sanitizer suppression is no longer needed, since we
don't get any warnings when running our checks without it.

Because our Rust code no longer has any ASAN-specific behavior, we don't
need the `asan` feature anymore.

Closes #12366
2026-01-25 03:19:22 +01:00
xtqqczze
17129f64db assertions: use assert_matches!
Closes #12348
2026-01-25 03:19:22 +01:00
Johannes Altmanninger
524c73d8b7 history search: remove unused "match everything" search type
The "match everything" search type is weird because it implicitly
overrules SearchFlags::IGNORE_CASE and the search string.

Remove it.
2026-01-25 03:19:22 +01:00
Johannes Altmanninger
78e91e6bc4 history search: prune call graph 2026-01-25 03:15:33 +01:00
Johannes Altmanninger
9d260cac0b reader autosuggestion: doc comments 2026-01-25 03:14:43 +01:00
Johannes Altmanninger
01d91dd65c history: unexport test-only constructors 2026-01-25 03:14:43 +01:00
xtqqczze
69d96e1e66 build.rs: inline fn canonicalize 2026-01-24 16:05:07 -08:00
Peter Ammon
1ab12dadac Remove "parse_util" prefix from functions in that module
This was a naming holdover from C++
2026-01-24 14:35:46 -08:00
Peter Ammon
a7dc701f26 Make ParseTreeFlags an ordinary struct instead of bitflags
Simplifies some code.
2026-01-24 14:05:34 -08:00
Peter Ammon
590ad9cd93 Rename var_err_len into VAR_ERR_LEN 2026-01-24 14:05:34 -08:00
Peter Ammon
cbca5177ea Adopt get_or_insert_with_default()
Minor cleanup of path.rs
2026-01-24 12:21:03 -08:00
Peter Ammon
e3105bee39 Fix out-of-tree builds
This fixes cmake builds outside of the fish-shell source tree.
2026-01-24 12:05:14 -08:00
Fabian Boehm
0778919e7f reader: Remove two more case-insensitive asserts
See #12326. Turns out there wasn't just one assert, it was three.

These can be triggered by entering (interactively) "flatpak in"
after having "flatpak İnstall" in your history so it's autosuggested.

Removing the asserts it generally *works* okay, and there's absolutely
no good reason for turning this into a crash.
2026-01-24 13:41:46 +01:00
Fabian Boehm
e36684786f completions/systemctl: List disabled units for start as well
I'm pretty sure this used to list "dead", now it shows "disabled".

Fixes #12368
2026-01-22 06:52:22 +01:00
Fabian Boehm
8fcd6524db Remove unused variable 2026-01-21 17:47:43 +01:00
Daniel Rainer
23fb01f921 cmake: don't build fish_test_helper
Our test driver (`tests/test_driver.py`) already builds this
unconditionally on each run, so there is no point in having CMake build
the binary as well. If we want to reduce the effort of rebuilding the
test helper on each invocation of the test driver, we could consider
some other caching approach, but it should work for non-CMake builds as
well. I consider this a low priority, since building the executable only
takes a few 10s of milliseconds on relatively modern hardware.

Closes #12364
2026-01-21 17:43:26 +01:00
Daniel Rainer
14915108d1 xtask: add man-pages task
Use the new xtask in CMake builds.

Closes #12292
2026-01-21 17:43:26 +01:00
Daniel Rainer
20cc07c5cd xtask: add html-docs task
This task is a bit annoying to implement because `sphinx-build` depends
on `fish_indent`, so that needs to be built, and the binary location
added to `PATH`.

Building `fish_indent` can be avoided by setting the `--fish-indent`
argument to the path to an existing `fish_indent` executable.

Use the new xtask in CMake builds. To do so without having to add
configuration options about where to put the docs, or having to copy
them from the default location to `build/user_doc`, we instead get rid of
the `user_doc` directory and change the code which accesses the docs to
read them from the non-configurable default output location in Cargo's
target directory.

Part of #12292
2026-01-21 17:43:26 +01:00
Daniel Rainer
2f74785620 xtask: add initial setup
This is an initial implementation of the cargo xtask approach
https://github.com/matklad/cargo-xtask

For now, the only xtask is "check", which can be triggered by running
`cargo xtask check`. It is a thin wrapper around `build_tools/check.sh`.

Part of #12292
2026-01-21 17:43:26 +01:00
Daniel Rainer
03894fea3c cmake: remove unnecessary dir nesting
The `cargo` directory in the CMake build directory is only used to store
Cargo's build output, as would be done by `target` when building without
CMake. But instead of putting the build output directly into the `cargo`
directory, a nested `build` directory was used. There is no point in
this nesting, so remove it.

Closes #12352
2026-01-21 17:43:26 +01:00
Daniel Rainer
f407ca18a4 docs: fix doc dir computation
`FISH_CMAKE_BINARY_DIR` is the top-level CMake output directory, not its
subdirectory used as the target directory by Cargo. So far, this has not
caused issues because CMake builds explicitly call `sphinx-build` to
build the man pages, instead of using the Rust crate for embedding them.

Closes #12354
2026-01-21 17:43:26 +01:00
Daniel Rainer
07394a1621 style.fish: canonicalize workspace root
Use a canonicalized absolute path for the workspace root to make errors
containing paths easier to read.

Closes #12359
2026-01-21 17:43:26 +01:00
Fabian Boehm
8bf416cd64 completions/flatpak: Fix broken cache usage
This can't work because __fish_cached calls out to /bin/sh for fairly dubious reasons,
and it tries to get it to run a function.

So just run the *command* that the function runs and do the added filtering outside.
2026-01-21 17:31:20 +01:00
Fabian Boehm
1d2a5997cc Remove broken assert
See #12326

I have been able to trigger this pretty reliably, and the simplest fix is
to... just not assert out when we would return anyway.

It doesn't reproduce with `commandline` because it needs the
suggestion to exist, it'll happen when you enter the "n" of "install"
if the suggestion is "İnstall" (i.e. uppercase turkish dotted i)

In general asserts in the reader make for a terrible experience.
2026-01-21 17:28:14 +01:00
Daniel Rainer
53e6758cc3 cmake: use variable for sphinx output dir
This clarifies the usage of the directory, simplifies usages, and makes
it easier to move the directory.

Closes #12353
2026-01-20 16:24:56 +00:00
Fabian Boehm
2df1fa90f1 completions/cargo: Uncouple from rustup completions
We could technically extract this into a function, but it's a trivial
one-liner.

This allows rustup completions to be independently overridden.

Fixes #12357
2026-01-20 16:43:54 +01:00
neglu
a454d53c28 realpath: allow multiple arguments
Closes #12344
2026-01-18 13:25:00 +01:00
xtqqczze
6d5948d814 update sphinx-doc url
Closes #12346
2026-01-18 13:25:00 +01:00
xtqqczze
18061ad177 refactor list_available_languages
remove the unnecessary temporary HashSet

Closes #12343
2026-01-18 13:25:00 +01:00
xtqqczze
8eb59bc500 clippy: fix stable_sort_primitive lint
https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive

Part of #12343
2026-01-18 13:25:00 +01:00
xtqqczze
ddb3046ccd clippy: fix clippy::manual_assert lint
https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert

Closes #12341
2026-01-18 13:25:00 +01:00
xtqqczze
5e3bc86268 clippy: fix explicit_into_iter_loop lint
https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop

Closes #12340
2026-01-18 13:25:00 +01:00
xtqqczze
9dfb5705c5 Remove unnecessary qualification from size_of
Closes #12339
2026-01-18 13:25:00 +01:00
xtqqczze
66ea0f78be Use inline const expressions
Inline const expressions were stablised in Rust 1.79

Part of #12339
2026-01-18 13:25:00 +01:00
xtqqczze
c99c84558f clippy: fix format_push_string lint
https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string

Closes #12338
2026-01-18 13:25:00 +01:00
Daniel Rainer
d35edcf5fa assertions: use assert_ne! where possible
Closes #12336
2026-01-18 13:25:00 +01:00
Daniel Rainer
8718d62b11 assertions: use assert_eq! where possible
Using `assert_eq!` instead of `assert!` has the advantage that when the
assertion fails, the debug representation of both sides will be shown,
which can provide more information about the failure than only seeing
that the assertion failed.

Part of #12336
2026-01-18 13:25:00 +01:00
Fabian Boehm
939fa25a5b Disable fossil prompt by default
Apparently `fossil changes --differ` is slow.

Fixes #12342
2026-01-17 15:53:41 +01:00
Daniel Rainer
93eb9ee4d8 wgetopt: extract into new crate
Move `src/wgetopt.rs` into a new dedicated crate.

Closes #12317
2026-01-16 16:22:34 +01:00
Daniel Rainer
154c095e66 wgetopt: remove dependencies on main lib
Only items from `fish_widestring`'s prelude are used, so depend on that
directly, to prepare for extraction.

Part of #12317
2026-01-16 16:22:33 +01:00
Johannes Altmanninger
50a97856dc Update rust-embed 2026-01-16 16:09:13 +01:00
Daniel Rainer
02cb93311a docs: use shared output dir for all docs
Prepare for having HTML docs in the build output as well as man pages.
To avoid cluttering the top-level build dir, introduce a new `fish-docs`
directory, and put directories for the different types of docs in it.

The doctrees (cache files for sphinx) will be put parallel to the output
directories, to have a clear separation between desired output and cache
files. Note that we use separate cache directories for the different
builders, since sphinx does not handle shared caches well.

Part of #12292
2026-01-16 11:40:47 +01:00
xtqqczze
49b3721b75 clippy: fix unnecessary_semicolon lint
https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_semicolon

Closes #12328
2026-01-16 11:40:47 +01:00
xtqqczze
db18515aed clippy: fix range_plus_one lint
https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one

Closes #12329
2026-01-16 11:40:44 +01:00
xtqqczze
2708191d3c clippy: fix ref_option lint
https://rust-lang.github.io/rust-clippy/master/index.html#ref_option

Closes #12327
2026-01-16 11:39:22 +01:00
xtqqczze
16612c6e49 Use Rust new_uninit feature
new_uninit was stabilised in Rust 1.82

Closes #12323
2026-01-16 11:39:22 +01:00
xtqqczze
40d45ee21e clippy: fix ptr_offset_by_literal lint
https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_by_literal

Closes #12322
2026-01-16 11:39:22 +01:00
xtqqczze
3df3b52b18 clippy: fix mut_mut lint
https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut

Closes #12321
2026-01-16 11:39:22 +01:00
xtqqczze
efbf8b0203 Inline have_proc_stat
Closes #12319
2026-01-16 11:39:22 +01:00
Johannes Altmanninger
c84c006f42 util: remove code clone
Not in the right module yet but it's a start.
2026-01-16 11:39:22 +01:00
Daniel Rainer
97e0eda477 util: extract into new crate
Move `src/util.rs` into a new dedicated crate.

Closes #12316
2026-01-15 17:26:29 +01:00
Daniel Rainer
f1d78103e4 util: remove dependencies on main crate
Only items from `fish_widestring`'s prelude are used, so depend on that
directly, to prepare for extraction.

Part of #12316
2026-01-15 17:22:08 +01:00
Daniel Rainer
7072eec225 wcstringutil: extract into new crate
This moves the code from `src/wcstringutil.rs` into a new dedicated
crate.

Closes #12315
2026-01-15 17:20:07 +01:00
Daniel Rainer
9b1cda9025 wcstringutil: remove dependencies on main lib
This is done in preparation for extraction into its own crate.

Part of #12315
2026-01-15 17:19:00 +01:00
Daniel Rainer
2c06731a14 color: extract into new crate
This moves the code from `src/color.rs` into a new dedicated crate.

Closes #12311
2026-01-15 17:17:40 +01:00
Daniel Rainer
b14692cb95 color: be explicit about widestring usage
This eliminates the dependency on the main library crate, preparing
`src/color.rs` for extraction into a dedicated crate.

Part of #12311
2026-01-15 17:17:40 +01:00
Daniel Rainer
253f6726c0 widestring: rename wchar -> widestring
This is a more descriptive name for the crate.

https://github.com/fish-shell/fish-shell/pull/12311#discussion_r2681191208

Closes #12313
2026-01-15 17:17:40 +01:00
Daniel Rainer
36db3b7f3f wchar: remove dependency on fish_common
Now, the `fish_wchar` crate does not have any local dependencies, making
it easy to depend on it in other crates without worrying about cyclic
dependencies.

Additionally, remove the (non-fish) `widestring` crate as a direct
dependency of the main crate.
Now, only the `fish_wchar` and `fish_printf` crates directly depend on
`widestring`. `fish_printf` could also depend on `fish_wchar`, but I
left that as is since `fish_printf` was published, so depending on a
crate which is not published to crates.io does not seem like a good
idea.

Part of #12313
2026-01-15 17:17:20 +01:00
Daniel Rainer
385cdef89b wchar: don't wrap utf32str macro
There is no need to wrap this macro. We can just introduce `L` as an
alias.

https://github.com/fish-shell/fish-shell/pull/12311#discussion_r2681174391

Closes #12312
2026-01-15 17:07:13 +01:00
Johannes Altmanninger
72870d8331 forward-char: fix buffer overflow
Fixes 701c5da823 (forward-char: respect fish_cursor_end_mode again,
2026-01-12).

Fixes #12325
2026-01-14 18:28:04 +01:00
Johannes Altmanninger
081c469f6f tarball: include .cargo/config.toml again
The problem worked around by commit e4674cd7b5 (.cargo/config.toml:
exclude from tarball, 2025-01-12) is as follows:
our OBS packages are built by doing something like

	tar xf $tarball && tar xf $vendor_tarball

except that the tool apparently break when a file is present in
both tarballs.

Our workaround is to not include .cargo/config.toml in the tarball.

The workaround seems too broad. It need not affect all tarballs
but only the ones used by OBS. We want to add xtask aliases to
.cargo/config.toml. They will be used by CMake targets (sphinx-doc),
so they should be available to tarball consumers.

Restrict the scope of the workaround: add back .cargo/config.toml
to the export, and allow opting in to this behavioer by passing a
negative pathspec

	build_tools/make_tarball.sh :/!.cargo/config.toml
2026-01-14 18:28:04 +01:00
phanium
1affa7a16e bind: nextd-or-forward-word/prevd-or-backward-word don't emit nextd/prevd
Fixes bbb2f0de8d (feat(vi-mode): make word movements
vi-compliant,2026-01-10)

Closes #12320
2026-01-13 11:59:48 +01:00
Johannes Altmanninger
701c5da823 forward-char: respect fish_cursor_end_mode again
Fixes bbb2f0de8d (feat(vi-mode): make word movements vi-compliant,
2026-01-10).

When setting cursor pos, we need to make sure to call update_buff_pos,
which knows whether the one-past-last character ought to be selectable.
2026-01-13 11:59:48 +01:00
Daniel Rainer
860f75ee97 tarballs: append to cargo config
This allows putting other configuration into the config file without it
being deleted for tarballs.

https://github.com/fish-shell/fish-shell/pull/12292#discussion_r2678697643
2026-01-12 19:49:32 +01:00
Daniel Rainer
334710ebf7 cargo: remove obsolete config
The settings we had in `.cargo/config.toml` correspond to the defaults
in Rust 2024, so there is no point in setting them explicitly.

https://github.com/fish-shell/fish-shell/pull/12292#discussion_r2678697643
2026-01-12 19:49:32 +01:00
phanium
b15da3e118 bind: forward-word to go to end of line again
Fixes bbb2f0de8d (feat(vi-mode): make word movements vi-compliant,
2026-01-10)

Closes #12314
2026-01-12 19:40:45 +01:00
David Adam
f1a9b9bf43 CHANGELOG: editing for clarity and formatting 2026-01-12 23:10:01 +08:00
David Adam
abffe983f8 Debian packaging: correct sphinx dependency name 2026-01-12 22:19:42 +08:00
Johannes Altmanninger
b363e4afe7 tests/checks/version: support shallow clones 2026-01-12 13:04:56 +01:00
Johannes Altmanninger
b60582ff75 build.rs: remove code duplication in version computation
get_version() in build.rs duplicates some logic in
build_tools/git_version_gen.sh. There are some differences

1. When computing the Git hash, get_version() falls back to reading
  .git/HEAD directly if "git describe" fails
1.1. Git is not installed
     Not sure if this is a good strong reason. If you don't have
     Git installed, how would you have created ".git"? If the exact
     Git SHA is important, maybe we should use something like gitoxide
     for this rather than implementing our own.
1.2. there is a permission problem
     The case mentiond in 0083192fcb (Read git SHA ourselves if it
     is unavailable, 2024-12-09) doesn't seem to happen with current
     versions of Git: "sudo git describe" works fine.  Something like
     "sudo -u postgres git describe" doesn't. We could support that
     but let's wait until we know of a use case.
1.3 there are no tags
    (when doing "cargo install --git", as mentioned in 0dfc490721
    (build.rs: Use Cargo_PKG_VERSION if no version could be found,
    2024-06-10)).
    Missing tags are no longer a problem because we read the version
    from Cargo.toml now. Tweak the script to make sure that the
    version is 4.3.3-g${git_sha} instead of just ${git_sha}.

2. get_version() falls back to jj too.
   That was added for jj workspaces that don't have a Git worktree;
   but those should be one their way out; when using jj's Git backend,
   all workspaces will get an associated worktre.
2026-01-12 12:17:48 +01:00
Johannes Altmanninger
92dae88f62 tarball: remove redundant "version" file
Use the version in Cargo.toml instead.

Print a warning if the Cargo.toml version is not a prefix of the Git
version.  This can happen legit scenarios, see 0dfc490721 (build.rs:
Use Cargo_PKG_VERSION if no version could be found, 2024-06-10)
but the next commit will fix that.

Also remove stale comments in git_version_gen.sh.
2026-01-12 12:17:48 +01:00
Johannes Altmanninger
f7d9c92820 git_version_gen: don't silence stderr
This should never fail, and error output doesn't hurt here.
2026-01-12 12:17:48 +01:00
Daniel Rainer
860bba759d git_version_gen: always output to stdout
Now that the script no longer writes to files, we can remove the
`--stdout` flag and instead output to stdout by default.

Closes #12307
2026-01-12 12:17:27 +01:00
Daniel Rainer
564ef66665 git_version_gen: remove obsolete code
The `git_version_gen.sh` script is no longer used to write any files, so
remove the logic for it.

This code included handling for permission problems when running `git
describe`. It was introduced by
15f1b5f368, but unfortunately without
mentioning which CVE caused the change in git. I found CVE-2022-24765,
which was published not long before the commit was made, and its
description looks like it's fitting. On a recent version of git
(2.52.0), I had no problems running `make && sudo make install`, so it
seems like this issue might no longer be relevant. One suboptimal thing
to note is that `sudo make install` currently builds `fish_indent` even
if it was built via `make` before, which is not great, but unrelated to
these changes.

Part of #12307
2026-01-12 12:17:27 +01:00
Daniel Rainer
c8642efeb0 cmake: remove version file handling
This version file handling is no longer in use. Version strings are
generated on-demand using either `build_tools/git_version_gen.sh` or the
Rust implementation in `build.rs`. This makes the CMake code for version
handling obsolete.

Note that the current handling for version strings in Rust builds is
imperfect. If none of the build inputs change, the Rust code will not be
rebuilt, meaning the version strings in the executables are not updated.
This has been the case for a while and is not caused by this patch
series. This trade-off has been deemed worthwhile, because it simplifies
the implementation and eliminates the need for rebuilds when only the
version string changed. Because any changes to the actual input files
will trigger rebuilds, the version string will reference a commit which
is close enough to the actual version that it should not cause problems.

Part of #12307
2026-01-12 12:17:27 +01:00
Daniel Rainer
309bb778af cmake: use script for setting version in fish.pc
The `build_tools/git_version_gen.sh` script can be used to determine the
appropriate version string. With this change, the convoluted version
file generation logic in CMake is no longer used and can subsequently be
removed.

Part of #12307
2026-01-12 12:17:27 +01:00
Daniel Rainer
36c340b40f cmake: remove obsolete version env var
The `FISH_BUILD_VERSION_FILE` variable was only read in
`doc_src/conf.py`. There, it can be replaced by the
`build_tools/git_version_gen.sh` script, which takes the version from a
file called `version` at the workspace root if one exists, and otherwise
from git. This should cover all cases where the docs are built, so there
is no need to keep using the `FISH_BUILD_VERSION_FILE` variable.

Part of #12307
2026-01-12 12:17:27 +01:00
Johannes Altmanninger
a79c54be66 shell_modes: clear the FLUSHO flag
On macOS, pressing ctrl-o (VDISCARD) before starting fish will discard
all terminal output, from shell or its child processes. This breaks
querying and seems like something we don't want to support, so maybe
disable it?

Not sure if term_fix_external_modes needs it too, add it I guess.

Fixes #12304
2026-01-12 12:17:27 +01:00
Johannes Altmanninger
143c8c4ffd termios: slim down reference graph 2026-01-12 09:44:42 +01:00
Daniel Rainer
116bcdac28 common: extract more code into separate crate
This extracts the remaining code from `src/common.rs` which does not
depend on other parts of the main library crate. No functional changes.

Closes #12310
2026-01-12 09:44:42 +01:00
Daniel Rainer
4762d6a0a7 common: extract constants into crate
Part of #12310
2026-01-12 07:37:23 +01:00
Daniel Rainer
6e00deffd0 common: remove unused constant
There was only a single usage of `EMPTY_STRING`, and there it was
immediately dereferenced, so use an empty static `&wstr` instead and
remove the `WString` constant.

Closes #12309
2026-01-12 07:34:23 +01:00
Johannes Altmanninger
34f3e5ef23 __fish_change_key_bindings: remove dead code
We embed share/functions/*.fish now, so we should be able to assume
that "set --no-event" works.
2026-01-11 21:12:40 +01:00
Johannes Altmanninger
b9dfbcee13 Fix spurious status=1 from calling binding function from config
A command like "fish -C fish_default_key_bindings" shows a exit status
of 1 in the default prompt. Fix that.
2026-01-11 21:12:40 +01:00
Johannes Altmanninger
de2ac37c92 Extract typedef for AtomicU64 2026-01-11 21:12:40 +01:00
Daniel Rainer
a194a557c5 cmake: remove obsolete doc setup
The `conf.py` file that was copied is not used for building. It seems
that it was used as a mechanism for triggering rebuilds on changes to
the original file, but in the current setup, `sphinx-build` is called
unconditionally when the relevant targets are built, so there is no
point in keeping on copying the `conf.py` file.

Closes #12303
2026-01-11 21:12:40 +01:00
iksuddle
8e34dc4cdb bind: show all modes by default
Ensure `bind` builtin lists binds for all modes if `--mode` is not
given.

- The `get` function in `src/input.rs` now takes an optional bind
  mode and returns a list of input mappings (binds).
- The `list_one` function in `src/builtins/bind.rs` lists binds in
  the results returned by `get`.
- Creating the output string for a bind has been extracted to its
  own function: `BuiltinBind::generate_output_string`.
- The `bind_mode_given` option has been removed.

Fixes #12214

Closes #12285
2026-01-11 21:12:40 +01:00
SharzyL
bbb2f0de8d feat(vi-mode): make word movements vi-compliant
- The behavior of `{,d}{w,W}`, `{,d}{,g}{e,E}` bindings in vi-mode is
  now more compatible with vim, except that the underscore is not a
  keyword (which can be achieved by setting `set iskeyword-=_` in vim).

- Add commands `{forward,kill}-{word,bigword}-vi`,
  `{forward,backward,kill,backward-kill}-{word,bigword}-end` and
  `kill-{a,inner}-{word,bigword}` corresponding to above-mentioned
  bindings.

- Closes #10393.

Closes #12269

Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>
2026-01-11 21:12:40 +01:00
SharzyL
4c3fcc7b16 feat(wchar): add word_char module for vi-mode character classification
Part of #12269
2026-01-11 21:12:40 +01:00
Johannes Altmanninger
f33fef3ca3 word_motion: rewrite state machine logic
The state machine uses polymorphic state stored as a raw u8.  This is
not very idiomatic Rust.

Define proper enum types. This helps the upcoming behavior change.

Ref: https://github.com/fish-shell/fish-shell/pull/12269#discussion_r2679005387

Co-authored-by: SharzyL <me@sharzy.in>
2026-01-11 21:12:40 +01:00
Johannes Altmanninger
10537df82d word_motion: remove unused function 2026-01-11 21:12:40 +01:00
SharzyL
8af6b07d07 word_motion: tighten test assertions 2026-01-11 21:12:40 +01:00
SharzyL
c014c95e1b word_motion: deduplicate type
Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>
2026-01-11 21:12:40 +01:00
Johannes Altmanninger
bd86f53b9f Extract MoveWordStateMachine to a reader submodule
This has a very small dependency on the tokenizer.
2026-01-11 21:12:40 +01:00
SharzyL
042117ee30 refactor(tokenizer): use unqualified names in word motion tests
Add `use Direction::*` and `use MoveWordStyle::*` in tests to reduce
verbosity. Reformat tests to one-line style and reorder by test type.
No behavior change.

Part of #12269
2026-01-11 18:37:14 +01:00
Heitor Augusto
38e633d49b fish_vi_key_bindings: add support for count
Closes #12170
2026-01-11 18:37:14 +01:00
Peter Ammon
ce286010a8 Remove a stale comment 2026-01-10 09:52:18 -08:00
Wang Bing-hua
ef18b6684b history: fix highlighting for inline timestamps
Highlighting the entire record caused custom prefixes to be
parsed as command syntax. For example, using:
`history --show-time="[%Y-%m-%d %H:%M:%S] "`
resulted in the timestamp being colorized as shell code.

Move highlighting inside format_history_record to process the
command string before the timestamp is prepended.

Closes #12300
2026-01-10 13:46:46 +01:00
Ilya Grigoriev
ab984f98ab completions: add completions for ijq
https://codeberg.org/gpanders/ijq
https://github.com/gpanders/ijq

For comparison purposes, here's the output of `ijq --help`
as of ijq 1.2.0:

```
ijq - interactive jq

Usage: ijq [-cnsrRMSV] [-f file] [filter] [files ...]

Options:
  -C	force colorized JSON, even if writing to a pipe or file
  -H string
    	set path to history file. Set to '' to disable history. (default "/Users/ilyagr/Library/Application Support/ijq/history")
  -M	monochrome (don't colorize JSON)
  -R	read raw strings, not JSON texts
  -S	sort keys of objects on output
  -V	print version and exit
  -c	compact instead of pretty-printed output
  -f filename
    	read initial filter from filename
  -hide-input-pane
    	hide input (left) viewing pane
  -jqbin string
    	name of or path to jq binary to use (default "jq")
  -n	use `null` as the single input value
  -r	output raw strings, not JSON texts
  -s	read (slurp) all inputs into an array; apply filter to it
```

Closes #12297
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
cfadb2de36 Temporary hack to restore historical behavior of "read --prompt-str ''"
As mentioned in commit 289057f981 (reset_abandoning_line: actually
clear line on first prompt, 2025-11-11), we want to eventually allow
builtin read with a starting cursor with x>0.  Until then, add a hack
to restore historical behavior in the case that users observed.

See #12296
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
3d0b378c40 test_wwrite_to_fd: remove redundant assertion
Also should have been -1, not 0.
Ref: https://github.com/fish-shell/fish-shell/pull/12199#discussion_r2677040787
2026-01-10 13:46:46 +01:00
Daniel Rainer
aa56359834 gettext: use wgettext_fmt for formatting
Using `wgettext_fmt` instead of `wgettext` + `sprintf` in these cases
allows for proper localization of the colons.
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
a0a60c2d29 completions/git: cover up some plumbing subcommands
Also hide show-branch, it doesn't seem very useful?

See 400d5281f4 (r174249792)
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
324223ddff history: assume pager supports color sequences
Git does the same, try: git -c core.pager='cat -v' show

Closes #12298
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
58e7a50de8 Fix line-wise autosuggestion false positive when line doesn't start command
To reduce the likelihood of false positive line-wise history
autosuggestions, we only them when the cursor's line starts a new
process ("parse_util_process_extent").

There are still false positives. Given

	$ true &&
          somecommand
	$ echo "
	someothercommand
	"

typing "some" suggests "someothercommand" from history even though
that was not actually used as command.

Fix this by using similar rules for suggestion candidates.

Might help #12290
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
739b82c34d Fix line-wise autosuggestion false positive when command doesn't exist
If my history has

	git clean -dxf &&
	./autogen.sh &&
	./configure --prefix=...

then autosuggestions for "./conf" will show the third line, even if
./configure does not exist.

This is because even for line-wise autosuggestions, we only check
validity of the first command ("git"). Fix that by checking
the command from the line that's actually suggested.

The next commit will fix the issue that line-wise autosuggestions
may not actually be commands.

See also #12290
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
b5bf9d17e3 OSC 7: also escape hostname
I think gethostname() is not guaranteed to return only URL-safe
characters, so better safe than sorry.
2026-01-10 13:46:46 +01:00
Johannes Altmanninger
917fb024ea end-of-buffer: accept autosuggestion if already at ned 2026-01-09 10:29:10 +01:00
Johannes Altmanninger
7a05ea0f93 Reapply "cmake: rename WITH_GETTEXT to WITH_MESSAGE_LOCALIZATION"
This reverts commit 94fdb36f6b.
2026-01-07 08:56:03 +01:00
Johannes Altmanninger
5d75c47fcc start new cycle
Created by ./build_tools/release.sh 4.3.3
2026-01-07 08:52:39 +01:00
Johannes Altmanninger
c98fd886fd Release 4.3.3
Created by ./build_tools/release.sh 4.3.3
2026-01-07 08:34:20 +01:00
Johannes Altmanninger
94fdb36f6b Revert "cmake: rename WITH_GETTEXT to WITH_MESSAGE_LOCALIZATION"
This reverts commit 77e1aead40 for the
patch release.
2026-01-07 08:32:33 +01:00
Johannes Altmanninger
d1a40ace7d changelog: fix RST syntax 2026-01-07 08:32:20 +01:00
Johannes Altmanninger
dd4d69a288 cirrus: fix FreeBSD pkg install failure
Multiple PRs fail with

	pkg: Repository FreeBSD-ports cannot be opened. 'pkg update' required
	Updating database digests format: . done
	pkg: No packages available to install matching 'cmake-core' have been found in the repositories
2026-01-07 08:01:21 +01:00
Daniel Rainer
e16ea8df11 sync: once_cell::sync::OnceCell -> std::sync::OnceLock
Rust 1.70 stabilized `std::sync::OnceLock`, which replaces
`once_cell::sync::OnceCell`.

With this, we only have a single remaining direct dependency on
`once_cell`: `VAR_DISPATCH_TABLE` in `src/env_dispatch.rs`, where we use
`Lazy::get`. This can be replaced with `LazyLock::get` once our MSRV
reaches 1.94, where the function is stabilized.

At the moment, `serial_test` depends on `once_cell`, so even if we
eliminate it as a direct dependency, it will remain a transitive
dependency.

Closes #12289
2026-01-07 07:59:24 +01:00
Daniel Rainer
80e1942980 sync: once_cell::sync::Lazy -> std::sync::LazyLock
Rust 1.80 stabilized `std::sync::LazyLock`, which replaces
`once_cell::sync::Lazy`. There is one exception in
`src/env_dispatch.rs`, which still uses the `once_cell` variant, since
the code there relies on `Lazy::get`, which also exists for `LazyLock`,
but will only be stabilized in Rust 1.94, so we can't use it yet.

Part of #12289
2026-01-07 07:59:24 +01:00
Johannes Altmanninger
99109278a6 Enable color theme reporting again on tmux >= 3.7
Color theme reporting has race conditions, so we might want to disable
it until we have fixed those. Not sure.

At least the tmux-specific issue hsa been fixed,
so treat new tmux like other terminals.
See https://github.com/tmux/tmux/issues/4787#issuecomment-3716135010

See #12261
2026-01-07 07:59:24 +01:00
Johannes Altmanninger
f924a880c8 Update changelog 2026-01-07 07:59:23 +01:00
Johannes Altmanninger
5683d26d24 github: update pull request template
Repeat here that 'Fixes #' should go into commit message, and remove
redundant PR description.
2026-01-07 07:35:30 +01:00
Lumynous
740aef06df l10n(zh-TW): Complete untranslated strings
Closes #12288
2026-01-07 07:35:30 +01:00
Daniel Rainer
557f6d1743 check: allow overriding default Rust toolchain
This is useful for running the checks with a toolchain which is
different from the default toolchain, for example to check if everything
works with our MSRV, or on beta/nightly toolchains. Additionally,
providing a way to run using the nightly toolchain allows writing
wrappers around `check.sh` which make use of nightly-only features.

The toolchain could be changed using `rustup toolchain default`, but if
the toolchain should only be used for a specific run, this is
inconvenient, and it does not allow for concurrent builds using
different toolchains.

Closes #12281
2026-01-06 17:54:56 +00:00
Johannes Altmanninger
8d257f5c57 completions/fastboot: one item per line 2026-01-06 14:29:32 +01:00
Johannes Altmanninger
d880a14b1a changelog: add sections 2026-01-06 14:29:32 +01:00
Next Alone
d4fcc00821 completions(fastboot): sync partitions from Xiaomi images
Signed-off-by: Next Alone <12210746+NextAlone@users.noreply.github.com>

Closes #12283
2026-01-06 14:29:32 +01:00
Johannes Altmanninger
6d8bb292ec fish_config theme choose: overwrite color-aware theme's vars also if called from config
Webconfig persists themes to ~/.config/fish/conf.d/fish_frozen_theme.fish
(the name is due to historical reasons).

That file's color variables have no "--theme=foo" annotations, which
means that fish_config can't distinguish them from other "user-set"
values.  We can change this in future, but that doesn't affect the
following fix.

A "fish_config theme choose foo" command is supposed to
overwrite all variables that are defined in "foo.theme".
If the theme is color-theme-aware *and* this command runs before
$fish_terminal_color_theme is initialized, we delay loading of the
theme until that initialization happens.  But the --on-variable
invocation won't have the override bit set, thus it will not touch
variables that don't have "--theme=*" value.  Fix this by clearing
immediately the variables mentioned in the theme.

Fixes #12278

While at it, tweak the error message for this command because it's
not an internal error:

	fish -c 'echo yes | fish_config theme save tomorrow'
2026-01-06 14:29:32 +01:00
Lennard Hofmann
c638401469 Speedup syntax highlighting of redirection targets
Instead of checking twice whether the redirection target is a valid file,
use the return value from test_redirection_target().

Closes #12276
2026-01-06 10:39:58 +01:00
Fabian Boehm
5930574d8a README: Mention cargo
A bit pedantic, we're also not mentioning that you need a linker, but
oh well.

Fixes #12277
2026-01-05 17:16:33 +01:00
Daniel Rainer
fdef7c8689 l10n: add initialize_localization function
This replaces `initialize_gettext`. It is only defined when the
`localize-messages` feature is enabled, to avoid giving the impression
that it does anything useful when the feature is disabled.

With this change, Fluent will be initialized as well once it is added,
without requiring any additional code for initialization.

Closes #12190
2026-01-05 15:12:34 +00:00
Daniel Rainer
5c36a1be1b l10n: create gettext submodule
Put the gettext-specific code into `localization/gettext`.

Part of #12190
2026-01-05 15:12:34 +00:00
Daniel Rainer
14f747019b l10n: create localization/settings
Extract the language selection code from the gettext crate, and to a
lesser extent from `src/localization/mod.rs` and put it into
`src/localization/settings.rs`. No functional changes are intended.

Aside from better separation of concerns, this refactoring makes it
feasible to reuse the language selection logic for Fluent later on.

Part of #12190
2026-01-05 15:12:34 +00:00
Johannes Altmanninger
d7d5d2a9be completions/make: fix on OpenBSD/FreeBSD
Tested with the POSIX Makefile from https://github.com/mawww/kakoune

Closes #12272
2026-01-05 12:52:00 +01:00
Johannes Altmanninger
750955171a __fish_migrate: don't leak standard file descriptors
The __fish_migrate.fish function spawns a "sh -c 'sleep 7' &" child
process that inherits stdin/stdout/stderr file descriptors fish.

This means that if the app running "fish
tests/checks/__fish_migrate.fish" actually waits for fish to close its
standard file descriptors, it will appear to hang for 7 seconds. Fix
that by closing the file descriptors in the background job when
creating it.

Closes #12271
2026-01-05 12:52:00 +01:00
Denys Zhak
36fd93215b fish_indent: Keep braces on same line in if/while conditions
Closes #12270
2026-01-05 12:50:19 +01:00
Lennard Hofmann
6e7353170a Highlight valid paths in redirection targets
Closes #12260
2026-01-05 12:50:19 +01:00
Peter Ammon
62cc117c12 Minor refactoring of add_to_history
Preparation for other refactoring in the future.
2026-01-04 12:33:53 -08:00
Peter Ammon
af00695383 Clean up replace_home_directory_with_tilde
Fix a stale comment and add a test.
2026-01-04 11:08:21 -08:00
Johannes Altmanninger
85ac91eb2b fish_config theme choose: fix Tomorrow always using light version
The backward compat hack canonicalization caused us to always treat
"tomorrow" light theme.

Restrict this hack to the legacy name (Tomorrow); don't do it when
the new canonical name (tomorrow) is used.  The same issue does not
affect other themes because their legacy names always have a "light"
or 'dark' suffix, which means that the canonical name is different,
so the legacy hacks don't affect the canonical name.

Fixes #12266
2026-01-04 13:08:26 +01:00
Johannes Altmanninger
d1ed582919 fish_config theme choose: apply backwards compat hacks only to sample themes
This logic exists to not break user configurations as we renamed
themes.  But user-sourced themes haven't been renamed.

(It's also questionable whether we should really have these compat
hacks; they might cause confusion in the long run).
2026-01-04 13:08:26 +01:00
xtqqczze
06a14c4a76 clippy: fix assigning_clones lint
https://rust-lang.github.io/rust-clippy/master/index.html#assigning_clones

Closes #12267
2026-01-04 13:08:26 +01:00
takeokunn
400d5281f4 feat(git-completion): add missing options and completions for commands
- Add missing options and completions for fetch, show-branch, am,
  checkout, archive, grep, pull, push, revert, rm, config, clean, and
  other commands
- Replace TODO comments with actual option completions for improved
  usability
- Ensure all new options have appropriate descriptions and argument
  handling for fish shell completion

Closes #12263
2026-01-04 13:08:26 +01:00
Johannes Altmanninger
50778670fb Disable color theme reporting in tmux for now
Due to the way tmux implements it, color theme reporting
causes issues when typing commands really quickly (such as
when synthesizing keys).  We're working on fixing this, see
https://github.com/tmux/tmux/issues/4787#issuecomment-3707866550
Disable it for now. AFAIK other terminals are not affected.

Closes #12261
2026-01-04 13:08:26 +01:00
WitherZuo
9037cd779d Optimize functions page style of fish_config.
- Fix the background color of .function-body in dark mode to improve readability.
- Switch to Tomorrow Night Bright color theme for better contrast and readability in dark mode.
- Format all stylesheets of fish_config.

Closes #12257
2026-01-04 13:08:26 +01:00
phanium
c23a4cbd9f Add --color option for some builtins
Fixes #9716

Closes #12252
2026-01-04 13:08:26 +01:00
Johannes Altmanninger
5d8f7801f7 builtin fish_indent/fish_key_reader: call help from existing fish process
Something like

	PATH=mypath builtin fish_indent --help

runs "fish -c '__fish_print_help fish_indent'" internally.  Since we
don't call setenv(), the PATH update doesn't reach the child shell.
Fix this by using what other builtins use if we are one (i.e. if we
have a Parser in context).

Fixes #12229
Maybe also #12085
2026-01-04 13:08:26 +01:00
Johannes Altmanninger
756134cf2b test/tmux-complete3: fail more reliably 2026-01-04 13:08:26 +01:00
Johannes Altmanninger
c16677fd6f tty_handoff: use Drop consistently
We sometimes use explicit reclaim() and sometimes rely on the drop
implementation. This adds an unnecesary step to reading all uses of
this code.  Make this consistent. Use drop everywhere though we could
use explicit reclaim too.
2026-01-04 13:08:26 +01:00
Johannes Altmanninger
13bc514aa6 __fish_complete_directories: remove use of empty variable
Closes #12248
2026-01-04 09:42:25 +01:00
Johannes Altmanninger
1c3403825c completions/signify: consistent style
Also, replace use of "ls" with globbing.
2026-01-04 09:42:25 +01:00
LunarEclipse
6f1ac7c949 Prioritize files with matching extensions for flag arguments in signify completions
Closes #12243
2026-01-04 09:42:25 +01:00
LunarEclipse
f5d3fd8a82 Full completions for openbsd signify
Part of #12243
2026-01-04 09:42:25 +01:00
Johannes Altmanninger
0a23a78523 Soft-wrapped autosuggestion to hide right prompt for now
Prior to f417cbc981 (Show soft-wrapped portions in autosuggestions,
2025-12-11), we'd truncate autosuggestions before the right prompt.
We no longer do that for autosuggestions that soft-wrap, which means
we try to draw both right prompt and suggestion in the same space.

Make suggestion paint over right prompt for now, since this seems to
be a simple and robust solution.  We can revisit this later.

Fixes #12255
2026-01-03 15:54:04 +01:00
xtqqczze
725cf33f1a fix: remove never read collection from parse_cmd_opts
Closes #12251
2026-01-03 15:54:04 +01:00
xtqqczze
2d6db3f980 clippy: fix implicit_clone lint
https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone

Closes #12245
2026-01-03 15:54:04 +01:00
xtqqczze
41b9584bb3 clippy: fix cloned_instead_of_copied lint
https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied

Closes #12244
2026-01-03 15:54:04 +01:00
Tin Lai
c915435417 respect canonical config for untracked files
Signed-off-by: Tin Lai <tin@tinyiu.com>

Closes #11709
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
afcde1222b Update cargo dependencies
cargo update && cargo +nightly -Zunstable-options update --breaking
2026-01-03 15:54:04 +01:00
Benjamin A. Beasley
a3cb512628 Update phf from 0.12 to 0.13
Closes #12222
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
fc71ba07da share: fix typo 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
9c867225ee reader handle_completions(): remove code duplication
We fail to flash the command line if we filter out completions due
to !reader_can_replace. Fix that and de-duplicate the logic
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
972355e2fc reader handle_completions(): remove stale comment
See 656b39a0b3 (Also show case-insensitive prefix matches in completion pager, 2025-11-23).
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
8f4c80699f reader handle_completions(): don't allocate a second completion list 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
e79b00d9d1 reader handle_completions(): also truncate common prefix when replacing
I don't know why we don't apply the common-prefix truncation logic
when all completions are replacing.
Let's do that.
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
2f6b1eaaf9 reader handle_completions(): don't consider odd replacing completions for common prefix
If "will_replace_token" is set, we generally only consider
appending completions.  This changed in commit 656b39a0b3 (Also show
case-insensitive prefix matches in completion pager, 2025-11-23) which
also allowed icase completions as long as they are also prefix matches.

Such replacing completions might cause the common prefix to be empty,
which breaks the appending completions.

Fix this by not considering these replacing completions for the
common-prefix computation. The pager already doesn't show the prefix
for these completions specifically.

Fixes #12249
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
3546ffa3ef reader handle_completions(): remove dead filtering code
We skip completions where "will_replace_token != c.replaces_token()".
This means that
- if will_replace_token, we filter out non-replacing completions.
  But those do not exist because, by definition, will_replace_token
  is true iff there are no non-replacing completions.
- if !will_replace_token, we filter out replacing completions.
  From the definition of will_replace_token follows that there is
  some non-replacing completion, which must be a prefix or exact match.
  Since we've filtered by rank, any replacing ones must have the same rank.
  So the replacement bit must be due to smartcase.  Smartcase
  completions are already passed through explicitly here since
  656b39a0b3 (Also show case-insensitive prefix matches in completion
  pager, 2025-11-23).

So the cases where we 'continue' here can never happen.
Remove this redundant check.
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
30f96860a7 reader handle_completions(): closed form for pager prefix bool 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
41d50f1a71 reader handle_completions(): don't duplicate pager prefix allocation
While at it, use Cow I guess?
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
1e9c80f34c reader handle_completions(): don't allocate common prefix 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
b88d2ed812 reader handle_completions(): don't clone surviving completions 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
92dd37d3c7 reader handle_completions(): remove dead code for skipping to add prefix
The tuple (will_replace_token, all_matches_exact_or_prefix) can never
be (false, false).

Proof by contraction:
1. Goal: show unsatisfiability of: !will_replace_token && !all_matches_exact_or_prefix
2. Substitute defintions: !all(replaces) && !all(is_exact_or_prefix)
3. wlog, !replaces(c1) && !is_exact_or_prefix(c2)
4. since c1 and c2 have same rank we know that !is_exact_or_prefix(c1)
5. !is_exact_or_prefix() implies requires_full_replacement()
6. all callers that create a Completion from StringFuzzyMatch::try_create(),
   set CompleteFlags::REPLACE_TOKEN if requires_full_replacement(),
   so requires_full_replacement() implies replaces()
7. From 4-6 follows: !is_exact_or_prefix(c1) implies replaces(c1), which is a contradiction
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
f24cc6a8fc reader handle_completions(): remove code clone
While at it,
1. add assertions to tighten some screws
2. migrate to closed form / inline computation.
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
3117a488ec complete: reuse replaces_token() 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
185b91de13 reader rls: remove redundant initial value
This initial value is weird and None works the same way so use that.
2026-01-03 15:54:04 +01:00
Johannes Altmanninger
e20024f0f0 reader rls: minimize state for tracking the completion pager 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
501ec1905e Test completion pager invalidation behavior 2026-01-03 15:54:04 +01:00
Johannes Altmanninger
7ebd2011ff update-dependencies.sh: fix uv lock --check command 2026-01-03 15:54:04 +01:00
Daniel Rainer
8004f354aa changelog: put new changes into upcoming release
The update to `WITH_GETTEXT` was not released in 4.3.2, so remove it
from there and put it into the section for the upcoming release.

Closes #12254
2026-01-02 00:11:36 +00:00
Daniel Rainer
77e1aead40 cmake: rename WITH_GETTEXT to WITH_MESSAGE_LOCALIZATION
This change is made to make the option name appropriate for Fluent
localization.

While at it, add a feature summary for this feature.

Closes #12208
2026-01-01 23:46:07 +00:00
Peter Ammon
e48a88a4b3 Minor refactoring of history item deletion
Clean this up.
2026-01-01 12:00:18 -08:00
Peter Ammon
1154d9f663 Correct error reporting when rewriting history files
A recent change attempted this:

    let result: std::io::Result<()> = { code()? }

However this doesn't initialize the Result on error - instead it
returns from the function, meaning that the error would be silently
dropped.

Fix that by reporting the error at the call site instead.
2025-12-31 10:20:31 -08:00
Johannes Altmanninger
810a707069 Fix PROMPT_SP hack regression
Commit fbad0ab50a (reset_abandoning_line: remove redundant
allocations, 2025-11-13) uses byte count of ⏎ (3) instead of char
count (1), thus overestimating the number of spaces this symbol takes.

Fixes #12246
2025-12-31 07:46:50 +01:00
Peter Ammon
7fa9e9bfb9 History: use Rust's buffered writing instead of our own
Simplify some code.
2025-12-30 20:26:07 -08:00
Alan Somers
48b0e7e695 Fix installation of prompts and theme files after 4.3.0
Installation of these files was accidentally broken by d8f1a2a.

Fixes #12241
2025-12-31 11:10:34 +08:00
Peter Ammon
848fa57144 Introduce and adopt BorrowedFdFile
Rust has this annoying design where all of the syscall conveniences on
File assume that it owns its fd; in particular this means that we can't
easily construct File from stdin, a raw file descriptor, etc.

The usual workarounds are to construct a File and then mem::forget it
(this is apparently idiomatic Rust!). But this has problems of its own:
for example it can't easily be used in Drop.

Introduce BorrowedFdFile which wraps File with ManuallyDrop and then
never drops the file (i.e. it's always forgotten). Replace some raw FDs
with BorrowedFdFile.
2025-12-30 14:07:39 -08:00
Peter Ammon
4101e831af Make fish_indent stop panicing on closed stdin
Prior to this commit, this code:

    fish_indent <&-

would panic as we would construct a File with a negative fd.
Check for a closed fd as other builtins do.
2025-12-30 14:07:39 -08:00
Peter Ammon
eb803ba6a7 Minor cleanup of shared::Arguments 2025-12-30 14:07:39 -08:00
Fabian Boehm
248a8e7c54 Fix doc test 2025-12-30 20:40:55 +01:00
Peter Ammon
2fa8c8cd7f Allow ctrl-C to work in fish_indent builtin
Since fish_indent became a builtin, it cannot be canceled with control-C,
because Rust's `read_to_end` retries on EINTR. Add our own function which
propagates EINTR and use it.

Fixes #12238
2025-12-30 10:39:19 -08:00
Johannes Altmanninger
8d5f5586dc start new cycle
Created by ./build_tools/release.sh 4.3.2
2025-12-30 17:43:15 +01:00
Johannes Altmanninger
c5bc7bd5f9 Release 4.3.2
Created by ./build_tools/release.sh 4.3.2
2025-12-30 17:21:04 +01:00
Johannes Altmanninger
2b3bd29588 Fix infinite repaint when setting magic variables in prompt
Commit 7996637db5 (Make fish immediately show color changes again,
2025-12-01) repaints unnecessarily when a local unexported color
variable changes.  Also, it repaints when the change comes from
fish_prompt, causing an easy infinite loop.  Same when changing TERM,
COLORTERM and others.

This feature is relevant when using a color-theme aware theme, so
try to keep it. Repaint only on global/universal changes.
Also ignore changes if already repainting fish prompt.

This change may be at odds with concurrent execution (parser should
not care about whether we are repainting) but that's intentional
because of 1. time constraints and 2. I'm not sure what the solution
will look like; we could use the event infrastructure.  But a lot of
existing variable listeners don't use that.

Extract a context object we pass whenever we mutate the environment; While
at it, use it to pass EnvMode::USER, to reduce EnvMode responsibilities.

Fixes #12233
2025-12-30 17:20:42 +01:00
Johannes Altmanninger
0be3f9e57e Fix some non_upper_case_globals warnings 2025-12-30 17:20:42 +01:00
Johannes Altmanninger
2524ece2cc builtin function: error when trying to inherit read-only variable
Also, deduplicate error checks and do them as early as possible since
we always return on error.
2025-12-30 17:20:42 +01:00
Johannes Altmanninger
b975472828 builtin function: improve option parsing structure 2025-12-30 17:20:42 +01:00
Johannes Altmanninger
20427ff1f6 env: check cheaper condition first 2025-12-30 17:20:42 +01:00
Johannes Altmanninger
5b3b825ab2 env: dispatch variable changes again if global was modified explicitly
We set "global_modified" to true if the global exist, or if the
default scope is global but not if EnvMode::GLOBAL.

This is an accident from 77aeb6a2a8 (Port execution, 2023-10-08).
Restore it. Tested in a following commit.
2025-12-30 17:20:42 +01:00
Johannes Altmanninger
ccd3348eed env_init: early return 2025-12-30 17:20:42 +01:00
Johannes Altmanninger
845b9be1f5 builtin set: remove unused argument
The "user" bit is only for getting errors when trying to set read-only
variables.  It's not relevant for reading from variables.
2025-12-30 17:20:42 +01:00
Johannes Altmanninger
400f2b130a set --erase: simplify erasing from multiple scopes in one go
We have pretty weird behavior:

	   $ set --path somepath 1 2 3
	     set --erase --unpath somepath[2]
	[1]$ set --path somepath 1 2 3
	     set --erase --unpath somepath
	   $

The first command fails to erase from the variable, because the
--path/--unpath mismatch prevents us from accessing the variable.
The second succeeds at erasing because we ignore --path/--unpath.

We should probably fix this; for now only simplify the unrelated
change added by fed64999bc (Allow erasing in multiple scopes in one
go, 2022-10-15):
we implement "set --erase --global --path" as

	try_erase(scope="--global")
	try_erase(scope="--path")

Do this instead, which is closer to historical behavior.

	try_erase(scope="--global --path")

This also allows us to express more obviously the behavior if no scope
(out of -lfgU) was specified.
2025-12-30 17:20:42 +01:00
Johannes Altmanninger
362f7cedf6 builtin set: fix inconsistent name
The --path and --export flags are not scopes, so use "mode" name
as elsewhere.
2025-12-30 17:20:42 +01:00
Johannes Altmanninger
2c959469f0 env mode: extract constant for scope bits 2025-12-30 17:20:42 +01:00
Johannes Altmanninger
6c34bcf8f6 parser: reuse set_var() 2025-12-30 17:20:42 +01:00
Johannes Altmanninger
f510b62b7f Don't schedule redundant repaints as autosuggestions are toggled
Tweak 86b8cc2097 (Allow turning off autosuggestions, 2021-10-21)
to avoid redundantly executing the prompt and repainting.
2025-12-30 17:20:42 +01:00
Fabian Boehm
b31387416d Optimize fish_config theme choose
Just following basic shellscript optimization:

- Remove a useless use of cat (`status get-file` takes microseconds,
`status get-file | cat` is on the order of a millisecond - slower with
bigger $PATH)
- Pipe, don't run in a loop
- Filter early

This reduces the time taken from 12ms to 6ms on one of my systems, and
6.5ms to 4.5ms on another.

This is paid on every single shell startup, including
non-interactively, so it's worth it.

There's more to investigate, but this is a good first pass.
2025-12-29 17:26:55 +01:00
Johannes Altmanninger
941a6cb434 changelog for 4.3.2 2025-12-29 16:25:23 +01:00
Johannes Altmanninger
931072f5d1 macos packages: don't add redundant hardlinks to fat binary
Commit 7b4802091a installs fish_indent and fish_key_reader as
hardlinks to fish.  When we create our fat binary for macOS, we add
3 of these X86 binaries to the fattened one,
resulting in a corrupted Mach-O binary. Fix that.

Fixes #12224
2025-12-29 16:19:48 +01:00
Johannes Altmanninger
f4f9db73da Add a CMake option to work around broken cross-compilation builds
Commit 135fc73191 (Remove man/HTML docs from tarball, require Sphinx
instead, 2025-11-20) broke cross compilation of tarballs.

Add an option to allow users to pick any fish_indent (such as
"target/debug/fish_indent" as created by "cargo build"), to allow
cross compilation.

In future, we should remove this option in favor of doing all of this
transparently at build type (in build.rs).

Ref: https://matrix.to/#/!YLTeaulxSDauOOxBoR:matrix.org/$psPcu-ogWK5q9IkgvfdvBGTdJ2XGhNq5z_Ug0iTCx2Q
2025-12-29 16:19:48 +01:00
Johannes Altmanninger
9ef3f30c56 Revert "Re-enable focus reporting on non-tmux"
When I ssh to a macOS system, typing ctrl-p ctrl-j in quick succession
sometimes causes ^[[I (focus in) to be echoed.  Looks like we fail to
disable terminal-echo in time. Possible race condition?  Revert until
we find out more.

This reverts commit 7dd2004da7.

Closes #12232
2025-12-29 16:19:48 +01:00
Johannes Altmanninger
d19c927760 status get-file: simplify wrapper
The __fish_data_with_file wrapper was born out of a desire to simplify
handling of file paths that may or may not be embedded into the
fish binary.

Since 95aeb16ca2 (Remove embed-data feature flag, 2025-11-20) this is
no longer needed since almost everything is embedded unconditionally.
The exception is man pages (see a1baf97f54 (Do not embed man pages
in CMake builds, 2025-11-20)), but they use __fish_data_with_directory.
2025-12-29 16:19:48 +01:00
Misty De Meo
22e5b21f10 changelog: fix minor typo
Closes #12227
2025-12-29 16:19:48 +01:00
Johannes Altmanninger
f0d2444769 docs: removed dead code around FISH_BUILD_VERSION
Man pages used to be built by "build.rs" but now are built by a
dependent "crates/build-man-pages/build.rs". This means that changing
the environment of build.rs is ineffective.

In future, "fn get_version" should probably be a part of
"crates/build-helper/", so Cargo builds only need to compute the
version once.

Lack of this dependency means that "build-man-pages" does not
pass FISH_BUILD_VERSION, which means that Sphinx will fall back to
build_tools/git_version_gen.sh.  This acceptable for now given that
"build-man-pages" is not used in CMake builds.
2025-12-29 16:19:48 +01:00
Johannes Altmanninger
7975060e4a docs: consistently use FISH_BUILD_VERSION_FILE
Commit 2343a6b1f1 passed the FISH_BUILD_VERSION_FILE to
sphinx-manpages to remove the fish_indent dependency.

For sphinx-docs this has been solved in another way in e895f96f8a
(Do not rely on `fish_indent` for version in Sphinx, 2025-08-19).

This is a needless inconsistency.

Remove it. Use FISH_BUILD_VERSION_FILE whenever possible, since that
means that a full build process only needs to call git_version_gen.sh
once.

Keep the fallback to git_version_gen.sh, in case someone calls
sphinx-build directly.
2025-12-29 16:19:47 +01:00
Johannes Altmanninger
354dc3d272 docs: use correct version file for HTML docs from tarball
Prior to commit 135fc73191 (Remove man/HTML docs from tarball, require
Sphinx instead, 2025-11-20), HTML docs were built from a Git worktree.

Now they are built in the tarball.  We call
build_tools/git_version_gen.sh from doc_src so it fails to find the
version file. Fix that.

Fixes #12228
2025-12-29 16:19:47 +01:00
Johannes Altmanninger
7640e95bd7 Create user config file/directories only on first startup again
Not being able to delete these for good (if unused) seems to be
a nuisance.  Let's go back to storing universal __fish_initialized
also on fresh installations, which I guess is a small price to to
avoid recreating these files.

Closes #12230
2025-12-29 16:19:47 +01:00
Johannes Altmanninger
767115a93d build_tools/make_macos_pkg.sh: fix when no CMake option is passed 2025-12-29 16:19:47 +01:00
David Adam
f0c8788a52 exec: add custom message for EBADMACHO error on Apple platforms
Otherwise the error is 'unknown error number 88'.
2025-12-29 22:46:47 +08:00
Fabian Boehm
a3cbb01b27 source __fish_build_paths directly
This didn't work because the cheesy helper function added an extra
scope block.

Just skip it.

Fixes #12226
2025-12-28 19:57:08 +01:00
Johannes Altmanninger
d630b4ae8a start new cycle
Created by ./build_tools/release.sh 4.3.1
2025-12-28 17:16:50 +01:00
Johannes Altmanninger
a2c5b2a567 Release 4.3.1
Created by ./build_tools/release.sh 4.3.1
2025-12-28 16:54:44 +01:00
Johannes Altmanninger
18295f4402 Fix icase prefix/suffix checks
Commit 30942e16dc (Fix prefix/suffix icase comparisons, 2025-12-27)
incorrectly treated "gs " as prefix of "gs" which causes a crash
immediately after expanding that abbreviation iff "gs" is our
autosuggestion (i.e. there's no history autosuggestion taking
precedence).

Fixes #12223
2025-12-28 16:54:34 +01:00
Johannes Altmanninger
443fd604cc build_tools/release.sh: don't add dch entry for snapshot version 2025-12-28 10:55:09 +01:00
Johannes Altmanninger
9e022ff7cf build_tools/release.sh: fix undefined variable for next milestone 2025-12-28 10:52:20 +01:00
Johannes Altmanninger
aba927054f start new cycle
Created by ./build_tools/release.sh 4.3.0
2025-12-28 10:45:34 +01:00
375 changed files with 24810 additions and 12398 deletions

View File

@@ -1,8 +1,2 @@
# This file is _not_ included in the tarballs for now
# Binary builds on Linux packaging infrastructure need to overwrite it to make `cargo vendor` work
# Releases and development builds made using OBS/Launchpad will _not_ reflect the contents of this
# file
[resolver]
# Make cargo 1.84+ respect MSRV (rust-version in Cargo.toml)
incompatible-rust-versions = "fallback"
[alias]
xtask = "run --package xtask --"

View File

@@ -29,6 +29,7 @@ freebsd_task:
freebsd_instance:
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
tests_script:
- pkg update
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
# libclang.so is a required build dependency for rust-c++ ffi bridge
- pkg install -y llvm

View File

@@ -9,7 +9,7 @@ trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
[{Makefile,*.in}]
[{Makefile,{BSD,GNU}makefile}]
indent_style = tab
[*.{md,rst}]
@@ -21,7 +21,7 @@ indent_size = 4
[build_tools/release.sh]
max_line_length = 72
[Dockerfile]
[{Dockerfile,Vagrantfile}]
indent_size = 2
[share/{completions,functions}/**.fish]
@@ -29,3 +29,6 @@ max_line_length = unset
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
max_line_length = 72
[*.yml]
indent_size = 2

3
.gitattributes vendored
View File

@@ -18,9 +18,6 @@
/.github/* export-ignore
/.builds export-ignore
/.builds/* export-ignore
# to make cargo vendor work correctly
/.cargo/ export-ignore
/.cargo/config.toml export-ignore
# for linguist, which drives GitHub's language statistics
alpine.js linguist-vendored

View File

@@ -1,11 +1,8 @@
## Description
Talk about your changes here.
Fixes issue #
## TODOs:
<!-- Just check off what what we know been done so far. We can help you with this stuff. -->
<!-- Check off what what has been done so far. -->
- [ ] If addressing an issue, a commit message mentions `Fixes issue #<issue-number>`
- [ ] Changes to fish usage are reflected in user documentation/manpages.
- [ ] Tests have been added for regressions fixed
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Don't document changes for completions inside CHANGELOG.rst, there are lot of such edits -->
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Usually skipped for changes to completions -->

View File

@@ -25,7 +25,7 @@ runs:
set -x
toolchain=$(
case "$toolchain_channel" in
(stable) echo 1.92 ;; # updatecli.d/rust.yml
(stable) echo 1.93 ;; # updatecli.d/rust.yml
(msrv) echo 1.85 ;; # updatecli.d/rust.yml
(*)
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Set label and milestone
id: set-label-milestone
uses: actions/github-script@v7
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8, build_tools/update-dependencies.sh
with:
script: |
const completionsLabel = 'completions';

View File

@@ -37,10 +37,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
-
name: Login to Container registry
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0, build_tools/update-dependencies.sh
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -48,14 +48,14 @@ jobs:
-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0, build_tools/update-dependencies.sh
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
flavor: |
latest=true
-
name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0, build_tools/update-dependencies.sh
with:
context: docker/context
push: true

View File

@@ -16,9 +16,9 @@ jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: EmbarkStudios/cargo-deny-action@44db170f6a7d12a6e90340e9e0fca1f650d34b14 # v2.0.15, build_tools/update-dependencies.sh
with:
command: check licenses
arguments: --all-features --locked --exclude-dev
rust-version: 1.92 # updatecli.d/rust.yml
rust-version: 1.93 # updatecli.d/rust.yml

View File

@@ -9,7 +9,7 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
with:
components: rustfmt
@@ -35,11 +35,13 @@ jobs:
- rust_version: "msrv"
features: ""
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: ${{ matrix.rust_version }}
components: clippy
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: |
sudo apt install gettext
@@ -49,8 +51,10 @@ jobs:
rustdoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: |
sudo apt install gettext

View File

@@ -18,7 +18,7 @@ jobs:
pull-requests: write # for dessant/lock-threads to lock PRs
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@f5f995c727ac99a91dec92781a8e34e7c839a65e # v6.0.0, build_tools/update-dependencies.sh
with:
github-token: ${{ github.token }}
issue-inactive-days: '365'

View File

@@ -16,7 +16,7 @@ jobs:
name: Pre-release checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
@@ -36,10 +36,12 @@ jobs:
version: ${{ steps.version.outputs.version }}
tarball-name: ${{ steps.version.outputs.tarball-name }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Update package database
run: sudo apt-get update
- name: Install dependencies
run: sudo apt install cmake gettext ninja-build python3-pip
- uses: ./.github/actions/install-sphinx
@@ -61,7 +63,7 @@ jobs:
sed -n 2p "$relnotes" | grep -q '^$'
sed -i 1,2d "$relnotes"
- name: Upload tarball artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
with:
name: source-tarball
path: |
@@ -74,7 +76,7 @@ jobs:
name: Build single-file fish for Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
@@ -82,6 +84,8 @@ jobs:
uses: ./.github/actions/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl
- name: Update package database
run: sudo apt-get update
- name: Install dependencies
run: sudo apt install crossbuild-essential-arm64 gettext musl-tools
- uses: ./.github/actions/install-sphinx
@@ -100,7 +104,7 @@ jobs:
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
-C target/$arch-unknown-linux-musl/release fish
done
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
with:
name: Static builds for Linux
path: fish-${{ inputs.version }}-linux-*.tar.xz
@@ -114,19 +118,19 @@ jobs:
name: Create release draft
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Download all artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0, build_tools/update-dependencies.sh
with:
merge-multiple: true
path: /tmp/artifacts
- name: List artifacts
run: find /tmp/artifacts -type f
- name: Create draft release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0, build_tools/update-dependencies.sh
with:
tag_name: ${{ inputs.version }}
name: fish ${{ inputs.version }}
@@ -142,7 +146,7 @@ jobs:
runs-on: macos-latest
environment: macos-codesign
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}

View File

@@ -13,7 +13,7 @@ jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@oldest-supported
- name: Install deps
uses: ./.github/actions/install-dependencies
@@ -44,18 +44,19 @@ jobs:
ubuntu-32bit-static-pcre2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@oldest-supported
with:
targets: "i586-unknown-linux-gnu"
- name: Update package database
run: sudo apt-get update
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_pcre: false
include_sphinx: false
- name: Install g++-multilib
run: |
sudo apt install g++-multilib
run: sudo apt install g++-multilib
- name: cmake
env:
CFLAGS: "-m32"
@@ -80,13 +81,15 @@ jobs:
RUSTFLAGS: "-Zsanitizer=address"
# RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
# All -Z options require running nightly
- uses: dtolnay/rust-toolchain@nightly
with:
# ASAN uses `cargo build -Zbuild-std` which requires the rust-src component
# this is comma-separated
components: rust-src
- name: Update package database
run: sudo apt-get update
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
@@ -128,7 +131,7 @@ jobs:
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
CARGO_NET_GIT_FETCH_WITH_CLI: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@oldest-supported
- name: Install deps
run: |
@@ -155,8 +158,8 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0, build_tools/update-dependencies.sh
with:
update: true
msystem: MSYS

3
AGENTS.md Normal file
View File

@@ -0,0 +1,3 @@
# Coding style
- Use comments sparingly. Don't explain what the code is doing, rather explain why.

View File

@@ -1,9 +1,104 @@
fish 4.5.0 (released February 17, 2026)
=======================================
This is mostly a patch release for Vi mode regressions in 4.4.0 but other minor behavior changes are included as well.
Interactive improvements
------------------------
- :kbd:`ctrl-l` no longer cancels history search (:issue:`12436`).
- History search cursor positioning now works correctly with characters of arbitrary width.
Deprecations and removed features
---------------------------------
- fish no longer reads the terminfo database to alter behaviour based on the :envvar:`TERM` environment variable, and does not depend on ncurses or terminfo. The ``ignore-terminfo`` feature flag, introduced and enabled by default in fish 4.1, is now permanently enabled. fish may no longer work correctly on Data General Dasher D220 and Wyse WY-350 terminals, but should continue to work on all known terminal emulators released in the 21st century.
Regression fixes:
-----------------
- (from 4.4.0) Vi mode ``d,f`` key binding did not work (:issue:`12417`).
- (from 4.4.0) Vi mode ``c,w`` key binding wrongly deleted trailing spaces (:issue:`12443`).
- (from 4.4.0) Vi mode crash on ``c,i,w`` after accepting autosuggestion (:issue:`12430`).
- (from 4.4.0) ``fish_vi_key_bindings`` called with a mode argument produced an error (:issue:`12413`).
- (from 4.0.0) Build on Illumos (:issue:`12410`).
fish 4.4.0 (released February 03, 2026)
=======================================
Deprecations and removed features
---------------------------------
- The default fossil prompt has been disabled (:issue:`12342`).
Interactive improvements
------------------------
- The ``bind`` builtin lists mappings from all modes if ``--mode`` is not provided (:issue:`12214`).
- Line-wise autosuggestions that don't start a command are no longer shown (739b82c34db, 58e7a50de8a).
- Builtin ``history`` now assumes that :envvar:`PAGER` supports ANSI color sequences.
- fish now clears the terminal's ``FLUSHO`` flag when acquiring control of the terminal, to fix an issue caused by pressing :kbd:`ctrl-o` on macOS (:issue:`12304`).
New or improved bindings
------------------------
- Vi mode word movements (``w``, ``W``, ``e``, and ``E``) are now largely in line with Vim. The only exception is that underscores are treated as word separators (:issue:`12269`).
- New special input functions to support these movements: ``forward-word-vi``, ``kill-word-vi``, ``forward-bigword-vi``, ``kill-bigword-vi``, ``forward-word-end``, ``backward-word-end``, ``forward-bigword-end``, ``backward-bigword-end``, ``kill-a-word``, ``kill-inner-word``, ``kill-a-bigword``, and ``kill-inner-bigword``.
- Vi mode key bindings now support counts for movement and deletion commands (e.g. `d3w` or `3l`), via a new operator mode (:issue:`2192`).
- New ``catpuccin-*`` color themes.
Improved terminal support
-------------------------
- ``set_color`` learned the strikethrough (``--strikethrough`` or ``-s``) modifier.
For distributors and developers
-------------------------------
- The CMake option ``WITH_GETTEXT`` has been renamed to ``WITH_MESSAGE_LOCALIZATION``, to reflect that it toggles localization independently of the backend used in the implementation.
- New ``cargo xtask`` commands can replace some CMake workflows.
Regression fixes:
-----------------
- (from 4.1.0) Crash when autosuggesting Unicode characters with nontrivial lowercase mapping (:issue:`12326`, 78f4541116e).
- (from 4.3.0) Glitch on ``read --prompt-str ""`` (:issue:`12296`).
fish 4.3.3 (released January 07, 2026)
======================================
This release fixes the following problems identified in fish 4.3.0:
- Selecting a completion could insert only part of the token (:issue:`12249`).
- Glitch with soft-wrapped autosuggestions and :doc:`fish_right_prompt <cmds/fish_right_prompt>` (:issue:`12255`).
- Spurious echo in tmux when typing a command really fast (:issue:`12261`).
- ``tomorrow`` theme always using the light variant (:issue:`12266`).
- ``fish_config theme choose`` sometimes not shadowing themes set by e.g. webconfig (:issue:`12278`).
- The sample prompts and themes are correctly installed (:issue:`12241`).
- Last line of command output could be hidden when missing newline (:issue:`12246`).
Other improvements include:
- The ``abbr``, ``bind``, ``complete``, ``functions``, ``history`` and ``type`` commands now support a ``--color`` option to control syntax highlighting in their output. Valid values are ``auto`` (default), ``always``, or ``never``.
- Existing file paths in redirection targets such as ``> file.txt`` are now highlighted using :envvar:`fish_color_valid_path`, indicating that ``file.txt`` will be clobbered (:issue:`12260`).
fish 4.3.2 (released December 30, 2025)
=======================================
This release fixes the following problems identified in 4.3.0:
- Pre-built macOS packages failed to start due to a ``Malformed Mach-O file`` error (:issue:`12224`).
- ``extra_functionsdir`` (usually ``vendor_functions.d``) and friends were not used (:issue:`12226`).
- Sample config file ``~/.config/fish/config.fish/`` and config directories ``~/.config/fish/conf.d/``, ``~/.config/fish/completions/`` and ``~/.config/fish/functions/`` were recreated on every startup instead of only the first time fish runs on a system (:issue:`12230`).
- Spurious echo of ``^[[I`` in some scenarios (:issue:`12232`).
- Infinite prompt redraw loop on some prompts (:issue:`12233`).
- The removal of pre-built HTML docs from tarballs revealed that cross compilation is broken because we use ``${CMAKE_BINARY_DIR}/fish_indent`` for building HTML docs.
As a workaround, the new CMake build option ``FISH_INDENT_FOR_BUILDING_DOCS`` can be set to the path of a runnable ``fish_indent`` binary.
fish 4.3.1 (released December 28, 2025)
=======================================
This release fixes the following problem identified in 4.3.0:
- Possible crash after expanding an abbreviation (:issue:`12223`).
fish 4.3.0 (released December 28, 2025)
=======================================
Deprecations and removed features
---------------------------------
- fish no longer sets :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
- fish no longer sets user-facing :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
Specifically, the ``fish_color_*``, ``fish_pager_color_*`` and ``fish_key_bindings`` variables are now set in the global scope by default.
After upgrading to 4.3.0, fish will (once and never again) migrate these universals to globals set at startup in the
``~/.config/fish/conf.d/fish_frozen_theme.fish`` and
@@ -30,7 +125,7 @@ Interactive improvements
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
- Prefix-matching completions are now shown even if they don't match the case typed by the user (:issue:`7944`).
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
- When using the exe name (``foo.exe``), fish will use to the description and completions for ``foo`` if there are none for ``foo.exe``.
- When using the exe name (``foo.exe``), fish will use the description and completions for ``foo`` if there are none for ``foo.exe``.
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
New or improved bindings
@@ -45,8 +140,6 @@ Improved terminal support
- The working directory is now reported on every fresh prompt (via OSC 7), fixing scenarios where a child process (like ``ssh``) left behind a stale working directory (:issue:`12191`).
- OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`).
- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS <status-terminal-os>`.
- Focus reporting is enabled unconditionally, not just inside tmux.
To use it, define functions that handle the ``fish_focus_in`` or ``fish_focus_out`` :ref:`events <event>`.
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around some incompatible terminals.
For distributors and developers
@@ -176,8 +269,6 @@ This release fixes the following regressions identified in 4.1.0:
fish 4.1.0 (released September 27, 2025)
========================================
.. ignore for 4.1: 10929 10940 10948 10955 10965 10975 10989 10990 10998 11028 11052 11055 11069 11071 11079 11092 11098 11104 11106 11110 11140 11146 11148 11150 11214 11218 11259 11288 11299 11328 11350 11373 11395 11417 11419
Notable improvements and fixes
------------------------------
- Compound commands (``begin; echo 1; echo 2; end``) can now be written using braces (``{ echo1; echo 2 }``), like in other shells.

View File

@@ -24,12 +24,12 @@ include(cmake/Rust.cmake)
# Work around issue where archive-built libs go in the wrong place.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
# Set up the machinery around FISH-BUILD-VERSION-FILE
# This defines the FBVF variable.
include(Version)
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
HINTS
$ENV{SPHINX_DIR}
PATH_SUFFIXES bin
DOC "Sphinx documentation generator")
# Set up the docs.
include(cmake/Docs.cmake)
# Tell Cargo where our build directory is so it can find Cargo.toml.
set(VARS_FOR_CARGO
@@ -91,6 +91,9 @@ create_link(fish_indent)
# Define fish_key_reader.
create_link(fish_key_reader)
# Set up the docs.
include(cmake/Docs.cmake)
# Set up tests.
include(cmake/Tests.cmake)

View File

@@ -50,7 +50,7 @@ Guidelines
In short:
- Be conservative in what you need (keep to the agreed minimum supported Rust version, limit new dependencies)
- Use automated tools to help you (``build_tools/check.sh``)
- Use automated tools to help you (``cargo xtask check``)
Commit History
==============
@@ -105,21 +105,24 @@ Contributing documentation
==========================
The documentation is stored in ``doc_src/``, and written in ReStructured Text and built with Sphinx.
The builtins and various functions shipped with fish are documented in ``doc_src/cmds/``.
To build it locally, run either::
To build an HTML version of the docs locally, run::
sphinx-build -j auto -b html doc_src/ /tmp/fish-doc/
cargo xtask html-docs
which will output HTML docs to /tmp/fish-doc.
You can open it in a browser and see that it looks okay.
Alternatively, you can use::
will output to ``target/fish-docs/html`` or, if you use CMake::
cmake --build build -t sphinx-docs
which outputs to build/user_doc/html/.
will output to ``build/cargo/fish-docs/html/``. You can also run ``sphinx-build`` directly, which allows choosing the output directory::
sphinx-build -j auto -b html doc_src/ /tmp/fish-doc/
will output HTML docs to ``/tmp/fish-doc``.
After building them, you can open the HTML docs in a browser and see that it looks okay.
The builtins and various functions shipped with fish are documented in doc_src/cmds/.
Code Style
==========
@@ -245,7 +248,7 @@ In this example we're in the root of the workspace and have run ``cargo build``
To run all tests and linters, use::
build_tools/check.sh
cargo xtask check
Contributing Translations
=========================

475
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -17,6 +17,62 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "assert_matches"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -40,9 +96,9 @@ dependencies = [
[[package]]
name = "bstr"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
dependencies = [
"memchr",
"serde",
@@ -50,9 +106,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.41"
version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -72,6 +128,52 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.5.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -83,9 +185,9 @@ dependencies = [
[[package]]
name = "crypto-common"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
@@ -122,6 +224,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -146,20 +254,22 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fish"
version = "4.3.0"
version = "4.5.0"
dependencies = [
"assert_matches",
"bitflags",
"cc",
"cfg-if",
"errno",
"fish-build-helper",
"fish-build-man-pages",
"fish-color",
"fish-common",
"fish-fallback",
"fish-gettext",
@@ -167,8 +277,12 @@ dependencies = [
"fish-gettext-mo-file-parser",
"fish-printf",
"fish-tempfile",
"fish-wchar",
"fish-util",
"fish-wcstringutil",
"fish-wgetopt",
"fish-widecharwidth",
"fish-widestring",
"itertools",
"libc",
"lru",
"macro_rules_attribute",
@@ -176,15 +290,14 @@ dependencies = [
"num-traits",
"once_cell",
"pcre2",
"phf_codegen 0.12.1",
"phf_codegen",
"portable-atomic",
"rand 0.9.2",
"rand",
"rsconf",
"rust-embed",
"serial_test",
"terminfo",
"unicode-width",
"unix_path",
"widestring",
"xterm-color",
]
@@ -203,13 +316,22 @@ dependencies = [
"rsconf",
]
[[package]]
name = "fish-color"
version = "0.0.0"
dependencies = [
"fish-common",
"fish-widestring",
]
[[package]]
name = "fish-common"
version = "0.0.0"
dependencies = [
"bitflags",
"fish-widestring",
"libc",
"nix",
"once_cell",
]
[[package]]
@@ -218,10 +340,9 @@ version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-common",
"fish-wchar",
"fish-widecharwidth",
"fish-widestring",
"libc",
"once_cell",
"rsconf",
"widestring",
]
@@ -231,8 +352,7 @@ name = "fish-gettext"
version = "0.0.0"
dependencies = [
"fish-gettext-maps",
"once_cell",
"phf 0.12.1",
"phf",
]
[[package]]
@@ -250,8 +370,8 @@ version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-gettext-mo-file-parser",
"phf 0.12.1",
"phf_codegen 0.12.1",
"phf",
"phf_codegen",
"rsconf",
]
@@ -263,6 +383,7 @@ version = "0.0.0"
name = "fish-printf"
version = "0.2.1"
dependencies = [
"assert_matches",
"libc",
"unicode-segmentation",
"unicode-width",
@@ -274,15 +395,35 @@ name = "fish-tempfile"
version = "0.0.0"
dependencies = [
"nix",
"rand 0.9.2",
"rand",
]
[[package]]
name = "fish-wchar"
name = "fish-util"
version = "0.0.0"
dependencies = [
"fish-widestring",
"rand",
]
[[package]]
name = "fish-wcstringutil"
version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-common",
"widestring",
"fish-fallback",
"fish-widestring",
"rsconf",
]
[[package]]
name = "fish-wgetopt"
version = "0.0.0"
dependencies = [
"assert_matches",
"fish-wcstringutil",
"fish-widestring",
]
[[package]]
@@ -290,22 +431,23 @@ name = "fish-widecharwidth"
version = "0.0.0"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
name = "fish-widestring"
version = "0.0.0"
dependencies = [
"widestring",
]
[[package]]
name = "foldhash"
version = "0.1.5"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "generic-array"
version = "0.14.9"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
@@ -313,9 +455,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
@@ -336,9 +478,9 @@ dependencies = [
[[package]]
name = "globset"
version = "0.4.16"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
dependencies = [
"aho-corasick",
"bstr",
@@ -349,15 +491,36 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "jobserver"
version = "0.1.34"
@@ -370,15 +533,15 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.177"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libredox"
version = "0.1.10"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
@@ -395,15 +558,15 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.28"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru"
version = "0.13.0"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown",
]
@@ -430,17 +593,11 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.30.1"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
dependencies = [
"bitflags",
"cfg-if",
@@ -448,16 +605,6 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -473,6 +620,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -530,76 +683,38 @@ dependencies = [
[[package]]
name = "phf"
version = "0.11.3"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_shared 0.12.1",
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
]
[[package]]
name = "phf_codegen"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
dependencies = [
"phf_generator 0.12.1",
"phf_shared 0.12.1",
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared 0.11.3",
"rand 0.8.5",
]
[[package]]
name = "phf_generator"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
"phf_shared 0.12.1",
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
"siphasher",
]
@@ -612,9 +727,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.1"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "ppv-lite86"
@@ -627,18 +742,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.101"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.41"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
@@ -649,15 +764,6 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
@@ -665,7 +771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core 0.9.3",
"rand_core",
]
[[package]]
@@ -675,20 +781,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
@@ -708,7 +808,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"getrandom 0.2.17",
"libredox",
"thiserror",
]
@@ -732,18 +832,18 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rsconf"
version = "0.2.2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd2af859f1af0401e7fc7577739c87b0d239d8a5da400d717183bca92336bcdc"
checksum = "06cbd984e96cc891aa018958ac3d09986c0ea7635eedfff670b99a90970f159f"
dependencies = [
"cc",
]
[[package]]
name = "rust-embed"
version = "8.9.0"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -752,9 +852,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "8.9.0"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
dependencies = [
"proc-macro2",
"quote",
@@ -766,9 +866,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
version = "8.9.0"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
dependencies = [
"globset",
"sha2",
@@ -836,9 +936,9 @@ dependencies = [
[[package]]
name = "serial_test"
version = "3.2.0"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555"
dependencies = [
"once_cell",
"parking_lot",
@@ -848,9 +948,9 @@ dependencies = [
[[package]]
name = "serial_test_derive"
version = "3.2.0"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83"
dependencies = [
"proc-macro2",
"quote",
@@ -885,9 +985,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "smallvec"
@@ -896,42 +996,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.107"
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "terminfo"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
dependencies = [
"fnv",
"nom",
"phf 0.11.3",
"phf_codegen 0.11.3",
]
[[package]]
name = "thiserror"
version = "2.0.17"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@@ -946,9 +1040,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-segmentation"
@@ -977,6 +1071,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -1044,26 +1144,35 @@ version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "xtask"
version = "0.0.0"
dependencies = [
"clap",
"fish-build-helper",
"fish-tempfile",
]
[[package]]
name = "xterm-color"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f"
checksum = "7008a9d8ba97a7e47d9b2df63fcdb8dade303010c5a7cd5bf2469d4da6eba673"
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -11,12 +11,15 @@ repository = "https://github.com/fish-shell/fish-shell"
license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
[workspace.dependencies]
assert_matches = "1.5.0"
bitflags = "2.5.0"
cc = "1.0.94"
cfg-if = "1.0.3"
clap = { version = "4.5.54", features = ["derive"] }
errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" }
fish-color = { path = "crates/color" }
fish-common = { path = "crates/common" }
fish-fallback = { path = "crates/fallback" }
fish-gettext = { path = "crates/gettext" }
@@ -25,27 +28,35 @@ fish-gettext-maps = { path = "crates/gettext-maps" }
fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" }
fish-printf = { path = "crates/printf", features = ["widestring"] }
fish-tempfile = { path = "crates/tempfile" }
fish-wchar = { path = "crates/wchar" }
fish-util = { path = "crates/util" }
fish-wcstringutil = { path = "crates/wcstringutil" }
fish-widecharwidth = { path = "crates/widecharwidth" }
fish-widestring = { path = "crates/widestring" }
fish-wgetopt = { path = "crates/wgetopt" }
itertools = "0.14.0"
libc = "0.2.177"
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
# files as of 22 April 2024.
lru = "0.13.0"
nix = { version = "0.30.1", default-features = false, features = [
lru = "0.16.2"
nix = { version = "0.31.1", default-features = false, features = [
"event",
"inotify",
"resource",
"fs",
"inotify",
"hostname",
"resource",
"process",
"signal",
"term",
"user",
] }
num-traits = "0.2.19"
once_cell = "1.19.0"
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
"utf32",
] }
phf = { version = "0.12", default-features = false }
phf_codegen = { version = "0.12" }
phf = { version = "0.13", default-features = false }
phf_codegen = "0.13"
portable-atomic = { version = "1", default-features = false, features = [
"fallback",
] }
@@ -54,15 +65,13 @@ rand = { version = "0.9.2", default-features = false, features = [
"small_rng",
"thread_rng",
] }
rsconf = "0.2.2"
rust-embed = { version = "8.9.0", features = [
rsconf = "0.3.0"
rust-embed = { version = "8.11.0", features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
] }
serial_test = { version = "3", default-features = false }
# We need 0.9.0 specifically for some crash fixes.
terminfo = "0.9.0"
widestring = "1.2.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"
@@ -79,7 +88,7 @@ debug = true
[package]
name = "fish"
version = "4.3.0"
version = "4.5.0"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
@@ -88,19 +97,25 @@ homepage = "https://fishshell.com"
readme = "README.rst"
[dependencies]
assert_matches.workspace = true
bitflags.workspace = true
cfg-if.workspace = true
errno.workspace = true
fish-build-helper.workspace = true
fish-build-man-pages = { workspace = true, optional = true }
fish-color.workspace = true
fish-common.workspace = true
fish-fallback.workspace = true
fish-gettext = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true }
fish-printf.workspace = true
fish-tempfile.workspace = true
fish-wchar.workspace = true
fish-util.workspace = true
fish-wcstringutil.workspace = true
fish-wgetopt.workspace = true
fish-widecharwidth.workspace = true
fish-widestring.workspace = true
itertools.workspace = true
libc.workspace = true
lru.workspace = true
macro_rules_attribute = "0.2.2"
@@ -109,9 +124,8 @@ num-traits.workspace = true
once_cell.workspace = true
pcre2.workspace = true
rand.workspace = true
terminfo.workspace = true
unicode-width.workspace = true
xterm-color.workspace = true
widestring.workspace = true
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic.workspace = true
@@ -168,12 +182,11 @@ embed-manpages = ["dep:fish-build-man-pages"]
localize-messages = ["dep:fish-gettext"]
# This feature is used to enable extracting messages from the source code for localization.
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
# desired. This happens when running tests via `build_tools/check.sh` and when calling
# desired. This happens when running tests via `cargo xtask check` and when calling
# `build_tools/update_translations.fish`, so there should not be a need to enable it manually.
gettext-extract = ["dep:fish-gettext-extraction"]
# The following features are auto-detected by the build-script and should not be enabled manually.
asan = []
tsan = []
[workspace.lints]
@@ -182,19 +195,35 @@ rust.non_upper_case_globals = "allow"
rust.unknown_lints = "allow"
rust.unstable_name_collisions = "allow"
rustdoc.private_intra_doc_links = "allow"
clippy.len_without_is_empty = "allow" # we're not a library crate
clippy.let_and_return = "allow"
clippy.manual_range_contains = "allow"
clippy.map_unwrap_or = "warn"
clippy.needless_lifetimes = "allow"
clippy.new_without_default = "allow"
clippy.option_map_unit_fn = "allow"
[workspace.lints.clippy]
assigning_clones = "warn"
cloned_instead_of_copied = "warn"
explicit_into_iter_loop = "warn"
format_push_string = "warn"
implicit_clone = "warn"
len_without_is_empty = "allow" # we're not a library crate
let_and_return = "allow"
manual_assert = "warn"
manual_range_contains = "allow"
map_unwrap_or = "warn"
mut_mut = "warn"
needless_lifetimes = "allow"
new_without_default = "allow"
option_map_unit_fn = "allow"
ptr_offset_by_literal = "warn"
ref_option = "warn"
semicolon_if_nothing_returned = "warn"
stable_sort_primitive = "warn"
str_to_string = "warn"
unnecessary_semicolon = "warn"
unused_trait_names = "warn"
# We do not want to use the e?print(ln)?! macros.
# These lints flag their use.
# In the future, they might change to flag other methods of printing.
clippy.print_stdout = "deny"
clippy.print_stderr = "deny"
print_stdout = "deny"
print_stderr = "deny"
[lints]
workspace = true

View File

@@ -1,18 +0,0 @@
FROM centos:latest
# Build dependency
RUN yum update -y &&\
yum install -y epel-release &&\
yum install -y clang cmake3 gcc-c++ make &&\
yum clean all
# Test dependency
RUN yum install -y expect vim-common
ADD . /src
WORKDIR /src
# Build fish
RUN cmake3 . &&\
make &&\
make install

View File

@@ -7,7 +7,7 @@
CMAKE ?= cmake
GENERATOR ?= $(shell (which ninja > /dev/null 2> /dev/null && echo Ninja) || \
echo 'Unix Makefiles')
echo 'Unix Makefiles')
prefix ?= /usr/local
PREFIX ?= $(prefix)
@@ -34,7 +34,7 @@ all: .begin build/fish
.PHONY: .begin
.begin:
@which $(CMAKE) > /dev/null 2> /dev/null || \
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
.PHONY: build/fish
build/fish: build/$(BUILDFILE)

View File

@@ -117,7 +117,7 @@ Dependencies
Compiling fish requires:
- Rust (version 1.85 or later)
- Rust (version 1.85 or later), including cargo
- CMake (version 3.15 or later)
- a C compiler (for system feature detection and the test helper binary)
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
@@ -158,8 +158,13 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
- Rust_CARGO=path - the path to cargo. If not set, cmake will check $PATH and ~/.cargo/bin
- Rust_CARGO_TARGET=target - the target to pass to cargo. Set this for cross-compilation.
- WITH_DOCS=ON|OFF - whether to build the documentation. By default, this is ON when Sphinx is installed.
- FISH_INDENT_FOR_BUILDING_DOCS - useful for cross-compilation.
Set this to the path to the ``fish_indent`` executable to use for building HTML docs.
By default, ``${CMAKE_BINARY_DIR}/fish_indent`` will be used.
If that's not runnable on the compile host,
you can build a native one with ``cargo build --bin fish_indent`` and set this to ``$PWD/target/debug/fish_indent``.
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
- WITH_GETTEXT=ON|OFF - whether to include translations.
- WITH_MESSAGE_LOCALIZATION=ON|OFF - whether to include translations.
- extra_functionsdir, extra_completionsdir and extra_confdir - to compile in an additional directory to be searched for functions, completions and configuration snippets
Building fish with Cargo

107
build.rs
View File

@@ -3,13 +3,8 @@
workspace_root,
};
use rsconf::Target;
use std::env;
use std::path::{Path, PathBuf};
fn canonicalize<P: AsRef<Path>>(path: P) -> PathBuf {
std::fs::canonicalize(path).unwrap()
}
fn main() {
setup_paths();
@@ -20,14 +15,14 @@ fn main() {
"FISH_RESOLVED_BUILD_DIR",
// If set by CMake, this might include symlinks. Since we want to compare this to the
// dir fish is executed in we need to canonicalize it.
canonicalize(fish_build_dir()).to_str().unwrap(),
fish_build_dir().canonicalize().unwrap().to_str().unwrap(),
);
// We need to canonicalize (i.e. realpath) the manifest dir because we want to be able to
// compare it directly as a string at runtime.
rsconf::set_env_value(
"CARGO_MANIFEST_DIR",
canonicalize(workspace_root()).to_str().unwrap(),
workspace_root().canonicalize().unwrap().to_str().unwrap(),
);
// Some build info
@@ -35,13 +30,9 @@ fn main() {
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap());
let version = &get_version(&env::current_dir().unwrap());
// Per https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script,
// the source directory is the current working directory of the build script
rsconf::set_env_value("FISH_BUILD_VERSION", version);
// safety: single-threaded code.
unsafe { std::env::set_var("FISH_BUILD_VERSION", version) };
rsconf::set_env_value("FISH_BUILD_VERSION", &get_version());
fish_build_helper::rebuild_if_embedded_path_changed("share");
@@ -107,7 +98,7 @@ fn detect_cfgs(target: &mut Target) {
target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"])
}),
] {
rsconf::declare_cfg(name, handler(target))
rsconf::declare_cfg(name, handler(target));
}
}
@@ -168,16 +159,14 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
}
let prefix = overridable_path("PREFIX", |env_prefix| {
Some(PathBuf::from(
env_prefix.unwrap_or("/usr/local".to_string()),
))
Some(PathBuf::from(env_prefix.unwrap_or("/usr/local".to_owned())))
})
.unwrap();
overridable_path("SYSCONFDIR", |env_sysconfdir| {
Some(join_if_relative(
&prefix,
env_sysconfdir.unwrap_or("/etc/".to_string()),
env_sysconfdir.unwrap_or("/etc/".to_owned()),
))
});
@@ -198,79 +187,15 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
});
}
fn get_version(src_dir: &Path) -> String {
use std::fs::read_to_string;
fn get_version() -> String {
use std::process::Command;
if let Some(var) = env_var("FISH_BUILD_VERSION") {
return var;
}
let path = src_dir.join("version");
if let Ok(strver) = read_to_string(path) {
return strver;
}
let args = &["describe", "--always", "--dirty=-dirty"];
if let Ok(output) = Command::new("git").args(args).output() {
let rev = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !rev.is_empty() {
// If it contains a ".", we have a proper version like "3.7",
// or "23.2.1-1234-gfab1234"
if rev.contains('.') {
return rev;
}
// If it doesn't, we probably got *just* the commit SHA,
// like "f1242abcdef".
// So we prepend the crate version so it at least looks like
// "3.8-gf1242abcdef"
// This lacks the commit *distance*, but that can't be helped without
// tags.
let version = env!("CARGO_PKG_VERSION").to_owned();
return version + "-g" + &rev;
}
}
// git did not tell us a SHA either because it isn't installed,
// or because it refused (safe.directory applies to `git describe`!)
// So we read the SHA ourselves.
fn get_git_hash() -> Result<String, Box<dyn std::error::Error>> {
let workspace_root = workspace_root();
let gitdir = workspace_root.join(".git");
let jjdir = workspace_root.join(".jj");
let commit_id = if gitdir.exists() {
// .git/HEAD contains ref: refs/heads/branch
let headpath = gitdir.join("HEAD");
let headstr = read_to_string(headpath)?;
let headref = headstr.split(' ').nth(1).unwrap().trim();
// .git/refs/heads/branch contains the SHA
let refpath = gitdir.join(headref);
// Shorten to 9 characters (what git describe does currently)
read_to_string(refpath)?
} else if jjdir.exists() {
let output = Command::new("jj")
.args([
"log",
"--revisions",
"@",
"--no-graph",
"--ignore-working-copy",
"--template",
"commit_id",
])
.output()
.unwrap();
String::from_utf8_lossy(&output.stdout).to_string()
} else {
return Err("did not find either of .git or .jj".into());
};
let refstr = &commit_id[0..9];
let refstr = refstr.trim();
let version = env!("CARGO_PKG_VERSION").to_owned();
Ok(version + "-g" + refstr)
}
get_git_hash().expect("Could not get a version. Either set $FISH_BUILD_VERSION or install git.")
String::from_utf8(
Command::new("build_tools/git_version_gen.sh")
.output()
.unwrap()
.stdout,
)
.unwrap()
.trim_ascii_end()
.to_owned()
}

View File

@@ -33,8 +33,13 @@ fi
cargo() {
subcmd=$1
shift
# shellcheck disable=2086
command cargo "$subcmd" $cargo_args "$@"
if [ -n "$FISH_CHECK_RUST_TOOLCHAIN" ]; then
# shellcheck disable=2086
command cargo "+$FISH_CHECK_RUST_TOOLCHAIN" "$subcmd" $cargo_args "$@"
else
# shellcheck disable=2086
command cargo "$subcmd" $cargo_args "$@"
fi
}
cleanup () {

View File

@@ -1,70 +1,22 @@
#!/bin/sh
# Originally from the git sources (GIT-VERSION-GEN)
# Presumably (C) Junio C Hamano <junkio@cox.net>
# Reused under GPL v2.0
# Modified for fish by David Adam <zanchey@ucc.gu.uwa.edu.au>
set -e
# Find the fish directory as two levels up from script directory.
FISH_BASE_DIR="$( cd "$( dirname "$( dirname "$0" )" )" && pwd )"
DEF_VER=unknown
git_permission_failed=0
# First see if there is a version file (included in release tarballs),
# then try git-describe, then default.
if test -f version
then
VN=$(cat version) || VN="$DEF_VER"
else
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
:
version=$(
awk <"$FISH_BASE_DIR/Cargo.toml" -F'"' '$1 == "version = " { print $2 }'
)
if git_version=$(
GIT_CEILING_DIRECTORIES=$FISH_BASE_DIR/.. \
git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
if [ "$git_version" = "${git_version#"$version"}" ]; then
version=$version-g$git_version
else
if test $? = 128; then
# Current git versions return status 128
# when run in a repo owned by another user.
# Even for describe and everything.
# This occurs for `sudo make install`.
git_permission_failed=1
fi
VN="$DEF_VER"
version=$git_version
fi
fi
# If the first param is --stdout, then output to stdout and exit.
if test "$1" = '--stdout'
then
echo $VN
exit 0
fi
# Set the output directory as either the first param or cwd.
test -n "$1" && OUTPUT_DIR=$1/ || OUTPUT_DIR=
FBVF="${OUTPUT_DIR}FISH-BUILD-VERSION-FILE"
if test "$VN" = unknown && test -r "$FBVF" && test "$git_permission_failed" = 1
then
# HACK: Git failed, so we keep the current version file.
# This helps in case you built fish as a normal user
# and then try to `sudo make install` it.
date +%s > "${OUTPUT_DIR}"fish-build-version-witness.txt
exit 0
fi
if test -r "$FBVF"
then
VC=$(cat "$FBVF")
else
VC="unset"
fi
# Maybe output the FBVF
# It looks like "2.7.1-621-ga2f065e6"
test "$VN" = "$VC" || {
echo >&2 "$VN"
echo "$VN" >"$FBVF"
}
# Output the fish-build-version-witness.txt
# See https://cmake.org/cmake/help/v3.4/policy/CMP0058.html
date +%s > "${OUTPUT_DIR}"fish-build-version-witness.txt
echo "$version"

View File

@@ -1,3 +0,0 @@
# LSAN can detect leaks tracing back to __asan::AsanThread::ThreadStart (probably caused by our
# threads not exiting before their TLS dtors are called). Just ignore it.
leak:AsanThread

View File

@@ -25,7 +25,7 @@ NOTARIZE=
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.12'
cmake_args=
cmake_args=()
while getopts "c:sf:i:p:e:nj:" opt; do
case $opt in
@@ -49,7 +49,7 @@ if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
usage
fi
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
VERSION=$(build_tools/git_version_gen.sh)
echo "Version is $VERSION"
@@ -82,17 +82,16 @@ do_cmake() {
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
}
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
# Build for x86-64 but do not install; instead we will make a fat binary inside the root.
{ cd "$PKGDIR/build_x86_64" \
&& do_cmake -DRust_CARGO_TARGET=x86_64-apple-darwin \
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
# Fatten them up.
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
chmod 755 "$FILE"
done
# Fatten it up.
FILE=$PKGDIR/root/usr/local/bin/fish
X86_FILE=$PKGDIR/build_x86_64/$(basename "$FILE")
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
chmod 755 "$FILE"
if test -n "$SIGN"; then
echo "Signing executables"
@@ -105,9 +104,7 @@ if test -n "$SIGN"; then
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
done
(set +x; rcodesign sign "${ARGS[@]}" "$PKGDIR"/root/usr/local/bin/fish)
fi
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
@@ -128,15 +125,13 @@ fi
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
# Make the app's /usr/local/bin/fish binary universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
cd "$PKGDIR/build_arm64"
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
# macho-universal-create screws up the permissions.
chmod 755 "$FILE"
done
FILE=fish.app/Contents/Resources/base/usr/local/bin/fish
X86_FILE=$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
# macho-universal-create screws up the permissions.
chmod 755 "$FILE"
if test -n "$SIGN"; then
echo "Signing app"

View File

@@ -3,21 +3,41 @@
# Script to generate a tarball
# Outputs to $FISH_ARTEFACT_PATH or ~/fish_built by default
# Exit on error
set -e
# Get the version
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
VERSION=$(build_tools/git_version_gen.sh)
prefix=fish-$VERSION
path=${FISH_ARTEFACT_PATH:-~/fish_built}/$prefix.tar.xz
tmpdir=$(mktemp -d)
manifest=$tmpdir/Cargo.toml
lockfile=$tmpdir/Cargo.lock
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml \
>"$manifest"
awk -v version=$VERSION '
/^name = "fish"$/ { ok=1 }
ok == 1 && /^version = ".*"$/ {
ok = 2;
$0 = "version = \"" version "\"";
}
{print}
' \
Cargo.lock >"$lockfile"
git archive \
--prefix="$prefix/" \
--add-virtual-file="$prefix/version:$VERSION" \
--add-virtual-file="$prefix/Cargo.toml:$(cat "$manifest")" \
--add-virtual-file="$prefix/Cargo.lock:$(cat "$lockfile")" \
HEAD |
xz >"$path"
rm "$manifest"
rm "$lockfile"
rmdir "$tmpdir"
# Output what we did, and the sha256 hash
echo "Tarball written to $path"
openssl dgst -sha256 "$path"

View File

@@ -26,7 +26,7 @@ fi
wd="$PWD"
# Get the version from git-describe
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
VERSION=$(build_tools/git_version_gen.sh)
# The name of the prefix, which is the directory that you get when you untar
prefix="fish-$VERSION"
@@ -42,8 +42,14 @@ rm -f "$path" "$path".xz
PREFIX_TMPDIR=$(mktemp -d)
cd "$PREFIX_TMPDIR"
# Add .cargo/config.toml. This means that the caller may need to remove that file from the tarball.
# See e4674cd7b5f (.cargo/config.toml: exclude from tarball, 2025-01-12)
mkdir .cargo
cargo vendor --manifest-path "$wd/Cargo.toml" > .cargo/config.toml
{
cat "$wd"/.cargo/config.toml
cargo vendor --manifest-path "$wd/Cargo.toml"
} > .cargo/config.toml
tar cfvJ "$path".xz vendor .cargo

View File

@@ -1,28 +1,28 @@
<html>
<head>
<style>
body {
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
code, tt {
font-family: ui-monospace, Menlo, monospace;
}
</style>
</head>
<body>
<p>
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
</p>
<p>
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
</p>
<p>
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
</p>
<p>
<code>chsh -s /usr/local/bin/fish</code>
</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.org/fish-shell/fish-shell/">GitHub</a>.</p>
</body>
<head>
<style>
body {
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
code, tt {
font-family: ui-monospace, Menlo, monospace;
}
</style>
</head>
<body>
<p>
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
</p>
<p>
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
</p>
<p>
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
</p>
<p>
<code>chsh -s /usr/local/bin/fish</code>
</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.com/fish-shell/fish-shell/">GitHub</a>.</p>
</body>
</html>

View File

@@ -4,8 +4,8 @@
if test $# -eq 0
then
echo "usage: $0 shellname [shellname ...]"
exit 1
echo "usage: $0 shellname [shellname ...]"
exit 1
fi
scriptname=$(basename "$0")

View File

@@ -83,9 +83,15 @@ sed -i \
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
CHANGELOG.rst
CommitVersion() {
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline
CreateCommit() {
git commit -m "$1
Created by ./build_tools/release.sh $version"
}
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline # bumps the version in Cargo.lock
if [ "$1" = "$version" ]; then
# debchange is a Debian script to manage the Debian changelog, but
# it's too annoying to install everywhere. Just do it by hand.
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
@@ -99,14 +105,13 @@ fish (${version}-1) stable; urgency=medium
EOF
mv contrib/debian/changelog.new contrib/debian/changelog
git add CHANGELOG.rst Cargo.toml Cargo.lock contrib/debian/changelog
git commit -m "$2
Created by ./build_tools/release.sh $version"
}
CommitVersion "$version" "Release $version"
git add contrib/debian/changelog
fi
git add CHANGELOG.rst Cargo.toml Cargo.lock
CreateCommit "Release $version"
# Tags must be full objects, not lightweight tags, for
# git_version-gen.sh to work.
git -c "user.signingKey=$committer" \
tag --sign --message="Release $version" $version
@@ -174,7 +179,7 @@ actual_tag_oid=$(git ls-remote "$remote" |
)
CopyDocs() {
rm -rf "$fish_site/site/docs/$1"
cp -r "$tmpdir/local-tarball/fish-$version/user_doc/html" "$fish_site/site/docs/$1"
cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
git -C $fish_site add "site/docs/$1"
}
minor_version=${version%.*}
@@ -276,7 +281,8 @@ fish ?.?.? (released ???)
EOF
)
printf %s\\n "$changelog" >CHANGELOG.rst
CommitVersion ${version}-snapshot "start new cycle"
git add CHANGELOG.rst
CreateCommit "start new cycle"
git push $remote HEAD:master
} fi
@@ -296,6 +302,8 @@ milestone_number() {
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
--method PATCH --raw-field state=closed
next_minor_version=$(echo "$minor_version" |
awk -F. '{ printf "%s.%s", $1, $2+1 }')
if [ -z "$(milestone_number "$next_minor_version")" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_minor_version"

View File

@@ -24,7 +24,7 @@ if set -l -q _flag_all
end
end
set -l workspace_root (status dirname)/..
set -l workspace_root (realpath (status dirname)/..)
if test $all = yes
if not set -l -q _flag_force; and not set -l -q _flag_check

View File

@@ -10,22 +10,49 @@ command -v updatecli
command -v uv
sort --version-sort </dev/null
uv lock --check
# TODO This is copied from .github/actions/install-sphinx/action.yml
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
update_gh_action() {
repo=$1
version=$(curl -fsS "https://api.github.com/repos/$repo/releases/latest" | jq -r .tag_name)
[ -n "$version" ]
tag_oid=$(git ls-remote "https://github.com/$repo.git" "refs/tags/$version" | cut -f1)
[ -n "$tag_oid" ]
find .github/workflows -name '*.yml' -type f -exec \
sed -i "s|uses: $repo@\S\+\( \+#.*\)\?|\
uses: $repo@$tag_oid # $version, build_tools/update-dependencies.sh|g" {} +
}
update_gh_action actions/checkout
update_gh_action actions/github-script
update_gh_action actions/upload-artifact
update_gh_action actions/download-artifact
update_gh_action docker/login-action
update_gh_action docker/build-push-action
update_gh_action docker/metadata-action
update_gh_action EmbarkStudios/cargo-deny-action
update_gh_action dessant/lock-threads
update_gh_action softprops/action-gh-release
update_gh_action msys2/setup-msys2
updatecli "${@:-apply}"
uv lock # Python version constraints may have changed.
# Python version constraints may have changed.
uv lock --upgrade --exclude-newer="$(date --date='7 days ago' --iso-8601)"
from_gh() {
repo=$1
path=$2
out_dir=$3
destination=$3
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/master/"${path}")
printf '%s\n' >"$out_dir/$(basename "$path")" "$contents"
printf '%s\n' >"$destination" "$contents"
}
from_gh ridiculousfish/widecharwidth widechar_width.rs crates/widecharwidth/src/
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/
from_gh ridiculousfish/widecharwidth widechar_width.rs crates/widecharwidth/src/widechar_width.rs
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/littlecheck.py
from_gh catppuccin/fish 'themes/Catppuccin Frappe.theme' share/themes/catppuccin-frappe.theme
from_gh catppuccin/fish 'themes/Catppuccin Macchiato.theme' share/themes/catppuccin-macchiato.theme
from_gh catppuccin/fish 'themes/Catppuccin Mocha.theme' share/themes/catppuccin-mocha.theme
# Update Cargo.lock
cargo update

View File

@@ -1,42 +1,32 @@
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
HINTS
$ENV{SPHINX_DIR}
PATH_SUFFIXES bin
DOC "Sphinx documentation generator")
include(FeatureSummary)
set(SPHINX_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/doc_src")
set(SPHINX_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/user_doc")
set(SPHINX_BUILD_DIR "${SPHINX_ROOT_DIR}/build")
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
set(SPHINX_OUTPUT_DIR "${FISH_RUST_BUILD_DIR}/fish-docs")
set(FISH_INDENT_FOR_BUILDING_DOCS "" CACHE FILEPATH "Path to fish_indent executable for building HTML docs")
if(FISH_INDENT_FOR_BUILDING_DOCS)
set(SPHINX_HTML_FISH_INDENT_DEP)
else()
set(FISH_INDENT_FOR_BUILDING_DOCS "${CMAKE_CURRENT_BINARY_DIR}/fish_indent")
set(SPHINX_HTML_FISH_INDENT_DEP fish_indent)
endif()
set(VARS_FOR_CARGO_SPHINX_WRAPPER
"CARGO_TARGET_DIR=${FISH_RUST_BUILD_DIR}"
"FISH_SPHINX=${SPHINX_EXECUTABLE}"
)
# sphinx-docs uses fish_indent for highlighting.
# Prepend the output dir of fish_indent to PATH.
add_custom_target(sphinx-docs
mkdir -p ${SPHINX_HTML_DIR}/_static/
COMMAND env PATH="${CMAKE_BINARY_DIR}:$$PATH"
${SPHINX_EXECUTABLE}
-j auto
-q -b html
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
"${SPHINX_SRC_DIR}"
"${SPHINX_HTML_DIR}"
DEPENDS ${SPHINX_SRC_DIR}/fish_indent_lexer.py fish_indent
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
${Rust_CARGO} xtask html-docs --fish-indent=${FISH_INDENT_FOR_BUILDING_DOCS}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS ${SPHINX_HTML_FISH_INDENT_DEP}
COMMENT "Building HTML documentation with Sphinx")
add_custom_target(sphinx-manpages
env FISH_BUILD_VERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/${FBVF}
${SPHINX_EXECUTABLE}
-j auto
-q -b man
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_ROOT_DIR}/.doctrees-man"
"${SPHINX_SRC_DIR}"
"${SPHINX_MANPAGE_DIR}/man1"
DEPENDS CHECK-FISH-BUILD-VERSION-FILE
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
${Rust_CARGO} xtask man-pages
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Building man pages with Sphinx")
if(NOT DEFINED WITH_DOCS) # Don't check for legacy options if the new one is defined, to help bisecting.
@@ -61,7 +51,6 @@ endif()
add_feature_info(Documentation WITH_DOCS "user manual and documentation")
if(WITH_DOCS)
configure_file("${SPHINX_SRC_DIR}/conf.py" "${SPHINX_BUILD_DIR}/conf.py" @ONLY)
add_custom_target(doc ALL
DEPENDS sphinx-docs sphinx-manpages)
# Group docs targets into a DocsTargets folder

View File

@@ -28,18 +28,18 @@ set(extra_confdir
# These are the man pages that go in system manpath; all manpages go in the fish-specific manpath.
set(MANUALS ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_indent.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_key_reader.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-doc.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-tutorial.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-language.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-interactive.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-terminal-compatibility.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-completions.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-prompt-tutorial.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-for-bash-users.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-faq.1
set(MANUALS ${SPHINX_OUTPUT_DIR}/man/man1/fish.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_indent.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_key_reader.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-doc.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-language.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-interactive.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-terminal-compatibility.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-completions.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-prompt-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-for-bash-users.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-faq.1
)
# Determine which man page we don't want to install.
@@ -114,9 +114,9 @@ configure_file(fish.pc.in fish.pc.noversion @ONLY)
add_custom_command(OUTPUT fish.pc
COMMAND sed '/Version/d' fish.pc.noversion > fish.pc
COMMAND printf "Version: " >> fish.pc
COMMAND cat ${FBVF} >> fish.pc
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh >> fish.pc
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS CHECK-FISH-BUILD-VERSION-FILE ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion)
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion)
add_custom_target(build_fish_pc ALL DEPENDS fish.pc)
@@ -131,9 +131,17 @@ install(DIRECTORY share/functions/
DESTINATION ${rel_datadir}/fish/functions
FILES_MATCHING PATTERN "*.fish")
install(DIRECTORY share/prompts/
DESTINATION ${rel_datadir}/fish/prompts
FILES_MATCHING PATTERN "*.fish")
install(DIRECTORY share/themes/
DESTINATION ${rel_datadir}/fish/themes
FILES_MATCHING PATTERN "*.theme")
# CONDEMNED_PAGE is managed by the conditional above
# Building the man pages is optional: if sphinx isn't installed, they're not built
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
install(DIRECTORY ${SPHINX_OUTPUT_DIR}/man/man1/
DESTINATION ${rel_datadir}/fish/man/man1
FILES_MATCHING
PATTERN "*.1"
@@ -149,18 +157,16 @@ install(DIRECTORY share/tools/web_config
PATTERN "*.css"
PATTERN "*.html"
PATTERN "*.py"
PATTERN "*.js"
PATTERN "*.theme"
PATTERN "*.fish")
PATTERN "*.js")
# Building the man pages is optional: if Sphinx isn't installed, they're not built
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/html/ # Trailing slash is important!
install(DIRECTORY ${SPHINX_OUTPUT_DIR}/html/ # Trailing slash is important!
DESTINATION ${docdir} OPTIONAL)
install(FILES CHANGELOG.rst DESTINATION ${docdir})
# Group install targets into a InstallTargets folder
set_property(TARGET build_fish_pc CHECK-FISH-BUILD-VERSION-FILE
set_property(TARGET build_fish_pc
PROPERTY FOLDER cmake/InstallTargets)
# Make a target build_root that installs into the buildroot directory, for testing.

View File

@@ -26,7 +26,7 @@ add_executable(fish_macapp EXCLUDE_FROM_ALL
# so cmake must be re-run after version changes for the app to be updated. But
# generally this will be run by make_macos_pkg.sh which always re-runs cmake.
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh
COMMAND cut -d- -f1
OUTPUT_VARIABLE FISH_SHORT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)

View File

@@ -1,11 +1,11 @@
include(FeatureSummary)
include(FindRust)
find_package(Rust REQUIRED)
set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo/build")
set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo")
if(DEFINED ASAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
list(APPEND FISH_CARGO_FEATURES_LIST "asan")
endif()
if(DEFINED TSAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
@@ -20,10 +20,17 @@ endif()
set(rust_profile $<IF:$<CONFIG:Debug>,debug,$<IF:$<CONFIG:RelWithDebInfo>,release-with-debug,release>>)
option(WITH_GETTEXT "Build with gettext localization support. Requires `msgfmt` to work." ON)
if (NOT DEFINED WITH_MESSAGE_LOCALIZATION) # Don't check for legacy options if the new one is defined, to help bisecting.
if(DEFINED WITH_GETTEXT)
message(FATAL_ERROR "the WITH_GETTEXT option is no longer supported, use -DWITH_MESSAGE_LOCALIZATION=ON|OFF")
endif()
endif()
option(WITH_MESSAGE_LOCALIZATION "Build with localization support. Requires `msgfmt` to work." ON)
# Enable gettext feature unless explicitly disabled.
if(NOT DEFINED WITH_GETTEXT OR "${WITH_GETTEXT}")
if(NOT DEFINED WITH_MESSAGE_LOCALIZATION OR "${WITH_MESSAGE_LOCALIZATION}")
list(APPEND FISH_CARGO_FEATURES_LIST "localize-messages")
endif()
add_feature_info(Translation WITH_MESSAGE_LOCALIZATION "message localization (requires gettext)")
list(JOIN FISH_CARGO_FEATURES_LIST , FISH_CARGO_FEATURES)

View File

@@ -1,5 +1,3 @@
add_executable(fish_test_helper tests/fish_test_helper.c)
FILE(GLOB FISH_CHECKS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/checks/*.fish)
foreach(CHECK ${FISH_CHECKS})
get_filename_component(CHECK_NAME ${CHECK} NAME)
@@ -8,7 +6,7 @@ foreach(CHECK ${FISH_CHECKS})
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK_NAME}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader fish_test_helper
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
endforeach(CHECK)
@@ -21,7 +19,7 @@ foreach(PEXPECT ${PEXPECTS})
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader fish_test_helper
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
endforeach(PEXPECT)
@@ -67,6 +65,6 @@ add_custom_target(fish_run_tests
--target-dir ${rust_target_dir}
${cargo_test_flags}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS fish fish_indent fish_key_reader fish_test_helper
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)

View File

@@ -1,55 +0,0 @@
# This file adds commands to manage the FISH-BUILD-VERSION-FILE (hereafter
# FBVF). This file exists in the build directory and is used to populate the
# documentation and also the version string in fish_version.o (printed with
# `echo $version` and also fish --version). The essential idea is that we are
# going to invoke git_version_gen.sh, which will update the
# FISH-BUILD-VERSION-FILE only if it needs to change; this is what makes
# incremental rebuilds fast.
#
# This code is delicate, with the chief subtlety revolving around Ninja. A
# natural and naive approach would tell the generated build system that FBVF is
# a dependency of fish_version.o, and that git_version_gen.sh updates it. Make
# will then invoke the script, check the timestamp on fish_version.o and FBVF,
# see that FBVF is earlier, and then not rebuild fish_version.o. Ninja,
# however, decides what to build up-front and will unconditionally rebuild
# fish_version.o.
#
# To avoid this with Ninja, we want to hook into its 'restat' option which we
# can do through the BYPRODUCTS feature of CMake. See
# https://cmake.org/cmake/help/latest/policy/CMP0058.html
#
# Unfortunately BYPRODUCTS behaves strangely with the Makefile generator: it
# marks FBVF as generated and then CMake itself will `touch` it on every build,
# meaning that using BYPRODUCTS will cause fish_version.o to be rebuilt
# unconditionally with the Makefile generator. Thus we want to use the
# natural-and-naive approach for Makefiles.
# **IMPORTANT** If you touch these build rules, please test both Ninja and
# Makefile generators with both a clean and dirty git tree. Verify that both
# generated build systems rebuild fish when the git tree goes from dirty to
# clean (and vice versa), and verify they do NOT rebuild it when the git tree
# stays the same (incremental builds must be fast).
# Just a handy abbreviation.
set(FBVF FISH-BUILD-VERSION-FILE)
# TODO: find a cleaner way to do this.
IF (${CMAKE_GENERATOR} STREQUAL Ninja)
set(FBVF-OUTPUT fish-build-version-witness.txt)
set(CFBVF-BYPRODUCTS ${FBVF})
else(${CMAKE_GENERATOR} STREQUAL Ninja)
set(FBVF-OUTPUT ${FBVF})
set(CFBVF-BYPRODUCTS)
endif(${CMAKE_GENERATOR} STREQUAL Ninja)
# Set up the version targets
add_custom_target(CHECK-FISH-BUILD-VERSION-FILE
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS ${CFBVF-BYPRODUCTS})
add_custom_command(OUTPUT ${FBVF-OUTPUT}
DEPENDS CHECK-FISH-BUILD-VERSION-FILE)
# Abbreviation for the target.
set(CFBVF CHECK-FISH-BUILD-VERSION-FILE)

View File

@@ -1,3 +1,43 @@
fish (4.5.0-1) stable; urgency=medium
* Release of new version 4.5.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.5.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 17 Feb 2026 11:32:33 +1100
fish (4.4.0-1) stable; urgency=medium
* Release of new version 4.4.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.4.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 03 Feb 2026 12:11:51 +1100
fish (4.3.3-1) stable; urgency=medium
* Release of new version 4.3.3.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.3 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Wed, 07 Jan 2026 08:34:20 +0100
fish (4.3.2-1) stable; urgency=medium
* Release of new version 4.3.2.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.2 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 30 Dec 2025 17:21:04 +0100
fish (4.3.1-1) stable; urgency=medium
* Release of new version 4.3.1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.1 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Sun, 28 Dec 2025 16:54:44 +0100
fish (4.3.0-1) stable; urgency=medium
* Release of new version 4.3.0.

View File

@@ -10,7 +10,7 @@ Build-Depends: debhelper-compat (= 13),
gettext,
libpcre2-dev,
rustc (>= 1.85) | rustc-web (>= 1.85) | rustc-1.85,
sphinx-doc,
python3-sphinx,
# Test dependencies
locales-all,
man-db,

View File

@@ -34,7 +34,7 @@ Copyright: 1990-2007 Free Software Foundation, Inc.
2022 fish-shell contributors
License: GPL-2+
Files: src/wgetopt.rs
Files: crates/wgetopt/src/wgetopt.rs
Copyright: 1989-1994 Free Software Foundation, Inc.
License: LGPL-2+

View File

@@ -16,14 +16,12 @@ export DEB_BUILD_MAINT_OPTIONS=optimize=-lto
# Setting the build system is still required, because otherwise the GNUmakefile gets picked up
override_dh_auto_configure:
ln -s cargo-vendor/vendor vendor
ln -s cargo-vendor/.cargo .cargo
dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DRust_CARGO=$$(command -v cargo-1.85 || command -v cargo) \
-DRust_COMPILER=$$(command -v rustc-1.85 || command -v rustc)
override_dh_clean:
dh_clean --exclude=Cargo.toml.orig
-unlink .cargo
-unlink vendor
override_dh_auto_test:

View File

@@ -0,0 +1,3 @@
# The vendor tarball drops a new version of .cargo/config into place. Representing this as a patch
# in automated workflows is tricky, so for our purposes auto-commit is fine.
auto-commit

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt, path::Path};
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt as _, path::Path};
pub fn env_var(name: &str) -> Option<String> {
let err = match env::var(name) {
@@ -34,6 +34,10 @@ pub fn fish_build_dir() -> Cow<'static, Path> {
.unwrap_or(cargo_target_dir())
}
pub fn fish_doc_dir() -> Cow<'static, Path> {
cargo_target_dir().join("fish-docs").into()
}
// TODO Move this to rsconf
pub fn rebuild_if_path_changed<P: AsRef<Path>>(path: P) {
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());
@@ -83,3 +87,18 @@ pub fn target_os_is_bsd() -> bool {
pub fn target_os_is_cygwin() -> bool {
target_os() == "cygwin"
}
#[macro_export]
macro_rules! as_os_strs {
[ $( $x:expr, )* ] => {
{
use std::ffi::OsStr;
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
s.as_ref()
}
&[
$( as_os_str($x), )*
]
}
}
}

View File

@@ -1,25 +1,24 @@
use std::path::Path;
use fish_build_helper::{as_os_strs, fish_doc_dir};
fn main() {
let man_dir = fish_build_helper::fish_build_dir().join("fish-man");
let sec1_dir = man_dir.join("man1");
let sec1_dir = fish_doc_dir().join("man").join("man1");
// Running `cargo clippy` on a clean build directory panics, because when rust-embed
// tries to embed a directory which does not exist it will panic.
let _ = std::fs::create_dir_all(&sec1_dir);
if !cfg!(clippy) {
build_man(&man_dir, &sec1_dir);
build_man(&sec1_dir);
}
}
fn build_man(man_dir: &Path, sec1_dir: &Path) {
fn build_man(sec1_dir: &Path) {
use fish_build_helper::{env_var, workspace_root};
use std::{
ffi::OsStr,
process::{Command, Stdio},
};
use std::process::{Command, Stdio};
let workspace_root = workspace_root();
let doc_src_dir = workspace_root.join("doc_src");
let doctrees_dir = fish_doc_dir().join(".doctrees-man");
fish_build_helper::rebuild_if_paths_changed([
&workspace_root.join("CHANGELOG.rst"),
@@ -27,36 +26,24 @@ fn build_man(man_dir: &Path, sec1_dir: &Path) {
&doc_src_dir,
]);
let args: &[&OsStr] = {
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
s.as_ref()
}
macro_rules! as_os_strs {
( [ $( $x:expr, )* ] ) => {
&[
$( as_os_str($x), )*
]
}
}
as_os_strs!([
"-j",
"auto",
"-q",
"-b",
"man",
"-c",
&doc_src_dir,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d",
&man_dir,
&doc_src_dir,
&sec1_dir,
])
};
let args = as_os_strs![
"-j",
"auto",
"-q",
"-b",
"man",
"-c",
&doc_src_dir,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d",
&doctrees_dir,
&doc_src_dir,
&sec1_dir,
];
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
if env_var("FISH_BUILD_DOCS") == Some("0".to_owned()) {
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
@@ -73,12 +60,12 @@ macro_rules! as_os_strs {
.spawn()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if env_var("FISH_BUILD_DOCS") == Some("1".to_string()) {
panic!(
"Could not find sphinx-build required to build man pages.\n\
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
);
}
assert_ne!(
env_var("FISH_BUILD_DOCS"),
Some("1".to_owned()),
"Could not find sphinx-build required to build man pages.\n\
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
);
rsconf::warn!(
"Could not find sphinx-build required to build man pages. \
If you install Sphinx now, you need to trigger a rebuild to include man pages. \
@@ -105,9 +92,10 @@ macro_rules! as_os_strs {
rsconf::warn!("sphinx-build: {}", String::from_utf8_lossy(&out.stderr));
}
assert_eq!(&String::from_utf8_lossy(&out.stdout), "");
if !out.status.success() {
panic!("sphinx-build failed to build the man pages.");
}
assert!(
out.status.success(),
"sphinx-build failed to build the man pages."
);
}
}
}

14
crates/color/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "fish-color"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
fish-widestring.workspace = true
[lints]
workspace = true

View File

@@ -1,6 +1,7 @@
use std::cmp::Ordering;
use crate::prelude::*;
use fish_common::assert_sorted_by_name;
use fish_widestring::{L, wstr};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Color24 {
@@ -321,8 +322,8 @@ fn term256_color_for_rgb(color: Color24) -> u8 {
#[cfg(test)]
mod tests {
use crate::color::{Color, Color24};
use crate::prelude::*;
use super::{Color, Color24};
use fish_widestring::L;
#[test]
fn parse() {
@@ -342,9 +343,18 @@ fn parse() {
#[test]
fn parse_rgb() {
assert!(Color::from_wstr(L!("##FF00A0")).is_none());
assert!(Color::from_wstr(L!("#FF00A0")) == Some(Color::from_rgb(0xff, 0x00, 0xa0)));
assert!(Color::from_wstr(L!("FF00A0")) == Some(Color::from_rgb(0xff, 0x00, 0xa0)));
assert!(Color::from_wstr(L!("FAF")) == Some(Color::from_rgb(0xff, 0xaa, 0xff)));
assert_eq!(
Color::from_wstr(L!("#FF00A0")),
Some(Color::from_rgb(0xff, 0x00, 0xa0))
);
assert_eq!(
Color::from_wstr(L!("FF00A0")),
Some(Color::from_rgb(0xff, 0x00, 0xa0))
);
assert_eq!(
Color::from_wstr(L!("FAF")),
Some(Color::from_rgb(0xff, 0xaa, 0xff))
);
}
// Regression test for multiplicative overflow in convert_color.

View File

@@ -7,9 +7,10 @@ repository.workspace = true
license.workspace = true
[dependencies]
bitflags.workspace = true
fish-widestring.workspace = true
libc.workspace = true
nix.workspace = true
once_cell.workspace = true
[lints]
workspace = true

View File

@@ -1,35 +1,130 @@
use libc::STDIN_FILENO;
use once_cell::sync::OnceCell;
use std::env;
use std::os::unix::ffi::OsStrExt;
use bitflags::bitflags;
use fish_widestring::{L, char_offset, wstr};
use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO};
use std::cell::{Cell, RefCell};
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
use std::os::unix::ffi::OsStrExt as _;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
use std::{env, mem, time};
// These are in the Unicode private-use range. We really shouldn't use this
// range but have little choice in the matter given how our lexer/parser works.
// We can't use non-characters for these two ranges because there are only 66 of
// them and we need at least 256 + 64.
//
// If sizeof(wchar_t))==4 we could avoid using private-use chars; however, that
// would result in fish having different behavior on machines with 16 versus 32
// bit wchar_t. It's better that fish behave the same on both types of systems.
//
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
pub const ENCODE_DIRECT_BASE: char = '\u{F600}';
pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256);
pub const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME");
pub const fn char_offset(base: char, offset: u32) -> char {
match char::from_u32(base as u32 + offset) {
Some(c) => c,
None => panic!("not a valid char"),
// Highest legal ASCII value.
pub const ASCII_MAX: char = 127 as char;
// Highest legal 16-bit Unicode value.
pub const UCS2_MAX: char = '\u{FFFF}';
// Highest legal byte value.
pub const BYTE_MAX: char = 0xFF as char;
// Unicode BOM value.
pub const UTF8_BOM_WCHAR: char = '\u{FEFF}';
// Use Unicode "non-characters" for internal characters as much as we can. This
// gives us 32 "characters" for internal use that we can guarantee should not
// appear in our input stream. See http://www.unicode.org/faq/private_use.html.
pub const RESERVED_CHAR_BASE: char = '\u{FDD0}';
pub const RESERVED_CHAR_END: char = '\u{FDF0}';
// Split the available non-character values into two ranges to ensure there are
// no conflicts among the places we use these special characters.
pub const EXPAND_RESERVED_BASE: char = RESERVED_CHAR_BASE;
pub const EXPAND_RESERVED_END: char = char_offset(EXPAND_RESERVED_BASE, 16);
pub const WILDCARD_RESERVED_BASE: char = EXPAND_RESERVED_END;
pub const WILDCARD_RESERVED_END: char = char_offset(WILDCARD_RESERVED_BASE, 16);
// Make sure the ranges defined above don't exceed the range for non-characters.
// This is to make sure we didn't do something stupid in subdividing the
// Unicode range for our needs.
const _: () = assert!(WILDCARD_RESERVED_END <= RESERVED_CHAR_END);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EscapeStringStyle {
Script(EscapeFlags),
Url,
Var,
Regex,
}
impl Default for EscapeStringStyle {
fn default() -> Self {
Self::Script(EscapeFlags::default())
}
}
pub fn subslice_position<T: Eq>(a: &[T], b: &[T]) -> Option<usize> {
if b.is_empty() {
return Some(0);
impl TryFrom<&wstr> for EscapeStringStyle {
type Error = &'static wstr;
fn try_from(s: &wstr) -> Result<Self, Self::Error> {
use EscapeStringStyle::*;
match s {
s if s == "script" => Ok(Self::default()),
s if s == "var" => Ok(Var),
s if s == "url" => Ok(Url),
s if s == "regex" => Ok(Regex),
_ => Err(L!("Invalid escape style")),
}
}
}
bitflags! {
/// Flags for the [`escape_string()`] function. These are only applicable when the escape style is
/// [`EscapeStringStyle::Script`].
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct EscapeFlags: u32 {
/// Do not escape special fish syntax characters like the semicolon. Only escape non-printable
/// characters and backslashes.
const NO_PRINTABLES = 1 << 0;
/// Do not try to use 'simplified' quoted escapes, and do not use empty quotes as the empty
/// string.
const NO_QUOTED = 1 << 1;
/// Do not escape tildes.
const NO_TILDE = 1 << 2;
/// Replace non-printable control characters with Unicode symbols.
const SYMBOLIC = 1 << 3;
/// Escape ,
const COMMA = 1 << 4;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnescapeStringStyle {
Script(UnescapeFlags),
Url,
Var,
}
impl Default for UnescapeStringStyle {
fn default() -> Self {
Self::Script(UnescapeFlags::default())
}
}
impl TryFrom<&wstr> for UnescapeStringStyle {
type Error = &'static wstr;
fn try_from(s: &wstr) -> Result<Self, Self::Error> {
use UnescapeStringStyle::*;
match s {
s if s == "script" => Ok(Self::default()),
s if s == "var" => Ok(Var),
s if s == "url" => Ok(Url),
_ => Err(L!("Invalid escape style")),
}
}
}
bitflags! {
/// Flags for unescape_string functions.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct UnescapeFlags: u32 {
/// escape special fish syntax characters like the semicolon
const SPECIAL = 1 << 0;
/// allow incomplete escape sequences
const INCOMPLETE = 1 << 1;
/// don't handle backslash escapes
const NO_BACKSLASHES = 1 << 2;
}
a.windows(b.len()).position(|aw| aw == b)
}
/// This function attempts to distinguish between a console session (at the actual login vty) and a
@@ -39,7 +134,7 @@ pub fn subslice_position<T: Eq>(a: &[T], b: &[T]) -> Option<usize> {
/// session. We err on the side of assuming it's not a console session. This approach isn't
/// bullet-proof and that's OK.
pub fn is_console_session() -> bool {
static IS_CONSOLE_SESSION: OnceCell<bool> = OnceCell::new();
static IS_CONSOLE_SESSION: OnceLock<bool> = OnceLock::new();
// TODO(terminal-workaround)
*IS_CONSOLE_SESSION.get_or_init(|| {
nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) })
@@ -58,3 +153,557 @@ pub fn is_console_session() -> bool {
})
})
}
/// Exits without invoking destructors (via _exit), useful for code after fork.
pub fn exit_without_destructors(code: libc::c_int) -> ! {
unsafe { libc::_exit(code) };
}
/// The character to use where the text has been truncated.
pub fn get_ellipsis_char() -> char {
'\u{2026}' // ('…')
}
/// The character or string to use where text has been truncated (ellipsis if possible, otherwise
/// ...)
pub fn get_ellipsis_str() -> &'static wstr {
L!("\u{2026}")
}
// Only pub for `src/common.rs`
pub static OBFUSCATION_READ_CHAR: AtomicU32 = AtomicU32::new(0);
pub fn get_obfuscation_read_char() -> char {
char::from_u32(OBFUSCATION_READ_CHAR.load(Ordering::Relaxed)).unwrap()
}
/// Call read, blocking and repeating on EINTR. Exits on EAGAIN.
/// Return the number of bytes read, or 0 on EOF, or an error.
pub fn read_blocked(fd: RawFd, buf: &mut [u8]) -> nix::Result<usize> {
loop {
let res = nix::unistd::read(unsafe { BorrowedFd::borrow_raw(fd) }, buf);
if let Err(nix::Error::EINTR) = res {
continue;
}
return res;
}
}
pub trait ReadExt {
/// Like [`std::io::Read::read_to_end`], but does not retry on EINTR.
fn read_to_end_interruptible(&mut self, buf: &mut Vec<u8>) -> std::io::Result<()>;
}
impl<T: Read + ?Sized> ReadExt for T {
fn read_to_end_interruptible(&mut self, buf: &mut Vec<u8>) -> std::io::Result<()> {
let mut chunk = [0_u8; 4096];
loop {
match self.read(&mut chunk)? {
0 => return Ok(()),
n => buf.extend_from_slice(&chunk[..n]),
}
}
}
}
/// A rusty port of the C++ `write_loop()` function from `common.cpp`. This should be deprecated in
/// favor of native rust read/write methods at some point.
pub fn safe_write_loop<Fd: AsRawFd>(fd: &Fd, buf: &[u8]) -> std::io::Result<()> {
let fd = fd.as_raw_fd();
let mut total = 0;
while total < buf.len() {
match nix::unistd::write(unsafe { BorrowedFd::borrow_raw(fd) }, &buf[total..]) {
Ok(written) => {
total += written;
}
Err(err) => {
if matches!(err, nix::Error::EAGAIN | nix::Error::EINTR) {
continue;
}
return Err(std::io::Error::from(err));
}
}
}
Ok(())
}
pub use safe_write_loop as write_loop;
pub const fn help_section_exists(section: &str) -> bool {
let haystack = include_str!("../../../share/help_sections");
let needle = section;
let needle = needle.as_bytes();
let haystack = haystack.as_bytes();
let nlen = needle.len();
let mut line_start = 0;
let mut i = 0;
while i <= haystack.len() {
if i == haystack.len() || haystack[i] == b'\n' {
let line_len = i - line_start;
if line_len == nlen {
let mut j = 0;
while j < nlen && haystack[line_start + j] == needle[j] {
j += 1;
}
if j == nlen {
return true;
}
}
line_start = i + 1;
}
i += 1;
}
false
}
#[macro_export]
macro_rules! help_section {
($section:expr) => {{
const {
assert!($crate::help_section_exists($section));
}
$section
}};
}
pub type Timepoint = f64;
/// Return the number of seconds from the UNIX epoch, with subsecond precision. This function uses
/// the gettimeofday function and will have the same precision as that function.
pub fn timef() -> Timepoint {
match time::SystemTime::now().duration_since(time::UNIX_EPOCH) {
Ok(difference) => difference.as_secs() as f64,
Err(until_epoch) => -(until_epoch.duration().as_secs() as f64),
}
}
/// Be able to restore the term's foreground process group.
/// This is set during startup and not modified after.
static INITIAL_FG_PROCESS_GROUP: AtomicI32 = AtomicI32::new(-1); // HACK, should be pid_t
const _: () = assert!(mem::size_of::<i32>() >= mem::size_of::<libc::pid_t>());
/// Save the value of tcgetpgrp so we can restore it on exit.
pub fn save_term_foreground_process_group() {
INITIAL_FG_PROCESS_GROUP.store(unsafe { libc::tcgetpgrp(STDIN_FILENO) }, Ordering::Relaxed);
}
pub fn restore_term_foreground_process_group_for_exit() {
// We wish to restore the tty to the initial owner. There's two ways this can go wrong:
// 1. We may steal the tty from someone else (#7060).
// 2. The call to tcsetpgrp may deliver SIGSTOP to us, and we will not exit.
// Hanging on exit seems worse, so ensure that SIGTTOU is ignored so we do not get SIGSTOP.
// Note initial_fg_process_group == 0 is possible with Linux pid namespaces.
// This is called during shutdown and from a signal handler. We don't bother to complain on
// failure because doing so is unlikely to be noticed.
// Safety: All of getpgrp, signal, and tcsetpgrp are async-signal-safe.
let initial_fg_process_group = INITIAL_FG_PROCESS_GROUP.load(Ordering::Relaxed);
if initial_fg_process_group > 0 && initial_fg_process_group != unsafe { libc::getpgrp() } {
unsafe {
libc::signal(SIGTTOU, SIG_IGN);
libc::tcsetpgrp(STDIN_FILENO, initial_fg_process_group);
}
}
}
/// A wrapper around Cell which supports modifying the contents, scoped to a region of code.
/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value,
/// instead of requiring an accessor function which returns a mutable reference to a field.
pub struct ScopedCell<T>(Cell<T>);
impl<T> Deref for ScopedCell<T> {
type Target = Cell<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for ScopedCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: Copy> ScopedCell<T> {
pub fn new(value: T) -> Self {
Self(Cell::new(value))
}
/// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped.
///
/// This is useful when you want to apply a change for the duration of a scope
/// without having to manually restore the previous value.
///
/// # Example
///
/// ```
/// use fish_common::ScopedCell;
///
/// let cell = ScopedCell::new(5);
/// assert_eq!(cell.get(), 5);
///
/// {
/// let _guard = cell.scoped_mod(|v| *v += 10);
/// assert_eq!(cell.get(), 15);
/// }
///
/// // Restored after scope
/// assert_eq!(cell.get(), 5);
/// ```
pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>(
&'a self,
modifier: Modifier,
) -> impl ScopeGuarding + 'a {
let mut val = self.get();
modifier(&mut val);
let saved = self.replace(val);
ScopeGuard::new(self, move |cell| cell.set(saved))
}
}
/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code.
pub struct ScopedRefCell<T>(RefCell<T>);
impl<T> Deref for ScopedRefCell<T> {
type Target = RefCell<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for ScopedRefCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> ScopedRefCell<T> {
pub fn new(value: T) -> Self {
Self(RefCell::new(value))
}
/// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped.
///
/// This is useful when you want to change part of a data structure for the duration of a scope,
/// and automatically restore the original value afterward.
///
/// The `accessor` function selects the field to modify by returning a mutable reference to it.
///
/// # Example
/// ```
/// use fish_common::ScopedRefCell;
///
/// struct State { flag: bool }
///
/// let cell = ScopedRefCell::new(State { flag: false });
/// assert_eq!(cell.borrow().flag, false);
///
/// {
/// let _guard = cell.scoped_set(true, |s| &mut s.flag);
/// assert_eq!(cell.borrow().flag, true);
/// }
///
/// // Restored after scope
/// assert_eq!(cell.borrow().flag, false);
/// ```
pub fn scoped_set<'a, Accessor, Value: 'a>(
&'a self,
value: Value,
accessor: Accessor,
) -> impl ScopeGuarding + 'a
where
Accessor: Fn(&mut T) -> &mut Value + 'a,
{
let mut data = self.borrow_mut();
let mut saved = std::mem::replace(accessor(&mut data), value);
ScopeGuard::new(self, move |cell| {
let mut data = cell.borrow_mut();
std::mem::swap((accessor)(&mut data), &mut saved);
})
}
/// Convenience method for replacing the entire contents of the ScopedRefCell, restoring it when dropped.
///
/// Equivalent to `scoped_set(value, |s| s)`.
///
/// # Example
/// ```
/// use fish_common::ScopedRefCell;
///
/// let cell = ScopedRefCell::new(10);
/// assert_eq!(*cell.borrow(), 10);
///
/// {
/// let _guard = cell.scoped_replace(99);
/// assert_eq!(*cell.borrow(), 99);
/// }
///
/// assert_eq!(*cell.borrow(), 10);
/// ```
pub fn scoped_replace<'a>(&'a self, value: T) -> impl ScopeGuarding + 'a {
self.scoped_set(value, |s| s)
}
}
/// A RAII cleanup object. Unlike in C++ where there is no borrow checker, we can't just provide a
/// callback that modifies live objects willy-nilly because then there would be two &mut references
/// to the same object - the original variables we keep around to use and their captured references
/// held by the closure until its scope expires.
///
/// Instead we have a `ScopeGuard` type that takes exclusive ownership of (a mutable reference to)
/// the object to be managed. In lieu of keeping the original value around, we obtain a regular or
/// mutable reference to it via ScopeGuard's [`Deref`] and [`DerefMut`] impls.
///
/// The `ScopeGuard` is considered to be the exclusively owner of the passed value for the
/// duration of its lifetime. If you need to use the value again, use `ScopeGuard` to shadow the
/// value and obtain a reference to it via the `ScopeGuard` itself:
///
/// ```rust
/// use std::io::prelude::*;
/// use fish_common::ScopeGuard;
///
/// let file = std::fs::File::create("/dev/null").unwrap();
/// // Create a scope guard to write to the file when the scope expires.
/// // To be able to still use the file, shadow `file` with the ScopeGuard itself.
/// let mut file = ScopeGuard::new(file, |mut file| file.write_all(b"goodbye\n").unwrap());
/// // Now write to the file normally "through" the capturing ScopeGuard instance.
/// file.write_all(b"hello\n").unwrap();
///
/// // hello will be written first, then goodbye.
/// ```
pub struct ScopeGuard<T, F: FnOnce(T)>(Option<(T, F)>);
impl<T, F: FnOnce(T)> ScopeGuard<T, F> {
/// Creates a new `ScopeGuard` wrapping `value`. The `on_drop` callback is executed when the
/// ScopeGuard's lifetime expires or when it is manually dropped.
pub fn new(value: T, on_drop: F) -> Self {
Self(Some((value, on_drop)))
}
/// Invokes the callback, consuming the ScopeGuard.
pub fn commit(guard: Self) {
std::mem::drop(guard);
}
/// Cancels the invocation of the callback, returning the original wrapped value.
pub fn cancel(mut guard: Self) -> T {
let (value, _) = guard.0.take().expect("Should always have Some value");
value
}
}
impl<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0.as_ref().unwrap().0
}
}
impl<T, F: FnOnce(T)> DerefMut for ScopeGuard<T, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0.as_mut().unwrap().0
}
}
impl<T, F: FnOnce(T)> Drop for ScopeGuard<T, F> {
fn drop(&mut self) {
if let Some((value, on_drop)) = self.0.take() {
on_drop(value);
}
}
}
/// A trait expressing what ScopeGuard can do. This is necessary because our scoped cells return an
/// `impl Trait` object and therefore methods on ScopeGuard which take a self parameter cannot be
/// used.
pub trait ScopeGuarding: DerefMut + Sized {
/// Invokes the callback, consuming the guard.
fn commit(guard: Self) {
std::mem::drop(guard);
}
}
impl<T, F: FnOnce(T)> ScopeGuarding for ScopeGuard<T, F> {}
pub const fn assert_send<T: Send>() {}
pub const fn assert_sync<T: Sync>() {}
/// Asserts that a slice is alphabetically sorted by a <code>&[wstr]</code> `name` field.
///
/// Mainly useful for static asserts/const eval.
///
/// # Panics
///
/// This function panics if the given slice is unsorted.
///
/// # Examples
///
/// ```
/// use fish_widestring::{L, wstr};
/// use fish_common::assert_sorted_by_name;
///
/// const COLORS: &[(&wstr, u32)] = &[
/// // must be in alphabetical order
/// (L!("blue"), 0x0000ff),
/// (L!("green"), 0x00ff00),
/// (L!("red"), 0xff0000),
/// ];
///
/// assert_sorted_by_name!(COLORS, 0);
/// ```
///
/// While this example would fail to compile:
///
/// ```compile_fail
/// use fish_widestring::{L, wstr};
/// use fish_common::assert_sorted_by_name;
///
/// const COLORS: &[(&wstr, u32)] = &[
/// // not in alphabetical order
/// (L!("green"), 0x00ff00),
/// (L!("blue"), 0x0000ff),
/// (L!("red"), 0xff0000),
/// ];
///
/// assert_sorted_by_name!(COLORS, 0);
/// ```
#[macro_export]
macro_rules! assert_sorted_by_name {
($slice:expr, $field:tt) => {
const _: () = {
use std::cmp::Ordering;
// ugly const eval workarounds below.
const fn cmp_i32(lhs: i32, rhs: i32) -> Ordering {
match lhs - rhs {
..=-1 => Ordering::Less,
0 => Ordering::Equal,
1.. => Ordering::Greater,
}
}
const fn cmp_slice(s1: &[char], s2: &[char]) -> Ordering {
let mut i = 0;
while i < s1.len() && i < s2.len() {
match cmp_i32(s1[i] as i32, s2[i] as i32) {
Ordering::Equal => i += 1,
other => return other,
}
}
cmp_i32(s1.len() as i32, s2.len() as i32)
}
let mut i = 1;
while i < $slice.len() {
let prev = $slice[i - 1].$field.as_char_slice();
let cur = $slice[i].$field.as_char_slice();
if matches!(cmp_slice(prev, cur), Ordering::Greater) {
panic!("array must be sorted");
}
i += 1;
}
};
};
($slice:expr) => {
assert_sorted_by_name!($slice, name);
};
}
pub trait Named {
fn name(&self) -> &'static wstr;
}
/// Return a reference to the first entry with the given name, assuming the entries are sorted by
/// name. Return None if not found.
pub fn get_by_sorted_name<T: Named>(name: &wstr, vals: &'static [T]) -> Option<&'static T> {
match vals.binary_search_by_key(&name, |val| val.name()) {
Ok(index) => Some(&vals[index]),
Err(_) => None,
}
}
/// Given an input string, return a prefix of the string up to the first NUL character,
/// or the entire string if there is no NUL character.
pub fn truncate_at_nul(input: &wstr) -> &wstr {
match input.chars().position(|c| c == '\0') {
Some(nul_pos) => &input[..nul_pos],
None => input,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scoped_cell() {
let cell = ScopedCell::new(42);
{
let _guard = cell.scoped_mod(|x| *x += 1);
assert_eq!(cell.get(), 43);
}
assert_eq!(cell.get(), 42);
}
#[test]
fn test_scoped_refcell() {
#[derive(Debug, PartialEq, Clone)]
struct Data {
x: i32,
y: i32,
}
let cell = ScopedRefCell::new(Data { x: 1, y: 2 });
{
let _guard = cell.scoped_set(10, |d| &mut d.x);
assert_eq!(cell.borrow().x, 10);
}
assert_eq!(cell.borrow().x, 1);
{
let _guard = cell.scoped_replace(Data { x: 42, y: 99 });
assert_eq!(*cell.borrow(), Data { x: 42, y: 99 });
}
assert_eq!(*cell.borrow(), Data { x: 1, y: 2 });
}
#[test]
fn test_scope_guard() {
let relaxed = std::sync::atomic::Ordering::Relaxed;
let counter = std::sync::atomic::AtomicUsize::new(0);
{
let guard = ScopeGuard::new(123, |arg| {
assert_eq!(arg, 123);
counter.fetch_add(1, relaxed);
});
assert_eq!(counter.load(relaxed), 0);
std::mem::drop(guard);
assert_eq!(counter.load(relaxed), 1);
}
// commit also invokes the callback.
{
let guard = ScopeGuard::new(123, |arg| {
assert_eq!(arg, 123);
counter.fetch_add(1, relaxed);
});
assert_eq!(counter.load(relaxed), 1);
ScopeGuard::commit(guard);
assert_eq!(counter.load(relaxed), 2);
}
}
#[test]
fn test_truncate_at_nul() {
assert_eq!(truncate_at_nul(L!("abc\0def")), L!("abc"));
assert_eq!(truncate_at_nul(L!("abc")), L!("abc"));
assert_eq!(truncate_at_nul(L!("\0abc")), L!(""));
}
}

View File

@@ -8,10 +8,9 @@ license.workspace = true
[dependencies]
fish-common.workspace = true
fish-wchar.workspace = true
fish-widecharwidth.workspace = true
fish-widestring.workspace = true
libc.workspace = true
once_cell.workspace = true
widestring.workspace = true
[build-dependencies]

View File

@@ -1,5 +1,5 @@
use fish_build_helper::target_os_is_cygwin;
fn main() {
rsconf::declare_cfg("cygwin", target_os_is_cygwin())
rsconf::declare_cfg("cygwin", target_os_is_cygwin());
}

View File

@@ -3,11 +3,13 @@
//!
//! Many of these functions are more or less broken and incomplete.
use fish_wchar::prelude::*;
use fish_widecharwidth::{WcLookupTable, WcWidth};
use once_cell::sync::Lazy;
use fish_widestring::prelude::*;
use std::cmp;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::sync::{
LazyLock,
atomic::{AtomicIsize, Ordering},
};
/// Width of ambiguous East Asian characters and, as of TR11, all private-use characters.
/// 1 is the typical default, but we accept any non-negative override via `$fish_ambiguous_width`.
@@ -25,7 +27,7 @@
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
pub static FISH_EMOJI_WIDTH: AtomicIsize = AtomicIsize::new(1);
static WC_LOOKUP_TABLE: Lazy<WcLookupTable> = Lazy::new(WcLookupTable::new);
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
/// A safe wrapper around the system `wcwidth()` function
#[cfg(not(cygwin))]
@@ -34,7 +36,10 @@ pub fn wcwidth(c: char) -> isize {
pub unsafe fn wcwidth(c: libc::wchar_t) -> libc::c_int;
}
const _: () = assert!(std::mem::size_of::<libc::wchar_t>() >= std::mem::size_of::<char>());
const {
assert!(size_of::<libc::wchar_t>() >= size_of::<char>());
}
let width = unsafe { wcwidth(c as libc::wchar_t) };
isize::try_from(width).unwrap()
}
@@ -183,7 +188,7 @@ pub fn new(mut chars: Chars, to_lowercase: fn(char) -> ToLowercase) -> Self {
#[cfg(test)]
mod tests {
use super::wcscasecmp;
use fish_wchar::prelude::*;
use fish_widestring::prelude::*;
use std::cmp::Ordering;
#[test]

View File

@@ -1,7 +1,7 @@
extern crate proc_macro;
use fish_tempfile::random_filename;
use proc_macro::TokenStream;
use std::{ffi::OsString, io::Write, path::PathBuf};
use std::{ffi::OsString, io::Write as _, path::PathBuf};
fn unescape_multiline_rust_string(s: String) -> String {
if !s.contains('\n') {
@@ -26,7 +26,7 @@ enum State {
Escaped => match c {
'\\' => {
unescaped.push('\\');
state = Ground
state = Ground;
}
'\n' => state = ContinuationLineLeadingWhitespace,
_ => panic!("Unsupported escape sequence '\\{c}' in message string '{s}'"),
@@ -35,7 +35,7 @@ enum State {
' ' | '\t' => (),
_ => {
unescaped.push(c);
state = Ground
state = Ground;
}
},
}
@@ -47,11 +47,18 @@ enum State {
// unsynchronized writers to the same file.
fn write_po_entry_to_file(message: &TokenStream, dir: &OsString) {
let message_string = unescape_multiline_rust_string(message.to_string());
if message_string.contains('\n') {
panic!(
"Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'"
)
}
assert!(
!message_string.contains('\n'),
"Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'"
);
let msgid_without_quotes = &message_string[1..(message_string.len() - 1)];
// We don't want leading or trailing whitespace in our messages.
let trimmed_msgid = msgid_without_quotes.trim();
assert_eq!(msgid_without_quotes, trimmed_msgid);
assert!(!trimmed_msgid.starts_with("\\n"));
assert!(!trimmed_msgid.ends_with("\\n"));
assert!(!trimmed_msgid.starts_with("\\t"));
assert!(!trimmed_msgid.ends_with("\\t"));
// Crude check for format strings. This might result in false positives.
let format_string_annotation = if message_string.contains('%') {
"#, c-format\n"
@@ -90,11 +97,10 @@ pub fn gettext_extract(message: TokenStream) -> TokenStream {
let first_token = token_trees
.next()
.expect("gettext_extract got empty token stream. Expected one token.");
if token_trees.next().is_some() {
panic!(
"Invalid number of tokens passed to gettext_extract. Expected one token, but got more."
)
}
assert!(
token_trees.next().is_none(),
"Invalid number of tokens passed to gettext_extract. Expected one token, but got more."
);
let proc_macro2::TokenTree::Group(group) = first_token else {
panic!("Expected group in gettext_extract, but got: {first_token:?}");
};
@@ -102,11 +108,10 @@ pub fn gettext_extract(message: TokenStream) -> TokenStream {
let first_group_token = group_tokens
.next()
.expect("gettext_extract expected one group token but got none.");
if group_tokens.next().is_some() {
panic!(
"Invalid number of tokens in group passed to gettext_extract. Expected one token, but got more."
)
}
assert!(
group_tokens.next().is_none(),
"Invalid number of tokens in group passed to gettext_extract. Expected one token, but got more."
);
if let proc_macro2::TokenTree::Literal(_) = first_group_token {
write_po_entry_to_file(&message, &dir_path);
} else {

View File

@@ -22,7 +22,7 @@ fn embed_localizations(cache_dir: &Path) {
use fish_gettext_mo_file_parser::parse_mo_file;
use std::{
fs::File,
io::{BufWriter, Write},
io::{BufWriter, Write as _},
};
let po_dir = fish_build_helper::workspace_root()
@@ -91,7 +91,7 @@ fn embed_localizations(cache_dir: &Path) {
if cached_map_mtime > po_mtime {
// Cached map file is considered up-to-date.
continue;
};
}
}
// Generate the map file.
@@ -104,7 +104,7 @@ fn embed_localizations(cache_dir: &Path) {
cmd = cmd.arg("--check-format");
} else {
tmp_mo_file = Some(cache_dir.join("messages.mo"));
};
}
cmd.arg(format!(
"--output-file={}",
tmp_mo_file
@@ -115,12 +115,11 @@ fn embed_localizations(cache_dir: &Path) {
.output()
.unwrap()
};
if !output.status.success() {
panic!(
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
}
assert!(
output.status.success(),
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
let mo_data =
tmp_mo_file.map_or(output.stdout, |path| std::fs::read(path).unwrap());

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;
const U32_SIZE: usize = std::mem::size_of::<u32>();
const U32_SIZE: usize = size_of::<u32>();
fn read_le_u32(bytes: &[u8]) -> u32 {
u32::from_le_bytes(bytes[..U32_SIZE].try_into().unwrap())
@@ -47,9 +47,12 @@ fn check_if_revision_is_supported(revision: u32) -> std::io::Result<()> {
}
fn as_usize(value: u32) -> usize {
use std::mem::size_of;
const _: () = assert!(size_of::<u32>() <= size_of::<usize>());
usize::try_from(value).unwrap()
const {
assert!(size_of::<u32>() <= size_of::<usize>());
}
// SAFETY: `usize` is guaranteed to be at least as wide as `u32` by the const assert above.
unsafe { usize::try_from(value).unwrap_unchecked() }
}
fn parse_strings(

View File

@@ -8,7 +8,6 @@ license.workspace = true
[dependencies]
fish-gettext-maps.workspace = true
once_cell.workspace = true
phf.workspace = true
[lints]

View File

@@ -1,259 +1,18 @@
use fish_gettext_maps::CATALOGS;
use once_cell::sync::Lazy;
use std::{collections::HashSet, sync::Mutex};
use std::{
collections::HashMap,
sync::{LazyLock, Mutex},
};
type Catalog = &'static phf::Map<&'static str, &'static str>;
pub struct SetLanguageLints<'a> {
pub duplicates: Vec<&'a str>,
pub non_existing: Vec<&'a str>,
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum LanguagePrecedenceOrigin {
Default,
LocaleVariable(LocaleVariable),
LanguageEnvVar,
StatusLanguage,
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum LocaleVariable {
#[allow(clippy::upper_case_acronyms)]
LANG,
#[allow(non_camel_case_types)]
LC_MESSAGES,
#[allow(non_camel_case_types)]
LC_ALL,
}
impl LocaleVariable {
fn as_language_precedence_origin(&self) -> LanguagePrecedenceOrigin {
LanguagePrecedenceOrigin::LocaleVariable(*self)
}
pub fn as_str(&self) -> &'static str {
match self {
Self::LANG => "LANG",
Self::LC_MESSAGES => "LC_MESSAGES",
Self::LC_ALL => "LC_ALL",
}
}
}
impl std::fmt::Display for LocaleVariable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
struct InternalLocalizationState {
precedence_origin: LanguagePrecedenceOrigin,
language_precedence: Vec<(String, Catalog)>,
}
pub struct PublicLocalizationState {
pub precedence_origin: LanguagePrecedenceOrigin,
pub language_precedence: Vec<String>,
}
/// Stores the current localization status.
/// `is_active` indicates whether localization is currently active, and the reason if it is
/// not.
/// The `origin` indicates where the values in `language_precedence` were taken from.
/// `language_precedence` stores the catalogs in the order they should be used.
///
/// This struct should be updated when the relevant variables change or `status language` is used
/// to modify the localization state.
static LOCALIZATION_STATE: Lazy<Mutex<InternalLocalizationState>> =
Lazy::new(|| Mutex::new(InternalLocalizationState::new()));
impl InternalLocalizationState {
fn new() -> Self {
Self {
precedence_origin: LanguagePrecedenceOrigin::Default,
language_precedence: vec![],
}
}
fn to_public(&self) -> PublicLocalizationState {
PublicLocalizationState {
precedence_origin: self.precedence_origin,
language_precedence: self
.language_precedence
.iter()
.map(|(lang, _)| lang.to_owned())
.collect(),
}
}
fn update_from_env(
&mut self,
message_locale: Option<(LocaleVariable, String)>,
language_var: Option<Vec<String>>,
) {
// Do not override values set via `status language`.
if self.precedence_origin == LanguagePrecedenceOrigin::StatusLanguage {
return;
}
if let Some((precedence_origin, locale)) = &message_locale {
// Regular locale names start with lowercase letters (`ll_CC`, followed by some suffix).
// The C or POSIX locale is special, and often used to disable localization.
// Their names are upper-case, but variants with suffixes (`C.UTF-8`) exist.
// To ensure that such variants are accounted for, we match on prefixes of the
// locale name.
// https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02
fn is_c_locale(locale: &str) -> bool {
locale.starts_with('C') || locale.starts_with("POSIX")
}
if is_c_locale(locale) {
self.precedence_origin =
LanguagePrecedenceOrigin::LocaleVariable(*precedence_origin);
self.language_precedence.clear();
return;
}
}
let (precedence_origin, language_list) = if let Some(list) = language_var {
(LanguagePrecedenceOrigin::LanguageEnvVar, list)
} else if let Some((precedence_origin, locale)) = message_locale {
let mut normalized_name = String::new();
// Strip off encoding and modifier. (We always expect UTF-8 and don't support modifiers.)
for c in locale.chars() {
if c.is_alphabetic() || c == '_' {
normalized_name.push(c);
} else {
break;
}
}
// At this point, the normalized_name should have the shape `ll` or `ll_CC`.
(
precedence_origin.as_language_precedence_origin(),
vec![normalized_name],
)
} else {
(LanguagePrecedenceOrigin::Default, vec![])
};
let mut seen_languages = HashSet::new();
self.language_precedence = language_list
.into_iter()
.flat_map(|lang| find_existing_catalogs(&lang))
.filter(|(lang, _)| seen_languages.insert(lang.to_owned()))
.collect();
self.precedence_origin = precedence_origin;
}
fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
&mut self,
langs: &'b [S],
) -> SetLanguageLints<'a> {
let mut seen = HashSet::new();
let mut duplicates = vec![];
for lang in langs {
let lang = lang.as_ref();
if !seen.insert(lang) {
duplicates.push(lang)
}
}
let mut existing_langs = vec![];
let mut non_existing = vec![];
for lang in langs {
let lang = lang.as_ref();
if let Some(catalog) = CATALOGS.get(lang) {
existing_langs.push((lang.to_owned(), *catalog));
} else {
non_existing.push(lang);
}
}
let mut seen = HashSet::new();
let unique_langs = existing_langs
.into_iter()
.filter(|(lang, _)| seen.insert(lang.to_owned()))
.collect();
self.language_precedence = unique_langs;
self.precedence_origin = LanguagePrecedenceOrigin::StatusLanguage;
SetLanguageLints {
duplicates,
non_existing,
}
}
}
/// Tries to find catalogs for `language`.
/// `language` must be an ISO 639 language code, optionally followed by an underscore and an ISO
/// 3166 country/territory code.
/// Uses the catalog with the exact same name as `language` if it exists.
/// If a country code is present (`ll_CC`), only the catalog named `ll` will be considered as a fallback.
/// If no country code is present (`ll`), all catalogs whose names start with `ll_` will be used in
/// arbitrary order.
fn find_existing_catalogs(language: &str) -> Vec<(String, Catalog)> {
// Try the exact name first.
// If there already is a corresponding catalog return the language.
if let Some(catalog) = CATALOGS.get(language) {
return vec![(language.to_owned(), catalog)];
}
let language_without_country_code = language.split_once('_').map_or(language, |(ll, _cc)| ll);
if language == language_without_country_code {
// We have `ll` format. In this case, try to find any catalog whose name starts with `ll_`.
// Note that it is important to include the underscore in the pattern, otherwise `ll` might
// fall back to `llx_CC`, where `llx` is a 3-letter language identifier.
let ll_prefix = format!("{language}_");
let mut lang_catalogs = vec![];
for (&lang_name, &catalog) in CATALOGS.entries() {
if lang_name.starts_with(&ll_prefix) {
lang_catalogs.push((lang_name.to_owned(), catalog));
}
}
lang_catalogs
} else {
// If `language` contained a country code, we only try to fall back to a catalog
// without a country code.
if let Some(catalog) = CATALOGS.get(language_without_country_code) {
vec![(language_without_country_code.to_owned(), catalog)]
} else {
vec![]
}
}
}
pub fn update_from_env(
locale: Option<(LocaleVariable, String)>,
language_var: Option<Vec<String>>,
) {
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
localization_state.update_from_env(locale, language_var);
}
pub fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
langs: &'b [S],
) -> SetLanguageLints<'a> {
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
localization_state.update_from_status_language_builtin(langs)
}
pub fn unset_from_status_language_builtin(
locale: Option<(LocaleVariable, String)>,
language_var: Option<Vec<String>>,
) {
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
localization_state.precedence_origin = LanguagePrecedenceOrigin::Default;
localization_state.update_from_env(locale, language_var);
}
pub fn status_language() -> PublicLocalizationState {
let localization_state = LOCALIZATION_STATE.lock().unwrap();
localization_state.to_public()
}
static LANGUAGE_PRECEDENCE: Mutex<Vec<(&'static str, Catalog)>> = Mutex::new(Vec::new());
pub fn gettext(message_str: &'static str) -> Option<&'static str> {
let localization_state = LOCALIZATION_STATE.lock().unwrap();
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
// Use the localization from the highest-precedence language that has one available.
for (_, catalog) in localization_state.language_precedence.iter() {
for (_, catalog) in language_precedence.iter() {
if let Some(localized_str) = catalog.get(message_str) {
return Some(localized_str);
}
@@ -261,8 +20,40 @@ pub fn gettext(message_str: &'static str) -> Option<&'static str> {
None
}
pub fn list_available_languages() -> Vec<&'static str> {
let mut langs: Vec<_> = CATALOGS.entries().map(|(&lang, _)| lang).collect();
langs.sort();
langs
#[derive(Clone, Copy)]
pub struct GettextLocalizationLanguage {
language: &'static str,
}
static AVAILABLE_LANGUAGES: LazyLock<HashMap<&'static str, GettextLocalizationLanguage>> =
LazyLock::new(|| {
HashMap::from_iter(
CATALOGS
.entries()
.map(|(&language, _)| (language, GettextLocalizationLanguage { language })),
)
});
pub fn get_available_languages() -> &'static HashMap<&'static str, GettextLocalizationLanguage> {
&AVAILABLE_LANGUAGES
}
pub fn set_language_precedence(new_precedence: &[GettextLocalizationLanguage]) {
let catalogs = new_precedence
.iter()
.map(|lang| {
(
lang.language,
*CATALOGS
.get(lang.language)
.expect("Only languages for which catalogs exist may be passed to gettext."),
)
})
.collect();
*LANGUAGE_PRECEDENCE.lock().unwrap() = catalogs;
}
pub fn get_language_precedence() -> Vec<&'static str> {
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
language_precedence.iter().map(|&(lang, _)| lang).collect()
}

View File

@@ -8,10 +8,11 @@ description = "printf implementation, based on musl"
license = "MIT"
[dependencies]
assert_matches.workspace = true
libc.workspace = true
widestring = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-width.workspace = true
widestring = { workspace = true, optional = true }
[lints]
workspace = true

View File

@@ -199,27 +199,28 @@ fn to_arg(self) -> Arg<'a> {
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[cfg(feature = "widestring")]
use widestring::utf32str;
#[test]
fn test_to_arg() {
assert!(matches!("test".to_arg(), Arg::Str("test")));
assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
assert_matches!("test".to_arg(), Arg::Str("test"));
assert_matches!(String::from("test").to_arg(), Arg::Str(_));
#[cfg(feature = "widestring")]
assert!(matches!(utf32str!("test").to_arg(), Arg::WStr(_)));
assert_matches!(utf32str!("test").to_arg(), Arg::WStr(_));
#[cfg(feature = "widestring")]
assert!(matches!(WString::from("test").to_arg(), Arg::WStr(_)));
assert!(matches!(42f32.to_arg(), Arg::Float(_)));
assert!(matches!(42f64.to_arg(), Arg::Float(_)));
assert!(matches!('x'.to_arg(), Arg::UInt(120)));
assert_matches!(WString::from("test").to_arg(), Arg::WStr(_));
assert_matches!(42f32.to_arg(), Arg::Float(_));
assert_matches!(42f64.to_arg(), Arg::Float(_));
assert_matches!('x'.to_arg(), Arg::UInt(120));
let mut usize_val: usize = 0;
assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
assert!(matches!(42i8.to_arg(), Arg::SInt(42)));
assert!(matches!(42i16.to_arg(), Arg::SInt(42)));
assert!(matches!(42i32.to_arg(), Arg::SInt(42)));
assert!(matches!(42i64.to_arg(), Arg::SInt(42)));
assert!(matches!(42isize.to_arg(), Arg::SInt(42)));
assert_matches!((&mut usize_val).to_arg(), Arg::USizeRef(_));
assert_matches!(42i8.to_arg(), Arg::SInt(42));
assert_matches!(42i16.to_arg(), Arg::SInt(42));
assert_matches!(42i32.to_arg(), Arg::SInt(42));
assert_matches!(42i64.to_arg(), Arg::SInt(42));
assert_matches!(42isize.to_arg(), Arg::SInt(42));
assert_eq!((-42i8).to_arg(), Arg::SInt(-42));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42));
@@ -227,14 +228,14 @@ fn test_to_arg() {
assert_eq!((-42i64).to_arg(), Arg::SInt(-42));
assert_eq!((-42isize).to_arg(), Arg::SInt(-42));
assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
assert!(matches!(42u16.to_arg(), Arg::UInt(42)));
assert!(matches!(42u32.to_arg(), Arg::UInt(42)));
assert!(matches!(42u64.to_arg(), Arg::UInt(42)));
assert!(matches!(42usize.to_arg(), Arg::UInt(42)));
assert_matches!(42u8.to_arg(), Arg::UInt(42));
assert_matches!(42u16.to_arg(), Arg::UInt(42));
assert_matches!(42u32.to_arg(), Arg::UInt(42));
assert_matches!(42u64.to_arg(), Arg::UInt(42));
assert_matches!(42usize.to_arg(), Arg::UInt(42));
let ptr = std::ptr::from_ref(&42f32);
assert!(matches!(ptr.to_arg(), Arg::UInt(_)));
assert_matches!(ptr.to_arg(), Arg::UInt(_));
}
#[test]

View File

@@ -266,7 +266,7 @@ fn should_round_up(&self, digit_idx: i32, remainder: u32, mod_base: u32) -> bool
if rounding_digit & 1 != 0 {
round += 2.0;
// round now has an odd lsb (though round itself is even).
debug_assert!(round.to_bits() & 1 != 0);
debug_assert_ne!(round.to_bits() & 1, 0);
}
// Set 'small' to a value which is less than halfway, exactly halfway, or more than halfway

View File

@@ -4,6 +4,7 @@
use super::locale::Locale;
use super::printf_impl::{ConversionSpec, Error, ModifierFlags, pad};
use assert_matches::debug_assert_matches;
use decimal::{DIGIT_WIDTH, Decimal, DigitLimit};
use std::cmp::min;
use std::fmt::Write;
@@ -129,10 +130,10 @@ pub(crate) fn format_float(
) -> Result<usize, Error> {
// Only float conversions are expected.
type CS = ConversionSpec;
debug_assert!(matches!(
debug_assert_matches!(
conv_spec,
CS::e | CS::E | CS::f | CS::F | CS::g | CS::G | CS::a | CS::A
));
);
let prefix = match (y.is_sign_negative(), flags.mark_pos, flags.pad_pos) {
(true, _, _) => "-",
(false, true, _) => "+",

View File

@@ -51,7 +51,7 @@ macro_rules! sprintf {
{
// May be no args!
#[allow(unused_imports)]
use $crate::ToArg;
use $crate::ToArg as _;
$crate::printf_c_locale(
$target,
$fmt,
@@ -84,7 +84,7 @@ macro_rules! sprintf {
///
/// let result = printf_c_locale(&mut output, fmt, &mut args);
///
/// assert!(result == Ok(10));
/// assert_eq!(result, Ok(10));
/// assert_eq!(output, "1.2346e+05");
/// ```
pub fn printf_c_locale(

View File

@@ -2,11 +2,12 @@
use super::arg::Arg;
use super::fmt_fp::format_float;
use super::locale::Locale;
use assert_matches::assert_matches;
use std::fmt::{self, Write};
use std::mem;
use std::result::Result;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation as _;
use unicode_width::UnicodeWidthStr as _;
#[cfg(feature = "widestring")]
use widestring::Utf32Str as wstr;
@@ -57,7 +58,7 @@ fn try_set(&mut self, c: char) -> bool {
'+' => self.mark_pos = true,
'\'' => self.grouped = true,
_ => return false,
};
}
true
}
}
@@ -301,7 +302,7 @@ pub(super) fn pad(
min_width: usize,
current_width: usize,
) -> fmt::Result {
assert!(c == '0' || c == ' ');
assert_matches!(c, '0' | ' ');
if current_width >= min_width {
return Ok(());
}
@@ -341,7 +342,7 @@ pub(super) fn pad(
///
/// let result = sprintf_locale(&mut output, fmt, &locale::EN_US_LOCALE, &mut args);
///
/// assert!(result == Ok(12));
/// assert_eq!(result, Ok(12));
/// assert_eq!(output, "1,234,567.89");
/// ```
pub fn sprintf_locale(
@@ -371,7 +372,7 @@ pub fn sprintf_locale(
}
// Consume the % at the start of the format specifier.
debug_assert!(s.at(0) == Some('%'));
debug_assert_eq!(s.at(0), Some('%'));
s.advance_by(1);
// Read modifier flags. '-' and '0' flags are mutually exclusive.
@@ -561,7 +562,7 @@ pub fn sprintf_locale(
};
// Numeric output should be empty iff the value is 0.
if spec_is_numeric && body.is_empty() {
debug_assert!(arg.as_uint().unwrap() == 0);
debug_assert_eq!(arg.as_uint().unwrap(), 0);
}
// Decide if we want to apply thousands grouping to the body, and compute its size.

View File

@@ -1,6 +1,6 @@
use crate::arg::ToArg;
use crate::locale::{C_LOCALE, EN_US_LOCALE, Locale};
use crate::{Error, FormatString, sprintf_locale};
use crate::{Error, FormatString as _, sprintf_locale};
use std::f64::consts::{E, PI, TAU};
use std::ffi::CStr;
use std::fmt;
@@ -13,7 +13,7 @@ macro_rules! sprintf_check {
$(,)? // optional trailing comma
) => {
{
use unicode_width::UnicodeWidthStr;
use unicode_width::UnicodeWidthStr as _;
let mut target = String::new();
let mut args = [$($arg.to_arg()),*];
let len = $crate::printf_c_locale(
@@ -861,8 +861,8 @@ fn test_float_hex_prec() {
let mut libc_sprintf = libc_sprintf_one_float_with_precision(&mut c_storage, c"%.*a");
let mut failed = false;
for sign in [1.0, -1.0].into_iter() {
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E].into_iter() {
for sign in [1.0, -1.0] {
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E] {
v *= sign;
for preci in 1..=200_usize {
rust_str.clear();

View File

@@ -5,7 +5,7 @@
path::PathBuf,
};
use rand::distr::{Alphanumeric, Distribution};
use rand::distr::{Alphanumeric, Distribution as _};
pub struct TempFile {
file: File,
@@ -114,7 +114,7 @@ pub fn new_dir() -> std::io::Result<TempDir> {
mod tests {
use std::{
fs::File,
io::{Read, Seek, Write},
io::{Read as _, Seek as _, Write as _},
};
#[test]

14
crates/util/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "fish-util"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
fish-widestring.workspace = true
rand.workspace = true
[lints]
workspace = true

View File

@@ -1,7 +1,7 @@
//! Generic utilities library.
use crate::prelude::*;
use rand::{SeedableRng, rngs::SmallRng};
use fish_widestring::prelude::*;
use rand::{SeedableRng as _, rngs::SmallRng};
use std::cmp::Ordering;
use std::time;
@@ -93,7 +93,7 @@ pub fn wcsfilecmp(a: &wstr, b: &wstr) -> Ordering {
Ordering::Less // string a is a prefix of b and b is longer
}
} else {
assert!(bi == b.len());
assert_eq!(bi, b.len());
Ordering::Greater // string b is a prefix of a and a is longer
}
}
@@ -158,7 +158,7 @@ pub fn wcsfilecmp_glob(a: &wstr, b: &wstr) -> Ordering {
Ordering::Less // string a is a prefix of b and b is longer
}
} else {
assert!(bi == b.len());
assert_eq!(bi, b.len());
Ordering::Greater // string b is a prefix of a and a is longer
}
}
@@ -234,33 +234,10 @@ fn wcsfilecmp_leading_digits(a: &wstr, b: &wstr) -> (Ordering, usize, usize) {
(ret, ai, bi)
}
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
///
/// # Examples
///
/// ```
/// use fish::util::find_subslice;
/// let haystack = b"ABC ABCDAB ABCDABCDABDE";
///
/// assert_eq!(find_subslice(b"ABCDABD", haystack), Some(15));
/// assert_eq!(find_subslice(b"ABCDE", haystack), None);
/// ```
pub fn find_subslice<T: PartialEq>(
needle: impl AsRef<[T]>,
haystack: impl AsRef<[T]>,
) -> Option<usize> {
let needle = needle.as_ref();
if needle.is_empty() {
return Some(0);
}
let haystack = haystack.as_ref();
haystack.windows(needle.len()).position(|w| w == needle)
}
#[cfg(test)]
mod tests {
use super::wcsfilecmp;
use crate::prelude::*;
use fish_widestring::prelude::*;
use std::cmp::Ordering;
/// Verify the behavior of the `wcsfilecmp()` function.

View File

@@ -0,0 +1,21 @@
[package]
name = "fish-wcstringutil"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
fish-fallback.workspace = true
fish-widestring.workspace = true
# Only needed for cygwin detection.
# TODO(MSRV>=1.86): remove
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,3 @@
fn main() {
rsconf::declare_cfg("cygwin", fish_build_helper::target_os_is_cygwin());
}

View File

@@ -1,9 +1,8 @@
//! Helper functions for working with wcstring.
use crate::common::{get_ellipsis_char, get_ellipsis_str};
use crate::prelude::*;
use fish_common::{get_ellipsis_char, get_ellipsis_str};
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
use fish_wchar::decode_byte_from_char;
use fish_widestring::{decode_byte_from_char, prelude::*};
/// Return the number of newlines in a string.
pub fn count_newlines(s: &wstr) -> usize {
@@ -19,11 +18,35 @@ pub fn count_newlines(s: &wstr) -> usize {
count
}
#[derive(Eq, PartialEq)]
pub enum IsPrefix {
Prefix,
Equal,
}
pub fn is_prefix(
mut lhs: impl Iterator<Item = char>,
mut rhs: impl Iterator<Item = char>,
) -> Option<IsPrefix> {
use IsPrefix::*;
loop {
match (lhs.next(), rhs.next()) {
(None, None) => return Some(Equal),
(None, Some(_)) => return Some(Prefix),
(Some(_), None) => return None,
(Some(lhs), Some(rhs)) => {
if lhs != rhs {
return None;
}
}
}
}
}
/// Test if a string prefixes another without regard to case. Returns true if a is a prefix of b.
pub fn string_prefixes_string_case_insensitive(proposed_prefix: &wstr, value: &wstr) -> bool {
let mut proposed_prefix = lowercase(proposed_prefix.chars());
let proposed_prefix = lowercase(proposed_prefix.chars());
let value = lowercase(value.chars());
proposed_prefix.by_ref().zip(value).all(|(a, b)| a == b) && proposed_prefix.next().is_none()
is_prefix(proposed_prefix, value).is_some()
}
pub fn string_prefixes_string_maybe_case_insensitive(
@@ -48,9 +71,9 @@ pub fn strip_executable_suffix(path: &wstr) -> Option<&wstr> {
/// Test if a string is a suffix of another.
pub fn string_suffixes_string_case_insensitive(proposed_suffix: &wstr, value: &wstr) -> bool {
let mut proposed_suffix = lowercase_rev(proposed_suffix.chars());
let proposed_suffix = lowercase_rev(proposed_suffix.chars());
let value = lowercase_rev(value.chars());
proposed_suffix.by_ref().zip(value).all(|(a, b)| a == b) && proposed_suffix.next().is_none()
is_prefix(proposed_suffix, value).is_some()
}
/// Test if a string prefixes another. Returns true if a is a prefix of b.
@@ -124,7 +147,7 @@ fn fuzzy_canonicalize(c: char) -> char {
// Note that the order of entries below affects the sort order of completions.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ContainType {
/// Exact match: `foobar` matches `foo`
/// Exact match
Exact,
/// Prefix match: `foo` matches `foobar`
Prefix,
@@ -199,7 +222,7 @@ pub fn try_create(
// Helper to lazily compute if case insensitive matches should use icase or smartcase.
// Use icase if the input contains any uppercase characters, smartcase otherwise.
#[inline(always)]
fn get_case_fold(s: &widestring::Utf32Str) -> CaseSensitivity {
fn get_case_fold(s: &wstr) -> CaseSensitivity {
if s.chars().any(|c| c.is_uppercase()) {
CaseSensitivity::Insensitive
} else {
@@ -375,7 +398,7 @@ pub fn split_string_tok<'val>(
pos = next_sep + 1;
}
if pos < end && max_results > 0 {
assert!(out.len() + 1 == max_results, "Should have split the max");
assert_eq!(out.len() + 1, max_results, "Should have split the max");
out.push(wstr::from_char_slice(&val[pos..]));
}
assert!(out.len() <= max_results, "Got too many results");
@@ -557,7 +580,7 @@ mod tests {
split_string_tok, string_fuzzy_match_string, string_prefixes_string_case_insensitive,
string_suffixes_string_case_insensitive,
};
use crate::prelude::*;
use fish_widestring::prelude::*;
#[test]
fn test_string_prefixes_string_case_insensitive() {
@@ -573,6 +596,10 @@ macro_rules! validate {
validate!("İ", "i\u{307}_", true);
validate!("i\u{307}", "İ", true); // prefix is longer
validate!("i", "İ", true);
validate!("gs", "gs_", true);
validate!("gs_", "gs", false);
assert_eq!("İn".to_lowercase().as_str(), "i\u{307}n");
validate!("echo in", "echo İnstall", false);
}
#[test]
@@ -590,6 +617,8 @@ macro_rules! validate {
validate!("İ", "i\u{307}", true); // suffix is longer
validate!("İ", "", true);
validate!("i", "", false);
validate!("gs", "_gs", true);
validate!("_gs ", "gs", false);
}
#[test]

15
crates/wgetopt/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "fish-wgetopt"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
assert_matches.workspace = true
fish-wcstringutil.workspace = true
fish-widestring.workspace = true
[lints]
workspace = true

View File

@@ -24,7 +24,8 @@
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. */
use crate::prelude::*;
use assert_matches::assert_matches;
use fish_widestring::prelude::*;
/// Special char used with [`Ordering::ReturnInOrder`].
pub const NON_OPTION_CHAR: char = '\x01';
@@ -397,7 +398,7 @@ fn update_long_opt(
option_index: usize,
) -> char {
self.wopt_index += 1;
assert!(matches!(self.remaining_text.char_at(name_end), '\0' | '='));
assert_matches!(self.remaining_text.char_at(name_end), '\0' | '=');
if self.remaining_text.char_at(name_end) == '=' {
if opt_found.arg_type == ArgType::NoArgument {
@@ -566,9 +567,9 @@ fn wgetopt_inner(&mut self, longopt_index: &mut usize) -> Option<char> {
#[cfg(test)]
mod tests {
use super::{ArgType, WGetopter, WOption, wopt};
use crate::prelude::*;
use crate::wcstringutil::join_strings;
use super::{ArgType, WGetopter, wopt};
use fish_wcstringutil::join_strings;
use fish_widestring::prelude::*;
#[test]
fn test_exchange() {
@@ -611,8 +612,8 @@ fn test_exchange() {
#[test]
fn test_wgetopt() {
// Regression test for a crash.
const short_options: &wstr = L!("-a");
const long_options: &[WOption] = &[wopt(L!("add"), ArgType::NoArgument, 'a')];
let short_options = L!("-a");
let long_options = &[wopt(L!("add"), ArgType::NoArgument, 'a')];
let mut argv = [
L!("abbr"),
L!("--add"),

View File

@@ -1,5 +1,5 @@
[package]
name = "fish-wchar"
name = "fish-widestring"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
@@ -7,7 +7,6 @@ repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
widestring.workspace = true
[lints]

View File

@@ -4,23 +4,29 @@
//! - wstr: a string slice without a nul terminator. Like `&str` but wide chars.
//! - WString: an owning string without a nul terminator. Like `String` but wide chars.
use fish_common::{ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, subslice_position};
pub mod word_char;
use std::{iter, slice};
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utfstr::CharsUtf32};
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utf32str as L, utfstr::CharsUtf32};
pub mod prelude {
pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr};
}
/// Creates a wstr string slice, like the "L" prefix of C++.
/// The result is of type wstr.
/// It is NOT nul-terminated.
#[macro_export]
macro_rules! L {
($string:expr) => {
widestring::utf32str!($string)
};
}
// These are in the Unicode private-use range. We really shouldn't use this
// range but have little choice in the matter given how our lexer/parser works.
// We can't use non-characters for these two ranges because there are only 66 of
// them and we need at least 256 + 64.
//
// If sizeof(wchar_t))==4 we could avoid using private-use chars; however, that
// would result in fish having different behavior on machines with 16 versus 32
// bit wchar_t. It's better that fish behave the same on both types of systems.
//
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
pub const ENCODE_DIRECT_BASE: char = '\u{F600}';
pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256);
/// Encode a literal byte in a UTF-32 character. This is required for e.g. the echo builtin, whose
/// escape sequences can be used to construct raw byte sequences which are then interpreted as e.g.
@@ -47,6 +53,36 @@ pub fn decode_byte_from_char(c: char) -> Option<u8> {
}
}
pub const fn char_offset(base: char, offset: u32) -> char {
match char::from_u32(base as u32 + offset) {
Some(c) => c,
None => panic!("not a valid char"),
}
}
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
///
/// # Examples
///
/// ```
/// use fish_widestring::subslice_position;
/// let haystack = b"ABC ABCDAB ABCDABCDABDE";
///
/// assert_eq!(subslice_position(haystack, b"ABCDABD"), Some(15));
/// assert_eq!(subslice_position(haystack, b"ABCDE"), None);
/// ```
pub fn subslice_position<T: PartialEq>(
haystack: impl AsRef<[T]>,
needle: impl AsRef<[T]>,
) -> Option<usize> {
let needle = needle.as_ref();
if needle.is_empty() {
return Some(0);
}
let haystack = haystack.as_ref();
haystack.windows(needle.len()).position(|w| w == needle)
}
/// Helpers to convert things to widestring.
/// This is like std::string::ToString.
pub trait ToWString {
@@ -204,6 +240,15 @@ fn extend_wstring(&self, out: &mut WString) -> bool {
}
}
impl<A: DoubleEndedIterator<Item = char> + Clone, B: DoubleEndedIterator<Item = char> + Clone>
IntoCharIter for std::iter::Chain<A, B>
{
type Iter = std::iter::Chain<A, B>;
fn chars(self) -> Self::Iter {
self
}
}
/// Return true if `prefix` is a prefix of `contents`.
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
where
@@ -417,7 +462,7 @@ fn test_prefix() {
assert!(L!("abc").starts_with('a'));
assert!(L!("abc").starts_with("ab"));
assert!(L!("abc").starts_with(L!("ab")));
assert!(L!("abc").starts_with(&WString::from_str("abc")));
assert!(L!("abc").starts_with(L!("abc")));
}
#[test]
@@ -427,7 +472,7 @@ fn test_suffix() {
assert!(L!("abc").ends_with('c'));
assert!(L!("abc").ends_with("bc"));
assert!(L!("abc").ends_with(L!("bc")));
assert!(L!("abc").ends_with(&WString::from_str("abc")));
assert!(L!("abc").ends_with(L!("abc")));
}
#[test]

View File

@@ -0,0 +1,391 @@
//! Support for character classification for vi-mode word movements
use std::{cmp::Ordering, ops::RangeInclusive};
/// Character class for word movements
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WordCharClass {
Blank, // whitespace
Newline, // newline
Punctuation, // punctuation and symbols
Word, // word character
Emoji, // emoji
Superscript, // superscript (U+2070-U+207F)
Subscript, // subscript (U+2080-U+2094)
Braille, // braille (U+2800-U+28FF)
Hiragana, // Hiragana (U+3040-U+309F)
Katakana, // Katakana (U+30A0-U+30FF)
Cjk, // CJK Ideographs
Hangul, // Hangul Syllables
}
pub fn is_blank(c: char) -> bool {
WordCharClass::from_char(c) == WordCharClass::Blank
}
impl WordCharClass {
/// Reference: <https://github.com/vim/vim/blob/48940d94/src/mbyte.c#L2866-L2982>
pub fn from_char(c: char) -> Self {
// Quick check for Latin1 characters
if u32::from(c) < 0x100 {
// newline
if c == '\n' {
return WordCharClass::Newline;
}
// space, tab, NUL, or non-breaking space
if matches!(c, ' ' | '\t' | '\0' | '\u{a0}' /* NBSP */) {
return WordCharClass::Blank;
}
if is_latin1_word_char(c) {
return WordCharClass::Word;
}
return WordCharClass::Punctuation;
}
// emoji check
if is_emoji(c) {
return WordCharClass::Emoji;
}
// binary search in table
CLASSES
.binary_search_by(|interval| compare_range_to_char(&interval.range, c))
.map_or(
// most other characters are "word" characters
WordCharClass::Word,
|i| CLASSES[i].class,
)
}
}
/// Check if codepoint is a word character (alphanumeric)
/// Note: Different from vim default behavior, we do not regard underscore as a word character!
fn is_latin1_word_char(c: char) -> bool {
c.is_ascii_alphanumeric() || (matches!( c, | 'À'..='ÿ') && c != '×' && c != '÷')
}
fn compare_range_to_char(range: &RangeInclusive<char>, c: char) -> Ordering {
if *range.end() < c {
Ordering::Less
} else if *range.start() > c {
Ordering::Greater
} else {
Ordering::Equal
}
}
/// Check if codepoint is in emoji table using binary search (like vim's intable)
fn is_emoji(c: char) -> bool {
EMOJI_ALL
.binary_search_by(|range| compare_range_to_char(range, c))
.is_ok()
}
/// Character class interval
struct ClassInterval {
range: RangeInclusive<char>,
class: WordCharClass,
}
impl ClassInterval {
const fn new(range: RangeInclusive<char>, class: WordCharClass) -> Self {
Self { range, class }
}
const fn single(c: char, class: WordCharClass) -> Self {
Self::new(c..=c, class)
}
}
/// Character classification table (sorted non-overlapping intervals)
/// Reference: <https://github.com/vim/vim/blob/48940d94/src/mbyte.c#L2867-L2948>
static CLASSES: &[ClassInterval] = {
use ClassInterval as I;
use WordCharClass::*;
&[
I::single('\u{037e}', Punctuation), // Greek question mark
I::single('\u{0387}', Punctuation), // Greek ano teleia
I::new('\u{055a}'..='\u{055f}', Punctuation), // Armenian punctuation
I::single('\u{0589}', Punctuation), // Armenian full stop
I::single('\u{05be}', Punctuation),
I::single('\u{05c0}', Punctuation),
I::single('\u{05c3}', Punctuation),
I::new('\u{05f3}'..='\u{05f4}', Punctuation),
I::single('\u{060c}', Punctuation),
I::single('\u{061b}', Punctuation),
I::single('\u{061f}', Punctuation),
I::new('\u{066a}'..='\u{066d}', Punctuation),
I::single('\u{06d4}', Punctuation),
I::new('\u{0700}'..='\u{070d}', Punctuation), // Syriac punctuation
I::new('\u{0964}'..='\u{0965}', Punctuation),
I::single('\u{0970}', Punctuation),
I::single('\u{0df4}', Punctuation),
I::single('\u{0e4f}', Punctuation),
I::new('\u{0e5a}'..='\u{0e5b}', Punctuation),
I::new('\u{0f04}'..='\u{0f12}', Punctuation),
I::new('\u{0f3a}'..='\u{0f3d}', Punctuation),
I::single('\u{0f85}', Punctuation),
I::new('\u{104a}'..='\u{104f}', Punctuation), // Myanmar punctuation
I::single('\u{10fb}', Punctuation), // Georgian punctuation
I::new('\u{1361}'..='\u{1368}', Punctuation), // Ethiopic punctuation
I::new('\u{166d}'..='\u{166e}', Punctuation), // Canadian Syl. punctuation
I::single('\u{1680}', Blank),
I::new('\u{169b}'..='\u{169c}', Punctuation),
I::new('\u{16eb}'..='\u{16ed}', Punctuation),
I::new('\u{1735}'..='\u{1736}', Punctuation),
I::new('\u{17d4}'..='\u{17dc}', Punctuation), // Khmer punctuation
I::new('\u{1800}'..='\u{180a}', Punctuation), // Mongolian punctuation
I::new('\u{2000}'..='\u{200b}', Blank), // spaces
I::new('\u{200c}'..='\u{2027}', Punctuation), // punctuation and symbols
I::new('\u{2028}'..='\u{2029}', Blank),
I::new('\u{202a}'..='\u{202e}', Punctuation), // punctuation and symbols
I::single('\u{202f}', Blank),
I::new('\u{2030}'..='\u{205e}', Punctuation), // punctuation and symbols
I::single('\u{205f}', Blank),
I::new('\u{2060}'..='\u{206f}', Punctuation), // punctuation and symbols
I::new('\u{2070}'..='\u{207f}', Superscript),
I::new('\u{2080}'..='\u{2094}', Subscript),
I::new('\u{20a0}'..='\u{27ff}', Punctuation), // all kinds of symbols
I::new('\u{2800}'..='\u{28ff}', Braille),
I::new('\u{2900}'..='\u{2998}', Punctuation), // arrows, brackets, etc.
I::new('\u{29d8}'..='\u{29db}', Punctuation),
I::new('\u{29fc}'..='\u{29fd}', Punctuation),
I::new('\u{2e00}'..='\u{2e7f}', Punctuation), // supplemental punctuation
I::single('\u{3000}', Blank), // ideographic space
I::new('\u{3001}'..='\u{3020}', Punctuation), // ideographic punctuation
I::single('\u{3030}', Punctuation),
I::single('\u{303d}', Punctuation),
I::new('\u{3040}'..='\u{309f}', Hiragana),
I::new('\u{30a0}'..='\u{30ff}', Katakana),
I::new('\u{3300}'..='\u{9fff}', Cjk),
I::new('\u{ac00}'..='\u{d7a3}', Hangul),
I::new('\u{f900}'..='\u{faff}', Cjk),
I::new('\u{fd3e}'..='\u{fd3f}', Punctuation),
I::new('\u{fe30}'..='\u{fe6b}', Punctuation), // punctuation forms
I::new('\u{ff00}'..='\u{ff0f}', Punctuation), // half/fullwidth ASCII
I::new('\u{ff1a}'..='\u{ff20}', Punctuation), // half/fullwidth ASCII
I::new('\u{ff3b}'..='\u{ff40}', Punctuation), // half/fullwidth ASCII
I::new('\u{ff5b}'..='\u{ff65}', Punctuation), // half/fullwidth ASCII
I::new('\u{1d000}'..='\u{1d24f}', Punctuation), // Musical notation
I::new('\u{1d400}'..='\u{1d7ff}', Punctuation), // Mathematical Alphanumeric Symbols
I::new('\u{1f000}'..='\u{1f2ff}', Punctuation), // Game pieces; enclosed characters
I::new('\u{1f300}'..='\u{1f9ff}', Punctuation), // Many symbol blocks
I::new('\u{20000}'..='\u{2a6df}', Cjk),
I::new('\u{2a700}'..='\u{2b73f}', Cjk),
I::new('\u{2b740}'..='\u{2b81f}', Cjk),
I::new('\u{2f800}'..='\u{2fa1f}', Cjk),
]
};
/// Reference: <https://github.com/vim/vim/blob/48940d94/src/mbyte.c#L2704-L2852>
static EMOJI_ALL: &[RangeInclusive<char>] = &[
'\u{203c}'..='\u{203c}',
'\u{2049}'..='\u{2049}',
'\u{2122}'..='\u{2122}',
'\u{2139}'..='\u{2139}',
'\u{2194}'..='\u{2199}',
'\u{21a9}'..='\u{21aa}',
'\u{231a}'..='\u{231b}',
'\u{2328}'..='\u{2328}',
'\u{23cf}'..='\u{23cf}',
'\u{23e9}'..='\u{23f3}',
'\u{23f8}'..='\u{23fa}',
'\u{24c2}'..='\u{24c2}',
'\u{25aa}'..='\u{25ab}',
'\u{25b6}'..='\u{25b6}',
'\u{25c0}'..='\u{25c0}',
'\u{25fb}'..='\u{25fe}',
'\u{2600}'..='\u{2604}',
'\u{260e}'..='\u{260e}',
'\u{2611}'..='\u{2611}',
'\u{2614}'..='\u{2615}',
'\u{2618}'..='\u{2618}',
'\u{261d}'..='\u{261d}',
'\u{2620}'..='\u{2620}',
'\u{2622}'..='\u{2623}',
'\u{2626}'..='\u{2626}',
'\u{262a}'..='\u{262a}',
'\u{262e}'..='\u{262f}',
'\u{2638}'..='\u{263a}',
'\u{2640}'..='\u{2640}',
'\u{2642}'..='\u{2642}',
'\u{2648}'..='\u{2653}',
'\u{265f}'..='\u{2660}',
'\u{2663}'..='\u{2663}',
'\u{2665}'..='\u{2666}',
'\u{2668}'..='\u{2668}',
'\u{267b}'..='\u{267b}',
'\u{267e}'..='\u{267f}',
'\u{2692}'..='\u{2697}',
'\u{2699}'..='\u{2699}',
'\u{269b}'..='\u{269c}',
'\u{26a0}'..='\u{26a1}',
'\u{26a7}'..='\u{26a7}',
'\u{26aa}'..='\u{26ab}',
'\u{26b0}'..='\u{26b1}',
'\u{26bd}'..='\u{26be}',
'\u{26c4}'..='\u{26c5}',
'\u{26c8}'..='\u{26c8}',
'\u{26ce}'..='\u{26cf}',
'\u{26d1}'..='\u{26d1}',
'\u{26d3}'..='\u{26d4}',
'\u{26e9}'..='\u{26ea}',
'\u{26f0}'..='\u{26f5}',
'\u{26f7}'..='\u{26fa}',
'\u{26fd}'..='\u{26fd}',
'\u{2702}'..='\u{2702}',
'\u{2705}'..='\u{2705}',
'\u{2708}'..='\u{270d}',
'\u{270f}'..='\u{270f}',
'\u{2712}'..='\u{2712}',
'\u{2714}'..='\u{2714}',
'\u{2716}'..='\u{2716}',
'\u{271d}'..='\u{271d}',
'\u{2721}'..='\u{2721}',
'\u{2728}'..='\u{2728}',
'\u{2733}'..='\u{2734}',
'\u{2744}'..='\u{2744}',
'\u{2747}'..='\u{2747}',
'\u{274c}'..='\u{274c}',
'\u{274e}'..='\u{274e}',
'\u{2753}'..='\u{2755}',
'\u{2757}'..='\u{2757}',
'\u{2763}'..='\u{2764}',
'\u{2795}'..='\u{2797}',
'\u{27a1}'..='\u{27a1}',
'\u{27b0}'..='\u{27b0}',
'\u{27bf}'..='\u{27bf}',
'\u{2934}'..='\u{2935}',
'\u{2b05}'..='\u{2b07}',
'\u{2b1b}'..='\u{2b1c}',
'\u{2b50}'..='\u{2b50}',
'\u{2b55}'..='\u{2b55}',
'\u{3030}'..='\u{3030}',
'\u{303d}'..='\u{303d}',
'\u{3297}'..='\u{3297}',
'\u{3299}'..='\u{3299}',
'\u{1f004}'..='\u{1f004}',
'\u{1f0cf}'..='\u{1f0cf}',
'\u{1f170}'..='\u{1f171}',
'\u{1f17e}'..='\u{1f17f}',
'\u{1f18e}'..='\u{1f18e}',
'\u{1f191}'..='\u{1f19a}',
'\u{1f1e6}'..='\u{1f1ff}',
'\u{1f201}'..='\u{1f202}',
'\u{1f21a}'..='\u{1f21a}',
'\u{1f22f}'..='\u{1f22f}',
'\u{1f232}'..='\u{1f23a}',
'\u{1f250}'..='\u{1f251}',
'\u{1f300}'..='\u{1f321}',
'\u{1f324}'..='\u{1f393}',
'\u{1f396}'..='\u{1f397}',
'\u{1f399}'..='\u{1f39b}',
'\u{1f39e}'..='\u{1f3f0}',
'\u{1f3f3}'..='\u{1f3f5}',
'\u{1f3f7}'..='\u{1f4fd}',
'\u{1f4ff}'..='\u{1f53d}',
'\u{1f549}'..='\u{1f54e}',
'\u{1f550}'..='\u{1f567}',
'\u{1f56f}'..='\u{1f570}',
'\u{1f573}'..='\u{1f57a}',
'\u{1f587}'..='\u{1f587}',
'\u{1f58a}'..='\u{1f58d}',
'\u{1f590}'..='\u{1f590}',
'\u{1f595}'..='\u{1f596}',
'\u{1f5a4}'..='\u{1f5a5}',
'\u{1f5a8}'..='\u{1f5a8}',
'\u{1f5b1}'..='\u{1f5b2}',
'\u{1f5bc}'..='\u{1f5bc}',
'\u{1f5c2}'..='\u{1f5c4}',
'\u{1f5d1}'..='\u{1f5d3}',
'\u{1f5dc}'..='\u{1f5de}',
'\u{1f5e1}'..='\u{1f5e1}',
'\u{1f5e3}'..='\u{1f5e3}',
'\u{1f5e8}'..='\u{1f5e8}',
'\u{1f5ef}'..='\u{1f5ef}',
'\u{1f5f3}'..='\u{1f5f3}',
'\u{1f5fa}'..='\u{1f64f}',
'\u{1f680}'..='\u{1f6c5}',
'\u{1f6cb}'..='\u{1f6d2}',
'\u{1f6d5}'..='\u{1f6d7}',
'\u{1f6dc}'..='\u{1f6e5}',
'\u{1f6e9}'..='\u{1f6e9}',
'\u{1f6eb}'..='\u{1f6ec}',
'\u{1f6f0}'..='\u{1f6f0}',
'\u{1f6f3}'..='\u{1f6fc}',
'\u{1f7e0}'..='\u{1f7eb}',
'\u{1f7f0}'..='\u{1f7f0}',
'\u{1f90c}'..='\u{1f93a}',
'\u{1f93c}'..='\u{1f945}',
'\u{1f947}'..='\u{1f9ff}',
'\u{1fa70}'..='\u{1fa7c}',
'\u{1fa80}'..='\u{1fa88}',
'\u{1fa90}'..='\u{1fabd}',
'\u{1fabf}'..='\u{1fac5}',
'\u{1face}'..='\u{1fadb}',
'\u{1fae0}'..='\u{1fae8}',
'\u{1faf0}'..='\u{1faf8}',
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blank() {
assert_eq!(WordCharClass::from_char(' '), WordCharClass::Blank); // space
assert_eq!(WordCharClass::from_char('\t'), WordCharClass::Blank); // tab
assert_eq!(WordCharClass::from_char('\0'), WordCharClass::Blank); // NUL
assert_eq!(WordCharClass::from_char('\u{a0}'), WordCharClass::Blank); // non-breaking space
assert_eq!(WordCharClass::from_char('\u{3000}'), WordCharClass::Blank); // ideographic space
}
#[test]
fn test_word() {
assert_eq!(WordCharClass::from_char('a'), WordCharClass::Word); // 'a'
assert_eq!(WordCharClass::from_char('Z'), WordCharClass::Word); // 'Z'
assert_eq!(WordCharClass::from_char('0'), WordCharClass::Word); // '0'
assert_eq!(WordCharClass::from_char('e'), WordCharClass::Word); // 'e' with acute
}
#[test]
fn test_punctuation() {
assert_eq!(WordCharClass::from_char('.'), WordCharClass::Punctuation);
assert_eq!(WordCharClass::from_char(','), WordCharClass::Punctuation);
assert_eq!(WordCharClass::from_char(';'), WordCharClass::Punctuation);
assert_eq!(WordCharClass::from_char(''), WordCharClass::Punctuation); // ideographic comma
assert_eq!(WordCharClass::from_char('_'), WordCharClass::Punctuation);
}
#[test]
fn test_cjk() {
assert_eq!(WordCharClass::from_char('中'), WordCharClass::Cjk); // CJK character
assert_eq!(WordCharClass::from_char('𠀀'), WordCharClass::Cjk); // CJK Extension B
}
#[test]
fn test_japanese() {
assert_eq!(WordCharClass::from_char('あ'), WordCharClass::Hiragana);
assert_eq!(WordCharClass::from_char('ア'), WordCharClass::Katakana);
}
#[test]
fn test_hangul() {
assert_eq!(WordCharClass::from_char('한'), WordCharClass::Hangul);
}
#[test]
fn test_emoji() {
assert_eq!(WordCharClass::from_char('😀'), WordCharClass::Emoji); // grinning face
assert_eq!(WordCharClass::from_char('🚀'), WordCharClass::Emoji); // rocket
assert_eq!(WordCharClass::from_char('❤'), WordCharClass::Emoji); // red heart
assert_eq!(WordCharClass::from_char('✅'), WordCharClass::Emoji); // check mark
}
#[test]
fn test_special() {
assert_eq!(WordCharClass::from_char('⁰'), WordCharClass::Superscript); // superscript zero
assert_eq!(WordCharClass::from_char('₀'), WordCharClass::Subscript); // subscript zero
assert_eq!(WordCharClass::from_char('\u{2800}'), WordCharClass::Braille); // braille blank
}
}

11
crates/xtask/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "xtask"
version = "0.0.0"
rust-version.workspace = true
edition.workspace = true
repository.workspace = true
[dependencies]
clap.workspace = true
fish-build-helper.workspace = true
fish-tempfile.workspace = true

28
crates/xtask/src/lib.rs Normal file
View File

@@ -0,0 +1,28 @@
use std::{ffi::OsStr, process::Command};
pub trait CommandExt {
fn run_or_panic(&mut self);
}
impl CommandExt for Command {
fn run_or_panic(&mut self) {
match self.status() {
Ok(exit_status) => {
if !exit_status.success() {
panic!("Command did not run successfully: {:?}", self.get_program())
}
}
Err(err) => {
panic!("Failed to run command: {err}");
}
}
}
}
pub fn cargo<I, S>(cargo_args: I)
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(env!("CARGO")).args(cargo_args).run_or_panic();
}

95
crates/xtask/src/main.rs Normal file
View File

@@ -0,0 +1,95 @@
use clap::{Parser, Subcommand};
use fish_build_helper::as_os_strs;
use std::{path::PathBuf, process::Command};
use xtask::{CommandExt as _, cargo};
#[derive(Parser)]
#[command(
name = "cargo xtask",
about = "Wrapper for running various utilities",
arg_required_else_help(true)
)]
struct Cli {
#[command(subcommand)]
task: Task,
}
#[derive(Subcommand)]
enum Task {
/// Run various checks on the repo.
Check,
/// Build HTML docs
HtmlDocs {
/// Path to a fish_indent executable. If none is specified, fish_indent will be built.
#[arg(long)]
fish_indent: Option<PathBuf>,
},
/// Build man pages
ManPages,
}
fn main() {
let cli = Cli::parse();
match cli.task {
Task::Check => run_checks(),
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
}
}
fn run_checks() {
let repo_root_dir = fish_build_helper::workspace_root();
let check_script = repo_root_dir.join("build_tools").join("check.sh");
Command::new(check_script).run_or_panic();
}
fn build_html_docs(fish_indent: Option<PathBuf>) {
let fish_indent_path = fish_indent.unwrap_or_else(|| {
// Build fish_indent if no existing one is specified.
cargo([
"build",
"--bin",
"fish_indent",
"--profile",
"dev",
"--no-default-features",
]);
fish_build_helper::fish_build_dir()
.join("debug")
.join("fish_indent")
});
// Set path so `sphinx-build` can find `fish_indent`.
// Create tempdir to store symlink to fish_indent.
// This is done to avoid adding other binaries to the PATH.
let tempdir = fish_tempfile::new_dir().unwrap();
std::os::unix::fs::symlink(
std::fs::canonicalize(fish_indent_path).unwrap(),
tempdir.path().join("fish_indent"),
)
.unwrap();
let new_path = format!(
"{}:{}",
tempdir.path().to_str().unwrap(),
fish_build_helper::env_var("PATH").unwrap()
);
let doc_src_dir = fish_build_helper::workspace_root().join("doc_src");
let doctrees_dir = fish_build_helper::fish_doc_dir().join(".doctrees-html");
let html_dir = fish_build_helper::fish_doc_dir().join("html");
let args = as_os_strs![
"-j",
"auto",
"-q",
"-b",
"html",
"-c",
&doc_src_dir,
"-d",
&doctrees_dir,
&doc_src_dir,
&html_dir,
];
Command::new(option_env!("FISH_SPHINX").unwrap_or("sphinx-build"))
.env("PATH", new_path)
.args(args)
.run_or_panic();
}

View File

@@ -10,7 +10,7 @@ Synopsis
[--set-cursor[=MARKER]] ([-f | --function FUNCTION] | EXPANSION)
abbr --erase [ [-c | --command COMMAND]... ] NAME ...
abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD
abbr --show
abbr [--show] [--color WHEN]
abbr --list
abbr --query NAME ...
@@ -75,7 +75,6 @@ With **--set-cursor=MARKER**, the cursor is moved to the first occurrence of **M
With **-f FUNCTION** or **--function FUNCTION**, **FUNCTION** is treated as the name of a fish function instead of a literal replacement. When the abbreviation matches, the function will be called with the matching token as an argument. If the function's exit status is 0 (success), the token will be replaced by the function's output; otherwise the token will be left unchanged. No **EXPANSION** may be given separately.
Examples
########

View File

@@ -6,8 +6,8 @@ Synopsis
.. synopsis::
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] KEYS COMMAND ...
bind [(-M | --mode) MODE] [--preset] [--user] [KEYS]
bind [-a | --all] [--preset] [--user]
bind [(-M | --mode) MODE] [--preset] [--user] [--color WHEN] [KEYS]
bind [-a | --all] [--preset] [--user] [--color WHEN]
bind (-f | --function-names)
bind (-K | --key-names)
bind (-L | --list-modes)
@@ -19,8 +19,8 @@ Description
``bind`` manages key bindings.
If both ``KEYS`` and ``COMMAND`` are given, ``bind`` adds (or replaces) a binding in ``MODE``.
If only ``KEYS`` is given, any existing binding for those keys in the given ``MODE`` will be printed.
If no ``KEYS`` argument is provided, all bindings (in the given ``MODE``) are printed.
If only ``KEYS`` is given, ``bind`` lists any existing bindings for those keys in ``MODE`` or in all modes.
If no ``KEYS`` argument is provided, ``bind`` lists all bindings in ``MODE`` or in all modes.
``KEYS`` is a comma-separated list of key names.
Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-``, ``shift-`` and ``super-`` (i.e. the "windows" or "command" key).
@@ -102,7 +102,11 @@ The following options are available:
**--preset** should only be used in full binding sets (like when working on ``fish_vi_key_bindings``).
**-s** or **--silent**
Silences some of the error messages, including for unknown key names and unbound sequences.
Silences error message for unbound sequences.
**--color** *WHEN*
Controls when to use syntax highlighting colors when listing bindings.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.
@@ -127,18 +131,12 @@ The following special input functions are available:
move one character to the left, but do not trigger any non-movement-related operations. If the cursor is at the start of
the commandline, does nothing. Does not change the selected item in the completion pager UI when shown.
``backward-bigword``
move one whitespace-delimited word to the left
``backward-token``
move one argument to the left
``backward-delete-char``
deletes one character of input to the left of the cursor
``backward-kill-bigword``
move the whitespace-delimited word to the left of the cursor to the killring
``backward-kill-token``
move the argument to the left of the cursor to the killring
@@ -151,13 +149,25 @@ The following special input functions are available:
move one path component to the left of the cursor to the killring. A path component is everything likely to belong to a path component, i.e. not any of the following: `/={,}'\":@ |;<>&`, plus newlines and tabs.
``backward-kill-word``
move the word to the left of the cursor to the killring. The "word" here is everything up to punctuation or whitespace.
move the word to the left of the cursor to the killring, until the start of the current word (like vim's ``db``)
``backward-kill-bigword``
move the whitespace-delimited word to the left of the cursor to the killring, until the start of the current word (like vim's ``dB``)
``backward-path-component``
move one :ref:`path component <cmd-bind-backward-kill-path-component>` to the left.
move one :ref:`path component <cmd-bind-backward-kill-path-component>` to the left
``backward-word``
move one word to the left
move one word to the left, stopping at the start of the previous word (like vim's ``b``, or Emacs' ``M-b`` but differs slightly in word division rules)
``backward-bigword``
move one whitespace-delimited word to the left, stopping at the start of the previous word (like vim's ``B``)
``backward-word-end``
move to the end of the previous word (like vim's ``ge``)
``backward-bigword-end``
move to the end of the previous whitespace-delimited word (like vim's ``gE``)
``beginning-of-buffer``
moves to the beginning of the buffer, i.e. the start of the first line
@@ -211,7 +221,8 @@ The following special input functions are available:
make the current word lowercase
``end-of-buffer``
moves to the end of the buffer, i.e. the end of the first line
moves to the end of the buffer, i.e. the end of the last line;
or if already at the end of the commandline, accept the current autosuggestion.
``end-of-history``
move to the end of the history
@@ -231,9 +242,6 @@ The following special input functions are available:
``exit``
exit the shell
``forward-bigword``
move one whitespace-delimited word to the right
``forward-token``
move one argument to the right
@@ -252,10 +260,29 @@ The following special input functions are available:
``forward-single-char``
move one character to the right; or if at the end of the commandline, accept a single char from the current autosuggestion.
.. _cmd-bind-forward-word:
``forward-word``
move one word to the right; or if at the end of the commandline, accept one word
move one word to the right, stopping after the end of the current word; or if at the end of the commandline, accept one word
from the current autosuggestion.
``forward-word-vi``
like :ref:`forward-word <cmd-bind-forward-word>`, but stops at the start of the next word (like vim's ``w``)
``forward-word-end``
like :ref:`forward-word <cmd-bind-forward-word>`, but stops at the end of the next word (like vim's ``e``)
.. _cmd-bind-forward-bigword:
``forward-bigword``
move one whitespace-delimited word to the right, stopping after the end of the current word; or if at the end of the commandline, accept one word from the current autosuggestion.
``forward-bigword-vi``
like :ref:`forward-bigword <cmd-bind-forward-bigword>`, but stops at the start of the next word (like vim's ``W``)
``forward-bigword-end``
like :ref:`forward-bigword <cmd-bind-forward-bigword>`, but stops at the end of the next word (like vim's ``E``)
``history-pager``
invoke the searchable pager on history (incremental search); or if the history pager is already active, search further backwards in time.
@@ -307,9 +334,6 @@ The following special input functions are available:
The input function is useful to emulate ``ib`` vi text object.
The following brackets are considered: ``([{}])``
``kill-bigword``
move the next whitespace-delimited word to the killring
``kill-token``
move the next argument to the killring
@@ -329,7 +353,28 @@ The following special input functions are available:
move the line (without the following newline) to the killring
``kill-word``
move the next word to the killring
move the next word to the killring, stopping after the end of the killed word
``kill-word-vi``
move the next word to the killring, stopping at the start of the next word (like vim's ``dw``)
``kill-bigword``
move the next whitespace-delimited word to the killring, stopping after the end of the current word
``kill-bigword-vi``
move the next whitespace-delimited word to the killring, stopping at the start of the next word (like vim's ``dW``)
``kill-inner-word``
delete the word under the cursor (like vim's ``diw``)
``kill-inner-bigword``
delete the whitespace-delimited word under the cursor (like vim's ``diW``)
``kill-a-word``
delete the word under the cursor plus surrounding whitespace (like vim's ``daw``)
``kill-a-bigword``
delete the whitespace-delimited word under the cursor plus surrounding whitespace (like vim's ``daW``)
``nextd-or-forward-word``
if the commandline is empty, then move forward in the directory history, otherwise move one word to the right;
@@ -356,6 +401,13 @@ The following special input functions are available:
``self-insert-notfirst``
inserts the matching sequence into the command line, unless the cursor is at the beginning
``get-key``
sets :envvar:`fish_key` to the key that was pressed to trigger this binding. Example use::
for i in (seq 0 9)
bind $i get-key 'commandline -i "#$fish_key"' 'set -eg fish_key'
end
``suppress-autosuggestion``
remove the current autosuggestion. Returns true if there was a suggestion to remove.

View File

@@ -6,7 +6,7 @@ Synopsis
.. synopsis::
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS]
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS] [--color WHEN]
complete (-C | --do-complete) [--escape] STRING
Description
@@ -74,6 +74,10 @@ The following options are available:
**--escape**
When used with ``-C``, escape special characters in completions.
**--color** *WHEN*
Controls when to use syntax highlighting colors when printing completions.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -20,7 +20,7 @@ Description
The ``fish_mode_prompt`` function outputs the mode indicator for use in vi mode.
The default ``fish_mode_prompt`` function will output indicators about the current vi editor mode displayed to the left of the regular prompt. Define your own function to customize the appearance of the mode indicator. The ``$fish_bind_mode variable`` can be used to determine the current mode. It will be one of ``default``, ``insert``, ``replace_one``, ``replace``, or ``visual``.
The default ``fish_mode_prompt`` function will output indicators about the current vi editor mode displayed to the left of the regular prompt. Define your own function to customize the appearance of the mode indicator. The ``$fish_bind_mode`` variable can be used to determine the current mode. It will be one of ``default``, ``insert``, ``replace_one``, ``replace``, ``visual``, or ``operator``.
You can also define an empty ``fish_mode_prompt`` function to remove the vi mode indicators::
@@ -55,6 +55,9 @@ Example
case visual
set_color --bold brmagenta
echo 'V'
case operator f F t T
set_color --bold cyan
echo 'N'
case '*'
set_color --bold red
echo '?'

View File

@@ -24,6 +24,19 @@ The following parameters are available:
Further information on how to use :ref:`vi mode <vi-mode>`.
Differences from Vim
--------------------
Fish's vi mode aims to be familiar to vim users, but there are some differences:
**Word character handling**
In vim, underscore (``_``) is treated as a keyword character by default, so word motions like ``w``, ``b``, and ``e`` treat ``foo_bar`` as a single word. In fish, underscore is treated as punctuation, so word motions stop at underscores. For example, pressing ``w`` on ``foo_bar`` in fish stops at the ``_``, while in vim it would jump past the entire identifier.
**The** ``cw`` **command**
In vim, ``cw`` has special behavior: when the cursor is on a non-space character, it behaves like ``ce`` (change to end of word), but when the cursor is on a space, it behaves like ``dwi`` (delete word then insert).
In fish, ``cw`` always behaves like ``dwi`` - it deletes to the start of the next word (including trailing whitespace), then enters insert mode. To get vim's ``cw`` behavior in fish, use ``ce`` instead.
Examples
--------

View File

@@ -6,8 +6,8 @@ Synopsis
.. synopsis::
functions [-a | --all] [-n | --names]
functions [-D | --details] [-v] FUNCTION
functions [-a | --all] [-n | --names] [--color WHEN]
functions [-D | --details] [-v] [--color WHEN] FUNCTION
functions -c OLDNAME NEWNAME
functions -d DESCRIPTION FUNCTION
functions [-e | -q] FUNCTION ...
@@ -60,6 +60,10 @@ The following options are available:
**-t** or **--handlers-type** *TYPE*
Show all event handlers matching the given *TYPE*.
**--color** *WHEN*
Controls when to use syntax highlighting colors when printing function definitions.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -75,6 +75,10 @@ These flags can appear before or immediately after one of the sub-commands liste
**-R** or **--reverse**
Causes the history search results to be ordered oldest to newest. Which is the order used by most shells. The default is newest to oldest.
**--color** *WHEN*
Controls when to use syntax highlighting colors for the history entries.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help for this command.

View File

@@ -54,6 +54,9 @@ The following options are available:
**-r** or **--reverse**
Sets reverse mode.
**-s** or **--strikethrough**
Sets strikethrough mode.
**-u** or **--underline**, or **-uSTYLE** or **--underline=STYLE**
Set the underline mode; supported styles are **single** (default), **double**, **curly**, **dotted** and **dashed**.

View File

@@ -41,6 +41,10 @@ The following options are available:
**-q** or **--query**
Suppresses all output; this is useful when testing the exit status. For compatibility with old fish versions this is also **--quiet**.
**--color** *WHEN*
Controls when to use syntax highlighting colors when printing function definitions.
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
**-h** or **--help**
Displays help about using this command.

View File

@@ -2,9 +2,9 @@
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
from glob import glob
import os.path
@@ -117,17 +117,10 @@ copyright = "fish-shell developers"
author = "fish-shell developers"
issue_url = "https://github.com/fish-shell/fish-shell/issues"
# Parsing FISH-BUILD-VERSION-FILE is possible but hard to ensure that it is in the right place
# fish_indent is guaranteed to be on PATH for the Pygments highlighter anyway
if "FISH_BUILD_VERSION_FILE" in os.environ:
f = open(os.environ["FISH_BUILD_VERSION_FILE"], "r")
ret = f.readline().strip()
elif "FISH_BUILD_VERSION" in os.environ:
ret = os.environ["FISH_BUILD_VERSION"]
else:
ret = subprocess.check_output(
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
).decode("utf-8")
# From Cargo, or no build system.
ret = subprocess.check_output(
("../build_tools/git_version_gen.sh"), stderr=subprocess.STDOUT
).decode("utf-8")
# The full version, including alpha/beta/rc tags
release = ret.strip().split(" ")[-1]

View File

@@ -110,6 +110,7 @@ Options accepted by ``set_color`` like
``--dim``,
``--italics``,
``--reverse``,
``--strikethrough``,
``--underline`` and
``--underline-color=``
are also accepted.
@@ -133,7 +134,7 @@ Variable Meaning
.. envvar:: fish_color_end process separators like ``;`` and ``&``
.. envvar:: fish_color_error syntax errors
.. envvar:: fish_color_param ordinary command parameters
.. envvar:: fish_color_valid_path parameters that are filenames (if the file exists)
.. envvar:: fish_color_valid_path parameters and redirection targets that are filenames (if the file exists)
.. envvar:: fish_color_option options starting with "-", up to the first "--" parameter
.. envvar:: fish_color_comment comments like '# important'
.. envvar:: fish_color_selection selected text in vi visual mode

View File

@@ -2044,7 +2044,7 @@ Here is what they mean:
- ``remove-percent-self`` turns off the special ``%self`` expansion. It was introduced in 4.0. To get fish's pid, you can use the :envvar:`fish_pid` variable.
- ``test-require-arg`` removes :doc:`builtin test <cmds/test>`'s one-argument form (``test "string"``. It was introduced in 4.0. To test if a string is non-empty, use ``test -n "string"``. If disabled, any call to ``test`` that would change sends a :ref:`debug message <debugging-fish>` of category "deprecated-test", so starting fish with ``fish --debug=deprecated-test`` can be used to find offending calls.
- ``mark-prompt`` makes fish report to the terminal the beginning and and of both shell prompts and command output.
- ``ignore-terminfo`` disables lookup of $TERM in the terminfo database. Use ``no-ignore-terminfo`` to turn it back on.
- ``ignore-terminfo`` was introduced in fish 4.1 and cannot be turned off since fish 4.5. It can still be tested for compatibility, but a ``no-ignore-terminfo`` value will be ignored. The flag disabled lookup of $TERM in the terminfo database.
- ``query-term`` allows fish to query the terminal by writing escape sequences and reading the terminal's response.
This enables features such as :ref:`scrolling <term-compat-cursor-position-report>`.
If you use an incompatible terminal, you can -- for the time being -- work around it by running (once) ``set -Ua fish_features no-query-term``.

View File

@@ -92,7 +92,7 @@ Optional Commands
* - ``\e[m``
- sgr0
- Turn off bold/dim/italic/underline/reverse attribute modes and select default colors.
- Turn off bold/dim/italic/underline/strikethrough/reverse attribute modes and select default colors.
* - ``\e[1m``
- bold
- Enter bold mode.
@@ -120,12 +120,18 @@ Optional Commands
* - ``\e[7m``
- rev
- Enter reverse video mode (swap foreground and background colors).
* - ``\e[9m``
- smxx
- Enter strikethrough mode
* - ``\e[23m``
- ritm
- Exit italic mode.
* - ``\e[24m``
- rmul
- Exit underline mode.
* - ``\e[29m``
- rmxx
- Exit strikethrough mode.
* - ``\e[38;5; Ps m``
- setaf
- Select foreground color Ps from the 256-color-palette.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More