Compare commits

...

275 Commits

Author SHA1 Message Date
Shayan
acd3c45a47 completions/magick: remove deprecated convert command and dependencies 2026-06-02 22:29:34 +08:00
David Adam
1abeae2f4b docs: standardise on "fish" as the name for the shell
This is a potential bikeshed project but if fish is being used as a
proper noun, it should either be capitalised all the time or none of the
time, and the latter looks better.
2026-06-02 14:13:24 +08:00
David Adam
7304fdfffb CHANGELOG: log #7206
Closes #11909.
2026-06-02 13:44:24 +08:00
David Adam
af09b60aeb fix cd symlink resolution tests on Cygwin 2026-06-02 13:43:40 +08:00
David Adam
611a1a0bdf highlight: handle cd now that it has multiple arguments
Work on #11909.
2026-06-02 13:43:39 +08:00
David Adam
7154dd8051 update translations 2026-06-02 13:43:39 +08:00
Asuka Minato
0eb17fa58f Implement cd -L and cd -P
Closes #7206
2026-06-02 13:43:32 +08:00
Johannes Altmanninger
e4123df7be config.fish: remove unicode_start on linux console login
This command typically does

	ioctl(open("/dev/tty"), KDSKBMODE, K_UNICODE)
	printf '\033%%G'
	stty iutf8

Other shells don't do this; it seems like this is typically already done
before the shell is started, for example by systemd's vconsole-setup.
We only do it in very specific scenarios (login shells on linux), so
if this is really needed, we should make it reliable. Bravely remove it.
2026-06-02 11:43:40 +08:00
Wes Higbee
4b2aba31ee complete: tab-complete anywhere-position abbrs in non-command position
Make abbreviations with `--position anywhere` appear in the completion
menu when tabbing in argument position (e.g. `cat foo.log pg<TAB>`
shows pgr, pgrv, pjq, etc.), not just in command position.

Lift "do_file" into an enum, so we don't have to check "is_redirection"
twice, and can express more accurately the reason for not completing
abbreviations after redirection.

Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>

Closes #12764
2026-06-02 11:38:29 +08:00
Johannes Altmanninger
c10e782838 Remove unused allocation in complete_abbr argument
Helps the next commit.
2026-06-02 11:32:52 +08:00
Johannes Altmanninger
71ce491783 abbr unit tests: remove abbreviations after test
This helps avoid a test failure in a futre commit which offers
abbreviations created with --position=anywhere as completions.
2026-06-02 11:32:52 +08:00
Johannes Altmanninger
b5ae8df24c abbrs: use safer API 2026-06-02 11:31:21 +08:00
Johannes Altmanninger
f717c5e2a0 Fix "bind -M somemode" crash for empty bind modes
This loop iterates over all bindings and crashes if a mode filter
argument is given for a mode that has no binding.  Fix this by
continuing the loop earlier.

Fixes 11d6e92cb5 (Show file location when querying bindings with bind, 2025-12-28)

Fixes #12798
2026-06-02 08:44:27 +08:00
Johannes Altmanninger
7acd489a06 Don't show private mode greeting if greeting is empty list
Reintroduce a subset of 0e4a75c0b5 (Do not print greeting with empty
$fish_greeting and --private, 2019-11-08) after it has been removed
in 72a44460c6 (Move fish_greeting to a function, 2020-05-30).

Closes #12796
2026-06-01 15:50:31 +08:00
ritoban23
11d6e92cb5 Show file location when querying bindings with bind
Closes #12215
Closes #12504
Closes #12221
2026-05-31 07:43:32 +08:00
Sean Reifschneider
eb535322d0 Fix cW Vi mode regression deleting trailing space
Missed in 02c0455 (Fix Vi mode cw deleting trailing whitespace, 2026-02-12),

Closes #12790
Closes #12791
2026-05-31 07:38:44 +08:00
Johannes Altmanninger
cb2f279550 minor style cleanup 2026-05-30 21:34:19 +08:00
Johannes Altmanninger
dc9668c8a4 Remove unused workflow to build docker images
Implied by f48166ff36 (update-dependencies: remove things used for Cirrus,
2026-05-29).

See #12626
2026-05-30 18:05:50 +08:00
Johannes Altmanninger
a3b009dd43 build_tools/update-dependencies.sh
Steps:

1. Run: build_tools/update-dependencies.sh
2. Remove obsolete small_rng feature from Cargo.toml
3. Re-run failed part
4. Run: cargo +nightly -Zunstable-options update --breaking
5. Address rng breaking changes
2026-05-30 17:52:48 +08:00
Johannes Altmanninger
ccfbfe9965 CI: use Python from uv's virtualenv
uv allows "uv python install" to a ~/.local, but doesn't allow
"uv pip install --user", only "--system". According to [1], we are
supposed to use a virtualenv, and make sure that "$VIRTUAL_ENV/bin"
is in "$PATH" (before "/bin").  Use ~/.local; there should be no
collisions in practice.

This fixes the problem worked around by fe3c42af9e (Work around
github actions python failure, 2026-02-08). Revert that.
Update lock files with:

	updatecli apply --config updatecli.d/python.yml
	uv lock --upgrade --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"

[1]: https://github.com/astral-sh/uv/issues/2077#issuecomment-1971579880
2026-05-30 17:52:48 +08:00
Johannes Altmanninger
cb983e5fb7 update-dependencies: fix catpuccin theme path 2026-05-30 13:57:27 +08:00
Johannes Altmanninger
f48166ff36 update-dependencies: remove things used for Cirrus
See ff0afd14c2 (Remove Cirrus CI, 2026-05-28)
2026-05-30 13:48:17 +08:00
Fabian Boehm
25cc01845a string: Fix swapped arguments in error message
Really this could do with a bespoke error, but this'll do for now.
2026-05-29 10:23:59 +02:00
Johannes Altmanninger
bb74d8155e Decode f1-f5 on Linux console
Other things like shift-f1 are not supported on console yet because
Linux sends "CSI 25 ~" which means shift-f3 in urxvt, so there's
a conflict.

Closes #12787
2026-05-29 15:11:59 +08:00
Nahor
240647327a init-unreadable-cwd.fish: replace custom check with __fish_is_cygwin 2026-05-28 11:10:14 -07:00
Nahor
04c7ba5ca0 Cygwin: include other MSYS2 environments than just MSYS 2026-05-28 09:40:09 -07:00
Johannes Altmanninger
7b358d2122 Fix init-unreadable-cwd test on Windows 2026-05-28 21:10:20 +08:00
Johannes Altmanninger
ff0afd14c2 Remove Cirrus CI
See #12626
2026-05-28 16:43:42 +08:00
Johannes Altmanninger
17ef326c8f Disable cwd-error-reporting test on Windows
On Windows, "chmod 000 .; cd ." doesn't fail, so disable this
test case on Windows.  On the Windows CI runner, "uname" prints
"MINGW64_NT-10.0-26100" so we can't use "__fish_is_cygwin".
2026-05-28 16:40:30 +08:00
Johannes Altmanninger
9a56334292 Fix signal unit tests on BSD 2026-05-28 16:17:15 +08:00
Peter Ammon
821efeb99c Justify the continued presence of cwd_fd 2026-05-25 22:25:30 -07:00
Peter Ammon
960b96750a Revert "parser: remove unused cwd_fd field"
cwd_fd is crucial to maintain: long-term vision for fish is multiple
threads each with their own CWD with the process-wide CWD used for
nothing at all except transiently (e.g. during fork).

Note this fd is heightenized per open_dir.

This reverts commit 6701b7f6c8.
2026-05-25 22:25:23 -07:00
Henrik Hørlück Berg
79bf53aaa5 Fix Principal topic monitor not initialized panic
Like 1b16d318cc, reproduces with `cargo test test_pthread`

Fix by using stdlib `println!` to avoid interacting with topicMonitor.

Enable `allow-print-in-tests` like with crates/printf, such that there i
no deny-warning.
2026-05-25 18:34:04 -07:00
Henrik Hørlück Berg
8dee291200 Fix threads::init() was not called at startup!
Happens when running `cargo nextest run test_replace_home_directory_with_tilde`, due to process-isolation model.

Backtrace shows:
    EnvStack::set_one(HOME)
    EnvStack::set
    env_dispatch_var_change
    reader_current_data
    reader_data_stack
    assert_is_main_thread
    is_main_thread
    main_thread_id

Fix by using TestEnvironment instead, as this is a unit-test and does
not need anything more.
2026-05-25 18:34:04 -07:00
Henrik Hørlück Berg
8513bb8dc4 Fix Principal topic monitor not initialized
in `test_path_normalize_for_cd`

This happens when running the tests using `cargo nextest run`, where
each test runs in its own process, but does not happen with normal
`cargo test`.

Fix by avoiding eprintf!, which can reach SigChecker::check. Instead use a custom panic-message.
2026-05-25 18:34:04 -07:00
Peter Ammon
17fdd2f153 Clean up "strip control characters" changes
Simplify these and move the sanitization closer to the point of output.

Stop worrying about control characters beyond what PCRE2 reports in
"[[:cntrl:]]" for clarity.
2026-05-25 16:48:30 -07:00
saku0512
cbc3cd21f7 prompts: strip control characters from VCS state and PWD
Strip control characters from VCS branch and state strings before writing them in prompts.

Also route the informative and minimalist sample prompts through prompt_pwd instead of printing PWD directly.
2026-05-25 15:51:41 -07:00
Harry Garrood
7ddc8ee195 git completion: only suggest files/directories with --no-index
Fixes #12773.

Closes #12776
2026-05-24 18:59:08 +08:00
Jonas Natten
0a2879440a completions/terraform: add missing subcommands
Compared against "terraform -help" from v1.15.4.  The completion was
missing the following top-level subcommands:

	force-unlock  Release a stuck lock on the current workspace
	metadata      Metadata related commands
	modules       Show all declared modules in a working directory
	query         Search and list remote infrastructure with Terraform
	stacks        Manage HCP Terraform stack operations

Also add the global "-chdir" option, which switches the working
directory before running the subcommand, and teach
"__fish_terraform_needs_command" to skip past it so subcommands are
still offered after e.g. "terraform -chdir=foo".

While at it, fix two unrelated bugs:

