Compare commits

..

283 Commits
4.3.3 ... 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
329 changed files with 15401 additions and 10481 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

@@ -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

@@ -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,3 +1,60 @@
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)
======================================
@@ -212,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
=========================

416
Cargo.lock generated
View File

@@ -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"
@@ -50,9 +106,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.51"
version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
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"
@@ -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.6"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fish"
version = "4.3.3"
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.13.1",
"phf_codegen",
"portable-atomic",
"rand 0.9.2",
"rand",
"rsconf",
"rust-embed",
"serial_test",
"terminfo",
"unicode-width",
"unix_path",
"widestring",
"xterm-color",
]
@@ -203,10 +316,20 @@ 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",
]
@@ -217,8 +340,8 @@ version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-common",
"fish-wchar",
"fish-widecharwidth",
"fish-widestring",
"libc",
"rsconf",
"widestring",
@@ -229,7 +352,7 @@ name = "fish-gettext"
version = "0.0.0"
dependencies = [
"fish-gettext-maps",
"phf 0.13.1",
"phf",
]
[[package]]
@@ -247,8 +370,8 @@ version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-gettext-mo-file-parser",
"phf 0.13.1",
"phf_codegen 0.13.1",
"phf",
"phf_codegen",
"rsconf",
]
@@ -260,6 +383,7 @@ version = "0.0.0"
name = "fish-printf"
version = "0.2.1"
dependencies = [
"assert_matches",
"libc",
"unicode-segmentation",
"unicode-width",
@@ -271,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]]
@@ -287,10 +431,11 @@ 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"
@@ -310,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",
@@ -355,6 +500,27 @@ dependencies = [
"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"
@@ -367,15 +533,15 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.178"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libredox"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
@@ -398,9 +564,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru"
version = "0.16.2"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown",
]
@@ -427,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",
@@ -445,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"
@@ -470,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"
@@ -525,32 +681,13 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_shared 0.13.1",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"phf_shared",
]
[[package]]
@@ -559,18 +696,8 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
dependencies = [
"phf_generator 0.13.1",
"phf_shared 0.13.1",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared 0.11.3",
"rand 0.8.5",
"phf_generator",
"phf_shared",
]
[[package]]
@@ -580,16 +707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
"phf_shared 0.13.1",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
"phf_shared",
]
[[package]]
@@ -609,9 +727,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.12.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "ppv-lite86"
@@ -624,18 +742,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.103"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
@@ -646,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"
@@ -662,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]]
@@ -672,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",
]
@@ -705,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",
]
@@ -738,9 +841,9 @@ dependencies = [
[[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",
@@ -749,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",
@@ -763,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",
@@ -833,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",
@@ -845,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",
@@ -882,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"
@@ -893,42 +996,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.111"
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
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",
@@ -974,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"
@@ -1041,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.31"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
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,19 +28,27 @@ 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.16.2"
nix = { version = "0.30.1", default-features = false, features = [
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"
@@ -55,14 +66,12 @@ rand = { version = "0.9.2", default-features = false, features = [
"thread_rng",
] }
rsconf = "0.3.0"
rust-embed = { version = "8.9.0", features = [
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.3"
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]
@@ -185,15 +198,26 @@ rustdoc.private_intra_doc_links = "allow"
[workspace.lints.clippy]
assigning_clones = "warn"
implicit_clone = "warn"
cloned_instead_of_copied = "warn"
len_without_is_empty = "allow" # we're not a library crate
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.

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

@@ -164,7 +164,7 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
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

104
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,10 +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);
rsconf::set_env_value("FISH_BUILD_VERSION", &get_version());
fish_build_helper::rebuild_if_embedded_path_changed("share");
@@ -104,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));
}
}
@@ -165,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()),
))
});
@@ -195,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

