Compare commits

..

177 Commits

Author SHA1 Message Date
Johannes Altmanninger
0c7062def9 WIP cirrus: trigger after github action finishes
(TODO this is totally untested and mostly vibe coded.)

Whenever we push changes do docker/**, our docker images for Cirrus
CI will be rebuilt.

However, the Cirrus CI jobs will kick off at the same time as the
Docker builds, so they will likely use old images.  This can cause
surprising (albeit transient) failures.

Fix this by having Cirrus wait for GitHub Actions.

This addresses the second part of
https://github.com/fish-shell/fish-shell/pull/11884#discussion_r2423344925
2025-10-12 08:41:14 +02:00
Johannes Altmanninger
8a4a8bb6a0 cirrus: disable jammy-armv7-32bit for now
This fails with "exec format error" because our container is built
on a 64 bit system on GitHub Actions.  Not yet sure how to fix that.
2025-10-12 07:17:48 +02:00
David Adam
e1b064f6cc Bravely revert "CI: Disable some Cirrus CI jobs during RIIR transition"
This reverts what remains of commit
91be7489bc.

Closes #11871
Closes #11884
2025-10-12 07:17:48 +02:00
Johannes Altmanninger
68a8cd4501 cirrus.yml: switch back to org docker image paths
These should work now that we have (automatically-updated) docker
builds via .github/workflows/docker_builds.yml.

Ref: https://github.com/fish-shell/fish-shell/pull/11884#discussion_r2405536855
2025-10-12 07:17:48 +02:00
Johannes Altmanninger
5f0d83d2f2 cirrus: remove "file" dependency from focal-arm64 builds
Probably that was also part of --install-recommends.
fish requires "file", but system tests don't.
2025-10-12 07:17:48 +02:00
Johannes Altmanninger
03f54171c6 cirrus: delete commented configurations 2025-10-12 07:17:48 +02:00
Johannes Altmanninger
f391b4a179 docker: add back CMake for the images used in Cirrus
build_tools/check.sh would give more coverage (the translation
checks is the main difference) but it tests embed-data builds;
for now testing, traditionally installed builds is more important
(especially since I always test check.sh locally already). In future
we will probably make embedding mandatory and get rid of this schism.
2025-10-12 07:17:48 +02:00
Johannes Altmanninger
8db674b6b5 docker: fix SSL error
Cirrus builds fail with

	error: failed to get `pcre2` as a dependency of package `fish v4.1.0-snapshot (/tmp/cirrus-ci-build)`
	...
	Caused by:
	  the SSL certificate is invalid; class=Ssl (16); code=Certificate (-17)

Likely introduced by b644fdbb04 (docker: do not install recommended
packages on Ubuntu, 2025-10-06).  Some Ubuntu Dockerfiles already
install ca-certificates explicitly but others do not. Fix the latter.
2025-10-12 07:17:48 +02:00
Johannes Altmanninger
f03113d048 __fish_cache_put: fix for BusyBox stat
On alpine, tests/checks/check-completions fails with

	stat: can't read file system information for '%u:%g': No such file or directory
2025-10-12 07:17:48 +02:00
Johannes Altmanninger
6c48e214ca tests/checks/check-completions: fix for embed-data builds 2025-10-12 07:17:48 +02:00
Johannes Altmanninger
189a2e90dd __fish_print_commands: remove code clone
Also, use it for help completions also on embed-data builds.
2025-10-12 07:11:35 +02:00
Johannes Altmanninger
9b44138917 __fish_print_commands: fix environment variable injection 2025-10-12 07:08:24 +02:00
Johannes Altmanninger
ec7d20b347 Reapply "test_driver: support Python 3.8 for now"
Re-apply commit ec27b418e after it was accidentally reverted in
5102c8b137 (Update littlecheck, 2025-10-11),
fixing a hang in e.g.

	sudo docker/docker_run_tests.sh docker/jammy.Dockerfile
2025-10-12 07:01:36 +02:00
Peter Ammon
84b52a3ed1 Resurrect some Dockerfiles
Add missing black and rustfmt
2025-10-11 12:24:20 -07:00
Peter Ammon
30b1c9570f Suppress a deprecation warning on time_t
Continue to pull cirrus builds back into the land of the living.
2025-10-11 12:24:20 -07:00
Johannes Altmanninger
b88622bc35 tests/tmux-job: fix on macOS CI
The rapid input can make the screen look like this:

    fish: Job 1, 'sleep 0.5 &' has ended
    prompt 0> echo hello world
    prompt 0> sleep 3 | cat &
    bg %1 <= no check matches this, previous check on line 24
2025-10-11 18:02:57 +02:00
Johannes Altmanninger
a4edb4020d test_driver.py: output as tests/checks/... not checks/...
Something like

	tests/test_driver.py target/debug checks/foo.fish

is invalid (needs "tests/checks/foo.fish").
Let's make failure output list the right fiel name.

At least when run from the root directory.
Don't change the behavior when run from "tests/";
in that case the path was already relative.
2025-10-11 17:56:31 +02:00
Johannes Altmanninger
e1e5dfdd62 test_driver: don't chdir in async driver
The test driver is async now; so we can't change the process-wide
working directory without causing unpredictable behavior.
For example, given these two tests:

	$ cat tests/checks/a.fish
	#RUN: %fish %s
	#REQUIRES: sleep 1
	pwd >/tmp/pwd-of-a

	$ cat tests/checks/b.fish
	#RUN: %fish %s
	pwd >/tmp/pwd-of-b

running them may give both fish processes the same working directory.

	$ tests/test_driver.py target/debug tests/checks/a.fish tests/checks/b.fish
	tests/checks/b.fish  PASSED      13 ms
	tests/checks/a.fish  PASSED    1019 ms
	2 / 2 passed (0 skipped)
	$ grep . /tmp/pwd-of-a /tmp/pwd-of-b
	/tmp/pwd-of-a:/tmp/fishtest-root-1q_tnyqa/fishtest-s9cyqkgz
	/tmp/pwd-of-b:/tmp/fishtest-root-1q_tnyqa/fishtest-s9cyqkgz
2025-10-11 17:56:31 +02:00
Johannes Altmanninger
5102c8b137 Update littlecheck
Commit c12b8ed (Clean up some lints, 2025-08-28)
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
9ae9db7f70 test_driver: fix code clone 2025-10-11 17:54:09 +02:00
Johannes Altmanninger
d0aaa8d809 create_manpage_completions: fix deprecation warning
Fixes #11930
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
6024539c12 CI lint: consolidate clippy definitions 2025-10-11 17:54:09 +02:00
Johannes Altmanninger
fb06ad4a44 Update sourcehut build images 2025-10-11 17:54:09 +02:00
Johannes Altmanninger
598e98794c Fixes for tmux-fish_config.fish
This fails sometimes in high-concurrency scenarios
(build_tools/check.sh), so allow sleeping a bit longer.
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
b3a295959d webconfig: remove obsolete macOS workaround
As mentioned in #11926 our "fish_config" workaround for macOS
10.12.5 or later has been fixed in macOS 10.12.6 according to
https://andrewjaffe.net/blog/2017/05/python_bug_hunt/, I think we
can assume all users have upgraded to that patch version Remove
the workaround.
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
50dfd962ec Document system test dependencies
Notably, the parent commit adds wget.

While at it, extract a reusable action.
2025-10-11 17:54:09 +02:00
Nahor
65332eaacc Add test for fish_config in browser modified
In particular
- test that it will return an error if the URL is invalid
- that the main page matches the index.html in git
- that "Enter" key will exit

Part of #11907
2025-10-11 17:54:09 +02:00
Nahor
a00e6f8696 Add Github action to compile for MSYS2
Closes #11907
2025-10-11 17:54:09 +02:00
Nahor
6415dfbd35 Ensure different network ports in fish_config
- Windows allows port reuse under certain conditions. In fish_config
case, this allows the signal socket to use the same port as http (e.g.
when using MINGW python). This can cause some browsers to access the
signal socket rather than the http one (e.g. when connecting using
IPv4/"127.0.0.1" instead of IPv6/"::1").
- This is also more efficient since we already know that all ports up to
and including the http one are not available

Fixes #11805

Part of #11907
2025-10-11 17:54:09 +02:00
Nahor
8c387c58de Fix build for MSYS2 (missing ulimits)
Part of #11907
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
da411f6fa7 bg/fg/wait/disown/function: check for negative PID argument
While at it, extract a function.

Seems to have regressed in 4.0.0 (fc47d9fa1d (Use strongly typed
`Pid` for job control, 2024-11-11)).

Fixes #11929
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
8fe402e9f7 fg: remove rogue abs() on PID argument
Present since the initial commit but I don't think anyone relies
on this.
2025-10-11 17:54:09 +02:00
Johannes Altmanninger
c41fc52077 bg: deduplicate job argument
"bg %1" of a pipline prints the same line twice because it tries
to background the same job twice.  This doesn't make sense and
other builtins like "disown" already deduplicate, so do the same.
Unfortunately we can't use the same approach as "disown" because we
can't hold on to references to job, since we're modifying the job list.
2025-10-11 17:53:30 +02:00
Johannes Altmanninger
f7d730390c Rename process id -> process ID 2025-10-11 11:47:34 +02:00
Johannes Altmanninger
8fc30d5243 po/fr.po: delete bad translation 2025-10-11 11:47:16 +02:00
Isaac Oscar Gariano
93c4d63295 Allow overwriting argv with function -a and -V
Previously, if you called a function parameter 'argv', within the body
of the function, argv would be set to *all* the arguments to the
function, and not the one indicated by the parameter name.
The same behaviour happened if you inherited a variable named 'argv'.
Both behaviours were quite surprising, so this commit makes things more
obvious, although they could alternatively simply be made errors.

Part of #11780
2025-10-11 10:51:36 +02:00
Isaac Oscar Gariano
7a07c08860 Output function argument-names in one group.
This makes it so that printing a function definition will only use one
--argument-names group, instead of one for argument name.
For example, "function foo -a x y; ..." will print with "function foo
--argument-names x y" instead of "function foo --argument-names x
--argument-names y", which is very bizarre.

Moreover, the documentation no longer says that argument-names "Has to
be the last option.". This sentence appears to have been introduced in
error by pull #10524, since the ability to have options afterwards was
deliberately added by pull #6188.

Part of #11780
2025-10-11 10:50:07 +02:00
Isaac Oscar Gariano
1cf110d083 Use idiomatic Rust error handling for function builtin.
This simply does the same thing as #10948, but for the function builtin.

Part of #11780
2025-10-11 10:46:21 +02:00
Johannes Altmanninger
fef358fc74 Move remaining builtin implementations to dedicated files
This makes them a bit easier to find I guess.
2025-10-11 09:40:37 +02:00
Daniel Rainer
b5feb79a7c style: format entire repo by default
We want all Rust and Python files formatted, and making formatting
behavior dependent on the directory `style.fish` is called from can be
counter-intuitive, especially since `check.sh`, which also calls
`style.fish` is otherwise written in a way that allows calling it from
arbitrary working directories and getting the same results.

Closes #11925
2025-10-10 10:36:52 +02:00
Daniel Rainer
4d52245617 ci: run style.fish
This allows checking formatting of fish script and Python files, in
addition to Rust files.

Closes #11923
2025-10-10 10:36:52 +02:00
Daniel Rainer
ff308b36af ci: rename workflows
The new names are consistently formulated as commands.

`main` is not very descriptive, so change it to `test`, which is more
informative and accurate.

`rust_checks` are replaced be the more general `lint` in preparation for
non-Rust-related checks.

These changes were suggested in
https://github.com/fish-shell/fish-shell/pull/11918#discussion_r2415957733

Closes #11922
2025-10-10 10:36:08 +02:00
Johannes Altmanninger
fa8cf8a1a5 abbr: fix extra Chinese translation
Closes #11919
2025-10-09 18:12:04 +02:00
Daniel Rainer
5ade4a037e style: replace black with ruff for Python formatting
Ruff's default format is very similar to black's, so there are only a
few changes made to our Python code. They are all contained in this
commit. The primary benefit of this change is that ruff's performance is
about an order of magnitude better, reducing runtime on this repo down
to under 20ms on my machine, compared to over 150ms with black, and even
more if any changes are performed by black.

Closes #11894

Closes #11918
2025-10-09 18:12:03 +02:00
Johannes Altmanninger
861002917a tests/checks/po-files-well-formed: fix inconsistent msgfmt check 2025-10-09 18:12:03 +02:00
Fabian Boehm
eddb26d490 completions/ssh: Don't read ":" from historical hosts
Fixes #11917
2025-10-09 17:14:09 +02:00
Johannes Altmanninger
b7fe3190bb Revert "builtin function: remove dead code"
This reverts commit 993b977c9b.

Fixes #11912
2025-10-08 10:51:47 +02:00
Johannes Altmanninger
b6ddb56cc7 release.sh: fix milestone API calls 2025-10-08 10:51:47 +02:00
Johannes Altmanninger
8dd59081d7 github workflows lockthreads: only run on main repo
We should disable the whole action instead of the job but I don't
know how to do that.
2025-10-08 10:51:47 +02:00
Johannes Altmanninger
3ae17ea100 release.sh: fix deployment approval logic
(cherry picked from commit e074b27abf)
2025-10-08 10:51:47 +02:00
Fabian Boehm
10c34c5353 Revert "Move the C compiler requrement in readme"
build.rs still uses cc:: for feature detection, as 50819666b1 points out.

This reverts commit 594f1df39c.
2025-10-07 22:47:14 +02:00
Johannes Altmanninger
e3ebda3647 tests/checks/fish_config: fix for non-embedded builds 2025-10-07 22:23:04 +02:00
Johannes Altmanninger
092e7fa274 release.sh: check fish-site worktree staleness 2025-10-07 22:23:04 +02:00
Johannes Altmanninger
dd47c2baa2 Sanitize cursor position report on kitty click_events
This is easy to trigger by having a background process do "echo" to
move the terminal cursor to the next line, and then clicking anywhere.

Fixes #11905
2025-10-07 21:54:26 +02:00
Johannes Altmanninger
15065255e9 fish_config: fix regression "theme show" not showing custom themes
This regressed in 6f0532460a5~2..6f0532460a5 (fish_config: fix for
non-embedded builds, 2025-09-28).

Fixes #11903
2025-10-07 21:54:26 +02:00
Johannes Altmanninger
594f1df39c Move the C compiler requrement in readme
Fixes #11908
2025-10-07 21:54:26 +02:00
Johannes Altmanninger
724416125e tests/checks/config-paths-standalone.fish: fix bad assertion
Fixes #11906
2025-10-07 17:26:48 +02:00
Johannes Altmanninger
3afafe6398 Fix build on OpenBSD/NetBSD
Co-authored-by: Michael Nickerson <darkshadow02@me.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>

Tested on OpenBSD; we'll see about NetBSD.

Closes #11893
Closes #11892
Closes #11904
2025-10-07 15:24:02 +02:00
Johannes Altmanninger
af7446a055 Start using cfg_if 2025-10-07 15:24:02 +02:00
Johannes Altmanninger
7be101e8c9 Add OpenBSD sourcehut config 2025-10-07 15:24:01 +02:00
Johannes Altmanninger
5f18b173dd ulimit: add back RLIMIT_NICE on linux 2025-10-07 15:24:01 +02:00
Johannes Altmanninger
3fec9c8145 Embedded builds to use $workspace_root/etc again if run from build dir
Commit f05ad46980 (config_paths: remove vestiges of installable
builds, 2025-09-06) removed a bunch of code paths for embed-data
builds, since those builds can do without most config paths.

However they still want the sysconfig path.  That commit made
embedded builds use "/etc/fish" unconditionally.  Previously they
used "$workspace_root/etc".  This is important when running tests,
which should not read /etc/fish.

tests/checks/invocation.fish tests this implicitly: if /etc/fish does
not exist, then

	fish --profile-startup /dev/stdout

will not contain "builtin source".

Let's restore historical behavior.  This might be annoying for users
who "install" with "ln -s target/debug/fish ~/bin/", but that hasn't
ever been recommended, and the historical behavior was in effect
until 4.1.0.

Fixes #11900
2025-10-07 15:24:01 +02:00
Johannes Altmanninger
bdca70bfb0 build.rs: extract function for overridable paths
Also get rid of cursed get_path().
2025-10-07 15:24:01 +02:00
Johannes Altmanninger
5e28f068ec build.rs: dedicated error for bad encoding in environment variables
We should probably not silently treat invalid Unicode the same as
"the variable is unset", even though it probably makes no difference
in practice.
2025-10-07 15:24:01 +02:00
Johannes Altmanninger
a0b22077a5 Extract constant for resolved build directory
Also use a different name than for the CMake variable, to reduce
confusion.
2025-10-07 11:59:45 +02:00
Daniel Rainer
1d36b04ea6 check.sh: export gettext extraction file variable
Some versions of `/bin/sh`, e.g. the one on FreeBSD, do not propagate
variables set on a command through shell functions. This results in
`FISH_GETTEXT_EXTRACTION_FILE` being set in the `cargo` function, but
not for the actual `cargo` process spawned from the function, which
breaks our localization scripts and tests. Exporting the variable
prevents that.

Fixes #11896

Closes #11899
2025-10-07 10:49:39 +02:00
Daniel Rainer
6829c9d678 printf-c: restore length modifiers
These were accidentally removed when semi-automatically removing length
modifiers from Rust code and shell scripts.

In C, the length modifiers are required.

Closes #11898
2025-10-07 07:52:26 +02:00
Ilya Grigoriev
e1f6ab8916 checks: make tmux-multiline-prompt less affected by less config
Fixes #11881 for me. Thanks, @krobelus, for the help with debugging
this!

The `-+X` is unrelated to the bug, strictly speaking, but makes sure the
test tests what it is intended to test.

I initially thought of also adding `LESS=` and something like
`--lesskey-content=""` to the command, but I decided against it since
`less` can also maybe be configured with `LESSOPEN` (?) and I don't know
how long the `--lesskey-content` option existed.

Closes #11891
2025-10-06 15:08:25 +02:00
Daniel Rainer
778baaecb5 sprintf: remove signed int size info
The size was used to keep track of the number of bits of the input type
the `Arg::SInt` variant was created from. This was only relevant for
arguments defined in Rust, since the `printf` command takes all
arguments as strings.

The only thing the size was used for is for printing negative numbers
with the `x` and `X` format specifiers. In these cases, the `i64` stored
in the `SInt` variant would be cast to a `u64`, but only the number of
bits present in the original argument would be kept, so `-1i8` would be
formatted as `ff` instead of `ffffffffffffffff`.

There are no users of this feature, so let's simplify the code by
removing it. While we're at it, also remove the unused `bool` returned
by `as_wrapping_sint`.

Closes #11889
2025-10-06 15:08:25 +02:00
Daniel Rainer
a189f79590 input: remove dead code
See https://github.com/fish-shell/fish-shell/pull/11874#discussion_r2404478880
> There is a comment saying `// Keep this function for debug purposes`
but I'm sure that's obsolete, since ReadlineCmd implements `Debug` now.

Closes #11887
2025-10-06 15:08:25 +02:00
Ada Magicat
6395644e8c doc: correct example of fish_should_add_to_history
Closes #11886
2025-10-06 15:08:25 +02:00
Johannes Altmanninger
a958f23f63 Fix regression on paste in non-interactive read
As reported in
https://github.com/fish-shell/fish-shell/issues/11836#issuecomment-3369973613,
running "fish -c read" and pasting something would result this error
from __fish_paste:

	commandline: Can not set commandline in non-interactive mode

Bisects to 32c36aa5f8 (builtins commandline/complete: allow handling
commandline before reader initialization, 2025-06-13).  That commit
allowed "commandline" to work only in interactive sessions, i.e. if
stdin is a TTY or if overridden with -i.

But this is not the only case where fish might read from the TTY.
The notable other case is builtin read, which also works in
noninteractive shells.
Let's allow "commandline" at least after we have initialized the TTY
for the first reader, which restores the relevant historical behavior
(which is weird, e.g. « fish -c 'read; commandline foo' »).
2025-10-06 15:08:25 +02:00
Johannes Altmanninger
ec8756d7a3 tests/checks/read: add test for non-interactive use of commandline 2025-10-06 15:04:19 +02:00
Xiretza
b7fabb11ac Make command run by __fish_echo output to TTY for color detection
Without this, e.g. Alt-L shows the directory entries one per line and
without colors.

Closes #11888
2025-10-06 13:39:30 +02:00
Johannes Altmanninger
74ba4e9a98 docker_builds: run only on fish-shell/fish-shell repo
Else this runs when people push to their master's forks, see
https://github.com/fish-shell/fish-shell/pull/11884#discussion_r2405618358
2025-10-06 13:29:08 +02:00
Johannes Altmanninger
b1e8fdfaa2 Revert "CI: use build_tools/check.sh in Cirrus CI"
I think we do want to stop using CMake on Cirrus but this should
first be tested in combination with all the other changes that made
it to master concurrently (test by pushing a temporary branch to the
fish-shell repo), to avoid confusion as to what exactly broke.

This reverts commit d167ab9376.

See #11884
2025-10-06 13:28:49 +02:00
Johannes Altmanninger
9eb439c01d Revert "Allow black to be missing in style.fish"
The root cause was that FISH_CHECK_LINT was not set. By
default, check.sh should fail if any tool is not installed, see
59b43986e9 (build_tools/style.fish: fail if formatters are not
available, 2025-07-24); like it does for rustfmt and clippy.

This reverts commit fbfd29d6d2.

See #11884
2025-10-06 13:27:47 +02:00
Peter Ammon
fbfd29d6d2 Allow black to be missing in style.fish
Stop failing BSD builds because black is missing.
2025-10-05 20:49:49 -07:00
Peter Ammon
f158a3ae3e Revert "Attempt to fix Cirrus builds harder"
This reverts commit d683769e1f.
2025-10-05 20:38:22 -07:00
Peter Ammon
7d59b4f4e2 Add an unnecessary_cast suppression
Continue to help fix BSD builds.
2025-10-05 20:12:55 -07:00
Peter Ammon
e99eca47c3 Add .claude to gitignore 2025-10-05 19:58:28 -07:00
Peter Ammon
d683769e1f Attempt to fix Cirrus builds harder
Install black
2025-10-05 19:58:02 -07:00
Peter Ammon
9ea328e43a Fix the BSD builds
These relied on constants that don't actually exist.
2025-10-05 19:30:33 -07:00
David Adam
f6d93f2fdb GitHub Actions: add workflow to build Docker images for CI 2025-10-06 09:50:56 +08:00
David Adam
b644fdbb04 docker: do not install recommended packages on Ubuntu
This should speed things up a bit, but various additional packages need
to be installed.
2025-10-06 09:50:56 +08:00
David Adam
7647d68b68 docker: fix Rust package name for jammy 2025-10-06 09:50:56 +08:00
David Adam
d167ab9376 CI: use build_tools/check.sh in Cirrus CI
08b03a733a removed CMake from the Docker images used for the
Cirrus builds.

It might be better to use fish_run_tests.sh in the Docker image, but
that requires some context which I'm not sure is set up properly in
Cirrus.
2025-10-06 09:50:55 +08:00
David Adam
e68bd2f980 build_tools/check.sh: add support for FISH_TEST_MAX_CONCURRENCY 2025-10-06 09:50:55 +08:00
Jesse Harwin
b5c17d4743 completions/bind: bug fixes, cleanup, and complete multiple functions
Revamped and renamed the __fish_bind_test2 function. Now has a
more explicit name, `__fish_bind_has_keys`  and allows for multiple
functions after the key-combo, doesn't offer function names after an
argument with a parameter (e.g. -M MODE).

Logic on the function is now more concise.

Closes #11864
2025-10-05 15:16:41 +02:00
Jesse Harwin
66ca7ac6d0 completions/bind: removed the unused __fish_bind_test1 function 2025-10-05 15:16:41 +02:00
Johannes Altmanninger
e97a616ffa completions/bind: don't suggest key names if --function-names is given
This combination makes no sense and should be an error.  (Also the
short options and --key-names were missing, so this was quite
inconsistent.)

See #11864
2025-10-05 15:16:41 +02:00
Ilya Grigoriev
061517cd14 completion/set: fix bug preventing showing history or fish_killring
Previously, `set -S fish_kill<TAB>` did not list `fish_killring`. This
was because `$1` wasn't sufficiently escaped, and so instead of
referring to a regex capture group, it was always empty.

Closes #11880
2025-10-05 15:16:41 +02:00
Johannes Altmanninger
6accc475c9 Don't use kitty keyboard protocol support to decide timeout
As reported in
https://github.com/fish-shell/fish-shell/discussions/11868, some
terminals advertise support for the kitty keyboard protocol despite
it not necessarily being enabled.

We use this flag in 30ff3710a0 (Increase timeout when reading
escape sequences inside paste/kitty kbd, 2025-07-24), to support
the AutoHotKey scenario on terminals that support the kitty keyboard
protocols.

Let's move towards the more comprehensive fix mentioned in abd23d2a1b
(Increase escape sequence timeout while waiting for query response,
2025-09-30), i.e. only apply a low timeout when necessary to actually
distinguish legacy escape.

Let's pick 30ms for now (which has been used successfully for similar
things historically, see 30ff3710a0); a higher timeout let alone
a warning on incomplete sequence seems risky for a patch relase,
and it's also not 100% clear if this is actually a degraded state
because in theory the user might legitimately type "escape [ 1"
(while kitty keyboard protocol is turned off, e.g. before the shell
regains control).

This obsoletes and hence reverts commit 623c14aed0 (Kitty keyboard
protocol is non-functional on old versions of Zellij, 2025-10-04).
2025-10-05 15:16:41 +02:00
Johannes Altmanninger
c2e2fd6432 fish_add_path: remove extra argument to printf 2025-10-05 15:16:41 +02:00
Daniel Rainer
83af5c91bd printf: remove all uses of length modifiers
Length modifiers are useless. This simplifies the code a bit, results in
more consistency, and allows removing a few PO messages which only
differed in the use of length modifiers.

Closes #11878
2025-10-05 15:16:41 +02:00
Peter Ammon
e9f5982147 Fix a clipply 2025-10-04 19:25:10 -07:00
Peter Ammon
50819666b1 Remove our own C bits
fish-shell itself no longer depends on a C compiler; however we still
use cc for feature detection. Removing that will have to wait for another day.
2025-10-04 18:56:11 -07:00
Peter Ammon
6ad13e35c0 Bravely define PATH_BSHELL
PATH_BSHELL is always "/bin/sh" except on Android where it's "/system/bin/sh".

This isn't exposed by Rust, so just define it ourselves.
2025-10-04 17:27:16 -07:00
Peter Ammon
39e2f1138b Bravely stop setting stdout to unbuffered
Issue #3748 made stdout (the C FILE*, NOT the file descriptor) unbuffered,
due to concerns about mixing output to the stdout FILE* with output output.

We no longer write to the C FILE* and Rust libc doesn't expose stdout, which may
be a macro. This code no longer looks useful. Bravely remove it.
2025-10-04 17:27:15 -07:00
Peter Ammon
cd37c71e29 Adopt Rust libc RLIMIT_* fields
Moving more stuff out of C.
2025-10-04 14:09:47 -07:00
Peter Ammon
c1f3d93b3b Adopt Rust libc::MNT_LOCAL
Note the ST_LOCAL usage on NetBSD is also covered by this.
2025-10-04 14:09:46 -07:00
Peter Ammon
0aa05032c4 Adopt rust _PC_CASE_SENSITIVE
fish no longer needs to expose this - the libc crate does the job.
2025-10-04 14:01:13 -07:00
Peter Ammon
174130fe2f Adopt Rust libc _CS_PATH
This is now supported directly by the libc crate - no need for fish
to expose this via C.
2025-10-04 14:01:11 -07:00
Peter Ammon
d06f7f01d2 Remove MB_CUR_MAX from our libc ffi
We no longer need this.
2025-10-04 13:39:22 -07:00
Peter Ammon
a04ddd9b17 Adopt get_is_multibyte_locale in the pager 2025-10-04 13:39:22 -07:00
Peter Ammon
12929fed74 Adopt get_is_multibyte_locale in decode_input_byte
Move away from MB_CUR_MAX
2025-10-04 13:39:22 -07:00
Peter Ammon
87bf580f68 Adopt get_is_multibyte_locale in wcs2string_callback
Move away from MB_CUR_MAX
2025-10-04 13:39:22 -07:00
Peter Ammon
66bab5e767 Early work aiming to remove MB_CUR_MAX from fish libc FFI
Start detecting multibyte locales in Rust.
2025-10-04 13:39:21 -07:00
Peter Ammon
4b12fb2887 Migrate invalidate_numeric_locale into fish_setlocale
Centralizes where locale information is recomputed.
2025-10-04 13:39:21 -07:00
Johannes Altmanninger
623c14aed0 Kitty keyboard protocol is non-functional on old versions of Zellij
try_readb() uses a high timeout when the kitty keyboard protocol is
enabled, because in that case it should basically never be necessary
to interpret \e as escape key, see 30ff3710a0 (Increase timeout when
reading escape sequences inside paste/kitty kbd, 2025-07-24).

Zellij before commit 0075548a (fix(terminal): support kitty keyboard
protocol setting with "=" (#3942), 2025-01-17) fails to enable kitty
keyboard protocol, so it sends the raw escape bytes, causing us to
wait 300ms.

Closes #11868
2025-10-04 07:17:34 +02:00
Johannes Altmanninger
7d83dc4758 Refresh TTY timestamps after firing focus events
Using a multi-line prompt with focus events on:

	tmux new-session fish -C '
		tmux set -g focus-events on
		set -g fish_key_bindings fish_vi_key_bindings
		function fish_prompt
		    echo (prompt_pwd)
		    echo -n "> "
		end
		tmux split
	'

switching to the fish pane and typing any key sometimes leads to our
two-line-prompt being redawn one line below it's actual place.

Reportedly, it bisects to d27f5a5 which changed when we print things.
I did not verify root cause, but
1. symptoms are very similar to other
   problems with TTY timestamps, see eaa837effa (Refresh TTY
   timestamps again in most cases, 2025-07-24).
2. this seems fixed if we refresh timestamps after
   running the focus events, which print some cursor shaping commands
   to stdout. So bravely do that.

Closes #11870
2025-10-03 22:35:31 +02:00
Johannes Altmanninger
493d0bca95 Update changelog for patch release 2025-10-03 22:01:39 +02:00
qianlongzt
983501ff8c zh_CN: fix vi case
Part of #11854
2025-10-03 20:51:57 +02:00
The0x539
20da9a2b51 ast: use macro_rules_attribute for the Acceptor trait
Closes #11867
2025-10-03 20:45:01 +02:00
The0x539
7aec6c55f9 ast: use macro_rules_attribute for Leaf trait
Part of #11867
2025-10-03 20:45:01 +02:00
The0x539
532f30e031 ast: use macro_rules_attribute for Node trait
Part of #11867
2025-10-03 20:45:01 +02:00
Daniel Rainer
1d7ab57e3a xgettext: remove --strict flag from msguniq
As with `msgmerge`, this introduces unwanted empty comment lines above
`#, c-format`
lines. We don't need this strict formatting, so we get rid of the flag
and the associated empty comment lines.

Closes #11863
2025-10-03 20:22:59 +02:00
Étienne Deparis
8adc598e90 web_config: Support long options separated with = from their value
Closes #11861
2025-10-03 20:18:38 +02:00
Étienne Deparis
c884c08257 web_config: Use None as default for underline style
Underline is no more a boolean and should be one of the accepted style,
or None. By keeping False as default value, web_config was generating
wrong --underline=False settings

Part of #11861
2025-10-03 20:18:38 +02:00
Daniel Rainer
66dc734c11 printf: remove useless length modifiers
Closes #11858
2025-10-03 20:14:20 +02:00
Daniel Rainer
77fee9acb9 printf: rename direc -> directive
The abbreviation is ambiguous, which makes the code unnecessarily hard
to read. (possible misleading expansions: direct, direction, director,
...)

Part of #11858
2025-10-03 20:14:20 +02:00
Daniel Rainer
6b66c2bc1d printf: use options for idiomatic code
The `have_foo: bool` + `foo: i64` combination is more idiomatically
represented as `foo: Option<i64>`. This change is applied for
`field_width` and `precision`.

In addition, the sketchy explicit cast from `i64` to `c_int`, and the
subsequent implicit cast from `c_int` to `i32` are avoided by using
`i64` consistently.

Part of #11858
2025-10-03 20:14:20 +02:00
Daniel Rainer
81b9f50dc2 printf: reformat doc comments
Part of #11858
2025-10-03 20:14:20 +02:00
Johannes Altmanninger
fcd246064b Stop requesting modifyOtherKeys on old Midnight Commander again
This upstream issue was fixed in 0ea77d2ec (Ticket #4597: fix CSI
parser, 2024-10-09); for old mc's we had worked around this but the
workaround was accidentally removed. Add it back for all the versions
that don't have that fix.

Fixes f0e007c439 (Relocate tty metadata and protocols and clean
it up, 2025-06-19) Turns out this was why the "Capability" enum was
added in 2d234bb676 (Only request keyboard protocols once we know
if kitty kbd is supported, 2025-01-26).

Fixes #11869
2025-10-03 20:14:20 +02:00
Johannes Altmanninger
86a0a348ee Harmonize temporary Midnight Commander workarounds a bit 2025-10-03 18:18:05 +02:00
Johannes Altmanninger
ed36e852d2 release.sh: add next patch milestone
This is still the common case.
2025-10-03 18:08:28 +02:00
Johannes Altmanninger
da5d93c1e2 release.sh: close milestone when done
Don't fail early if this doesn't exist, because it's not needed for
testing this on fish-shell forks that live on GitHub.
2025-10-03 18:08:28 +02:00
Johannes Altmanninger
7b59ae0d82 Unbreak hack to strip " (deleted)" suffix from executable path
Commit 49b88868df (Fix stripping of " (deleted)" from non-UTF8 paths
to fish, 2024-10-12) was wrong because Path::ends_with() considers
entire path components. Fix that.

Refs:
- https://matrix.to/#/!YLTeaulxSDauOOxBoR:matrix.org/$k2IQazfmztFUXrairmIQvx_seS1ZJ7HlFWhmNy479Dg
- https://matrix.to/#/!YLTeaulxSDauOOxBoR:matrix.org/$4pugfHejL9J9L89zuFU6Bfg41UMjA0y79orc3EaBego
2025-10-03 18:08:28 +02:00
Fabian Boehm
97acc12d62 Fix scp completions
Introduced when __fish_mktemp_relative was.

Fixes #11860
2025-10-02 18:20:41 +02:00
Johannes Altmanninger
db6a7d26cd update_translations.sh: add header to new files too
Fixes #11855
2025-10-01 18:30:38 +02:00
Johannes Altmanninger
6be03d7cc4 update_translations.sh: fix test invocation when passed a file in non-extant directory
Need to make sure test arguments are not empty lists.
2025-10-01 18:30:38 +02:00
Daniel Rainer
617a6edb13 Extract messages without building default features
Default features are not needed for message extraction, so there is no
need to spend any resources on them.

If a PO files contains a syntax error, extraction would fail if the
`localize-messages` feature is active. This is undesirable, because it
results in an unnecessary failure with worse error messages than if the
`msgmerge` invocation of the `update_translations.fish` script fails.

Closes #11849
2025-10-01 08:05:37 +02:00
王宇逸
31c85723e8 zh_CN: don't translate set command
Closes #11852
2025-10-01 08:02:38 +02:00
王宇逸
d22c905d9f zh_CN: fix typo
Part of #11852
2025-10-01 08:02:38 +02:00
Daniel Rainer
216dc2d473 Remove redundant variable declaration
Closes #11851
2025-10-01 08:00:21 +02:00
Daniel Rainer
918e7abe6b Hide output of msgfmt -h
This command is only used to determine availability of `msgfmt`. Without
these changes, the entire help output shows up if the code panics later
on, which adds useless bloat to the output, making it harder to analyze
what went wrong.

Closes #11848
2025-10-01 07:59:46 +02:00
Daniel Rainer
c145ee6df3 Check exit status of msgfmt
Prior to this, when `msgfmt` failed, this would be detected indirectly
by the parser, which would then panic due to it input being empty.

Explicit checking allows us to properly display `msgfmt`'s error
message.

Closes #11847
2025-10-01 07:59:21 +02:00
Johannes Altmanninger
62543b36a4 release.sh: sunset release announcement email
I'm not sure if our peer projects do this or if it's useful to have
on top of github releases (especially as most releases are patch
releases mainly).

We could certainly use "sphinx-build -b text" in
build_tools/release-notes.sh to extract a nice plaintext changelog
and send that, but that doesn't support links.  Not sure about HTML
email either.
2025-10-01 07:26:50 +02:00
Daniel Rainer
751aad5302 Refactor PO section marking
Use msgids to mark sections. In the PO format, comments are associated
with specific messages, which does not match the semantics for section
markers.
Furthermore, comments are not preserved by `msgmerge`, which required
quite convoluted handling to copy them over from the template.
By using msgids to mark sections, this problem is avoided.

This convoluted handling was also used for header comments. Header
comments are now handled in a simpler way. There is a fixed prefix,
identifying these comments, as well as a list variable containing the
lines which should be put into the header. When a PO file is generated,
all existing lines starting with the prefix are deleted, the and the
current version of the lines is prepended to the file.

Closes #11845
2025-09-30 19:45:31 +02:00
Daniel Rainer
efabab492a Remove useless comments
Most of them have been added automatically for no good reason.

Also remove outdated comments referring to source locations in pt_BR.po.

Closes #11844
2025-09-30 19:44:36 +02:00
Daniel Rainer
c7cdbe60cd Put general comments on top of empty msgid
These should not be comments on an actual message, since they apply
throughout the entire file, so the sensible location is as comments on
the empty msgid.

Closes #11843
2025-09-30 19:44:15 +02:00
Johannes Altmanninger
412149a5de Changelog update for 4.1.1 2025-09-30 19:22:41 +02:00
Johannes Altmanninger
abd23d2a1b Increase escape sequence timeout while waiting for query response
Running "fish -d reader" inside SSH inside Windows terminal sometimes
results in hangs on startup (or whenever we run "scrollback-push"),
because not all of the Primary DA response is available for reading
at once:

	reader: Incomplete escape sequence: \e\[?61\;4\;6\;7\;14\;21\;22\;23\;24\;28\;32

Work around this by increasing the read timeout while we're waiting
for query responses.

We should try to find a better (more comprehensive?) fix in future,
but for the patch release, this small change will do.

Fixes #11841
2025-09-30 19:00:40 +02:00
Johannes Altmanninger
b774c54a6f Changelog for translation fixes from #11833 2025-09-30 19:00:40 +02:00
王宇逸
e4b797405b zh_CN: fix tier 1 translations
Closes #11833
2025-09-30 18:41:44 +02:00
Johannes Altmanninger
81a89a5dec release-notes.sh: fix sphinx warning for patch release notes
Integration_4.1.1 fails to generate release notes with

	CHANGELOG.rst:9: WARNING: Bullet list ends without a blank
	line; unexpected unindent. [docutils].
2025-09-30 18:00:09 +02:00
Johannes Altmanninger
0da12a6b55 Primary Device Attribute is a proper noun
We don't care about any specific attributes but we do very much care
about the specific query and response format associated with VT100's
primary device attribute query. Use a proper noun to emphasize that
we want that one and no other.

Ref: https://github.com/fish-shell/fish-shell/pull/11833#discussion_r2385659040
2025-09-30 12:06:08 +02:00
Johannes Altmanninger
86ec8994e6 build.rs: fix MSRV (1.70) clippy 2025-09-30 12:06:08 +02:00
Johannes Altmanninger
caf426ddb2 po/de.po: copy-edit German translations
Go through all existing translations except for tier3.
2025-09-30 11:50:43 +02:00
Johannes Altmanninger
508ae410a6 builtin commandline: fix completion description 2025-09-30 11:47:26 +02:00
Johannes Altmanninger
993b977c9b builtin function: remove dead code 2025-09-30 11:47:26 +02:00
Johannes Altmanninger
a7f0138fc7 builtin function: fix a misleading error message
Issue introduced in 7914c92824 (replaced the functions '--rename'
option with '--copy'., 2010-09-09).
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
ab3c932903 builtin set: fix regression in error message description 2025-09-30 11:47:26 +02:00
Johannes Altmanninger
ae0fdadcff Remove translations for some error messages that basically never happen
Executable path is empty only in contrived circumstances.

The regex error happens only when the user explicitly turns off a
feature flag.

The orphaned process error seems very unlikely, not worth translating.
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
e3974989d8 Fix short description for builtin wait 2025-09-30 11:47:26 +02:00
Johannes Altmanninger
080b1e0e4f Translation update implied by parent commit
Part of #11833
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
a5db91dd85 po: add section markers to indicate translation priority
Part of #11833
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
b62f54631b Translation update implied by parent commit
Part of #11833
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
d835c5252a Prepare to not localize private function descriptions
The overwhelming majority of localizable messages comes from
completions:

	$ ls share/completions/ | wc -l
	$ 1048

OTOH functions also contribute a small amount, mostly via their
descriptions (so usually just one per file).

	$ ls share/functions/ | wc -l
	$ 237

Most of these are private and almost never shown to the user, so it's
not worth bothering translators with them. So:

- Skip private (see the parent commit) and deprecated functions.
- Skip wrapper functions like grep (where the translation seems to
  be provided by apropos), and even the English description is not
  helpful.
  - Assume that most real systems have "seq", "realpath" etc.,
    so it's no use providing our own translations for our fallbacks.
- Mark fish's own functions as tier1, and some barely-used functiosn
  and completions as tier3, so we can order them that way in
  po/*.po. Most translators should only look at tier1 and tier2.
  In future we could disable localization for tier3.

See the explanation at the bottom of
tests/checks/message-localization-tier-is-declared.fish

Part of #11833
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
a53db72564 Mark private functions that don't need localization
See the next commit.

Part of #11833
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
61b0368dac functions/realpath: remove weird wrapping
Wrapping the same thing is redundant and wrapping grealpath is kinda
pointless since we only provide completions for realpath.
2025-09-30 11:47:26 +02:00
Integral
568b4a22f9 completions/help: correct the spelling of "redirection"
Closes #11839
2025-09-30 11:47:26 +02:00
Jiangqiu Shen
8abba8a089 Re-add translations for share/completions/cjpm.fish
As removed in the parent commit.
Cherry-picked from e4c55131c7 (Update translation, 2025-07-04)
2025-09-30 11:47:26 +02:00
王宇逸
b3b789cd68 zh_CN: bad translations are worse than none
Part of #11833
2025-09-30 11:47:26 +02:00
Johannes Altmanninger
425a166111 functions/seq: use early return 2025-09-30 10:37:11 +02:00
Johannes Altmanninger
1dcc290e29 tests/checks/check-all-fish-files.fish: follow naming convention 2025-09-30 10:37:11 +02:00
Johannes Altmanninger
863204dbfa build.rs: remove dead code 2025-09-30 10:37:11 +02:00
Sebastian Fleer
4b21e7c9c7 webconfig: Replace str.stripprefix with str.removeprefix
str.stripprefix doesn't exist in Python:
https://docs.python.org/3/library/stdtypes.html#str.removeprefix

Closes #11840
2025-09-30 10:37:06 +02:00
Johannes Altmanninger
df5230ff4a Reliably disable modifyOtherKeys on WezTerm
WezTerm allows applications to enable modifyOtherKeys by default.
Its implementation has issues on non-English or non-QWERTY layouts,
see https://github.com/wezterm/wezterm/issues/6087 and #11204.

fish 4.0.1 disabled modifyOtherKeys on WezTerm specifically
(7ee6d91ba0 (Work around keyboard-layout related bugs in WezTerm's
modifyOtherKeys, 2025-03-03)), fish 4.1.0 didn't, because at that
time, WezTerm would advertise support for the kitty keyboard protocol
(even if applications are not allowed to enable it) which would make
fish skip requesting the legacy modifyOtherKeys.

WezTerm no longer advertises that if config.enable_kitty_keyboard
is false.  Let's work around it in another way.
2025-09-30 10:33:03 +02:00
Johannes Altmanninger
7cd0943056 Tighten some screws for TTY-specific workarounds 2025-09-30 10:25:23 +02:00
Johannes Altmanninger
6f0532460a fish_config: fix for non-embedded builds
I only tested with embedded-builds; CMake tests were failing because
they use different code paths here.

fish_config could use some love.  Start by extracting common
functionality between "{theme,prompt} show", fixing the behavior.

Fixes 29a35a7951 (fish_config: fix "prompt/theme show" in embed-data
builds, 2025-09-28).
2025-09-28 12:29:18 +02:00
Johannes Altmanninger
29a35a7951 fish_config: fix "prompt/theme show" in embed-data builds
Fixes #11832
2025-09-28 10:59:53 +02:00
Johannes Altmanninger
dd0d45f88f fish_config prompt: remove dead code
Commit 2b74affaf0 (Add prompt selector, 2021-04-22)
intentionally added an unused code path that checks for
$__fish_data_dir/sample_prompts, see

https://github.com/fish-shell/fish-shell/pull/7958#discussion_r621320945

> (note: This was added in preparation of moving the sample_prompts directory out of web_config -
> because it no longer is web-exclusive)

Remove it.
2025-09-28 10:11:01 +02:00
Johannes Altmanninger
0ff0de7efe release workflow: install msgfmt for staticbuilds
This makes us actually embed localized messages.

Part of #11828
2025-09-28 09:56:57 +02:00
Johannes Altmanninger
092ef99551 macos CI: explicitly install gettext
We need msgfmt for embedding translations.

Part of #11828
2025-09-28 09:54:30 +02:00
Johannes Altmanninger
97ae05b69d build_tools/release.sh: actually add new docs
Not quite sure for which step this is actually needed.  While at it,
fix the errexit issue that caused this blunder.
2025-09-27 22:56:49 +02:00
Johannes Altmanninger
3d8eca178e start new cycle
Created by ./build_tools/release.sh 4.1.0
2025-09-27 22:41:54 +02:00
193 changed files with 7958 additions and 7682 deletions

View File

@@ -1,7 +1,6 @@
image: alpine/edge
packages:
- cargo
- clang17-libclang
- cmake
- ninja
- pcre2-dev
@@ -24,4 +23,4 @@ tasks:
ninja
- test: |
cd fish-shell/build
ninja test
ninja fish_run_tests

View File

@@ -20,4 +20,4 @@ tasks:
ninja
- test: |
cd fish/build
ninja test
ninja fish_run_tests

View File

@@ -5,7 +5,6 @@ packages:
- gettext
- gmake
- llvm
- terminfo-db
- ninja
- pcre2
- py311-pexpect
@@ -27,4 +26,4 @@ tasks:
ninja
- test: |
cd fish-shell/build
ninja test
ninja fish_run_tests

29
.builds/openbsd.yml Normal file
View File

@@ -0,0 +1,29 @@
image: openbsd/latest
packages:
- cmake
- gcc
- gettext
- gmake
- llvm
- ninja
- pcre2
- py311-pexpect
- python
- rust
- tmux
sources:
- https://github.com/fish-shell/fish-shell
tasks:
- build: |
cd fish-shell
mkdir build
cd build
cmake -GNinja .. \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_DATADIR=share \
-DCMAKE_INSTALL_DOCDIR=share/doc/fish \
-DCMAKE_INSTALL_SYSCONFDIR=/etc
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests

View File

@@ -1,3 +1,5 @@
skip: $CIRRUS_REPO_OWNER == 'fish-shell' && $CIRRUS_BRANCH == 'master'
env:
CIRRUS_CLONE_DEPTH: 100
CI: 1
@@ -6,20 +8,12 @@ linux_task:
matrix:
- name: alpine
container: &step
image: ghcr.io/krobelus/fish-ci/alpine:latest
image: ghcr.io/fish-shell/fish-ci/alpine:latest
memory: 4GB
- name: jammy
container:
<<: *step
image: ghcr.io/krobelus/fish-ci/jammy:latest
# - name: jammy-asan
# container:
# <<: *step
# image: ghcr.io/krobelus/fish-ci/jammy-asan:latest
# - name: focal-32bit
# container:
# <<: *step
# image: ghcr.io/krobelus/fish-ci/focal-32bit:latest
image: ghcr.io/fish-shell/fish-ci/jammy:latest
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
@@ -36,9 +30,6 @@ linux_arm_task:
- name: focal-arm64
arm_container:
image: ghcr.io/fish-shell/fish-ci/focal-arm64
- name: jammy-armv7-32bit
arm_container:
image: ghcr.io/fish-shell/fish-ci/jammy-armv7-32bit
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
@@ -47,10 +38,8 @@ linux_arm_task:
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- file ./fish
- ninja fish_run_tests
# CI task disabled during RIIR transition
only_if: false && $CIRRUS_REPO_OWNER == 'fish-shell'
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
freebsd_task:
matrix:

View File

@@ -0,0 +1,43 @@
name: Install dependencies for system tests
inputs:
include_sphinx:
description: Whether to install Sphinx
required: true
default: false
include_pcre:
description: Whether to install the PCRE library
required: false
default: true
permissions:
contents: read
runs:
using: "composite"
steps:
- shell: bash
env:
include_sphinx: ${{ inputs.include_sphinx }}
include_pcre: ${{ inputs.include_pcre }}
run: |
set -x
: "optional dependencies"
sudo apt install \
gettext \
$(if $include_pcre; then echo libpcre2-dev; fi) \
$(if $include_sphinx; then echo python3-sphinx; fi) \
;
: "system test dependencies"
sudo apt install \
diffutils $(: "for diff") \
git \
gettext \
less \
$(if ${{ inputs.include_pcre }}; then echo libpcre2-dev; fi) \
python3-pexpect \
tmux \
wget \
;
- uses: ./.github/actions/install-sphinx-markdown-builder
if: ${{ inputs.include_sphinx == 'true' }}

View File

@@ -0,0 +1,41 @@
name: Rust Toolchain
inputs:
toolchain_channel:
description: Either "stable" or "msrv"
required: true
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- name: Set toolchain
env:
toolchain_channel: ${{ inputs.toolchain_channel }}
shell: bash
run: |
set -x
toolchain=$(
case "$toolchain_channel" in
(stable) echo 1.90 ;;
(msrv) echo 1.70 ;;
(*)
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
exit 1
;;
esac
)
printf 'TOOLCHAIN=%s\n' "$toolchain" >>"$GITHUB_ENV"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.TOOLCHAIN }}
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -14,7 +14,8 @@ permissions:
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@1.70
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: "msrv"
targets: ${{ inputs.targets }}
components: ${{ inputs.components}}
components: ${{ inputs.components }}

View File

@@ -14,7 +14,8 @@ permissions:
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@1.90
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: "stable"
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -0,0 +1,126 @@
name: Build Docker test images
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: docker-builds
env:
REGISTRY: ghcr.io
NAMESPACE: fish-ci
ONLY_FOR_REPO_OWNER: fish-shell
jobs:
check-docker-changes:
if: github.repository_owner == env.ONLY_FOR_REPO_OWNER
runs-on: ubuntu-latest
outputs:
docker-changed: ${{ steps.changes.outputs.docker }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
docker:
- 'docker/**'
docker-build:
needs: check-docker-changes
if: github.repository_owner == env.ONLY_FOR_REPO_OWNER && needs.check-docker-changes.outputs.docker-changed == 'true'
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: alpine
- os: ubuntu-latest
target: centos9
- os: ubuntu-latest
target: fedora
- os: ubuntu-latest
target: focal-32bit
- os: ubuntu-24.04-arm
target: focal-arm64
- os: ubuntu-latest
target: focal
- os: ubuntu-24.04-arm
target: jammy-armv7-32bit
- os: ubuntu-latest
target: jammy-asan
- os: ubuntu-latest
target: jammy-tsan
- os: ubuntu-latest
target: jammy
- os: ubuntu-latest
target: noble
- os: ubuntu-latest
target: opensuse-tumbleweed
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Login to Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
flavor: |
latest=true
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: docker/context
push: true
file: docker/${{ matrix.target }}.Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
trigger-cirrus:
needs: [check-docker-changes, docker-build]
if: always() && github.repository_owner == env.ONLY_FOR_REPO_OWNER
runs-on: ubuntu-latest
steps:
- name: Trigger Cirrus CI
env:
CIRRUS_TOKEN: ${{ secrets.CIRRUS_TOKEN }}
run: |
set -x
# N.B. push-triggered workflows are usually from master.
branch=${{ github.ref_name }}
repository_id=${{ github.repository_id }}
curl -X POST \
-H "Authorization: Bearer $CIRRUS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation {
createBuild(input: {
repositoryId: \"$repository_id\",
branch: \"$branch\"
})
{ build { id } }
}"
}' \
https://api.cirrus-ci.com/graphql

View File

@@ -1,4 +1,4 @@
name: Rust checks
name: Lint
on: [push, pull_request]
@@ -6,25 +6,36 @@ permissions:
contents: read
jobs:
rustfmt:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@stable
with:
components: rustfmt
- name: cargo fmt
run: cargo fmt --check
- name: install dependencies
run: pip install ruff
- name: build fish
run: cargo build
- name: check format
run: PATH="target/debug:$PATH" build_tools/style.fish --all --check
clippy-stable:
clippy:
runs-on: ubuntu-latest
strategy:
matrix:
features: ["", "--no-default-features"]
include:
- rust_version: "stable"
features: ""
- rust_version: "stable"
features: "--no-default-features"
- rust_version: "msrv"
features: ""
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@stable
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: ${{ matrix.rust_version }}
components: clippy
- name: Install deps
run: |
@@ -32,19 +43,6 @@ jobs:
- name: cargo clippy
run: cargo clippy --workspace --all-targets ${{ matrix.features }} -- --deny=warnings
clippy-msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@oldest-supported
with:
components: clippy
- name: Install deps
run: |
sudo apt install gettext
- name: cargo clippy
run: cargo clippy --workspace --all-targets -- --deny=warnings
rustdoc:
runs-on: ubuntu-latest
steps:

View File

@@ -12,6 +12,7 @@ permissions:
jobs:
lock:
if: github.repository_owner == 'fish-shell'
permissions:
issues: write # for dessant/lock-threads to lock issues
pull-requests: write # for dessant/lock-threads to lock PRs

View File

@@ -150,6 +150,8 @@ jobs:
uses: ./.github/actions/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Install dependencies
run: brew install gettext
- name: Build and codesign
run: |
die() { echo >&2 "$*"; exit 1; }

View File

@@ -1,4 +1,4 @@
name: make fish_run_tests
name: Test
on: [push, pull_request]
@@ -11,18 +11,17 @@ permissions:
jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_sphinx: true
- name: Generate a locale that uses a comma as decimal separator.
run: |
sudo apt install gettext libpcre2-dev python3-pexpect python3-sphinx tmux
# Generate a locale that uses a comma as decimal separator.
sudo locale-gen fr_FR.UTF-8
- uses: ./.github/actions/install-sphinx-markdown-builder
- name: cmake
run: |
mkdir build && cd build
@@ -43,18 +42,20 @@ jobs:
git --no-pager diff --exit-code || { echo 'There are uncommitted changes after regenerating the gettext PO files. Make sure to update them via `build_tools/update_translations.fish` after changing source files.'; exit 1; }
ubuntu-32bit-static-pcre2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
with:
targets: "i586-unknown-linux-gnu" # rust-toolchain wants this comma-separated
targets: "i586-unknown-linux-gnu"
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_pcre: false
include_sphinx: false
- name: Install g++-multilib
run: |
sudo apt update
sudo apt install gettext python3-pexpect g++-multilib tmux
sudo apt install g++-multilib
- name: cmake
env:
CFLAGS: "-m32"
@@ -69,7 +70,6 @@ jobs:
make -C build VERBOSE=1 fish_run_tests
ubuntu-asan:
runs-on: ubuntu-latest
env:
# Rust has two different memory sanitizers of interest; they can't be used at the same time:
@@ -79,7 +79,6 @@ jobs:
#
RUSTFLAGS: "-Zsanitizer=address"
# RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
steps:
- uses: actions/checkout@v4
# All -Z options require running nightly
@@ -89,8 +88,11 @@ jobs:
# this is comma-separated
components: rust-src
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_sphinx: false
- name: Install llvm
run: |
sudo apt install gettext libpcre2-dev python3-pexpect tmux
sudo apt install llvm # for llvm-symbolizer
- name: cmake
env:
@@ -119,38 +121,8 @@ jobs:
export LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$PWD/build_tools/lsan_suppressions.txt"
make -C build VERBOSE=1 fish_run_tests
# Our clang++ tsan builds are not recognizing safe rust patterns (such as the fact that Drop
# cannot be called while a thread is using the object in question). Rust has its own way of
# running TSAN, but for the duration of the port from C++ to Rust, we'll keep this disabled.
# ubuntu-threadsan:
#
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v4
# - uses: ./.github/actions/rust-toolchain@oldest-supported
# - name: Install deps
# run: |
# sudo apt install gettext libpcre2-dev python3-pexpect tmux
# - name: cmake
# env:
# FISH_CI_SAN: 1
# CC: clang
# run: |
# mkdir build && cd build
# cmake ..
# - name: make
# run: |
# make
# - name: make fish_run_tests
# run: |
# make -C build fish_run_tests
macos:
runs-on: macos-latest
env:
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
@@ -163,7 +135,7 @@ jobs:
# --break-system-packages because homebrew has now declared itself "externally managed".
# this is CI so we don't actually care.
sudo pip3 install --break-system-packages pexpect
brew install tmux
brew install gettext tmux
- name: cmake
run: |
mkdir build && cd build
@@ -174,3 +146,28 @@ jobs:
- name: make fish_run_tests
run: |
make -C build VERBOSE=1 fish_run_tests
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
update: true
msystem: MSYS
- name: Install deps
# Not using setup-msys2 `install` option to make it easier to copy/paste
run: |
pacman --noconfirm -S --needed git rust
- name: cargo build
run: |
cargo build
- name: smoketest
# We can't use `cargo test` yet, there are just too many failures
# so this is just a quick check to make sure that fish can swim
run: |
set -x
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]

3
.gitignore vendored
View File

@@ -105,3 +105,6 @@ target/
# JetBrains editors.
.idea/
# AI slop
.claude/

View File

@@ -1,5 +1,19 @@
fish 4.1.2 (released October 07, 2025)
======================================
fish ?.?.? (released ???)
=========================
fish 4.1.3 (released ???)
=========================
This release fixes the following regressions identified in 4.1.0:
- Crash on invalid :doc:`function <cmds/function>` command (:issue:`11912`).
as well as the following regressions identified in 4.0.0:
- Crash when passing negative PIDs to :doc:`wait <cmds/wait>` (:issue:`11929`).
fish 4.1.2 (released October 7, 2025)
=====================================
This release fixes the following regressions identified in 4.1.0:

View File

@@ -110,7 +110,7 @@ before committing your change. That will run our autoformatters:
- ``rustfmt`` for Rust
- ``fish_indent`` (shipped with fish) for fish script
- ``black`` for python
- ``ruff format`` for python
If youve already committed your changes thats okay since it will then
check the files in the most recent commit. This can be useful after
@@ -343,7 +343,7 @@ command-line and graphical user interface programs. For simple use, you can use
Open up the PO file, for example ``po/sv.po``, and you'll see something like::
msgid "%ls: No suitable job\n"
msgid "%s: No suitable job\n"
msgstr ""
The ``msgid`` here is the "name" of the string to translate, typically the English string to translate.
@@ -351,10 +351,10 @@ The second line (``msgstr``) is where your translation goes.
For example::
msgid "%ls: No suitable job\n"
msgstr "%ls: Inget passande jobb\n"
msgid "%s: No suitable job\n"
msgstr "%s: Inget passande jobb\n"
Any ``%s`` / ``%ls`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string should have the same placeholders in the same order.
Any ``%s`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string should have the same placeholders in the same order.
Also any escaped characters, like that ``\n`` newline at the end, should be kept so the translation has the same behavior.
@@ -381,7 +381,7 @@ macros:
::
streams.out.append(wgettext_fmt!("%ls: There are no jobs\n", argv[0]));
streams.out.append(wgettext_fmt!("%s: There are no jobs\n", argv[0]));
All messages in fish script must be enclosed in single or double quote
characters for our message extraction script to find them.

30
Cargo.lock generated
View File

@@ -42,9 +42,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "cfg_aliases"
@@ -105,10 +105,11 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fish"
version = "4.1.2"
version = "4.1.0-snapshot"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"errno",
"fish-build-helper",
"fish-build-man-pages",
@@ -118,6 +119,7 @@ dependencies = [
"fish-printf",
"libc",
"lru",
"macro_rules_attribute",
"nix",
"num-traits",
"once_cell",
@@ -254,6 +256,22 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "macro_rules_attribute"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
dependencies = [
"macro_rules_attribute-proc_macro",
"paste",
]
[[package]]
name = "macro_rules_attribute-proc_macro"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "memchr"
version = "2.7.4"
@@ -326,6 +344,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pcre2"
version = "0.2.9"

View File

@@ -11,6 +11,7 @@ repository = "https://github.com/fish-shell/fish-shell"
[workspace.dependencies]
bitflags = "2.5.0"
cc = "1.0.94"
cfg-if = "1.0.3"
errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" }
@@ -64,7 +65,7 @@ debug = true
[package]
name = "fish"
version = "4.1.2"
version = "4.1.0-snapshot"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
@@ -76,6 +77,7 @@ readme = "README.rst"
[dependencies]
bitflags.workspace = true
cfg-if.workspace = true
errno.workspace = true
fish-build-helper.workspace = true
fish-build-man-pages = { workspace = true, optional = true }
@@ -84,6 +86,7 @@ fish-gettext-maps = { workspace = true, optional = true }
fish-printf.workspace = true
libc.workspace = true
lru.workspace = true
macro_rules_attribute = "0.2.2"
nix.workspace = true
num-traits.workspace = true
once_cell.workspace = true

View File

@@ -128,7 +128,7 @@ Compiling fish requires:
Sphinx is also optionally required to build the documentation from a
cloned git repository.
Additionally, running the full test suite requires Python 3.5+, tmux, and the pexpect package.
Additionally, running the full test suite requires diff, git, Python 3.5+, pexpect, less, tmux and wget.
Building from source with CMake
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

106
build.rs
View File

@@ -1,6 +1,6 @@
#![allow(clippy::uninlined_format_args)]
use fish_build_helper::{fish_build_dir, workspace_root};
use fish_build_helper::{env_var, fish_build_dir, workspace_root};
use rsconf::Target;
use std::env;
use std::path::{Path, PathBuf};
@@ -30,9 +30,9 @@ fn main() {
);
// Some build info
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env::var("TARGET").unwrap());
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env::var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env::var("PROFILE").unwrap());
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env_var("TARGET").unwrap());
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,
@@ -50,9 +50,6 @@ fn main() {
#[cfg(feature = "gettext-extract")]
rsconf::rebuild_if_env_changed("FISH_GETTEXT_EXTRACTION_FILE");
rsconf::rebuild_if_path_changed("src/libc.c");
cc::Build::new().file("src/libc.c").compile("flibc.a");
let build = cc::Build::new();
let mut target = Target::new_from(build).unwrap();
// Keep verbose mode on until we've ironed out rust build script stuff
@@ -117,7 +114,7 @@ fn detect_apple(_: &Target) -> bool {
fn detect_cygwin(_: &Target) -> bool {
// Cygwin target is usually cross-compiled.
std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin"
env_var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin"
}
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
@@ -129,7 +126,7 @@ fn detect_cygwin(_: &Target) -> bool {
fn detect_bsd(_: &Target) -> bool {
// Instead of using `uname`, we can inspect the TARGET env variable set by Cargo. This lets us
// support cross-compilation scenarios.
let mut target = std::env::var("TARGET").unwrap();
let mut target = env_var("TARGET").unwrap();
if !target.chars().all(|c| c.is_ascii_lowercase()) {
target = target.to_ascii_lowercase();
}
@@ -172,10 +169,7 @@ fn has_small_stack(_: &Target) -> bool {
// Modern macOS versions default to an 8 MiB main stack but legacy OS X have a 0.5 MiB one.
let stack_size = unsafe { pthread_get_stacksize_np(pthread_self()) };
const TWO_MIB: usize = 2 * 1024 * 1024 - 1;
match stack_size {
0..=TWO_MIB => true,
_ => false,
}
stack_size <= TWO_MIB
}
}
@@ -183,51 +177,57 @@ fn setup_paths() {
#[cfg(windows)]
use unix_path::{Path, PathBuf};
fn get_path(name: &str, default: &str, onvar: &Path) -> PathBuf {
let mut var = PathBuf::from(env::var(name).unwrap_or(default.to_string()));
if var.is_relative() {
var = onvar.join(var);
}
var
fn overridable_path(env_var_name: &str, f: impl FnOnce(Option<String>) -> PathBuf) -> PathBuf {
rsconf::rebuild_if_env_changed(env_var_name);
let path = f(env_var(env_var_name));
rsconf::set_env_value(env_var_name, path.to_str().unwrap());
path
}
let prefix = PathBuf::from(env::var("PREFIX").unwrap_or("/usr/local".to_string()));
rsconf::rebuild_if_env_changed("PREFIX");
rsconf::set_env_value("PREFIX", prefix.to_str().unwrap());
let datadir = get_path("DATADIR", "share/", &prefix);
let sysconfdir = get_path(
"SYSCONFDIR",
// Embedded builds use "/etc," not "./share/etc".
if cfg!(feature = "embed-data") {
"/etc/"
fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
let path = PathBuf::from(path);
if path.is_relative() {
parent_if_relative.join(path)
} else {
"etc/"
},
&datadir,
);
rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("SYSCONFDIR");
path
}
}
let prefix = overridable_path("PREFIX", |env_prefix| {
PathBuf::from(env_prefix.unwrap_or("/usr/local".to_string()))
});
let datadir = join_if_relative(&prefix, env_var("DATADIR").unwrap_or("share/".to_string()));
rsconf::rebuild_if_env_changed("DATADIR");
#[cfg(not(feature = "embed-data"))]
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
overridable_path("SYSCONFDIR", |env_sysconfdir| {
join_if_relative(
&datadir,
env_sysconfdir.unwrap_or(
// Embedded builds use "/etc," not "./share/etc".
if cfg!(feature = "embed-data") {
"/etc/"
} else {
"etc/"
}
.to_string(),
),
)
});
#[cfg(not(feature = "embed-data"))]
{
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DATADIR");
let bindir = get_path("BINDIR", "bin/", &prefix);
rsconf::set_env_value("BINDIR", bindir.to_str().unwrap());
rsconf::rebuild_if_env_changed("BINDIR");
let localedir = get_path("LOCALEDIR", "locale/", &datadir);
let localedir = localedir.to_str().unwrap();
assert!(!localedir.is_empty(), "empty LOCALEDIR is not supported");
rsconf::set_env_value("LOCALEDIR", localedir);
rsconf::rebuild_if_env_changed("LOCALEDIR");
let docdir = get_path("DOCDIR", "doc/fish", &datadir);
rsconf::set_env_value("DOCDIR", docdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DOCDIR");
overridable_path("BINDIR", |env_bindir| {
join_if_relative(&prefix, env_bindir.unwrap_or("bin/".to_string()))
});
overridable_path("LOCALEDIR", |env_localedir| {
join_if_relative(&datadir, env_localedir.unwrap_or("locale/".to_string()))
});
overridable_path("DOCDIR", |env_docdir| {
join_if_relative(&datadir, env_docdir.unwrap_or("doc/fish".to_string()))
});
}
}
@@ -235,7 +235,7 @@ fn get_version(src_dir: &Path) -> String {
use std::fs::read_to_string;
use std::process::Command;
if let Ok(var) = std::env::var("FISH_BUILD_VERSION") {
if let Some(var) = env_var("FISH_BUILD_VERSION") {
return var;
}

View File

@@ -43,8 +43,16 @@ fi
# Currently, all builds are debug builds.
build_dir="$target_dir/debug"
if [ -n "$FISH_TEST_MAX_CONCURRENCY" ]; then
export RUST_TEST_THREADS="$FISH_TEST_MAX_CONCURRENCY"
export CARGO_BUILD_JOBS="$FISH_TEST_MAX_CONCURRENCY"
fi
template_file=$(mktemp)
FISH_GETTEXT_EXTRACTION_FILE=$template_file cargo build --workspace --all-targets --features=gettext-extract
(
export FISH_GETTEXT_EXTRACTION_FILE="$template_file"
cargo build --workspace --all-targets --features=gettext-extract
)
if $lint; then
PATH="$build_dir:$PATH" "$workspace_root/build_tools/style.fish" --all --check
for features in "" --no-default-features; do

View File

@@ -27,13 +27,21 @@ begin
else
set rust_extraction_file (mktemp)
# We need to build to ensure that the proc macro for extracting strings runs.
FISH_GETTEXT_EXTRACTION_FILE=$rust_extraction_file cargo check --features=gettext-extract
FISH_GETTEXT_EXTRACTION_FILE=$rust_extraction_file cargo check --no-default-features --features=gettext-extract
or exit 1
end
echo '# fish-section-tier1-from-rust'
function mark_section
set -l section_name $argv[1]
echo 'msgid "fish-section-'$section_name'"'
echo 'msgstr ""'
echo ''
end
mark_section tier1-from-rust
# Get rid of duplicates and sort.
msguniq --no-wrap --strict --sort-output $rust_extraction_file
msguniq --no-wrap --sort-output $rust_extraction_file
or exit 1
if not set -l --query _flag_use_existing_template
@@ -77,13 +85,13 @@ begin
# This regex handles explicit requests to translate a message. These are more important to translate
# than messages which should be implicitly translated.
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
echo "# fish-section-$tier-from-script-explicitly-added"
mark_section "$tier-from-script-explicitly-added"
extract_fish_script_messages_impl $explicit_regex $argv
# This regex handles descriptions for `complete` and `function` statements. These messages are not
# particularly important to translate. Hence the "implicit" label.
set -l implicit_regex '^(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
echo "# fish-section-$tier-from-script-implicitly-added"
mark_section "$tier-from-script-implicitly-added"
extract_fish_script_messages_impl $implicit_regex $argv
end

View File

@@ -233,7 +233,7 @@ milestone_number=$(
gh_api_repo milestones?state=open |
jq '.[] | select(.title == "fish '"$version"'") | .number'
)
gh_api_repo --method PATCH milestones/$milestone_number \
gh_api_repo milestones/$milestone_number --method PATCH \
--raw-field state=closed
next_patch_version=$(
@@ -244,7 +244,7 @@ next_patch_version=$(
'
)
if [ -n "$next_patch_version" ]; then
gh_api_repo --method POST milestones \
gh_api_repo milestones --method POST \
--raw-field title="fish $next_patch_version"
fi

View File

@@ -44,7 +44,7 @@ if test $all = yes
end
end
set fish_files $workspace_root/{benchmarks,build_tools,etc,share}/**.fish
set python_files {doc_src,share,tests}/**.py
set python_files $workspace_root
else
# Format the files specified as arguments.
set -l files $argv
@@ -76,19 +76,19 @@ if set -q fish_files[1]
end
if set -q python_files[1]
if not type -q black
if not type -q ruff
echo
echo $yellow'Please install `black` to style python'$normal
echo $yellow'Please install `ruff` to style python'$normal
exit 127
end
echo === Running "$green"black"$normal"
echo === Running "$green"ruff format"$normal"
if set -l -q _flag_check
if not black --check $python_files
if not ruff format --check $python_files
echo $red"Python files are not formatted correctly."$normal
exit 1
end
else
black $python_files
ruff format $python_files
end
end
@@ -101,7 +101,7 @@ end
echo === Running "$green"rustfmt"$normal"
if set -l -q _flag_check
if set -l -q _flag_all
if not cargo fmt --check
if not cargo fmt --all --check
echo $red"Rust files are not formatted correctly."$normal
exit 1
end
@@ -115,7 +115,7 @@ if set -l -q _flag_check
end
else
if set -l -q _flag_all
cargo fmt
cargo fmt --all
else
if set -q rust_files[1]
rustfmt $rust_files

View File

@@ -31,7 +31,6 @@
set -gx LC_ALL C.UTF-8
set -l build_tools (status dirname)
set -g tmpdir
set -l po_dir $build_tools/../po
set -l extract
@@ -46,8 +45,8 @@ if test -z $argv[1]
else
set -l po_dir_id (stat --format='%d:%i' -- $po_dir)
for arg in $argv
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg))
if test $po_dir_id != $arg_dir_id
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg) 2>/dev/null)
if test $po_dir_id != "$arg_dir_id"
echo "Argument $arg is not a file in the directory $(realpath $po_dir)."
echo "Non-option arguments must specify paths to files in this directory."
echo ""
@@ -99,44 +98,34 @@ if set -l --query _flag_dry_run
cp -r $po_dir/* $tmpdir
end
# This is used to identify lines which should be set here via $header_lines.
# Make sure that this prefix does not appear elsewhere in the file and only contains characters
# without special meaning in a sed pattern.
set -g header_prefix "# fish-note-sections: "
function print_header
set -l header_lines \
"Translations are divided into sections, each starting with a fish-section-* pseudo-message." \
"The first few sections are more important." \
"Ignore the tier3 sections unless you have a lot of time."
for line in $header_lines
printf '%s%s\n' $header_prefix $line
end
end
function merge_po_files --argument-names template_file po_file
msgmerge --no-wrap --update --no-fuzzy-matching --backup=none --quiet \
$po_file $template_file
or cleanup_exit
set -l new_po_file (mktemp) # TODO Remove on failure.
# Remove obsolete messages instead of keeping them as #~ entries.
and msgattrib --no-wrap --no-obsolete -o $new_po_file $po_file
or cleanup_exit
begin
echo "# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment."
echo "# fish-note-sections: The first few sections are more important."
echo "# fish-note-sections: Ignore the tier3 sections unless you have a lot of time."
sed -i '
/^# fish-note-sections:/d;
/^# fish-section-/d;
' $new_po_file
set -l next_line 1
set -l section
awk <$template_file '
/^# fish-section-\S*$/ {
section = $0
}
section != "" && /^msgid ".+"$/ {
print section
print $0
section = ""
}
' |
while read -l section
read -l msgid_line
set -l line_number (grep -m1 -Fxn $msgid_line $new_po_file | string split :)[1]
sed -n "$next_line,$(math $line_number - 1)"p $new_po_file
echo $section
set next_line $line_number
# set section
end
sed -n "$next_line,\$"p $new_po_file
print_header
# Paste PO file without old header lines.
sed '/^'$header_prefix'/d' $new_po_file
end >$po_file
rm $new_po_file
end
@@ -149,7 +138,10 @@ for po_file in $po_files
if test -e $po_file
merge_po_files $template_file $po_file
else
cp $template_file $po_file
begin
print_header
cat $template_file
end >$po_file
end
end
end

View File

@@ -1,4 +1,21 @@
use std::{borrow::Cow, env, path::Path};
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt, path::Path};
pub fn env_var(name: &str) -> Option<String> {
let err = match env::var(name) {
Ok(p) => return Some(p),
Err(err) => err,
};
use env::VarError::*;
match err {
NotPresent => None,
NotUnicode(os_string) => {
panic!(
"Environment variable {name} is not valid Unicode: {:?}",
os_string.as_bytes()
)
}
}
}
pub fn workspace_root() -> &'static Path {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));

View File

@@ -14,12 +14,9 @@ fn main() {
#[cfg(not(clippy))]
fn build_man(man_dir: &Path) {
use std::{
env,
process::{Command, Stdio},
};
use std::process::{Command, Stdio};
use fish_build_helper::workspace_root;
use fish_build_helper::{env_var, workspace_root};
let workspace_root = workspace_root();
@@ -47,7 +44,7 @@ fn build_man(man_dir: &Path) {
let _ = std::fs::create_dir_all(sec1_str);
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env::var("FISH_BUILD_DOCS") == Ok("0".to_string()) {
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
@@ -64,7 +61,7 @@ fn build_man(man_dir: &Path) {
.spawn()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if env::var("FISH_BUILD_DOCS") == Ok("1".to_string()) {
if env_var("FISH_BUILD_DOCS") == Some("1".to_string()) {
panic!("Could not find sphinx-build to build man pages.\nInstall sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0.");
}
rsconf::warn!("Cannot find sphinx-build to build man pages.");

View File

@@ -1,10 +1,11 @@
use std::{
env,
ffi::OsStr,
path::{Path, PathBuf},
process::Command,
process::{Command, Stdio},
};
use fish_build_helper::env_var;
fn main() {
let cache_dir =
PathBuf::from(fish_build_helper::fish_build_dir()).join("fish-localization-map-cache");
@@ -27,14 +28,18 @@ fn embed_localizations(cache_dir: &Path) {
std::fs::create_dir_all(cache_dir).unwrap();
let localization_map_path =
Path::new(&env::var("OUT_DIR").unwrap()).join("localization_maps.rs");
Path::new(&env_var("OUT_DIR").unwrap()).join("localization_maps.rs");
let mut localization_map_file = BufWriter::new(File::create(&localization_map_path).unwrap());
// This will become a map which maps from language identifiers to maps containing localizations
// for the respective language.
let mut catalogs = phf_codegen::Map::new();
match Command::new("msgfmt").arg("-h").status() {
match Command::new("msgfmt")
.arg("-h")
.stdout(Stdio::null())
.status()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
rsconf::warn!(
"Cannot find msgfmt to build gettext message catalogs. Localization will not work."
@@ -97,6 +102,12 @@ fn embed_localizations(cache_dir: &Path) {
.arg(&po_file_path)
.output()
.unwrap();
if !output.status.success() {
panic!(
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
}
let mo_data = output.stdout;
// Extract map from MO data.

View File

@@ -15,7 +15,7 @@ pub enum Arg<'a> {
#[cfg(feature = "widestring")]
WString(WString),
UInt(u64),
SInt(i64, u8), // signed integers track their width as the number of bits
SInt(i64),
Float(f64),
USizeRef(&'a mut usize), // for use with %n
}
@@ -59,7 +59,7 @@ pub fn as_str<'s>(&'s self, storage: &'s mut String) -> Result<&'s str, Error>
pub fn as_uint(&self) -> Result<u64, Error> {
match *self {
Arg::UInt(u) => Ok(u),
Arg::SInt(i, _w) => i.try_into().map_err(|_| Error::Overflow),
Arg::SInt(i) => i.try_into().map_err(|_| Error::Overflow),
_ => Err(Error::BadArgType),
}
}
@@ -68,25 +68,18 @@ pub fn as_uint(&self) -> Result<u64, Error> {
pub fn as_sint(&self) -> Result<i64, Error> {
match *self {
Arg::UInt(u) => u.try_into().map_err(|_| Error::Overflow),
Arg::SInt(i, _w) => Ok(i),
Arg::SInt(i) => Ok(i),
_ => Err(Error::BadArgType),
}
}
// If this is a signed value, then return the sign (true if negative) and the magnitude,
// masked to the value's width. This allows for e.g. -1 to be returned as 0xFF, 0xFFFF, etc.
// depending on the original width.
// If this is an unsigned value, simply return (false, u64).
pub fn as_wrapping_sint(&self) -> Result<(bool, u64), Error> {
/// Unwraps [`Arg::UInt`] to [`u64`].
/// Unwraps [`Arg::SInt`] and casts the [`i64`] to [`u64`].
/// Calling this on other variants of `[Arg]` is an error.
pub fn as_wrapping_sint(&self) -> Result<u64, Error> {
match *self {
Arg::UInt(u) => Ok((false, u)),
Arg::SInt(i, w) => {
// Need to shift twice in case w is 64.
debug_assert!(w > 0);
let mask = ((1u64 << (w - 1)) << 1).wrapping_sub(1);
let ui = (i as u64) & mask;
Ok((i < 0, ui))
}
Arg::UInt(u) => Ok(u),
Arg::SInt(i) => Ok(i as u64),
_ => Err(Error::BadArgType),
}
}
@@ -97,7 +90,7 @@ pub fn as_float(&self) -> Result<f64, Error> {
match *self {
Arg::Float(f) => Ok(f),
Arg::UInt(u) => Ok(u as f64),
Arg::SInt(i, _w) => Ok(i as f64),
Arg::SInt(i) => Ok(i as f64),
_ => Err(Error::BadArgType),
}
}
@@ -181,7 +174,7 @@ macro_rules! impl_to_arg {
$(
impl<'a> ToArg<'a> for $t {
fn to_arg(self) -> Arg<'a> {
Arg::SInt(self as i64, <$t>::BITS as u8)
Arg::SInt(self as i64)
}
}
)*
@@ -211,8 +204,6 @@ mod tests {
#[test]
fn test_to_arg() {
const SIZE_WIDTH: u8 = isize::BITS as u8;
assert!(matches!("test".to_arg(), Arg::Str("test")));
assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
#[cfg(feature = "widestring")]
@@ -224,17 +215,17 @@ fn test_to_arg() {
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, 8)));
assert!(matches!(42i16.to_arg(), Arg::SInt(42, 16)));
assert!(matches!(42i32.to_arg(), Arg::SInt(42, 32)));
assert!(matches!(42i64.to_arg(), Arg::SInt(42, 64)));
assert!(matches!(42isize.to_arg(), Arg::SInt(42, SIZE_WIDTH)));
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, 8));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42, 16));
assert_eq!((-42i32).to_arg(), Arg::SInt(-42, 32));
assert_eq!((-42i64).to_arg(), Arg::SInt(-42, 64));
assert_eq!((-42isize).to_arg(), Arg::SInt(-42, SIZE_WIDTH));
assert_eq!((-42i8).to_arg(), Arg::SInt(-42));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42));
assert_eq!((-42i32).to_arg(), Arg::SInt(-42));
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)));

View File

@@ -472,7 +472,7 @@ pub fn sprintf_locale(
// If someone passes us a negative value, format it with the width
// we were given.
let lower = conv_spec.is_lower();
let (_, uint) = arg.as_wrapping_sint()?;
let uint = arg.as_wrapping_sint()?;
if uint != 0 {
if flags.alt_form {
prefix = if lower { "0x" } else { "0X" };

View File

@@ -77,7 +77,7 @@ fn write_str(&mut self, _s: &str) -> fmt::Result {
#[test]
fn smoke() {
assert_fmt!("Hello, %s!", "world" => "Hello, world!");
assert_fmt!("Hello, %ls!", "world" => "Hello, world!");
assert_fmt!("Hello, %ls!", "world" => "Hello, world!"); // length modifier
assert_fmt!("Hello, world! %d %%%%", 3 => "Hello, world! 3 %%");
assert_fmt!("" => "");
}
@@ -225,7 +225,7 @@ fn test_int() {
assert_fmt!("%d", -123 => "-123");
assert_fmt!("~%d~", 148 => "~148~");
assert_fmt!("00%dxx", -91232 => "00-91232xx");
assert_fmt!("%x", -9232 => "ffffdbf0");
assert_fmt!("%x", -9232 => "ffffffffffffdbf0");
assert_fmt!("%X", 432 => "1B0");
assert_fmt!("%09X", 432 => "0000001B0");
assert_fmt!("%9X", 432 => " 1B0");
@@ -234,6 +234,7 @@ fn test_int() {
assert_fmt!("%2o", 4 => " 4");
assert_fmt!("% 12d", -4 => " -4");
assert_fmt!("% 12d", 48 => " 48");
// with length modifier
assert_fmt!("%ld", -4_i64 => "-4");
assert_fmt!("%lld", -4_i64 => "-4");
assert_fmt!("%lX", -4_i64 => "FFFFFFFFFFFFFFFC");
@@ -248,6 +249,7 @@ fn test_int() {
assert_fmt!("%9X", 492 => " 1EC");
assert_fmt!("% 12u", 4 => " 4");
assert_fmt!("% 12u", 48 => " 48");
// with length modifier
assert_fmt!("%lu", 4_u64 => "4");
assert_fmt!("%llu", 4_u64 => "4");
assert_fmt!("%lX", 4_u64 => "4");
@@ -414,6 +416,7 @@ fn test_float() {
assert_fmt1!("%f", 0.0, "0.000000");
assert_fmt1!("%g", 0.0, "0");
assert_fmt1!("%#g", 0.0, "0.00000");
// with length modifier
assert_fmt1!("%la", 0.0, "0x0p+0");
assert_fmt1!("%le", 0.0, "0.000000e+00");
assert_fmt1!("%lf", 0.0, "0.000000");
@@ -430,7 +433,7 @@ fn test_float() {
assert_fmt1!("%.4f", 1.03125, "1.0312"); /* 0x1.08p0 */
assert_fmt1!("%.2f", 1.375, "1.38");
assert_fmt1!("%.1f", 1.375, "1.4");
assert_fmt1!("%.1lf", 1.375, "1.4");
assert_fmt1!("%.1lf", 1.375, "1.4"); // length modifier
assert_fmt1!("%.15f", 1.1, "1.100000000000000");
assert_fmt1!("%.16f", 1.1, "1.1000000000000001");
assert_fmt1!("%.17f", 1.1, "1.10000000000000009");
@@ -755,8 +758,8 @@ fn test_errors() {
sprintf_err!("%1", => BadFormatString);
sprintf_err!("%%%k", => BadFormatString);
sprintf_err!("%B", => BadFormatString);
sprintf_err!("%lC", 'q' => BadFormatString);
sprintf_err!("%lS", 'q' => BadFormatString);
sprintf_err!("%lC", 'q' => BadFormatString); // length modifier
sprintf_err!("%lS", 'q' => BadFormatString); // length modifier
sprintf_err!("%d", => MissingArg);
sprintf_err!("%d %u", 1 => MissingArg);
sprintf_err!("%*d", 5 => MissingArg);

View File

@@ -42,6 +42,6 @@ The typical use is to run something, stop it with ctrl-z, and then continue it i
If only 123 and 789 exist, it will still background them and print an error about 456.
``bg 123 banana`` or ``bg banana 123`` will complain that "banana" is not a valid job specifier.
``bg 123 banana`` or ``bg banana 123`` will complain that "banana" is not a valid process ID.
``bg %2`` will background job 2.

View File

@@ -48,7 +48,7 @@ This refuses to store any immediate "vault", "mysql" or "ls" calls. Commands sta
function fish_should_add_to_history
# I don't want `git pull`s in my history when I'm in a specific repository
if string match -qr '^git pull'
if string match -qr '^git pull' -- "$argv"
and string match -qr "^/home/me/my-secret-project/" -- (pwd -P)
return 1
end

View File

@@ -21,7 +21,7 @@ A function is a list of commands that will be executed when the name of the func
The following options are available:
**-a** *NAMES* or **--argument-names** *NAMES*
Has to be the last option. Assigns the value of successive command-line arguments to the names given in *NAMES* (separated by space). These are the same arguments given in :envvar:`argv`, and are still available there. See also :ref:`Argument Handling <variables-argv>`.
Assigns the value of successive command-line arguments to the names given in *NAMES* (separated by spaces). These are the same arguments given in :envvar:`argv`, and are still available there (unless ``--inherit-variable argv`` was used or one of the given *NAMES* is ``argv``). See also :ref:`Argument Handling <variables-argv>`.
**-d** *DESCRIPTION* or **--description** *DESCRIPTION*
A description of what the function does, suitable as a completion description.
@@ -40,7 +40,7 @@ The following options are available:
Run this function when the variable *VARIABLE_NAME* changes value. Note that :program:`fish` makes no guarantees on any particular timing or even that the function will be run for every single ``set``. Rather it will be run when the variable has been set at least once, possibly skipping some values or being run when the variable has been set to the same value (except for universal variables set in other shells - only changes in the value will be picked up for those).
**-j** *PID* or **--on-job-exit** *PID*
Run this function when the job containing a child process with the given process identifier *PID* exits. Instead of a PID, the string 'caller' can be specified. This is only allowed when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
Run this function when the job containing a child process with the given process ID *PID* exits. Instead of a PID, the string 'caller' can be specified. This is only allowed when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
This will not trigger for :doc:`disowned <disown>` jobs.
**-p** *PID* or **--on-process-exit** *PID*

View File

@@ -3,8 +3,10 @@ LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV PIP_ROOT_USER_ACTION=ignore
RUN apk add --no-cache \
cmake ninja \
bash \
cargo \
g++ \
@@ -14,11 +16,15 @@ RUN apk add --no-cache \
musl-dev \
pcre2-dev \
py3-pexpect \
py3-pip \
python3 \
rust \
rustfmt \
sudo \
tmux
RUN pip install --break-system-packages black
RUN addgroup -g 1000 fishuser
RUN adduser \

View File

@@ -6,12 +6,15 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
build-essential \
ca-certificates \
curl \
g++-multilib \
gettext \
git \
locales \
openssl \
pkg-config \
python3 \
python3-pexpect \

View File

@@ -6,14 +6,17 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
build-essential \
ca-certificates \
cargo \
clang \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
rustc \

View File

@@ -6,12 +6,14 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
build-essential \
ca-certificates \
cargo \
gettext \
git \
locales \
openssl \
pkg-config \
python3 \
python3-pexpect \

View File

@@ -6,8 +6,10 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
build-essential \
ca-certificates \
cargo \
file \
g++ \
@@ -15,10 +17,11 @@ RUN apt-get update \
git \
libpcre2-dev \
locales \
openssl \
pkg-config \
python3 \
python3-pexpect \
rust \
rustc \
sudo \
tmux \
&& locale-gen en_US.UTF-8 \

View File

@@ -5,13 +5,16 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
build-essential \
ca-certificates \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
sudo \

View File

@@ -5,13 +5,16 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
build-essential \
ca-certificates \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
sudo \

View File

@@ -5,22 +5,28 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
build-essential \
ca-certificates \
cargo \
clang \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
rustc \
sudo \
tmux \
python3-pip \
&& locale-gen en_US.UTF-8 \
&& apt-get clean
RUN pip install black
RUN groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
&& adduser fishuser sudo \

View File

@@ -5,12 +5,16 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install \
&& apt-get -y install --no-install-recommends \
adduser \
build-essential \
ca-certificates \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
tmux \

1690
po/de.po

File diff suppressed because it is too large Load Diff

1644
po/en.po

File diff suppressed because it is too large Load Diff

1652
po/fr.po

File diff suppressed because it is too large Load Diff

1628
po/pl.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1626
po/sv.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,16 @@
function __fish_bind_test1
set -l args
set -l use_keys no
for i in (commandline -pxc)
switch $i
case -k --k --ke --key
set use_keys yes
case "-*"
case "*"
set -a args $i
end
end
switch $use_keys
case yes
switch (count $args)
case 1
return 0
end
end
return 1
end
function __fish_bind_test2
set -l args
for i in (commandline -pxc)
switch $i
case "-*"
case "*"
set -a args $i
end
end
switch (count $args)
case 2
return 0
end
return 1
set -l bind_optspecs \
a/all \
e/erase \
M/mode= \
m/sets-mode= \
preset \
s/silent \
user
function __fish_bind_has_keys --inherit-variable bind_optspecs
argparse $bind_optspecs -- $argv 2>/dev/null
or return
test (count $argv) -ge 2
end
complete -c bind -f
@@ -56,11 +26,10 @@ complete -c bind -s s -l silent -d 'Operate silently'
complete -c bind -l preset -d 'Operate on preset bindings'
complete -c bind -l user -d 'Operate on user bindings'
complete -c bind -n __fish_bind_test2 -a '(bind --function-names)' -d 'Function name' -x
complete -c bind -n '__fish_bind_has_keys (commandline -pcx)' -a '(bind --function-names)' -d 'Function name' -x
function __fish_bind_complete
argparse M/mode= m/sets-mode= preset user s/silent \
a/all function-names list-modes e/erase -- (commandline -xpc)[2..] 2>/dev/null
function __fish_bind_complete --inherit-variable bind_optspecs
argparse $bind_optspecs -- (commandline -xpc)[2..] 2>/dev/null
or return 1
set -l token (commandline -ct)
if test (count $argv) = 0 && set -l prefix (string match -r -- '(.*,)?(ctrl-|alt-|shift-|super-)*' $token)

View File

@@ -41,7 +41,7 @@ complete -c find -o empty -d "File is empty and is either a regular file or a di
complete -c find -o executable -d "File is executable"
complete -c find -o false -d "Always false"
complete -c find -o fstype -d "File is on filesystem of specified type" -a "(__fish_print_filesystems)" -x
complete -c find -o gid -d "Numeric group id of file" -x -a "(__fish_complete_group_ids)"
complete -c find -o gid -d "Numeric group ID of file" -x -a "(__fish_complete_group_ids)"
complete -c find -o group -d "Group name of file" -x -a "(__fish_complete_groups)"
complete -c find -o ilname -d "File is symlink matching specified case insensitive pattern" -x

View File

@@ -1468,7 +1468,7 @@ complete -x -c git -n '__fish_git_using_command daemon' -l user-path -d 'Allow ~
complete -f -c git -n '__fish_git_using_command daemon' -l verbose -d 'Log all details'
complete -f -c git -n '__fish_git_using_command daemon' -l reuseaddr -d 'Reuse address when binding to listening server'
complete -f -c git -n '__fish_git_using_command daemon' -l detach -d 'Detach from shell'
complete -x -c git -n '__fish_git_using_command daemon' -l reuseaddr -d 'Save the process id in file'
complete -x -c git -n '__fish_git_using_command daemon' -l reuseaddr -d 'Save the process ID in file'
complete -x -c git -n '__fish_git_using_command daemon' -l user -d 'Change daemon\'s uid'
complete -x -c git -n '__fish_git_using_command daemon' -l group -d 'Change daemon\'s gid'
complete -x -c git -n '__fish_git_using_command daemon' -l enable -a 'upload-pack upload-archive receive-pack' -d 'Enable service'

View File

@@ -1,6 +1,4 @@
if test -d "$__fish_data_dir/man/man1/"
complete -c help -x -a '(__fish_print_commands)' -d 'Help for this command'
end
complete -c help -x -a '(__fish_print_commands)' -d 'Help for this command'
# Help topics in index.html
# This was semi-automated with `grep 'class="anchor"' -A1 /usr/share/doc/fish/index.html

View File

@@ -4,7 +4,7 @@ if string match -eq 'GNU coreutils' (id --version 2>&1)
complete id -s Z -l context -d "Print security context"
complete id -s z -l zero -d "Delimit entries with NUL"
complete id -s n -l name -d "Print name, not number"
complete id -s g -l group -d "Print effective group id"
complete id -s g -l group -d "Print effective group ID"
complete id -s G -l groups -d "Print all group ids"
complete id -s r -l real -d "Print real ID, not effective"
complete id -s u -l user -d "Print effective user ID"
@@ -24,7 +24,7 @@ else
complete id -s F -d "Print full name of the user"
complete id -s G -d "Print all group ids"
complete id -s P -d "Print as passwd file entry"
complete id -s g -d "Print effective group id"
complete id -s g -d "Print effective group ID"
complete id -s n -d "Print name, not number"
complete id -s p -d "Human-readable output"
complete id -s r -d "Print real ID, not effective"

View File

@@ -548,8 +548,8 @@ function __fish_complete_ip
attach "Attach process to network namespace" \
delete "Delete network namespace" \
set "Change network namespace attributes" \
identify "Display network namespace for a process id" \
pids "Display process ids of processes running in network namespace" \
identify "Display network namespace for a process ID" \
pids "Display process IDs of processes running in network namespace" \
monitor "Report as network namespace names are added and deleted" \
exec "Execute command in network namespace" \
help "Display help" \

View File

@@ -1,6 +1,6 @@
complete -c jobs -s h -l help -d 'Display help and exit'
complete -c jobs -s p -l pid -d "Show the process id of each process in the job"
complete -c jobs -s g -l group -d "Show group id of job"
complete -c jobs -s p -l pid -d "Show the process ID of each process in the job"
complete -c jobs -s g -l group -d "Show group ID of job"
complete -c jobs -s c -l command -d "Show commandname of each job"
complete -c jobs -s l -l last -d "Only show status for last job to be started"
complete -c jobs -s q -l quiet -l query -d "Check if a job exists without output"

View File

@@ -110,7 +110,7 @@ set -l maybe_filter_private_vars '
)'
# We do not *filter* these by the given scope because you might want to set e.g. a global to shadow a universal.
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -U | $maybe_filter_private_vars | string replace ' ' \t'Universal Variable: ')"
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -g | $maybe_filter_private_vars | string replace -r '^((?:history|fish_killring) ).*' '$1' | string replace ' ' \t'Global Variable: ')"
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -g | $maybe_filter_private_vars | string replace -r '^((?:history|fish_killring) ).*' '\$1' | string replace ' ' \t'Global Variable: ')"
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -l | $maybe_filter_private_vars | string replace ' ' \t'Local Variable: ')"
# Complete some fish configuration variables even if they aren't set.
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(__fish_complete_special_vars)"