* The "refresh" section conditioned "-compact-warnings" on "$apply"
  instead of "refresh", so it was only offered for apply/destroy
  (where it's already covered).

* The "show" section had a stray "validate -json" completion (a copy
  of the entry in the validate section).  Replace it with the
  actually-missing "show -json".

Closes #12771
2026-05-24 18:56:54 +08:00
Johannes Altmanninger
07b552aa78 Delete unused en.po
Commit 4bdd35b8d1 (feat: stop language fallback at English, 2026-05-11)
rendered en.po unused.  Delete it.

In future, we should replace most single quotes in msgids with
backticks, since Markdown is very popular nowadays.

Closes #12745
2026-05-24 18:11:04 +08:00
Peter Ammon
8067a5c2c8 Fix some clipplies 2026-05-23 18:45:02 -07:00
Peter Ammon
a2bfd93451 Re-apply test_only_suppress_stderr
Fix some obnoxious output during certain tests.
2026-05-23 18:21:07 -07:00
Peter Ammon
068fa42202 Reimplement test_only_suppress_stderr
This parameter is used to suppress certain verbose errors that are
expected during tests. It was awkwardly threaded through multiple call
sites. Just set it (test only) on Parser.
2026-05-23 18:13:16 -07:00
Nahor
ffaa07d687 Fix unused import warning on non-Linux platforms 2026-05-23 12:04:41 -07:00
Johannes Altmanninger
4ad3c48e3f Fix "cargo doc" error 2026-05-23 16:34:16 +08:00
Johannes Altmanninger
19e0c8b6b9 signal: remove duplicate definition of SIGIOT 2026-05-23 10:31:18 +08:00
Johannes Altmanninger
0f81bb0748 signal: extract function for querying SIGHUP disposition 2026-05-23 10:15:37 +08:00
Johannes Altmanninger
cd75d3d3ca proc: remove obsolete NixSignal alias
See parent commit.
2026-05-23 10:14:09 +08:00
Johannes Altmanninger
8250507a0f Rename Signal to RawSignal to make way for nix Signal 2026-05-23 09:24:21 +08:00
Johannes Altmanninger
2c6b76d5fe Remove redundant uses of std::mem::zeroed()
We already use "inode: 0". 

See 7c2c7f5874 (Use uninit instead of zeroed, 2025-05-19).

Part of #12337
2026-05-23 09:24:21 +08:00
xtqqczze
78633cab0f format: reformat TOML files
Format using Tombi with the schema at https://www.schemastore.org/cargo.json.
Some ordering in Cargo.toml is left for later.

Closes #12702
2026-05-23 01:00:27 +08:00
Nahor
954c68c0b1 Fix comment typo (CLO_EXEC -> CLOEXEC)
Closes #12774
2026-05-23 00:59:56 +08:00
Nahor
01b848bbbb Fix formatting of tokenizer.rs
Remove a couple of unhelpful comments that stopped rustfmt for working
(https://github.com/rust-lang/rustfmt/issues/4119)

Part of #12774
2026-05-23 00:59:56 +08:00
Johannes Altmanninger
b6f8a7bf4a Changelog: mention that redundant /usr/share/fish is gone 2026-05-21 12:50:14 +08:00
sorgel
bf59785377 completion: fix du to support both GNU and BSD
The current file has no platform detection, causing GNU-only completions
on macOS/BSD. It also has a factual error: -H is described as "Human
readable sizes, powers of 1000", but -H actually follows command-line
symlinks.

Rewritten using __fish_gnu_complete and GNU detection, following
the same pattern as sed.fish. Platform-specific flags added.

Closes #12767
2026-05-20 18:40:12 +08:00
Drazape
4c30c9ec2d (refactor) default mode prompt: multiple test equality checks → contains
While at it, use early return.

Closes #12766
2026-05-20 18:40:01 +08:00
Johannes Altmanninger
684d360ece completions/cargo: remove xtask completions for now
We source "COMPLETE=fish cargo xtask" at completion load time.

This means that completions will be missing if completions were requested 
before entering a fish-shell worktree.

Also, as reported in [1], this will compile xtask even for non-fish
projects, which is usually fast but not necessarily (it might download
crates etc).

As suggested in the initial revision of #12763, we could
hardcode the output of

	$ COMPLETE=fish cargo xtask
	complete --keep-order --exclusive --command cargo --arguments \
		"(COMPLETE=fish /home/johannes/git/fish-shell/target/debug/xtask --
			(commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))"

but that might still be too intrusive because according to [2],
calls to "cargo metadata" can be slow on big repos with hundreds of
Cargo.toml files.

[1]: 959cbb4259 (r185615416)
[2]: 959cbb4259 (r185637424)

FWIW, commit comments are also accessible via

	gh api -H "X-GitHub-Api-Version: 2026-03-10" \
	    /repos/fish-shell/fish-shell/commits/959cbb42597444577d26ed5ba6151ccb1d7f3600/comments
2026-05-19 13:38:51 +08:00
Johannes Altmanninger
30b9cababa completions/cargo: fix spurious error message if jq is not installed
Reported in 959cbb4259 (r185615058)
2026-05-18 16:22:13 +08:00
Johannes Altmanninger
29182dae54 completions/cargo: don't implicitly install cargo via rustup
Set RUSTUP_AUTO_INSTALL=0 for completions, to be consistent with
88d01f7eb8 (completions/cargo: avoid auto-installing toolchain via
rustup, 2026-03-27).
2026-05-18 16:20:40 +08:00
Johannes Altmanninger
f598a7b444 completions/cargo: remove redundant arguments 2026-05-18 16:20:40 +08:00
Johannes Altmanninger
5d317be52c completions/cargo: fix error completing "cargo asm"
"timeout __fish_cargo" doesn't work because "__fish_cargo" is a
fish function.  Work around this.
2026-05-18 16:20:39 +08:00
Johannes Altmanninger
e59a61e5e6 share/config.fish: lazy-load __fish_expand_pid_args
This is only needed when invoking fg/bg/disown/kill.
Trims down share/config.fish a bit more.
2026-05-18 16:20:39 +08:00
Johannes Altmanninger
1284527fac Path component movement to skip all escaped characters
Extend the hack added in ebc140a3ea (Hack path component movement to
skip escaped spaces, 2025-12-16) to apply to all characters, such as
";" that may be part of a file name.
2026-05-18 16:18:52 +08:00
Johannes Altmanninger
ec52a999b3 Use ST (\x1b\\) as OSC terminator
The test balloon in 012007ce7b (Test balloon for ST OSC terminator,
2025-11-18) released in 4.3.0 did not trigger problem reports.
Bravely switch over to the standard ST rather than xterm's BEL.

Closes #12032
2026-05-18 16:18:52 +08:00
David Adam
afd869be2a README: update Actions status badge URL 2026-05-18 06:12:12 +08:00
Johannes Altmanninger
d72e323a6f Replace occasional uses of EN DASH (U+2013) with ASCII HYPHEN MINUS
See #12745
2026-05-14 16:38:08 +08:00
Johannes Altmanninger
d383733747 doc terminal-compatibility: don't call out informative Git prompt's arrows
The up/down arrows (as well as lots of other non-ASCII characters) are
printed by the informative Git prompt, which is not eabled by default.
2026-05-14 16:35:31 +08:00
Johannes Altmanninger
1f18b9715f Fix repeated tab causing repeated smartcase completion insertion
Doing

	firefox --pro TAB TAB TAB

results in

	firefox --profile --ProfileManager

git-bisect points to 3546ffa3ef (reader
handle_completions(): remove dead filtering code, 2026-01-02)
but that regression has already been fixed by 85e76ba356
(Fix option substr completions not being filtered out, 2026-04-16).

However in between those two commits, the above case has also been
broken by 2f6b1eaaf9 (reader handle_completions(): don't consider
odd replacing completions for common prefix, 2026-01-02)

The first TAB inserts "--profile ", including the trailing space.
However it also shows the completion pager, which means that subsequent
TABs will insert after the space.

The trailing space does not make sense unless we navigate the pager.
Remove it in all cases, to fix this smartcase scenario.

Alternatively, we could start navigating the pager in this case
(and keep the trailing space), but that's probably too inconsistent.
2026-05-14 16:17:41 +08:00
Johannes Altmanninger
5876ff66ff fish_git_prompt: fix buggy rename parsing
Commit f86c9af455 (fish_git_prompt: skip rename/copy source paths in
status parsing, 2026-05-12).  filters from "git-status --porcelain"
output all lines that don't look like a status entry.  This is
to filter out the rename source path.  But the filtering has false
positives, e.g. if a rename source path starts with "AA".  Let's only
skip lines immediately after each rename entry.  Also for copy (C)
though I haven't found a test case for that yet.
2026-05-14 14:25:21 +08:00
Johannes Altmanninger
ee4eea51ec fish_git_prompt: extract function 2026-05-14 14:25:21 +08:00
Hans Larsen
a3984ace4a Add git pull/fetch --recurse-submodules to completion
Closes #12746
2026-05-13 19:58:52 +08:00
Daniel Rainer
4bdd35b8d1 feat: stop language fallback at English
Treating `en` the same as any other language is problematic as shown by
#12690. When the language precedence list contains entries after
English and we don't treat English specially, a lack of translations in
`en.po` (or a lack of `en.po`, once we delete it) results in
translations into those subsequent languages being displayed, instead of
the msgid, which is in English, and thus preferable.

By truncating the fallback list when we encounter `en`, this problem is
resolved. As it is implemented now, the `en.po` catalog is never used.
This is intended, as the plan is to delete it (#12745). In any case, its
translations are identical to the msgids modulo some fancy quotes.

While at it, also treat `LANGUAGE` values of `C` and `POSIX` as
referring to the English version of the messages.

Fixes #12690

Closes #12747
2026-05-13 19:58:52 +08:00
June Kim
a93fcd97a7 fish_git_prompt: include T (typechange) in status filter
The status entry filter regex was missing T, which is a valid
porcelain status code for file type changes (e.g. regular file
to symlink). Without it, typechange entries would be silently
dropped from dirty/staged detection.

Closes #12754
2026-05-13 19:58:52 +08:00
June Kim
f86c9af455 fish_git_prompt: skip rename/copy source paths in status parsing
When git status --porcelain -z reports renames or copies, it outputs
the source filename as a separate NUL-delimited field after the status
line. This extra entry was counted as an additional change, inflating
staged/dirty counts when the source filename started with [ACDMRTU].

Filter split results to entries matching a valid two-char status code
prefix, which excludes the bare source filenames.

Fixes #11296

Part of #12754
2026-05-13 19:58:52 +08:00
June Kim
f69f074f66 Add failing test for git prompt rename miscount (issue #11296)
git status --porcelain -z outputs the rename source filename as a
separate NUL-delimited entry after the status line. When the source
filename starts with [ACDMRTU], the informative prompt miscounts it
as an additional staged change.

Part of #12754
2026-05-13 19:58:52 +08:00
Nahor
f0054336ea man-pages: force encoding when reading doc sources
Force the encoding to not be dependent on the environment locale.

In particular on Windows, the encoding could default to an ANSI page
code, which would fail to load any file containing bytes 0x80+, i.e any
multi-byte UTF-8 character.

Closes #12748
2026-05-13 19:58:52 +08:00
Daniel Rainer
959cbb4259 feat: dynamic cargo xtask completions for fish
Use `clap_complete` to generate completions for our xtasks. This comes
with two complications:
- Due to the unusual CLI of the whole xtask CLI definition being itself
  a subcommand under `cargo` (via the `cargo xtask` alias), we need to
  tell `clap_complete` that we're generating completions for `cargo`,
  which is fairly straightforward to do via two new types which are only
  used for generating completions.
- Our completions for `cargo xtask` only make sense within fish's
  workspace, so we need to ensure that they are not active elsewhere.
  `clap_complete` does not support adding such conditions, so we hack
  them together by post-processing its output with sed.

Closes #12739
2026-05-13 19:58:52 +08:00
Collin Styles
fa6cbbdb40 Add completions for git history
This command was added in git 2.54:

94f057755b/Documentation/RelNotes/2.54.0.adoc

Both subcommands (`reword` and `split`) take a commit-ish object to
target. `split` also optionally accepts filenames so I tried to handle
that by copying the pattern from existing completions.

Closes #12737
2026-05-13 19:58:52 +08:00
Daniel Rainer
2b378b9c1f feat: add info how to update outdated PO files
Putting this in the error message makes it easier to find out how to
resolve the problem.

Closes #12735
2026-05-13 19:58:52 +08:00
Milo
2863960836 docs: update Homebrew link to HTTPS
Co-authored-by: KeloYuan <keloyuan@users.noreply.github.com>

Closes #12733
2026-05-13 19:58:52 +08:00
Johannes Altmanninger
c003ec3795 vi mode: fix "x" command in builtin read
When we push a new reader for builtin read, we use the default
CursorSelectionMode::Exclusive, which is wrong in Vi mode.
Add a haphazard fix for that.
This is very ugly, we should improve this.

Closes #12724
2026-05-13 19:58:52 +08:00
PowerUser64
51ab1f5d35 add example of how to set default variable values
Closes #12720
2026-05-13 19:58:52 +08:00
Nahor
01b9fd9e31 complete: remove unescaping of -c/-p values
This removes the need to double-escape the values on the command line
(once for the command line parser, another for the option handling)

This also brings it in line with the implicit case (`complete cmd ...`)

Fixes #12712

Closes #12718
2026-05-13 18:01:48 +08:00
Jian Weihang
b2c23eb397 completions/tmux: complete directories for new-session -c
Closes #12713
2026-05-13 17:25:39 +08:00
Johannes Altmanninger
ef8e62727c Pass Parser by exclusive reference
As described in
https://github.com/fish-shell/fish-shell/pull/9990#discussion_r1382494440,
prior to 77aeb6a2a8 (Port execution, 2023-10-08), "Parser" was
passed by mutable reference ("parser_t&"), even though operation
context was passed as "const operation_context_t &".  This worked
because C++ doesn't propagate const to pointers by default (see
https://en.cppreference.com/cpp/experimental/propagate_const).

	class operation_context_t {
		std::shared_ptr<parser_t> parser;
		...
	};

So "*ctx->parser" was a "parser_t&", not "const parser_t&".

Rust has stricter const propagation rules which means that const
operation context can't simply hand out a non-const reference to parser.

To be able to port code without changing its structure,
77aeb6a2a8 passed "Parser" by shared reference, using interior
mutability (RefCell) to modify parser fields. This is a bit ugly
(c.f. https://doc.rust-lang.org/std/cell/index.html "interior mutability
is something of a last resort") and means that some borrowing conflicts
are not found at compile time but runtime.

Pass both parser and operation context by exclusive reference, and
remove the interior mutability wrappers from parser's fields.
Since "libdata" is no longer inside a "RefCell", add a "ScopedRefCell"
around "transient_commandline".

The downside is that "ScopeGuard" use can become more intrusive
when we pass "Parser" or "OperationContext" as context (especially
when we use "zelf" since we can't shadow "self"), see
* 2930466d53 (Introduce ScopedCell and ScopedRefCell, 2025-03-15)
* 29ae571afa (Make scoped_push nicer, 2024-12-28)
Avoid this in some cases, specifically when using "ScopedCell" or
"ScopedRefCell". Since "&mut Parser" prevents the "ScopeCell"'s
"ScopeGuard" from holding a shared reference, use an "Rc" to capture
a dynamically-checked reference to the Cell. We could also use raw
pointers instead.

Change "Completer::apply_var_assignments" to return  a block ID, to
avoid the need to return a "zelf" "ScopeGuard".  In future, we could
probably untangle completer and get away with returning a "ScopeGuard"
called "ctx".

Closes #12694
2026-05-11 10:41:14 +08:00
Johannes Altmanninger
638777a4de Remove unused ScopeGuarding trait
As of commit 0441bdc634 (Remove ScopeGuard::commit in favor of drop,
2026-04-30), this trait is empty so there's not much use having it
for now.
2026-05-11 10:41:14 +08:00
Johannes Altmanninger
6705d27f93 Remove unused ScopeGuard::cancel() 2026-05-11 10:41:14 +08:00
Johannes Altmanninger
ef626cfdf9 uvar migration: clarify it's about 4.3 or higher 2026-05-11 10:41:14 +08:00
Johannes Altmanninger
30976d8970 uvar migration: recogize more historical defaults as such
During our one-time migration away from universal variables,
we create ~/.config/fish/conf.d/fish_frozen_theme.fish
if we think that the current theme is different from the default.

The default uvar-backed theme had changed over time, but existing
installations would not be upgraded.  Because of this, we have
a heuristic that assumes that values coinciding with historical
default values also stem from a default.  Some historical values are
missing. Add them.

There are more left, see 03b23dd1b6 (Update default colors,
2022-01-27).

Fixes #12725
2026-05-11 10:41:14 +08:00
Johannes Altmanninger
d601ceb55b fish_command_not_found: move non-interactive logic
In non-interactive shells we only ever use our simple command-not-found
handler; the fancy ones are only intended for interactive shells, see
537ab32dd9 (Add support for the Ubuntu 'command-no-found' handler,
which suggests a package to install in order to get a command.,
2008-01-15).

I'm not sure if this behavior difference is really a good idea,
but I guess we can avoid rocking the boat for now.

Make the implementation less surprising by moving it into the obvious
file. No behavior change intended.
2026-05-11 10:41:14 +08:00
Johannes Altmanninger
6e036740de fish_command_not_found: remove legacy event handler
We no longer emit the "fish_command_not_found" event ourselves,
so the event handlers are only useful to users who
1. run "emit fish_command_not_found"
2. copy the definition of "fish_command_not_found" to their config and run
   that with fish < 3.2.

Probably no one does 1; there are no matches in
https://github.com/search?utf8=%E2%9C%93&q=%22emit+fish_command_not_found%22+language%3Afish&type=code

Reason 2 is less relevant after 5 years.

For additional evidence, none of our specializations
("/usr/libexec/pk-command-not-found" etc.)  react to the event,
and no one has ever complained.

Stop registering any fish_command_not_found as event handler,
for consistency and simplicity.

While at it, remove the documentation on how to make it work for
version < 3.2.
2026-05-11 10:24:12 +08:00
Johannes Altmanninger
e25ebf1067 handle_command_not_found: extract constant 2026-05-11 10:24:12 +08:00
Johannes Altmanninger
1f870b360a fish_command_not_found: deduplicate /etc/os-release regex 2026-05-11 10:24:12 +08:00
Johannes Altmanninger
c1a6f6ddc8 fish_command_not_found: remove duplicate function definition 2026-05-11 10:24:12 +08:00
Johannes Altmanninger
184f4f6571 share: remove redundant continuation lines escaping 2026-05-11 10:24:12 +08:00
Johannes Altmanninger
3dc36f74bc Don't print greetings in interactive read in fake-interactive shell
If a user passes "-i" when running a script, they ought to expect
weird behavior i.e. fish might run the user's interactive-only
configuration which might print things to TTY etc.  But at least
for our part of the configuration, we can avoid depending on the
user-settable interactive bit.

__fish_config_interactive is already only called when we paint the
first prompt, either for a prompt (which implies we're an interactive
shell) or for builtin read (which does not imply anything about the
interactivity of the shell).

Only print greetings when not in interactive read. Notably, "status
is-interactive-read" is not overridable by the user.

This helps us get rid of more "status is-interactive" switches.
2026-05-11 10:21:56 +08:00
Johannes Altmanninger
9374410bb6 fish_git_prompt: remove checks for interactive bit
The "if status is-interactive" was added by ae593decfc (Replace
__fish_git_branch_prompt.fish with __fish_git_prompt.fish, 2012-06-20)
presumably to avoid repaints in noninteractive cases.  The repaints
have been removed in 76457bdc4e (fish_git_prompt: Remove repaint
from variable handlers, 2021-03-04) so this is no longer necessary.
2026-05-11 10:21:56 +08:00
Johannes Altmanninger
1e0ff8712d share: use "status is-*" instead of old-school "status --is-*" 2026-05-11 10:21:56 +08:00
Nahor
e697f960c8 windows build: disable lint checks
There is already a GitHub workflow doing lint checks so it is redundant
and wastes time (4+ min).
 
Moreover, other platforms do not do it, so when it fails, it gives
the appearance that there is a Windows specific build issue beyond
linting.

Closes #12740
2026-05-10 23:52:47 +02:00
Daniel Rainer
cab3bdabc4 l10n: update PO files 2026-05-10 15:18:40 +02:00
Fabian Boehm
5c6acdee09 string shorten: Don't produce output with --quiet
This would've printed stuff in case it didn't have to shorten (which
means it "failed" because the logic is "did something").

Fixes #12732
2026-05-10 11:16:41 +02:00
huaji2369
a9cc505d62 completions/waydroid:
add completions for adb/bugreport
add package name completions for app launch/remove
replace all -fr with -x
2026-05-09 21:55:20 -07:00
Nahor
6c6b53cdd8 check.sh: on Cygwin, only re-run the tests that mention ln
On Cygwin, check.sh was running all the tests twice, one with symlinks
enabled, and one without. But most tests do no use symlinks so
re-running those does not test anything new and it's just a major waste
of resources and time (and cygwin is quite slow already).
So only re-run the tests that use symlinks, i.e. that use `ln`.

Filter on mentions of `ln` and not `cygwin_nosymlink` because the latter
is only needed when a test would fail in one of the two scenarios, and
not all tests with symlinks do.
2026-05-09 20:38:50 -07:00
Remo Senekowitsch
f5ff9aac2b completions/dive: add image tags from podman 2026-05-09 20:20:11 -07:00
Remo Senekowitsch
0ddad4fcb1 completions/dive: use upstream completions for builtins 2026-05-09 20:20:11 -07:00
Justin Su
7d1604a116 docs/read: Clarify that --null cannot be used with --line
Fixes issue #12726
2026-05-09 20:04:33 -07:00
Peter Ammon
aa5ecd0efa Make the tmux-history-search2 test more reliable 2026-05-09 18:37:07 -07:00
David Adam
0fafff2c89 CMake: stop installing embedded files
Completions, functions, tools, and various ancillary files have been
shipped within the fish binary for some time. We don't need two copies
in packages, plus some of them are never read from the filesystem.
2026-05-10 02:06:04 +08:00
Johannes Altmanninger
1b18d08611 Fix zellij completions
Note that on some distros, this is shadowed by
/usr/share/fish/vendor_completions.d/zellij.fish.
2026-05-08 01:33:49 +08:00
Johannes Altmanninger
5a2e9f4f3c build_tools/release.sh: update milestones even when pushing fails 2026-05-08 01:33:49 +08:00
Johannes Altmanninger
c592b5d957 start new cycle
Created by ./build_tools/release.sh 4.7.1
2026-05-08 01:33:49 +08:00
Johannes Altmanninger
77285d46b8 Merge tag 4.7.1 2026-05-08 01:33:49 +08:00
Johannes Altmanninger
efb0223da1 Release 4.7.1
Created by ./build_tools/release.sh 4.7.1
2026-05-08 00:02:14 +08:00
Johannes Altmanninger
b6f30f11e4 webconfig: hack webconfig to fake interactive colors
Commit e2b18fc5b6 (config.fish: don't load default theme in
noninteractive shells, 2026-04-28) broke webconfig: since "fish_config
theme choose default" was removed from non-interactive shells,
webconfig won't know the current theme in interactive shells.

Fix this by adding secret knob that allows webconfig to have
noninteractive fish set the same colors as interactive fish again.

This assumes that plugins won't need the knob, i.e. won't need to
know the "current" theme.

Alternatively, webconfig could run "fish -i" but that could cause
issues if an "if status is-interactive" block in user-config does
something naughty such as writing to stdout even if it's not a terminal.

Alternatively, we could do

	fish -c '
		if test -z "$(__fish_theme_variables)"
			fish_config theme choose default
		end
		# can dump current theme now
	'

but that does not feel as reliable (what if the user explicitly does
"set -e" on all color variables or).

Fixes #12717
2026-05-08 00:01:25 +08:00
Remo Senekowitsch
4296e9bd75 completions/zellij: add upstream completions
Closes #12723
2026-05-07 17:52:21 +02:00
Remo Senekowitsch
894ca81464 completions/mise: add upstream completions
Part of #12723
2026-05-07 17:52:16 +02:00
Remo Senekowitsch
7c53bded3a completions/chezmoi: add upstream completions
Part of #12723
2026-05-07 17:52:00 +02:00
Remo Senekowitsch
163f25d516 completions/niri: add upstream completions
Part of #12723
2026-05-07 17:51:44 +02:00
Remo Senekowitsch
d8e73d2263 completions/just: use upstream completions
Closes #12722
2026-05-07 17:51:21 +02:00
Johannes Altmanninger
fb29c85a62 start new cycle
Created by ./build_tools/release.sh 4.7.0
2026-05-06 14:51:33 +08:00
Johannes Altmanninger
e071de3b68 Release 4.7.0
Created by ./build_tools/release.sh 4.7.0
2026-05-05 15:24:27 +08:00
Nahor
ed6fe3f315 tmux-history-search2: fix test on WSL or in console sessions
Those two do not use the "return symbol"

Closes #12704
2026-05-05 14:57:42 +08:00
Nahor
4f539dffaf cd: fix path when trying to cd out of the root directory
Part of #12704
2026-05-05 14:57:42 +08:00
Remo Senekowitsch
d885e0efd7 completions/date: add rfc-3339 option
Closes #12703
2026-05-05 14:57:23 +08:00
Johannes Altmanninger
330e897acc Update changelog 2026-05-05 14:55:56 +08:00
Peter Ammon
b638aa198f Make the tmux-history-search2.fish test pass reliably on macOS 2026-05-03 19:50:26 -07:00
Peter Ammon
fd44c23678 Suppress an annoying warning on nightly
Prior to this commit, running

> cargo +nightly bench --features benchmark --no-run

Reports:
warning: feature `test` is declared but not used
 --> src/lib.rs:1:58
  |
1 | #![cfg_attr(all(nightly, feature = "benchmark"), feature(test))]
  |                                                          ^^^^
  |
  = note: `#[warn(unused_features)]` (part of `#[warn(unused)]`) on by default

Which is a false positive. Allow unused features in this cfg_attr.
2026-05-03 18:03:37 -07:00
David Adam
f84179f8fe RPM/Debian packaging: add pkg-config dependency
This is required by the pcre2 crate to find the system PCRE2 library.
2026-05-03 23:05:03 +08:00
Johannes Altmanninger
71d6ec4ab9 wcsfilecmp: make sure trailing slashes sort first
This command

	cd $(mktemp -d)
	mkdir a "a b"
	complete -C": "

prints

	a b/
	a/

which is wrong ordering.
Usually the trailing slash should not be compared.

Fix this by always sorting slashes first.  Not sure if this is correct
for middle slashes but I couldn't find a case where it matters.

Closes #12695
2026-05-03 20:04:21 +08:00
Johannes Altmanninger
683e4c8d15 wcsfilecmp: extract function, use shadowing 2026-05-03 20:04:21 +08:00
xtqqczze
d7cc3c7bb6 format: use 2-space indents in toml files
Closes #12699
2026-05-03 20:04:21 +08:00
Johannes Altmanninger
9370830733 Make profiling API a bit harder to misuse 2026-05-03 20:03:46 +08:00
Johannes Altmanninger
161f31f42b run_1_job: remove code clone for profiling 2026-05-03 20:03:46 +08:00
Johannes Altmanninger
5998421410 Remove obsolete Send/Sync impls for ParsedSource 2026-05-03 20:03:46 +08:00
David Adam
5b1e163f22 docs/function: add caution about shadowing builtins
See #3000, #12962
2026-05-02 15:14:33 +08:00
Johannes Altmanninger
7c5fc85d96 builtin commandline: fix unintuitive clone 2026-05-01 18:35:41 +08:00
Johannes Altmanninger
2f9f46b2a5 eval_node: extract function for getting exec counts 2026-05-01 18:35:41 +08:00
Johannes Altmanninger
4b069b51e7 Remove "get_" prefix from some getters
In C++ we can't have a field and method sharing a name,
but in Rust we can.

For some structs, most getters don't have a "get_", so it's weird
that some do.  Remove the "get_" prefix where it's obvious enough.

While at it, give some related getters better names.
2026-05-01 18:35:41 +08:00
Johannes Altmanninger
398fc17b81 reader: use simpler test environment constructor
A following commit wants to pass parser by exclusive reference,
which disallows passing "parser" as well as "parser.vars()"
in one function call.  This use case also doesn't make sense.
The "OperationContext::test_only_foreground" constructor is used to
inject a special "PwdEnvironment" into the context.  When we don't need
this environment, we can use a regular constructor, which already uses
"parser.vars()".
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
12fa0d8b3d reader: make exec_prompt_cmd a free function
A following commit will pass parser as "&mut Parser".  This would
create aliasing issues in our calls to exec_prompt_cmd; make it a
free function so rustc can understand how the borrows are split.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
0441bdc634 Remove ScopeGuard::commit in favor of drop
As of commit a296ee085c (Stop returning a value from ScopeGuarding::commit,
2025-03-15) "ScopeGuard::commit()" is equivalent to "drop()".
Let's use that instead.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
d0e47cf58a Move current_filename out of LibraryData
The ScopedRefCell wrapping from library_data
is used for two things
1. to allow mutating library_data from a &Parser (for this, a RefCell would be enough)
2. to replace "current_filename" for a scope

A following commit wants to pass parser as "&mut Parser", which
voids reason 1.  It will also remove the ScopedRefCell wrapping
from LibraryData because reason 2 alone is not strong enough.  Move
"current_filename" outside of that, next to "current_node" which is
already a ScopedRefCell.  In future we could maybe consolidate them
into one field, like (or even merging with) ScopedData.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
d35aa3860a Fix weird initial value for internal job ID
InternalJobId(0) aka InternalJobId::default() is treated specially
so it should not be given to a regular job.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
5971e79c3f Strong type for internal job IDs 2026-05-01 18:03:13 +08:00
Johannes Altmanninger
dad660cda5 Move internal job ID type
Move this type to where its non-default instances are constructed.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
8328e53050 reader: reduce variable scope 2026-05-01 18:03:13 +08:00
Johannes Altmanninger
4aadeea184 parser: remove unused field
This has been moved to InputData.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
df5067cc1c parse_execution: remove pipeline node reference from ExecutionContext
Upcoming changes will pass Parser by exclusive reference ("&mut") which
prevents aliasing; let's remove an alias which seems simpler anyway.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
3d708d6fc1 history: remove redundant argument 2026-05-01 18:03:13 +08:00
Johannes Altmanninger
a564238d82 highlight_and_colorize: remove redundant environment argument
This highlighting function is always called with with an operation
context created from a parser; Since parser.context().vars() is the same
as parser.vars(), we can use the former, reducing the number of aliases.
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
64443aa173 Lower OnceLock to LazyLock
LazyLock is less powerful so we should use it when possible.

Ref: https://github.com/fish-shell/fish-shell/pull/12661#discussion_r3158097032
2026-05-01 18:03:13 +08:00
Johannes Altmanninger
d2c2b23d1f Fix "fish -d reader" crash on left mouse click
See #12693
2026-05-01 18:00:37 +08:00
Daniel Rainer
4e3898d0d7 feat: xtask gettext
Rewrite the PO file handling logic in Rust and make it available via an
xtask. Replaces the
`build_tools/{update_translations,fish_xgettext}.fish` scripts.

Main benefits:
- Better ergonomics
- Better error handling
- Eliminates the need for a fish executable for updating PO files,
  which is particularly useful in CI
- Improved performance, mainly due to concurrent threads working on the
  PO files in parallel

The behavior is mostly unchanged, with the minor exception that section
headers for empty sections are now omitted in PO files.
The interface for invoking the tooling is quite different. Instead of
working with flags, `cargo xtask gettext` has 3 subcommands:
- `update` modifies the PO files to match the current sources
- `check` is like update, but instead of modifying the PO files, it
  shows diffs between the current version of the PO files and what they
  would look like after updating. When there is a difference, the xtask
  exits non-zero, making it useful for checks to detect outdated PO
  files.
- `new` creates a new PO file for the given language.

Both the `update` and `check` command take any number of file paths to
specify the PO files to consider. If none are specified, all files in
`localization/po/` are considered.

Extracting gettext messages from Rust still requires compiling with the
`gettext-extract` feature active. In situations where compilation is
needed for other purposes as well, it can make sense to only build once
and then tell the gettext xtask about the directory into which the
messages have been extracted. This can be done via the
`--rust-extraction-dir` flag. If we stop having gettext messages in
Rust, this logic can be removed.

Closes #12676
2026-04-30 17:31:03 +00:00
Daniel Rainer
3ad45d8fb1 feat: make as_os_strs easier to use
Make trailing comma optional.

Return array, rather than reference to array, to eliminate lifetime
issues.

Closes #12688
2026-04-30 18:16:43 +08:00
xtqqczze
39bd54cb49 highlight: derive Display trait for HighlightRole 2026-04-28 21:49:51 +02:00
Johannes Altmanninger
281399561b Distinguish builtin read history session ID from private mode
Fixes #12662
2026-04-29 01:55:32 +08:00
Johannes Altmanninger
e5f57b1daf history: fix constructor naming
The only public constructor should be called new().
2026-04-29 01:55:32 +08:00
Johannes Altmanninger
6c04a72697 history: hide private constructor 2026-04-29 01:55:32 +08:00
Johannes Altmanninger
1034945690 Fix regression causing "nosuchcommand || hello" to short-circuit
Commit 3534c07584 (Adopt the new AST in parse_execution, 2020-07-03)
added to parse_execution_context_t::run_job_conjunction an early
return when any job in a job conjunction fails to launch.  This causes
"nosuchcommand || echo hello" to not execute the continuation.

Fix this by restoring the previous behavior.

Fixes #12654
2026-04-29 01:55:32 +08:00
Johannes Altmanninger
e2b18fc5b6 config.fish: don't load default theme in noninteractive shells
We define colors in noninteractive shells for historical reasons
(because colors used to be universal variables).

The other potential reason is to get regular syntax highlighting for
commands like:

	fish -c 'read --shell'

but if anyone actually uses that they can probably load a theme
explicitly.

Stop defining colors in noninteractive shells.  It's usually not
a good idea to make them behave differently from interactive ones,
but color seems only relevant for interactive shells?

Let's see if anyone complains.. we may end up reverting this if people
want to use noninteractive fish to query colors..  but I'm not sure
why that would be necessary.

Closes #12673
2026-04-29 01:48:47 +08:00
Johannes Altmanninger
319b093ef8 autoload: improve enum naming 2026-04-28 23:11:33 +08:00
Johannes Altmanninger
ab2678082e builtin string: add names to RegexError enum fields 2026-04-28 23:11:33 +08:00
Johannes Altmanninger
81e8eebd8d Use UpperCamelCase for enum variants
Missed in 17ba602acf (Use PascalCase for Enums, 2025-12-14).

Fixes #12647
2026-04-28 23:11:33 +08:00
Johannes Altmanninger
2b41f132be Remove obsolete comment working around late fish_indent bug 2026-04-28 15:41:15 +08:00
Johannes Altmanninger
688d1954a8 Fix unused import on systems without eventfd (Cygwin) 2026-04-28 15:27:35 +08:00
Johannes Altmanninger
96695a2859 Document how to remove workaround for Cygwin select() 2026-04-28 14:49:28 +08:00
Johannes Altmanninger
f2b0706494 reader: repaint commands to not disable "last_cmd"-based UI states (pager etc.)
"commandline -f repaint" might be triggered for various reasons;
since this sets "last_cmd", it will reset some UI states, notably
pager selection:

1. press tab
2. trigger repaint
3. press tab

The repaint prevents us from selecting the first candidate.

Work around this by ignoring repaint events for the last_cmd logic.

Fixes #12683
2026-04-28 14:43:55 +08:00
Johannes Altmanninger
c91bfba08c env_dispatch: reduce scope of captured $TERM local var 2026-04-28 14:19:51 +08:00
Daniel Rainer
cc40fa4a4c completions: use typst's built-in completions
https://github.com/typst/typst/pull/6568 (merged 2025-07-09), presumably
released in 0.14.0 (2025-10-24) introduces completion generation in
typst. Use them to replace our outdated manual completions.

Closes #12679

Closes #12684
2026-04-28 13:51:24 +08:00
Johannes Altmanninger
ff6ee65deb Assert that FD monitor Drop implementation is really test-only 2026-04-28 13:51:24 +08:00
Nahor
1771a325aa CI: enable check.sh on Windows
Closes #12171
2026-04-28 13:51:24 +08:00
Nahor
58648054c0 fd_monitor: wait for select() to return when removing an item
It is unspecified what `select()` returns if a descriptor is closed
while `select()` uses it. This can result in spurious error messages,
notably in Cygwin.

Also delete corresponding tests since they don't really help with
anything. Any `select()` result is valid when a socket is closed, so
checking that result is pointless. Moreover, fish already does not rely
on any specific result beyond logging.

Part of #12171
2026-04-28 13:51:24 +08:00
Nahor
27fb4d6731 Always heightenize file descriptors
Fixes #12618

Closes #12681
2026-04-28 13:51:24 +08:00
Nahor
6701b7f6c8 parser: remove unused cwd_fd field
Part of #12681
2026-04-28 13:51:24 +08:00
Johannes Altmanninger
e175a317af proc: use shorthand method for reading file /proc/pid/stat 2026-04-28 13:51:24 +08:00
Jaakko Koivisto
7b98a275fe Added 'updates' -directory to the kernel module locations.
Linux kernel modules installed by target 'modules_install' are installed
to '/usr/lib/<kernel>/updates'. This applies to both out-of-tree kernel
modules, or when building in-tree modules individually.

Module tools like 'modprobe' and 'modinfo' search the
'updates'-directory automatically, so it should be expected that fish
autocomplete to provide these modules as well.

Closes #12682
2026-04-28 13:51:24 +08:00
Johannes Altmanninger
b78dc4fbec completions/sudo-rs: fix when sudo is not installed
Fixes #12678
2026-04-28 13:51:24 +08:00
Johannes Altmanninger
12e97ea7fc fd monitor: hide test-only method 2026-04-28 13:15:28 +08:00
Johannes Altmanninger
af8594c611 Fix inconsistent case 2026-04-27 15:18:01 +08:00
Johannes Altmanninger
006fa86ef4 tests/checks/tmux-source.fish: reduce flakiness
As seen in
https://github.com/fish-shell/fish-shell/actions/runs/24944417077/job/73043241890?pr=12171

	Failure:

	  The CHECK on line 12 wants:
	    prompt 1> source -

	  which failed to match line stdout:3:
	    source -

	  Context:
	    prompt 0> source
	    source: missing filename argument or input redirection
	    source - <= no check matches this, previous check on line 11
	    prompt 1> source -
	    prompt 1>
2026-04-26 13:15:23 +08:00
Daniel Rainer
9b04300dc3 refactor: use anyhow for xtask errors
Terminating the process at arbitrary points with `std::process::exit`
when errors occur has several problems. There is a lack of information
about what lead up to the error, and it prevents destructors from
running, which in the cases of xtasks can for example result in
temporary files being left on the file system.

Instead, use `anyhow` which conveniently integrates with Rust's Result
type, allowing to return `anyhow::Result<T>`, which is an alias for
`Result<T, anyhow::Error>`, which is compatible with any error type that
implements `std::error::Error`. The advantages of using `anyhow` over
plain `Result`s are that it makes it easier to handle different error
types, attach context to errors, and show the call/context stack
associated with the error. Returning an `anyhow::Result<()>` from `main`
is possible because it implements `std::process::Termination`, so we get
automatic error reporting and corresponding exit codes by simply
bubbling up errors to `main`, attaching context as desired, and finally
returning the result from `main.`

In addition to removing the `std::process::exit` calls, this commit also
improves error handling in a few spots in other ways, such as replacing
`unwrap` by returning errors.

Closes #12674
2026-04-26 13:12:25 +08:00
Daniel Rainer
c80496fad1 cleanup: remove useless variable
Closes #12675
2026-04-25 17:08:03 +08:00
Johannes Altmanninger
ca56949028 release-notes.sh: fix language 2026-04-24 18:28:02 +08:00
Nathaniel
fa74d0fe54 complections/systemctl add missing subcommands
reorder subcommand descriptions

remove unused subcommands

add extra subcommand descriptions

remove old version check

Closes #12672
2026-04-24 13:34:22 +08:00
cunlem
59f3719e95 Allow opening script read-only with editor
Closes #12671
2026-04-24 13:30:25 +08:00
Johannes Altmanninger
170c171e85 shellcheck: lower OnceLock to LazyLock 2026-04-24 13:28:54 +08:00
Johannes Altmanninger
c33ca660e3 Replace OnceLock<()> with better(?) alternatives 2026-04-24 13:26:22 +08:00
Johannes Altmanninger
f7c336021b threads: ThreadId type 2026-04-23 19:12:40 +08:00
Johannes Altmanninger
523e25df17 reader: fix improper use of get_or_init() 2026-04-23 19:12:40 +08:00
Johannes Altmanninger
c8b28d4d24 cargo-test: remove unnecessary TTY initialization 2026-04-23 19:12:40 +08:00
Johannes Altmanninger
ba35214e1e Fix exit handlers being called on panic in background threads
Commit 1286745e78 (Remove bits for async-signal-safety of old SIGTERM
handler, 2026-04-11) introduced inconsistency; fix that.
2026-04-23 16:17:45 +08:00
Johannes Altmanninger
d05d8557a7 build_tools/*.sh: fix inconsistent bash shebang 2026-04-22 14:47:51 +08:00
Daniel Rainer
a3dc57873c lint: run shellcheck in CI
Closes #12661
2026-04-22 14:38:22 +08:00
Daniel Rainer
0c078c179d lint: run shellcheck xtask in main checks
Part of #12661
2026-04-22 14:28:45 +08:00
Daniel Rainer
ca443e2e54 lint: add xtask for running ShellCheck
ShellCheck does not have a built-in way of detecting which files it
should check, so we use ripgrep's `ignore` library to find files not
ignored by our gitignore rules, and then look for a non-fish shebang in
the first line of the file. The resulting shell scripts are then passed
to ShellCheck.

Part of #12661
2026-04-22 14:28:45 +08:00
Daniel Rainer
63c3306e6c lint: fix ShellCheck warnings
Part of #12661
2026-04-22 14:23:25 +08:00
Johannes Altmanninger
923d0b7974 config: use default XDG_DATA_DIRS when unset or empty
Installing a program like sway to /usr/local installs fish
completions to /usr/local/share/fish/vendor_completions.d/sway.fish.
When $XDG_DATA_DIRS is empty, these will typically not
be picked up.

(Since "__extra_completionsdir" is usually
"/usr/share/fish/vendor_completions.d/", this issue typically only
affects "/usr/share", not "/usr".)

Fix this by using the correct fallback value for XDG_DATA_DIRS.

Fixes #11349

Closes #12656
2026-04-22 14:21:09 +08:00
joveian
52998635f9 Avoid losing work in funced when no changes between parse errors
From the inital dd69ca5 commit that started checking if the file was modified
the initial checksum to compare against has been updated in the loop, causing
funced to lose work silently if you get a parse error, can't find the issue,
and want to look at the error message again.

Closes #12663
2026-04-22 00:53:56 +08:00
Saúl Nogueras
1ccf4ad480 Fix wget completion typo: non-verbose -> no-verbose
Closes #12664
2026-04-22 00:48:26 +08:00
Johannes Altmanninger
23b5b01242 Prune stale gitignore rules
After a few changes to our build system, lots of gitignore rules
are obsolete. Meanwhile, in-tree CMake builds are missing some rules
like "/cargo/".

Drop the obsolete ones, and add the in-tree CMake ones for now.
Also add ".venv/" (used by build_tools/release.sh).
Also limit some rules like .vscode to top-level (?).
2026-04-22 00:09:57 +08:00
Daniel Rainer
ca2b5dc40b checks: run with all features enabled
As discussed in #12649, we should check builds with all Cargo features
enabled. Previously, this did cause issues with the `benchmark` feature,
since that only works with nightly Rust. #12653 resolves that by only
enabling the `benchmark` feature with the nightly toolchain, so now we
can use `--all-features` with stable Rust.

Closes #12657
2026-04-20 21:21:24 +08:00
Armandas Jarušauskas
0dfe06f4c9 webconfig: highlight table entries on hover
- Makes it easier to identify which history entry is being deleted.
- Remove gap between rows that becomes visible on hover.
- Makes delete button a bit nicer looking by centering it and giving it a bit more space from the edge.

Closes #12659
2026-04-20 21:21:24 +08:00
xtqqczze
4e47f47d85 clippy: fix question_mark lint
https://rust-lang.github.io/rust-clippy/master/index.html#question_mark

Closes #12658
2026-04-20 21:21:24 +08:00
xtqqczze
f3e43e932f clippy: fix byte_char_slices lint
https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slices

Part of #12658
2026-04-20 21:21:24 +08:00
Johannes Altmanninger
1dfc75bb9c Better name for async-signal-safe functions
In Rust, "safety" is usually used in the context of unsafe functions,
which have documented preconditions.  Our async-signal-safe functions
are different; they offer extra safety properties. Rename them to
reduce confusion.

Ref: https://github.com/fish-shell/fish-shell/pull/12625#discussion_r3067819966
2026-04-20 21:21:24 +08:00
Johannes Altmanninger
fa33f6f0e0 tests/checks/disown.fish: improve test robustness
If the job never gets into stopped state, it will keep running forever.
Narrow the wait condition, to prevent a timeout in failure scenarios.
2026-04-20 17:03:09 +08:00
Johannes Altmanninger
31363120aa build_tools/version-available-in-debian.sh: fix for BSD sed
Fixes https://github.com/fish-shell/fish-shell/pull/12651#issuecomment-4275827646
2026-04-20 09:57:42 +08:00
xtqqczze
2304077e0d gate benchmark feature on nightly toolchain
Closes #12653
2026-04-19 17:38:04 +08:00
xtqqczze
86c052b6ba fix non_upper_case_globals lint
Closes #12648
2026-04-19 17:37:41 +08:00
xtqqczze
68472da48a highlight: derive Display trait for HighlightRole
Closes #12645
2026-04-19 17:14:42 +08:00
Daniel D. Beck
4b172fc735 set_color: document output more prominently
Issue: https://github.com/fish-shell/fish-shell/issues/2378

Closes #12644
2026-04-19 17:14:42 +08:00
Nahor
944ab91fab tests: various fixes for Cygwin itself and ACL mounts
Most notably:
- Unlike MSYS, Cygwin seems to always properly handle symlinks (at least
in common scenarios)
- With ACL, "x" permission also requires "r" do to anything, be it files
or directories

Closes #12642
2026-04-19 17:09:36 +08:00
Johannes Altmanninger
34535fcb61 tests/checks/disown: fix signal delivery race
Intermittent test failure suggests that kill(3p) returns before the
signal is delivered.  Fix the failure by waiting until the signal
has been delivered before continuing the test.

Fixes #12635
2026-04-19 17:07:39 +08:00
Johannes Altmanninger
9e4eb37696 complete: remove stale comment
Commit a4b6348315 (clippy: fix collapsible_match lint, 2026-04-18) made
it so '$' characters are handled here, which contradicts the comment.
Remove it.
2026-04-19 15:55:13 +08:00
Johannes Altmanninger
dda76d7f18 Update to Rust 1.95 2026-04-19 15:53:36 +08:00
Johannes Altmanninger
fdb1d95521 updatecli.d/rust.yml: fix staleness check when using rustup 1.29 2026-04-19 15:53:08 +08:00
xtqqczze
937f3bc6cb Update to Rust 1.94 2026-04-19 00:17:30 +00:00
Daniel Rainer
ebc32adc09 clippy: fix map_unwrap_or lint
https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#map_unwrap_or
2026-04-18 23:27:51 +00:00
xtqqczze
a4b6348315 clippy: fix collapsible_match lint
https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#collapsible_match
2026-04-18 23:16:34 +00:00
xtqqczze
b21a4a7197 benchmark: fix unresolved import error
```rust
error[E0432]: unresolved import `crate::common::bytes2wcstring`
   --> src/common.rs:714:9
    |
714 |     use crate::common::bytes2wcstring;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `bytes2wcstring` in `common`
```
2026-04-18 21:28:15 +00:00
xtqqczze
0cd227533f highlight: implement Display trait for HighlightRole 2026-04-17 20:27:24 -07:00
Johannes Altmanninger
5eb7687a64 tests/checks/tmux-complete4.fish: fix for macOS sed 2026-04-17 03:22:56 +08:00
Johannes Altmanninger
8d6426295e complete: automatically resolve REPLACES_TOKEN flag
This flag is implied by matches that require replacements.  Reflect that
in the Completion::new, reducing the number of places where we raise the
flag.  This slightly simplifies tasks like proving the parent commit.

There are other scenarios (e.g. wildcards) where we currently set
the flag additionally.
2026-04-17 01:31:29 +08:00
Johannes Altmanninger
85e76ba356 Fix option substr completions not being filtered out
Commit 3546ffa3ef (reader handle_completions(): remove dead filtering code,
2026-01-02) gives a proof of correctness that still makes sense;
The first lemma ("if will_replace_token") is trivially true, so no need to
assert it.
The second lemma ("if !will_replace_token") is violated in some edge cases:
we claim that given a token "-c", the option completion "--clip" is an exact match,
which is not true, it's a substring match.

Fix that, asserting the claim.
2026-04-17 01:31:29 +08:00
Johannes Altmanninger
fee4288122 complete: reuse string fuzzy match when completing ~$USER
If we get to this code path, we'll only get completions for user
names, so technically the full StringFuzzyMatch with its ranking of
samecase/smartcase/icase (only showing the best) might be overkill,
but it seems like a good idea to treat this the same way as other
completions.

The occasion for this commit is to correct a wrong
StringFuzzyMatch::exact_match() in the icase branch; which will be
important for a following commit.  Add a test for that.
2026-04-17 01:28:54 +08:00
Johannes Altmanninger
413246a93d reader handle_completions(): move loop-invariant code 2026-04-17 01:28:02 +08:00
Daniel Rainer
3cb939c9a8 fix: actually run without symlinks
The old behavior seems to have been introduced inadvertently:
https://github.com/fish-shell/fish-shell/pull/12636#issuecomment-4254328105

Closes #12636
2026-04-16 15:10:15 +08:00
Daniel Rainer
4790a444d8 lint: disable incorrect warning about unused fn
`cleanup` is used via `trap`.

Part of #12636
2026-04-16 15:10:15 +08:00
Daniel Rainer
da924927a0 cleanup: split up assignment and export
This prevents hiding failures of the `rustc` command.

Part of #12636
2026-04-16 15:10:15 +08:00
Daniel Rainer
29ff2fdd43 lint: disable warning about variable export
Here, we want `"$@"` to be expanded, since its components are the
arguments we want to pass to `export`.

Part of #12636
2026-04-16 15:10:14 +08:00
Daniel Rainer
732c04420b lint: disable warnings about desired behavior
We deliberately create subshells for the export in these cases, so we
don't want warnings about it.

Part of #12636
2026-04-16 15:10:14 +08:00
Daniel Rainer
947abd7464 cleanup: quote shell variables
This is not a functional change, since the variable names don't have
spaces, but it is more robust to changes and removes ShellCheck warnings

Part of #12636
2026-04-16 15:10:14 +08:00
Daniel Rainer
12cfe59578 fix: don't use echo -e in POSIX shell
The `-e` flag is not defined for `echo` in POSIX shell. Use `printf`
instead.

Part of #12636
2026-04-16 15:10:13 +08:00
Daniel Rainer
4b60d18b44 cleanup: remove unused variable
Part of #12636
2026-04-16 15:10:13 +08:00
Daniel Rainer
dd8e59db03 fix: check if system_tests args are empty
When `system_tests` is called without arguments, `[ -n "$@" ]` becomes
`[ -n ]`, which is true, resulting in running `export`, which lists all
exported variables, unnecessarily cluttering the output.
If `system_tests` is called with more than one argument, the check would
fail because having more than one argument after `-n` is invalid syntax.
Fix this by using `$*`, which concatenates all positional arguments to
`system_tests` into a single value.

Part of #12636
2026-04-16 15:10:13 +08:00
Johannes Altmanninger
47a3757f73 update changelog 2026-04-14 16:56:43 +08:00
Johannes Altmanninger
f278c29733 key: address "non_upper_case_globals" lint on named key constants 2026-04-14 16:56:43 +08:00
Peter Ammon
2bab8b5830 prompt_pwd: strip control characters
If a directory has a control sequence in it, then prompt_pwd (used in
the default prompt) would emit it to the console, which could cause
the terminal to interpret the escape sequence.

Strip control sequences from within prompt_pwd, in the same way as
we do in __fish_paste.fish, to sanitize it.

Closes #12629
2026-04-14 16:56:43 +08:00
Johannes Altmanninger
1dac221684 doc terminal_compatibility: tab title is OSC 1 2026-04-14 16:56:43 +08:00
David Adam
8dbbe71bc6 disable Linux development builds for now
I'll add the rest of the infrastructure later.
2026-04-13 21:12:51 +08:00
David Adam
d9d9eced98 workflow: build development builds on master branch 2026-04-12 21:11:26 +08:00
David Adam
64a829f0df add workflow to create development build source packages 2026-04-12 18:50:56 +08:00
Nahor
440e7fcbc1 Fix failing system tests on Cygwin
The main changes are:
- disabling some checks related to POSIX file permissions when a filesystem is
mounted with "noacl" (default on MSYS2)
- disabling some checks related to symlinks when using fake ones (file copy)

Windows with acl hasn't been tested because 1) Cygwin itself does not have any
Rust package yet to compile fish, and 2) MSYS2 defaults to `noacl`

Part of #12171
2026-04-11 18:55:00 +08:00
Nahor
52495c8124 tests: make realpath tests easier to debug
- Use the different strings for different checks to more easily narrow down
where a failure happens
- Move CHECK comments outside a `if...else...end` to avoid giving the impression
that the check only runs in the `if` case.

Part of #12171
2026-04-11 18:44:22 +08:00
Vishrut Sachan
467b03d715 git completions: prioritize recent commits for rebase --interactive
Fixes #12537

Closes #12619
2026-04-11 18:33:05 +08:00
Johannes Altmanninger
b5c40478f6 Fix typo, closes #12586, closes #12577 2026-04-11 18:33:05 +08:00
Johannes Altmanninger
1286745e78 Remove bits for async-signal-safety of old SIGTERM handler
This is implied by the parent commit.

To enable this, stop trying to run cleanup in panic handlers if we
panic on a background thread.
2026-04-11 18:25:26 +08:00
Yakov Till
b99ae291d6 Save history on SIGTERM and SIGHUP before exit
Previously, SIGTERM immediately re-raised with SIG_DFL, killing
fish without saving history. SIGHUP deferred via a flag but never
re-raised, so the parent saw a normal exit instead of signal death.

Unify both signals: the handler stores the signal number in a single
AtomicI32, the reader loop exits normally, throwing_main() saves
history and re-raises with SIG_DFL so the parent sees WIFSIGNALED.

Fixes #10300

Closes #12615
2026-04-11 18:06:02 +08:00
Daniel Rainer
8ae71c80f4 refactor: extract string escape and unescape funcs
Move the functions for escaping and unescaping strings from
`src/common.rs` into `fish_common`. It might make sense to move them
into a dedicated crate at some point, but for now just move them to the
preexisting crate to unblock other extraction.

Closes #12625
2026-04-11 17:49:50 +08:00
Daniel Rainer
cf6170200c refactor: move const to fish_widestring
Another step to eliminate dependency cycles between `src/expand.rs` and
`src/common.rs`.

Part of #12625
2026-04-11 17:49:49 +08:00
Daniel Rainer
c13038b968 refactor: move move char consts to widestring
This time, move char constants from `src/expand.rs` to
`fish_widestring`, which resolves a dependency cycle between
`src/expand.rs` and `src/common.rs`.

Part of #12625
2026-04-11 17:49:48 +08:00
Daniel Rainer
816077281d refactor: move encoding functions to widestring
The decoding functions for our widestrings are already in the
`fish_widestring` crate, so by symmetry, it makes sense to put the
encoding functions there as well. This also makes it easier to depend on
these functions, giving more options when it comes to further code
extraction.

Part of #12625
2026-04-11 17:49:47 +08:00
Daniel Rainer
78ea24a262 refactor: move char definitions into widestring
Use `fish_widestring` as the place where char definitions live. This has
the advantage that all our code can depend on `fish_widestring` without
introducing dependency cycles. Having a common place for character
definitions also makes it easier to see which chars have a special
meaning assigned to them.

This change also unblocks some follow-up refactoring by removing a
dependency cycle between `src/common.rs` and `src/wildcard.rs`.

Part of #12625
2026-04-11 17:49:46 +08:00
Daniel Rainer
6a5b9bcde1 refactor: move PUA-decoding function to widestring
These functions don't depend on `wcstringutil` functionality, so there
is no need for them to be there. The advantage of putting them into our
`widestring` crate is that quite a lot of code depends on it, and
extracting some of that code would result in crate dependency cycles if
the functions stayed in the `wcstringutil` crate. Our `widestring` crate
does not depend on any of our other crates, so there won't be any cyclic
dependency issues with code in it.

Part of #12625
2026-04-11 17:49:45 +08:00
Daniel Rainer
b7b786aabf cleanup: move escape_string_with_quote
It makes a lot more sense to have this function in the same module as
the other escaping functions. There was no usage of this function in
`parse_util` except for the test, so it makes little sense to keep the
function there. Moving it also eliminates a pointless cyclic dependency
between `common` and `parse_util`.

Part of #12625
2026-04-11 17:49:43 +08:00
Daniel Rainer
dc63b7bb20 cleanup: don't export write_loop twice
Exporting it as both `safe_write_loop` and `write_loop` is redundant and
causes inconsistencies. Remove the `pub use` and use `write_loop` for
the function name. It is shorter, and in Rust the default assumption is
that code is safe unless otherwise indicated, so there is no need to be
explicit about it.

Part of #12625
2026-04-11 17:49:42 +08:00
Daniel Rainer
9c819c020e refactor: don't reexport fish_common in common
Not reexporting means that imports have to change to directly import
from `fish_common`. This makes it easier to see which dependencies on
`src/common.rs` actually remain, which helps with identifying candidates
for extraction.

While at it, group some imports.

Part of #12625
2026-04-11 17:49:41 +08:00
Daniel Rainer
dc9b1141c8 refactor: extract fish_reserved_codepoint
Part of #12625
2026-04-11 17:49:40 +08:00
Daniel Rainer
3d364478ee refactor: remove dep on key mod from common
Removing this dependency allows extracting the `fish_reserved_codepoint`
function, and other code depending on it in subsequent commits.

Part of #12625
2026-04-11 17:49:39 +08:00
Daniel Rainer
faf331fdad refactor: use macro for special key char def
Reduce verbosity of const definitions. Define a dedicated const for the
base of the special key encoding range. This range is 256 bytes wide, so
by defining consts via an u8 offset from the base, we can guarantee that
the consts fall into the allocated range. Ideally, we would also check
for collisions, but Rust's const capabilities don't allow for that as
far as I'm aware.

Having `SPECIAL_KEY_ENCODE_BASE` in the `rust-widestring` crate allows
getting rid of the dependency on `key::Backspace` in the
`fish_reserved_codepoint` function, which unblocks code extraction.

Part of #12625
2026-04-11 17:49:37 +08:00
Daniel Rainer
47b6c0aec2 cleanup: rename and document UTF-8 decoding function
While the function is only used to decode single codepoints, nothing in
its implementation limits it to single codepoints, so the name
`decode_one_codepoint_utf8` is misleading. Change it to the simpler and
more accurate `decode_utf8`. Add a doc comment to describe the
function's behavior.

Part of #12625
2026-04-11 17:49:36 +08:00
Daniel Rainer
eb478bfc3e refactor: remove syntactic deps on main crate
Part of #12625
2026-04-11 17:49:35 +08:00
Johannes Altmanninger
63cf79f5f6 Reuse function for creating sighupint topic monitor 2026-04-11 17:27:25 +08:00
Johannes Altmanninger
ff284d642e tests/checks/tmux-abbr.fish: fix on BusyBox less (alpine CI) 2026-04-11 17:03:46 +08:00
Johannes Altmanninger
cc64da62a9 fish_color_valid_path: also apply bg and underline colors
Closes #12622
2026-04-11 17:03:46 +08:00
Johannes Altmanninger
a974fe990f fish_color_valid_path: respect explicit normal foreground 2026-04-11 17:03:46 +08:00
David Adam
39239724ec make_vendor_tarball: drop unused tar search 2026-04-10 05:56:00 +08:00
Daniel Rainer
524a7bac6e l10n: restore translations from error rewrite
Most translations were adjusted correctly, but a few were missed, so
restore them here.

Closes #12623
2026-04-09 01:36:53 +08:00
Nahor
d649c2aab4 string: remove StringError::NotANumber
Use the more generic `StringError::InvalidArgs` instead

Closes #12556
2026-04-08 14:11:31 +08:00
Nahor
30e6aa85e2 error rewrite: use new Error to report errors
To homogenize error reporting format, use a new Error struct. Currently this
is used for builtins and ensuring a common cmd/subcmd prefix.

Part of #12556
2026-04-08 14:11:31 +08:00
Nahor
abd7442521 printf: de-duplicate code
Part of #12556
2026-04-08 14:05:05 +08:00
Johannes Altmanninger
f5c48038b5 Skip tmux-abbr's "pipe builtin into less" test on BusyBox less
Fails on Alpine CI.
2026-04-08 14:04:15 +08:00
Daniel Rainer
895a6e7034 l10n(fr): use non-breaking space before colon
Closes #12617
2026-04-08 13:54:04 +08:00
Johannes Altmanninger
2193e88423 Pass job group down to builtin_print_help
Repro:

	$ abbr -h | less
	fish: Job 1, 'abbr --help | less' has stopped

Bisects to 5ece9bec6c (__fish_print_help: use man as-is, 2025-08-29).

Root cause is similar to
https://github.com/fish-shell/fish-shell/issues/8699#issuecomment-1030762748
except it's specific to how __fish_print_help is invoked, since
"__fish_print_help abbr | less" works fine.

Fix this by letting __fish_print_help access the terminal.

Fixes #12612

Solved by Claude in a single prompt:

---

 ▐▛███▜▌   Claude Code v2.1.92
▝▜█████▛▘  Sonnet 4.6
  ▘▘ ▝▝    ~/git/fish-shell

❯ "abbr -h | less" gets a signal, resulting in the message "fish: Job 1, 'abbr -h | less' has stopped"
whereas "__fish_print_help abbr | less" works just fine, even though "abbr -h" should run exactly that.
Why this difference?

[...]

● Now I can give you a clear answer.

  ---
  The difference comes down to job group inheritance when man is
  spawned.

  The deferred execution order

  In abbr -h | less, fish uses the "deferred process" optimization:
  since abbr is a non-external process piping into an external one,
  fish launches less first,
   transfers the terminal to less's process group, then runs the
   deferred abbr builtin. By the time abbr's help function spawns man,
   the terminal already
  belongs to less's pgroup.

  Same thing happens with __fish_print_help abbr | less — the outer
  less is launched first and gets the terminal.

  Where the paths diverge

  Function case (__fish_print_help abbr | less):
  get_performer_for_function (exec.rs:1050) captures job.group.clone()
  and passes it to eval_node. So when command man runs inside the
  function, it's given the outer pipeline's job group — the same
  one that owns the terminal. man's inner less pager joins that group
  and can read from the terminal freely. ✓

  Builtin case (abbr -h | less): builtin_print_help calls
  parser.eval(&cmd, streams.io_chain) (shared.rs:652). parser.eval
  is defined as:

  pub fn eval(&self, cmd: &wstr, io: &IoChain) -> EvalRes {
      self.eval_with(cmd, io, None, BlockType::top, false)
      //                      ^^^^ job_group = None
  }

  job_group = None means man creates a brand-new job group with no
  terminal access. When man spawns its less pager, the pager tries
  to read keyboard input from
   the terminal, but the terminal belongs to the outer less's process
   group. The pager receives SIGTTIN → stops → the whole job is
   reported as stopped.

  The fix

  builtin_print_help should pass streams.job_group to the eval call
  (which is already populated at exec.rs:1192). Changing line 652 from:

  let res = parser.eval(&cmd, streams.io_chain);

  to:

  let res = parser.eval_with(&cmd, streams.io_chain, streams.job_group.as_ref(), BlockType::top, false);

  would give man the outer job's job group, letting its pager access
  the terminal just like the function case.

✻ Sautéed for 13m 0s
2026-04-07 20:21:38 +08:00
Kayce Basques
86c3778c2a docs: Format path as inline code
Closes #12609
2026-04-07 18:52:36 +08:00
354 changed files with 17913 additions and 96259 deletions

View File

@@ -1,49 +0,0 @@
env:
CIRRUS_CLONE_DEPTH: 100
CI: 1
linux_task:
matrix:
- name: alpine
container: &step
image: ghcr.io/krobelus/fish-ci/alpine:latest
memory: 4GB
- name: ubuntu-oldest-supported
container:
<<: *step
image: ghcr.io/krobelus/fish-ci/ubuntu-oldest-supported:latest
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
freebsd_task:
matrix:
- name: FreeBSD Stable
freebsd_instance:
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
tests_script:
- pkg update
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
# libclang.so is a required build dependency for rust-c++ ffi bridge
- pkg install -y llvm
# BSDs have the following behavior: root may open or access files even if
# the mode bits would otherwise disallow it. For example root may open()
# a file with write privileges even if the file has mode 400. This breaks
# our tests for e.g. cd and path. So create a new unprivileged user to run tests.
- pw user add -n fish-user -s /bin/csh -d /home/fish-user
- mkdir -p /home/fish-user
- chown -R fish-user /home/fish-user
- mkdir build && cd build
- chown -R fish-user ..
- sudo -u fish-user -s whoami
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- sudo -u fish-user -s ninja -j 6 fish
- sudo -u fish-user -s ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'

View File

@@ -30,5 +30,5 @@ max_line_length = unset
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
max_line_length = 72
[*.yml]
[*.{toml,yml}]
indent_size = 2

View File

@@ -13,11 +13,9 @@ runs:
command -v uv
command -v uvx
# Check that pyproject.toml and the lock file are in sync.
# TODO Use "uv" to install Python as well.
: 'Note that --no-managed-python below would be implied but be explicit'
uv='env UV_PYTHON=python uv --no-managed-python'
$uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
# Install globally.
sudo $uv pip install --group=dev --system --break-system-packages
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
uv venv ~/.local --allow-existing
uv pip install --group=dev
# Smoke test.
python -c 'import sphinx; import sphinx_markdown_builder'
python3 -c 'import sphinx; import sphinx_markdown_builder'

View File

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

View File

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

View File

@@ -1,64 +0,0 @@
name: Build Docker test images
on:
push:
branches:
- master
paths:
- 'docker/**'
workflow_dispatch:
concurrency:
group: docker-builds
env:
REGISTRY: ghcr.io
NAMESPACE: fish-ci
jobs:
docker-build:
if: github.repository_owner == 'fish-shell'
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: ubuntu-oldest-supported
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
-
name: Login to Container registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0, build_tools/update-dependencies.sh
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0, build_tools/update-dependencies.sh
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
flavor: |
latest=true
-
name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0, build_tools/update-dependencies.sh
with:
context: docker/context
push: true
file: docker/${{ matrix.target }}.Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -0,0 +1,32 @@
name: Linux development builds
on:
push:
branches:
- buildscript
jobs:
deploy:
runs-on: ubuntu-latest
environment: linux-development
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: astral-sh/setup-uv@v7
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: sudo apt install debhelper devscripts dpkg-dev
- name: Create tarball and source packages
run: |
version=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
mkdir /tmp/gpg
echo "$SIGNING_GPG_KEY" > /tmp/gpg/signing-gpg-key
mkdir /tmp/fish-built
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
FISH_ARTEFACT_PATH=/tmp/fish-built DEB_SIGN_KEYFILE=/tmp/gpg/signing-gpg-key ./build_tools/make_linux_packages.sh $version
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
with:
name: linux-source-packages
path: |
/tmp/fish-built
! /tmp/fish-built/fish-*/* # don't include the unpacked source directory

View File

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

View File

@@ -22,6 +22,27 @@ jobs:
- name: check rustfmt
run: find build.rs crates src -type f -name '*.rs' | xargs rustfmt --check
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
- name: Update package database
run: sudo apt-get update
- name: Install shellcheck
run: sudo apt install shellcheck
- name: shellcheck
run: cargo xtask shellcheck
po_files_up_to_date:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
- name: Install deps
uses: ./.github/actions/install-dependencies
- name: Check PO files
run: cargo xtask gettext check
clippy:
runs-on: ubuntu-latest
@@ -32,6 +53,8 @@ jobs:
features: ""
- rust_version: "stable"
features: "--no-default-features"
- rust_version: "stable"
features: "--all-features"
- rust_version: "msrv"
features: ""
steps:

View File

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

View File

@@ -63,7 +63,7 @@ jobs:
sed -n 2p "$relnotes" | grep -q '^$'
sed -i 1,2d "$relnotes"
- name: Upload tarball artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
with:
name: source-tarball
path: |
@@ -104,7 +104,7 @@ jobs:
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
-C target/$arch-unknown-linux-musl/release fish
done
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
with:
name: Static builds for Linux
path: fish-${{ inputs.version }}-linux-*.tar.xz
@@ -123,14 +123,14 @@ jobs:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Download all artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0, build_tools/update-dependencies.sh
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1, build_tools/update-dependencies.sh
with:
merge-multiple: true
path: /tmp/artifacts
- name: List artifacts
run: find /tmp/artifacts -type f
- name: Create draft release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0, build_tools/update-dependencies.sh
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0, build_tools/update-dependencies.sh
with:
tag_name: ${{ inputs.version }}
name: fish ${{ inputs.version }}

View File

@@ -32,14 +32,6 @@ jobs:
- name: make fish_run_tests
run: |
make -C build VERBOSE=1 fish_run_tests
- name: translation updates
run: |
# Generate PO files. This should not result it a change in the repo if all translations are
# up to date.
# Ensure that fish is available as an executable.
PATH="$PWD/build:$PATH" build_tools/update_translations.fish
# Show diff output. Fail if there is any.
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
@@ -159,7 +151,7 @@ jobs:
shell: msys2 {0}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0, build_tools/update-dependencies.sh
- uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1, build_tools/update-dependencies.sh
with:
update: true
msystem: MSYS
@@ -167,7 +159,7 @@ jobs:
- name: Install deps
# Not using setup-msys2 `install` option to make it easier to copy/paste
run: |
pacman --noconfirm -S --needed git rust
pacman --noconfirm -S --needed git rust python3 diffutils tmux
- name: rebase
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
@@ -177,10 +169,8 @@ jobs:
- name: cargo build
run: |
cargo build
- name: smoketest
# We can't run `build_tools/check.sh` yet, there are just too many failures
# so this is just a quick check to make sure that fish can swim
- name: tests
env:
FISH_CHECK_LINT: false
run: |
set -x
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]
cargo test
cargo xtask check

53
.gitignore vendored
View File

@@ -20,7 +20,6 @@
*.o
*.obj
*.orig
!tests/*.out
*.out
*.pch
*.slo
@@ -36,46 +35,31 @@
Desktop.ini
Thumbs.db
ehthumbs.db
__pycache__/
.directory
.fuse_hidden*
# Directories that only contain transitory files from building and testing.
/doc/
/share/man/
/share/doc/
/test/
/user_doc/
# File names that can appear in the project root that represent artifacts from
# building and testing.
/FISH-BUILD-VERSION-FILE
/command_list.txt
/command_list_toc.txt
/compile_commands.json
/doc.h
# Artifacts from in-tree builds ("cmake .").
/build.ninja
/cargo/
/CMakeCache.txt
/CMakeFiles/
/cmake_install.cmake
/fish
/fish.pc
/fish_indent
/fish_key_reader
/fish_tests
/lexicon.txt
/lexicon_filter
/toc.txt
/version
fish-build-version-witness.txt
__pycache__
/fish-localization-map-cache/
/fish.pc
/fish.pc.noversion
/.ninja_log
# File names that can appear below the project root that represent artifacts
# from building and testing.
/doc_src/commands.hdr
/doc_src/index.hdr
/po/*.gmo
/share/__fish_build_paths.fish
/share/pkgconfig
/tests/*.tmp.*
/tests/.last-check-all-files
/.venv/
# xcode
## Build generated
@@ -83,24 +67,19 @@ __pycache__
*.xccheckout
*.xcscmblueprin
.vscode
/DerivedData/
/build/
/DerivedData/
/tags
xcuserdata/
/xcuserdata/
# Generated by Cargo
# will have compiled files and executables
debug/
target/
/target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by clangd
/.cache
/.cache/
# JetBrains editors.
.idea/

View File

@@ -1,28 +1,86 @@
fish ?.?.? (released ???)
=========================
Notable improvements and fixes
------------------------------
Deprecations and removed features
---------------------------------
- `--command` and `--path` options in `complete` no longer unescape their value.
Interactive improvements
------------------------
Improved terminal support
-------------------------
- On the first run after upgrading from an older version, fish will try harder to check if the current theme matches a historical default, in which case fish won't create ``~/.config/fish/conf.d/fish_frozen_theme.fish``.
This means that on systems where fish version 3.x was installed originally, the update will avoid creating that file (:issue:`12725`).
- ``fish_hg_prompt``, ``fish_git_prompt`` and ``fish_fossil_prompt`` now strip control characters from VCS state read off disk, matching ``prompt_pwd``.
- The sample informative and minimalist prompts now use ``prompt_pwd`` instead of printing ``$PWD`` directly.
- ``bind`` shows the file where bindings were defined (:issue:`12504`).
- Abbreviations with ``--position=anywhere`` can now be completed in argument position, not just in command position (:issue:`12630`).
Other improvements
------------------
- ``fish_update_completions`` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
- ``cd`` supports the ``-L`` and ``-P`` options, like other shells, to allow specifying whether symbolic links (symlinks) are resolved when changing directories (:issue:`7206`).
For distributors and developers
-------------------------------
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden with ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
- With the exception of the ``$CMAKE_INSTALL_PREFIX/share/fish/man`` directory, fish no longer installs files to ``$CMAKE_INSTALL_PREFIX/share/fish``.
In particular, this means that both
``$CMAKE_INSTALL_PREFIX/share/fish/completions`` and
``$CMAKE_INSTALL_PREFIX/share/fish/functions``
should now be empty.
If another package installs completions or functions to those directories,
they should be changed to install to
``extra_completionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_completions.d``) or
``extra_functionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_functions.d``)
instead.
The old location has been ignored since fish 4.2.
Regression fixes:
-----------------
- (from 4.4.0) Vi mode ``c,W`` key binding wrongly deleted trailing spaces (:issue:`12790`).
- (from 4.4.0) Vi mode ``x`` in :doc:`builtin read <cmds/read>` (:issue:`12724`).
- (from 4.3.3) Repeated tab would sometimes insert smartcase completions redundantly.
fish 4.7.1 (released May 08, 2026)
==================================
This release fixes a regression in 4.7.0 that caused the web config (``fish_config``) to fail to start (:issue:`12717`).
fish 4.7.0 (released May 05, 2026)
==================================
Deprecations and removed features
---------------------------------
- The default theme (i.e. the ``fish_color_*`` variables) is no longer set in non-interactive shells.
Interactive improvements
------------------------
- :doc:`prompt_pwd <cmds/prompt_pwd>` now strips control characters.
- Repaint events (as triggered by changes to color variables or by event handlers running ``commandline -f repaint``) no longer reset the completion pager and other transient UI states (:issue:`12683`).
- :envvar:`fish_color_valid_path` now respects background and underline colors (:issue:`12622`).
- :doc:`funced <cmds/funced>` will no longer lose work if there are parse errors multiple times without new changes to the file.
- Fixed a case where directory completions were sorted in a surprising order (:issue:`12695`).
- When at the command token, the :kbd:`alt-o` binding will now open read-only files too (:issue:`12671`).
- Private mode in-memory history (``set fish_history``) is no longer shared with :doc:`builtin read <cmds/read>` (:issue:`12662`).
Other improvements
------------------
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
- :doc:`fish_update_completions <cmds/fish_update_completions>` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
- Removing history entries via the :doc:`web-based config <cmds/fish_config>` is more intuitive.
- If :envvar:`XDG_DATA_DIRS` is empty, the default value is assumed, which means that fish will now also use configuration from paths like ``$PREFIX/share/fish/vendor_completions.d`` (:issue:`11349`).
- Some internal file descriptors were moved to number 10 or higher, to reduce risk of clashes with those used by the user in scripts.
- The wording of error messages has been made consistent, especially for builtin subcommands (:issue:`12556`).
For distributors and developers
-------------------------------
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden via ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
- ``build_tools/update_translations.fish`` has been replaced by ``cargo xtask gettext {check,new,update}`` (:issue:`12676`).
- ``cargo xtask shellcheck`` to lint shell-scripts.
Regression fixes:
-----------------
- (from 4.6) Vi mode ``dl`` (:issue:`12461`).
- (from 4.6) Backspace after newline (:issue:`12583`).
- (from 4.3.3) Long options were spuriously completed after typing short options (85e76ba3561).
- (from 3.2) ``nosuchcommand || echo hello`` executes the right hand side again (:issue:`12654`).
fish 4.6.0 (released March 28, 2026)
====================================
@@ -1402,7 +1460,7 @@ Deprecations and removed features
Like ``stderr-nocaret``, they will eventually be made read-only.
- Most ``string`` subcommands no longer append a newline to their input if the input didn't have one (:issue:`8473`, :issue:`3847`)
- Fish's escape sequence removal (like for ``string length --visible`` or to figure out how wide the prompt is) no longer has special support for non-standard color sequences like from Data General terminals, e.g. the Data General Dasher D220 from 1984. This removes a bunch of work in the common case, allowing ``string length --visible`` to be much faster with unknown escape sequences. We don't expect anyone to have ever used fish with such a terminal (:issue:`8769`).
- fish's escape sequence removal (like for ``string length --visible`` or to figure out how wide the prompt is) no longer has special support for non-standard color sequences like from Data General terminals, e.g. the Data General Dasher D220 from 1984. This removes a bunch of work in the common case, allowing ``string length --visible`` to be much faster with unknown escape sequences. We don't expect anyone to have ever used fish with such a terminal (:issue:`8769`).
- Code to upgrade universal variables from fish before 3.0 has been removed. Users who upgrade directly from fish versions 2.7.1 or before will have to set their universal variables & abbreviations again. (:issue:`8781`)
- The meaning of an empty color variable has changed (:issue:`8793`). Previously, when a variable was set but empty, it would be interpreted as the "normal" color. Now, empty color variables cause the same effect as unset variables - the general highlighting variable for that type is used instead. For example::
@@ -1413,14 +1471,14 @@ Deprecations and removed features
This makes it easier to make self-contained color schemes that don't accidentally use color that was set before.
``fish_config`` has been adjusted to set known color variables that a theme doesn't explicitly set to empty.
- ``eval`` is now a reserved keyword, so it can't be used as a function name. This follows ``set`` and ``read``, and is necessary because it can't be cleanly shadowed by a function - at the very least ``eval set -l argv foo`` breaks. Fish will ignore autoload files for it, so left over ``eval.fish`` from previous fish versions won't be loaded.
- ``eval`` is now a reserved keyword, so it can't be used as a function name. This follows ``set`` and ``read``, and is necessary because it can't be cleanly shadowed by a function - at the very least ``eval set -l argv foo`` breaks. fish will ignore autoload files for it, so left over ``eval.fish`` from previous fish versions won't be loaded.
- The git prompt in informative mode now defaults to skipping counting untracked files, as this was extremely slow. To turn it on, set :envvar:`__fish_git_prompt_showuntrackedfiles` or set the git config value "bash.showuntrackedfiles" to ``true`` explicitly (which can be done for individual repositories). The "informative+vcs" sample prompt already skipped display of untracked files, but didn't do so in a way that skipped the computation, so it should be quite a bit faster in many cases (:issue:`8980`).
- The ``__terlar_git_prompt`` function, used by the "Terlar" sample prompt, has been rebuilt as a configuration of the normal ``fish_git_prompt`` to ease maintenance, improve performance and add features (like reading per-repo git configuration). Some slight changes remain; users who absolutely must have the same behavior are encouraged to copy the old function (:issue:`9011`, :issue:`7918`, :issue:`8979`).
Scripting improvements
----------------------
- Quoted command substitution that directly follow a variable expansion (like ``echo "$var$(echo x)"``) no longer affect the variable expansion (:issue:`8849`).
- Fish now correctly expands command substitutions that are preceded by an escaped dollar (like ``echo \$(echo)``). This regressed in version 3.4.0.
- fish now correctly expands command substitutions that are preceded by an escaped dollar (like ``echo \$(echo)``). This regressed in version 3.4.0.
- ``math`` can now handle underscores (``_``) as visual separators in numbers (:issue:`8611`, :issue:`8496`)::
math 5 + 2_123_252
@@ -1440,7 +1498,7 @@ Scripting improvements
Interactive improvements
------------------------
- Fish now reports a special error if a command wasn't found and there is a non-executable file by that name in :envvar:`PATH` (:issue:`8804`).
- fish now reports a special error if a command wasn't found and there is a non-executable file by that name in :envvar:`PATH` (:issue:`8804`).
- ``less`` and other interactive commands would occasionally be stopped when run in a pipeline with fish functions; this has been fixed (:issue:`8699`).
- Case-changing autosuggestions generated mid-token now correctly append only the suffix, instead of duplicating the token (:issue:`8820`).
- ``ulimit`` learned a number of new options for the resource limits available on Linux, FreeBSD ande NetBSD, and returns a specific warning if the limit specified is not available on the active operating system (:issue:`8823`, :issue:`8786`).
@@ -1450,9 +1508,9 @@ Interactive improvements
- Since fish 3.2.0, pressing :kbd:`ctrl-d` while a command is running would end up inserting a space into the next commandline, which has been fixed (:issue:`8871`).
- A bug that caused multi-line prompts to be moved down a line when pasting or switching modes has been fixed (:issue:`3481`).
- The Web-based configuration system no longer strips too many quotes in the abbreviation display (:issue:`8917`, :issue:`8918`).
- Fish started with ``--no-config`` will now use the default keybindings (:issue:`8493`)
- fish started with ``--no-config`` will now use the default keybindings (:issue:`8493`)
- When fish inherits a :envvar:`USER` environment variable value that doesn't correspond to the current effective user ID, it will now correct it in all cases (:issue:`8879`, :issue:`8583`).
- Fish sets a new :envvar:`EUID` variable containing the current effective user id (:issue:`8866`).
- fish sets a new :envvar:`EUID` variable containing the current effective user id (:issue:`8866`).
- ``history search`` no longer interprets the search term as an option (:issue:`8853`)
- The status message when a job terminates should no longer be erased by a multiline prompt (:issue:`8817`)
@@ -1713,7 +1771,7 @@ Improved terminal support
Other improvements
------------------
- Fish's test suite now uses ``ctest``, and has become much faster to run. It is now also possible to run only specific tests with targets named ``test_$filename`` - ``make test_set.fish`` only runs the set.fish test. (:issue:`7851`)
- fish's test suite now uses ``ctest``, and has become much faster to run. It is now also possible to run only specific tests with targets named ``test_$filename`` - ``make test_set.fish`` only runs the set.fish test. (:issue:`7851`)
- The HTML version of the documentation now includes copy buttons for code examples (:issue:`8218`).
- The HTML version of the documentation and the web-based configuration tool now pick more modern system fonts instead of falling back to Arial and something like Courier New most of the time (:issue:`8632`).
- The Debian & Ubuntu package linked from fishshell.com is now a single package, rather than split into ``fish`` and ``fish-common`` (:issue:`7845`).
@@ -3312,7 +3370,7 @@ Other fixes and improvements
variables (:issue:`4200`, :issue:`4341`), executing functions, globs (:issue:`4579`),
``string`` reading from standard input (:issue:`4610`), and slicing history
(in particular, ``$history[1]`` for the last executed command).
- Fishs internal wcwidth function has been updated to deal with newer
- fishs internal wcwidth function has been updated to deal with newer
Unicode, and the width of some characters can be configured via the
``fish_ambiguous_width`` (:issue:`5149`) and ``fish_emoji_width`` (:issue:`2652`)
variables. Alternatively, a new build-time option INTERNAL_WCWIDTH
@@ -3349,7 +3407,7 @@ For distributors and developers
standard sh instead.
- The ``hostname`` command is no longer required for fish to operate.
-
fish 2.7.1 (released December 23, 2017)
=======================================
@@ -3361,7 +3419,7 @@ session (:issue:`4521`).
If you are upgrading from version 2.6.0 or before, please also review
the release notes for 2.7.0 and 2.7b1 (included below).
-
fish 2.7.0 (released November 23, 2017)
=======================================
@@ -3373,7 +3431,7 @@ from version 2.6.0 or before, please also review the release notes for
Xcode builds and macOS packages could not be produced with 2.7b1, but
this is fixed in 2.7.0.
-
fish 2.7b1 (released October 31, 2017)
======================================
@@ -4068,13 +4126,13 @@ Backward-incompatible changes
Other notable fixes and improvements
------------------------------------
- Fish no longer silences errors in config.fish (:issue:`2702`)
- fish no longer silences errors in config.fish (:issue:`2702`)
- Directory autosuggestions will now descend as far as possible if
there is only one child directory (:issue:`2531`)
- Add support for bright colors (:issue:`1464`)
- Allow Ctrl-J (``\cj``) to be bound separately from Ctrl-M
(``\cm``) (:issue:`217`)
- psub now has a “-s”/“suffix” option to name the temporary file with
- psub now has a “-s”/“-suffix” option to name the temporary file with
that suffix
- Enable 24-bit colors on select terminals (:issue:`2495`)
- Support for SVN status in the prompt (:issue:`2582`)
@@ -4098,13 +4156,13 @@ Other notable fixes and improvements
systemd-analyze, localectl, timedatectl
- and more
- Fish no longer has a function called sgrep, freeing it for user
- fish no longer has a function called sgrep, freeing it for user
customization (:issue:`2245`)
- A rewrite of the completions for cd, fixing a few bugs (:issue:`2299`, :issue:`2300`,
:issue:`562`)
- Linux VTs now run in a simplified mode to avoid issues (:issue:`2311`)
- The vi-bindings now inherit from the emacs bindings
- Fish will also execute ``fish_user_key_bindings`` when in vi-mode
- fish will also execute ``fish_user_key_bindings`` when in vi-mode
- ``funced`` will now also check $VISUAL (:issue:`2268`)
- A new ``suspend`` function (:issue:`2269`)
- Subcommand completion now works better with split /usr (:issue:`2141`)
@@ -4173,7 +4231,7 @@ Other notable fixes and improvements
- New documentation design (:issue:`1662`), which requires a Doxygen version
1.8.7 or newer to build.
- Fish now defines a default directory for other packages to provide
- fish now defines a default directory for other packages to provide
completions. By default this is
``/usr/share/fish/vendor-completions.d``; on systems with
``pkgconfig`` installed this path is discoverable with
@@ -4464,7 +4522,7 @@ Other Notable Fixes
- xsel is no longer built as part of fish. It will still be invoked if
installed separately :issue:`633`
- \__fish_filter_mime no longer spews :issue:`628`
- The no-execute option to fish no longer falls over when reaching the
- The -no-execute option to fish no longer falls over when reaching the
end of a block :issue:`624`
- fish_config knows how to find fish even if its not in the $PATH :issue:`621`
- A leading space now prevents writing to history, as is done in bash

View File

@@ -1,10 +1,10 @@
####################
Contributing To Fish
Contributing To fish
####################
This document tells you how you can contribute to fish.
Fish is free and open source software, distributed under the terms of the GPLv2.
fish is free and open source software, distributed under the terms of the GPLv2.
Contributions are welcome, and there are many ways to contribute!
@@ -21,7 +21,7 @@ Archives are available at https://lists.sr.ht/~krobelus/fish-shell/.
GitHub
======
Fish is available on GitHub, at https://github.com/fish-shell/fish-shell.
fish is available on GitHub, at https://github.com/fish-shell/fish-shell.
First, you'll need an account there, and you'll need a git clone of fish.
Fork it on GitHub and then run::
@@ -140,7 +140,7 @@ To reformat files, there is an xtask
cargo xtask format --all
cargo xtask format somefile.rs some.fish
Fish Script Style Guide
fish Script Style Guide
-----------------------
1. All fish scripts, such as those in the *share/functions* and *tests*
@@ -154,7 +154,7 @@ Fish Script Style Guide
for public vars or ``_fish`` for private vars to minimize the
possibility of name clashes with user defined vars.
Configuring Your Editor for Fish Scripts
Configuring Your Editor for fish Scripts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you use Vim: Install `vim-fish <https://github.com/dag/vim-fish>`__,
@@ -253,7 +253,7 @@ To run all tests and linters, use::
Contributing Translations
=========================
Fish uses GNU gettext to translate messages from English to other languages.
fish uses GNU gettext to translate messages from English to other languages.
We use custom tools for extracting messages from source files and to localize at runtime.
This means that we do not have a runtime dependency on the gettext library.
It also means that some features are not supported, such as message context and plurals.
@@ -271,13 +271,14 @@ Adding translations for a new language
--------------------------------------
Creating new translations requires the Gettext tools.
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
language.
To create a new translation, run::
More specifically, you will need ``msguniq``, ``msgmerge``, and ``msgattrib``
for creating translations for a new language.
To create a PO file for a new language ``ll_CC``, run::
build_tools/update_translations.fish localization/po/ll_CC.po
cargo xtask gettext new ll_CC
This will create a new PO file containing all messages available for translation.
This will create a new PO file in ``localization/po/``
containing all messages available for translation.
If the file already exists, it will be updated.
After modifying a PO file, you can recompile fish, and it will integrate the modifications you made.
@@ -347,10 +348,12 @@ Modifications to strings in source files
----------------------------------------
If a string changes in the sources, the old translations will no longer work.
They will be preserved in the PO files, but commented-out (starting with ``#~``).
If you add/remove/change a translatable strings in a source file,
run ``build_tools/update_translations.fish`` to propagate this to all translation files (``localization/po/*.po``).
run ``cargo xtask gettext update`` to propagate this to all translation files (``localization/po/*.po``).
This is only relevant for developers modifying the source files of fish or fish scripts.
Note translations for messages which are no longer present in the sources will be deleted from the PO files.
If the source string changed in a way which should not affect translations,
consider updating the ``msgid`` in the PO files such that translations are preserved.
Setting Code Up For Translations
--------------------------------

634
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,9 @@
members = ["crates/*"]
[workspace.package]
edition = "2024"
# To build revisions that use Corrosion (those before 2024-01), use CMake 3.19, Rustc 1.78 and Rustup 1.27.
rust-version = "1.85"
edition = "2024"
repository = "https://github.com/fish-shell/fish-shell"
# see doc_src/license.rst for details
# don't forget to update COPYING and debian/copyright too
@@ -12,11 +12,13 @@ license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
[workspace.dependencies]
anstyle = "1.0.13"
anyhow = "1.0.102"
assert_matches = "1.5.0"
bitflags = "2.5.0"
cc = "1.0.94"
cfg-if = "1.0.3"
clap = { version = "4.5.54", features = ["derive"] }
clap_complete = { version = "4.6.4", features = ["unstable-dynamic"] }
errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" }
@@ -32,53 +34,54 @@ fish-printf = { path = "crates/printf", features = ["widestring"] }
fish-tempfile = { path = "crates/tempfile" }
fish-util = { path = "crates/util" }
fish-wcstringutil = { path = "crates/wcstringutil" }
fish-wgetopt = { path = "crates/wgetopt" }
fish-widecharwidth = { path = "crates/widecharwidth" }
fish-widestring = { path = "crates/widestring" }
fish-wgetopt = { path = "crates/wgetopt" }
ignore = "0.4.25"
itertools = "0.14.0"
libc = "0.2.177"
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
# files as of 22 April 2024.
lru = "0.16.2"
lru = "0.18.0"
nix = { version = "0.31.1", default-features = false, features = [
"event",
"fs",
"inotify",
"hostname",
"resource",
"process",
"signal",
"term",
"user",
"event",
"fs",
"hostname",
"inotify",
"process",
"resource",
"signal",
"term",
"user",
] }
num-traits = "0.2.19"
once_cell = "1.19.0"
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
"utf32",
"utf32",
] }
phf = { version = "0.13", default-features = false }
phf_codegen = "0.13"
portable-atomic = { version = "1", default-features = false, features = [
"fallback",
"fallback",
] }
proc-macro2 = "1.0"
rand = { version = "0.9.2", default-features = false, features = [
"small_rng",
"thread_rng",
] }
rand = { version = "0.10.1", default-features = false, features = ["thread_rng"] }
regex = "1.12.3"
rsconf = "0.3.0"
rust-embed = { version = "8.11.0", features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
] }
rustc_version = "0.4.1"
serial_test = { version = "3", default-features = false }
widestring = "1.2.0"
strum_macros = "0.28.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"
unix_path = "1.0.1"
walkdir = "2.5.0"
widestring = "1.2.0"
xterm-color = "1.0.1"
[profile.release]
@@ -91,7 +94,7 @@ debug = true
[package]
name = "fish"
version = "4.6.0"
version = "4.7.1"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
@@ -128,6 +131,7 @@ num-traits.workspace = true
once_cell.workspace = true
pcre2.workspace = true
rand.workspace = true
strum_macros.workspace = true
xterm-color.workspace = true
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
@@ -135,16 +139,16 @@ portable-atomic.workspace = true
[target.'cfg(windows)'.dependencies]
rust-embed = { workspace = true, features = [
"deterministic-timestamps",
"debug-embed",
"include-exclude",
"interpolate-folder-path",
"deterministic-timestamps",
"debug-embed",
"include-exclude",
"interpolate-folder-path",
] }
[target.'cfg(not(windows))'.dependencies]
rust-embed = { workspace = true, features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
] }
[dev-dependencies]
@@ -156,6 +160,7 @@ fish-build-helper.workspace = true
fish-gettext-mo-file-parser.workspace = true
phf_codegen = { workspace = true, optional = true }
rsconf.workspace = true
rustc_version.workspace = true
[target.'cfg(windows)'.build-dependencies]
unix_path.workspace = true
@@ -180,21 +185,19 @@ path = "src/bin/fish_key_reader.rs"
default = ["embed-manpages", "localize-messages"]
benchmark = []
embed-manpages = ["dep:fish-build-man-pages"]
# This feature is used to enable extracting messages from the source code for localization.
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
# desired. This happens for the `gettext` xtask, which is also invoked via `cargo xtask check`.
# There should not be a need to enable this feature manually.
gettext-extract = ["dep:fish-gettext-extraction"]
# Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at
# build time.
localize-messages = ["dep:fish-gettext"]
# This feature is used to enable extracting messages from the source code for localization.
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
# desired. This happens when running tests via `cargo xtask check` and when calling
# `build_tools/update_translations.fish`, so there should not be a need to enable it manually.
gettext-extract = ["dep:fish-gettext-extraction"]
# The following features are auto-detected by the build-script and should not be enabled manually.
tsan = []
[workspace.lints]
rust.non_camel_case_types = "allow"
rust.non_upper_case_globals = "allow"
rust.unknown_lints = { level = "allow", priority = -1 }
rust.unstable_name_collisions = "allow"
rustdoc.private_intra_doc_links = "allow"
@@ -227,6 +230,8 @@ unused_trait_names = "warn"
# In the future, they might change to flag other methods of printing.
print_stdout = "deny"
print_stderr = "deny"
# usage in tests is fine since it avoids interacting with TopicMonitor
# and is configured in clippy.toml with `allow-print-in-tests`
[lints]
workspace = true

View File

@@ -1,9 +1,5 @@
.. |Cirrus CI| image:: https://api.cirrus-ci.com/github/fish-shell/fish-shell.svg?branch=master
:target: https://cirrus-ci.com/github/fish-shell/fish-shell
:alt: Cirrus CI Build Status
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status| |Cirrus CI|
=============================================================================================
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status|
=================================================================================
fish is a smart and user-friendly command line shell for macOS, Linux,
and the rest of the family. fish includes features like syntax
@@ -31,7 +27,7 @@ macOS
fish can be installed:
- using `Homebrew <http://brew.sh/>`__: ``brew install fish``
- using `Homebrew <https://brew.sh/>`__: ``brew install fish``
- using `MacPorts <https://www.macports.org/>`__:
``sudo port install fish``
- using the `installer from fishshell.com <https://fishshell.com/>`__
@@ -66,7 +62,7 @@ Windows
for Linux with the instructions for the appropriate distribution
listed above under “Packages for Linux”, or from source with the
instructions below.
- Fish can also be installed on all versions of Windows using
- fish can also be installed on all versions of Windows using
`Cygwin <https://cygwin.com/>`__ or `MSYS2 <https://github.com/Berrysoft/fish-msys2>`__.
Building from source
@@ -221,5 +217,5 @@ There is also a fish tag on Stackoverflow, but it is typically a poor fit.
Found a bug? Have an awesome idea? Please `open an
issue <https://github.com/fish-shell/fish-shell/issues/new>`__.
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/workflows/make%20test/badge.svg
:target: https://github.com/fish-shell/fish-shell/actions
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/actions/workflows/test.yml/badge.svg
:target: https://github.com/fish-shell/fish-shell/actions/workflows/test.yml

View File

@@ -6,6 +6,10 @@
use std::path::{Path, PathBuf};
fn main() {
let is_nightly =
rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly;
rsconf::declare_cfg("nightly", is_nightly);
setup_paths();
// Add our default to enable tools that don't go through CMake, like "cargo test" and the

View File

@@ -8,11 +8,29 @@ if [ "$FISH_CHECK_LINT" = false ]; then
lint=false
fi
case "$(uname)" in
MSYS*)
is_cygwin=true
cygwin_var=MSYS
;;
CYGWIN*)
is_cygwin=true
cygwin_var=CYGWIN
;;
*)
is_cygwin=false
;;
esac
check_dependency_versions=false
if [ "${FISH_CHECK_DEPENDENCY_VERSIONS:-false}" != false ]; then
check_dependency_versions=true
fi
green='\e[0;32m'
yellow='\e[1;33m'
reset='\e[m'
if $check_dependency_versions; then
command -v curl
command -v jq
@@ -42,6 +60,7 @@ cargo() {
fi
}
# shellcheck disable=2317,2329
cleanup () {
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
rm -r "$gettext_template_dir"
@@ -71,6 +90,7 @@ fi
gettext_template_dir=$(mktemp -d)
(
# shellcheck disable=2030
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
cargo build --workspace --all-targets --features=gettext-extract
)
@@ -78,17 +98,65 @@ if $lint; then
if command -v cargo-deny >/dev/null; then
cargo deny --all-features --locked --exclude-dev check licenses
fi
if command -v shellcheck >/dev/null || { test -n "$CI" && ! $is_cygwin; }; then
cargo xtask shellcheck
fi
PATH="$build_dir:$PATH" cargo xtask format --all --check
for features in "" --no-default-features; do
for features in "" --no-default-features --all-features; do
cargo clippy --workspace --all-targets $features
done
cargo xtask gettext --rust-extraction-dir="$gettext_template_dir" check
fi
cargo test --no-default-features --workspace --all-targets
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
# are dynamically linked against Rust's `std-xxx.dll` instead of being
# statically link as they usually are.
# On Cygwin, `PATH`is not properly updated to point to the `std-xxx.dll`
# location, so we have to do it manually.
# See:
# - https://github.com/rust-lang/rust/issues/149050
# - https://github.com/msys2/MSYS2-packages/issues/5784
(
if $is_cygwin; then
PATH="$PATH:$(rustc --print target-libdir)"
export PATH
fi
cargo test --no-default-features --workspace --all-targets
)
cargo test --doc --workspace
if $lint; then
cargo doc --workspace --no-deps
fi
FISH_GETTEXT_EXTRACTION_DIR=$gettext_template_dir "$workspace_root/tests/test_driver.py" "$build_dir"
system_tests() {
"$workspace_root/tests/test_driver.py" "$build_dir" "$@"
}
if $is_cygwin; then
# shellcheck disable=2059
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
(
export "$cygwin_var"=winsymlinks
system_tests
)
# shellcheck disable=2059
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
(
# Only redo the tests that use `ln` to saves some time
export "$cygwin_var"=
# shellcheck disable=2046
system_tests $(grep -l -E '\bln\b' -r tests/checks/)
)
else
# shellcheck disable=2059
printf "=== Running ${green}integration tests${reset}\n"
system_tests
fi
exit
}

View File

@@ -1,150 +0,0 @@
#!/usr/bin/env fish
#
# Tool to generate gettext messages template file.
# Writes to stdout.
# Intended to be called from `update_translations.fish`.
argparse use-existing-template= -- $argv
or exit $status
begin
# Write header. This is required by msguniq.
# Note that this results in the file being overwritten.
# This is desired behavior, to get rid of the results of prior invocations
# of this script.
set -l header 'msgid ""\nmsgstr "Content-Type: text/plain; charset=UTF-8\\\\n"\n\n'
printf $header
set -g workspace_root (path resolve (status dirname)/..)
set -l rust_extraction_dir
if set -l --query _flag_use_existing_template
set rust_extraction_dir $_flag_use_existing_template
else
set rust_extraction_dir (mktemp -d)
# We need to build to ensure that the proc macro for extracting strings runs.
FISH_GETTEXT_EXTRACTION_DIR=$rust_extraction_dir cargo check --features=gettext-extract
or exit 1
end
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.
begin
# Without providing this header, msguniq complains when a msgid is non-ASCII.
printf $header
find $rust_extraction_dir -type f -exec cat {} +
end |
msguniq --no-wrap --sort-output |
# Remove the header again. Otherwise it would appear twice, breaking the msguniq at the end
# of this file.
sed '/^msgid ""$/ {N; /\nmsgstr "Content-Type: text\/plain; charset=UTF-8\\\\n"$/ {N; d}}'
if not set -l --query _flag_use_existing_template
rm -r $rust_extraction_dir
end
function extract_fish_script_messages_impl
set -l regex $argv[1]
set -e argv[1]
# Using xgettext causes more trouble than it helps.
# This is due to handling of escaping in fish differing from formats xgettext understands
# (e.g. POSIX shell strings).
# We work around this issue by manually writing the file content.
# Steps:
# 1. We extract strings to be translated from the relevant files and drop the rest. This step
# depends on the regex matching the entire line, and the first capture group matching the
# string.
# 2. We unescape. This gets rid of some escaping necessary in fish strings.
# 3. The resulting strings are sorted alphabetically. This step is optional. Not sorting would
# result in strings from the same file appearing together. Removing duplicates is also
# optional, since msguniq takes care of that later on as well.
# 4. Single backslashes are replaced by double backslashes. This results in the backslashes
# being interpreted as literal backslashes by gettext tooling.
# 5. Double quotes are escaped, such that they are not interpreted as the start or end of
# a msgid.
# 6. We transform the string into the format expected in a PO file.
cat $argv |
string replace --filter --regex $regex '$1' |
string unescape |
sort -u |
sed -E -e 's_\\\\_\\\\\\\\_g' -e 's_"_\\\\"_g' -e 's_^(.*)$_msgid "\1"\nmsgstr ""\n_'
end
function extract_fish_script_messages
set -l tier $argv[1]
set -e argv[1]
if not set -q argv[1]
return
end
# 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) *\).*'
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).*'
mark_section "$tier-from-script-implicitly-added"
extract_fish_script_messages_impl $implicit_regex $argv
end
set -g share_dir $workspace_root/share
set -l tier1 $share_dir/config.fish
set -l tier2
set -l tier3
for file in $share_dir/completions/*.fish $share_dir/functions/*.fish
# set -l tier (string match -r '^# localization: .*' <$file)
set -l tier (string replace -rf -m1 \
'^# localization: (.*)$' '$1' <$file)
if set -q tier[1]
switch "$tier"
case tier1 tier2 tier3
set -a $tier $file
case 'skip*'
case '*'
echo >&2 "$file:1 unexpected localization tier: $tier"
exit 1
end
continue
end
set -l dirname (path basename (path dirname $file))
set -l command_name (path basename --no-extension $file)
if test $dirname = functions &&
string match -q -- 'fish_*' $command_name
set -a tier1 $file
continue
end
if test $dirname != completions
echo >&2 "$file:1 missing localization tier for function file"
exit 1
end
if test -e $workspace_root/doc_src/cmds/$command_name.rst
set -a tier1 $file
else
set -a tier3 $file
end
end
extract_fish_script_messages tier1 $tier1
extract_fish_script_messages tier2 $tier2
extract_fish_script_messages tier3 $tier3
end |
# At this point, all extracted strings have been written to stdout,
# starting with the ones taken from the Rust sources,
# followed by strings explicitly marked for translation in fish scripts,
# and finally the strings from fish scripts which get translated implicitly.
# Because we do not eliminate duplicates across these categories,
# we do it here, since other gettext tools expect no duplicates.
msguniq --no-wrap

View File

@@ -16,7 +16,7 @@ manifest=$tmpdir/Cargo.toml
lockfile=$tmpdir/Cargo.lock
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml >"$manifest"
awk -v version=$VERSION '
awk -v version="$VERSION" '
/^name = "fish"$/ { ok=1 }
ok == 1 && /^version = ".*"$/ {
ok = 2;

View File

@@ -8,20 +8,6 @@
# Exit on error
set -e
# We need GNU tar as that supports the --mtime and --transform options
TAR=notfound
for try in tar gtar gnutar; do
if $try -Pcf /dev/null --mtime now /dev/null >/dev/null 2>&1; then
TAR=$try
break
fi
done
if [ "$TAR" = "notfound" ]; then
echo 'No suitable tar (supporting --mtime) found as tar/gtar/gnutar in PATH'
exit 1
fi
# Get the current directory, which we'll use for telling Cargo where to find the sources
wd="$PWD"

View File

@@ -48,7 +48,7 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
printf %s \
"This release brings $num_commits new commits since $previous_version," \
" contributed by $num_authors authors, $num_new_authors of which are new committers."
" contributed by $num_authors authors, $num_new_authors of which are new faces."
echo
echo
)
@@ -86,10 +86,13 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
echo
echo 'Download links:'
echo 'To download the source code for fish, we suggest the file named ``fish-'"$version"'.tar.xz``.'
# shellcheck disable=2016
echo 'The file downloaded from ``Source code (tar.gz)`` will not build correctly.'
# shellcheck disable=2016
echo 'A GPG signature using `this key <'"${FISH_GPG_PUBLIC_KEY_URL:-???}"'>`__ is available as ``fish-'"$version"'.tar.xz.asc``.'
echo
echo 'The files called ``fish-'"$version"'-linux-*.tar.xz`` contain'
# shellcheck disable=2016
echo '`standalone fish binaries <https://github.com/fish-shell/fish-shell/?tab=readme-ov-file#building-fish-with-cargo>`__'
echo 'for any Linux with the given CPU architecture.'
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst

View File

@@ -72,7 +72,7 @@ integration_branch=$(
--format='%(refname:strip=2)'
)
[ -n "$integration_branch" ] ||
git merge-base --is-ancestor $remote/master HEAD
git merge-base --is-ancestor "$remote"/master HEAD
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
@@ -113,9 +113,9 @@ CreateCommit "Release $version"
# Tags must be full objects, not lightweight tags, for
# git_version-gen.sh to work.
git -c "user.signingKey=$committer" \
tag --sign --message="Release $version" $version
tag --sign --message="Release $version" "$version"
git push $remote $version
git push "$remote" "$version"
TIMEOUT=
gh() {
@@ -173,6 +173,7 @@ actual_tag_oid=$(git ls-remote "$remote" |
(
cd "$tmpdir/local-tarball/fish-$version"
uv --no-managed-python venv
# shellcheck disable=1091
. .venv/bin/activate
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .
ninja doc
@@ -180,14 +181,17 @@ actual_tag_oid=$(git ls-remote "$remote" |
CopyDocs() {
rm -rf "$fish_site/site/docs/$1"
cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
git -C $fish_site add "site/docs/$1"
git -C "$fish_site" add "site/docs/$1"
}
minor_version=${version%.*}
CopyDocs "$minor_version"
latest_release=$(
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
's/-/~/g' | LC_ALL=C sort --version-sort)
sed '
# De-prioritize release candidates (1.2.3-rc0)
s/-/~/g
' | LC_ALL=C sort --version-sort
)
printf %s\\n "$releases" | tail -1
)
if [ "$version" = "$latest_release" ]; then
@@ -251,6 +255,28 @@ do
sleep 20
done
milestone_version="$(
if echo "$version" | grep -q '\.0$'; then
echo "$minor_version"
else
echo "$version"
fi
)"
milestone_number() {
gh_api_repo milestones?state=open |
jq --arg name "fish $1" '
.[] | select(.title == $name) | .number
'
}
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
--method PATCH --raw-field state=closed
next_minor_version=$(echo "$minor_version" |
awk -F. '{ printf "%s.%s", $1, $2+1 }')
if [ -z "$(milestone_number "$next_minor_version")" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_minor_version"
fi
(
cd "$fish_site"
make new-release
@@ -272,7 +298,7 @@ done
)
if [ -n "$integration_branch" ]; then {
git push $remote "$version^{commit}":refs/heads/$integration_branch
git push "$remote" "$version^{commit}:refs/heads/$integration_branch"
} else {
changelog=$(cat - CHANGELOG.rst <<EOF
fish ?.?.? (released ???)
@@ -283,32 +309,9 @@ EOF
printf %s\\n "$changelog" >CHANGELOG.rst
git add CHANGELOG.rst
CreateCommit "start new cycle"
git push $remote HEAD:master
git push "$remote" HEAD:master
} fi
milestone_version="$(
if echo "$version" | grep -q '\.0$'; then
echo "$minor_version"
else
echo "$version"
fi
)"
milestone_number() {
gh_api_repo milestones?state=open |
jq --arg name "fish $1" '
.[] | select(.title == $name) | .number
'
}
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
--method PATCH --raw-field state=closed
next_minor_version=$(echo "$minor_version" |
awk -F. '{ printf "%s.%s", $1, $2+1 }')
if [ -z "$(milestone_number "$next_minor_version")" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_minor_version"
fi
exit
}

View File

@@ -3,7 +3,6 @@
set -ex
command -v curl
command -v gcloud
command -v jq
command -v rustup
command -v updatecli
@@ -42,17 +41,18 @@ uv lock --upgrade --exclude-newer="$(date --date='7 days ago' --iso-8601)"
from_gh() {
repo=$1
path=$2
destination=$3
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/master/"${path}")
branch=$2
path=$3
destination=$4
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/"${branch}"/"${path}")
printf '%s\n' "$contents" >"$destination"
}
from_gh ridiculousfish/widecharwidth widechar_width.rs crates/widecharwidth/src/widechar_width.rs
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/littlecheck.py
from_gh catppuccin/fish themes/catppuccin-frappe.theme share/themes/catppuccin-frappe.theme
from_gh catppuccin/fish themes/catppuccin-macchiato.theme share/themes/catppuccin-macchiato.theme
from_gh catppuccin/fish themes/catppuccin-mocha.theme share/themes/catppuccin-mocha.theme
from_gh ridiculousfish/widecharwidth master widechar_width.rs crates/widecharwidth/src/widechar_width.rs
from_gh ridiculousfish/littlecheck master littlecheck/littlecheck.py tests/littlecheck.py
from_gh catppuccin/fish main themes/catppuccin-frappe.theme share/themes/catppuccin-frappe.theme
from_gh catppuccin/fish main themes/catppuccin-macchiato.theme share/themes/catppuccin-macchiato.theme
from_gh catppuccin/fish main themes/catppuccin-mocha.theme share/themes/catppuccin-mocha.theme
# Update Cargo.lock
cargo update

View File

@@ -1,156 +0,0 @@
#!/usr/bin/env fish
# Updates the files used for gettext translations.
# By default, the whole xgettext + msgmerge pipeline runs,
# which extracts the messages from the source files into $template_file,
# and updates the PO files for each language from that.
#
# Use cases:
# For developers:
# - Run with no args to update all PO files after making changes to Rust/fish sources.
# For translators:
# - Specify the language you want to work on as an argument, which must be a file in the
# localization/po/ directory. You can specify a language which does not have translations
# yet by specifying the name of a file which does not yet exist.
# Make sure to follow the naming convention.
# For testing:
# - Specify `--dry-run` to see if any updates to the PO files would by applied by this script.
# If this flag is specified, the script will exit with an error if there are outstanding
# changes, and will display the diff. Do not specify other flags if `--dry-run` is specified.
#
# Specify `--use-existing-template=DIR` to prevent running cargo for extracting an up-to-date
# version of the localized strings. This flag is intended for testing setups which make it
# inconvenient to run cargo here, but run it in an earlier step to ensure up-to-date values.
# This argument is passed on to the `fish_xgettext.fish` script and has no other uses.
# `DIR` must be the path to a gettext template file generated from our compilation process.
# It can be obtained by running:
# set -l DIR (mktemp -d)
# FISH_GETTEXT_EXTRACTION_DIR=$DIR cargo check --features=gettext-extract
# The sort utility is locale-sensitive.
# Ensure that sorting output is consistent by setting LC_ALL here.
set -gx LC_ALL C.UTF-8
set -l build_tools (status dirname)
set -l po_dir $build_tools/../localization/po
set -l extract
argparse dry-run use-existing-template= -- $argv
or exit $status
if test -z $argv[1]
# Update everything if not specified otherwise.
set -g po_files $po_dir/*.po
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) 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 ""
echo "If you want to add a new language to the translations not the following:"
echo "The filename must identify a language, with a two letter ISO 639-1 language code of the target language (e.g. 'pt' for Portuguese), and use the file extension '.po'."
echo "Optionally, you can specify a regional variant (e.g. 'pt_BR')."
echo "So valid filenames are of the shape 'll.po' or 'll_CC.po'."
exit 1
end
if not basename $arg | grep -qE '^[a-z]{2,3}(_[A-Z]{2})?\.po$'
echo "Filename does not match the expected format ('ll.po' or 'll_CC.po')."
exit 1
end
end
set -g po_files $argv
end
set -g template_file (mktemp)
# Protect from externally set $tmpdir leaking into this script.
set -g tmpdir
function cleanup_exit
set -l exit_status $status
rm $template_file
if set -g --query tmpdir[1]
rm -r $tmpdir
end
exit $exit_status
end
if set -l --query extract
set -l xgettext_args
if set -l --query _flag_use_existing_template
set xgettext_args --use-existing-template=$_flag_use_existing_template
end
$build_tools/fish_xgettext.fish $xgettext_args >$template_file
or cleanup_exit
end
if set -l --query _flag_dry_run
# On a dry run, we do not modify localization/po/ but write to a temporary directory instead
# and check if there is a difference between localization/po/ and the tmpdir after re-generating
# the PO files.
set -g tmpdir (mktemp -d)
# Ensure tmpdir has the same initial state as the po dir.
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
print_header
# Paste PO file without old header lines.
sed '/^'$header_prefix'/d' $new_po_file
end >$po_file
rm $new_po_file
end
for po_file in $po_files
if set --query tmpdir[1]
set po_file $tmpdir/(basename $po_file)
end
if test -e $po_file
merge_po_files $template_file $po_file
else
begin
print_header
cat $template_file
end >$po_file
end
end
if set -g --query tmpdir[1]
diff -ur $po_dir $tmpdir
or begin
echo ERROR: translations in localization/po/ are stale. Try running build_tools/update_translations.fish
cleanup_exit
end
end
cleanup_exit

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
@@ -11,6 +11,6 @@ codename=$(
curl -fsS https://sources.debian.org/api/src/"${package}"/ |
jq -r --arg codename "${codename}" '
.versions[] | select(.suites[] == $codename) | .version' |
sed 's/^\([0-9]\+\.[0-9]\+\).*/\1/' |
sed -E 's/^([0-9]+\.[0-9]+).*/\1/' |
sort --version-sort |
tail -1

1
clippy.toml Normal file
View File

@@ -0,0 +1 @@
allow-print-in-tests = true

View File

@@ -104,20 +104,12 @@ fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
fish_create_dirs(
${rel_datadir}/fish ${rel_datadir}/fish/completions
${rel_datadir}/fish/functions
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
${rel_datadir}/fish/tools/web_config
${rel_datadir}/fish/tools/web_config/js
${rel_datadir}/fish/prompts
${rel_datadir}/fish/themes
${rel_datadir}/fish
${rel_datadir}/fish/man/man1
)
# This file is embedded in the executable by rust-embed and never read from the filesystem
configure_file(share/__fish_build_paths.fish.in share/__fish_build_paths.fish)
install(FILES share/config.fish
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
DESTINATION ${rel_datadir}/fish
)
# Create only the vendor directories inside the prefix (#5029 / #6508)
fish_create_dirs(
@@ -145,30 +137,6 @@ install(
DESTINATION ${rel_datadir}/pkgconfig
)
install(
DIRECTORY share/completions/
DESTINATION ${rel_datadir}/fish/completions
FILES_MATCHING PATTERN "*.fish"
)
install(
DIRECTORY share/functions/
DESTINATION ${rel_datadir}/fish/functions
FILES_MATCHING PATTERN "*.fish"
)
install(
DIRECTORY share/prompts/
DESTINATION ${rel_datadir}/fish/prompts
FILES_MATCHING PATTERN "*.fish"
)
install(
DIRECTORY share/themes/
DESTINATION ${rel_datadir}/fish/themes
FILES_MATCHING PATTERN "*.theme"
)
# CONDEMNED_PAGE is managed by the conditional above
# Building the man pages is optional: if sphinx isn't installed, they're not built
install(
@@ -179,22 +147,6 @@ install(
PATTERN ${CONDEMNED_PAGE} EXCLUDE
)
install(
PROGRAMS share/tools/create_manpage_completions.py
DESTINATION ${rel_datadir}/fish/tools/
)
install(
DIRECTORY share/tools/web_config
DESTINATION ${rel_datadir}/fish/tools/
FILES_MATCHING
PATTERN "*.png"
PATTERN "*.css"
PATTERN "*.html"
PATTERN "*.py"
PATTERN "*.js"
)
# Building the man pages is optional: if Sphinx isn't installed, they're not built
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
install(

View File

@@ -1,3 +1,19 @@
fish (4.7.1-1) stable; urgency=medium
* Release of new version 4.7.1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.1 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Fri, 08 May 2026 00:02:14 +0800
fish (4.7.0-1) stable; urgency=medium
* Release of new version 4.7.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 05 May 2026 15:24:27 +0800
fish (4.6.0-1) stable; urgency=medium
* Release of new version 4.6.0.

View File

@@ -10,6 +10,8 @@ Build-Depends: debhelper-compat (= 13),
gettext,
libpcre2-dev,
rustc (>= 1.85) | rustc-web (>= 1.85) | rustc-1.85,
# pkg-config is needed for the pcre2 crate to find the pcre2 system library
pkgconf | pkg-config,
python3-sphinx,
# Test dependencies
locales-all,

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-build-helper"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -98,14 +98,14 @@ pub fn target_os_is_cygwin() -> bool {
#[macro_export]
macro_rules! as_os_strs {
[ $( $x:expr, )* ] => {
[ $( $x:expr ),* $(,)? ] => {
{
use std::ffi::OsStr;
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
s.as_ref()
}
&[
$( as_os_str($x), )*
[
$( as_os_str($x) ),*
]
}
}

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-build-man-pages"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-color"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,13 +1,14 @@
[package]
name = "fish-common"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
bitflags.workspace = true
fish-feature-flags.workspace = true
fish-widestring.workspace = true
libc.workspace = true
nix.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-fallback"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-feature-flags"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,11 +1,11 @@
[package]
name = "fish-gettext-extraction"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
description = "proc-macro for extracting strings for gettext translation"
repository.workspace = true
license.workspace = true
description = "proc-macro for extracting strings for gettext translation"
[lib]
proc-macro = true

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-gettext-maps"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -139,7 +139,7 @@ fn to_raw_str(s: &str) -> String {
write!(
&mut cached_map_file,
"static {}: phf::Map<&'static str, &'static str> = {}",
&map_name,
map_name,
single_language_localization_map.build()
)
.unwrap();

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-gettext-mo-file-parser"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-gettext"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,10 +1,10 @@
[package]
name = "fish-printf"
version = "0.2.1"
edition.workspace = true
rust-version.workspace = true
version = "0.2.1"
repository.workspace = true
description = "printf implementation, based on musl"
repository.workspace = true
license = "MIT"
[dependencies]

View File

@@ -1,13 +1,13 @@
[package]
name = "fish-tempfile"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
nix = { workspace = true, features = ["fs", "feature"] }
nix = { workspace = true, features = ["feature", "fs"] }
rand.workspace = true
[lints]

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-util"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -63,14 +63,23 @@ pub fn wcsfilecmp(a: &wstr, b: &wstr) -> Ordering {
continue;
}
// Sort dashes after Z - see #5634
let mut acl = if ac == '-' { '[' } else { ac };
let mut bcl = if bc == '-' { '[' } else { bc };
let transform = |c| {
// Sort dashes after Z - see #5634
if c == '-' {
return '[';
}
if c == '/' {
return '\0';
}
c
};
let ac = transform(ac);
let bc = transform(bc);
// TODO Compare the tail (enabled by Rust's Unicode support).
acl = acl.to_uppercase().next().unwrap();
bcl = bcl.to_uppercase().next().unwrap();
let ac = ac.to_uppercase().next().unwrap();
let bc = bc.to_uppercase().next().unwrap();
match acl.cmp(&bcl) {
match ac.cmp(&bc) {
Ordering::Equal => {
ai += 1;
bi += 1;
@@ -133,9 +142,9 @@ pub fn wcsfilecmp_glob(a: &wstr, b: &wstr) -> Ordering {
}
// TODO Compare the tail (enabled by Rust's Unicode support).
let acl = ac.to_lowercase().next().unwrap();
let bcl = bc.to_lowercase().next().unwrap();
match acl.cmp(&bcl) {
let ac = ac.to_lowercase().next().unwrap();
let bc = bc.to_lowercase().next().unwrap();
match ac.cmp(&bc) {
Ordering::Equal => {
ai += 1;
bi += 1;
@@ -314,5 +323,8 @@ macro_rules! validate {
validate!("a00b", "a0b", Ordering::Less);
validate!("a0b", "a00b", Ordering::Greater);
validate!("a-b", "azb", Ordering::Greater);
validate!("a", "a b", Ordering::Less);
validate!("a/", "a b/", Ordering::Less);
validate!("a/b", "a b", Ordering::Less); // Note this is arbitrary.
}
}

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-wcstringutil"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,12 +1,7 @@
//! Helper functions for working with wcstring.
use std::{
ffi::{CStr, CString, OsString},
os::unix::ffi::OsStringExt as _,
};
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
use fish_widestring::{ELLIPSIS_CHAR, decode_byte_from_char, prelude::*};
use fish_widestring::{ELLIPSIS_CHAR, prelude::*};
/// Return the number of newlines in a string.
pub fn count_newlines(s: &wstr) -> usize {
@@ -340,145 +335,6 @@ pub fn string_fuzzy_match_string(
StringFuzzyMatch::try_create(string, match_against, anchor_start)
}
/// Implementation of wcs2bytes that accepts a callback.
/// The first argument can be either a `&str` or `&wstr`.
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
/// input, doing one invocation per character.
/// If `func` returns false, it stops; otherwise it continues.
/// Return false if the callback returned false, otherwise true.
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
let mut converted = [0_u8; 4];
for c in input.chars() {
let bytes = if let Some(byte) = decode_byte_from_char(c) {
converted[0] = byte;
&converted[..=0]
} else {
c.encode_utf8(&mut converted).as_bytes()
};
if !func(bytes) {
return false;
}
}
true
}
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
/// string.
///
/// This function decodes illegal character sequences in a reversible way using the private use
/// area.
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
let mut result = vec![];
wcs2bytes_appending(&mut result, input);
result
}
pub fn wcs2osstring(input: &wstr) -> OsString {
if input.is_empty() {
return OsString::new();
}
let mut result = vec![];
wcs2bytes_appending(&mut result, input);
OsString::from_vec(result)
}
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
pub fn wcs2zstring(input: &wstr) -> CString {
if input.is_empty() {
return CString::default();
}
let mut vec = Vec::with_capacity(input.len() + 1);
str2bytes_callback(input, |buff| {
vec.extend_from_slice(buff);
true
});
vec.push(b'\0');
match CString::from_vec_with_nul(vec) {
Ok(cstr) => cstr,
Err(err) => {
// `input` contained a NUL in the middle; we can retrieve `vec`, though
let mut vec = err.into_bytes();
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
vec.truncate(pos + 1);
// Safety: We truncated after the first NUL
unsafe { CString::from_vec_with_nul_unchecked(vec) }
}
}
}
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
str2bytes_callback(input, |buff| {
output.extend_from_slice(buff);
true
});
}
/// A trait to make it more convenient to pass ascii/Unicode strings to functions that can take
/// non-Unicode values. The result is nul-terminated and can be passed to OS functions.
///
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
/// left with the original item if we're going to allocate from scratch in all cases.
pub trait ToCString {
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
fn to_cstring(self) -> CString;
}
impl ToCString for CString {
fn to_cstring(self) -> CString {
self
}
}
impl ToCString for &CStr {
fn to_cstring(self) -> CString {
self.to_owned()
}
}
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
/// range by using [`wcs2zstring()`].
impl ToCString for &wstr {
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
fn to_cstring(self) -> CString {
self::wcs2zstring(self)
}
}
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
/// functions, taking into account non-Unicode values that have been shifted into the private-use
/// range by using [`wcs2zstring()`].
impl ToCString for &WString {
fn to_cstring(self) -> CString {
self.as_utfstr().to_cstring()
}
}
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
impl ToCString for Vec<u8> {
fn to_cstring(mut self) -> CString {
self.push(b'\0');
CString::from_vec_with_nul(self).unwrap()
}
}
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
impl ToCString for &[u8] {
fn to_cstring(self) -> CString {
CString::new(self).unwrap()
}
}
/// Split a string by runs of any of the separator characters provided in `seps`.
/// Note the delimiters are the characters in `seps`, not `seps` itself.
/// `seps` may contain the NUL character.

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-wgetopt"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true

View File

@@ -1,8 +1,8 @@
[package]
name = "fish-widecharwidth"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license = "CC0-1.0"

View File

@@ -1,12 +1,13 @@
[package]
name = "fish-widestring"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
libc.workspace = true
unicode-width.workspace = true
widestring.workspace = true

View File

@@ -6,16 +6,34 @@
pub mod word_char;
use std::{iter, slice};
use std::{
ffi::{CStr, CString, OsStr, OsString},
iter,
os::unix::ffi::{OsStrExt as _, OsStringExt as _},
slice,
};
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utf32str as L, utfstr::CharsUtf32};
pub mod prelude {
pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr};
}
// Highest legal ASCII value.
pub const ASCII_MAX: char = 127 as char;
// Highest legal 16-bit Unicode value.
pub const UCS2_MAX: char = '\u{FFFF}';
// Highest legal byte value.
pub const BYTE_MAX: char = 0xFF as char;
// Unicode BOM value.
pub const UTF8_BOM_WCHAR: char = '\u{FEFF}';
/// The character to use where the text has been truncated.
pub const ELLIPSIS_CHAR: char = '\u{2026}'; // ('…')
pub const SPECIAL_KEY_ENCODE_BASE: char = '\u{F500}';
// These are in the Unicode private-use range. We really shouldn't use this
// range but have little choice in the matter given how our lexer/parser works.
// We can't use non-characters for these two ranges because there are only 66 of
@@ -28,9 +46,79 @@ pub mod prelude {
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
pub const ENCODE_DIRECT_BASE: char = '\u{F600}';
pub const ENCODE_DIRECT_BASE: char = char_offset(SPECIAL_KEY_ENCODE_BASE, 256);
pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256);
// Use Unicode "non-characters" for internal characters as much as we can. This
// gives us 32 "characters" for internal use that we can guarantee should not
// appear in our input stream. See http://www.unicode.org/faq/private_use.html.
pub const RESERVED_CHAR_BASE: char = '\u{FDD0}';
pub const RESERVED_CHAR_END: char = '\u{FDF0}';
// Split the available non-character values into two ranges to ensure there are
// no conflicts among the places we use these special characters.
pub const EXPAND_RESERVED_BASE: char = RESERVED_CHAR_BASE;
pub const EXPAND_RESERVED_END: char = char_offset(EXPAND_RESERVED_BASE, 16);
pub const WILDCARD_RESERVED_BASE: char = EXPAND_RESERVED_END;
pub const WILDCARD_RESERVED_END: char = char_offset(WILDCARD_RESERVED_BASE, 16);
// Make sure the ranges defined above don't exceed the range for non-characters.
// This is to make sure we didn't do something stupid in subdividing the
// Unicode range for our needs.
const _: () = assert!(WILDCARD_RESERVED_END <= RESERVED_CHAR_END);
/// Character representing any character except '/' (slash).
pub const ANY_CHAR: char = char_offset(WILDCARD_RESERVED_BASE, 0);
/// Character representing any character string not containing '/' (slash).
pub const ANY_STRING: char = char_offset(WILDCARD_RESERVED_BASE, 1);
/// Character representing any character string.
pub const ANY_STRING_RECURSIVE: char = char_offset(WILDCARD_RESERVED_BASE, 2);
/// This is a special pseudo-char that is not used other than to mark the
/// end of the special characters so we can sanity check the enum range.
#[allow(dead_code)]
pub const ANY_SENTINEL: char = char_offset(WILDCARD_RESERVED_BASE, 3);
/// Character representing a home directory.
pub const HOME_DIRECTORY: char = char_offset(EXPAND_RESERVED_BASE, 0);
/// Character representing process expansion for %self.
pub const PROCESS_EXPAND_SELF: char = char_offset(EXPAND_RESERVED_BASE, 1);
/// Character representing variable expansion.
pub const VARIABLE_EXPAND: char = char_offset(EXPAND_RESERVED_BASE, 2);
/// Character representing variable expansion into a single element.
pub const VARIABLE_EXPAND_SINGLE: char = char_offset(EXPAND_RESERVED_BASE, 3);
/// Character representing the start of a bracket expansion.
pub const BRACE_BEGIN: char = char_offset(EXPAND_RESERVED_BASE, 4);
/// Character representing the end of a bracket expansion.
pub const BRACE_END: char = char_offset(EXPAND_RESERVED_BASE, 5);
/// Character representing separation between two bracket elements.
pub const BRACE_SEP: char = char_offset(EXPAND_RESERVED_BASE, 6);
/// Character that takes the place of any whitespace within non-quoted text in braces
pub const BRACE_SPACE: char = char_offset(EXPAND_RESERVED_BASE, 7);
/// Separate subtokens in a token with this character.
pub const INTERNAL_SEPARATOR: char = char_offset(EXPAND_RESERVED_BASE, 8);
/// Character representing an empty variable expansion. Only used transitively while expanding
/// variables.
pub const VARIABLE_EXPAND_EMPTY: char = char_offset(EXPAND_RESERVED_BASE, 9);
const _: () = assert!(
EXPAND_RESERVED_END as u32 > VARIABLE_EXPAND_EMPTY as u32,
"Characters used in expansions must stay within private use area"
);
/// The string represented by PROCESS_EXPAND_SELF
pub const PROCESS_EXPAND_SELF_STR: &wstr = L!("%self");
/// Return true if the character is in a range reserved for fish's private use.
///
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
/// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted
/// string. We don't want external input to be able to feed reserved characters into our
/// lexer/parser or code evaluator.
//
// TODO: Actually implement the replacement as documented above.
pub fn fish_reserved_codepoint(c: char) -> bool {
(c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END)
|| (c >= SPECIAL_KEY_ENCODE_BASE && c < ENCODE_DIRECT_END)
}
/// Encode a literal byte in a UTF-32 character. This is required for e.g. the echo builtin, whose
/// escape sequences can be used to construct raw byte sequences which are then interpreted as e.g.
/// UTF-8 by the terminal. If we were to interpret each of those bytes as a codepoint and encode it
@@ -43,6 +131,86 @@ pub fn encode_byte_to_char(byte: u8) -> char {
.expect("private-use codepoint should be valid char")
}
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
/// string.
///
/// This function decodes illegal character sequences in a reversible way using the private use
/// area.
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
let mut result = vec![];
wcs2bytes_appending(&mut result, input);
result
}
pub fn wcs2osstring(input: &wstr) -> OsString {
if input.is_empty() {
return OsString::new();
}
let mut result = vec![];
wcs2bytes_appending(&mut result, input);
OsString::from_vec(result)
}
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
pub fn wcs2zstring(input: &wstr) -> CString {
if input.is_empty() {
return CString::default();
}
let mut vec = Vec::with_capacity(input.len() + 1);
str2bytes_callback(input, |buff| {
vec.extend_from_slice(buff);
true
});
vec.push(b'\0');
match CString::from_vec_with_nul(vec) {
Ok(cstr) => cstr,
Err(err) => {
// `input` contained a NUL in the middle; we can retrieve `vec`, though
let mut vec = err.into_bytes();
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
vec.truncate(pos + 1);
// Safety: We truncated after the first NUL
unsafe { CString::from_vec_with_nul_unchecked(vec) }
}
}
}
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
str2bytes_callback(input, |buff| {
output.extend_from_slice(buff);
true
});
}
/// Implementation of wcs2bytes that accepts a callback.
/// The first argument can be either a `&str` or `&wstr`.
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
/// input, doing one invocation per character.
/// If `func` returns false, it stops; otherwise it continues.
/// Return false if the callback returned false, otherwise true.
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
let mut converted = [0_u8; 4];
for c in input.chars() {
let bytes = if let Some(byte) = decode_byte_from_char(c) {
converted[0] = byte;
&converted[..=0]
} else {
c.encode_utf8(&mut converted).as_bytes()
};
if !func(bytes) {
return false;
}
}
true
}
/// Decode a literal byte from a UTF-32 character.
pub fn decode_byte_from_char(c: char) -> Option<u8> {
if c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END {
@@ -56,6 +224,65 @@ pub fn decode_byte_from_char(c: char) -> Option<u8> {
}
}
/// A trait to make it more convenient to pass ascii/Unicode strings to functions that can take
/// non-Unicode values. The result is nul-terminated and can be passed to OS functions.
///
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
/// left with the original item if we're going to allocate from scratch in all cases.
pub trait ToCString {
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
fn to_cstring(self) -> CString;
}
impl ToCString for CString {
fn to_cstring(self) -> CString {
self
}
}
impl ToCString for &CStr {
fn to_cstring(self) -> CString {
self.to_owned()
}
}
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
/// range by using [`wcs2zstring()`].
impl ToCString for &wstr {
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
fn to_cstring(self) -> CString {
self::wcs2zstring(self)
}
}
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
/// functions, taking into account non-Unicode values that have been shifted into the private-use
/// range by using [`wcs2zstring()`].
impl ToCString for &WString {
fn to_cstring(self) -> CString {
self.as_utfstr().to_cstring()
}
}
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
impl ToCString for Vec<u8> {
fn to_cstring(mut self) -> CString {
self.push(b'\0');
CString::from_vec_with_nul(self).unwrap()
}
}
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
impl ToCString for &[u8] {
fn to_cstring(self) -> CString {
CString::new(self).unwrap()
}
}
mod decoder {
use crate::{ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, char_offset, wstr};
use buffer::Buffer;
@@ -276,6 +503,82 @@ pub const fn char_offset(base: char, offset: u32) -> char {
}
}
/// Encodes the bytes in `input` into a [`WString`], encoding non-UTF-8 bytes into private-use-area
/// code-points. Bytes which would be parsed into our reserved PUA range are encoded individually,
/// to allow for correct round-tripping.
pub fn bytes2wcstring(mut input: &[u8]) -> WString {
if input.is_empty() {
return WString::new();
}
let mut result = WString::with_capacity(input.len());
fn append_escaped_str(output: &mut WString, input: &str) {
for (i, c) in input.char_indices() {
if fish_reserved_codepoint(c) {
for byte in &input.as_bytes()[i..i + c.len_utf8()] {
output.push(encode_byte_to_char(*byte));
}
} else {
output.push(c);
}
}
}
while !input.is_empty() {
match std::str::from_utf8(input) {
Ok(parsed_str) => {
append_escaped_str(&mut result, parsed_str);
// The entire remaining input could be parsed, so we are done.
break;
}
Err(e) => {
let (valid, after_valid) = input.split_at(e.valid_up_to());
// SAFETY: The previous `str::from_utf8` call established that the prefix `valid`
// is valid UTF-8. This prefix may be empty.
let parsed_str = unsafe { std::str::from_utf8_unchecked(valid) };
append_escaped_str(&mut result, parsed_str);
// The length of the prefix of `after_valid` which is invalid UTF-8.
// The remaining bytes of `input` (if any) will be parsed in subsequent iterations
// of the loop, starting from the first byte that starts a valid UTF-8-encoded codepoint.
// `error_len` can return `None`, if it sees a byte sequence that could be the
// prefix of a valid code-point encoding at the end of the byte slice.
// This is useful when the input is chunked, but we don't do that, so in this case
// we use our custom encoding for all remaining bytes (at most 3).
let error_len = e.error_len().unwrap_or(after_valid.len());
for byte in &after_valid[..error_len] {
result.push(encode_byte_to_char(*byte));
}
input = &after_valid[error_len..];
}
}
}
result
}
/// Use this rather than [`WString::from_str`] when the input could contain PUA bytes we use to
/// encode non-UTF-8 bytes. Otherwise, when decoding the resulting [`WString`], the PUA bytes in
/// the input would be converted to non-UTF-8 bytes.
pub fn str2wcstring<S: AsRef<str>>(input: S) -> WString {
bytes2wcstring(input.as_ref().as_bytes())
}
pub fn cstr2wcstring<C: AsRef<CStr>>(input: C) -> WString {
bytes2wcstring(input.as_ref().to_bytes())
}
pub fn osstr2wcstring<O: AsRef<OsStr>>(input: O) -> WString {
bytes2wcstring(input.as_ref().as_bytes())
}
/// # SAFETY
///
/// `input` must point to a valid NUL-terminated string.
pub unsafe fn charptr2wcstring(input: *const libc::c_char) -> WString {
let input: &[u8] = unsafe { CStr::from_ptr(input).to_bytes() };
bytes2wcstring(input)
}
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
///
/// # Examples

View File

@@ -1,13 +1,19 @@
[package]
name = "xtask"
version = "0.0.0"
rust-version.workspace = true
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
[dependencies]
anstyle.workspace = true
anyhow.workspace = true
clap.workspace = true
clap_complete.workspace = true
fish-build-helper.workspace = true
fish-common.workspace = true
fish-tempfile.workspace = true
fish-widestring.workspace = true
ignore.workspace = true
pcre2.workspace = true
walkdir.workspace = true

View File

@@ -1,4 +1,5 @@
use anstyle::{AnsiColor, Style};
use anyhow::{Context, Result, bail};
use clap::Args;
use std::{
io::{ErrorKind, Write},
@@ -25,12 +26,12 @@ pub struct FormatArgs {
paths: Vec<PathBuf>,
}
pub fn format(args: FormatArgs) {
pub fn format(args: FormatArgs) -> Result<()> {
if !args.all && args.paths.is_empty() {
println!(
"{YELLOW}warning: No paths specified. Nothing to do. Use the \"--all\" flag to consider all eligible files.{YELLOW:#}"
);
return;
return Ok(());
}
if !args.force && !args.check {
match Command::new("git")
@@ -39,16 +40,22 @@ pub fn format(args: FormatArgs) {
{
Ok(output) => {
if !output.stdout.is_empty() {
std::io::stdout().write_all(&output.stdout).unwrap();
std::io::stdout()
.write_all(&output.stdout)
.context("Could not write to stdout.")?;
print!(
"You have uncommitted changes (listed above). Are you sure you want to format? (y/N): "
);
std::io::stdout().flush().unwrap();
std::io::stdout()
.flush()
.context("Could not flush stdout.")?;
let mut response = String::new();
std::io::stdin().read_line(&mut response).unwrap();
std::io::stdin()
.read_line(&mut response)
.context("Could not read from stdin.")?;
if response.trim_end() != "y" {
println!("Exiting without formatting.");
return;
return Ok(());
}
}
}
@@ -58,22 +65,25 @@ pub fn format(args: FormatArgs) {
"{YELLOW}warning: Did not find git, will proceed without checking for unstaged changes.{YELLOW:#}"
)
} else {
fail!("Failed to run git status:\n{e}")
bail!("Failed to run git status:\n{e}");
}
}
}
}
format_fish(&args);
format_python(&args);
format_rust(&args);
format_fish(&args)?;
format_python(&args)?;
format_rust(&args)?;
Ok(())
}
fn run_formatter(formatter: &mut Command, name: &str) {
fn run_formatter(formatter: &mut Command, name: &str) -> Result<()> {
println!("=== Running {GREEN}{name}{GREEN:#}");
match formatter.status() {
Ok(exit_status) => {
if !exit_status.success() {
fail!("{name:?}: Files are not formatted correctly.");
if exit_status.success() {
Ok(())
} else {
bail!("{name:?}: Files are not formatted correctly.");
}
}
Err(e) => {
@@ -81,15 +91,16 @@ fn run_formatter(formatter: &mut Command, name: &str) {
eprintln!(
"{YELLOW}Formatter not found: {name:?}. Skipping associated files.{YELLOW:#}"
);
Ok(())
} else {
fail!("Error occurred while running {name:?}:\n{e}")
Err(e).with_context(|| format!("Error occurred while running {name:?}"))
}
}
}
}
fn format_fish(args: &FormatArgs) {
let mut fish_paths = files_with_extension(&args.paths, "fish");
fn format_fish(args: &FormatArgs) -> Result<()> {
let mut fish_paths = files_with_extension(&args.paths, "fish")?;
if args.all {
let workspace_root = fish_build_helper::workspace_root();
let fish_formatting_dirs = ["benchmarks", "build_tools", "etc", "share"];
@@ -98,10 +109,10 @@ fn format_fish(args: &FormatArgs) {
.iter()
.map(|dir_name| workspace_root.join(dir_name)),
"fish",
));
)?);
};
if fish_paths.is_empty() {
return;
return Ok(());
}
// TODO: make `fish_indent` available as a Rust library function, to avoid needing a
// `fish_indent` binary in `$PATH`.
@@ -113,40 +124,40 @@ fn format_fish(args: &FormatArgs) {
}
formatter.arg("--");
formatter.args(fish_paths);
run_formatter(&mut formatter, "fish_indent");
run_formatter(&mut formatter, "fish_indent")
}
fn format_python(args: &FormatArgs) {
fn format_python(args: &FormatArgs) -> Result<()> {
let mut formatter = Command::new("ruff");
formatter.arg("format");
if args.check {
formatter.arg("--check");
}
let mut python_files = files_with_extension(&args.paths, "py");
let mut python_files = files_with_extension(&args.paths, "py")?;
if args.all {
python_files.push(fish_build_helper::workspace_root().to_owned());
};
if python_files.is_empty() {
return;
return Ok(());
}
formatter.args(python_files);
run_formatter(&mut formatter, "ruff format");
run_formatter(&mut formatter, "ruff format")
}
fn format_rust(args: &FormatArgs) {
fn format_rust(args: &FormatArgs) -> Result<()> {
let rustfmt_status = Command::new("cargo")
.arg("fmt")
.arg("--version")
.stdout(Stdio::null())
.status()
.unwrap();
.context("Failed to run cargo")?;
if !rustfmt_status.success() {
eprintln!(
"{YELLOW}Please install \"rustfmt\" to format Rust, e.g. via:\n\
rustup component add rustfmt{YELLOW:#}"
);
return;
return Ok(());
}
if args.all {
let mut formatter = Command::new("cargo");
@@ -155,16 +166,17 @@ fn format_rust(args: &FormatArgs) {
if args.check {
formatter.arg("--check");
}
run_formatter(&mut formatter, "cargo fmt");
run_formatter(&mut formatter, "cargo fmt")?;
}
let rust_files = files_with_extension(&args.paths, "rs");
if !rust_files.is_empty() {
let mut formatter = Command::new("rustfmt");
if args.check {
formatter.arg("--check");
formatter.arg("--files-with-diff");
}
formatter.args(rust_files);
run_formatter(&mut formatter, "rustfmt");
let rust_files = files_with_extension(&args.paths, "rs")?;
if rust_files.is_empty() {
return Ok(());
}
let mut formatter = Command::new("rustfmt");
if args.check {
formatter.arg("--check");
formatter.arg("--files-with-diff");
}
formatter.args(rust_files);
run_formatter(&mut formatter, "rustfmt")
}

552
crates/xtask/src/gettext.rs Normal file
View File

@@ -0,0 +1,552 @@
use crate::{CARGO, CommandExt, files_with_extension};
use anyhow::{Context as _, Result, bail};
use clap::{Args, Subcommand};
use fish_build_helper::po_dir;
use pcre2::bytes::Regex;
use std::{
fs::OpenOptions,
io::{Write as _, stdout},
path::{Path, PathBuf},
process::Command,
thread::spawn,
};
#[derive(Args)]
pub struct GettextArgs {
/// Path to the directory into which the messages from the Rust sources have been extracted.
/// If this is not specified, fish will be compiled with the `gettext-extract` feature to
/// obtain the messages.
#[arg(long)]
rust_extraction_dir: Option<PathBuf>,
#[command(subcommand)]
task: Task,
}
#[derive(Subcommand)]
enum Task {
/// Check whether the PO files are up to date.
/// Prints a diff and exits non-zero if they are outdated.
/// Considers all our PO files by default, also allows explicitly specifying which files to
/// consider.
Check { paths: Vec<PathBuf> },
/// Add a PO file for a new language.
New {
/// An ISO 639-1 language identifier (ISO 639-2 if the former does not exits),
/// optionally followed by and underscore and an ISO 3166-1 country code to specify the variant,
/// e.g. `de` or `pt_BR`.
language: String,
},
/// Update PO files.
/// This will delete entries for msgids which are no longer used in the sources and introduce
/// new, untranslated entries for messages which do not have an entry yet.
/// This tool should run every time a change to the messages localized via gettext occurs,
/// including fish script files, where many strings are implicitly localized.
/// Considers all our PO files by default, also allows explicitly specifying which files to
/// consider.
Update { paths: Vec<PathBuf> },
}
fn get_po_paths<P: AsRef<Path>>(specified_paths: &[P]) -> Result<Vec<PathBuf>> {
let extension = "po";
if specified_paths.is_empty() {
files_with_extension([po_dir()], extension)
} else {
files_with_extension(specified_paths, extension)
}
}
fn update_po_file<P: AsRef<Path>, Q: AsRef<Path>>(file_to_update: P, template: Q) -> Result<()> {
Command::new("msgmerge")
.args([
"--no-wrap",
"--update",
"--no-fuzzy-matching",
"--backup=none",
"--quiet",
])
.arg(file_to_update.as_ref())
.arg(template.as_ref())
.run()?;
let msgattrib_output_file = fish_tempfile::new_file().context("Failed to create temp file")?;
Command::new("msgattrib")
.args(["--no-wrap", "--no-obsolete"])
.arg("-o")
.arg(msgattrib_output_file.path())
.arg(file_to_update.as_ref())
.run()?;
crate::copy_file(msgattrib_output_file.path(), file_to_update.as_ref())?;
Ok(())
}
pub fn gettext(args: GettextArgs) -> Result<()> {
let template = match args.rust_extraction_dir {
Some(dir) => template::Template::new(dir)?,
None => {
let temp_dir = fish_tempfile::new_dir().context("Failed to create temp file")?;
Command::new(CARGO)
.args([
"check",
"--workspace",
"--all-targets",
"--features=gettext-extract",
])
.env("FISH_GETTEXT_EXTRACTION_DIR", temp_dir.path())
.run()?;
template::Template::new(temp_dir.path())?
}
};
let mut template_file = fish_tempfile::new_file().context("Failed to create temp file")?;
template_file
.get_mut()
.write_all(template.serialize())
.with_context(|| format!("Failed to write to temp file {:?}", template_file.path()))?;
template_file
.get_mut()
.flush()
.with_context(|| format!("Failed to flush temporary file {:?}", template_file.path()))?;
match args.task {
Task::Check { paths } => {
let mut thread_handles = vec![];
for path in get_po_paths(&paths)? {
let template_path_buf = template_file.path().to_owned();
let handle = spawn(move || -> Result<Option<Vec<u8>>> {
let tmp_copy =
fish_tempfile::new_file().context("Failed to create temp file")?;
crate::copy_file(&path, tmp_copy.path())?;
update_po_file(tmp_copy.path(), template_path_buf)?;
let diff_output = Command::new("diff")
.arg("-u")
.arg(&path)
.arg(tmp_copy.path())
.output()
.context("Failed to run diff")?;
if diff_output.status.success() {
Ok(None)
} else {
Ok(Some(diff_output.stdout))
}
});
thread_handles.push(handle);
}
let mut found_diff = false;
let mut error = None;
for handle in thread_handles {
// SAFETY: `handle.join()` only returns `Err` if the thread panicked.
// Our threads should not panic, and if they do, it's OK to deal with the unexpected
// behavior by panicking here as well.
match handle.join().unwrap() {
Ok(None) => {}
Ok(Some(diff)) => {
found_diff = true;
stdout()
.write_all(&diff)
.context("Could not write to stdout")?;
}
Err(e) => {
error = Some(e);
}
}
}
if let Some(e) = error {
return Err(e);
}
if found_diff {
bail!(
"Not all PO files are up to date.\n\
Run `cargo xtask gettext update` to bring them up to date automatically.\
"
);
}
Ok(())
}
Task::New { language } => {
let language_regex = Regex::new("^[a-z]{2,3}(_[A-Z]{2})?$").unwrap();
if !language_regex.is_match(language.as_bytes()).unwrap() {
bail!(
"The language name '{language}' does not match the expected format.\n\
It needs to be a two-letter ISO 639-1 language code, \
or a three-letter ISO 639-2 language code \
if no ISO 639-1 code exists for the language.\n\
Optionally, the language code can be followed be an underscore \
followed by an ISO 3166-1 country code to indicate a regional variant.\n\
Check the existing file names in {:?} for examples.",
po_dir()
);
}
// TODO (MSRV>=1.91): use with_added_extension instead of with_extension
let po_path = po_dir().join(&language).with_extension("po");
let mut new_po_file = OpenOptions::new()
.create_new(true)
.write(true)
.open(&po_path)
.with_context(|| format!("Failed to create file at {po_path:?}"))?;
let mut header = String::new();
let line_prefix = "# fish-note-sections: ";
let 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 lines {
use std::fmt::Write as _;
let _ = writeln!(header, "{line_prefix}{line}");
}
new_po_file
.write_all(header.as_bytes())
.with_context(|| format!("Failed to write to {po_path:?}"))?;
new_po_file
.write_all(template.serialize())
.with_context(|| format!("Failed to write to {po_path:?}"))?;
Ok(())
}
Task::Update { paths } => {
let mut thread_handles = vec![];
for path in get_po_paths(&paths)? {
let template_path_buf = template_file.path().to_owned();
let handle =
spawn(move || -> Result<()> { update_po_file(path, template_path_buf) });
thread_handles.push(handle);
}
let mut error = None;
for handle in thread_handles {
// SAFETY: `handle.join()` only returns `Err` if the thread panicked.
// Our threads should not panic, and if they do, it's OK to deal with the unexpected
// behavior by panicking here as well.
if let Err(e) = handle.join().unwrap() {
error = Some(e);
}
}
if let Some(e) = error {
return Err(e);
}
Ok(())
}
}
}
mod template {
use crate::{CommandExt as _, files_with_extension};
use anyhow::{Context as _, Result, bail};
use fish_build_helper::workspace_root;
use fish_common::{UnescapeFlags, unescape_string};
use fish_widestring::{str2wcstring, wcs2bytes};
use pcre2::bytes::Regex;
use std::{
collections::{HashMap, HashSet},
fmt::Display,
fs::OpenOptions,
io::Read as _,
path::Path,
process::Command,
sync::LazyLock,
};
// Gettext tools require this header to know which encoding is used.
const MINIMAL_HEADER: &str = r#"msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
"#;
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
enum LocalizationTier {
Tier1,
Tier2,
Tier3,
}
impl TryFrom<&str> for LocalizationTier {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"tier1" => Ok(Self::Tier1),
"tier2" => Ok(Self::Tier2),
"tier3" => Ok(Self::Tier3),
_ => Err(()),
}
}
}
impl Display for LocalizationTier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Tier1 => "tier1",
Self::Tier2 => "tier2",
Self::Tier3 => "tier3",
})
}
}
#[derive(Default)]
struct FishScriptMessages {
explicit: HashSet<String>,
implicit: HashSet<String>,
}
pub struct Template {
content: Vec<u8>,
}
impl Template {
pub fn serialize(&self) -> &[u8] {
&self.content
}
/// Create a gettext template.
/// `rust_extraction_dir` must be the path to a directory which contains the messages
/// extracted from the Rust sources.
pub fn new<P: AsRef<Path>>(rust_extraction_dir: P) -> Result<Self> {
let mut template = Self {
content: Vec::from(MINIMAL_HEADER.as_bytes()),
};
template.add_rust_messages(rust_extraction_dir)?;
template.add_fish_script_messages()?;
// TODO: keep internal set of msgids to avoid having to run msguniq. requires parsing
// gettext-extraction output
let msguniq_output = Command::new("msguniq")
.args(["--no-wrap"])
.run_with_stdio(template.content)?;
Ok(Template {
content: msguniq_output,
})
}
/// Expects `extraction_dir` to contain only files whose content are single PO entries which can be
/// concatenated into a valid PO file.
/// If this is the case, the messages are de-duplicated and sorted by `msguniq`.
/// The result is appended to `template`, with a leading section marker.
/// On failure, the process aborts.
fn add_rust_messages<P: AsRef<Path>>(&mut self, extraction_dir: P) -> Result<()> {
let extraction_dir = extraction_dir.as_ref();
let mut concatenated_content = Vec::from(MINIMAL_HEADER.as_bytes());
// Concatenate the content of all files in `extraction_dir` into `concatenated_content`.
for entry_result in extraction_dir
.read_dir()
.with_context(|| format!("Failed to read directory {extraction_dir:?}"))?
{
let entry = entry_result
.with_context(|| format!("Failed to get entry in {extraction_dir:?}"))?;
let entry_path = entry.path();
if !entry
.file_type()
.with_context(|| format!("Failed to get file type of {entry_path:?}"))?
.is_file()
{
bail!("Entry in {extraction_dir:?} is not a regular file");
}
let mut file = OpenOptions::new()
.read(true)
.open(&entry_path)
.with_context(|| format!("Failed to open file {entry_path:?}"))?;
file.read_to_end(&mut concatenated_content)
.with_context(|| format!("Failed to read file {entry_path:?}"))?;
}
// Get rid of duplicates and sort.
let msguniq_output = Command::new("msguniq")
.args(["--no-wrap", "--sort-output"])
.env("LC_ALL", "C.UTF-8")
.run_with_stdio(concatenated_content)?;
// The Header entry needs to be removed again,
// because it is added outside of this function.
let expected_prefix = MINIMAL_HEADER.as_bytes();
let actual_prefix = &msguniq_output[..expected_prefix.len()];
if expected_prefix != actual_prefix {
bail!(
"Prefix of msguniq output does not match expected header.\nExpected bytes:\n{expected_prefix:02x?}\nActual bytes:\n{actual_prefix:02x?}"
);
}
self.mark_section("tier1-from-rust");
self.content
.extend_from_slice(&msguniq_output[expected_prefix.len()..]);
self.content.push(b'\n');
Ok(())
}
fn mark_section(&mut self, section_name: &str) {
self.content
.extend_from_slice("msgid \"fish-section-".as_bytes());
self.content.extend_from_slice(section_name.as_bytes());
self.content
.extend_from_slice("\"\nmsgstr \"\"\n\n".as_bytes());
}
fn append_messages(&mut self, msgids: &HashSet<String>) -> Result<()> {
let mut unescaped_msgids = HashSet::new();
for msgid in msgids {
let unescaped_wstring = unescape_string(
&str2wcstring(msgid),
fish_common::UnescapeStringStyle::Script(UnescapeFlags::default()),
)
.with_context(|| format!("Failed to unescape the following string:\n{msgid}"))?;
unescaped_msgids.insert(
String::from_utf8(wcs2bytes(&unescaped_wstring))
.context("Parsed msgid is not valid UTF-8")?,
);
}
let mut unescaped_msgids = Vec::from_iter(unescaped_msgids);
unescaped_msgids.sort();
for msgid in &unescaped_msgids {
self.content
.extend_from_slice(format_msgid_for_po(msgid).as_bytes());
}
Ok(())
}
fn add_script_tier(
&mut self,
tier: LocalizationTier,
messages: FishScriptMessages,
) -> Result<()> {
if !messages.explicit.is_empty() {
self.mark_section(&format!("{tier}-from-script-explicitly-added"));
self.append_messages(&messages.explicit)?;
}
if !messages.implicit.is_empty() {
self.mark_section(&format!("{tier}-from-script-implicitly-added"));
self.append_messages(&messages.implicit)?;
}
Ok(())
}
fn add_fish_script_messages(&mut self) -> Result<()> {
let share_dir = workspace_root().join("share");
let relevant_file_paths = files_with_extension(
[
share_dir.join("config.fish"),
share_dir.join("completions"),
share_dir.join("functions"),
],
"fish",
)?;
let mut extracted_messages = HashMap::new();
for path in relevant_file_paths {
extract_messages_from_fish_script(path, &mut extracted_messages)?;
}
let mut messages_sorted_by_tier: Vec<_> = extracted_messages.into_iter().collect();
messages_sorted_by_tier.sort_by_key(|(tier, _)| *tier);
for (tier, messages) in messages_sorted_by_tier {
self.add_script_tier(tier, messages)?;
}
Ok(())
}
}
fn find_localization_tier<P: AsRef<Path>>(
input: &str,
path: P,
) -> Result<Option<LocalizationTier>> {
static L10N_ANNOTATION: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?:^|\n)# localization: (?<annotation_value>.*)\n").unwrap()
});
if let Some(annotation) = L10N_ANNOTATION.captures(input.as_bytes()).unwrap() {
// SAFETY: `tier` is the name of a capture group in the regex whose captures we are looking
// at. The capture is done on the bytes of an UTF-8 encoded string, so the result will also
// be UTF-8 encoded, and the sub-slice we are looking at will start and end at codepoint
// boundaries.
let annotation_value =
std::str::from_utf8(annotation.name("annotation_value").unwrap().as_bytes())
.unwrap();
if let Ok(tier) = LocalizationTier::try_from(annotation_value) {
return Ok(Some(tier));
}
if annotation_value.starts_with("skip") {
return Ok(None);
}
bail!(
"Unexpected localization annotation in file {:?}: {annotation_value}",
path.as_ref()
);
}
let dirname = path
.as_ref()
.parent()
.with_context(|| {
format!(
"Tried to get the parent of a path which does not have a parent: {:?}",
path.as_ref()
)
})?
.file_name()
.with_context(|| {
format!(
"The parent of {:?} does not have a filename component",
path.as_ref()
)
})?;
let command_name = path
.as_ref()
.file_stem()
.with_context(|| format!("The path {:?} does not have a file stem", path.as_ref()))?;
if dirname == "functions"
&& command_name
.to_str()
.is_some_and(|name| name.starts_with("fish_"))
{
return Ok(Some(LocalizationTier::Tier1));
}
if dirname != "completions" {
bail!(
"Missing localization tier for function file {:?}",
path.as_ref()
);
}
// TODO (MSRV>=1.91): use with_added_extension instead of with_extension
let doc_path = workspace_root()
.join("doc_src")
.join("cmds")
.join(command_name)
.with_extension("rst");
let doc_path_exists = std::fs::exists(&doc_path)
.with_context(|| format!("Failed to check whether a file exists at {doc_path:?}"))?;
Ok(Some(if doc_path_exists {
LocalizationTier::Tier1
} else {
LocalizationTier::Tier3
}))
}
fn extract_messages_from_fish_script<P: AsRef<Path>>(
path: P,
extracted_messages: &mut HashMap<LocalizationTier, FishScriptMessages>,
) -> Result<()> {
let path = path.as_ref();
let file_content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read from {path:?}"))?;
let Some(tier) = find_localization_tier(&file_content, path)? else {
return Ok(());
};
// TODO: use proper parser instead of regex
static EXPLICIT_MESSAGE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"\( *_ (?<message>(['"]).+?(?<!\\)\2) *\)"#).unwrap());
static IMPLICIT_MESSAGE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r#"(?:^|\n)(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (?<message>(['"]).+?(?<!\\)\2)"#).unwrap()
});
let messages_at_tier = extracted_messages.entry(tier).or_default();
for message in EXPLICIT_MESSAGE.captures_iter(file_content.as_bytes()) {
let message =
std::str::from_utf8(message.unwrap().name("message").unwrap().as_bytes()).unwrap();
messages_at_tier.explicit.insert(message.to_owned());
}
for message in IMPLICIT_MESSAGE.captures_iter(file_content.as_bytes()) {
let message =
std::str::from_utf8(message.unwrap().name("message").unwrap().as_bytes()).unwrap();
messages_at_tier.implicit.insert(message.to_owned());
}
Ok(())
}
fn format_msgid_for_po(msgid: &str) -> String {
let escaped_msgid = msgid.replace("\\", "\\\\").replace("\"", "\\\"");
format!(
"\
msgid \"{escaped_msgid}\"\n\
msgstr \"\"\n\
\n\
"
)
}
}