@@ -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 "$FISH_BASE_DIR"/version
then
VN=$(cat "$FISH_BASE_DIR"/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

@@ -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"

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,13 +83,18 @@ 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
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
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
fish (${version}-1) stable; urgency=medium
* Release of new version $version.
@@ -99,17 +104,14 @@ fish (${version}-1) stable; urgency=medium
-- $committer $(date -R)
EOF
mv contrib/debian/changelog.new contrib/debian/changelog
git add contrib/debian/changelog
fi
git add CHANGELOG.rst Cargo.toml Cargo.lock
git commit -m "$2
Created by ./build_tools/release.sh $version"
}
CommitVersion "$version" "Release $version"
mv contrib/debian/changelog.new contrib/debian/changelog
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
@@ -177,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%.*}
@@ -279,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

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

@@ -13,20 +13,46 @@ sort --version-sort </dev/null
# 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,55 +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)
get_filename_component(FISH_INDENT_DIR "${FISH_INDENT_FOR_BUILDING_DOCS}" DIRECTORY)
set(SPHINX_HTML_FISH_INDENT_PATH ${FISH_INDENT_DIR})
set(SPHINX_HTML_FISH_INDENT_DEP)
else()
set(SPHINX_HTML_FISH_INDENT_PATH ${CMAKE_BINARY_DIR})
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}"
)
add_custom_target(sphinx-docs
mkdir -p ${SPHINX_HTML_DIR}/_static/
COMMAND env FISH_BUILD_VERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/${FBVF}
PATH="${SPHINX_HTML_FISH_INDENT_PATH}:$$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
CHECK-FISH-BUILD-VERSION-FILE
${SPHINX_SRC_DIR}/fish_indent_lexer.py
${SPHINX_HTML_FISH_INDENT_DEP}
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.
@@ -74,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)
@@ -141,7 +141,7 @@ install(DIRECTORY share/themes/
# 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"
@@ -161,12 +161,12 @@ install(DIRECTORY share/tools/web_config
# 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,19 @@
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.

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,6 +7,8 @@ repository.workspace = true
license.workspace = true
[dependencies]
bitflags.workspace = true
fish-widestring.workspace = true
libc.workspace = true
nix.workspace = true

View File

@@ -1,35 +1,130 @@
use libc::STDIN_FILENO;
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
@@ -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,8 +8,8 @@ license.workspace = true
[dependencies]
fish-common.workspace = true
fish-wchar.workspace = true
fish-widecharwidth.workspace = true
fish-widestring.workspace = true
libc.workspace = true
widestring.workspace = true

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,8 +3,8 @@
//!
//! Many of these functions are more or less broken and incomplete.
use fish_wchar::prelude::*;
use fish_widecharwidth::{WcLookupTable, WcWidth};
use fish_widestring::prelude::*;
use std::cmp;
use std::sync::{
LazyLock,
@@ -36,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()
}
@@ -185,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

@@ -6,8 +6,7 @@
type Catalog = &'static phf::Map<&'static str, &'static str>;
static LANGUAGE_PRECEDENCE: LazyLock<Mutex<Vec<(&'static str, Catalog)>>> =
LazyLock::new(|| Mutex::new(vec![]));
static LANGUAGE_PRECEDENCE: Mutex<Vec<(&'static str, Catalog)>> = Mutex::new(Vec::new());
pub fn gettext(message_str: &'static str) -> Option<&'static str> {
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();

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,14 +18,24 @@ pub fn count_newlines(s: &wstr) -> usize {
count
}
fn is_prefix(mut lhs: impl Iterator<Item = char>, mut rhs: impl Iterator<Item = char>) -> bool {
#[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, _) => return true,
(Some(_), None) => return false,
(None, None) => return Some(Equal),
(None, Some(_)) => return Some(Prefix),
(Some(_), None) => return None,
(Some(lhs), Some(rhs)) => {
if lhs != rhs {
return false;
return None;
}
}
}
@@ -37,7 +46,7 @@ fn is_prefix(mut lhs: impl Iterator<Item = char>, mut rhs: impl Iterator<Item =
pub fn string_prefixes_string_case_insensitive(proposed_prefix: &wstr, value: &wstr) -> bool {
let proposed_prefix = lowercase(proposed_prefix.chars());
let value = lowercase(value.chars());
is_prefix(proposed_prefix, value)
is_prefix(proposed_prefix, value).is_some()
}
pub fn string_prefixes_string_maybe_case_insensitive(
@@ -64,7 +73,7 @@ pub fn strip_executable_suffix(path: &wstr) -> Option<&wstr> {
pub fn string_suffixes_string_case_insensitive(proposed_suffix: &wstr, value: &wstr) -> bool {
let proposed_suffix = lowercase_rev(proposed_suffix.chars());
let value = lowercase_rev(value.chars());
is_prefix(proposed_suffix, value)
is_prefix(proposed_suffix, value).is_some()
}
/// Test if a string prefixes another. Returns true if a is a prefix of b.
@@ -213,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 {
@@ -389,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");
@@ -571,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() {
@@ -589,6 +598,8 @@ macro_rules! validate {
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]

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 {
@@ -567,8 +568,8 @@ fn wgetopt_inner(&mut self, longopt_index: &mut usize) -> Option<char> {
#[cfg(test)]
mod tests {
use super::{ArgType, WGetopter, wopt};
use crate::prelude::*;
use crate::wcstringutil::join_strings;
use fish_wcstringutil::join_strings;
use fish_widestring::prelude::*;
#[test]
fn test_exchange() {

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

@@ -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,7 @@ 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.
@@ -131,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
@@ -155,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
@@ -215,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
@@ -235,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
@@ -256,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.
@@ -311,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
@@ -333,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;
@@ -360,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

@@ -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

@@ -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

@@ -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,16 +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
if "FISH_BUILD_VERSION_FILE" in os.environ:
# From Cmake
f = open(os.environ["FISH_BUILD_VERSION_FILE"], "r")
ret = f.readline().strip()
else:
# From Cargo, or no build system.
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.

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

File diff suppressed because it is too large Load Diff

View File

@@ -2,34 +2,34 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>fish shell</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>fish_term_icon</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>fish shell</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>0.1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>NSAppleEventsUsageDescription</key>
<string>A Terminal window with the fish shell running in it will be opened.</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2012, ridiculous_fish
<key>CFBundleDisplayName</key>
<string>fish shell</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>fish_term_icon</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>fish shell</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>0.1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>NSAppleEventsUsageDescription</key>
<string>A Terminal window with the fish shell running in it will be opened.</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2012, ridiculous_fish
All rights reserved.</string>
</dict>
</plist>

View File

@@ -2,7 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>

View File

@@ -3,12 +3,12 @@ name = "fish-shell"
version = "0.0.0"
# NOTE: versions for Python and Sphinx are specified for reproducibility.
# Lower versions will work too.
requires-python = ">=3.11" # updatecli.d/python.yml
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
dev = [
"sphinx>=9.0", # updatecli.d/python.yml
"sphinx>=9.1", # updatecli.d/python.yml
"sphinx-markdown-builder",
]

View File

@@ -29,7 +29,7 @@ complete -n '__fish_seen_subcommand_from apk; and __fish_seen_subcommand_from co
complete -n "__fish_seen_subcommand_from apk; and __fish_seen_subcommand_from $apk_subcommands" -c apkanalyzer -ka '(__fish_complete_suffix .apk)'
# files
# files
complete -f -n "__fish_seen_subcommand_from files; and not __fish_seen_subcommand_from $apk_subcommands" -c apkanalyzer -a list -d 'Lists all files in the APK'
complete -f -n "__fish_seen_subcommand_from files; and not __fish_seen_subcommand_from $apk_subcommands" -c apkanalyzer -a cat -d 'Prints out the file contents'
# files options
@@ -69,7 +69,7 @@ complete -n '__fish_seen_subcommand_from dex; and __fish_seen_subcommand_from co
complete -n "__fish_seen_subcommand_from dex; and __fish_seen_subcommand_from $dex_subcommands" -c apkanalyzer -ka '(__fish_complete_suffix .apk)'
complete -n '__fish_seen_subcommand_from dex; and __fish_seen_subcommand_from code' -c apkanalyzer -ka '(__fish_complete_suffix .class)'
# resources
# resources
complete -f -n "__fish_seen_subcommand_from resources; and not __fish_seen_subcommand_from $resources_subcommands" -c apkanalyzer -a packages -d 'Prints a list of the packages that are defined in the resources table'
complete -f -n "__fish_seen_subcommand_from resources; and not __fish_seen_subcommand_from $resources_subcommands" -c apkanalyzer -a configs -d 'Prints a list of configurations for the specified type'
complete -f -n "__fish_seen_subcommand_from resources; and not __fish_seen_subcommand_from $resources_subcommands" -c apkanalyzer -a value -d 'Prints the value of the resource specified by config, name, and type'

View File

@@ -102,7 +102,7 @@ complete -f -c bzr -n '__fish_seen_subcommand_from check' -l tree -d 'Check the
complete -f -c bzr -n '__fish_seen_subcommand_from check' -l repo -d 'Check the repository related to the current directory'
complete -f -c bzr -n '__fish_seen_subcommand_from check' -l branch -d 'Check the branch related to the current directory'
# Common long/short options
# Common long/short options
set -l $cmds init branch add ignore mv status diff merge commit send log check
complete -f -c bzr -n '__fish_seen_subcommand_from $cmds' -l usage -d 'Show usage message and options'
complete -f -c bzr -n '__fish_seen_subcommand_from $cmds' -s h -l help -d 'Show help message'

View File

@@ -34,8 +34,7 @@ end
# have an easy way to do that in the `complete` machinery at this time.
function __fish_cargo_targets
if command -q rustup
functions -q __rustup_installed_targets || complete -C"rustup " &>/dev/null
__rustup_installed_targets
rustup target list | string replace -rf "^(\S+) \(installed\)" '$1'
else
rustc --print target-list
end

View File

@@ -4,7 +4,7 @@
# This grep tries to match nonempty lines that do not start with hash
complete -c chsh -s s -l shell -x -a "(string match -r '^[^#].*' < /etc/shells)" -d "Specify your login shell"
complete -c chsh -s u -l help -d "Display help and exit "
complete -c chsh -s u -l help -d "Display help and exit"
complete -c chsh -s v -l version -d "Display version and exit"
complete -x -c chsh -a "(__fish_complete_users)"

View File

@@ -0,0 +1,101 @@
# localization: skip(not-needed)
# Tab completion for claude (https://claude.com/claude-code)
# Top-level commands
complete -c claude -f -n __fish_use_subcommand -a doctor -d 'Check the health of your Claude Code auto-updater'
complete -c claude -f -n __fish_use_subcommand -a install -d 'Install Claude Code native build'
complete -c claude -f -n __fish_use_subcommand -a mcp -d 'Configure and manage MCP servers'
complete -c claude -f -n __fish_use_subcommand -a plugin -d 'Manage Claude Code plugins'
complete -c claude -f -n __fish_use_subcommand -a setup-token -d 'Set up a long-lived authentication token'
complete -c claude -f -n __fish_use_subcommand -a update -d 'Check for updates and install if available'
# Global options
complete -c claude -f -n __fish_use_subcommand -s h -l help -d 'Display help for command'
complete -c claude -f -n __fish_use_subcommand -s v -l version -d 'Output the version number'
complete -c claude -f -n __fish_use_subcommand -s p -l print -d 'Print response and exit (useful for pipes)'
complete -c claude -f -n __fish_use_subcommand -s c -l continue -d 'Continue the most recent conversation'
complete -c claude -x -n __fish_use_subcommand -s r -l resume -d 'Resume a conversation by session ID'
complete -c claude -f -n __fish_use_subcommand -l fork-session -d 'Create a new session ID when resuming'
complete -c claude -f -n __fish_use_subcommand -s d -l debug -d 'Enable debug mode'
# Model and agent options
complete -c claude -x -n __fish_use_subcommand -l model -d 'Model for the current session'
complete -c claude -x -n __fish_use_subcommand -l agent -d 'Agent for the current session'
complete -c claude -x -n __fish_use_subcommand -l agents -d 'JSON object defining custom agents'
# Prompt options
complete -c claude -x -n __fish_use_subcommand -l system-prompt -d 'System prompt to use for the session'
complete -c claude -x -n __fish_use_subcommand -l append-system-prompt -d 'Append a system prompt to the default'
# Tool and permission options
complete -c claude -x -n __fish_use_subcommand -l tools -d 'Specify the list of available tools'
complete -c claude -x -n __fish_use_subcommand -l allowed-tools -d 'Comma-separated list of tool names to allow'
complete -c claude -x -n __fish_use_subcommand -l disallowed-tools -d 'Comma-separated list of tool names to deny'
complete -c claude -f -n __fish_use_subcommand -l dangerously-skip-permissions -d 'Bypass all permission checks'
complete -c claude -f -n __fish_use_subcommand -l allow-dangerously-skip-permissions -d 'Enable bypassing permission checks as an option'
complete -c claude -x -n __fish_use_subcommand -l permission-mode -d 'Permission mode to use'
# Directory access options
complete -c claude -x -n __fish_use_subcommand -l add-dir -d 'Additional directories to allow tool access'
# MCP server options
complete -c claude -x -n __fish_use_subcommand -l mcp-config -d 'Load MCP servers from JSON files or strings'
complete -c claude -f -n __fish_use_subcommand -l strict-mcp-config -d 'Only use MCP servers from --mcp-config'
# Chrome integration options
complete -c claude -f -n __fish_use_subcommand -l chrome -d 'Enable Claude in Chrome integration'
complete -c claude -f -n __fish_use_subcommand -l no-chrome -d 'Disable Claude in Chrome integration'
# IDE options
complete -c claude -f -n __fish_use_subcommand -l ide -d 'Automatically connect to IDE on startup'
# Output and format options (for --print mode)
complete -c claude -x -n __fish_use_subcommand -l output-format -d 'Output format'
complete -c claude -x -n __fish_use_subcommand -l input-format -d 'Input format'
complete -c claude -f -n __fish_use_subcommand -l include-partial-messages -d 'Include partial message chunks'
complete -c claude -f -n __fish_use_subcommand -l replay-user-messages -d 'Re-emit user messages on stdout'
# Session and persistence options
complete -c claude -x -n __fish_use_subcommand -l session-id -d 'Use a specific session ID'
complete -c claude -f -n __fish_use_subcommand -l no-session-persistence -d 'Disable session persistence'
# Settings and configuration options
complete -c claude -x -n __fish_use_subcommand -l settings -d 'Load additional settings from file or JSON'
complete -c claude -x -n __fish_use_subcommand -l setting-sources -d 'Comma-separated list of setting sources'
complete -c claude -f -n __fish_use_subcommand -l disable-slash-commands -d 'Disable all skills'
# Plugin options
complete -c claude -x -n __fish_use_subcommand -l plugin-dir -d 'Load plugins from directories'
# API options
complete -c claude -x -n __fish_use_subcommand -l betas -d 'Beta headers to include in API requests'
complete -c claude -x -n __fish_use_subcommand -l max-budget-usd -d 'Maximum dollar amount to spend on API calls'
complete -c claude -x -n __fish_use_subcommand -l fallback-model -d 'Enable automatic fallback to specified model'
# JSON Schema options
complete -c claude -x -n __fish_use_subcommand -l json-schema -d 'JSON Schema for structured output validation'
# File resource options
complete -c claude -x -n __fish_use_subcommand -l file -d 'File resources to download at startup'
# Verbose option
complete -c claude -f -n __fish_use_subcommand -l verbose -d 'Override verbose mode setting'
# Install subcommand options
complete -c claude -f -n '__fish_seen_subcommand_from install' -a 'stable latest' -d 'Version to install'
complete -c claude -x -n '__fish_seen_subcommand_from install' -s h -l help -d 'Display help for install command'
# MCP subcommand
complete -c claude -f -n '__fish_seen_subcommand_from mcp' -s h -l help -d 'Display help for mcp command'
# Plugin subcommand
complete -c claude -f -n '__fish_seen_subcommand_from plugin' -s h -l help -d 'Display help for plugin command'
# Doctor subcommand
complete -c claude -f -n '__fish_seen_subcommand_from doctor' -s h -l help -d 'Display help for doctor command'
# Setup-token subcommand
complete -c claude -f -n '__fish_seen_subcommand_from setup-token' -s h -l help -d 'Display help for setup-token command'
# Update subcommand
complete -c claude -f -n '__fish_seen_subcommand_from update' -s h -l help -d 'Display help for update command'

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