View File

@@ -5,7 +5,11 @@ function __fish_cache_put
set -l dir (path dirname $cache_file)
chown --reference=$dir $cache_file 2>/dev/null ||
chown (
stat --format '%u:%g' $dir 2>/dev/null ||
stat -f '%u:%g' $dir
if stat --version 2>&1 | string match -q 'BusyBox*'
stat -c '%u:%g' $dir
else
stat --format '%u:%g' $dir 2>/dev/null ||
stat -f '%u:%g' $dir
end
) $cache_file
end

View File

@@ -0,0 +1,16 @@
# localization: skip(private)
function __fish_list_files
set -l dir $argv[1]
if set -q __fish_data_dir[1]
# Construct a directory prefix without trailing slash.
if test -n "$dir"
set dir $__fish_data_dir/$dir
else
set dir $__fish_data_dir
end
set -l files $dir/**
string replace -- $dir/ '' $files
else
status list-files $dir
end
end

View File

@@ -1,14 +1,11 @@
# localization: skip(private)
function __fish_print_commands --description "Print a list of documented fish commands"
if set -q __fish_data_dir[1] && test -d $__fish_data_dir/man/man1/
for file in $__fish_data_dir/man/man1/**.1*
string replace -r '.*/' '' -- $file |
string replace -r '.1(.gz)?$' '' |
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes|terminal-compatibility)$'
end
end
status list-files man/man1/ 2>/dev/null |
string replace -r '.*/' '' -- $file |
printf %s\n $__fish_data_dir/man/man1/**.1*
else
status list-files man/man1/ 2>/dev/null
end |
string replace -r '.*/' '' |
string replace -r '.1(.gz)?$' '' |
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes)$'
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes|terminal-compatibility)$'
end