View File

@@ -1,70 +1,101 @@
use std::{
ffi::OsStr,
io::Write,
path::{Path, PathBuf},
process::Command,
process::{Command, Stdio},
};
use anyhow::{Context, Result, bail};
use walkdir::WalkDir;
macro_rules! fail {
($($arg:tt)+) => {{
eprintln!($($arg)+);
std::process::exit(1);
}}
}
pub mod format;
pub mod gettext;
pub mod shellcheck;
pub trait CommandExt {
fn run_or_fail(&mut self);
fn run(&mut self) -> Result<()>;
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>>;
}
impl CommandExt for Command {
fn run_or_fail(&mut self) {
match self.status() {
Ok(exit_status) => {
if !exit_status.success() {
fail!("Command did not run successfully: {:?}", self.get_program())
}
}
Err(err) => {
fail!("Failed to run command: {err}")
}
fn run(&mut self) -> Result<()> {
if !self
.status()
.with_context(|| format!("Failed to run {:?}", self.get_program()))?
.success()
{
bail!("Command did not run successfully: {:?}", self.get_program())
}
Ok(())
}
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>> {
let command_name = self.get_program().to_owned();
let mut child = self
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.with_context(|| format!("Failed to run {command_name:?}"))?;
child
.stdin
.take()
.unwrap()
.write_all(&stdin)
.with_context(|| format!("Failed to write to stdin of {command_name:?}"))?;
let command_output = child
.wait_with_output()
.with_context(|| format!("Failed to read stdout of {command_name:?}"))?;
if !command_output.status.success() {
bail!("{command_name:?} failed");
}
Ok(command_output.stdout)
}
}
pub fn cargo<I, S>(cargo_args: I)
pub const CARGO: &str = env!("CARGO");
pub fn cargo<I, S>(cargo_args: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(env!("CARGO")).args(cargo_args).run_or_fail();
Command::new(CARGO).args(cargo_args).run()
}
fn get_matching_files<P: AsRef<Path>, I: IntoIterator<Item = P>, M: Fn(&Path) -> bool>(
all_paths: I,
matcher: M,
) -> Vec<PathBuf> {
all_paths
.into_iter()
.flat_map(WalkDir::new)
.filter_map(|res| {
let entry = res.unwrap();
let path = entry.path();
if entry.file_type().is_file() && matcher(path) {
Some(path.to_owned())
} else {
None
) -> Result<Vec<PathBuf>> {
let mut matching_files = vec![];
for path in all_paths {
for dir_entry in WalkDir::new(path.as_ref()) {
let dir_entry = dir_entry
.with_context(|| format!("Failed to check paths at {:?}", path.as_ref()))?;
let path = dir_entry.path();
if dir_entry.file_type().is_file() && matcher(path) {
matching_files.push(path.to_owned());
}
})
.collect()
}
}
Ok(matching_files)
}
fn files_with_extension<P: AsRef<Path>, I: IntoIterator<Item = P>>(
all_paths: I,
extension: &str,
) -> Vec<PathBuf> {
) -> Result<Vec<PathBuf>> {
let matcher = |p: &Path| p.extension().is_some_and(|e| e == extension);
get_matching_files(all_paths, matcher)
}
fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
std::fs::copy(&from, &to)
.with_context(|| {
format!(
"Failed to copy from {:?} to {:?}",
from.as_ref(),
to.as_ref()
)
})
.map(|_| ())
}

View File

@@ -1,7 +1,9 @@
use clap::{Parser, Subcommand};
use anyhow::{Context, Result};
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::CompleteEnv;
use fish_build_helper::as_os_strs;
use std::{path::PathBuf, process::Command};
use xtask::{CommandExt as _, cargo, format::FormatArgs};
use xtask::{CommandExt, cargo, format::FormatArgs, gettext::GettextArgs, shellcheck::shellcheck};
#[derive(Parser)]
#[command(
@@ -20,6 +22,8 @@ enum Task {
Check,
/// Format files or check if they are correctly formatted.
Format(FormatArgs),
/// Work on the gettext PO files.
Gettext(GettextArgs),
/// Build HTML docs
HtmlDocs {
/// Path to a fish_indent executable. If none is specified, fish_indent will be built.
@@ -28,53 +32,85 @@ enum Task {
},
/// Build man pages
ManPages,
/// Run ShellCheck on non-fish shell scripts
#[command(name = "shellcheck")]
ShellCheck,
}
fn main() {
/// Only used to enable completion generation.
/// [`clap_complete`] is not built to account for the situation we have here, where the CLI does not
/// correspond to a top-level shell command.
/// We work around this here by pretending that we are building a CLI for the `cargo` command, which
/// only has the single subcommand `xtask`.
/// These completions can then be combined with the regular cargo completions.
#[derive(Parser)]
#[command(name = "cargo")]
struct FakeCargoWrapperForCompletion {
#[command(subcommand)]
xtask: FakeCliForCompletion,
}
#[derive(Subcommand)]
enum FakeCliForCompletion {
/// Run fish's xtasks
#[command(subcommand)]
Xtask(Task),
}
fn main() -> Result<()> {
CompleteEnv::with_factory(FakeCargoWrapperForCompletion::command).complete();
let cli = Cli::parse();
match cli.task {
Task::Check => run_checks(),
Task::Format(format_args) => xtask::format::format(format_args),
Task::Gettext(gettext_args) => xtask::gettext::gettext(gettext_args),
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
Task::ShellCheck => shellcheck(),
}
}
fn run_checks() {
fn run_checks() -> Result<()> {
let repo_root_dir = fish_build_helper::workspace_root();
let check_script = repo_root_dir.join("build_tools").join("check.sh");
Command::new(check_script).run_or_fail();
Command::new(check_script).run()
}
fn build_html_docs(fish_indent: Option<PathBuf>) {
let fish_indent_path = fish_indent.unwrap_or_else(|| {
// Build fish_indent if no existing one is specified.
cargo([
"build",
"--bin",
"fish_indent",
"--profile",
"dev",
"--no-default-features",
]);
fish_build_helper::fish_build_dir()
.join("debug")
.join("fish_indent")
});
fn build_html_docs(fish_indent: Option<PathBuf>) -> Result<()> {
let fish_indent_path = match fish_indent {
Some(path) => path,
None => {
// Build fish_indent if no existing one is specified.
cargo([
"build",
"--bin",
"fish_indent",
"--profile",
"dev",
"--no-default-features",
])?;
fish_build_helper::fish_build_dir()
.join("debug")
.join("fish_indent")
}
};
// Set path so `sphinx-build` can find `fish_indent`.
// Create tempdir to store symlink to fish_indent.
// This is done to avoid adding other binaries to the PATH.
let tempdir = fish_tempfile::new_dir().unwrap();
let tempdir = fish_tempfile::new_dir().context("Failed to create tempdir")?;
std::os::unix::fs::symlink(
std::fs::canonicalize(fish_indent_path).unwrap(),
std::fs::canonicalize(&fish_indent_path).with_context(|| {
format!("Failed to canonicalize path to `fish_indent`: {fish_indent_path:?}")
})?,
tempdir.path().join("fish_indent"),
)
.unwrap();
let new_path = format!(
"{}:{}",
tempdir.path().to_str().unwrap(),
fish_build_helper::env_var("PATH").unwrap()
);
.context("Failed to create symlink for fish_indent")?;
let mut new_path = tempdir.path().as_os_str().to_owned();
if let Some(current_path) = std::env::var_os("PATH") {
new_path.push(":");
new_path.push(current_path);
}
let doc_src_dir = fish_build_helper::workspace_root().join("doc_src");
let doctrees_dir = fish_build_helper::fish_doc_dir().join(".doctrees-html");
let html_dir = fish_build_helper::fish_doc_dir().join("html");
@@ -94,5 +130,5 @@ fn build_html_docs(fish_indent: Option<PathBuf>) {
Command::new(option_env!("FISH_SPHINX").unwrap_or("sphinx-build"))
.env("PATH", new_path)
.args(args)
.run_or_fail();
.run()
}

View File

@@ -0,0 +1,52 @@
use anyhow::{Context, Result};
use fish_build_helper::workspace_root;
use ignore::Walk;
use pcre2::bytes::Regex;
use std::{
fs::File,
io::{BufRead, BufReader},
path::{Path, PathBuf},
process::Command,
sync::LazyLock,
};
use crate::CommandExt;
pub fn shellcheck() -> Result<()> {
Command::new("shellcheck")
.args(files_to_check()?)
.current_dir(workspace_root())
.run()
}
fn is_shell_script<P: AsRef<Path>>(path: P) -> Result<bool> {
let file = File::open(&path).with_context(|| format!("Failed to open {:?}", path.as_ref()))?;
let mut first_line = String::new();
let Ok(_) = BufReader::new(file).read_line(&mut first_line) else {
return Ok(false);
};
static SHEBANG_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("^#!.*[^i]sh").unwrap());
Ok(SHEBANG_REGEX
.is_match(first_line.trim().as_bytes())
.unwrap())
}
fn files_to_check() -> Result<Vec<PathBuf>> {
let mut files = vec![];
for dir_entry in Walk::new(workspace_root()) {
let dir_entry = dir_entry.context("Error traversing workspace")?;
if !dir_entry
.file_type()
.with_context(|| format!("Failed to determine file type of {dir_entry:?}"))?
.is_file()
{
continue;
}
let path = dir_entry.into_path();
if !is_shell_script(&path)? {
continue;
}
files.push(path);
}
Ok(files)
}

View File

@@ -1,24 +1,24 @@
[licenses]
# We want really high confidence when inferring licenses from text
confidence-threshold = 0.93
unused-allowed-license = "allow" # don't warn for unused licenses in this list
unused-allowed-license = "allow" # don't warn for unused licenses in this list
allow = [
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"GPL-2.0",
"GPL-2.0-only",
"ISC",
"LGPL-2.0",
"LGPL-2.0-or-later",
"MIT",
"MPL-2.0",
"PSF-2.0",
"Unicode-DFS-2016",
"Unicode-3.0",
"WTFPL",
"Zlib",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"GPL-2.0",
"GPL-2.0-only",
"ISC",
"LGPL-2.0",
"LGPL-2.0-or-later",
"MIT",
"MPL-2.0",
"PSF-2.0",
"Unicode-DFS-2016",
"Unicode-3.0",
"WTFPL",
"Zlib",
]
[sources.allow-org]

View File

@@ -221,7 +221,7 @@ These variables are passed to the function as local exported variables.
The script should write any error messages to stdout, not stderr. It should return a status of zero if the flag value is valid otherwise a non-zero status to indicate it is invalid.
Fish ships with a ``_validate_int`` function that accepts a ``--min`` and ``--max`` flag. Let's say your command accepts a ``-m`` or ``--max`` flag and the minimum allowable value is zero and the maximum is 5. You would define the option like this: ``m/max=!_validate_int --min 0 --max 5``. The default if you call ``_validate_int`` without those flags is to check that the value is a valid integer with no limits on the min or max value allowed.
fish ships with a ``_validate_int`` function that accepts a ``--min`` and ``--max`` flag. Let's say your command accepts a ``-m`` or ``--max`` flag and the minimum allowable value is zero and the maximum is 5. You would define the option like this: ``m/max=!_validate_int --min 0 --max 5``. The default if you call ``_validate_int`` without those flags is to check that the value is a valid integer with no limits on the min or max value allowed.
Here are some examples of flag validations::
@@ -251,7 +251,7 @@ Some *OPTION_SPEC* examples:
- ``n/name=?`` means that both ``-n`` and ``--name`` are valid. It accepts an optional value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurrence. Each value will be the value given to the option, or the empty string if no value was given.
- ``name=+`` means that only ``--name`` is valid. It requires a value and can be used more than once. If the flag is seen then ``_flag_name`` will be set with the values associated with each occurrence.

View File

@@ -6,7 +6,7 @@ Synopsis
.. synopsis::
cd [DIRECTORY]
cd [( -L | --no-dereference ) | ( -P | --dereference )] [DIRECTORY]
Description
-----------
@@ -18,14 +18,25 @@ Description
``cd`` changes the current working directory.
The :envvar:`PWD` environment variable is updated with the new working directory, and the previous directory
is added to the :ref:`directory history <directory-history>`.
If *DIRECTORY* is given, it will become the new directory. If no parameter is given, the :envvar:`HOME` environment variable will be used.
If *DIRECTORY* is a relative path, all the paths in the :envvar:`CDPATH` will be tried as prefixes for it, in addition to :envvar:`PWD`.
It is recommended to keep **.** as the first element of :envvar:`CDPATH`, or :envvar:`PWD` will be tried last.
Fish will also try to change directory if given a command that looks like a directory (starting with **.**, **/** or **~**, or ending with **/**), without explicitly requiring **cd**.
The new directory name is partially resolved to remove redundant segments (``.`` or ``..``).
Fish also ships a wrapper function around the builtin **cd** that understands ``cd -`` as changing to the previous directory.
``cd`` defaults to treating symbolic links as real directories, and not resolving them to their underlying
targets. The ``$PWD`` :ref:`special variable <variables-special>` variable will contain the path that was
supplied. This default behaviour can be enforced with the ``-L`` or ``--no-dereference`` option.
The ``-P`` or ``--dereference`` option resolves all symbolic links first. This was the default in fish versions before 3.0.0.
fish will also try to change directory if given a command that looks like a directory (starting with **.**, **/** or **~**, or ending with **/**), without explicitly requiring **cd**.
fish also ships a wrapper function around the builtin **cd** that understands ``cd -`` as changing to the previous directory.
See also :doc:`prevd <prevd>`.
This wrapper function maintains a history of the 25 most recently visited directories in the ``$dirprev`` and ``$dirnext`` global variables.
If you make those universal variables your **cd** history is shared among all fish instances.
@@ -45,6 +56,9 @@ Examples
cd /usr/src/fish-shell
# changes the working directory to /usr/src/fish-shell
cd -P /tmp/link
# resolves /tmp/link to its target before recording the directory
See Also
--------

View File

@@ -81,7 +81,7 @@ The following options are available:
**-h** or **--help**
Displays help about using this command.
Command-specific tab-completions in ``fish`` are based on the notion of options and arguments. An option is a parameter which begins with a hyphen, such as ``-h``, ``-help`` or ``--help``. Arguments are parameters that do not begin with a hyphen. Fish recognizes three styles of options, the same styles as the GNU getopt library. These styles are:
Command-specific tab-completions in ``fish`` are based on the notion of options and arguments. An option is a parameter which begins with a hyphen, such as ``-h``, ``-help`` or ``--help``. Arguments are parameters that do not begin with a hyphen. fish recognizes three styles of options, the same styles as the GNU getopt library. These styles are:
- Short options, like ``-a``. Short options are a single character long, are preceded by a single hyphen and can be grouped together (like ``-la``, which is equivalent to ``-l -a``). Option arguments may be specified by appending the option with the value (``-w32``), or, if ``--require-parameter`` is given, in the following parameter (``-w 32``).

View File

@@ -18,7 +18,7 @@ When fish tries to execute a command and can't find it, it invokes this function
It can print a message to tell you about it, and it often also checks for a missing package that would include the command.
Fish ships multiple handlers for various operating systems and chooses from them when this function is loaded,
fish ships multiple handlers for various operating systems and chooses from them when this function is loaded,
or you can define your own.
It receives the full commandline as one argument per token, so $argv[1] contains the missing command.
@@ -50,25 +50,3 @@ Or the simple default handler::
function fish_command_not_found
__fish_default_command_not_found_handler $argv
end
Backwards compatibility
-----------------------
This command was introduced in fish 3.2.0. Previous versions of fish used the "fish_command_not_found" :ref:`event <event>` instead.
To define a handler that works in older versions of fish as well, define it the old way::
function __fish_command_not_found_handler --on-event fish_command_not_found
echo COMMAND WAS NOT FOUND MY FRIEND $argv[1]
end
in which case fish will define a ``fish_command_not_found`` that calls it,
or define a wrapper::
function fish_command_not_found
echo "G'day mate, could not find your command: $argv"
end
function __fish_command_not_found_handler --on-event fish_command_not_found
fish_command_not_found $argv
end

View File

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

View File

@@ -40,7 +40,7 @@ Run::
>_ funced fish_prompt
This will open up your editor, allowing you to modify the function. When you're done, save and quit. Fish will reload the function, so you should see the changes right away.
This will open up your editor, allowing you to modify the function. When you're done, save and quit. fish will reload the function, so you should see the changes right away.
When you're done, use::

View File

@@ -32,7 +32,7 @@ The following options are available:
If the wrapped command is the same as the function name, this will be ignored.
**-e** *EVENT_NAME* or **--on-event** *EVENT_NAME*
Run this function when the specified named event is emitted. Fish internally generates named events, for example, when showing the prompt. Custom events can be emitted using the :doc:`emit <emit>` command.
Run this function when the specified named event is emitted. fish internally generates named events, for example, when showing the prompt. Custom events can be emitted using the :doc:`emit <emit>` command.
**-v** *VARIABLE_NAME* or **--on-variable** *VARIABLE_NAME*
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).
@@ -56,10 +56,12 @@ The following options are available:
**-V** or **--inherit-variable NAME**
Snapshots the value of the variable ``NAME`` and defines a local variable with that same name and value when the function is defined. This is similar to a closure in other languages like Python but a bit different. Note the word "snapshot" in the first sentence. If you change the value of the variable after defining the function, even if you do so in the same scope (typically another function) the new value will not be used by the function you just created using this option. See the ``function notify`` example below for how this might be used.
The event handler switches (``on-event``, ``on-variable``, ``on-job-exit``, ``on-process-exit`` and ``on-signal``) cause a function to run automatically at specific events. New named events for ``--on-event`` can be fired using the :doc:`emit <emit>` builtin. Fish already generates a few events, see :ref:`event` for more.
The event handler switches (``on-event``, ``on-variable``, ``on-job-exit``, ``on-process-exit`` and ``on-signal``) cause a function to run automatically at specific events. New named events for ``--on-event`` can be fired using the :doc:`emit <emit>` builtin. fish already generates a few events, see :ref:`event` for more.
Functions names cannot be reserved words. These are elements of fish syntax or builtin commands which are essential for the operations of the shell. Current reserved words are ``[``, ``_``, ``and``, ``argparse``, ``begin``, ``break``, ``builtin``, ``case``, ``command``, ``continue``, ``else``, ``end``, ``eval``, ``exec``, ``for``, ``function``, ``if``, ``not``, ``or``, ``read``, ``return``, ``set``, ``status``, ``string``, ``switch``, ``test``, ``time``, and ``while``.
Care should be taken when creating a function of the same name as an existing shell builtin or common program. If the function behaves differently, it is very common for problems to occur within fish or in scripts written by others. Consider writing an :doc:`abbreviation <abbr>` if you are wanting to replace one tool with another for interactive use.
Example
-------

View File

@@ -109,7 +109,7 @@ You can set the ``fish_history`` variable to another name for the current shell
You can change ``fish_history`` at any time (by using ``set -x fish_history "session_name"``) and it will take effect right away. If you set it to ``"default"``, it will use the default session name (which is ``"fish"``).
Other shells such as bash and zsh use a variable named ``HISTFILE`` for a similar purpose. Fish uses a different name to avoid conflicts and signal that the behavior is different (session name instead of a file path). Also, if you set the var to anything other than ``fish`` or ``default`` it will inhibit importing the bash history. That's because the most common use case for this feature is to avoid leaking private or sensitive history when giving a presentation.
Other shells such as bash and zsh use a variable named ``HISTFILE`` for a similar purpose. fish uses a different name to avoid conflicts and signal that the behavior is different (session name instead of a file path). Also, if you set the var to anything other than ``fish`` or ``default`` it will inhibit importing the bash history. That's because the most common use case for this feature is to avoid leaking private or sensitive history when giving a presentation.
Notes
-----

View File

@@ -53,7 +53,7 @@ See also
``if`` is only as useful as the command used as the condition.
Fish ships a few:
fish ships a few:
- :doc:`test` can compare numbers, strings and check paths
- :doc:`string` can perform string operations including wildcard and regular expression matches

View File

@@ -218,6 +218,6 @@ Examples
Compatibility notes
-------------------
Fish 1.x and 2.x releases relied on the ``bc`` command for handling ``math`` expressions. Starting with fish 3.0.0 fish uses the tinyexpr library and evaluates the expression without the involvement of any external commands.
fish 1.x and 2.x releases relied on the ``bc`` command for handling ``math`` expressions. Starting with fish 3.0.0 fish uses the tinyexpr library and evaluates the expression without the involvement of any external commands.
You don't need to use ``--`` before the expression, even if it begins with a minus sign which might otherwise be interpreted as an invalid option. If you do insert ``--`` before the expression, it will cause option scanning to stop just like for every other command and it won't be part of the expression.

View File

@@ -95,7 +95,7 @@ The following options control how much is read and how it is stored:
Marks the end of the line with the NUL character, instead of newline. This also disables interactive mode.
**-L** or **--line**
Reads each line into successive variables, and stops after each variable has been filled. This cannot be combined with the ``--delimiter`` option.
Reads each line into successive variables, and stops after each variable has been filled. This cannot be combined with the ``--null`` option, or options to control splitting like ``--delimiter``.
Without the ``--line`` option, ``read`` reads a single line of input from standard input, breaks it into tokens, and then assigns one token to each variable specified in *VARIABLES*. If there are more tokens than variables, the complete remainder is assigned to the last variable.

View File

@@ -220,4 +220,4 @@ This runs fish with a temporary home directory::
Notes
-----
- Fish versions prior to 3.0 supported the syntax ``set PATH[1] PATH[4] /bin /sbin``, which worked like ``set PATH[1 4] /bin /sbin``.
- fish versions prior to 3.0 supported the syntax ``set PATH[1] PATH[4] /bin /sbin``, which worked like ``set PATH[1 4] /bin /sbin``.

View File

@@ -11,8 +11,10 @@ Synopsis
Description
-----------
``set_color`` is used to control the color and styling of text in the terminal.
*VALUE* describes that styling.
``set_color`` controls the color and styling of text in the terminal.
It writes non-printing color and text style escape sequences to standard output.
*VALUE* describes the styling.
*VALUE* can be a reserved color name like **red** or an RGB color value given as 3 or 6 hexadecimal digits ("F27" or "FF2277").
A special keyword **normal** resets text formatting to terminal defaults, however it is not recommended and the **--reset** option is preferred as it is less confusing and more future-proof.
@@ -29,7 +31,7 @@ Hexadecimal RGB values can be in lower or uppercase.
If :envvar:`fish_term24bit` is set to 0, fish will translate RGB values to the nearest color on the 256-color palette.
If :envvar:`fish_term256` is also set to 0, fish will translate them to the 16-color palette instead.
Fish launched as ``fish -d term_support`` will include diagnostic messages that indicate the color support mode in use.
fish launched as ``fish -d term_support`` will include diagnostic messages that indicate the color support mode in use.
If multiple colors are specified, fish prefers the first RGB one.
However if :envvar:`fish_term256` is set to 0, fish prefers the first named color specified.
@@ -91,9 +93,11 @@ Notes
1. Using ``set_color normal`` will reset all colors and modes to the terminal's default.
2. In contrast, ``set_color --foreground normal`` will only reset the foreground color and leave all the other colors and modes unchanged.
3. Because of the risk of confusion, ``set_color --reset`` is recommended over ``set_color normal``.
4. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
4. Setting the background color only affects subsequently written characters. fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
5. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
6. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
6. If you use ``set_color`` in a command substitution or a pipe, these characters will also be captured.
This may or may not be desirable.
Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
Examples
--------

View File

@@ -38,7 +38,7 @@ just always use ``--`` to avoid unwelcome surprises.
``string join0`` adds a trailing NUL. This is most useful in conjunction with tools that accept NUL-delimited input, such as ``sort -z``.
Because Unix uses NUL as the string terminator, passing the output of ``string join0`` as an *argument* to a command (via a :ref:`command substitution <expand-command-substitution>`) won't actually work.
Fish will pass the correct bytes along, but the command won't be able to tell where the argument ends.
fish will pass the correct bytes along, but the command won't be able to tell where the argument ends.
This is a limitation of Unix' argument passing.
.. END DESCRIPTION

View File

@@ -102,11 +102,11 @@ The following additional options are also understood by ``ulimit``:
The ``fish`` implementation of ``ulimit`` should behave identically to the implementation in bash, except for these differences:
- Fish ``ulimit`` supports GNU-style long options for all switches.
- fish ``ulimit`` supports GNU-style long options for all switches.
- Fish ``ulimit`` does not support the **-p** option for getting the pipe size. The bash implementation consists of a compile-time check that empirically guesses this number by writing to a pipe and waiting for SIGPIPE. Fish does not do this because this method of determining pipe size is unreliable. Depending on bash version, there may also be further additional limits to set in bash that do not exist in fish.
- fish ``ulimit`` does not support the **-p** option for getting the pipe size. The bash implementation consists of a compile-time check that empirically guesses this number by writing to a pipe and waiting for SIGPIPE. fish does not do this because this method of determining pipe size is unreliable. Depending on bash version, there may also be further additional limits to set in bash that do not exist in fish.
- Fish ``ulimit`` does not support getting or setting multiple limits in one command, except reporting all values using the **-a** switch.
- fish ``ulimit`` does not support getting or setting multiple limits in one command, except reporting all values using the **-a** switch.
Example

View File

@@ -38,7 +38,7 @@ which offers yes/no in these cases::
> myprog -o <TAB>
> myprog --output <TAB>
Fish will also offer files by default, in addition to the arguments you specified. You would either inhibit file completion for a single option::
fish will also offer files by default, in addition to the arguments you specified. You would either inhibit file completion for a single option::
complete -c myprog -s o -l output --no-files -ra "yes no"
@@ -142,15 +142,15 @@ Functions beginning with the string ``__fish_print_`` print a newline separated
Where to put completions
------------------------
Completions can be defined on the commandline or in a configuration file, but they can also be automatically loaded. Fish automatically searches through any directories in the list variable ``$fish_complete_path``, and any completions defined are automatically loaded when needed. A completion file must have a filename consisting of the name of the command to complete and the suffix ``.fish``.
Completions can be defined on the commandline or in a configuration file, but they can also be automatically loaded. fish automatically searches through any directories in the list variable ``$fish_complete_path``, and any completions defined are automatically loaded when needed. A completion file must have a filename consisting of the name of the command to complete and the suffix ``.fish``.
By default, Fish searches the following for completions, using the first available file that it finds:
By default, fish searches the following for completions, using the first available file that it finds:
- A directory for end-users to keep their own completions, usually ``~/.config/fish/completions`` (controlled by the ``XDG_CONFIG_HOME`` environment variable);
- A directory for systems administrators to install completions for all users on the system, usually ``/etc/fish/completions``;
- A user-specified directory for third-party vendor completions, usually ``~/.local/share/fish/vendor_completions.d`` (controlled by the ``XDG_DATA_HOME`` environment variable);
- A directory for third-party software vendors to ship their own completions for their software, usually ``/usr/share/fish/vendor_completions.d``;
- The completions shipped with fish, usually installed in ``/usr/share/fish/completions``; and
- The completions shipped with fish, which are stored in the fish program and can be seen with ``status list-files``; and
- Completions automatically generated from the operating system's manual, usually stored in ``~/.cache/fish/generated_completions`` (controlled by ``XDG_CACHE_HOME`` environment variable).
These paths are controlled by parameters set at build, install, or run time, and may vary from the defaults listed above.

View File

@@ -218,7 +218,7 @@ latex_engine = "xelatex"
def get_command_description(path, name):
"""Return the description for a command, by parsing its synopsis line"""
with open(path) as opened:
with open(path, encoding="utf8") as opened:
for line in opened:
if line.startswith(name + " - "):
_, desc = line.split(" - ", 1)

View File

@@ -5,7 +5,7 @@ This is a description of the design principles that have been used to design fis
1. Everything that can be done in other shell languages should be possible to do in fish, though fish may rely on external commands in doing so.
2. Fish should be user-friendly, but not at the expense of expressiveness. Most tradeoffs between power and ease of use can be avoided with careful design.
2. fish should be user-friendly, but not at the expense of expressiveness. Most tradeoffs between power and ease of use can be avoided with careful design.
3. Whenever possible without breaking the above goals, fish should follow POSIX.
@@ -55,9 +55,9 @@ Different configuration options are a nightmare to maintain, since the number of
Examples:
- Fish allows the user to set various syntax highlighting colors. This is needed because fish does not know what colors the terminal uses by default, which might make some things unreadable. The proper solution would be for text color preferences to be defined centrally by the user for all programs, and for the terminal emulator to send these color properties to fish.
- fish allows the user to set various syntax highlighting colors. This is needed because fish does not know what colors the terminal uses by default, which might make some things unreadable. The proper solution would be for text color preferences to be defined centrally by the user for all programs, and for the terminal emulator to send these color properties to fish.
- Fish does not allow you to set the number of history entries, different language substyles or any number of other common shell configuration options.
- fish does not allow you to set the number of history entries, different language substyles or any number of other common shell configuration options.
A special note on the evils of configurability is the long list of very useful features found in some shells, that are not turned on by default. Both zsh and bash support command-specific completions, but no such completions are shipped with bash by default, and they are turned off by default in zsh. Other features that zsh supports that are disabled by default include tab-completion of strings containing wildcards, a sane completion pager and a history file.

View File

@@ -4,7 +4,7 @@ Frequently asked questions
What is the equivalent to this thing from bash (or other shells)?
-----------------------------------------------------------------
See :doc:`Fish for bash users <fish_for_bash_users>`
See :doc:`fish for bash users <fish_for_bash_users>`
How do I set or clear an environment variable?
----------------------------------------------
@@ -327,7 +327,7 @@ This is more important to fish than other shells because features like syntax hi
Sometimes, there is disagreement on the width. There are numerous causes and fixes for this:
- It is possible the character is too new for your system to know - in this case you need to refrain from using it.
- Fish or your terminal might not know about the character or handle it wrong - in this case fish or your terminal needs to be fixed, or you need to update to a fixed version.
- fish or your terminal might not know about the character or handle it wrong - in this case fish or your terminal needs to be fixed, or you need to update to a fixed version.
- The character has an "ambiguous" width and fish thinks that means a width of X while your terminal thinks it's Y. In this case you either need to change your terminal's configuration or set $fish_ambiguous_width to the correct value.
- The character is an emoji and your system only supports Unicode 8. In this case set $fish_emoji_width to 1.

View File

@@ -1,7 +1,7 @@
Fish for bash users
fish for Bash users
===================
This is to give you a quick overview if you come from bash (or to a lesser extent other shells like zsh or ksh) and want to know how fish differs. Fish is intentionally not POSIX-compatible and as such some of the things you are used to work differently.
This is to give you a quick overview if you come from bash (or to a lesser extent other shells like zsh or ksh) and want to know how fish differs. fish is intentionally not POSIX-compatible and as such some of the things you are used to work differently.
Many things are similar - they both fundamentally expand commandlines to execute commands, have pipes, redirections, variables, globs, use command output in various ways. This document is there to quickly show you the differences.
@@ -10,7 +10,7 @@ Many things are similar - they both fundamentally expand commandlines to execute
Command substitutions
---------------------
Fish spells command substitutions as ``$(command)`` or ``(command)``, but not ```command```.
fish spells command substitutions as ``$(command)`` or ``(command)``, but not ```command```.
In addition, it only splits them on newlines instead of $IFS. If you want to split on something else, use :doc:`string split <cmds/string-split>`, :doc:`string split0 <cmds/string-split>` or :doc:`string collect <cmds/string-collect>`. If those are used as the last command in a command substitution the splits they create are carried over. So::
@@ -21,7 +21,7 @@ will correctly handle all possible filenames.
Variables
---------
Fish sets and erases variables with :doc:`set <cmds/set>` instead of ``VAR=VAL`` and a variety of separate builtins like ``declare`` and ``unset`` and ``export``. ``set`` takes options to determine the scope and exportedness of a variable::
fish sets and erases variables with :doc:`set <cmds/set>` instead of ``VAR=VAL`` and a variety of separate builtins like ``declare`` and ``unset`` and ``export``. ``set`` takes options to determine the scope and exportedness of a variable::
# Define $PAGER *g*lobal and e*x*ported,
# so this is like ``export PAGER=less``
@@ -41,7 +41,7 @@ or to erase variables::
PAGER=cat git log
Fish does not perform word splitting. Once a variable has been set to a value, that value stays as it is, so double-quoting variable expansions isn't the necessity it is in bash. [#]_
fish does not perform word splitting. Once a variable has been set to a value, that value stays as it is, so double-quoting variable expansions isn't the necessity it is in bash. [#]_
For instance, here's bash
@@ -99,10 +99,19 @@ See :ref:`Shell variables <variables>` for more.
.. _bash-globs:
Variable defaults (``${my_variable:-"default value"}``)
-------------------------------------------------------
fish doesn't have ``${my_variable:-fallback}`` for providing default values to unset variables. Instead, you can set default values by checking whether the variable has been set yet::
# Ensure XDG_CONFIG_HOME is set or use a default value
set -q XDG_CONFIG_HOME || set XDG_CONFIG_HOME $HOME/.config
# now use XDG_CONFIG_HOME as normal
Wildcards (globs)
-----------------
Fish only supports the ``*`` and ``**`` glob (and the deprecated ``?`` glob) as syntax. If a glob doesn't match it fails the command (like with bash's ``failglob``) unless the command is ``for``, ``set`` or ``count`` or the glob is used with an environment override (``VAR=* command``), in which case it expands to nothing (like with bash's ``nullglob`` option).
fish only supports the ``*`` and ``**`` glob (and the deprecated ``?`` glob) as syntax. If a glob doesn't match it fails the command (like with bash's ``failglob``) unless the command is ``for``, ``set`` or ``count`` or the glob is used with an environment override (``VAR=* command``), in which case it expands to nothing (like with bash's ``nullglob`` option).
Globbing doesn't happen on expanded variables, so::
@@ -122,7 +131,7 @@ See :ref:`Wildcards <expand-wildcard>` for more.
Quoting
-------
Fish has two quoting styles: ``""`` and ``''``. Variables are expanded in double-quotes, nothing is expanded in single-quotes.
fish has two quoting styles: ``""`` and ``''``. Variables are expanded in double-quotes, nothing is expanded in single-quotes.
There is no ``$''``, instead the sequences that would transform are transformed *when unquoted*::
@@ -135,7 +144,7 @@ See :ref:`Quotes <quotes>` for more.
String manipulation
-------------------
Fish does not have ``${foo%bar}``, ``${foo#bar}`` and ``${foo/bar/baz}``. Instead string manipulation is done by the :doc:`string <cmds/string>` builtin.
fish does not have ``${foo%bar}``, ``${foo#bar}`` and ``${foo/bar/baz}``. Instead string manipulation is done by the :doc:`string <cmds/string>` builtin.
For example, to replace "bar" with "baz"::
@@ -199,7 +208,7 @@ as fish's :doc:`source <cmds/source>` can read from stdin.
Heredocs
--------
Fish does not have ``<<EOF`` "heredocs". Instead of
fish does not have ``<<EOF`` "heredocs". Instead of
.. code-block:: sh
@@ -271,14 +280,14 @@ So heredocs really are minor syntactical sugar that introduces a lot of special
Test (``test``, ``[``, ``[[``)
------------------------------
Fish has a POSIX-compatible ``test`` or ``[`` builtin. There is no ``[[`` and ``test`` does not accept ``==`` as a synonym for ``=``. It can compare floating point numbers, however.
fish has a POSIX-compatible ``test`` or ``[`` builtin. There is no ``[[`` and ``test`` does not accept ``==`` as a synonym for ``=``. It can compare floating point numbers, however.
``set -q`` can be used to determine if a variable exists or has a certain number of elements (``set -q foo[2]``).
Arithmetic Expansion
--------------------
Fish does not have ``$((i+1))`` arithmetic expansion, computation is handled by :doc:`math <cmds/math>`::
fish does not have ``$((i+1))`` arithmetic expansion, computation is handled by :doc:`math <cmds/math>`::
math $i + 1
@@ -301,7 +310,7 @@ Both ``*`` and ``x`` are valid ways to spell multiplication, but ``*`` needs to
Prompts
-------
Fish does not use the ``$PS1``, ``$PS2`` and so on variables. Instead the prompt is the output of the :doc:`fish_prompt <cmds/fish_prompt>` function, plus the :doc:`fish_mode_prompt <cmds/fish_mode_prompt>` function if :ref:`vi mode <vi-mode>` is enabled. The output of the :doc:`fish_right_prompt <cmds/fish_right_prompt>` function is used for the right-sided prompt.
fish does not use the ``$PS1``, ``$PS2`` and so on variables. Instead the prompt is the output of the :doc:`fish_prompt <cmds/fish_prompt>` function, plus the :doc:`fish_mode_prompt <cmds/fish_mode_prompt>` function if :ref:`vi mode <vi-mode>` is enabled. The output of the :doc:`fish_right_prompt <cmds/fish_right_prompt>` function is used for the right-sided prompt.
As an example, here's a relatively simple bash prompt:
@@ -323,18 +332,18 @@ and a rough fish equivalent::
This shows a few differences:
- Fish provides :doc:`set_color <cmds/set_color>` to color text. It can use the 16 named colors and also RGB sequences (so you could also use ``set_color 5555FF``)
- fish provides :doc:`set_color <cmds/set_color>` to color text. It can use the 16 named colors and also RGB sequences (so you could also use ``set_color 5555FF``)
- Instead of introducing specific escapes like ``\h`` for the hostname, the prompt is a function. To achieve the effect of ``\h``, fish provides helper functions like :doc:`prompt_hostname <cmds/prompt_hostname>`, which prints a shortened version of the hostname.
- Fish offers other helper functions for adding things to the prompt, like :doc:`fish_vcs_prompt <cmds/fish_vcs_prompt>` for adding a display for common version control systems (git, mercurial, svn), and :doc:`prompt_pwd <cmds/prompt_pwd>` for showing a shortened ``$PWD`` (the user's home directory becomes ``~`` and any path component is shortened).
- fish offers other helper functions for adding things to the prompt, like :doc:`fish_vcs_prompt <cmds/fish_vcs_prompt>` for adding a display for common version control systems (git, mercurial, svn), and :doc:`prompt_pwd <cmds/prompt_pwd>` for showing a shortened ``$PWD`` (the user's home directory becomes ``~`` and any path component is shortened).
The default prompt is reasonably full-featured and its code can be read via ``type fish_prompt``.
Fish does not have ``$PS2`` for continuation lines, instead it leaves the lines indented to show that the commandline isn't complete yet.
fish does not have ``$PS2`` for continuation lines, instead it leaves the lines indented to show that the commandline isn't complete yet.
Blocks and loops
----------------
Fish's blocking constructs look a little different. They all start with a word, end in ``end`` and don't have a second starting word::
fish's blocking constructs look a little different. They all start with a word, end in ``end`` and don't have a second starting word::
for i in 1 2 3; do
echo $i
@@ -393,7 +402,7 @@ Fish's blocking constructs look a little different. They all start with a word,
# (bash allows the word "function",
# but this is an extension)
Fish does not have an ``until``. Use ``while not`` or ``while !``.
fish does not have an ``until``. Use ``while not`` or ``while !``.
Subshells
---------
@@ -415,7 +424,7 @@ This includes things like:
baz &
done
Fish does not currently have subshells. You will have to find a different solution. The isolation can usually be achieved by scoping variables (with ``set -l``), but if you really do need to run your code in a new shell environment you can use ``fish -c 'your code here'`` to do so explicitly.
fish does not currently have subshells. You will have to find a different solution. The isolation can usually be achieved by scoping variables (with ``set -l``), but if you really do need to run your code in a new shell environment you can use ``fish -c 'your code here'`` to do so explicitly.
``()`` subshells are often confused with ``{}`` grouping, which does *not* use a subshell. When you just need to group, you can use ``begin; end`` in fish::

View File

@@ -1,16 +1,16 @@
Interactive use
===============
Fish prides itself on being really nice to use interactively. That's down to a few features we'll explain in the next few sections.
fish prides itself on being really nice to use interactively. That's down to a few features we'll explain in the next few sections.
Fish is used by giving commands in the fish language, see :doc:`The Fish Language <language>` for information on that.
fish is used by giving commands in the fish language, see :doc:`The fish Language <language>` for information on that.
Help
----
Fish has an extensive help system. Use the :doc:`help <cmds/help>` command to obtain help on a specific subject or command. For instance, writing ``help syntax`` displays the :ref:`syntax section <syntax>` of this documentation.
fish has an extensive help system. Use the :doc:`help <cmds/help>` command to obtain help on a specific subject or command. For instance, writing ``help syntax`` displays the :ref:`syntax section <syntax>` of this documentation.
Fish also has man pages for its commands, and translates the help pages to man pages. For example, ``man set`` will show the documentation for ``set`` as a man page.
fish also has man pages for its commands, and translates the help pages to man pages. For example, ``man set`` will show the documentation for ``set`` as a man page.
Help on a specific builtin can also be obtained with the ``-h`` parameter. For instance, to obtain help on the :doc:`fg <cmds/fg>` builtin, either type ``fg -h`` or ``help fg``.
@@ -40,7 +40,7 @@ Tab completion is a time saving feature of any modern shell. When you type :kbd:
The pager can be navigated with the arrow keys, :kbd:`pageup` / :kbd:`pagedown`, :kbd:`tab` or :kbd:`shift-tab`. Pressing :kbd:`ctrl-s` (the ``pager-toggle-search`` binding - :kbd:`/` in vi mode) opens up a search menu that you can use to filter the list.
Fish provides some general purpose completions, like for commands, variable names, usernames or files.
fish provides some general purpose completions, like for commands, variable names, usernames or files.
It also provides a large number of program specific scripted completions. Most of these completions are simple options like the ``-l`` option for ``ls``, but a lot are more advanced. For example:
@@ -54,14 +54,14 @@ It also provides a large number of program specific scripted completions. Most o
You can also write your own completions or install some you got from someone else. For that, see :doc:`Writing your own completions <completions>`.
Completion scripts are loaded on demand, like :ref:`functions are <syntax-function-autoloading>`. The difference is the ``$fish_complete_path`` :ref:`list <variables-lists>` is used instead of ``$fish_function_path``. Typically you can drop new completions in ~/.config/fish/completions/name-of-command.fish and fish will find them automatically.
Completion scripts are loaded on demand, like :ref:`functions are <syntax-function-autoloading>`. The difference is the ``$fish_complete_path`` :ref:`list <variables-lists>` is used instead of ``$fish_function_path``. Typically you can drop new completions in ``~/.config/fish/completions/<name-of-command>.fish`` and fish will find them automatically.
.. _syntax-highlighting:
Syntax highlighting
-------------------
Fish interprets the command line as it is typed and uses syntax highlighting to provide feedback. The most important feedback is the detection of potential errors. By default, errors are marked red.
fish interprets the command line as it is typed and uses syntax highlighting to provide feedback. The most important feedback is the detection of potential errors. By default, errors are marked red.
Detected errors include:
@@ -72,7 +72,7 @@ Detected errors include:
To customize the syntax highlighting, you can set the environment variables listed in the :ref:`Variables for changing highlighting colors <variables-color>` section.
Fish also provides pre-made color themes you can pick with :doc:`fish_config <cmds/fish_config>`.
fish also provides pre-made color themes you can pick with :doc:`fish_config <cmds/fish_config>`.
Running just ``fish_config`` opens a browser interface, or you can use ``fish_config theme`` from fish::
# disable nearly all coloring
@@ -243,7 +243,7 @@ For :ref:`vi mode <vi-mode>`, the output of :doc:`fish_mode_prompt <cmds/fish_mo
If :envvar:`fish_transient_prompt` is set to 1, fish will redraw the prompt with a ``--final-rendering`` argument before running a commandline, allowing you to change it before pushing it to the scrollback.
Fish ships with a few prompts which you can see with :doc:`fish_config <cmds/fish_config>`. If you run just ``fish_config`` it will open a web interface [#]_ where you'll be shown the prompts and can pick which one you want. ``fish_config prompt show`` will show you the prompts right in your terminal.
fish ships with a few prompts which you can see with :doc:`fish_config <cmds/fish_config>`. If you run just ``fish_config`` it will open a web interface [#]_ where you'll be shown the prompts and can pick which one you want. ``fish_config prompt show`` will show you the prompts right in your terminal.
For example ``fish_config prompt choose disco`` will temporarily select the "disco" prompt. If you like it and decide to keep it, run ``fish_config prompt save``.
@@ -274,7 +274,7 @@ Programmable title
------------------
Most terminals allow setting the text displayed in the titlebar of the terminal window.
Fish does this by running the :doc:`fish_title <cmds/fish_title>` function.
fish does this by running the :doc:`fish_title <cmds/fish_title>` function.
It is executed before and after a command and the output is used as a titlebar message.
The :doc:`status current-command <cmds/status>` builtin will always return the name of the job to be put into the foreground (or ``fish`` if control is returning to the shell) when the :doc:`fish_title <cmds/fish_title>` function is called. The first argument will contain the most recently executed foreground command as a string.
@@ -474,7 +474,7 @@ The ``fish_vi_cursor`` function will be used to change the cursor's shape depend
Additionally, ``blink`` can be added after each of the cursor shape parameters to set a blinking cursor in the specified shape.
Fish knows the shapes "block", "line" and "underscore", other values will be ignored.
fish knows the shapes "block", "line" and "underscore", other values will be ignored.
If the cursor shape does not appear to be changing after setting the above variables, it's likely your terminal emulator does not support the capabilities necessary to do this.
@@ -593,7 +593,7 @@ If you change your mind on a binding and want to go back to fish's default, you
bind --erase ctrl-c
Fish remembers its preset bindings and so it will take effect again. This saves you from having to remember what it was before and add it again yourself.
fish remembers its preset bindings and so it will take effect again. This saves you from having to remember what it was before and add it again yourself.
If you use :ref:`vi bindings <vi-mode>`, note that ``bind`` will by default bind keys in :ref:`command mode <vi-mode-command>`. To bind something in :ref:`insert mode <vi-mode-insert>`::
@@ -638,7 +638,7 @@ Similarly, to disambiguate *other* keypresses where you've bound a subsequence a
Copy and paste (Kill Ring)
^^^^^^^^^^^^^^^^^^^^^^^^^^
Fish uses an Emacs-style kill ring for copy and paste functionality. For example, use :kbd:`ctrl-k` (`kill-line`) to cut from the current cursor position to the end of the line. The string that is cut (a.k.a. killed in emacs-ese) is inserted into a list of kills, called the kill ring. To paste the latest value from the kill ring (emacs calls this "yanking") use :kbd:`ctrl-y` (the ``yank`` input function). After pasting, use :kbd:`alt-y` (``yank-pop``) to rotate to the previous kill.
fish uses an Emacs-style kill ring for copy and paste functionality. For example, use :kbd:`ctrl-k` (`kill-line`) to cut from the current cursor position to the end of the line. The string that is cut (a.k.a. killed in emacs-ese) is inserted into a list of kills, called the kill ring. To paste the latest value from the kill ring (emacs calls this "yanking") use :kbd:`ctrl-y` (the ``yank`` input function). After pasting, use :kbd:`alt-y` (``yank-pop``) to rotate to the previous kill.
Copy and paste from outside are also supported, both via the :kbd:`ctrl-x` / :kbd:`ctrl-v` bindings (the ``fish_clipboard_copy`` and ``fish_clipboard_paste`` functions [#]_) and via the terminal's paste function, for which fish enables "Bracketed Paste Mode", so it can tell a paste from manually entered text.
In addition, when pasting inside single quotes, pasted single quotes and backslashes are automatically escaped so that the result can be used as a single token by closing the quote after.
@@ -700,7 +700,7 @@ If the commandline reads ``cd m``, place the cursor over the ``m`` character and
Private mode
-------------
Fish has a private mode, in which command history will not be written to the history file on disk. To enable it, either set ``$fish_private_mode`` to a non-empty value, or launch with ``fish --private`` (or ``fish -P`` for short).
fish has a private mode, in which command history will not be written to the history file on disk. To enable it, either set ``$fish_private_mode`` to a non-empty value, or launch with ``fish --private`` (or ``fish -P`` for short).
If you launch fish with ``-P``, it both hides old history and prevents writing history to disk. This is useful to avoid leaking personal information (e.g. for screencasts) or when dealing with sensitive information.
@@ -718,7 +718,7 @@ The current working directory can be displayed with the :doc:`pwd <cmds/pwd>` co
Directory history
^^^^^^^^^^^^^^^^^
Fish automatically keeps a trail of the recent visited directories with :doc:`cd <cmds/cd>` by storing this history in the ``dirprev`` and ``dirnext`` variables.
fish automatically keeps a trail of the recent visited directories with :doc:`cd <cmds/cd>` by storing this history in the ``dirprev`` and ``dirnext`` variables.
Several commands are provided to interact with this directory history:

View File

@@ -70,7 +70,7 @@ Sometimes you want to give a command an argument that contains characters specia
to remove a file called ``my file.txt`` instead of trying to remove two files, ``my`` and ``file.txt``.
Fish understands two kinds of quotes: Single (``'``) and double (``"``), and both work slightly differently.
fish understands two kinds of quotes: Single (``'``) and double (``"``), and both work slightly differently.
Between single quotes, fish performs no expansions. Between double quotes, fish only performs :ref:`variable expansion <expand-variable>` and :ref:`command substitution <expand-command-substitution>` in the ``$(command)``. No other kind of expansion (including :ref:`brace expansion <expand-brace>` or parameter expansion) is performed, and escape sequences (for example, ``\n``) are ignored. Within quotes, whitespace is not used to separate arguments, allowing quoted arguments to contain spaces.
@@ -315,7 +315,7 @@ Calling this as ``ll /tmp/`` will end up running ``ls -l /tmp/``, which will lis
This is a kind of function known as an :ref:`alias <syntax-aliases>`.
Fish's prompt is also defined in a function, called :doc:`fish_prompt <cmds/fish_prompt>`. It is run when the prompt is about to be displayed and its output forms the prompt::
fish's prompt is also defined in a function, called :doc:`fish_prompt <cmds/fish_prompt>`. It is run when the prompt is about to be displayed and its output forms the prompt::
function fish_prompt
# A simple prompt. Displays the current directory
@@ -413,7 +413,7 @@ Comments can also appear after a line like so::
Conditions
----------
Fish has some builtins that let you execute commands only if a specific criterion is met: :doc:`if <cmds/if>`, :doc:`switch <cmds/switch>`, :doc:`and <cmds/and>` and :doc:`or <cmds/or>`, and also the familiar :ref:`&&/|| <syntax-combiners>` syntax.
fish has some builtins that let you execute commands only if a specific criterion is met: :doc:`if <cmds/if>`, :doc:`switch <cmds/switch>`, :doc:`and <cmds/and>` and :doc:`or <cmds/or>`, and also the familiar :ref:`&&/|| <syntax-combiners>` syntax.
.. _syntax-if:
@@ -422,7 +422,7 @@ The ``if`` statement
The :doc:`if <cmds/if>` statement runs a block of commands if the condition was true.
Like other shells, but unlike typical programming languages you might know, the condition here is a *command*. Fish runs it, and if it returns a true :ref:`exit status <variables-status>` (that's 0), the if-block is run. For example::
Like other shells, but unlike typical programming languages you might know, the condition here is a *command*. fish runs it, and if it returns a true :ref:`exit status <variables-status>` (that's 0), the if-block is run. For example::
if test -e /etc/os-release
cat /etc/os-release
@@ -692,7 +692,7 @@ Quoting variables
Variable expansion also happens in double quoted strings. Inside double quotes (``"these"``), variables will always expand to exactly one argument. If they are empty or undefined, it will result in an empty string. If they have one element, they'll expand to that element. If they have more than that, the elements will be joined with spaces, unless the variable is a :ref:`path variable <variables-path>` - in that case it will use a colon (``:``) instead [#]_.
Fish variables are all :ref:`lists <variables-lists>`, and they are split into elements when they are *set* - that means it is important to decide whether to use quotes or not with :doc:`set <cmds/set>`::
fish variables are all :ref:`lists <variables-lists>`, and they are split into elements when they are *set* - that means it is important to decide whether to use quotes or not with :doc:`set <cmds/set>`::
set foo 1 2 3 # a variable with three elements
rm $foo # runs the equivalent of `rm 1 2 3` - trying to delete three files: 1, 2 and 3.
@@ -830,7 +830,7 @@ When using double quotes, the command output is not split up by lines, but trail
If the output is piped to :doc:`string split or string split0 <cmds/string-split>` as the last step, those splits are used as they appear instead of splitting lines.
Fish also allows spelling command substitutions without the dollar, like ``echo (pwd)``. This variant will not be expanded in double-quotes (``echo "(pwd)"`` will print ``(pwd)``).
fish also allows spelling command substitutions without the dollar, like ``echo (pwd)``. This variant will not be expanded in double-quotes (``echo "(pwd)"`` will print ``(pwd)``).
The exit status of the last run command substitution is available in the :ref:`status <variables-status>` variable if the substitution happens in the context of a :doc:`set <cmds/set>` command (so ``if set -l (something)`` checks if ``something`` returned true).
@@ -863,7 +863,7 @@ but if you need multiple or the command doesn't read from standard input, "proce
This creates a temporary file, stores the output of the command in that file and prints the filename, so it is given to the outer command.
Fish has a default limit of 1 GiB on the data it will read in a command substitution. If that limit is reached the command (all of it, not just the command substitution - the outer command won't be executed at all) fails and ``$status`` is set to 122. This is so command substitutions can't cause the system to go out of memory, because typically your operating system has a much lower limit, so reading more than that would be useless and harmful. This limit can be adjusted with the ``fish_read_limit`` variable (`0` meaning no limit). This limit also affects the :doc:`read <cmds/read>` command.
fish has a default limit of 1 GiB on the data it will read in a command substitution. If that limit is reached the command (all of it, not just the command substitution - the outer command won't be executed at all) fails and ``$status`` is set to 122. This is so command substitutions can't cause the system to go out of memory, because typically your operating system has a much lower limit, so reading more than that would be useless and harmful. This limit can be adjusted with the ``fish_read_limit`` variable (`0` meaning no limit). This limit also affects the :doc:`read <cmds/read>` command.
.. [#] One exception: Setting ``$IFS`` to empty will disable line splitting. This is deprecated, use :doc:`string split <cmds/string-split>` instead.
@@ -922,7 +922,7 @@ The very first character of a command token is never interpreted as expanding br
Combining lists
^^^^^^^^^^^^^^^
Fish expands lists like :ref:`brace expansions <expand-brace>`::
fish expands lists like :ref:`brace expansions <expand-brace>`::
>_ set -l foo x y z
>_ echo 1$foo
@@ -1348,7 +1348,7 @@ Note: Exporting is not a :ref:`scope <variables-scope>`, but an additional state
Lists
^^^^^
Fish can store a list (or an "array" if you wish) of multiple strings inside of a variable::
fish can store a list (or an "array" if you wish) of multiple strings inside of a variable::
> set mylist first second third
> printf '%s\n' $mylist # prints each element on its own line
@@ -1415,7 +1415,7 @@ When a list is exported as an environment variable, it is either space or colon
smurf=blue small
smurf_PATH=forest:mushroom
Fish automatically creates lists from all environment variables whose name ends in ``PATH`` (like :envvar:`PATH`, :envvar:`CDPATH` or :envvar:`MANPATH`), by splitting them on colons. Other variables are not automatically split.
fish automatically creates lists from all environment variables whose name ends in ``PATH`` (like :envvar:`PATH`, :envvar:`CDPATH` or :envvar:`MANPATH`), by splitting them on colons. Other variables are not automatically split.
Lists can be inspected with the :doc:`count <cmds/count>` or the :doc:`contains <cmds/contains>` commands::
@@ -1662,7 +1662,7 @@ You can change the settings of fish by changing the values of certain variables.
your preferred web browser. If this variable is set, fish will use the specified browser instead of the system default browser to display the fish documentation.
Fish also provides additional information through the values of certain environment variables. Most of these variables are read-only and their value can't be changed with ``set``.
fish also provides additional information through the values of certain environment variables. Most of these variables are read-only and their value can't be changed with ``set``.
.. envvar:: _
@@ -1739,7 +1739,7 @@ Fish also provides additional information through the values of certain environm
.. ENVVAR:: SHLVL
the level of nesting of shells. Fish increments this in interactive shells, otherwise it only passes it along.
the level of nesting of shells. fish increments this in interactive shells, otherwise it only passes it along.
.. envvar:: status
@@ -1763,7 +1763,7 @@ Fish also provides additional information through the values of certain environm
As a convention, an uppercase name is usually used for exported variables, while lowercase variables are not exported. (``CMD_DURATION`` is an exception for historical reasons). This rule is not enforced by fish, but it is good coding practice to use casing to distinguish between exported and unexported variables.
Fish also uses some variables internally, their name usually starting with ``__fish``. These are internal and should not typically be modified directly.
fish also uses some variables internally, their name usually starting with ``__fish``. These are internal and should not typically be modified directly.
.. _variables-status:
@@ -1772,7 +1772,7 @@ The status variable
Whenever a process exits, an exit status is returned to the program that started it (usually the shell). This exit status is an integer number, which tells the calling application how the execution of the command went. In general, a zero exit status means that the command executed without problem, but a non-zero exit status means there was some form of problem.
Fish stores the exit status of the last process in the last job to exit in the ``status`` variable.
fish stores the exit status of the last process in the last job to exit in the ``status`` variable.
If fish encounters a problem while executing a command, the status variable may also be set to a specific value:
@@ -1882,7 +1882,7 @@ In UNIX, these are made up of several categories. The categories used by fish ar
Builtin commands
----------------
Fish includes a number of commands in the shell directly. We call these "builtins". These include:
fish includes a number of commands in the shell directly. We call these "builtins". These include:
- Builtins that manipulate the shell state - :doc:`cd <cmds/cd>` changes directory, :doc:`set <cmds/set>` sets variables
- Builtins for dealing with data, like :doc:`string <cmds/string>` for strings and :doc:`math <cmds/math>` for numbers, :doc:`count <cmds/count>` for counting lines or arguments, :doc:`path <cmds/path>` for dealing with path
@@ -2101,7 +2101,7 @@ To specify a signal handler for the WINCH signal, write::
echo Got WINCH signal!
end
Fish already has the following named events for the ``--on-event`` switch:
fish already has the following named events for the ``--on-event`` switch:
- ``fish_prompt`` is emitted whenever a new fish prompt is about to be displayed.
@@ -2144,7 +2144,7 @@ For more information on how to define new event handlers, see the documentation
Debugging fish scripts
----------------------
Fish includes basic built-in debugging facilities that allow you to stop execution of a script at an arbitrary point. When this happens you are presented with an interactive prompt where you can execute any fish command to inspect or change state (there are no debug commands as such). For example, you can check or change the value of any variables using :doc:`printf <cmds/printf>` and :doc:`set <cmds/set>`. As another example, you can run :doc:`status print-stack-trace <cmds/status>` to see how the current breakpoint was reached. To resume normal execution of the script, type :doc:`exit <cmds/exit>` or :kbd:`ctrl-d`.
fish includes basic built-in debugging facilities that allow you to stop execution of a script at an arbitrary point. When this happens you are presented with an interactive prompt where you can execute any fish command to inspect or change state (there are no debug commands as such). For example, you can check or change the value of any variables using :doc:`printf <cmds/printf>` and :doc:`set <cmds/set>`. As another example, you can run :doc:`status print-stack-trace <cmds/status>` to see how the current breakpoint was reached. To resume normal execution of the script, type :doc:`exit <cmds/exit>` or :kbd:`ctrl-d`.
To start a debug session insert the :doc:`builtin command <cmds/breakpoint>` ``breakpoint`` at the point in a function or script where you wish to gain control, then run the function or script. Also, the default action of the ``TRAP`` signal is to call this builtin, meaning a running script can be actively debugged by sending it the ``TRAP`` signal (``kill -s TRAP <PID>``). There is limited support for interactively setting or modifying breakpoints from this debug prompt: it is possible to insert new breakpoints in (or remove old ones from) other functions by using the ``funced`` function to edit the definition of a function, but it is not possible to add or remove a breakpoint from the function/script currently loaded and being executed.

View File

@@ -7,7 +7,7 @@ Writing your own prompt
This document uses formatting to show what a prompt would look like. If you are viewing this in the man page,
you probably want to switch to looking at the html version instead. Run ``help custom-prompt`` to view it in a web browser.
Fish ships a number of prompts that you can view with the :doc:`fish_config <cmds/fish_config>` command, and many users have shared their prompts online.
fish ships a number of prompts that you can view with the :doc:`fish_config <cmds/fish_config>` command, and many users have shared their prompts online.
However, you can also write your own, or adjust an existing prompt. This is a good way to get used to fish's :doc:`scripting language <language>`.
@@ -207,7 +207,7 @@ Where to go from here?
We have now built a simple but working and usable prompt, but of course more can be done.
- Fish offers more helper functions:
- fish offers more helper functions:
- ``prompt_login`` to describe the user/hostname/container or ``prompt_hostname`` to describe just the host
- ``fish_is_root_user`` to help with changing the symbol for root.
- ``fish_vcs_prompt`` to show version control information (or ``fish_git_prompt`` / ``fish_hg_prompt`` / ``fish_svn_prompt`` to limit it to specific systems)

View File

@@ -8,7 +8,6 @@ while others enable optional features and may be ignored by the terminal.
The terminal must be able to parse Control Sequence Introducer (CSI) commands, Operating System Commands (OSC) and :ref:`optionally <term-compat-dcs-gnu-screen>` Device Control Strings (DCS).
These are defined by ECMA-48.
If a valid CSI, OSC or DCS sequence does not represent a command implemented by the terminal, the terminal must ignore it.
For historical reasons, OSC sequences may be terminated with ``\x07`` instead of ``\e\\``.
Control sequences are denoted in a fish-like syntax.
Special characters other than ``\`` are not escaped.
@@ -237,7 +236,7 @@ Optional Commands
``\e]0; Pt \e\\``
- ts
- Set terminal window title (OSC 0). Used in :doc:`fish_title <cmds/fish_title>`.
* - ``\e]2; Pt \e\\``
* - ``\e]1; Pt \e\\``
- ts
- Set terminal tab title (OSC 1). Used in :doc:`fish_tab_title <cmds/fish_tab_title>`.
* - ``\e]7;file:// Pt / Pt \e\\``
@@ -333,7 +332,7 @@ Unicode Codepoints
By default, fish outputs the following non-ASCII characters::
× ► ¶ ⏎ • ● … μ “ ” ← → ↑ ↓
× ► ¶ ⏎ • ● … μ “ ”
as well as control pictures (U+2400 through U+241F),
and locale-specific ones in :ref:`translated strings <variables-locale>`.
and locale-specific ones in :ref:`translated messages <variables-locale>`.

View File

@@ -6,7 +6,7 @@ Tutorial
Why fish?
---------
Fish is a fully-equipped command line shell (like bash or zsh) that is smart and user-friendly. Fish supports powerful features like syntax highlighting, autosuggestions, and tab completions that just work, with nothing to learn or configure.
fish is a fully-equipped command line shell (like bash or zsh) that is smart and user-friendly. fish supports powerful features like syntax highlighting, autosuggestions, and tab completions that just work, with nothing to learn or configure.
If you want to make your command line more productive, more useful, and more fun, without learning a bunch of arcane syntax and configuration options, then fish might be just what you're looking for!
@@ -46,7 +46,7 @@ For a comprehensive description of fish's scripting language, see :doc:`The Fish
Running Commands
----------------
Fish runs commands like other shells: you type a command, followed by its arguments. Spaces are separators::
fish runs commands like other shells: you type a command, followed by its arguments. Spaces are separators::
> echo hello world
hello world
@@ -332,7 +332,7 @@ For more on combining lists with strings (or even other lists), see :ref:`cartes
Wildcards
---------
Fish supports the familiar wildcard ``*``. To list all JPEG files::
fish supports the familiar wildcard ``*``. To list all JPEG files::
> ls *.jpg
lena.jpg
@@ -655,7 +655,7 @@ $PATH
``$PATH`` is an environment variable containing the directories that fish searches for commands. Unlike other shells, $PATH is a :ref:`list <tut-lists>`, not a colon-delimited string.
Fish takes care to set ``$PATH`` to a default, but typically it is just inherited from fish's parent process and is set to a value that makes sense for the system - see :ref:`Exports <tut-exports>`.
fish takes care to set ``$PATH`` to a default, but typically it is just inherited from fish's parent process and is set to a value that makes sense for the system - see :ref:`Exports <tut-exports>`.
To prepend /usr/local/bin and /usr/sbin to ``$PATH``, you can write::
@@ -689,7 +689,7 @@ Or you can modify $fish_user_paths yourself, but you should be careful *not* to
Startup (Where's .bashrc?)
--------------------------
Fish starts by executing commands in ``~/.config/fish/config.fish``. You can create it if it does not exist.
fish starts by executing commands in ``~/.config/fish/config.fish``. You can create it if it does not exist.
It is possible to directly create functions and variables in ``config.fish`` file, using the commands shown above. For example:
@@ -738,7 +738,7 @@ See the documentation for :doc:`funced <cmds/funced>` and :doc:`funcsave <cmds/f
Universal Variables
-------------------
A universal variable is a variable whose value is shared across all instances of fish, now and in the future even after a reboot. You can make a variable universal with ``set -U``::
A universal variable is a variable whose value is shared across all instances of fish, now and in the future - even after a reboot. You can make a variable universal with ``set -U``::
> set -U EDITOR vim

View File

@@ -1,50 +0,0 @@
# Version set by updatecli.d/docker.yml
FROM alpine:3.23
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++ \
gettext-dev \
git \
libintl \
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 \
--disabled-password \
--gecos "" \
--home "/home/fishuser" \
--ingroup fishuser \
--uid 1000 \
fishuser
RUN mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
USER fishuser
WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -1,5 +1,5 @@
# Version set by updatecli.d/docker.yml
FROM ubuntu:24.04
FROM ubuntu:26.04
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8

View File

@@ -1,45 +0,0 @@
# Version set by updatecli.d/docker.yml
FROM ubuntu:22.04
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
build-essential \
ca-certificates \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
sudo \
tmux \
&& locale-gen en_US.UTF-8 \
&& apt-get clean
RUN groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
&& adduser fishuser sudo \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
USER fishuser
WORKDIR /home/fishuser
RUN curl --proto '=https' --tlsv1.2 -fsS https://sh.rustup.rs > /tmp/rustup.sh \
&& sh /tmp/rustup.sh -y --no-modify-path
ENV PATH=/home/fishuser/.cargo/bin:$PATH
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -17,6 +17,9 @@ BuildRequires: /usr/bin/sphinx-build
# OBS: add eg "FileProvides: /usr/bin/sphinx-build python3-sphinx python3-Sphinx" to project config
BuildRequires: /usr/bin/man
# OBS: add eg "FileProvides: /usr/bin/man man-db man" to project config
# pkg-config is needed for the pcre2 crate to find the pcre2 system librar
BuildRequires: /usr/bin/pkg-config
# OBS: add eg "FileProvides: /usr/bin/pkg-config pkgconf-pkg-config pkg-config" to project config
BuildRequires: cmake >= 3.15
# for tests

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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