View File

@@ -0,0 +1,10 @@
# localization: skip(private)
function __fish_with_file
set -l file $argv[1]
set -l cmd $argv[2..]
if set -q __fish_data_dir[1]
$cmd $__fish_data_dir/$file
else
status get-file $file | $cmd
end
end

View File

@@ -1,4 +1,5 @@
# localization: tier3
function __ssh_history_completions -d "Retrieve `user@host` entries from history"
history --prefix ssh --max=100 | string replace -rf '.* ([A-Za-z0-9._:-]+@[A-Za-z0-9._:-]+).*' '$1'
# Accept the typical hostname/ip chars, but no ":" at the end
history --prefix ssh --max=100 | string replace -rf '.* ([A-Za-z0-9._:-]+@[A-Za-z0-9._:-]*[A-Za-z0-9._-]).*' '$1'
end

View File

@@ -103,7 +103,7 @@ function fish_add_path --description "Add paths to the PATH"
else
if set -q _flag_verbose
# print a message in verbose mode
printf (_ "No paths to add, not setting anything.\n") "$p"
printf (_ "No paths to add, not setting anything.\n")
end
return 1
end

View File

@@ -87,7 +87,7 @@ function fish_config --description "Launch fish's web based configuration"
return 1
end
set -l prompt_dir $__fish_data_dir/sample_prompts $__fish_data_dir/tools/web_config/sample_prompts
set -l prompt_dir $__fish_data_dir/tools/web_config/sample_prompts
switch $cmd
case show
set -l fish (status fish-path)

View File

@@ -10,7 +10,7 @@ function funced --description 'Edit function definition'
end
if not set -q argv[1]
printf (_ "%ls: Expected at least %d args, got only %d\n") funced 1 0
printf (_ "%s: Expected at least %d args, got only %d\n") funced 1 0
return 1
end

View File

@@ -17,7 +17,7 @@ function funcsave --description "Save the current definition of all specified fu
end
if not set -q argv[1]
printf (_ "%ls: Expected at least %d args, got only %d\n") funcsave 1 0 >&2
printf (_ "%s: Expected at least %d args, got only %d\n") funcsave 1 0 >&2
return 1
end

View File

@@ -185,11 +185,11 @@ function history --description "display or manipulate interactive command histor
case clear # clear the interactive command history
if test -n "$search_mode"
or set -q show_time[1]
printf (_ "%ls: %ls: subcommand takes no options\n") history $hist_cmd >&2
printf (_ "%s: %s: subcommand takes no options\n") history $hist_cmd >&2
return 1
end
if set -q argv[1]
printf (_ "%ls: %ls: expected %d arguments; got %d\n") history $hist_cmd 0 (count $argv) >&2
printf (_ "%s: %s: expected %d arguments; got %d\n") history $hist_cmd 0 (count $argv) >&2
return 1
end
@@ -214,7 +214,7 @@ function history --description "display or manipulate interactive command histor
builtin history append $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
case '*'
printf "%ls: unexpected subcommand '%ls'\n" $cmd $hist_cmd
printf "%s: unexpected subcommand '%s'\n" $cmd $hist_cmd
return 2
end
end

View File

@@ -15,7 +15,7 @@ if not command -sq open
end
if not set -q argv[1]
printf (_ "%ls: Expected at least %d args, got only %d\n") open 1 0 >&2
printf (_ "%s: Expected at least %d args, got only %d\n") open 1 0 >&2
return 1
end

View File

@@ -21,7 +21,6 @@ from __future__ import print_function
from deroff import Deroffer
import argparse
import bz2
import codecs
import errno
import gzip
import os
@@ -734,7 +733,7 @@ class TypeDeroffManParser(ManParser):
# Raises IOError if it cannot be opened
def file_is_overwritable(path):
result = False
file = codecs.open(path, "r", encoding="utf-8")
file = open(path, "r", encoding="utf-8")
for line in file:
# Skip leading empty lines
line = line.strip()
@@ -893,7 +892,7 @@ def parse_manpage_at_path(manpage_path, output_directory):
else:
fullpath = os.path.join(output_directory, CMDNAME + ".fish")
try:
output_file = codecs.open(fullpath, "w", encoding="utf-8")
output_file = open(fullpath, "w", encoding="utf-8")
except IOError as err:
add_diagnostic(
"Unable to open file '%s': error(%d): %s"

View File

@@ -68,14 +68,6 @@ def find_executable(exe, paths=()):
return proposed_path
def isMacOS10_12_5_OrLater():
"""Return whether this system is macOS 10.12.5 or a later version."""
try:
return [int(x) for x in platform.mac_ver()[0].split(".")] >= [10, 12, 5]
except ValueError:
return False
def is_wsl():
"""Return whether we are running under the Windows Subsystem for Linux"""
if "linux" in platform.system().lower() and os.access("/proc/version", os.R_OK):
@@ -1423,7 +1415,7 @@ fish_bin_path = None
fish_bin_name = "fish.exe" if is_windows() else "fish"
if not fish_bin_dir:
print("The $__fish_bin_dir environment variable is not set. " "Looking in $PATH...")
print("The $__fish_bin_dir environment variable is not set. Looking in $PATH...")
fish_bin_path = find_executable(fish_bin_name)
if not fish_bin_path:
print("fish could not be found. Is fish installed correctly?")
@@ -1544,8 +1536,9 @@ print("%sHit ENTER to stop.%s" % (ENTER_BOLD_MODE, EXIT_ATTRIBUTE_MODE))
def runThing():
if isMacOS10_12_5_OrLater():
subprocess.check_call(["open", fileurl])
if os.environ.get("BROWSER") == "true":
# Don't start a browser in this case (see issue #11926)
pass
elif is_wsl():
cmd_path = find_executable("cmd.exe", COMMON_WSL_CMD_PATHS)
if cmd_path:
@@ -1606,7 +1599,15 @@ def capture_enter(port):
def get_windows_signal():
"""Using socket as a replacement for stdin on Windows."""
(sig, sig_port) = create_socket(8000, 9000)
# The intent is to get a free port between 8000 and 9000, like for the HTTP
# server. But we already know that port 8000..PORT are not available. So
# starting from `PORT+1` is more efficient.
# More importantly though, Windows allows multiple sockets to bind to the
# same port in some circumstances (see SO_EXCLUSIVEADDRUSE documentation),
# and thus allows the signal socket to bind to the same port as HTTP.
# A browser may then end up reaching the wrong socket and causing
# fish_config to shutdown prematurely.
(sig, sig_port) = create_socket(PORT + 1, 9000)
threading.Thread(target=capture_enter, args=(sig_port,)).start()
return sig

View File

@@ -24,6 +24,7 @@
TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
};
use crate::wchar::prelude::*;
use macro_rules_attribute::derive;
use std::borrow::Cow;
use std::convert::AsMut;
use std::ops::{ControlFlow, Deref};
@@ -162,10 +163,10 @@ fn describe(&self) -> WString {
let mut res = ast_kind_to_string(self.kind()).to_owned();
if let Some(n) = self.as_token() {
let token_type = n.token_type().to_wstr();
sprintf!(=> &mut res, " '%ls'", token_type);
sprintf!(=> &mut res, " '%s'", token_type);
} else if let Some(n) = self.as_keyword() {
let keyword = n.keyword().to_wstr();
sprintf!(=> &mut res, " '%ls'", keyword);
sprintf!(=> &mut res, " '%s'", keyword);
}
res
}
@@ -404,8 +405,8 @@ trait CheckParse: Default {
}
/// Implement the node trait.
macro_rules! implement_node {
( $name:ident ) => {
macro_rules! Node {
($name:ident) => {
impl Node for $name {
fn kind(&self) -> Kind<'_> {
Kind::$name(self)
@@ -425,11 +426,19 @@ fn cast(node: &dyn Node) -> Option<&Self> {
}
}
};
( $(#[$_m:meta])* $_v:vis struct $name:ident $_:tt $(;)? ) => {
Node!($name);
};
( $(#[$_m:meta])* $_v:vis enum $name:ident $_:tt ) => {
Node!($name);
};
}
/// Implement the leaf trait.
macro_rules! implement_leaf {
( $name:ident ) => {
macro_rules! Leaf {
($name:ident) => {
impl Leaf for $name {
fn range(&self) -> Option<SourceRange> {
self.range
@@ -450,17 +459,20 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
}
}
};
( $(#[$_m:meta])* $_v:vis struct $name:ident $_:tt $(;)? ) => {
Leaf!($name);
};
}
/// Define a node that implements the keyword trait.
macro_rules! define_keyword_node {
( $name:ident, $($allowed:ident),* $(,)? ) => {
#[derive(Default, Debug)]
#[derive(Default, Debug, Leaf!)]
pub struct $name {
range: Option<SourceRange>,
keyword: ParseKeyword,
}
implement_leaf!($name);
impl Node for $name {
fn kind(&self) -> Kind<'_> {
Kind::Keyword(self)
@@ -489,7 +501,7 @@ fn as_leaf(&self) -> &dyn Leaf {
/// Define a node that implements the token trait.
macro_rules! define_token_node {
( $name:ident, $($allowed:ident),* $(,)? ) => {
#[derive(Default, Debug)]
#[derive(Default, Debug, Leaf!)]
pub struct $name {
range: Option<SourceRange>,
parse_token_type: ParseTokenType,
@@ -502,7 +514,6 @@ fn kind_mut(&mut self) -> KindMut<'_> {
KindMut::Token(self)
}
}
implement_leaf!($name);
impl Token for $name {
fn token_type(&self) -> ParseTokenType {
self.parse_token_type
@@ -535,11 +546,9 @@ macro_rules! define_list_node {
$name:ident,
$contents:ident
) => {
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!)]
pub struct $name(Box<[$contents]>);
implement_node!($name);
impl Deref for $name {
type Target = Box<[$contents]>;
fn deref(&self) -> &Self::Target {
@@ -582,11 +591,15 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
}
/// Implement the acceptor trait for the given branch node.
macro_rules! implement_acceptor_for_branch {
macro_rules! Acceptor {
(
$name:ident
$(, $field_name:ident )*
$(,)?
$(#[$_m:meta])*
$_v:vis struct $name:ident {
$(
$(#[$_fm:meta])*
$_fv:vis $field_name:ident : $_ft:ty
),* $(,)?
}
) => {
impl Acceptor for $name {
#[allow(unused_variables)]
@@ -612,18 +625,16 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
visitor.did_visit_fields_of(self, flow);
}
}
}
};
}
/// A redirection has an operator like > or 2>, and a target like /dev/null or &1.
/// Note that pipes are not redirections.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct Redirection {
pub oper: TokenRedirection,
pub target: String_,
}
implement_node!(Redirection);
implement_acceptor_for_branch!(Redirection, oper, target);
impl CheckParse for Redirection {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
@@ -633,7 +644,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(VariableAssignmentList, VariableAssignment);
#[derive(Debug)]
#[derive(Debug, Node!)]
pub enum ArgumentOrRedirection {
Argument(Argument),
Redirection(Box<Redirection>), // Boxed because it's bigger
@@ -689,8 +700,6 @@ pub fn redirection(&self) -> &Redirection {
}
}
implement_node!(ArgumentOrRedirection);
impl CheckParse for ArgumentOrRedirection {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
let typ = pop.peek_type(0);
@@ -701,7 +710,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(ArgumentOrRedirectionList, ArgumentOrRedirection);
/// A statement is a normal command, or an if / while / etc
#[derive(Debug)]
#[derive(Debug, Node!)]
pub enum Statement {
Decorated(DecoratedStatement),
Not(Box<NotStatement>),
@@ -710,7 +719,6 @@ pub enum Statement {
If(Box<IfStatement>),
Switch(Box<SwitchStatement>),
}
implement_node!(Statement);
impl Default for Statement {
fn default() -> Self {
@@ -756,7 +764,7 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
/// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
/// like if statements, where we require a command).
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct JobPipeline {
/// Maybe the time keyword.
pub time: Option<KeywordTime>,
@@ -769,11 +777,9 @@ pub struct JobPipeline {
/// Maybe backgrounded.
pub bg: Option<TokenBackground>,
}
implement_node!(JobPipeline);
implement_acceptor_for_branch!(JobPipeline, time, variables, statement, continuation, bg);
/// A job_conjunction is a job followed by a && or || continuations.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct JobConjunction {
/// The job conjunction decorator.
pub decorator: Option<JobConjunctionDecorator>,
@@ -786,8 +792,6 @@ pub struct JobConjunction {
/// only fail to be present if we ran out of tokens.
pub semi_nl: Option<SemiNl>,
}
implement_node!(JobConjunction);
implement_acceptor_for_branch!(JobConjunction, decorator, job, continuations, semi_nl);
impl CheckParse for JobConjunction {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
@@ -802,7 +806,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
}
}
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct ForHeader {
/// 'for'
pub kw_for: KeywordFor,
@@ -815,20 +819,16 @@ pub struct ForHeader {
/// newline or semicolon
pub semi_nl: SemiNl,
}
implement_node!(ForHeader);
implement_acceptor_for_branch!(ForHeader, kw_for, var_name, kw_in, args, semi_nl);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct WhileHeader {
/// 'while'
pub kw_while: KeywordWhile,
pub condition: JobConjunction,
pub andor_tail: AndorJobList,
}
implement_node!(WhileHeader);
implement_acceptor_for_branch!(WhileHeader, kw_while, condition, andor_tail);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct FunctionHeader {
pub kw_function: KeywordFunction,
/// functions require at least one argument.
@@ -836,20 +836,16 @@ pub struct FunctionHeader {
pub args: ArgumentList,
pub semi_nl: SemiNl,
}
implement_node!(FunctionHeader);
implement_acceptor_for_branch!(FunctionHeader, kw_function, first_arg, args, semi_nl);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct BeginHeader {
pub kw_begin: KeywordBegin,
/// Note that 'begin' does NOT require a semi or nl afterwards.
/// This is valid: begin echo hi; end
pub semi_nl: Option<SemiNl>,
}
implement_node!(BeginHeader);
implement_acceptor_for_branch!(BeginHeader, kw_begin, semi_nl);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct BlockStatement {
/// A header like for, while, etc.
pub header: BlockStatementHeader,
@@ -860,10 +856,8 @@ pub struct BlockStatement {
/// Arguments and redirections associated with the block.
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(BlockStatement);
implement_acceptor_for_branch!(BlockStatement, header, jobs, end, args_or_redirs);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct BraceStatement {
/// The opening brace, in command position.
pub left_brace: TokenLeftBrace,
@@ -874,16 +868,8 @@ pub struct BraceStatement {
/// Arguments and redirections associated with the block.
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(BraceStatement);
implement_acceptor_for_branch!(
BraceStatement,
left_brace,
jobs,
right_brace,
args_or_redirs
);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct IfClause {
/// The 'if' keyword.
pub kw_if: KeywordIf,
@@ -894,18 +880,14 @@ pub struct IfClause {
/// The body to execute if the condition is true.
pub body: JobList,
}
implement_node!(IfClause);
implement_acceptor_for_branch!(IfClause, kw_if, condition, andor_tail, body);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct ElseifClause {
/// The 'else' keyword.
pub kw_else: KeywordElse,
/// The 'if' clause following it.
pub if_clause: IfClause,
}
implement_node!(ElseifClause);
implement_acceptor_for_branch!(ElseifClause, kw_else, if_clause);
impl CheckParse for ElseifClause {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_token(0).keyword == ParseKeyword::Else
@@ -915,22 +897,20 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(ElseifClauseList, ElseifClause);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct ElseClause {
/// else ; body
pub kw_else: KeywordElse,
pub semi_nl: Option<SemiNl>,
pub body: JobList,
}
implement_node!(ElseClause);
implement_acceptor_for_branch!(ElseClause, kw_else, semi_nl, body);
impl CheckParse for ElseClause {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_token(0).keyword == ParseKeyword::Else
}
}
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct IfStatement {
/// if part
pub if_clause: IfClause,
@@ -943,17 +923,8 @@ pub struct IfStatement {
/// block args / redirs
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(IfStatement);
implement_acceptor_for_branch!(
IfStatement,
if_clause,
elseif_clauses,
else_clause,
end,
args_or_redirs
);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct CaseItem {
/// case \<arguments\> ; body
pub kw_case: KeywordCase,
@@ -961,15 +932,13 @@ pub struct CaseItem {
pub semi_nl: SemiNl,
pub body: JobList,
}
implement_node!(CaseItem);
implement_acceptor_for_branch!(CaseItem, kw_case, arguments, semi_nl, body);
impl CheckParse for CaseItem {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_token(0).keyword == ParseKeyword::Case
}
}
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct SwitchStatement {
/// switch \<argument\> ; body ; end args_redirs
pub kw_switch: KeywordSwitch,
@@ -979,20 +948,10 @@ pub struct SwitchStatement {
pub end: KeywordEnd,
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(SwitchStatement);
implement_acceptor_for_branch!(
SwitchStatement,
kw_switch,
argument,
semi_nl,
cases,
end,
args_or_redirs
);
/// A decorated_statement is a command with a list of arguments_or_redirections, possibly with
/// "builtin" or "command" or "exec"
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct DecoratedStatement {
/// An optional decoration (command, builtin, exec, etc).
pub opt_decoration: Option<DecoratedStatementDecorator>,
@@ -1001,11 +960,9 @@ pub struct DecoratedStatement {
/// Args and redirs
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(DecoratedStatement);
implement_acceptor_for_branch!(DecoratedStatement, opt_decoration, command, args_or_redirs);
/// A not statement like `not true` or `! true`
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct NotStatement {
/// Keyword, either not or exclam.
pub kw: KeywordNot,
@@ -1013,18 +970,14 @@ pub struct NotStatement {
pub variables: VariableAssignmentList,
pub contents: Statement,
}
implement_node!(NotStatement);
implement_acceptor_for_branch!(NotStatement, kw, time, variables, contents);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct JobContinuation {
pub pipe: TokenPipe,
pub newlines: MaybeNewlines,
pub variables: VariableAssignmentList,
pub statement: Statement,
}
implement_node!(JobContinuation);
implement_acceptor_for_branch!(JobContinuation, pipe, newlines, variables, statement);
impl CheckParse for JobContinuation {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_type(0) == ParseTokenType::pipe
@@ -1033,7 +986,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(JobContinuationList, JobContinuation);
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct JobConjunctionContinuation {
/// The && or || token.
pub conjunction: TokenConjunction,
@@ -1041,8 +994,6 @@ pub struct JobConjunctionContinuation {
/// The job itself.
pub job: JobPipeline,
}
implement_node!(JobConjunctionContinuation);
implement_acceptor_for_branch!(JobConjunctionContinuation, conjunction, newlines, job);
impl CheckParse for JobConjunctionContinuation {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
let typ = pop.peek_type(0);
@@ -1053,12 +1004,10 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
/// An andor_job just wraps a job, but requires that the job have an 'and' or 'or' job_decorator.
/// Note this is only used for andor_job_list; jobs that are not part of an andor_job_list are not
/// instances of this.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct AndorJob {
pub job: JobConjunction,
}
implement_node!(AndorJob);
implement_acceptor_for_branch!(AndorJob, job);
impl CheckParse for AndorJob {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
let keyword = pop.peek_token(0).keyword;
@@ -1080,12 +1029,10 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
/// A freestanding_argument_list is equivalent to a normal argument list, except it may contain
/// TOK_END (newlines, and even semicolons, for historical reasons).
/// In practice the tok_ends are ignored by fish code so we do not bother to store them.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Acceptor!)]
pub struct FreestandingArgumentList {
pub arguments: ArgumentList,
}
implement_node!(FreestandingArgumentList);
implement_acceptor_for_branch!(FreestandingArgumentList, arguments);
define_list_node!(JobConjunctionContinuationList, JobConjunctionContinuation);
@@ -1097,12 +1044,10 @@ pub struct FreestandingArgumentList {
define_list_node!(CaseItemList, CaseItem);
/// A variable_assignment contains a source range like FOO=bar.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Leaf!)]
pub struct VariableAssignment {
range: Option<SourceRange>,
}
implement_node!(VariableAssignment);
implement_leaf!(VariableAssignment);
impl CheckParse for VariableAssignment {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
// Do we have a variable assignment at all?
@@ -1124,21 +1069,17 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
}
/// Zero or more newlines.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Leaf!)]
pub struct MaybeNewlines {
range: Option<SourceRange>,
}
implement_node!(MaybeNewlines);
implement_leaf!(MaybeNewlines);
/// An argument is just a node whose source range determines its contents.
/// This is a separate type because it is sometimes useful to find all arguments.
#[derive(Default, Debug)]
#[derive(Default, Debug, Node!, Leaf!)]
pub struct Argument {
range: Option<SourceRange>,
}
implement_node!(Argument);
implement_leaf!(Argument);
impl CheckParse for Argument {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_type(0) == ParseTokenType::string
@@ -1226,14 +1167,13 @@ pub fn decoration(&self) -> StatementDecoration {
}
}
#[derive(Debug)]
#[derive(Debug, Node!)]
pub enum BlockStatementHeader {
Begin(BeginHeader),
For(ForHeader),
While(WhileHeader),
Function(FunctionHeader),
}
implement_node!(BlockStatementHeader);
impl Default for BlockStatementHeader {
fn default() -> Self {
@@ -1507,23 +1447,23 @@ pub fn dump(&self, orig: &wstr) -> WString {
if let Kind::Argument(n) = node.kind() {
result += "argument";
if let Some(argsrc) = n.try_source(orig) {
sprintf!(=> &mut result, ": '%ls'", argsrc);
sprintf!(=> &mut result, ": '%s'", argsrc);
}
} else if let Some(n) = node.as_keyword() {
sprintf!(=> &mut result, "keyword: %ls", n.keyword().to_wstr());
sprintf!(=> &mut result, "keyword: %s", n.keyword().to_wstr());
} else if let Some(n) = node.as_token() {
let desc = match n.token_type() {
ParseTokenType::string => {
let mut desc = WString::from_str("string");
if let Some(strsource) = n.try_source(orig) {
sprintf!(=> &mut desc, ": '%ls'", strsource);
sprintf!(=> &mut desc, ": '%s'", strsource);
}
desc
}
ParseTokenType::redirection => {
let mut desc = WString::from_str("redirection");
if let Some(strsource) = n.try_source(orig) {
sprintf!(=> &mut desc, ": '%ls'", strsource);
sprintf!(=> &mut desc, ": '%s'", strsource);
}
desc
}
@@ -1864,7 +1804,7 @@ fn visit_mut<N: NodeMut>(&mut self, node: &mut N) -> VisitResult {
fn will_visit_fields_of<N: NodeMut>(&mut self, node: &mut N) {
FLOGF!(
ast_construction,
"%*swill_visit %ls",
"%*swill_visit %s",
self.spaces(),
"",
node.describe()
@@ -1941,7 +1881,7 @@ fn did_visit_fields_of<'a, N: NodeMut>(&'a mut self, node: &'a mut N, flow: Visi
self,
header_kw_range,
ParseErrorCode::generic,
"Missing end to balance this %ls",
"Missing end to balance this %s",
enclosing_stmt
);
} else {
@@ -1949,7 +1889,7 @@ fn did_visit_fields_of<'a, N: NodeMut>(&'a mut self, node: &'a mut N, flow: Visi
self,
token,
ParseErrorCode::generic,
"Expected %ls, but found %ls",
"Expected %s, but found %s",
keywords_user_presentable_description(error.allowed_keywords),
error.token.user_presentable_description(),
);
@@ -1967,14 +1907,14 @@ fn visit_optional_mut<N: NodeMut + CheckParse>(&mut self, node: &mut Option<N>)
fn keywords_user_presentable_description(kws: &'static [ParseKeyword]) -> WString {
assert!(!kws.is_empty(), "Should not be empty list");
if kws.len() == 1 {
return sprintf!("keyword '%ls'", kws[0]);
return sprintf!("keyword '%s'", kws[0]);
}
let mut res = L!("keywords ").to_owned();
for (i, kw) in kws.iter().enumerate() {
if i != 0 {
res += L!(" or ");
}
res += &sprintf!("'%ls'", *kw)[..];
res += &sprintf!("'%s'", *kw)[..];
}
res
}
@@ -2092,7 +2032,7 @@ fn list_kind_chomps_newlines(&self, kind: Kind) -> bool {
internal_error!(
self,
list_kind_chomps_newlines,
"Type %ls not handled",
"Type %s not handled",
ast_kind_to_string(kind)
);
}
@@ -2142,7 +2082,7 @@ fn list_kind_chomps_semis(&self, kind: Kind) -> bool {
internal_error!(
self,
list_kind_chomps_semis,
"Type %ls not handled",
"Type %s not handled",
ast_kind_to_string(kind)
);
}
@@ -2215,7 +2155,7 @@ fn consume_token_type(&mut self, typ: ParseTokenType) -> SourceRange {
self,
tok,
ParseErrorCode::generic,
"Expected %ls, but found %ls",
"Expected %s, but found %s",
token_type_user_presentable_description(typ, ParseKeyword::None),
tok.user_presentable_description()
);
@@ -2240,7 +2180,7 @@ fn consume_excess_token_generating_error(&mut self) {
self,
tok,
ParseErrorCode::generic,
"Expected %ls, but found %ls",
"Expected %s, but found %s",
token_type_user_presentable_description(ParseTokenType::string, ParseKeyword::None),
tok.user_presentable_description()
);
@@ -2279,7 +2219,7 @@ fn consume_excess_token_generating_error(&mut self) {
internal_error!(
self,
consume_excess_token_generating_error,
"Token %ls should not have prevented parsing a job list",
"Token %s should not have prevented parsing a job list",
tok.user_presentable_description()
);
}
@@ -2304,7 +2244,7 @@ fn consume_excess_token_generating_error(&mut self) {
self,
tok,
ParseErrorCode::generic,
"Expected a string, but found %ls",
"Expected a string, but found %s",
tok.user_presentable_description()
);
}
@@ -2313,7 +2253,7 @@ fn consume_excess_token_generating_error(&mut self) {
self,
tok,
ParseErrorCode::from(tok.tok_error),
"%ls",
"%s",
tok.tok_error
);
}
@@ -2335,7 +2275,7 @@ fn consume_excess_token_generating_error(&mut self) {
internal_error!(
self,
consume_excess_token_generating_error,
"Unexpected excess token type: %ls",
"Unexpected excess token type: %s",
tok.user_presentable_description()
);
}
@@ -2361,7 +2301,7 @@ fn populate_list<Contents, List>(&mut self, list: &mut List, exhaust_stream: boo
// Mark in the list that it was unwound.
FLOGF!(
ast_construction,
"%*sunwinding %ls",
"%*sunwinding %s",
self.spaces(),
"",
ast_kind_to_string(list.kind())
@@ -2439,7 +2379,7 @@ fn populate_list<Contents, List>(&mut self, list: &mut List, exhaust_stream: boo
FLOGF!(
ast_construction,
"%*s%ls size: %lu",
"%*s%s size: %u",
self.spaces(),
"",
ast_kind_to_string(list.kind()),
@@ -2463,7 +2403,7 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> Statement {
slf,
slf.peek_token(0),
ParseErrorCode::generic,
"Expected %s, but found %ls",
"Expected %s, but found %s",
token_type_user_presentable_description(
ParseTokenType::end,
ParseKeyword::None
@@ -2488,7 +2428,7 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> Statement {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected a command, but found %ls",
"Expected a command, but found %s",
self.peek_token(0).user_presentable_description()
);
return got_error(self);
@@ -2577,7 +2517,7 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> Statement {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected a command, but found %ls",
"Expected a command, but found %s",
self.peek_token(0).user_presentable_description()
);
return got_error(self);
@@ -2719,7 +2659,7 @@ fn visit_token(&mut self, token: &mut dyn Token) {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected %ls, but found %ls",
"Expected %s, but found %s",
token_types_user_presentable_description(token.allowed_tokens()),
self.peek_token(0).user_presentable_description()
);
@@ -2763,7 +2703,7 @@ fn visit_keyword(&mut self, keyword: &mut dyn Keyword) -> VisitResult {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected %ls, but found %ls",
"Expected %s, but found %s",
keywords_user_presentable_description(allowed_keywords),
self.peek_token(0).user_presentable_description(),
);

View File

@@ -116,12 +116,12 @@ pub fn resolve_command(&mut self, cmd: &wstr, env: &dyn Environment) -> Option<A
match &path {
#[cfg(feature = "embed-data")]
AutoloadPath::Embedded(_) => {
FLOGF!(autoload, "Embedded: %ls", cmd);
FLOGF!(autoload, "Embedded: %s", cmd);
}
AutoloadPath::Path(path) => {
FLOGF!(
autoload,
"Loading %ls from var %ls from path %ls",
"Loading %s from var %s from path %s",
cmd,
self.env_var_name,
path
@@ -152,14 +152,14 @@ pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) {
AutoloadPath::Embedded(name) => {
use crate::common::str2wcstring;
use std::sync::Arc;
FLOGF!(autoload, "Loading embedded: %ls", name);
FLOGF!(autoload, "Loading embedded: %s", name);
let emfile = Asset::get(name).expect("Embedded file not found");
let src = str2wcstring(&emfile.data);
let mut widename = L!("embedded:").to_owned();
widename.push_str(name);
let ret = parser.eval_file_wstr(src, Arc::new(widename), &IoChain::new(), None);
if let Err(msg) = ret {
eprintf!("%ls", msg);
eprintf!("%s", msg);
}
}
}
@@ -527,8 +527,8 @@ fn touch_file(path: &wstr) {
.is_none());
assert!(autoload.get_autoloaded_commands().is_empty());
run!("touch %ls/file1.fish", p1);
run!("touch %ls/file2.fish", p2);
run!("touch %s/file1.fish", p1);
run!("touch %s/file2.fish", p2);
autoload.invalidate_cache();
assert!(!autoload.autoload_in_progress(L!("file1")));
@@ -586,11 +586,11 @@ fn touch_file(path: &wstr) {
autoload.resolve_command_impl(L!("file1"), paths),
AutoloadResult::Loaded
));
touch_file(&sprintf!("%ls/file1.fish", p1));
touch_file(&sprintf!("%s/file1.fish", p1));
autoload.invalidate_cache();
assert!(autoload.resolve_command_impl(L!("file1"), paths).is_some());
autoload.mark_autoload_finished(L!("file1"));
run!(L!("rm -Rf %ls"), p1);
run!(L!("rm -Rf %ls"), p2);
run!(L!("rm -Rf %s"), p1);
run!(L!("rm -Rf %s"), p2);
}

View File

@@ -156,7 +156,7 @@ fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool {
if waccess(&config_pathname, libc::R_OK) != 0 {
FLOGF!(
config,
"not sourcing %ls (not readable or does not exist)",
"not sourcing %s (not readable or does not exist)",
escaped_pathname
);
return false;
@@ -182,7 +182,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
let ret = parser.eval_file_wstr(src, fname, &IoChain::new(), None);
parser.libdata_mut().within_fish_init = false;
if let Err(msg) = ret {
eprintf!("%ls", msg);
eprintf!("%s", msg);
}
}
#[cfg(not(feature = "embed-data"))]
@@ -196,7 +196,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
let escaped_pathname = escape(&datapath);
FLOGF!(
error,
"Fish cannot find its asset files in '%ls'.\n\
"Fish cannot find its asset files in '%s'.\n\
Refusing to read configuration because of this.",
escaped_pathname,
);
@@ -287,7 +287,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
activate_flog_categories_by_pattern(w.woptarg.unwrap());
for cat in flog::categories::all_categories() {
if cat.enabled.load(Ordering::Relaxed) {
printf!("Debug enabled for category: %ls\n", cat.name);
printf!("Debug enabled for category: %s\n", cat.name);
}
}
}
@@ -315,7 +315,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
for cat in cats.iter() {
let desc = cat.description.localize();
// this is left-justified
printf!("%-*ls %ls\n", name_width, cat.name, desc);
printf!("%-*s %s\n", name_width, cat.name, desc);
}
return ControlFlow::Break(0);
}
@@ -342,21 +342,21 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
}
'?' => {
eprintf!(
"%ls\n",
"%s\n",
wgettext_fmt!(BUILTIN_ERR_UNKNOWN, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
}
':' => {
eprintf!(
"%ls\n",
"%s\n",
wgettext_fmt!(BUILTIN_ERR_MISSING, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
}
';' => {
eprintf!(
"%ls\n",
"%s\n",
wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
@@ -623,7 +623,7 @@ fn throwing_main() -> i32 {
if res.is_err() {
FLOGF!(
warning,
wgettext!("Error while reading file %ls\n"),
wgettext!("Error while reading file %s\n"),
path.to_string_lossy()
);
}
@@ -637,10 +637,7 @@ fn throwing_main() -> i32 {
parser.get_last_status()
};
event::fire(
parser,
Event::process_exit(Pid::new(getpid()).unwrap(), exit_status),
);
event::fire(parser, Event::process_exit(Pid::new(getpid()), exit_status));
// Trigger any exit handlers.
event::fire_generic(

View File

@@ -48,7 +48,7 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
if cmds.len() > 1 {
streams.err.append(wgettext_fmt!(
"%ls: Cannot combine options %ls\n",
"%s: Cannot combine options %s\n",
CMD,
join(&cmds, L!(", "))
));
@@ -63,28 +63,26 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
}
if !self.add && self.position.is_some() {
streams.err.append(wgettext_fmt!(
"%ls: --position option requires --add\n",
CMD
));
streams
.err
.append(wgettext_fmt!("%s: --position option requires --add\n", CMD));
return false;
}
if !self.add && self.regex_pattern.is_some() {
streams
.err
.append(wgettext_fmt!("%ls: --regex option requires --add\n", CMD));
.append(wgettext_fmt!("%s: --regex option requires --add\n", CMD));
return false;
}
if !self.add && self.function.is_some() {
streams.err.append(wgettext_fmt!(
"%ls: --function option requires --add\n",
CMD
));
streams
.err
.append(wgettext_fmt!("%s: --function option requires --add\n", CMD));
return false;
}
if !self.add && self.set_cursor_marker.is_some() {
streams.err.append(wgettext_fmt!(
"%ls: --set-cursor option requires --add\n",
"%s: --set-cursor option requires --add\n",
CMD
));
return false;
@@ -96,7 +94,7 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
.unwrap_or(false)
{
streams.err.append(wgettext_fmt!(
"%ls: --set-cursor argument cannot be empty\n",
"%s: --set-cursor argument cannot be empty\n",
CMD
));
return false;
@@ -182,7 +180,7 @@ fn abbr_list(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
const subcmd: &wstr = L!("--list");
if !opts.args.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls %ls: Unexpected argument -- '%ls'\n",
"%s %s: Unexpected argument -- '%s'\n",
CMD,
subcmd,
&opts.args[0]
@@ -206,7 +204,7 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
if opts.args.len() != 2 {
streams.err.append(wgettext_fmt!(
"%ls %ls: Requires exactly two arguments\n",
"%s %s: Requires exactly two arguments\n",
CMD,
subcmd
));
@@ -215,17 +213,15 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
let old_name = &opts.args[0];
let new_name = &opts.args[1];
if old_name.is_empty() || new_name.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls %ls: Name cannot be empty\n",
CMD,
subcmd
));
streams
.err
.append(wgettext_fmt!("%s %s: Name cannot be empty\n", CMD, subcmd));
return Err(STATUS_INVALID_ARGS);
}
if contains_whitespace(new_name) {
streams.err.append(wgettext_fmt!(
"%ls %ls: Abbreviation '%ls' cannot have spaces in the word\n",
"%s %s: Abbreviation '%s' cannot have spaces in the word\n",
CMD,
subcmd,
new_name.as_utfstr()
@@ -235,7 +231,7 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
abbrs::with_abbrs_mut(|abbrs| -> BuiltinResult {
if !abbrs.has_name(old_name) {
streams.err.append(wgettext_fmt!(
"%ls %ls: No abbreviation named %ls\n",
"%s %s: No abbreviation named %s\n",
CMD,
subcmd,
old_name.as_utfstr()
@@ -244,7 +240,7 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
if abbrs.has_name(new_name) {
streams.err.append(wgettext_fmt!(
"%ls %ls: Abbreviation %ls already exists, cannot rename %ls\n",
"%s %s: Abbreviation %s already exists, cannot rename %s\n",
CMD,
subcmd,
new_name.as_utfstr(),
@@ -280,7 +276,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
if opts.args.len() < 2 && opts.function.is_none() {
streams.err.append(wgettext_fmt!(
"%ls %ls: Requires at least two arguments\n",
"%s %s: Requires at least two arguments\n",
CMD,
subcmd
));
@@ -288,17 +284,15 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
if opts.args.is_empty() || opts.args[0].is_empty() {
streams.err.append(wgettext_fmt!(
"%ls %ls: Name cannot be empty\n",
CMD,
subcmd
));
streams
.err
.append(wgettext_fmt!("%s %s: Name cannot be empty\n", CMD, subcmd));
return Err(STATUS_INVALID_ARGS);
}
let name = &opts.args[0];
if name.chars().any(|c| c.is_whitespace()) {
streams.err.append(wgettext_fmt!(
"%ls %ls: Abbreviation '%ls' cannot have spaces in the word\n",
"%s %s: Abbreviation '%s' cannot have spaces in the word\n",
CMD,
subcmd,
name.as_utfstr()
@@ -319,17 +313,15 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
if let Err(error) = result {
streams.err.append(wgettext_fmt!(
"%ls: Regular expression compile error: %ls\n",
"%s: Regular expression compile error: %s\n",
CMD,
error.error_message(),
));
if let Some(offset) = error.offset() {
streams
.err
.append(wgettext_fmt!("%ls: %ls\n", CMD, regex_pattern.as_utfstr()));
streams
.err
.append(sprintf!("%ls: %*ls\n", CMD, offset, "^"));
.append(wgettext_fmt!("%s: %s\n", CMD, regex_pattern.as_utfstr()));
streams.err.append(sprintf!("%s: %*s\n", CMD, offset, "^"));
}
return Err(STATUS_INVALID_ARGS);
}
@@ -359,7 +351,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
// This is to prevent accidental usage of e.g. `--function 'string replace'`
if !valid_func_name(function) || contains_whitespace(function) {
streams.err.append(wgettext_fmt!(
"%ls: Invalid function name: %ls\n",
"%s: Invalid function name: %s\n",
CMD,
function.as_utfstr()
));
@@ -387,7 +379,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
});
if !opts.commands.is_empty() && position == Position::Command {
streams.err.appendln(wgettext_fmt!(
"%ls: --command cannot be combined with --position command",
"%s: --command cannot be combined with --position=command",
CMD,
));
return Err(STATUS_INVALID_ARGS);
@@ -501,7 +493,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'p' => {
if opts.position.is_some() {
streams.err.append(wgettext_fmt!(
"%ls: Cannot specify multiple positions\n",
"%s: Cannot specify multiple positions\n",
CMD
));
return Err(STATUS_INVALID_ARGS);
@@ -512,7 +504,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
opts.position = Some(Position::Anywhere);
} else {
streams.err.append(wgettext_fmt!(
"%ls: Invalid position '%ls'\n",
"%s: Invalid position '%s'\n",
CMD,
w.woptarg.unwrap_or_default()
));
@@ -525,7 +517,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'r' => {
if opts.regex_pattern.is_some() {
streams.err.append(wgettext_fmt!(
"%ls: Cannot specify multiple regex patterns\n",
"%s: Cannot specify multiple regex patterns\n",
CMD
));
return Err(STATUS_INVALID_ARGS);
@@ -535,7 +527,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
SET_CURSOR_SHORT => {
if opts.set_cursor_marker.is_some() {
streams.err.append(wgettext_fmt!(
"%ls: Cannot specify multiple set-cursor options\n",
"%s: Cannot specify multiple set-cursor options\n",
CMD
));
return Err(STATUS_INVALID_ARGS);
@@ -558,7 +550,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'U' => {
// Kept and made ineffective, so we warn.
streams.err.append(wgettext_fmt!(
"%ls: Warning: Option '%ls' was removed and is now ignored",
"%s: Warning: Option '%s' was removed and is now ignored",
cmd,
argv_read[w.wopt_index - 1]
));

View File

@@ -10,7 +10,7 @@
localizable_consts!(
BUILTIN_ERR_INVALID_OPT_SPEC
"%ls: Invalid option spec '%ls' at char '%lc'\n"
"%s: Invalid option spec '%s' at char '%c'\n"
);
#[derive(Default)]
@@ -146,7 +146,7 @@ fn check_for_mutually_exclusive_flags(
std::mem::swap(&mut flag1, &mut flag2);
}
streams.err.append(wgettext_fmt!(
"%ls: %ls %ls: options cannot be used together\n",
"%s: %s %s: options cannot be used together\n",
opts.name,
flag1,
flag2
@@ -168,7 +168,7 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
let xflags: Vec<_> = raw_xflags.split(',').collect();
if xflags.len() < 2 {
streams.err.append(wgettext_fmt!(
"%ls: exclusive flag string '%ls' is not valid\n",
"%s: exclusive flag string '%s' is not valid\n",
opts.name,
raw_xflags
));
@@ -186,7 +186,7 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
exclusive_set.push(*short_equiv);
} else {
streams.err.append(wgettext_fmt!(
"%ls: exclusive flag '%ls' is not valid\n",
"%s: exclusive flag '%s' is not valid\n",
opts.name,
flag
));
@@ -215,7 +215,7 @@ fn parse_flag_modifiers<'args>(
&& s.char_at(0) != '&'
{
streams.err.append(wgettext_fmt!(
"%ls: Implicit int short flag '%lc' does not allow modifiers like '%lc'\n",
"%s: Implicit int short flag '%c' does not allow modifiers like '%c'\n",
opts.name,
opt_spec.short_flag,
s.char_at(0)
@@ -277,7 +277,7 @@ fn parse_flag_modifiers<'args>(
if opts.options.contains_key(&opt_spec.short_flag) {
streams.err.append(wgettext_fmt!(
"%ls: Short flag '%lc' already defined\n",
"%s: Short flag '%c' already defined\n",
opts.name,
opt_spec.short_flag
));
@@ -309,7 +309,7 @@ fn parse_option_spec_sep<'args>(
}
if opts.implicit_int_flag != '\0' {
streams.err.append(wgettext_fmt!(
"%ls: Implicit int flag '%lc' already defined\n",
"%s: Implicit int flag '%c' already defined\n",
opts.name,
opts.implicit_int_flag
));
@@ -351,7 +351,7 @@ fn parse_option_spec_sep<'args>(
'#' => {
if opts.implicit_int_flag != '\0' {
streams.err.append(wgettext_fmt!(
"%ls: Implicit int flag '%lc' already defined\n",
"%s: Implicit int flag '%c' already defined\n",
opts.name,
opts.implicit_int_flag
));
@@ -395,7 +395,7 @@ fn parse_option_spec<'args>(
) -> bool {
if option_spec.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls: An option spec must have at least a short or a long flag\n",
"%s: An option spec must have at least a short or a long flag\n",
opts.name
));
return false;
@@ -405,7 +405,7 @@ fn parse_option_spec<'args>(
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' && !(s.char_at(0) == '/' && s.len() > 1)
{
streams.err.append(wgettext_fmt!(
"%ls: Short flag '%lc' invalid, must be alphanum or '#'\n",
"%s: Short flag '%c' invalid, must be alphanum or '#'\n",
opts.name,
s.char_at(0)
));
@@ -432,7 +432,7 @@ fn parse_option_spec<'args>(
opt_spec.long_flag = s.slice_to(long_flag_char_count);
if opts.long_to_short_flag.contains_key(opt_spec.long_flag) {
streams.err.append(wgettext_fmt!(
"%ls: Long flag '%ls' already defined\n",
"%s: Long flag '%s' already defined\n",
opts.name,
opt_spec.long_flag
));
@@ -480,7 +480,7 @@ fn collect_option_specs<'args>(
if *optind == argc {
streams
.err
.append(wgettext_fmt!("%ls: Missing -- separator\n", cmd));
.append(wgettext_fmt!("%s: Missing -- separator\n", cmd));
return Err(STATUS_INVALID_ARGS);
}
@@ -502,7 +502,7 @@ fn collect_option_specs<'args>(
if counter > counter_max {
streams
.err
.append(wgettext_fmt!("%ls: Too many long-only options\n", cmd));
.append(wgettext_fmt!("%s: Too many long-only options\n", cmd));
return Err(STATUS_INVALID_ARGS);
}
@@ -557,7 +557,7 @@ fn parse_cmd_opts<'args>(
ArgType::NoArgument
} else {
streams.err.append(wgettext_fmt!(
"%ls: Invalid --unknown-arguments value '%ls'\n",
"%s: Invalid --unknown-arguments value '%s'\n",
cmd,
kind
));
@@ -574,7 +574,7 @@ fn parse_cmd_opts<'args>(
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.append(wgettext_fmt!(
"%ls: Invalid --min-args value '%ls'\n",
"%s: Invalid --min-args value '%s'\n",
cmd,
w.woptarg.unwrap()
));
@@ -588,7 +588,7 @@ fn parse_cmd_opts<'args>(
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.append(wgettext_fmt!(
"%ls: Invalid --max-args value '%ls'\n",
"%s: Invalid --max-args value '%s'\n",
cmd,
w.woptarg.unwrap()
));
@@ -642,7 +642,7 @@ fn parse_cmd_opts<'args>(
// The user didn't specify any option specs.
streams
.err
.append(wgettext_fmt!("%ls: Missing -- separator\n", cmd));
.append(wgettext_fmt!("%s: Missing -- separator\n", cmd));
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -1,5 +1,7 @@
// Implementation of the bg builtin.
use std::collections::HashSet;
use crate::proc::Pid;
use super::prelude::*;
@@ -16,7 +18,7 @@ fn send_to_bg(
if !jobs[job_pos].wants_job_control() {
let job = &jobs[job_pos];
streams.err.append(wgettext_fmt!(
"%ls: Can't put job %s, '%ls' to background because it is not under job control\n",
"%s: Can't put job %s, '%s' to background because it is not under job control\n",
cmd,
job.job_id().to_wstring(),
job.command()
@@ -26,7 +28,7 @@ fn send_to_bg(
let job = &jobs[job_pos];
streams.err.append(wgettext_fmt!(
"Send job %s '%ls' to background\n",
"Send job %s '%s' to background\n",
job.job_id().to_wstring(),
job.command()
));
@@ -66,7 +68,7 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
let Some(job_pos) = job_pos else {
streams
.err
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
.append(wgettext_fmt!("%s: There are no suitable jobs\n", cmd));
return Err(STATUS_CMD_ERROR);
};
@@ -79,14 +81,9 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
let mut retval: BuiltinResult = Ok(SUCCESS);
let pids: Vec<Pid> = args[opts.optind..]
.iter()
.filter_map(|arg| match fish_wcstoi(arg).map(Pid::new) {
Ok(Some(pid)) => Some(pid),
.filter_map(|arg| match parse_pid(streams, cmd, arg) {
Ok(pid) => Some(pid),
_ => {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n",
cmd,
arg
));
retval = Err(STATUS_INVALID_ARGS);
None
}
@@ -97,13 +94,16 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
// Background all existing jobs that match the pids.
// Non-existent jobs aren't an error, but information about them is useful.
let mut seen = HashSet::new();
for pid in pids {
if let Some((job_pos, _job)) = parser.job_get_with_index_from_pid(pid) {
send_to_bg(parser, streams, cmd, job_pos)?;
if let Some((job_pos, job)) = parser.job_get_with_index_from_pid(pid) {
if seen.insert(&*job as *const _) {
send_to_bg(parser, streams, cmd, job_pos)?;
}
} else {
streams
.err
.append(wgettext_fmt!("%ls: Could not find job '%d'\n", cmd, pid));
.append(wgettext_fmt!("%s: Could not find job '%d'\n", cmd, pid));
}
}

View File

@@ -350,13 +350,13 @@ fn insert(
if !self.opts.silent {
if seq.len() == 1 {
streams.err.append(wgettext_fmt!(
"%ls: No binding found for key '%ls'\n",
"%s: No binding found for key '%s'\n",
cmd,
seq[0]
));
} else {
streams.err.append(wgettext_fmt!(
"%ls: No binding found for key sequence '%ls'\n",
"%s: No binding found for key sequence '%s'\n",
cmd,
eseq
));
@@ -432,7 +432,7 @@ fn parse_cmd_opts(
'h' => opts.print_help = true,
'k' => {
streams.err.append(wgettext_fmt!(
"%ls: the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`\n",
"%s: the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`\n",
cmd,
));
return Err(STATUS_INVALID_ARGS);
@@ -555,7 +555,7 @@ pub fn bind(
_ => {
streams
.err
.append(wgettext_fmt!("%ls: Invalid state\n", cmd));
.append(wgettext_fmt!("%s: Invalid state\n", cmd));
return Err(STATUS_CMD_ERROR);
}
}

View File

@@ -90,7 +90,7 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
if opts.erase {
if opts.scope != Scope::Unset {
streams.err.append(wgettext_fmt!(
"%ls: Can not specify scope when removing block\n",
"%s: Can not specify scope when removing block\n",
cmd
));
return Err(STATUS_INVALID_ARGS);
@@ -99,7 +99,7 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
if parser.global_event_blocks.load(Ordering::Relaxed) == 0 {
streams
.err
.append(wgettext_fmt!("%ls: No blocks defined\n", cmd));
.append(wgettext_fmt!("%s: No blocks defined\n", cmd));
return Err(STATUS_CMD_ERROR);
}
parser.global_event_blocks.fetch_sub(1, Ordering::Relaxed);

5
src/builtins/break.rs Normal file
View File

@@ -0,0 +1,5 @@
use super::prelude::*;
pub fn r#break(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
builtin_break_continue(parser, streams, argv)
}

View File

@@ -0,0 +1,44 @@
use super::prelude::*;
use crate::parser::{Block, BlockType};
use crate::reader::reader_read;
use libc::STDIN_FILENO;
/// Implementation of the builtin breakpoint command, used to launch the interactive debugger.
pub fn breakpoint(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
let cmd = argv[0];
if argv.len() != 1 {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT1,
cmd,
0,
argv.len() - 1
));
return Err(STATUS_INVALID_ARGS);
}
// If we're not interactive then we can't enter the debugger. So treat this command as a no-op.
if !parser.is_interactive() {
return Err(STATUS_CMD_ERROR);
}
// Ensure we don't allow creating a breakpoint at an interactive prompt. There may be a simpler
// or clearer way to do this but this works.
{
if parser
.block_at_index(1)
.map_or(true, |b| b.typ() == BlockType::breakpoint)
{
streams.err.append(wgettext_fmt!(
"%s: Command not valid at an interactive prompt\n",
cmd,
));
return Err(STATUS_ILLEGAL_CMD);
}
}
let bpb = parser.push_block(Block::breakpoint_block());
let io_chain = &streams.io_chain;
reader_read(parser, STDIN_FILENO, io_chain)?;
parser.pop_block(bpb);
BuiltinResult::from_dynamic(parser.get_last_status())
}

View File

@@ -39,7 +39,7 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
None => {
streams
.err
.append(wgettext_fmt!("%ls: Could not find home directory\n", cmd));
.append(wgettext_fmt!("%s: Could not find home directory\n", cmd));
return Err(STATUS_CMD_ERROR);
}
}
@@ -48,7 +48,7 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
// Stop `cd ""` from crashing
if dir_in.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls: Empty directory '%ls' does not exist\n",
"%s: Empty directory '%s' does not exist\n",
cmd,
dir_in
));
@@ -63,7 +63,7 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
let dirs = path_apply_cdpath(dir_in, &pwd, vars);
if dirs.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls: The directory '%ls' does not exist\n",
"%s: The directory '%s' does not exist\n",
cmd,
dir_in
));
@@ -132,41 +132,37 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
}
if best_errno == ENOTDIR {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a directory\n",
cmd,
dir_in
));
streams
.err
.append(wgettext_fmt!("%s: '%s' is not a directory\n", cmd, dir_in));
} else if !broken_symlink.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is a broken symbolic link to '%ls'\n",
"%s: '%s' is a broken symbolic link to '%s'\n",
cmd,
broken_symlink,
broken_symlink_target
));
} else if best_errno == ELOOP {
streams.err.append(wgettext_fmt!(
"%ls: Too many levels of symbolic links: '%ls'\n",
"%s: Too many levels of symbolic links: '%s'\n",
cmd,
dir_in
));
} else if best_errno == ENOENT {
streams.err.append(wgettext_fmt!(
"%ls: The directory '%ls' does not exist\n",
"%s: The directory '%s' does not exist\n",
cmd,
dir_in
));
} else if best_errno == EACCES || best_errno == EPERM {
streams.err.append(wgettext_fmt!(
"%ls: Permission denied: '%ls'\n",
cmd,
dir_in
));
streams
.err
.append(wgettext_fmt!("%s: Permission denied: '%s'\n", cmd, dir_in));
} else {
errno::set_errno(Errno(best_errno));
wperror(L!("cd"));
streams.err.append(wgettext_fmt!(
"%ls: Unknown error trying to locate directory '%ls'\n",
"%s: Unknown error trying to locate directory '%s'\n",
cmd,
dir_in
));

View File

@@ -393,7 +393,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(cmd) = input_function_get_code(arg) else {
streams
.err
.append(wgettext_fmt!("%ls: Unknown input function '%ls'", cmd, arg));
.append(wgettext_fmt!("%s: Unknown input function '%s'", cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
return Err(STATUS_INVALID_ARGS);
};
@@ -517,7 +517,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Ok(new_coord) = usize::try_from(new_coord) else {
streams
.err
.append(wgettext_fmt!("%ls: line/column index starts at 1", cmd));
.append(wgettext_fmt!("%s: line/column index starts at 1", cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
return Err(STATUS_INVALID_ARGS);
};
@@ -529,7 +529,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
) else {
streams
.err
.append(wgettext_fmt!("%ls: there is no line %ls\n", cmd, arg));
.append(wgettext_fmt!("%s: there is no line %s\n", cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
return Err(STATUS_INVALID_ARGS);
};
@@ -544,7 +544,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
.unwrap_or(rstate.text.len());
if line_offset + new_coord > next_line_offset {
streams.err.append(wgettext_fmt!(
"%ls: column %ls exceeds line length\n",
"%s: column %s exceeds line length\n",
cmd,
arg
));
@@ -601,7 +601,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(selection) = rstate.selection else {
return Err(STATUS_CMD_ERROR);
};
streams.out.append(sprintf!("%lu\n", selection.start));
streams.out.append(sprintf!("%u\n", selection.start));
return Ok(SUCCESS);
}
@@ -609,7 +609,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(selection) = rstate.selection else {
return Err(STATUS_CMD_ERROR);
};
streams.out.append(sprintf!("%lu\n", selection.end));
streams.out.append(sprintf!("%u\n", selection.end));
return Ok(SUCCESS);
}
@@ -716,7 +716,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
} else {
streams
.out
.append(sprintf!("%lu\n", current_cursor_pos - range.start));
.append(sprintf!("%u\n", current_cursor_pos - range.start));
}
return Ok(SUCCESS);
}

View File

@@ -296,7 +296,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
}
} else {
streams.err.append(wgettext_fmt!(
"%ls: Invalid token '%ls'\n",
"%s: Invalid token '%s'\n",
cmd,
w.woptarg.unwrap()
));
@@ -318,7 +318,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if arg.is_empty() {
streams
.err
.append(wgettext_fmt!("%ls: -s requires a non-empty string\n", cmd,));
.append(wgettext_fmt!("%s: -s requires a non-empty string\n", cmd,));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -328,7 +328,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if arg.is_empty() {
streams
.err
.append(wgettext_fmt!("%ls: -l requires a non-empty string\n", cmd,));
.append(wgettext_fmt!("%s: -l requires a non-empty string\n", cmd,));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -338,7 +338,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if arg.is_empty() {
streams
.err
.append(wgettext_fmt!("%ls: -o requires a non-empty string\n", cmd,));
.append(wgettext_fmt!("%s: -o requires a non-empty string\n", cmd,));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -442,7 +442,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if let Err(err_text) = parse_util_detect_errors_in_argument_list(&comp, &prefix) {
streams.err.append(wgettext_fmt!(
"%ls: %ls: contains a syntax error\n",
"%s: %s: contains a syntax error\n",
cmd,
comp
));

View File

@@ -73,7 +73,7 @@ pub fn contains(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
} else {
streams
.err
.append(wgettext_fmt!("%ls: Key not specified\n", cmd));
.append(wgettext_fmt!("%s: Key not specified\n", cmd));
}
return Err(STATUS_CMD_ERROR);

5
src/builtins/continue.rs Normal file
View File

@@ -0,0 +1,5 @@
use super::prelude::*;
pub fn r#continue(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
builtin_break_continue(parser, streams, argv)
}

View File

@@ -3,12 +3,8 @@
use super::prelude::*;
use crate::io::IoStreams;
use crate::parser::Parser;
use crate::proc::{add_disowned_job, Job, Pid};
use crate::{
builtins::shared::HelpOnlyCmdOpts,
wchar::wstr,
wutil::{fish_wcstoi, wgettext_fmt},
};
use crate::proc::{add_disowned_job, Job};
use crate::{builtins::shared::HelpOnlyCmdOpts, wchar::wstr, wutil::wgettext_fmt};
use libc::SIGCONT;
/// Helper for builtin_disown.
@@ -27,7 +23,7 @@ fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) {
}
}
streams.err.append(wgettext_fmt!(
"%ls: job %d ('%ls') was stopped and has been signalled to continue.\n",
"%s: job %d ('%s') was stopped and has been signalled to continue.\n",
cmd,
j.job_id(),
j.command()
@@ -71,7 +67,7 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
} else {
streams
.err
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
.append(wgettext_fmt!("%s: There are no suitable jobs\n", cmd));
retval = Err(STATUS_CMD_ERROR);
}
} else {
@@ -80,31 +76,23 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
// If one argument is not a valid pid (i.e. integer >= 0), fail without disowning anything,
// but still print errors for all of them.
// Non-existent jobs aren't an error, but information about them is useful.
let mut jobs: Vec<_> = args[1..]
let mut jobs: Vec<_> = args[opts.optind..]
.iter()
.filter_map(|arg| {
// Attempt to convert the argument to a PID.
match fish_wcstoi(arg).ok().and_then(Pid::new) {
None => {
// Invalid identifier
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n",
cmd,
arg
));
retval = Err(STATUS_INVALID_ARGS);
None
let pid = match parse_pid(streams, cmd, arg) {
Ok(pid) => pid,
Err(code) => {
retval = Err(code);
return None;
}
Some(pid) => parser.job_get_from_pid(pid).or_else(|| {
// Valid identifier but no such job
streams.err.append(wgettext_fmt!(
"%ls: Could not find job '%d'\n",
cmd,
pid
));
None
}),
}
};
parser.job_get_from_pid(pid).or_else(|| {
// Valid identifier but no such job
streams
.err
.append(wgettext_fmt!("%s: Could not find job '%d'\n", cmd, pid));
None
})
})
.collect();

View File

@@ -16,7 +16,7 @@ pub fn emit(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
let Some(event_name) = argv.get(opts.optind) else {
streams
.err
.append(sprintf!(L!("%ls: expected event name\n"), cmd));
.append(sprintf!(L!("%s: expected event name\n"), cmd));
return Err(STATUS_INVALID_ARGS);
};

5
src/builtins/false.rs Normal file
View File

@@ -0,0 +1,5 @@
use super::prelude::*;
pub fn r#false(_parser: &Parser, _streams: &mut IoStreams, _argv: &mut [&wstr]) -> BuiltinResult {
Err(STATUS_CMD_ERROR)
}

View File

@@ -1,7 +1,6 @@
//! Implementation of the fg builtin.
use crate::fds::make_fd_blocking;
use crate::proc::Pid;
use crate::reader::{reader_save_screen_state, reader_write_title};
use crate::tokenizer::tok_command;
use crate::wutil::perror;
@@ -38,7 +37,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
None => {
streams
.err
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
.append(wgettext_fmt!("%s: There are no suitable jobs\n", cmd));
return Err(STATUS_INVALID_ARGS);
}
Some((pos, j)) => {
@@ -49,47 +48,35 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
} else if optind + 1 < argv.len() {
// Specifying more than one job to put to the foreground is a syntax error, we still
// try to locate the job $argv[1], since we need to determine which error message to
// emit (ambiguous job specification vs malformed job id).
// emit (ambiguous job specification vs malformed job ID).
let mut found_job = false;
if let Ok(Some(pid)) = fish_wcstoi(argv[optind]).map(Pid::new) {
if let Ok(pid) = parse_pid(streams, cmd, argv[optind]) {
found_job = parser.job_get_from_pid(pid).is_some();
};
if found_job {
streams
.err
.append(wgettext_fmt!("%ls: Ambiguous job\n", cmd));
.append(wgettext_fmt!("%s: Ambiguous job\n", cmd));
} else {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a job\n",
cmd,
argv[optind]
));
streams
.err
.append(wgettext_fmt!("%s: '%s' is not a job\n", cmd, argv[optind]));
}
builtin_print_error_trailer(parser, streams.err, cmd);
job_pos = None;
job = None;
} else {
match fish_wcstoi(argv[optind]) {
Err(_) => {
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, argv[optind]));
job_pos = None;
job = None;
builtin_print_error_trailer(parser, streams.err, cmd);
}
match parse_pid(streams, cmd, argv[optind]) {
Ok(pid) => {
let raw_pid = pid;
let pid = Pid::new(pid.abs());
let j = pid.and_then(|pid| parser.job_get_with_index_from_pid(pid));
let j = parser.job_get_with_index_from_pid(pid);
if j.as_ref()
.map_or(true, |(_pos, j)| !j.is_constructed() || j.is_completed())
{
streams
.err
.append(wgettext_fmt!("%ls: No suitable job: %d\n", cmd, raw_pid));
.append(wgettext_fmt!("%s: No suitable job: %d\n", cmd, pid));
job_pos = None;
job = None
} else {
@@ -97,18 +84,23 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
job_pos = Some(pos);
job = if !j.wants_job_control() {
streams.err.append(wgettext_fmt!(
"%ls: Can't put job %d, '%ls' to foreground because it is not under job control\n",
cmd,
raw_pid,
j.command()
));
"%s: Can't put job %d, '%s' to foreground because it is not under job control\n",
cmd,
pid,
j.command()
));
None
} else {
Some(j)
};
}
}
}
Err(_err) => {
job_pos = None;
job = None;
builtin_print_error_trailer(parser, streams.err, cmd);
}
};
};
let Some(job) = job else {

View File

@@ -997,7 +997,7 @@ enum OutputType {
if args.is_empty() && i == 0 {
if output_type == OutputType::File {
streams.err.appendln(wgettext_fmt!(
"Expected file path to read/write for -w:\n\n $ %ls -w foo.fish",
"Expected file path to read/write for -w:\n\n $ %s -w foo.fish",
PROGRAM_NAME.get().unwrap()
));
return Err(STATUS_CMD_ERROR);
@@ -1299,7 +1299,7 @@ fn html_colorize(text: &wstr, colors: &[HighlightSpec]) -> Vec<u8> {
html.push_str("</span>");
}
if i == 0 || color != last_color {
sprintf!(=> &mut html, "<span class=\"%ls\">", html_class_name_for_color(color));
sprintf!(=> &mut html, "<span class=\"%s\">", html_class_name_for_color(color));
}
last_color = color;

View File

@@ -200,7 +200,7 @@ fn parse_flags(
}
'v' => {
streams.out.appendln(wgettext_fmt!(
"%ls, version %s",
"%s, version %s",
PROGRAM_NAME.get().unwrap(),
crate::BUILD_VERSION
));

View File

@@ -77,7 +77,7 @@ fn parse_cmd_opts(
argv: &mut [&wstr],
parser: &Parser,
streams: &mut IoStreams,
) -> c_int {
) -> BuiltinResult {
let cmd = L!("function");
let print_hints = false;
let mut handling_named_arguments = false;
@@ -94,20 +94,20 @@ fn parse_cmd_opts(
if handling_named_arguments {
if is_read_only(&woptarg) {
streams.err.append(wgettext_fmt!(
"%ls: variable '%ls' is read-only\n",
"%s: variable '%s' is read-only\n",
cmd,
woptarg
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
opts.named_arguments.push(woptarg);
} else {
streams.err.append(wgettext_fmt!(
"%ls: %ls: unexpected positional argument",
"%s: %s: unexpected positional argument",
cmd,
woptarg
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
}
'd' => {
@@ -116,11 +116,11 @@ fn parse_cmd_opts(
's' => {
let Some(signal) = Signal::parse(w.woptarg.unwrap()) else {
streams.err.append(wgettext_fmt!(
"%ls: Unknown signal '%ls'",
"%s: Unknown signal '%s'",
cmd,
w.woptarg.unwrap()
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
};
opts.events.push(EventDescription::Signal { signal });
}
@@ -130,7 +130,7 @@ fn parse_cmd_opts(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, name));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
opts.events.push(EventDescription::Variable { name });
}
@@ -149,34 +149,26 @@ fn parse_cmd_opts(
};
if caller_id == 0 {
streams.err.append(wgettext_fmt!(
"%ls: calling job for event handler not found",
"%s: calling job for event handler not found",
cmd
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
e = EventDescription::CallerExit { caller_id };
} else if opt == 'p' && woptarg == "%self" {
let pid = Pid::new(getpid());
e = EventDescription::ProcessExit { pid };
e = EventDescription::ProcessExit { pid: Some(pid) };
} else {
let Ok(pid @ 0..) = fish_wcstoi(woptarg) else {
streams.err.append(wgettext_fmt!(
"%ls: %ls: invalid process id",
cmd,
woptarg
));
return STATUS_INVALID_ARGS;
};
let pid = parse_pid_may_be_zero(streams, cmd, woptarg)?;
if opt == 'p' {
e = EventDescription::ProcessExit { pid: Pid::new(pid) };
e = EventDescription::ProcessExit { pid };
} else {
// TODO: rationalize why a default of 0 is sensible.
let internal_job_id = match Pid::new(pid) {
Some(pid) => job_id_for_pid(pid, parser).unwrap_or(0),
None => 0,
};
let internal_job_id = pid
.and_then(|pid| job_id_for_pid(pid, parser))
.unwrap_or_default();
e = EventDescription::JobExit {
pid: Pid::new(pid),
pid,
internal_job_id,
};
}
@@ -187,11 +179,11 @@ fn parse_cmd_opts(
let name = w.woptarg.unwrap().to_owned();
if is_read_only(&name) {
streams.err.append(wgettext_fmt!(
"%ls: variable '%ls' is read-only\n",
"%s: variable '%s' is read-only\n",
cmd,
name
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
handling_named_arguments = true;
opts.named_arguments.push(name);
@@ -208,7 +200,7 @@ fn parse_cmd_opts(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, woptarg));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
opts.inherit_vars.push(woptarg.to_owned());
}
@@ -217,7 +209,7 @@ fn parse_cmd_opts(
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
@@ -227,11 +219,11 @@ fn parse_cmd_opts(
argv[w.wopt_index - 1],
print_hints,
);
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
other => {
panic!("Unexpected retval from WGetopter: {}", other);
@@ -240,27 +232,39 @@ fn parse_cmd_opts(
}
*optind = w.wopt_index;
STATUS_CMD_OK
Ok(SUCCESS)
}
fn validate_function_name(function_name: &wstr, cmd: &wstr, streams: &mut IoStreams) -> c_int {
fn validate_function_name(
argv: &mut [&wstr],
function_name: &mut WString,
cmd: &wstr,
streams: &mut IoStreams,
) -> BuiltinResult {
if argv.len() < 2 {
streams
.err
.append(wgettext_fmt!("%ls: function name required", cmd));
return Err(STATUS_INVALID_ARGS);
}
*function_name = argv[1].to_owned();
if !valid_func_name(function_name) {
streams.err.append(wgettext_fmt!(
"%ls: %ls: invalid function name",
"%s: %s: invalid function name",
cmd,
function_name,
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
if parser_keywords_is_reserved(function_name) {
streams.err.append(wgettext_fmt!(
"%ls: %ls: cannot use reserved keyword as function name",
"%s: %s: cannot use reserved keyword as function name",
cmd,
function_name
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
STATUS_CMD_OK
Ok(SUCCESS)
}
/// Define a function. Calls into `function.rs` to perform the heavy lifting of defining a
@@ -271,7 +275,7 @@ pub fn function(
streams: &mut IoStreams,
c_args: &mut [&wstr],
func_node: NodeRef<BlockStatement>,
) -> c_int {
) -> BuiltinResult {
// The wgetopt function expects 'function' as the first argument. Make a new vec with
// that property. This is needed because this builtin has a different signature than the other
// builtins.
@@ -281,23 +285,17 @@ pub fn function(
let cmd = argv[0];
// A valid function name has to be the first argument.
let function_name = argv[1].to_owned();
let mut retval = validate_function_name(&function_name, cmd, streams);
if retval != STATUS_CMD_OK {
return retval;
}
let mut function_name = WString::new();
validate_function_name(argv, &mut function_name, cmd, streams)?;
let argv = &mut argv[1..];
let mut opts = FunctionCmdOpts::default();
let mut optind = 0;
retval = parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams);
if retval != STATUS_CMD_OK {
return retval;
}
parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams)?;
if opts.print_help {
builtin_print_error_trailer(parser, streams.err, cmd);
return STATUS_CMD_OK;
return Ok(SUCCESS);
}
if argv.len() != optind {
@@ -308,17 +306,17 @@ pub fn function(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, arg));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
opts.named_arguments.push(arg.to_owned());
}
} else {
streams.err.append(wgettext_fmt!(
"%ls: %ls: unexpected positional argument",
"%s: %s: unexpected positional argument",
cmd,
argv[optind],
));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
}
@@ -343,7 +341,7 @@ pub fn function(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, named));
return STATUS_INVALID_ARGS;
return Err(STATUS_INVALID_ARGS);
}
}
@@ -398,5 +396,5 @@ pub fn function(
}
}
STATUS_CMD_OK
Ok(SUCCESS)
}

View File

@@ -172,7 +172,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if let Some(desc) = opts.description {
if args.len() != 1 {
streams.err.append(wgettext_fmt!(
"%ls: Expected exactly one function name\n",
"%s: Expected exactly one function name\n",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
@@ -182,7 +182,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !function::exists(current_func, parser) {
streams.err.append(wgettext_fmt!(
"%ls: Function '%ls' does not exist\n",
"%s: Function '%s' does not exist\n",
cmd,
current_func
));
@@ -280,7 +280,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
&& !event::EVENT_FILTER_NAMES.contains(&opts.handlers_type.unwrap())
{
streams.err.append(wgettext_fmt!(
"%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n",
"%s: Expected generic | variable | signal | exit | job-id for --handlers-type\n",
cmd
));
return Err(STATUS_INVALID_ARGS);
@@ -320,7 +320,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if opts.copy {
if args.len() != 2 {
streams.err.append(wgettext_fmt!(
"%ls: Expected exactly two names (current function name, and new function name)\n",
"%s: Expected exactly two names (current function name, and new function name)\n",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
@@ -331,7 +331,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !function::exists(current_func, parser) {
streams.err.append(wgettext_fmt!(
"%ls: Function '%ls' does not exist\n",
"%s: Function '%s' does not exist\n",
cmd,
current_func
));
@@ -341,7 +341,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !valid_func_name(new_func) || parser_keywords_is_reserved(new_func) {
streams.err.append(wgettext_fmt!(
"%ls: Illegal function name '%ls'\n",
"%s: Illegal function name '%s'\n",
cmd,
new_func
));
@@ -351,7 +351,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if function::exists(new_func, parser) {
streams.err.append(wgettext_fmt!(
"%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n",
"%s: Function '%s' already exists. Cannot create copy of '%s'\n",
cmd,
new_func,
current_func
@@ -391,7 +391,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
}
Some(path) => {
comment.push_utfstr(&wgettext_fmt!(
"Defined in %ls @ line %d",
"Defined in %s @ line %d",
path,
props.definition_lineno()
));
@@ -406,7 +406,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
}
Some(path) => {
comment.push_utfstr(&wgettext_fmt!(
", copied in %ls @ line %d",
", copied in %s @ line %d",
path,
props.copy_definition_lineno()
));
@@ -420,7 +420,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !comment.is_empty() {
def.push_utfstr(&sprintf!(
"# %ls\n%ls",
"# %s\n%s",
comment,
props.annotated_definition(arg)
));

14
src/builtins/gettext.rs Normal file
View File

@@ -0,0 +1,14 @@
use super::prelude::*;
/// Used for the fish `_` builtin for requesting translations.
/// For scripts in `share/`, the corresponding strings are extracted from the scripts using
/// `build_tools/fish_xgettext.fish`.
/// Strings not present in our repo would require a custom MO file for translation to be possible.
pub fn gettext(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
for arg in &argv[1..] {
streams.out.append(
crate::wutil::LocalizableString::from_external_source((*arg).to_owned()).localize(),
);
}
Ok(SUCCESS)
}

View File

@@ -113,7 +113,7 @@ fn check_for_unexpected_hist_args(
if opts.search_type.is_some() || opts.show_time_format.is_some() || opts.null_terminate {
let subcmd_str = opts.hist_cmd.to_wstr();
streams.err.append(wgettext_fmt!(
"%ls: %ls: subcommand takes no options\n",
"%s: %s: subcommand takes no options\n",
cmd,
subcmd_str
));
@@ -335,7 +335,7 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
if in_private_mode(parser.vars()) {
streams.err.append(wgettext_fmt!(
"%ls: can't merge history in private mode\n",
"%s: can't merge history in private mode\n",
cmd
));
return Err(STATUS_INVALID_ARGS);

View File

@@ -5,7 +5,7 @@
use crate::io::IoStreams;
use crate::job_group::{JobId, MaybeJobId};
use crate::parser::Parser;
use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job, Pid};
use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job};
use crate::wchar_ext::WExt;
use crate::wgetopt::{wopt, ArgType, WGetopter, WOption};
use crate::wutil::wgettext;
@@ -22,7 +22,7 @@ enum JobsPrintMode {
Default, // print lots of general info
PrintPid, // print pid of each process in job
PrintCommand, // print command name of each process in job
PrintGroup, // print group id of job
PrintGroup, // print group ID of job
PrintNothing, // print nothing (exit status only)
}
@@ -110,7 +110,7 @@ fn builtin_jobs_print(j: &Job, mode: JobsPrintMode, header: bool, streams: &mut
}
for p in j.processes() {
out += &sprintf!("%ls\n", p.argv0().unwrap())[..];
out += &sprintf!("%s\n", p.argv0().unwrap())[..];
}
streams.out.append(out);
}
@@ -196,7 +196,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
match fish_wcstoi(&arg[1..]).ok().filter(|&job_id| job_id >= 0) {
None => {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job id\n",
"%s: '%s' is not a valid job ID\n",
cmd,
arg
));
@@ -214,19 +214,8 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
}
} else {
match fish_wcstoi(arg).ok().and_then(Pid::new) {
None => {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid process id\n",
cmd,
arg
));
return Err(STATUS_INVALID_ARGS);
}
Some(job_id) => {
j = parser.job_get_from_pid(job_id);
}
}
let pid = parse_pid(streams, cmd, arg)?;
j = parser.job_get_from_pid(pid)
}
if let Some(j) = j.filter(|j| !j.is_completed() && j.is_constructed()) {
@@ -236,7 +225,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if mode != JobsPrintMode::PrintNothing {
streams
.err
.append(wgettext_fmt!("%ls: No suitable job: %ls\n", cmd, arg));
.append(wgettext_fmt!("%s: No suitable job: %s\n", cmd, arg));
}
return Err(STATUS_CMD_ERROR);
}
@@ -256,7 +245,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if !streams.out_is_redirected && mode != JobsPrintMode::PrintNothing {
streams
.out
.append(wgettext_fmt!("%ls: There are no jobs\n", argv[0]));
.append(wgettext_fmt!("%s: There are no jobs\n", argv[0]));
}
return Err(STATUS_CMD_ERROR);
}

View File

@@ -69,7 +69,7 @@ fn parse_cmd_opts(
if scale < 0 || scale > 15 {
streams
.err
.append(wgettext_fmt!("%ls: %ls: invalid scale\n", cmd, optarg));
.append(wgettext_fmt!("%s: %s: invalid scale\n", cmd, optarg));
return Err(STATUS_INVALID_ARGS);
}
// We know the value is in the range [0, 15]
@@ -89,7 +89,7 @@ fn parse_cmd_opts(
} else {
streams
.err
.append(wgettext_fmt!("%ls: %ls: invalid mode\n", cmd, optarg));
.append(wgettext_fmt!("%s: %s: invalid mode\n", cmd, optarg));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -103,7 +103,7 @@ fn parse_cmd_opts(
let base = fish_wcstoi(optarg).unwrap_or(-1);
if base != 8 && base != 16 {
streams.err.append(wgettext_fmt!(
"%ls: %ls: invalid base value\n",
"%s: %s: invalid base value\n",
cmd,
optarg
));
@@ -159,7 +159,7 @@ fn format_double(mut v: f64, opts: &Options) -> WString {
if opts.base == 16 {
v = v.trunc();
let mneg = if v.is_sign_negative() { "-" } else { "" };
return sprintf!("%s0x%lx", mneg, v.abs() as u64);
return sprintf!("%s0x%x", mneg, v.abs() as u64);
} else if opts.base == 8 {
v = v.trunc();
if v == 0.0 {
@@ -167,7 +167,7 @@ fn format_double(mut v: f64, opts: &Options) -> WString {
return WString::from_str("0");
}
let mneg = if v.is_sign_negative() { "-" } else { "" };
return sprintf!("%s0%lo", mneg, v.abs() as u64);
return sprintf!("%s0%o", mneg, v.abs() as u64);
}
v *= pow(10f64, opts.scale);
@@ -245,24 +245,24 @@ fn evaluate_expression(
streams
.err
.append(sprintf!("%ls: Error: %ls\n", cmd, error_message));
streams.err.append(sprintf!("'%ls'\n", expression));
.append(sprintf!("%s: Error: %s\n", cmd, error_message));
streams.err.append(sprintf!("'%s'\n", expression));
Err(STATUS_CMD_ERROR)
}
Err(err) => {
streams.err.append(sprintf!(
L!("%ls: Error: %ls\n"),
L!("%s: Error: %s\n"),
cmd,
err.kind.describe_wstr()
));
streams.err.append(sprintf!("'%ls'\n", expression));
streams.err.append(sprintf!("'%s'\n", expression));
let padding = WString::from_chars(vec![' '; err.position + 1]);
if err.len >= 2 {
let tildes = WString::from_chars(vec!['~'; err.len - 2]);
streams.err.append(sprintf!("%ls^%ls^\n", padding, tildes));
streams.err.append(sprintf!("%s^%s^\n", padding, tildes));
} else {
streams.err.append(sprintf!("%ls^\n", padding));
streams.err.append(sprintf!("%s^\n", padding));
}
Err(STATUS_CMD_ERROR)

View File

@@ -5,23 +5,28 @@
pub mod bg;
pub mod bind;
pub mod block;
pub mod r#break;
pub mod breakpoint;
pub mod builtin;
pub mod cd;
pub mod command;
pub mod commandline;
pub mod complete;
pub mod contains;
pub mod r#continue;
pub mod count;
pub mod disown;
pub mod echo;
pub mod emit;
pub mod eval;
pub mod exit;
pub mod r#false;
pub mod fg;
pub mod fish_indent;
pub mod fish_key_reader;
pub mod function;
pub mod functions;
pub mod r#gettext;
pub mod history;
pub mod jobs;
pub mod math;
@@ -38,6 +43,7 @@
pub mod status;
pub mod string;
pub mod test;
pub mod r#true;
pub mod r#type;
pub mod ulimit;
pub mod wait;

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