Compare commits

..

210 Commits

Author SHA1 Message Date
Johannes Altmanninger
60ab561be1 uv.lock: add back dependency cooldown timestamp
We rely on this in build_tools/update-dependencies.sh and
.github/actions/install-sphinx/action.yml. Fixes 3968025421 (Don't
try to download vermin during test execution, 2026-06-17).
2026-06-18 16:00:21 +08:00
Tristan Partin
4e8d2c706d fish_git_prompt: rename single-letter variables for clarity
Rename $r, $b, $c, $p, $f, and $rbc to $operation, $branch,
$bare_prefix, $upstream, $flags, and $branch_state respectively. The
short names were not very descriptive, and made it quite a bit harder to
understand what the code was doing.

Closes #12803
2026-06-18 15:15:29 +08:00
Michael Morgan
0066defac8 completions/cargo: remove nonexistent subcommand complete
This was never added to cargo, and the PR was closed without merging.
See <https://github.com/rust-lang/cargo/pull/9288>.

cargo is in the process of stabilizing a new completions feature:
`CARGO_COMPLETE=fish cargo +nightly`.

Closes #12800
2026-06-18 15:15:09 +08:00
Johannes Altmanninger
d1042ef032 wcsringutil trim(): remove allocation 2026-06-18 14:52:22 +08:00
cyphercodes
7012f8bea3 Fix nested brace space trimming
Delay restoring protected brace spaces until recursive brace
expansion is complete, so nested brace items can strip boundary
spaces consistently.

Fixes #12794.

Closes #12806
Closes #12813
2026-06-18 14:30:21 +08:00
Tunglies
6dc7d53439 lint(clippy): redundant_clone
Closes #12812
2026-06-18 14:09:43 +08:00
Daniel Rainer
0d7d06f357 ci: rename step to check
Match the name of the xtask it runs, which does more than just run
tests.

Closes #12818
2026-06-18 14:09:43 +08:00
Daniel Rainer
e99e60ec4d ci: remove redundant build step
`cargo xtask check` already takes care of building, so there is no point
in having a separate build step, especially because the build options
used by `cargo xtask check` will trigger a rebuild of most of our code
to handle extraction for localization.

Part of #12818
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
c8d7476576 history: don't ignore re-added history items after vacuum
As root-caused in
https://github.com/fish-shell/fish-shell/issues/10300#issuecomment-4674848354,
we sometimes temporarily forget about history items:

Steps:

	1. start fish1
	2. fish1: run "echo remember me"
	3. start fish2
	4. fish1: run "echo something else"
	5. fish1: run "echo remember me"
	6. fish1: run up to VACUUM_FREQUENCY commands, until vacuum happens
	   (can watch ~/.config/fish/${fish_history:-fish}_history)
	7. fish2: run "echo reload"

Now fish2 can't recall "echo remember me" anymore.

This is because when re-running that command, fish1 updates the
command's history entry's "when" timestamp to something younger
than fish2.  When fish2 reloads the history, it ignores history
entries younger than itself (except for the entries created by itself).

Fix this by introducing a second timestamp called "added_when",
the immutable creation time.  Use this for determining the cutoff,
fixing the above scenario.

Keep the "when" key for forward compatibility.  Keep its semantics
(update it whenever we re-run a command; used for sorting); if we'd
instead let "when" be the creation time, then old fish will sort
wrongly commands that are run again in concurrent new fish. (If new
fish vacuums again, it will correct the ordering automatically.)

Alternatively, we could replace "added_when" with another
monotonically increasing number, maybe a integer sequence, with the
per-history next number stored on disk.  We only use it to compare
against the boundary timestamp in "offset_of_next_item_fish_2_0()"
and "rewrite_to_temporary_file()".  I haven't explored this yet because
"SystemTime" is easy and already used for "last added time".

Assisted-by: Theo Beers
Assisted-by: Daniel Rainer

Closes #10300
Closes #12817
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
784c53eb32 Remove attempt to recover history files corrupted by fish 1.x
Simplifies the following commit.
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
ed95a89e5d history: remove some allocations when merging valid paths 2026-06-18 14:09:43 +08:00
Johannes Altmanninger
c3853a9b18 history file: fix comment in offset decoding
This comment is incorrect because we don't read the entire item,
especially if no cutoff timestamp is given.
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
2778ac1987 history: extract test function and add test
RawHistoryFile::offsets() looks at every history item, even though it
could probably stop early once the cutoff timestamp has been reached.
An upcoming change makes the cutoff logic independent of the order
within the file. This will render such a naive optimization incorrect.
Add a test to document this.

Part of #12817
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
decf11c838 history file: inline a function 2026-06-18 14:09:43 +08:00
Daniel Rainer
8bcbc9497f l10n: port some more argparse messages to Fluent
Closes #11928
2026-06-18 14:09:43 +08:00
Daniel Rainer
ec129b94c7 l10n: port argparse message from gettext to Fluent
Make use of the new `Error::from_fluent_message` function.

This message in particular was chosen to demonstrate Fluent's ability to
use variables in arbitrary order. The suggestion in
https://github.com/fish-shell/fish-shell/pull/11833#issuecomment-3343835252
to allow reordering variables was one of the reasons for switching to a
more flexible localization system. `zh_CN.ftl` has been modified
accordingly. The effect can be seen with

```fish
for LC_MESSAGES in fr zh_CN zh_TW
    argparse h-
end
```

Part of #11928
2026-06-18 14:09:43 +08:00
Daniel Rainer
cbc6b24138 l10n: allow creating errors from Fluent messages
Part of #11928
2026-06-18 14:09:43 +08:00
Daniel Rainer
dfa14b90d6 l10n: add system tests for Fluent
Part of #11928
2026-06-18 14:09:43 +08:00
Daniel Rainer
88acc9cfda l10n: add first Fluent message
This migrates the fish version info message from gettext to Fluent. It
can be used to see Fluent-based localization in action.

Because this commit adds new FTL files, these languages show up in the
Fluent language precedence, requiring an update to the corresponding
tests.

Part of #11928
2026-06-18 14:09:43 +08:00
Daniel Rainer
8816960b87 l10n: add Fluent localization system
Add an implementation allowing to use Fluent for localization in Rust.

Fluent is significantly more expressive than gettext. It uses message
IDs which, unlike in gettext, are not necessarily the default message
string. This allows for proper support of messages which happen to be
identical in English, but not in other languages. In gettext, this could
be solved to some extent with contexts, but our gettext implementation
does not support that. In Fluent, arguments to the message are specified
as key-value pairs, which gives translators more semantic information
and allows reordering the arguments in the translation, which is
impossible with gettext. Fluent also allows for more complex grammatical
features, such as different plural forms, grammatical cases, and
adapting phrases to the correct gender.

This commit only introduces the infrastructure for using Fluent instead
of gettext, with the goal of eventually replacing gettext for
localization in Rust. Making use of the new infrastructure is left to
follow-up commits.

To localize a message with Fluent, the new `localize!` macro should be
used. Its arguments are key-value pairs. The first pair must consist of
two string literals. The key is a Fluent message ID and the value the
corresponding definition of the message in English. It must be valid
Fluent syntax for a message definition. The remaining key-value pairs
are used for Fluent variables which appear in the message definition.
Each key must match the name of a variable present in the message
definition, and the value is some Rust value which can be displayed by
Fluent. The variable in the Fluent message definition will be replaced
by that value. Example syntax:

```rust
localize!(
    "fish-version" = "{ $package_name }, version { $version }",
    package_name = "fish",
    version = crate::BUILD_VERSION,
)
````

If a message should be available in more than one place, define a
function containing the `localize!` macro and call that function from
the different locations needing access to the message, instead of
putting multiple `localize!` macros with the same message ID into the
code. The `localize_fn!` macro can help with that.

By having the message definition in the Rust sources, we have all the
relevant information for showing English messages in Rust, without
needing to rely on external sources. However, the Fluent library expects
data for all languages in the FTL format, so we have tooling to
automatically generate `en.ftl` from the Rust sources. Having this file
is also useful for other Fluent tooling, as well as being able to track
modifications to messages, which we also have tooling for.

To add translations for a new language, translators can create a new FTL
file in the `localization/fluent` directory with the appropriate file
name. No additional setup is needed. Translations for an individual
message can be added by copying the message from `en.ftl` and adjusting
the definition for the respective language.

These FTL files are included via `rust-embed` and parsed on demand at
runtime, ensuring that only languages specified in the user's language
settings are considered, saving the effort or parsing unneeded files.
`en.ftl` is always parsed, as it implicitly is the last fallback
option, when no other language in the user's precedence list has a
translation for a message ID. We know that `en.ftl` contains a
translation for all message IDs we use because message IDs can only be
used by putting them into the `localize!` macro with a corresponding
English message definition, and `en.ftl` is auto-generated from these
definitions. This generation happens as a 2-step process: First, we
compile with the `fluent-extract` feature, with the
`FISH_FLUENT_EXTRACTION_DIR` environment variable set to point to an
empty directory. This will result in the `localize!` macro passing the
message ID, definition, and variable names specified in subsequent
key-value pairs, to a proc macro which performs some checks on the
provided data and writes the message definition in the FTL format to a
fresh file in `FISH_FLUENT_EXTRACTION_DIR`. This results in one file per
`localize!` macro invocation. In the second step, the data from all
these files is combined into a single file, `en.ftl`, which is
automatically formatted. Before overwriting the old `en.ftl`, checks are
performed to look for changes to message definitions. If any change is
detected, translations of the message might have to be updated.
Some changes, like removal of a message, can be handled automatically,
by deleting all translations. For changes which cannot be handled fully
automatically, the affected translations are marked with an annotation,
which causes our checks to fail while it is present. It is the
responsibility of the developer who changed the message definition to
handle updates to its translations. This can be done manually, by
editing the affected translations and removing the annotation, which
should be the preferred option when translations need to be modified and
the developer knows the language. Otherwise, there is automation in the
form of `cargo xtask fluent resolve-outdated`, which allows specifying
what should happen to some or all translations of a message, with the
available option being:
- delete the translation, used when the meaning of the English message
  changed so much that keeping the old translations would be misleading
- delete the annotation, used when changes to the English message have
  no impact on translations, e.g. for typo fixes
- change the annotation to indicate that translator review is desired,
  used when the meaning or wording of the English message changed
  slightly, but not enough to invalidate the translations

There are several tools available as subcommands of `cargo xtask
fluent`:
- `check`: Checks the FTL files, ensuring that they can be parsed
  without errors, that no duplicate IDs are specified, that they are
  formatted correctly, and that there are no extra IDs, i.e. IDs not
  present in `en.ftl`, which is expected to be complete. More rigorous
  checks could be added, such as checking whether the same set of
  variables are used for a certain ID in all languages. The complexity
  of Fluent's syntax makes this non-trivial, which is the reason it's
  not already implemented.
- `format`: Formats the specified FTL files (or all by default). Also
  has a mode suitable for editor integration to format files from the
  editor. Examples for setting that up in Vim are provided in the
  `CONTRIBUTING.rst` docs.
- `rename`: Renames IDs or associated variables across all FTL files.
- `resolve-outdated`: Described above.
- `show-missing`: Shows which IDs don't have a translation yet.
- `update`: Runs the generation pipeline for `en.ftl` and potential
  translation updates/annotations.

There is one additional tool, which is designed to help with porting
existing messages localized via gettext to the new Fluent
implementation. This is intentionally not added to fish, because it
is only useful for the transition. Once we have ported all messages to
Fluent we won't have a use for it anymore. If you are interested in
using it to port messages, it's the `po-convert` binary in the
`fluent-ftl-tools` package. The CLI is somewhat convoluted, but can be
simplified by wrapping it with a script which hard-codes the path to the
relevant PO and FTL file directories. Then, the remaining information
which needs to be specified is:
- a line number in a PO file to identify the message to be ported
- the new message ID
- the name of each variable, in the order the formatting specifiers
  appear in the gettext msgid.
Specifying the line number and invoking the wrapper script can be
partially automated by using a custom editor shortcut.
The tool will port the msgstr for each language which has one defined,
and always generate and entry for `en.po` based on the msgid.
The tool does not edit Rust code, but suggests a Rust code snippet on
stdout based on the specified message ID and variable names.

Some of our tooling relies on features of the `fluent` package which
are not exported by default, so we use a fork which changes that until
our PR for adding it upstream is accepted.

Part of #11928
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
3968025421 Don't try to download vermin during test execution
The "vermin" test fails when offline; fix that by adding "vermin"
to the virtulenv.
2026-06-18 14:09:43 +08:00
Johannes Altmanninger
1c7653cc70 Fix typo 2026-06-18 10:49:20 +08:00
Johannes Altmanninger
d50c12e142 Better-named and idiomatic return type for cmdsub parsing
Commit 2f6ed61833 (parse_util_cmdsubst_extent to return an exclusive
range, 2024-04-27) used the "Parentheses" naming in preparation for
raw quotes.  We still don't have them, so let's clean that up for now.
Also, reuse the Result and Option types.
2026-06-18 10:49:20 +08:00
Johannes Altmanninger
7f9e1a89d6 Unit-test completion inside brace expansion with separators
Remove an incorrect comment. Completion does work, in a limited set
of cases.
2026-06-18 10:49:20 +08:00
Johannes Altmanninger
28ef1b33ac Update changelog 2026-06-18 10:49:20 +08:00
Johannes Altmanninger
b6447722bf tempfile: warn when temp file/dir is dropped without use 2026-06-18 10:33:51 +08:00
visrosa
7ee171949f completions/emerge: update against portage 3.0.79
Add missing options, descriptions, y/n completions, and correct short
flags throughout. Drop --changelog (removed from portage). Fix several
wrong option names in stubs (--ignore-built-slot-operator-deps,
--rebuilt-binaries, --sync-submodule, etc.). Use -xa for mandatory
boolean args and -a for optional ones.

Closes #12810
2026-06-18 10:33:51 +08:00
Johannes Altmanninger
ae299a27e7 Disable OSC 133 integration for Konsole specifically
When a command fails, Konsole draws a fat red line on the left edge
of the command output. Sadly the line overlaps with command output.
Since I'm not sure how important the rest of Konsole's semantic
integration is, let's disable it.
https://github.com/fish-shell/fish-shell/issues/11409
https://bugs.kde.org/show_bug.cgi?id=503125
2026-06-18 10:33:51 +08:00
Peter Ammon
8768b458a6 Correct user/sys time printing
These were swapped.
2026-06-13 12:41:38 -07:00
Mark Otzen
452651f79a Remove extraneous dash in path basename description 2026-06-13 12:40:44 -07:00
Peter Ammon
077d58d0d8 Changelog fix for #12700 2026-06-11 22:48:53 -07:00
Peter Ammon
d13ab22a27 Cleanup and add tests for cd builtin
Add a test for the following behavior: if we try to `cd` to a directory,
and it fails, and so we call realpath and discover that our $PWD is stale,
and correct it and then try to `cd` again, and it STILL fails, then:

1. We report the error for the second (better) failure.
2. $PWD is not modified.
2026-06-11 22:48:52 -07:00
Peter Ammon
119bb872d8 Miscellaneous cleanup of the cd builtin
Add some missing comments and make some C++-isms more Rust-friendly.
No user-visible behavior changes expected.
2026-06-11 22:46:25 -07:00
Vishrut Sachan
411a43254b env: fix stale $PWD after mv $PWD elsewhere
Fixes #12700
2026-06-11 21:17:44 -07:00
Johannes Altmanninger
de0e519b22 test unreadable-config-paths: skip on windows
See 17ef326c8f.
2026-06-10 00:44:54 +09:00
Johannes Altmanninger
6d0cd3d9b5 Merge changes to config paths 2026-06-09 22:31:14 +09:00
Johannes Altmanninger
9303e64708 env_init: remove unused argument 2026-06-09 22:31:14 +09:00
Johannes Altmanninger
74ff7d2c6c Make internal path variables read-only
It does not make sense to change these.

If anything, we should make them computed electric variable, so they
change whenever one of $HOME, $XDG_CONFIG_HOME and friends change.

Make them electric variables, purely because that's our only
way of marking variables as read-only.  In future we can add a
EnvVarFlags::READ_ONLY instead.  We'll want this anyway for "set
--read-only".
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
654041895b Stop using universal __fish_initialized to detect first launch
As of 7640e95bd7 (Create user config file/directories only on
first startup again, 2025-12-29) we interpret absence of the
__fish_initialized variable as "this is the first run, so create
~/.config/fish/conf.d etc.".

As reported in #11226, the presence of this universal variable causes
friction to users who track ~/.config/fish/fish_variables in version
control.

Also, __fish_initialized is the only universal variable we create
by default.

Use another way to detect the first run: since we already create
~/.config/fish on every run, assume that a successful mkdir() means
we should also create the subdirectories.

This has false negatives (if the user already created the directory)
and false positives (if the user doesn't want ~/.config/fish to exist)
but at least the latter should not really matter because historically
we always created it, at least for ~/.config/fish/fish_variables.

Alternatively, we could create a file at ~/.cache/fish/first-run-done.
But let's not add more state unless there's a good reason.
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
511006833c config.fish template: add back indentation
Fixes 3e7c5ae399 (__fish_config_interactive: make config file
initialization independent of uvars, 2025-11-24).
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
a79ca0cb29 Create ~/.config/fish/{completions,conf.d,functions} with mode 700
Same as ~/config/fish and other directories we create.
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
041f601050 make_base_directory: remove dead optimization code
path is never empty. Remove the check. Simplify the remaining code.
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
59135087d7 make_base_directory: remove unused function path_get_config_remoteness
I think the remoteness check is handled by fish::fs::LockedFile
nowadays.
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
93f483467f make_base_directory: remove extra trailing slash from statfs argument
This was added in d1fd3d5825 (Detect at startup whether config and
data paths are remote, 2021-05-08); I don't understand why
this would be necessary. On my system, statfs("/tmp") and
statfs("/tmp/") both yields f_type=TMPFS_MAGIC.
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
a66a7b2b30 fs: correct path formatting in an error message
Convert to WString which uses our PUA escaping trick.
2026-06-09 22:31:14 +09:00
Johannes Altmanninger
45765f4e00 env_init: minor refactoring for initializating user path vars 2026-06-09 22:31:14 +09:00
Johannes Altmanninger
f525b8a7b6 Merge misc fixes 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
c075982f4a env: electric variables to hold their computation-function directly
In addition to the entry in ELECTRIC_VARIABLES, each computed electric
variable has a piece of code in "try_get_computed()".  Makes more
sense to store this directly in the electric variable itself, no?

use consts for the getter functions, so we can reuse the type.
This is a bit weird..
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
8585def79d Merge key decoding fixes 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
bb6774bef8 CHANGELOG improvements 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
66ac8d3e11 electric vars: no general-purpose $history var on background threads
try_get_computed() returns None if the variable is not computed.

That's the case for "$history" on (future) background threads.

This is a weird edge case, because we don't ever return None for any
other electric variable.  This will become especially apparent when
an upcoming commit adds the computing-function to ElectricVar.

Given that the main thread already can't use "$history" from being
used as general purpose variable, there seems to be little harm in
banning it for all of them.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
85908c3950 input decoding: rewrite recursive escape sequence parsing
parse_escape_sequence() can self-recurse once, to parse sequences of
the form "\e\e...", where we attempt to interpret the first byte as
either legacy alt (if the "\e..." suffix decodes to a key), or legacy
escape (if it doesn't).

On "\e\e]11;rgb:ffff/ffff/ffff\e\\", i.e. with the inner sequence
being an OSC which doesn't produce a key event, we (tragically)
stop parsing the sequence and assume legacy escape.

Rewrite the logic to keep parsing whole escape sequences whenever
possible.  Also, don't drop any leading \e even if we later error.
Also, make sure each key event gets the correct "originating sequence".

This makes

	tmux new-session fish -C '
		bind alt-b "echo alt-b"
		bind escape "echo escape"
		bind alt-escape "echo alt-escape"
		sleep 2
	'

output the following after quickly pressing "escape" five times
followed by "b" one time:

	escape
	alt-escape
	escape
	alt-b

which is weird but fine because "alt-escape" is rarely used.

If this proves to be a problem, we could disable translation of "\e\e"
to "alt-escape"?

Closes #12379
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
24109b3fe7 CHANGELOG: fix rst formatting 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
353dc0526f env: move electric variables to impl module
Electric variables are largely an implementation detail, and a following
commit wants to add a circular dependency between the electric var
impl and environment_impl.  Specifically, "ElectricVar" will know
about "EnvScopedImpl". Move both impls into a common impl module,
to make this dependency less surprising.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
19535d9933 input decoding: minimize logic 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
a98000cb36 Add all terminal commands to reader log 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
3b29d62daa env: reformat electric var table 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
53edd49213 input decoding: sequence parsing to return query events too
A following commit wants to make "on_byte_read()" potentially return
multiple CharEvent values, in case there is a legacy escape.

Today "parse_csi()" and friends simply insert into the input queue.
This makes it hard to get the ordering right when we return multiple
events.

Change the parsing functions to return their result rather than
queuing directly.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
b541931b6e docs: replace mention of /usr/share/fish/completions
This directory is no longer since 0fafff2c89 (CMake: stop installing
embedded files, 2026-05-08). Instead of telling users to see "status
list-files", refer them to the source directly.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
974aa160f3 fish_update_completions: pass explicit directory argument
Gets rid of another use of XDG_CACHE_HOME; this gets us closer to
a single source of truth. Not super important right now, but will
be helpful if we allow users to override XDG_CACHE_HOME in future.
I guess we could also remove it from create_manpage_completions.py,
that's unnecessary breakage but probably fine since this is not
public-facing.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
0c3283cbed input decoding: make read_sequence_byte overridable
A following commit wants to inject mock input bytes in unit tests.
Allow this in a quick-and-dirty way by making the input-byte-yielding an
overridable trait method again. Unfortunately this needs to be in the
shared trait; we can't specialize a private trait since specialization
is not supported in stable Rust.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
070aa01472 docs fish_delta: reword 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
ac27876252 share: reuse __fish_make_cache_dir
Our apropos wrapper uses "getconf DARWIN_USER_CACHE_DIR"; we should
either use that everywhere or nowhere.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
943300c572 input decoding: fix parsing of CSI/SS3 sequences prefixed with legacy alt
Commit af137e5e96 (scrollback-push to query for indn/cuu via XTGETTCAP,
2025-01-05) extracted a bool variable

	recursive_invocation = buffer.len() == 2

It replaced another instance of "buffer.len() == 2" with that variable,
but that's wrong because buffer has grown since then (so it's either
2 or 3).  Flip the bool to fix this.

While at it, tighten an assertion.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
52fabc3e31 wildcard: make symlink check easier to read 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
4dc15113d8 Don't set $__fish_config_dir to empty if inaccessible
Since commit ea998b03f2 (First stab at directory transition. Test with
care..., 2006-10-19), "path_get_config()"
returns an empty string (now Option) if "~/.config/fish" is inaccessible.

This is okay in C++/Rust source code, because
it makes it very obvious to the user of "path_get_config()"
that they probably don't want to try to access these files.

However commit 3855c2217f (Remove scripted XDG_CONFIG_HOME uses,
2018-12-14) made us compute $__fish_config_dir like this:

	wcstring user_config_dir;
	path_get_config(user_config_dir);
	env_set_one(FISH_CONFIG_DIR, ENV_GLOBAL, user_config_dir);

with the (inadvertent?) change that we set $__fish_config_dir to the
empty string if the directory is inaccessible.

This situation causes us to stat weird paths like "/functions"
"/completions/", "/conf.d".  This is usually inconsequential because
these paths usually don't exist, but still, we shouldn't do this.

Fix this by setting the variables to the actual value we tried,
reverting the inadvertent behavior change of 3855c2217f.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
800d8a5728 input decoding: use byte literal
We use b"\x1b" elsewhere in this file so be consistent with that.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
86f92cb1a2 Remove crate-wide suppression of non_camel_case_types lint 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
2f236a71be env_init: inline variables
These are only used once, so reduce their scope.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
693169a633 input decoding: add log messages on invalid CSI format 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
59e68e23d5 Idiomatic type name for parsed options
Each builtin is inside its own module, so there is no need to repeat
the builtin name.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
b48b5025d5 path: inline default_vars_path_directory() 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
0033854858 Apply clippy::ref-option lint 2026-06-09 22:24:07 +09:00
Clément Bœsch
6fe05fa772 fix(git-completion): add missing completion for git rebase --show-current-patch and --quit
Closes #12805
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
a31768bf9e history: remove code for migrating from ~/.config/fish/fish_history
Commit 2971887bbd (Access fish_history from $XDG_DATA_HOME, 2015-09-16)
changed the history file path to ~/.local/share/fish/fish_history.

Migration is done by commit b13f0701a4 (Migrate fish_history from
config to data dir, 2015-09-18).

This should no longer be needed in practice. Remove it.

FWIW, we used a similar-length grace period (10 years) when dropping
support for history file format version 1 in 77aeb6a2a8 (Port
execution, 2023-10-08).
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
f2e2c622bb input/input_common: reorganize modules
While at it, rename "input mapping" to "binding", to be consistent
with builtin bind. The "input" bit is already implied by the module
hierarchy.

Also, merge definitions of "enum ReadlineCmd" and
"INPUT_FUNCTION_METADATA" using a macro.

Review with

	git show --color-moved=zebra --color-moved-ws=allow-indentation-change
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
b6e5e5196d path_emit_config_directory_messages: also print error about ~/.cache 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
a580d82407 Stop exposing internal module names when we can use re-exports
Types like Environment are sometimes imported using their FQN and
sometimes via their re-export in the parent module; it seems better
to have one canonical name. I think we usually use the re-exports
already, so use them always.

It't not as trivial to enforce this in child modules (such as "mod
tests" which) but that's not a big loss.

While at it, make some other consistency improvements to imports.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
6efb5c1a61 path_emit_config_directory_messages: remove code clone 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
31a348e70d wutil: remove unused function 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
e861c7f923 path_emit_config_directory_messages: fix condition for inaccessible $__fish_config_dir
Commit 77471c500e (path: use `std::io::Error` instead of raw ints,
2026-01-21) inadvertently prints the "Unable to locate config directory"
error when the *data* dir is inaccessible, rather than when the *config*
dir is inaccessible.

Fix that. While at it, rename variables to avoid this issue.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
c4aa726111 wutil: place re-exports before imports
Public items are more important to the reader, so put them first
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
616ff5c5e9 config.fish: lazy load job control functions
These don't really belong in config.fish.  Put them in function files,
like similar wrappers.  The redundancy is ugly but if it's a problem
we can use code generation to fix that.
2026-06-09 22:24:07 +09:00
Johannes Altmanninger
499297fb03 input: remove unused code 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
336df7793a Comment about handing over input data to reader on init 2026-06-09 22:24:07 +09:00
Johannes Altmanninger
a3239ef123 Run "gettext update" and "format" xtasks 2026-06-09 22:24:07 +09:00
Mahmoud Al-Qudsi
197f542750 completions/zpool: Improve zpool replace completion filtering 2026-06-08 12:29:44 -05:00
Mahmoud Al-Qudsi
0645ca231e completions/zpool: Fix broken regex and get rid of nested escape hell 2026-06-08 12:29:41 -05:00
Mahmoud Al-Qudsi
5e20c72232 completions/zpool: Complete diskid/DISK-XXXX and partitions under FreeBSD 2026-06-08 12:29:37 -05:00
Mahmoud Al-Qudsi
e37cad7fef Add disallowed-methods to clippy.toml
We should probably be using `disallowed-methods` more; this is one I had
locally before `clippy.toml` was included in the repo.
2026-06-08 12:28:48 -05:00
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
333 changed files with 13054 additions and 87712 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

@@ -11,13 +11,10 @@ runs:
set -x set -x
sudo pip install uv --break-system-packages sudo pip install uv --break-system-packages
command -v uv command -v uv
command -v uvx
# Check that pyproject.toml and the lock file are in sync. # Check that pyproject.toml and the lock file are in sync.
# TODO Use "uv" to install Python as well. uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
: 'Note that --no-managed-python below would be implied but be explicit' uv venv ~/.local --allow-existing
uv='env UV_PYTHON=python uv --no-managed-python' uv pip install --group=dev
$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
# Smoke test. # Smoke test.
python -c 'import sphinx; import sphinx_markdown_builder' 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 set -x
toolchain=$( toolchain=$(
case "$toolchain_channel" in case "$toolchain_channel" in
(stable) echo 1.95 ;; # updatecli.d/rust.yml (stable) echo 1.96 ;; # updatecli.d/rust.yml
(msrv) echo 1.85 ;; # updatecli.d/rust.yml (msrv) echo 1.85 ;; # updatecli.d/rust.yml
(*) (*)
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel" printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- name: Set label and milestone - name: Set label and milestone
id: set-label-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: with:
script: | script: |
const completionsLabel = 'completions'; 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

@@ -24,7 +24,7 @@ jobs:
mkdir /tmp/fish-built mkdir /tmp/fish-built
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh 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 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@v6 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
with: with:
name: linux-source-packages name: linux-source-packages
path: | path: |

View File

@@ -17,8 +17,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh - 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: with:
command: check licenses command: check licenses
arguments: --all-features --locked --exclude-dev arguments: --all-features --locked --exclude-dev
rust-version: 1.95 # updatecli.d/rust.yml rust-version: 1.96 # updatecli.d/rust.yml

View File

@@ -18,7 +18,7 @@ jobs:
pull-requests: write # for dessant/lock-threads to lock PRs pull-requests: write # for dessant/lock-threads to lock PRs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-inactive-days: '365' issue-inactive-days: '365'

View File

@@ -63,7 +63,7 @@ jobs:
sed -n 2p "$relnotes" | grep -q '^$' sed -n 2p "$relnotes" | grep -q '^$'
sed -i 1,2d "$relnotes" sed -i 1,2d "$relnotes"
- name: Upload tarball artifact - 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: with:
name: source-tarball name: source-tarball
path: | path: |
@@ -104,7 +104,7 @@ jobs:
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \ tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
-C target/$arch-unknown-linux-musl/release fish -C target/$arch-unknown-linux-musl/release fish
done 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: with:
name: Static builds for Linux name: Static builds for Linux
path: fish-${{ inputs.version }}-linux-*.tar.xz path: fish-${{ inputs.version }}-linux-*.tar.xz
@@ -123,14 +123,14 @@ jobs:
# Workaround for https://github.com/actions/checkout/issues/882 # Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }} ref: ${{ inputs.version }}
- name: Download all artifacts - 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: with:
merge-multiple: true merge-multiple: true
path: /tmp/artifacts path: /tmp/artifacts
- name: List artifacts - name: List artifacts
run: find /tmp/artifacts -type f run: find /tmp/artifacts -type f
- name: Create draft release - 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: with:
tag_name: ${{ inputs.version }} tag_name: ${{ inputs.version }}
name: fish ${{ inputs.version }} name: fish ${{ inputs.version }}

View File

@@ -151,7 +151,7 @@ jobs:
shell: msys2 {0} shell: msys2 {0}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh - 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: with:
update: true update: true
msystem: MSYS msystem: MSYS
@@ -166,9 +166,8 @@ jobs:
shell: cmd shell: cmd
run: | run: |
"%MSYS2_LOCATION%\usr\bin\dash" /usr/bin/rebaseall -p -v "%MSYS2_LOCATION%\usr\bin\dash" /usr/bin/rebaseall -p -v
- name: cargo build - name: check
run: | env:
cargo build FISH_CHECK_LINT: false
- name: tests
run: | run: |
cargo xtask check cargo xtask check

View File

@@ -1,3 +1,55 @@
fish ?.?.? (released ???)
=========================
Deprecations and removed features
---------------------------------
- The ``--command`` and ``--path`` options in :doc:`complete <cmds/complete>` no longer unescape their argument.
Interactive improvements
------------------------
- 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, fish 4.8 will avoid creating that file on upgrade (: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.
- :doc:`bind <cmds/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`).
- Path component movement (:kbd:`ctrl-w`) skips escaped characters.
Other improvements
------------------
- ``cd`` supports the ``-L`` and ``-P`` options, like other shells, to allow specifying whether symbolic links (symlinks) are resolved when changing directories (:issue:`7206`).
- ``cd`` with a relative path will now retry using the real current directory, if ``$PWD`` has been moved or deleted (:issue:`12700`).
- fish no longer creates universal variables by default; specifically the ``__fish_initialized`` variable is no longer created.
If you don't expect to need to downgrade to earlier versions, you can remove it with ``set --erase __fish_initialized``.
- Nested brace expansions now strip unquoted leading and trailing spaces from entries consistently (:issue:`12794`).
For distributors and developers
-------------------------------
- Messages defined in Rust source code may now be localized using `Fluent <https://projectfluent.org/>`__. To make this easy to work with, we have added Fluent tooling based on the new `fluent-ftl-tools <https://codeberg.org/danielrainer/fluent-ftl-tools>`__ Rust crate, see :ref:`Contributing Translations <localization>` (:issue:`11928`).
- 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 no longer exist.
These directories have been ignored since fish 4.2.
If another package installs fish scripts there, they should be corrected to install to
``extra_completionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_completions.d``),
``extra_functionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_functions.d``) or
``extra_confdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_functions.d``) instead.
See also the output of ``for var in completions functions conf; pkgconf fish --variable="$var"dir; end``.
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.
- (from 4.3.0) Pressing escape during command input would insert garbage text into the command line (:issue:`12379`).
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) fish 4.7.0 (released May 05, 2026)
================================== ==================================
@@ -1415,7 +1467,7 @@ Deprecations and removed features
Like ``stderr-nocaret``, they will eventually be made read-only. 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`) - 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`) - 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:: - 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::
@@ -1426,14 +1478,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. 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. ``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 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`). - 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 Scripting improvements
---------------------- ----------------------
- Quoted command substitution that directly follow a variable expansion (like ``echo "$var$(echo x)"``) no longer affect the variable expansion (:issue:`8849`). - 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`` can now handle underscores (``_``) as visual separators in numbers (:issue:`8611`, :issue:`8496`)::
math 5 + 2_123_252 math 5 + 2_123_252
@@ -1453,7 +1505,7 @@ Scripting improvements
Interactive 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`). - ``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`). - 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`). - ``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`).
@@ -1463,9 +1515,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`). - 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`). - 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`). - 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`). - 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`) - ``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`) - The status message when a job terminates should no longer be erased by a multiline prompt (:issue:`8817`)
@@ -1726,7 +1778,7 @@ Improved terminal support
Other improvements 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 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 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`). - The Debian & Ubuntu package linked from fishshell.com is now a single package, rather than split into ``fish`` and ``fish-common`` (:issue:`7845`).
@@ -3325,7 +3377,7 @@ Other fixes and improvements
variables (:issue:`4200`, :issue:`4341`), executing functions, globs (:issue:`4579`), variables (:issue:`4200`, :issue:`4341`), executing functions, globs (:issue:`4579`),
``string`` reading from standard input (:issue:`4610`), and slicing history ``string`` reading from standard input (:issue:`4610`), and slicing history
(in particular, ``$history[1]`` for the last executed command). (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 Unicode, and the width of some characters can be configured via the
``fish_ambiguous_width`` (:issue:`5149`) and ``fish_emoji_width`` (:issue:`2652`) ``fish_ambiguous_width`` (:issue:`5149`) and ``fish_emoji_width`` (:issue:`2652`)
variables. Alternatively, a new build-time option INTERNAL_WCWIDTH variables. Alternatively, a new build-time option INTERNAL_WCWIDTH
@@ -3362,7 +3414,7 @@ For distributors and developers
standard sh instead. standard sh instead.
- The ``hostname`` command is no longer required for fish to operate. - The ``hostname`` command is no longer required for fish to operate.
-
fish 2.7.1 (released December 23, 2017) fish 2.7.1 (released December 23, 2017)
======================================= =======================================
@@ -3374,7 +3426,7 @@ session (:issue:`4521`).
If you are upgrading from version 2.6.0 or before, please also review 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). the release notes for 2.7.0 and 2.7b1 (included below).
-
fish 2.7.0 (released November 23, 2017) fish 2.7.0 (released November 23, 2017)
======================================= =======================================
@@ -3386,7 +3438,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 Xcode builds and macOS packages could not be produced with 2.7b1, but
this is fixed in 2.7.0. this is fixed in 2.7.0.
-
fish 2.7b1 (released October 31, 2017) fish 2.7b1 (released October 31, 2017)
====================================== ======================================
@@ -4081,13 +4133,13 @@ Backward-incompatible changes
Other notable fixes and improvements 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 - Directory autosuggestions will now descend as far as possible if
there is only one child directory (:issue:`2531`) there is only one child directory (:issue:`2531`)
- Add support for bright colors (:issue:`1464`) - Add support for bright colors (:issue:`1464`)
- Allow Ctrl-J (``\cj``) to be bound separately from Ctrl-M - Allow Ctrl-J (``\cj``) to be bound separately from Ctrl-M
(``\cm``) (:issue:`217`) (``\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 that suffix
- Enable 24-bit colors on select terminals (:issue:`2495`) - Enable 24-bit colors on select terminals (:issue:`2495`)
- Support for SVN status in the prompt (:issue:`2582`) - Support for SVN status in the prompt (:issue:`2582`)
@@ -4111,13 +4163,13 @@ Other notable fixes and improvements
systemd-analyze, localectl, timedatectl systemd-analyze, localectl, timedatectl
- and more - 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`) customization (:issue:`2245`)
- A rewrite of the completions for cd, fixing a few bugs (:issue:`2299`, :issue:`2300`, - A rewrite of the completions for cd, fixing a few bugs (:issue:`2299`, :issue:`2300`,
:issue:`562`) :issue:`562`)
- Linux VTs now run in a simplified mode to avoid issues (:issue:`2311`) - Linux VTs now run in a simplified mode to avoid issues (:issue:`2311`)
- The vi-bindings now inherit from the emacs bindings - 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`) - ``funced`` will now also check $VISUAL (:issue:`2268`)
- A new ``suspend`` function (:issue:`2269`) - A new ``suspend`` function (:issue:`2269`)
- Subcommand completion now works better with split /usr (:issue:`2141`) - Subcommand completion now works better with split /usr (:issue:`2141`)
@@ -4186,7 +4238,7 @@ Other notable fixes and improvements
- New documentation design (:issue:`1662`), which requires a Doxygen version - New documentation design (:issue:`1662`), which requires a Doxygen version
1.8.7 or newer to build. 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 completions. By default this is
``/usr/share/fish/vendor-completions.d``; on systems with ``/usr/share/fish/vendor-completions.d``; on systems with
``pkgconfig`` installed this path is discoverable with ``pkgconfig`` installed this path is discoverable with
@@ -4477,7 +4529,7 @@ Other Notable Fixes
- xsel is no longer built as part of fish. It will still be invoked if - xsel is no longer built as part of fish. It will still be invoked if
installed separately :issue:`633` installed separately :issue:`633`
- \__fish_filter_mime no longer spews :issue:`628` - \__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` end of a block :issue:`624`
- fish_config knows how to find fish even if its not in the $PATH :issue:`621` - 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 - 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. 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! 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 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. First, you'll need an account there, and you'll need a git clone of fish.
Fork it on GitHub and then run:: 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 --all
cargo xtask format somefile.rs some.fish 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* 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 for public vars or ``_fish`` for private vars to minimize the
possibility of name clashes with user defined vars. 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>`__, If you use Vim: Install `vim-fish <https://github.com/dag/vim-fish>`__,
@@ -250,28 +250,52 @@ To run all tests and linters, use::
cargo xtask check cargo xtask check
.. _localization:
Contributing Translations Contributing Translations
========================= =========================
Fish uses GNU gettext to translate messages from English to other languages. fish can localize messages present in its Rust source code,
We use custom tools for extracting messages from source files and to localize at runtime. as well as messages in the various fish scripts present in this repository.
This means that we do not have a runtime dependency on the gettext library. The latter include a large amount of automatically identified messages,
It also means that some features are not supported, such as message context and plurals. originating for example from fish function descriptions.
We also expect all files to be UTF-8-encoded. When translating, prioritize the messages from the Rust source code.
In practice, this should not matter much for contributing translations.
Translation sources are stored in the ``localization/po`` directory and named ``ll_CC.po``, fish uses two different localization systems:
`GNU gettext <https://www.gnu.org/software/gettext/>`__ and `Fluent <https://projectfluent.org/>`__.
The former is used for all messages from fish scripts.
For messages from the Rust source code, we are in the process of replacing gettext with Fluent.
At the moment, both are used side-by-side,
with some messages localized with gettext and the others with Fluent.
We use custom tools for extracting messages from source files and for gettext localization at runtime.
This means that we do not have a runtime dependency on the gettext library.
It also means that some of gettext's features are not supported, such as message context and plurals.
We expect all files to be UTF-8-encoded.
Translation sources for gettext are stored in the ``localization/po`` directory and named ``ll_CC.po``,
whereas Fluent uses the ``localization/fluent`` directory and names of the shape ``ll_CC.ftl``,
where ``ll`` is the two (or possibly three) letter ISO 639-1 language code of the target language where ``ll`` is the two (or possibly three) letter ISO 639-1 language code of the target language
(e.g. ``pt`` for Portuguese). ``CC`` is an ISO 3166 country/territory code, (e.g. ``pt`` for Portuguese).
(e.g. ``BR`` for Brazil). ``CC`` is an ISO 3166 country/territory code, (e.g. ``BR`` for Brazil).
An example for a valid name is ``pt_BR.po``, indicating Brazilian Portuguese. An example for a valid name is ``pt_BR.po`` for gettext and ``pt_BR.ftl`` for Fluent,
indicating Brazilian Portuguese.
These are the files you will interact with when adding translations. These are the files you will interact with when adding translations.
In some cases, we also use language identifiers without a county code, i.e. ``ll.po``/``ll.ftl``.
Which variant is chosen involves various trade-offs for fallback behavior.
If you want to add a new language or language variant, feel free to ask about this.
Generally, if people who understand any variant of the language
are expected to understand the version you add
and there are no existing translations for another variant of the language,
it probably makes sense to omit the country code,
otherwise to use it for all variants of the language.
Adding translations for a new language Adding translations for a new language
-------------------------------------- --------------------------------------
Creating new translations requires the Gettext tools. Creating new translations for gettext requires the gettext tools.
More specifically, you will need ``msguniq``, ``msgmerge``, and ``msgattrib`` More specifically, you will need ``msguniq``, ``msgmerge``, and ``msgmerge``
for creating translations for a new language. for creating translations for a new language.
To create a PO file for a new language ``ll_CC``, run:: To create a PO file for a new language ``ll_CC``, run::
@@ -281,9 +305,15 @@ This will create a new PO file in ``localization/po/``
containing all messages available for translation. containing all messages available for translation.
If the file already exists, it will be updated. 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. For Fluent, it is sufficient to create a new, empty file
with the language-appropriate name in ``localization/fluent``.
After modifying a translation file, you can recompile fish,
and it will integrate the modifications you made.
This requires that the ``msgfmt`` utility is installed (comes as part of ``gettext``). This requires that the ``msgfmt`` utility is installed (comes as part of ``gettext``).
It is important that the ``localize-messages`` cargo feature is enabled, which it is by default. For messages localized with Fluent, recompiling fish is not necessary when you use a debug build.
Then, restarting fish is sufficient for seeing updated translations.
It is important that the ``localize-messages`` Cargo feature is enabled, which it is by default.
You can explicitly enable it using:: You can explicitly enable it using::
cargo build --features=localize-messages cargo build --features=localize-messages
@@ -296,7 +326,26 @@ or within the running fish shell::
set LANG pt_BR.utf8 set LANG pt_BR.utf8
For more options regarding how to choose languages, see Alternatively, you can also use the built-in ``status language`` command, e.g.::
status language set pt_BR
Use
::
status language list-available
to see a list of the available language identifiers.
This might also be helpful for checking that your new translations are recognized as expected.
Note that using environment variables enables fallback behavior,
e.g. if you specify ``LANG=de_DE.utf8`` and we do not have a ``de_DE`` catalog but a ``de`` catalog,
you will see messages from the latter.
With ``status language``, only exact matches are supported,
giving you more control over the fallback order.
If ``status language`` is used, it overrides the environment variable configuration.
For more options regarding how to choose languages via environment variables, see
`the corresponding gettext documentation `the corresponding gettext documentation
<https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html>`__. <https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html>`__.
One neat thing you can do is set a list of languages to check for translations in the order defined One neat thing you can do is set a list of languages to check for translations in the order defined
@@ -304,14 +353,19 @@ using the ``LANGUAGE`` variable, e.g.::
set LANGUAGE pt_BR de_DE set LANGUAGE pt_BR de_DE
or using::
status language set pt_BR de
to try to translate messages to Portuguese, if that fails try German, and if that fails too you will to try to translate messages to Portuguese, if that fails try German, and if that fails too you will
see the English version defined in the source code. see the default English version.
Modifying existing translations Modifying existing translations
------------------------------- -------------------------------
If you want to work on translations for a language which already has a corresponding ``po`` file, it If you want to work on translations for a language which already has translations, it
is sufficient to edit this file. No other changes are necessary. is sufficient to edit the existing files.
No other changes are necessary.
After recompiling fish, you should be able to see your translations in action. See the previous After recompiling fish, you should be able to see your translations in action. See the previous
section for details. section for details.
@@ -335,7 +389,7 @@ For example::
msgid "%s: No suitable job\n" msgid "%s: No suitable job\n"
msgstr "%s: Inget passande jobb\n" msgstr "%s: Inget passande jobb\n"
Any ``%s`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string should have the same placeholders in the same order. Any ``%s`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string must have the same placeholders in the same order.
Also any escaped characters, like that ``\n`` newline at the end, should be kept so the translation has the same behavior. Also any escaped characters, like that ``\n`` newline at the end, should be kept so the translation has the same behavior.
@@ -344,8 +398,104 @@ Our tests run ``msgfmt --check-format /path/to/file``, so they would catch misma
Be cautious about blindly updating an existing translation file. Be cautious about blindly updating an existing translation file.
``msgid`` strings should never be updated manually, only by running the appropriate script. ``msgid`` strings should never be updated manually, only by running the appropriate script.
Modifications to strings in source files Editing FTL files
---------------------------------------- -----------------
To get familiar with Fluent's FTL format,
you can read `Fluent's guide <https://projectfluent.org/fluent/guide/>`__.
The core principle is that each message has an ID.
This ID is specified in the source code.
At runtime, Fluent checks FTL files according to the user's language settings
to try to map the ID to a localized message.
All messages are localized into English because the source code only contains IDs, not proper messages.
Check ``localization/fluent/en.ftl`` to see which IDs are in use and what the corresponding messages are.
Some messages receive arguments, called variables in Fluent.
In Fluent, each variable has a name, which allows reordering them,
so they can appear in the order which makes most sense for the language.
The general format of a variable in an FTL file is ``{ $variable_name }``.
The Fluent ecosystem is not as mature as gettext, meaning there is less available tooling.
Therefore, we provide some of our own tools.
Running ``cargo xtask fluent`` provides an overview.
For translators, the following can be useful::
cargo xtask fluent format
to make FTL files conform to our expected format,
::
cargo xtask fluent show-missing
to show which message IDs do not have a translation yet, and
::
cargo xtask fluent check
to run checks on the FTL files, which can catch some mistakes.
Each of these commands takes optional path arguments,
so if you are working on a certain file like ``pt_BR.ftl``,
you might want to use
::
cargo xtask fluent check localization/fluent/pt_BR.ftl
from the repository root directory,
or, if you are in the ``localization/fluent`` directory,
::
cargo xtask fluent check pt_BR.ftl
If you want formatting in your editor,
::
cargo --quiet xtask fluent format -
might be useful, which reads FTL text from stdin and writes a formatted version to stdout,
or a copy of stdin if formatting failed.
Instead of invoking Cargo each time, you could also invoke the ``xtask`` binary if it exists.
A simple format-on-write setup in Vim:
.. code:: vim
function FormatFTL()
let cursor = getpos('.')
:%!cargo --quiet xtask fluent format -
call setpos('.', cursor)
endfunction
augroup ftl
autocmd!
autocmd! BufWritePre *.ftl :call FormatFTL()
augroup END
or equivalently in Lua for NeoVim:
.. code:: lua
local augroup_ftl = vim.api.nvim_create_augroup("ftl", { clear = true })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup_ftl,
pattern = "*.ftl",
callback = function()
local cursor = vim.fn.getpos(".")
vim.cmd("%!cargo --quiet xtask fluent format -")
vim.fn.setpos(".", cursor)
end,
})
There is also a `Vim plugin <https://github.com/projectfluent/fluent.vim>`__ for syntax highlighting.
Modifications to strings in source files (gettext-only)
-------------------------------------------------------
If a string changes in the sources, the old translations will no longer work. If a string changes in the sources, the old translations will no longer work.
If you add/remove/change a translatable strings in a source file, If you add/remove/change a translatable strings in a source file,
@@ -358,18 +508,56 @@ consider updating the ``msgid`` in the PO files such that translations are prese
Setting Code Up For Translations Setting Code Up For Translations
-------------------------------- --------------------------------
All non-debug messages output for user consumption should be marked for All non-debug messages output for user consumption should be marked for translation.
translation. In Rust, this requires the use of the ``wgettext!`` or ``wgettext_fmt!`` In Rust, this requires the use of the ``localize!`` macro for Fluent localization, e.g.:
macros:
.. code:: rust
localize!(
"some-message-id" = "English version of the message. Must be a valid Fluent message definition. Example variables: { $var1 }, { $var2 }",
var1 = "some string",
var2 = 42,
);
where the first key-value pair is the message's Fluent ID and the English version of the message.
The remaining key-value pairs specify Fluent variables and their values.
These must match the variables used in the message definition.
For changing message IDs or associated variable names accross all FTL files, the
:: ::
streams.out.append(wgettext_fmt!("%s: There are no jobs\n", argv[0])); cargo xtask fluent rename
All messages in fish script must be enclosed in single or double quote command can be helpful.
characters for our message extraction script to find them. The definitions in the Rust sources are the source of truth for the English version of the messages.
They must also be translated via a command substitution. This means Our tooling automatically generates a corresponding ``en.po`` file from these definitions,
that the following are **not** valid: but that file is fully auto-generated and manual modifications to it are not supported.
The `en.po` file exists to allow using Fluent tooling which expects such a file,
and to be able to detect changes to the definitions in the Rust sources.
Note that changes to the message definitions have consequences for translations.
Our tooling automatically detects such changes.
In some cases, they can be resolved automatically,
e.g. when a message ID no longer exists, the translations will be deleted.
If no automatic resolution is possible, annotations will be added to the affected translations,
indicating that they need developer attention.
As long as such annotations are present, our checks will not pass.
To resolve them, use ``cargo xtask fluent resolve-outdated``,
or, for languages into which you can translate,
update the translation and remove the annotation manually.
Legacy gettext localization uses the ``wgettext!`` or ``wgettext_fmt!`` macros.
New code should use Fluent instead.
.. code:: rust
streams.out.appendln(&wgettext_fmt!("%s: There are no jobs", argv[0]));
For explicit localization in fish scripts,
all messages must be enclosed in single or double quote characters
for our message extraction script to find them.
They must also be translated via a command substitution.
This means that the following are **not** valid:
:: ::

763
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,9 @@
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [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. # 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" rust-version = "1.85"
edition = "2024"
repository = "https://github.com/fish-shell/fish-shell" repository = "https://github.com/fish-shell/fish-shell"
# see doc_src/license.rst for details # see doc_src/license.rst for details
# don't forget to update COPYING and debian/copyright too # don't forget to update COPYING and debian/copyright too
@@ -18,6 +18,7 @@ bitflags = "2.5.0"
cc = "1.0.94" cc = "1.0.94"
cfg-if = "1.0.3" cfg-if = "1.0.3"
clap = { version = "4.5.54", features = ["derive"] } clap = { version = "4.5.54", features = ["derive"] }
clap_complete = { version = "4.6.4", features = ["unstable-dynamic"] }
errno = "0.3.0" errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" } fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" } fish-build-man-pages = { path = "crates/build-man-pages" }
@@ -25,31 +26,38 @@ fish-color = { path = "crates/color" }
fish-common = { path = "crates/common" } fish-common = { path = "crates/common" }
fish-fallback = { path = "crates/fallback" } fish-fallback = { path = "crates/fallback" }
fish-feature-flags = { path = "crates/feature-flags" } fish-feature-flags = { path = "crates/feature-flags" }
fish-fluent = { path = "crates/fluent" }
fish-fluent-extraction = { path = "crates/fluent-extraction" }
fish-gettext = { path = "crates/gettext" } fish-gettext = { path = "crates/gettext" }
fish-gettext-extraction = { path = "crates/gettext-extraction" } fish-gettext-extraction = { path = "crates/gettext-extraction" }
fish-gettext-maps = { path = "crates/gettext-maps" } fish-gettext-maps = { path = "crates/gettext-maps" }
fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" } fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" }
fish-localization = { path = "crates/localization" }
fish-localization-extraction = { path = "crates/localization-extraction" }
fish-printf = { path = "crates/printf", features = ["widestring"] } fish-printf = { path = "crates/printf", features = ["widestring"] }
fish-tempfile = { path = "crates/tempfile" } fish-tempfile = { path = "crates/tempfile" }
fish-util = { path = "crates/util" } fish-util = { path = "crates/util" }
fish-wcstringutil = { path = "crates/wcstringutil" } fish-wcstringutil = { path = "crates/wcstringutil" }
fish-wgetopt = { path = "crates/wgetopt" }
fish-widecharwidth = { path = "crates/widecharwidth" } fish-widecharwidth = { path = "crates/widecharwidth" }
fish-widestring = { path = "crates/widestring" } fish-widestring = { path = "crates/widestring" }
fish-wgetopt = { path = "crates/wgetopt" } fluent = { git = "https://github.com/danielrainer/fluent-rs", rev = "cf712bced280b217b6307edabc2089b3e57204ab" }
fluent-ftl-tools = { git = "https://codeberg.org/danielrainer/fluent-ftl-tools", rev = "5917664c8f2e4928ef1e480ff5c13bbe1e226066" }
fluent-syntax = { git = "https://github.com/danielrainer/fluent-rs", rev = "cf712bced280b217b6307edabc2089b3e57204ab" }
ignore = "0.4.25" ignore = "0.4.25"
itertools = "0.14.0" itertools = "0.14.0"
libc = "0.2.177" libc = "0.2.177"
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo. # 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 # disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
# files as of 22 April 2024. # files as of 22 April 2024.
lru = "0.16.2" lru = "0.18.0"
nix = { version = "0.31.1", default-features = false, features = [ nix = { version = "0.31.1", default-features = false, features = [
"event", "event",
"fs", "fs",
"inotify",
"hostname", "hostname",
"resource", "inotify",
"process", "process",
"resource",
"signal", "signal",
"term", "term",
"user", "user",
@@ -60,13 +68,13 @@ pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32",
"utf32", "utf32",
] } ] }
phf = { version = "0.13", default-features = false } phf = { version = "0.13", default-features = false }
phf_shared = "0.13"
phf_codegen = "0.13" phf_codegen = "0.13"
portable-atomic = { version = "1", default-features = false, features = [ portable-atomic = { version = "1", default-features = false, features = [
"fallback", "fallback",
] } ] }
proc-macro2 = "1.0" proc-macro2 = "1.0"
rand = { version = "0.9.2", default-features = false, features = [ rand = { version = "0.10.1", default-features = false, features = [
"small_rng",
"thread_rng", "thread_rng",
] } ] }
regex = "1.12.3" regex = "1.12.3"
@@ -79,11 +87,13 @@ rust-embed = { version = "8.11.0", features = [
rustc_version = "0.4.1" rustc_version = "0.4.1"
serial_test = { version = "3", default-features = false } serial_test = { version = "3", default-features = false }
strum_macros = "0.28.0" strum_macros = "0.28.0"
widestring = "1.2.0" syn = { version = "2.0.117", default-features = false, features = ["parsing"] }
unic-langid = "0.9.6"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
unicode-width = "0.2.0" unicode-width = "0.2.0"
unix_path = "1.0.1" unix_path = "1.0.1"
walkdir = "2.5.0" walkdir = "2.5.0"
widestring = "1.2.0"
xterm-color = "1.0.1" xterm-color = "1.0.1"
[profile.release] [profile.release]
@@ -96,7 +106,7 @@ debug = true
[package] [package]
name = "fish" name = "fish"
version = "4.7.0" version = "4.7.1"
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
default-run = "fish" default-run = "fish"
@@ -115,8 +125,11 @@ fish-color.workspace = true
fish-common.workspace = true fish-common.workspace = true
fish-fallback.workspace = true fish-fallback.workspace = true
fish-feature-flags.workspace = true fish-feature-flags.workspace = true
fish-fluent.workspace = true
fish-fluent-extraction = { workspace = true, optional = true }
fish-gettext = { workspace = true, optional = true } fish-gettext = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true } fish-gettext-extraction = { workspace = true, optional = true }
fish-localization = { workspace = true, optional = true }
fish-printf.workspace = true fish-printf.workspace = true
fish-tempfile.workspace = true fish-tempfile.workspace = true
fish-util.workspace = true fish-util.workspace = true
@@ -124,6 +137,7 @@ fish-wcstringutil.workspace = true
fish-wgetopt.workspace = true fish-wgetopt.workspace = true
fish-widecharwidth.workspace = true fish-widecharwidth.workspace = true
fish-widestring.workspace = true fish-widestring.workspace = true
fluent.workspace = true
itertools.workspace = true itertools.workspace = true
libc.workspace = true libc.workspace = true
lru.workspace = true lru.workspace = true
@@ -187,14 +201,22 @@ path = "src/bin/fish_key_reader.rs"
default = ["embed-manpages", "localize-messages"] default = ["embed-manpages", "localize-messages"]
benchmark = [] benchmark = []
embed-manpages = ["dep:fish-build-man-pages"] embed-manpages = ["dep:fish-build-man-pages"]
# Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at # This feature is used to enable extracting Fluent IDs from the source code for localization check.
# build time. # For normal builds, it should be disabled.
localize-messages = ["dep:fish-gettext"] # It only needs to be enabled if checking Fluent Translation List (FTL) files is desired.
fluent-extract = ["fish-fluent/fluent-extract", "dep:fish-fluent-extraction"]
# This feature is used to enable extracting messages from the source code for localization. # 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 # 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`. # 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. # There should not be a need to enable this feature manually.
gettext-extract = ["dep:fish-gettext-extraction"] gettext-extract = ["dep:fish-gettext-extraction"]
# Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at
# build time.
localize-messages = [
"fish-fluent/localize-messages",
"dep:fish-gettext",
"dep:fish-localization",
]
# The following features are auto-detected by the build-script and should not be enabled manually. # The following features are auto-detected by the build-script and should not be enabled manually.
tsan = [] tsan = []
@@ -220,6 +242,7 @@ needless_lifetimes = "allow"
new_without_default = "allow" new_without_default = "allow"
option_map_unit_fn = "allow" option_map_unit_fn = "allow"
ptr_offset_by_literal = "warn" ptr_offset_by_literal = "warn"
redundant_clone = "warn"
ref_option = "warn" ref_option = "warn"
semicolon_if_nothing_returned = "warn" semicolon_if_nothing_returned = "warn"
stable_sort_primitive = "warn" stable_sort_primitive = "warn"
@@ -232,6 +255,8 @@ unused_trait_names = "warn"
# In the future, they might change to flag other methods of printing. # In the future, they might change to flag other methods of printing.
print_stdout = "deny" print_stdout = "deny"
print_stderr = "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] [lints]
workspace = true workspace = true

View File

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

View File

@@ -62,6 +62,9 @@ cargo() {
# shellcheck disable=2317,2329 # shellcheck disable=2317,2329
cleanup () { cleanup () {
if [ -n "$fluent_extraction_dir" ] && [ -e "$fluent_extraction_dir" ]; then
rm -r "$fluent_extraction_dir"
fi
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
rm -r "$gettext_template_dir" rm -r "$gettext_template_dir"
fi fi
@@ -88,11 +91,14 @@ if [ -n "$FISH_TEST_MAX_CONCURRENCY" ]; then
export CARGO_BUILD_JOBS="$FISH_TEST_MAX_CONCURRENCY" export CARGO_BUILD_JOBS="$FISH_TEST_MAX_CONCURRENCY"
fi fi
fluent_extraction_dir=$(mktemp -d)
gettext_template_dir=$(mktemp -d) gettext_template_dir=$(mktemp -d)
( (
# shellcheck disable=2030
export FISH_FLUENT_EXTRACTION_DIR="$fluent_extraction_dir"
# shellcheck disable=2030 # shellcheck disable=2030
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir" export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
cargo build --workspace --all-targets --features=gettext-extract cargo build --workspace --all-targets --features=fluent-extract,gettext-extract
) )
if $lint; then if $lint; then
if command -v cargo-deny >/dev/null; then if command -v cargo-deny >/dev/null; then
@@ -111,6 +117,10 @@ if $lint; then
cargo xtask gettext --rust-extraction-dir="$gettext_template_dir" check cargo xtask gettext --rust-extraction-dir="$gettext_template_dir" check
fi fi
# An outdated `en.ftl` or translations using variables not provided by the source could crash fish,
# so don't treat this as a lint.
cargo xtask fluent check --from-source="$fluent_extraction_dir"
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`) # When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
# are dynamically linked against Rust's `std-xxx.dll` instead of being # are dynamically linked against Rust's `std-xxx.dll` instead of being
# statically link as they usually are. # statically link as they usually are.
@@ -124,6 +134,8 @@ fi
PATH="$PATH:$(rustc --print target-libdir)" PATH="$PATH:$(rustc --print target-libdir)"
export PATH export PATH
fi fi
# shellcheck disable=2031
export FISH_FLUENT_EXTRACTION_DIR="$fluent_extraction_dir"
cargo test --no-default-features --workspace --all-targets cargo test --no-default-features --workspace --all-targets
) )
cargo test --doc --workspace cargo test --doc --workspace
@@ -132,21 +144,26 @@ if $lint; then
cargo doc --workspace --no-deps cargo doc --workspace --no-deps
fi fi
# Using "()" not "{}" because we do want a subshell (for the export) system_tests() {
system_tests() ( "$workspace_root/tests/test_driver.py" "$build_dir" "$@"
# shellcheck disable=2163 }
[ -n "$*" ] && export "$@"
"$workspace_root/tests/test_driver.py" "$build_dir"
)
if $is_cygwin; then if $is_cygwin; then
# shellcheck disable=2059 # shellcheck disable=2059
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n" printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
system_tests "$cygwin_var"=winsymlinks (
export "$cygwin_var"=winsymlinks
system_tests
)
# shellcheck disable=2059 # shellcheck disable=2059
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n" printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
system_tests "$cygwin_var"= (
# 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 else
# shellcheck disable=2059 # shellcheck disable=2059
printf "=== Running ${green}integration tests${reset}\n" printf "=== Running ${green}integration tests${reset}\n"

View File

@@ -0,0 +1 @@
leak:fish_fluent::AVAILABLE_LANGUAGES

View File

@@ -255,6 +255,28 @@ do
sleep 20 sleep 20
done 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" cd "$fish_site"
make new-release make new-release
@@ -290,29 +312,6 @@ EOF
git push "$remote" HEAD:master git push "$remote" HEAD:master
} fi } 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 exit
} }

View File

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

6
clippy.toml Normal file
View File

@@ -0,0 +1,6 @@
allow-print-in-tests = true
disallowed-methods = [
# Not allowed to use libc::setlocale() directly, need to use the wrappers
# in src/locale.rs which takes a lock for the duration of the call.
"libc::setlocale",
]

View File

@@ -104,20 +104,12 @@ fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/) install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
fish_create_dirs( fish_create_dirs(
${rel_datadir}/fish ${rel_datadir}/fish/completions ${rel_datadir}/fish
${rel_datadir}/fish/functions ${rel_datadir}/fish/man/man1
${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
) )
# 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) 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) # Create only the vendor directories inside the prefix (#5029 / #6508)
fish_create_dirs( fish_create_dirs(
@@ -145,30 +137,6 @@ install(
DESTINATION ${rel_datadir}/pkgconfig 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 # CONDEMNED_PAGE is managed by the conditional above
# Building the man pages is optional: if sphinx isn't installed, they're not built # Building the man pages is optional: if sphinx isn't installed, they're not built
install( install(
@@ -179,22 +147,6 @@ install(
PATTERN ${CONDEMNED_PAGE} EXCLUDE 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 # Building the man pages is optional: if Sphinx isn't installed, they're not built
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL) install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
install( install(

View File

@@ -1,3 +1,11 @@
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 fish (4.7.0-1) stable; urgency=medium
* Release of new version 4.7.0. * Release of new version 4.7.0.

View File

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

View File

@@ -42,6 +42,16 @@ fn l10n_dir() -> Cow<'static, Path> {
workspace_root().join("localization").into() workspace_root().join("localization").into()
} }
pub fn ftl_dir() -> Cow<'static, Path> {
l10n_dir().join("fluent").into()
}
pub static DEFAULT_LANGUAGE: &str = "en";
pub fn default_ftl_file() -> Cow<'static, Path> {
ftl_dir().join(format!("{DEFAULT_LANGUAGE}.ftl")).into()
}
pub fn po_dir() -> Cow<'static, Path> { pub fn po_dir() -> Cow<'static, Path> {
l10n_dir().join("po").into() l10n_dir().join("po").into()
} }

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@
fd::{AsRawFd, BorrowedFd, RawFd}, fd::{AsRawFd, BorrowedFd, RawFd},
unix::ffi::OsStrExt as _, unix::ffi::OsStrExt as _,
}, },
rc::Rc,
sync::{ sync::{
Arc, LazyLock, Arc, LazyLock,
atomic::{AtomicI32, AtomicU32, Ordering}, atomic::{AtomicI32, AtomicU32, Ordering},
@@ -1184,10 +1185,10 @@ pub fn restore_term_foreground_process_group_for_exit() {
} }
} }
/// A wrapper around Cell which supports modifying the contents, scoped to a region of code. /// A wrapper around `Rc<Cell>` which supports modifying the contents, scoped to a region of code.
/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value, /// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the
/// instead of requiring an accessor function which returns a mutable reference to a field. /// value, instead of requiring an accessor function which returns a mutable reference to a field.
pub struct ScopedCell<T>(Cell<T>); pub struct ScopedCell<T>(Rc<Cell<T>>);
impl<T> Deref for ScopedCell<T> { impl<T> Deref for ScopedCell<T> {
type Target = Cell<T>; type Target = Cell<T>;
@@ -1197,15 +1198,9 @@ fn deref(&self) -> &Self::Target {
} }
} }
impl<T> DerefMut for ScopedCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: Copy> ScopedCell<T> { impl<T: Copy> ScopedCell<T> {
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self(Cell::new(value)) Self(Rc::new(Cell::new(value)))
} }
/// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped. /// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped.
@@ -1229,19 +1224,22 @@ pub fn new(value: T) -> Self {
/// // Restored after scope /// // Restored after scope
/// assert_eq!(cell.get(), 5); /// assert_eq!(cell.get(), 5);
/// ``` /// ```
pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>( pub fn scoped_mod<Modifier: FnOnce(&mut T)>(
&'a self, &self,
modifier: Modifier, modifier: Modifier,
) -> impl ScopeGuarding + 'a { ) -> impl DerefMut + use<T, Modifier> {
let mut val = self.get(); let mut val = self.get();
modifier(&mut val); modifier(&mut val);
let saved = self.replace(val); let saved = self.replace(val);
ScopeGuard::new(self, move |cell| cell.set(saved)) let inner = Rc::clone(&self.0);
ScopeGuard::new((), move |()| inner.set(saved))
} }
} }
/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code. /// A wrapper around `Rc<RefCell>` which supports modifying the contents, scoped to a region
pub struct ScopedRefCell<T>(RefCell<T>); /// of code.
#[derive(Default)]
pub struct ScopedRefCell<T>(Rc<RefCell<T>>);
impl<T> Deref for ScopedRefCell<T> { impl<T> Deref for ScopedRefCell<T> {
type Target = RefCell<T>; type Target = RefCell<T>;
@@ -1251,15 +1249,9 @@ fn deref(&self) -> &Self::Target {
} }
} }
impl<T> DerefMut for ScopedRefCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> ScopedRefCell<T> { impl<T> ScopedRefCell<T> {
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self(RefCell::new(value)) Self(Rc::new(RefCell::new(value)))
} }
/// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped. /// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped.
@@ -1286,19 +1278,20 @@ pub fn new(value: T) -> Self {
/// // Restored after scope /// // Restored after scope
/// assert_eq!(cell.borrow().flag, false); /// assert_eq!(cell.borrow().flag, false);
/// ``` /// ```
pub fn scoped_set<'a, Accessor, Value: 'a>( pub fn scoped_set<Accessor, Value>(
&'a self, &self,
value: Value, value: Value,
accessor: Accessor, accessor: Accessor,
) -> impl ScopeGuarding + 'a ) -> impl DerefMut + use<T, Accessor, Value>
where where
Accessor: Fn(&mut T) -> &mut Value + 'a, Accessor: Fn(&mut T) -> &mut Value,
{ {
let mut data = self.borrow_mut(); let mut data = self.borrow_mut();
let mut saved = std::mem::replace(accessor(&mut data), value); let mut saved = std::mem::replace(accessor(&mut data), value);
ScopeGuard::new(self, move |cell| { let inner = Rc::clone(&self.0);
let mut data = cell.borrow_mut(); ScopeGuard::new((), move |()| {
std::mem::swap((accessor)(&mut data), &mut saved); let mut inner = inner.borrow_mut();
std::mem::swap((accessor)(&mut inner), &mut saved);
}) })
} }
@@ -1320,7 +1313,7 @@ pub fn scoped_set<'a, Accessor, Value: 'a>(
/// ///
/// assert_eq!(*cell.borrow(), 10); /// assert_eq!(*cell.borrow(), 10);
/// ``` /// ```
pub fn scoped_replace<'a>(&'a self, value: T) -> impl ScopeGuarding + 'a { pub fn scoped_replace(&self, value: T) -> impl DerefMut + use<T> {
self.scoped_set(value, |s| s) self.scoped_set(value, |s| s)
} }
} }
@@ -1359,12 +1352,6 @@ impl<T, F: FnOnce(T)> ScopeGuard<T, F> {
pub fn new(value: T, on_drop: F) -> Self { pub fn new(value: T, on_drop: F) -> Self {
Self(Some((value, on_drop))) Self(Some((value, on_drop)))
} }
/// Cancels the invocation of the callback, returning the original wrapped value.
pub fn cancel(mut guard: Self) -> T {
let (value, _) = guard.0.take().expect("Should always have Some value");
value
}
} }
impl<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> { impl<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> {
@@ -1389,13 +1376,6 @@ fn drop(&mut self) {
} }
} }
/// A trait expressing what ScopeGuard can do. This is necessary because our scoped cells return an
/// `impl Trait` object and therefore methods on ScopeGuard which take a self parameter cannot be
/// used.
pub trait ScopeGuarding: DerefMut + Sized {}
impl<T, F: FnOnce(T)> ScopeGuarding for ScopeGuard<T, F> {}
pub const fn assert_send<T: Send>() {} pub const fn assert_send<T: Send>() {}
pub const fn assert_sync<T: Sync>() {} pub const fn assert_sync<T: Sync>() {}

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
[package]
name = "fish-fluent-extraction"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
description = "proc-macro for extracting IDs for fluent translation"
[lib]
proc-macro = true
[dependencies]
fish-tempfile.workspace = true
fluent-ftl-tools.workspace = true
fluent-syntax.workspace = true
proc-macro2.workspace = true
syn.workspace = true
[build-dependencies]
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,3 @@
fn main() {
rsconf::rebuild_if_env_changed("FISH_FLUENT_EXTRACTION_DIR");
}

View File

@@ -0,0 +1,105 @@
extern crate proc_macro;
use fluent_ftl_tools::{
HasEntries as _, format_resource, parse_str_as_syntax_resource, serialize_resource,
};
use proc_macro::TokenStream;
use std::{
collections::HashSet,
ffi::{OsStr, OsString},
io::Write as _,
path::PathBuf,
};
use syn::{
Ident, LitStr, Token,
parse::{Parse, ParseStream},
parse_macro_input,
};
struct LocalizeDefinition {
message_id: String,
message_definition: String,
variables: Vec<String>,
}
impl Parse for LocalizeDefinition {
fn parse(input: ParseStream) -> syn::Result<Self> {
let message_id = input.parse::<LitStr>()?.value();
input.parse::<Token![=]>()?;
let message_definition = input.parse::<LitStr>()?.value();
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
fn parse_key(input: ParseStream) -> syn::Result<String> {
let key = input.parse::<Ident>()?;
Ok(format!("{key}"))
}
let variables = Vec::from_iter(input.parse_terminated(parse_key, Token![,])?);
Ok(Self {
message_id,
message_definition,
variables,
})
}
}
fn check(localize_definition: &LocalizeDefinition) -> String {
let message = format!(
"{} = {}",
localize_definition.message_id, localize_definition.message_definition
);
let resource = parse_str_as_syntax_resource(&message)
.unwrap_or_else(|err| panic!("Failed to parse Fluent message\n{message}\n\n{err}"));
let resource_entries = &resource.body;
assert_eq!(
resource_entries.len(),
1,
"Expected exactly one Fluent entry specified via macro."
);
assert!(
matches!(resource_entries[0], fluent_syntax::ast::Entry::Message(_)),
"Expected definition of Fluent message, but got {:?}",
resource_entries[0]
);
let formatted_resource = format_resource(resource.clone())
.unwrap_or_else(|err| panic!("Resource is not formatted correctly:\n{err}"));
let formatted_resource_string = serialize_resource(&formatted_resource);
let formatted_resource_string_trimmed = formatted_resource_string.trim();
assert!(
message == formatted_resource_string_trimmed,
"Message is not formatted correctly.\nActual:\n{message}\nExpected:\n{formatted_resource_string_trimmed}"
);
let mut expected_variables = HashSet::new();
for variable in &localize_definition.variables {
assert!(
expected_variables.insert(variable.as_str()),
"Variable {variable} is used as a key more than once."
);
}
resource.check_if_expected_variables_match_message(&expected_variables, &localize_definition.message_id).unwrap_or_else(|err| {
panic!("Variables used in macro key-value pairs do not match variables used in Fluent message:\n{err}");
});
message
}
fn extract(message: &str, dir_path: &OsStr) {
let dir = PathBuf::from(dir_path);
let (path, result) = fish_tempfile::create_file_with_retry(|| {
dir.join(fish_tempfile::random_filename(OsString::new()))
});
let mut file = result.unwrap_or_else(|e| {
panic!("Failed to create temporary file {path:?}:\n{e}");
});
file.write_all(message.as_bytes()).unwrap();
file.write_all(b"\n").unwrap();
}
#[proc_macro]
pub fn fluent_extract(input: TokenStream) -> TokenStream {
if let Some(dir_path) = std::env::var_os("FISH_FLUENT_EXTRACTION_DIR") {
let localize_definition = parse_macro_input!(input as LocalizeDefinition);
let message = check(&localize_definition);
extract(&message, &dir_path);
}
TokenStream::new()
}

30
crates/fluent/Cargo.toml Normal file
View File

@@ -0,0 +1,30 @@
[package]
name = "fish-fluent"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
cfg-if.workspace = true
fish-build-helper.workspace = true
fish-localization.workspace = true
fish-fluent-extraction = { workspace = true, optional = true }
fluent.workspace = true
rust-embed.workspace = true
unic-langid.workspace = true
widestring.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
[dev-dependencies]
fluent-ftl-tools.workspace = true
[features]
fluent-extract = ["dep:fish-fluent-extraction"]
localize-messages = []
[lints]
workspace = true

4
crates/fluent/build.rs Normal file
View File

@@ -0,0 +1,4 @@
fn main() {
use fish_build_helper::{ftl_dir, rebuild_if_embedded_path_changed};
rebuild_if_embedded_path_changed(ftl_dir());
}

360
crates/fluent/src/lib.rs Normal file
View File

@@ -0,0 +1,360 @@
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
fmt::Write as _,
sync::{LazyLock, Mutex},
};
use cfg_if::cfg_if;
use fish_localization::{
DEFAULT_LANGUAGE, Language, LocalizationLanguage, define_localization_language_type,
};
use fluent::{
FluentArgs, FluentResource, FluentValue, concurrent::FluentBundle, types::FluentNumber,
};
use rust_embed::RustEmbed;
#[cfg(feature = "fluent-extract")]
pub extern crate fish_fluent_extraction;
use unic_langid::LanguageIdentifier;
type Bundle = &'static FluentBundle<FluentResource>;
type NamedBundle = (Language<'static>, Bundle);
pub type LocalizedMessage = Cow<'static, str>;
static LANGUAGE_BUNDLES: LazyLock<Mutex<HashMap<Language, Bundle>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static DEFAULT_BUNDLE: LazyLock<NamedBundle> =
LazyLock::new(|| (DEFAULT_LANGUAGE, make_bundle(DEFAULT_LANGUAGE)));
static LANGUAGE_BUNDLE_PRECEDENCE: LazyLock<Mutex<Vec<NamedBundle>>> =
LazyLock::new(|| Mutex::new(vec![*DEFAULT_BUNDLE]));
cfg_if!(
if #[cfg(feature = "localize-messages")] {
#[derive(RustEmbed)]
#[folder = "../../localization/fluent/"]
#[include = "*.ftl"]
struct FtlFiles;
} else {
#[derive(RustEmbed)]
#[folder = "../../localization/fluent/"]
#[include = "en.ftl"]
struct FtlFiles;
}
);
define_localization_language_type! {FluentLocalizationLanguage}
static AVAILABLE_LANGUAGES: LazyLock<HashSet<FluentLocalizationLanguage>> = LazyLock::new(|| {
HashSet::from_iter(FtlFiles::iter().map(|language| {
let suffix = ".ftl";
let language = Language(match language {
Cow::Borrowed(language) => language.strip_suffix(suffix).unwrap(),
Cow::Owned(mut language) => {
assert!(language.ends_with(suffix));
language.truncate(language.len() - suffix.len());
Box::leak(Box::new(language))
}
});
FluentLocalizationLanguage(language)
}))
});
pub fn get_available_languages() -> &'static HashSet<FluentLocalizationLanguage> {
&AVAILABLE_LANGUAGES
}
fn read_ftl_file(path: &str) -> String {
let file = FtlFiles::get(path)
.unwrap_or_else(|| panic!("Tried to get FTL file {path} which does not exist."));
String::from_utf8(Vec::from(file.data))
.unwrap_or_else(|e| panic!("Content of {path} is not valid UTF-8: {e}"))
}
fn make_bundle(language: Language<'static>) -> Bundle {
let langid: LanguageIdentifier = language
.parse()
.map_err(|e| format!("Failed to parse language identifier {language}: {e}"))
.unwrap();
let mut bundle = FluentBundle::new_concurrent(vec![langid]);
let file_data = read_ftl_file(&format!("{language}.ftl"));
// Error handling could use fluent_ftl_tools::display_parse_errors(), but that would require
// making the crate a regular dependency.
match FluentResource::try_new(file_data) {
Ok(res) => {
bundle
.add_resource(res)
.map_err(|e| {
format!(
"Failed to add FTL resources to the bundle for language {language}: {e:?}"
)
})
.unwrap();
// Isolation marks can result in undesirable behavior if the terminal does not support
// them properly.
// Turn this off for now.
// If we add detection for terminal support, we could enable it conditionally.
// Without these marks, text order can be incorrect when right-to-left characters are
// involved.
// https://www.w3.org/International/questions/qa-bidi-unicode-controls
// https://github.com/fish-shell/fish-shell/pull/11928#discussion_r2488850606
bundle.set_use_isolating(false);
// Leak to create static reference.
Box::leak(Box::new(bundle))
}
Err((_resource, errors)) => {
let mut error_string = format!("Errors parsing FTL file for {language}:\n");
for error in errors {
let _ = writeln!(error_string, "{error}");
}
panic!("{error_string}");
}
}
}
/// Set the order in which languages should be tried for localization.
/// The default language (en) will be added to the end of the list.
/// If the provided list contains `en`, anything after it will be ignored, since we assume that every
/// message can be localized in English, so there is no point in specifying further fallback
/// options.
/// This function also takes care of lazily loading and parsing data from the embedded FTL files,
/// so no additional initialization is needed.
pub fn set_language_precedence(precedence: &[FluentLocalizationLanguage]) {
let new_precedence = {
let mut bundles = LANGUAGE_BUNDLES.lock().unwrap();
// Only take the languages preceding the default language, since it is assumed that everything
// is translatable in the default language.
let mut new_precedence: Vec<_> = precedence
.iter()
.take_while(|&lang| Language::from(lang) != DEFAULT_LANGUAGE)
.map(|lang| {
let language = lang.into();
// If a bundle already exists for the language, use it.
// Otherwise create a new one and cache it.
let bundle = bundles.get(&language).copied().unwrap_or_else(|| {
let bundle = make_bundle(language);
bundles.insert(language, bundle);
bundle
});
(language, bundle)
})
.collect();
// Add the default language at the end of the precedence list.
new_precedence.push(*DEFAULT_BUNDLE);
new_precedence
};
*LANGUAGE_BUNDLE_PRECEDENCE.lock().unwrap() = new_precedence;
}
pub fn get_language_precedence() -> Vec<Language<'static>> {
let language_precedence = LANGUAGE_BUNDLE_PRECEDENCE.lock().unwrap();
language_precedence.iter().map(|&(lang, _)| lang).collect()
}
/// Use the [`localize!`] macro instead of calling this directly.
/// Panics on errors.
pub fn format_localized(id: &str, args: &FluentArgs) -> LocalizedMessage {
let mut errors = vec![];
let bundle_precedence = LANGUAGE_BUNDLE_PRECEDENCE.lock().unwrap();
for (_, bundle) in bundle_precedence.iter() {
let Some(message) = bundle.get_message(id) else {
continue;
};
let pattern = message.value().expect("Message has no value.");
let value = bundle.format_pattern(pattern, Some(args), &mut errors);
// NOTE: Unused arguments are not considered errors.
if !errors.is_empty() {
let mut error_message = format!(
"Unexpected formatting errors occurred for message ID '{id}' in language '{}':\n",
bundle.locales[0].language
);
for error in errors {
let _ = writeln!(error_message, "{error}");
}
panic!("{error_message}");
}
return value;
}
panic!("Message '{id}' not available in any catalog.")
}
/// Call this to localize a message with Fluent.
/// The first argument is a string literal, which defines a Fluent message ID.
/// It is followed by a mandatory `=` and the English version of the message as a string literal.
/// The message definition must be valid Fluent syntax. Specifically it must be permissible as the
/// definition of a Fluent message.
/// In the simplest case, it will just be a regular string.
/// If Fluent variables should be used, they need to appear in the message definition, e.g.
/// `{ $example_variable }`. Then, the variable must also be specified as a key-value pair in the
/// arguments of the [`localize!`] macro, as demonstrated in the example below.
/// The key is the Fluent variable name, which also needs to be a syntactically valid Rust
/// identifier, and the value is whatever the variable should be replaced by when formatting the
/// localized message.
/// It is considered an error if the variables specified in the message definition do not match the
/// variables specified via keys in subsequent arguments to the macro.
/// The order of key-value pairs can be chosen and modified arbitrarily, and variables may appear
/// more than once in the message definition.
///
/// ```
/// # use fish_fluent::localize;
/// # // Note that `localize!` usage in doc-texts is not checked.
/// let example_message = localize!("test-with-args" = "Two arguments: { $first }, { $second }", first = "foo", second = 42);
/// assert_eq!(example_message, "Two arguments: foo, 42");
/// ```
///
/// Note that changing the message ID or the message definition has consequences for translations.
/// If such a change is made, our tooling will automatically detect it and require the developer
/// making the change to specify what should happen to translations.
/// See `cargo xtask fluent resolve-outdated`.
#[macro_export]
macro_rules! localize {
($id:literal = $message:literal $(, $key:ident = $value:expr)* $(,)?) => {
{
use $crate::ToFluentValue as _;
#[cfg(feature = "fluent-extract")]
fish_fluent_extraction::fluent_extract!($id = $message $(, $key)*);
let mut args = fluent::FluentArgs::new();
$(
args.set(stringify!($key), $value.to_fluent_value());
)*
$crate::format_localized($id, &args)
}
};
}
/// Define a function calling [`localize!`] and returning the result.
/// This macro can be used when multiple locations need access to the same message.
/// Do not use [`localize!`] with the same message ID more than once.
/// Instead, define a function which takes the Fluent variables of the message as arguments and
/// internally calls [`localize!`]. Then, use this function wherever the message is needed.
/// This macro helps with avoiding some boilerplate. Its first argument is the name of the function
/// which should be defined. Then, a key-value pair specifying the message ID and English definition
/// follows, in the same format as for [`localize!`]. The remaining arguments are the names of
/// Fluent variables which appear in the message definition. These will become the function's
/// arguments names and they will be used in the internal [`localize!`] call.
#[macro_export]
macro_rules! localize_fn {
($vis:vis $fn:ident, $id:literal = $message:literal $(, $key:ident )* $(,)?) => {
$vis fn $fn<'a>($($key: impl $crate::ToFluentValue<'a>),*) -> $crate::LocalizedMessage {
localize!(
$id = $message,
$($key = $key),*
)
}
};
}
/// Trait to account for types which don't have a `Into<FluentValue>` implementation, i.e.
/// widestrings.
/// Can be removed once we no longer use such types.
pub trait ToFluentValue<'a> {
fn to_fluent_value(self) -> FluentValue<'a>;
}
macro_rules! impl_to_fluent_value_wrapper {
($($t:ty),* $(,)?) => {
$(
impl<'a> ToFluentValue<'a> for $t {
fn to_fluent_value(self) -> FluentValue<'a> {
self.into()
}
}
)*
};
}
impl_to_fluent_value_wrapper! {
FluentNumber,
String,
f32, f64,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
}
macro_rules! impl_to_fluent_value_wrapper_ref {
($($t:ty),* $(,)?) => {
$(
impl<'a> ToFluentValue<'a> for &'a $t {
fn to_fluent_value(self) -> FluentValue<'a> {
self.into()
}
}
)*
};
}
impl_to_fluent_value_wrapper_ref! {
String, str,
f32, f64,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
}
impl<'a, T: Into<FluentValue<'a>>> ToFluentValue<'a> for Option<T> {
fn to_fluent_value(self) -> FluentValue<'a> {
self.into()
}
}
impl<'a> ToFluentValue<'a> for char {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for &char {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for widestring::Utf32String {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for &widestring::Utf32String {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for &widestring::Utf32Str {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
#[cfg(test)]
mod tests {
use super::{DEFAULT_LANGUAGE, FtlFiles};
use crate::read_ftl_file;
use fluent_ftl_tools::consistency::check_all_resources;
#[test]
fn test_simple_message() {
let message_key_value = localize!(
"test-with-args" = "Two arguments: { $first }, { $second }",
first = "foo",
second = 42,
);
assert_eq!(message_key_value, "Two arguments: foo, 42");
}
#[test]
fn check_ftl_files() {
let default_resource_name = format!("{DEFAULT_LANGUAGE}.ftl");
let default_string = read_ftl_file(&default_resource_name);
let other_resources = FtlFiles::iter()
.map(|file_path| {
let file_string = read_ftl_file(&file_path);
(file_path.to_string(), file_string)
})
.collect::<Vec<_>>();
check_all_resources((&default_resource_name, default_string), other_resources)
.unwrap_or_else(|e| panic!("FTL resource checks failed:\n{e}"));
}
}

View File

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

View File

@@ -1,17 +1,19 @@
[package] [package]
name = "fish-gettext-maps" name = "fish-gettext-maps"
version = "0.0.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
version = "0.0.0"
repository.workspace = true repository.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
phf.workspace = true phf.workspace = true
fish-localization.workspace = true
[build-dependencies] [build-dependencies]
fish-build-helper.workspace = true fish-build-helper.workspace = true
fish-gettext-mo-file-parser.workspace = true fish-gettext-mo-file-parser.workspace = true
fish-localization.workspace = true
phf_codegen.workspace = true phf_codegen.workspace = true
rsconf.workspace = true rsconf.workspace = true

View File

@@ -5,6 +5,7 @@
}; };
use fish_build_helper::env_var; use fish_build_helper::env_var;
use fish_localization::Language;
fn main() { fn main() {
let cache_dir = let cache_dir =
@@ -54,16 +55,16 @@ fn embed_localizations(cache_dir: &Path) {
if po_file_path.extension() != Some(OsStr::new("po")) { if po_file_path.extension() != Some(OsStr::new("po")) {
continue; continue;
} }
let lang = po_file_path let language = po_file_path
.file_stem() .file_stem()
.expect("All entries in the po directory must be regular files."); .expect("All entries in the po directory must be regular files.");
let language = lang.to_str().unwrap().to_owned(); let language = language.to_str().unwrap();
// Each language gets its own static map for the mapping from message in the source code to // Each language gets its own static map for the mapping from message in the source code to
// the localized version. // the localized version.
let map_name = format!("LANG_MAP_{language}"); let map_name = format!("LANG_MAP_{language}");
let cached_map_path = cache_dir.join(lang); let cached_map_path = cache_dir.join(language);
// Include the file containing the map for this language in the main generated file. // Include the file containing the map for this language in the main generated file.
writeln!( writeln!(
@@ -74,7 +75,10 @@ fn embed_localizations(cache_dir: &Path) {
.unwrap(); .unwrap();
// Map from the language identifier to the map containing the localizations for this // Map from the language identifier to the map containing the localizations for this
// language. // language.
catalogs.entry(language, format!("&{map_name}")); catalogs.entry(
Language(Box::leak(Box::new(language.to_owned()))),
format!("&{map_name}"),
);
if let Ok(metadata) = std::fs::metadata(&cached_map_path) { if let Ok(metadata) = std::fs::metadata(&cached_map_path) {
// Cached map file exists, but might be outdated. // Cached map file exists, but might be outdated.
@@ -139,7 +143,7 @@ fn to_raw_str(s: &str) -> String {
write!( write!(
&mut cached_map_file, &mut cached_map_file,
"static {}: phf::Map<&'static str, &'static str> = {}", "static {}: phf::Map<&'static str, &'static str> = {}",
&map_name, map_name,
single_language_localization_map.build() single_language_localization_map.build()
) )
.unwrap(); .unwrap();
@@ -150,7 +154,8 @@ fn to_raw_str(s: &str) -> String {
write!( write!(
&mut localization_map_file, &mut localization_map_file,
"pub static CATALOGS: phf::Map<&str, &phf::Map<&str, &str>> = {}", "use fish_localization::Language;\n\
pub static CATALOGS: phf::Map<Language, &phf::Map<&str, &str>> = {}",
catalogs.build() catalogs.build()
) )
.unwrap(); .unwrap();

View File

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

View File

@@ -1,13 +1,14 @@
[package] [package]
name = "fish-gettext" name = "fish-gettext"
version = "0.0.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
version = "0.0.0"
repository.workspace = true repository.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
fish-gettext-maps.workspace = true fish-gettext-maps.workspace = true
fish-localization.workspace = true
phf.workspace = true phf.workspace = true
[lints] [lints]

View File

@@ -1,12 +1,13 @@
use fish_gettext_maps::CATALOGS; use fish_gettext_maps::CATALOGS;
use fish_localization::{Language, LocalizationLanguage, define_localization_language_type};
use std::{ use std::{
collections::HashMap, collections::HashSet,
sync::{LazyLock, Mutex}, sync::{LazyLock, Mutex},
}; };
type Catalog = &'static phf::Map<&'static str, &'static str>; type Catalog = &'static phf::Map<&'static str, &'static str>;
static LANGUAGE_PRECEDENCE: Mutex<Vec<(&'static str, Catalog)>> = Mutex::new(Vec::new()); static LANGUAGE_PRECEDENCE: Mutex<Vec<(Language, Catalog)>> = Mutex::new(Vec::new());
pub fn gettext(message_str: &'static str) -> Option<&'static str> { pub fn gettext(message_str: &'static str) -> Option<&'static str> {
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap(); let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
@@ -20,21 +21,17 @@ pub fn gettext(message_str: &'static str) -> Option<&'static str> {
None None
} }
#[derive(Clone, Copy)] define_localization_language_type! {GettextLocalizationLanguage}
pub struct GettextLocalizationLanguage {
language: &'static str,
}
static AVAILABLE_LANGUAGES: LazyLock<HashMap<&'static str, GettextLocalizationLanguage>> = static AVAILABLE_LANGUAGES: LazyLock<HashSet<GettextLocalizationLanguage>> = LazyLock::new(|| {
LazyLock::new(|| { HashSet::from_iter(
HashMap::from_iter( CATALOGS
CATALOGS .entries()
.entries() .map(|(&language, _)| GettextLocalizationLanguage(language)),
.map(|(&language, _)| (language, GettextLocalizationLanguage { language })), )
) });
});
pub fn get_available_languages() -> &'static HashMap<&'static str, GettextLocalizationLanguage> { pub fn get_available_languages() -> &'static HashSet<GettextLocalizationLanguage> {
&AVAILABLE_LANGUAGES &AVAILABLE_LANGUAGES
} }
@@ -43,9 +40,9 @@ pub fn set_language_precedence(new_precedence: &[GettextLocalizationLanguage]) {
.iter() .iter()
.map(|lang| { .map(|lang| {
( (
lang.language, lang.into(),
*CATALOGS *CATALOGS
.get(lang.language) .get(lang.as_ref())
.expect("Only languages for which catalogs exist may be passed to gettext."), .expect("Only languages for which catalogs exist may be passed to gettext."),
) )
}) })
@@ -53,7 +50,7 @@ pub fn set_language_precedence(new_precedence: &[GettextLocalizationLanguage]) {
*LANGUAGE_PRECEDENCE.lock().unwrap() = catalogs; *LANGUAGE_PRECEDENCE.lock().unwrap() = catalogs;
} }
pub fn get_language_precedence() -> Vec<&'static str> { pub fn get_language_precedence() -> Vec<Language<'static>> {
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap(); let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
language_precedence.iter().map(|&(lang, _)| lang).collect() language_precedence.iter().map(|&(lang, _)| lang).collect()
} }

View File

@@ -0,0 +1,14 @@
[package]
name = "fish-localization"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-build-helper.workspace = true
phf_shared.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,86 @@
use std::{borrow::Borrow, hash::Hash};
use phf_shared::{FmtConst, PhfBorrow, PhfHash};
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Language<'a>(pub &'a str);
pub const DEFAULT_LANGUAGE: Language = Language(fish_build_helper::DEFAULT_LANGUAGE);
impl<'a> std::ops::Deref for Language<'a> {
type Target = &'a str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> std::fmt::Display for Language<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for Language<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
impl Borrow<str> for Language<'_> {
fn borrow(&self) -> &str {
self.0
}
}
impl<'a> PhfHash for Language<'a> {
fn phf_hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.phf_hash(state);
}
}
impl<'a> FmtConst for Language<'a> {
fn fmt_const(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Language(")?;
self.0.fmt_const(f)?;
f.write_str(")")
}
}
impl<'a> PhfBorrow<Language<'a>> for Language<'a> {
fn borrow(&self) -> &Language<'a> {
self
}
}
pub trait LocalizationLanguage:
AsRef<Language<'static>> + Clone + Copy + Eq + Hash + Ord + PartialEq + PartialOrd + Borrow<str>
{
}
#[macro_export]
macro_rules! define_localization_language_type {
($name:ident) => {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(Language<'static>);
impl LocalizationLanguage for $name {}
impl AsRef<Language<'static>> for $name {
fn as_ref(&self) -> &Language<'static> {
&self.0
}
}
impl From<&$name> for Language<'static> {
fn from(value: &$name) -> Self {
value.0
}
}
impl std::borrow::Borrow<str> for $name {
fn borrow(&self) -> &str {
self.0.borrow()
}
}
};
}

View File

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

View File

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

View File

@@ -7,6 +7,7 @@
use rand::distr::{Alphanumeric, Distribution as _}; use rand::distr::{Alphanumeric, Distribution as _};
#[must_use]
pub struct TempFile { pub struct TempFile {
file: File, file: File,
path: PathBuf, path: PathBuf,
@@ -32,6 +33,7 @@ fn drop(&mut self) {
} }
} }
#[must_use]
pub struct TempDir { pub struct TempDir {
path: PathBuf, path: PathBuf,
} }
@@ -119,7 +121,7 @@ mod tests {
#[test] #[test]
fn create_tempfile() { fn create_tempfile() {
super::new_file().unwrap(); let _ = super::new_file().unwrap();
} }
#[test] #[test]
@@ -145,7 +147,7 @@ fn use_tempfile() {
#[test] #[test]
fn create_tempdir() { fn create_tempdir() {
super::new_dir().unwrap(); let _ = super::new_dir().unwrap();
} }
#[test] #[test]

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
//! Helper functions for working with wcstring. //! Helper functions for working with wcstring.
use std::{borrow::Cow, mem};
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy}; use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
use fish_widestring::{ELLIPSIS_CHAR, prelude::*}; use fish_widestring::{ELLIPSIS_CHAR, prelude::*};
@@ -458,19 +460,22 @@ pub fn truncate(input: &wstr, max_len: usize) -> WString {
output output
} }
pub fn trim(input: WString, any_of: Option<&wstr>) -> WString { pub fn trim<'a>(input: &'a mut WString, any_of: Option<&wstr>) -> Cow<'a, wstr> {
let any_of = any_of.unwrap_or(L!("\t\x0B \r\n")); let any_of = any_of.unwrap_or(L!("\t\x0B \r\n"));
let mut result = input; let result = input;
let Some(suffix) = result.chars().rposition(|c| !any_of.contains(c)) else { let Some(suffix) = result.chars().rposition(|c| !any_of.contains(c)) else {
return WString::new(); return Cow::Borrowed(L!(""));
}; };
result.truncate(suffix + 1);
let prefix = result let prefix = result
.chars() .chars()
.position(|c| !any_of.contains(c)) .position(|c| !any_of.contains(c))
.expect("Should have one non-trimmed character"); .expect("Should have one non-trimmed character");
result.split_off(prefix) result.truncate(suffix + 1);
if prefix == 0 {
Cow::Owned(mem::take(result))
} else {
Cow::Borrowed(result.slice_from(prefix))
}
} }
/// Return the number of escaping backslashes before a character. /// Return the number of escaping backslashes before a character.

View File

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

View File

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

View File

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

View File

@@ -608,6 +608,12 @@ pub trait ToWString {
fn to_wstring(&self) -> WString; fn to_wstring(&self) -> WString;
} }
impl ToWString for std::path::Path {
fn to_wstring(&self) -> WString {
bytes2wcstring(self.as_os_str().as_encoded_bytes())
}
}
#[inline] #[inline]
fn to_wstring_impl(mut val: u64, neg: bool) -> WString { fn to_wstring_impl(mut val: u64, neg: bool) -> WString {
// 20 digits max in u64: 18446744073709551616. // 20 digits max in u64: 18446744073709551616.

View File

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

434
crates/xtask/src/fluent.rs Normal file
View File

@@ -0,0 +1,434 @@
use crate::files_with_extension;
use anyhow::{Context, Result, anyhow, bail};
use clap::{Args, Subcommand};
use fish_build_helper::{default_ftl_file, ftl_dir};
use fluent_ftl_tools::{
HasEntries,
annotate::{
Annotation, MessageAnnotationRead, MessageAnnotationWrite, add_annotation_to_messages,
get_message_ids_with_different_value, modify_messages, remove_annotation_from_messages,
},
consistency::{check_all_resource_files, remove_inconsistent_translations},
delete_message_from_paths, filter_resource_messages,
format::{FormattingMode, format_text},
format_resource,
missing::find_missing_message_ids_in_files,
parse_as_syntax_resource, parse_str_as_syntax_resource,
rename::rename_in_all_files,
serialize_resource, serialize_resource_to_file, serialize_resources_to_files,
};
use fluent_syntax::{
ast::{Entry, Resource},
serializer::Serializer,
};
use std::{
collections::{HashMap, HashSet},
io::{Read, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
};
#[derive(Subcommand)]
pub enum FluentCommandArgs {
/// Check consistency of FTL files.
Check {
/// Also check if `en.ftl` is up to date with respect to the Rust sources.
/// This argument optionally takes a value, which must be the path to the directory into
/// which the message definitions have been extracted.
/// If this argument is provided without a value, fish will be recompiled with the `fluent-extract` feature.
/// See <https://github.com/fish-shell/fish-shell/pull/11928#discussion_r3379980617>
#[arg(long)]
from_source: Option<Option<PathBuf>>,
files: Vec<PathBuf>,
},
/// Format files. Without arguments, all FTL files are formatted.
/// If '-' is specified, input is read from stdin and output written to stdout.
Format { files: Vec<PathBuf> },
/// Rename an ID or variables used with an ID.
Rename(RenameArgs),
/// Resolve outdated translations.
/// The `update` subcommand of this xtask adds annotations to outdated translations.
/// These can be resolved manually, but for developers not familiar with the affected languages
/// can be more convenient to use this tool.
#[command(subcommand)]
ResolveOutdated(ResolveOutdatedCommand),
/// Display message IDs which could be added to the specified file(s), or all files if no
/// arguments are given.
ShowMissing { files: Vec<PathBuf> },
/// Generate en.ftl from the source code and update translations.
/// New messages will silently be inserted into en.ftl.
/// Deleted messages will be removed from all FTL files.
/// If a Fluent variable is renamed or removed, the translations of the message will be deleted.
/// For other changes to a message, developer interaction is required.
/// All affected translations will be annotated to indicate that they are outdated.
/// Our checks will fail as long as such annotations are present.
/// Developers have several options to address them, depending on the situation.
/// If the developer is familiar with the language of the translation, they can manually update
/// the translation as required and removed the annotation.
/// Otherwise, `cargo xtask fluent resolve-outdated` can be used.
/// See its description for details.
Update {
/// Path to the directory into which the message definitions from the Rust sources have been extracted.
/// If this is not specified, fish will be compiled with the `fluent-extract` feature to
/// obtain the message definitions.
#[arg(long)]
extraction_dir: Option<PathBuf>,
},
}
#[derive(Args)]
pub struct RenameArgs {
/// The message ID.
/// Use 'old=new', where 'old' is the old message ID as currently present in the file,
/// and 'new' the name it should be renamed to.
/// If the ID should not change, use 'old'.
id: String,
/// The variables to rename for the specified ID.
/// Use 'old=new', where 'old' is the old variable name as currently present in the file,
/// and 'new' the name it should be renamed to.
vars: Vec<String>,
}
#[derive(Subcommand)]
pub enum ResolveOutdatedCommand {
/// Delete the message.
/// Use this when the meaning of the message changed significantly.
Delete(ResolveOutdatedArgs),
/// Remove the annotation.
/// Use this when the translation is already up-to-date.
Ignore(ResolveOutdatedArgs),
/// Keep the message as is and replace the OUTDATED annotation with a NEEDS-REVIEW annotation to
/// indicate that translators should have a look at it.
/// Use this for minor changes to the English message, which do not change its meaning much.
NeedsReview(ResolveOutdatedArgs),
/// Show unresolved messages.
ShowUnresolved {
/// Show outdated translations for this message, instead of all messages with outdated
/// translations.
/// Can be specified multiple times.
#[arg(long, value_name = "Message ID")]
id: Vec<String>,
/// Consider translations for this language instead of all languages.
/// Can be specified multiple times.
/// The value must either be a path to an FTL file or the name of a language as it appears
/// in one of our FTL file names.
/// The `.ftl` suffix is optional when specifying the language name.
#[arg(long)]
language: Vec<PathBuf>,
/// Print the outdated message definitions.
#[arg(long)]
definitions: bool,
},
}
#[derive(Args)]
pub struct ResolveOutdatedArgs {
/// A Fluent message ID of a message annotated as outdated.
message_id: String,
/// Paths to any number of FTL files. If none are specified, all FTL files are considered.
files: Vec<PathBuf>,
}
impl ResolveOutdatedArgs {
fn get_file_paths(&self) -> Result<Vec<PathBuf>> {
if self.files.is_empty() {
non_default_ftl_files()
} else {
Ok(self.files.clone())
}
}
}
fn non_default_ftl_files() -> Result<Vec<PathBuf>> {
let all_ftl_files = files_with_extension([ftl_dir()], "ftl")?;
let mut non_default_files = Vec::with_capacity(all_ftl_files.len() - 1);
let default_path = default_ftl_file().canonicalize()?;
for path in all_ftl_files {
if path.canonicalize()? != default_path {
non_default_files.push(path);
}
}
Ok(non_default_files)
}
pub fn fluent(args: FluentCommandArgs) -> Result<()> {
use FluentCommandArgs::*;
match args {
Check { from_source, files } => check(from_source, files),
Format { files } => format(files),
Rename(rename_args) => rename(rename_args),
ResolveOutdated(command) => resolve_outdated(command),
ShowMissing { files } => show_missing(files),
Update { extraction_dir } => update(extraction_dir),
}
}
fn check(from_source: Option<Option<PathBuf>>, mut files: Vec<PathBuf>) -> Result<()> {
if let Some(source) = from_source {
let generated_resource = generate_default_resource(source)?;
let mut diff_process = Command::new("diff")
.arg("-u")
.arg(default_ftl_file().as_ref())
.arg("-")
.stdin(Stdio::piped())
.spawn()
.context("Failed to run diff")?;
diff_process
.stdin
.take()
.unwrap()
.write_all(serialize_resource(&generated_resource).as_bytes())
.context("Failed to write to stdin of diff process")?;
let diff_status = diff_process
.wait()
.context("Failed to wait for diff child.")?;
if !diff_status.success() {
bail!(
"{:?} is not up to date.\n\
Run `cargo xtask fluent update` to resolve this.",
default_ftl_file()
);
}
}
if files.is_empty() {
files = non_default_ftl_files()?;
}
check_all_resource_files(default_ftl_file(), &files)
}
fn format(mut files: Vec<PathBuf>) -> Result<()> {
if files == [PathBuf::from("-")] {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input).unwrap();
let formatted_text = format_text(&input).with_context(|| {
print!("{input}");
"Formatting input failed"
})?;
print!("{formatted_text}");
return Ok(());
}
if files.is_empty() {
files = files_with_extension([ftl_dir()], "ftl")?;
}
let errors = files
.iter()
.filter_map(|path| {
fluent_ftl_tools::format::format_path(path, FormattingMode::Rewrite).err()
})
.collect::<Vec<anyhow::Error>>();
if !errors.is_empty() {
let mut error_message = String::from("Found these errors:\n");
for e in errors {
error_message.push_str(&format!("{e}\n"));
}
bail!("{error_message}");
}
Ok(())
}
fn rename(args: RenameArgs) -> Result<()> {
let old_id;
let new_id;
match args.id.split_once('=') {
Some((old, new)) => {
old_id = old.into();
new_id = Some(new.into());
}
None => {
old_id = args.id;
new_id = None;
}
}
let mut variable_update = HashMap::new();
for arg in args.vars {
match arg.split_once('=') {
Some((old, new)) => {
variable_update.insert(old.into(), new.into());
}
None => {
bail!("Argument '{arg}' must use the format 'old=new'")
}
}
}
let files = files_with_extension([ftl_dir()], "ftl")?;
let resources = rename_in_all_files(&files, &old_id, &new_id, &variable_update)
.map_err(|e| anyhow!("Failed to perform renaming:\n{e}"))?;
serialize_resources_to_files(&resources).map_err(|e| anyhow!("Failed to update files:\n{e}"))
}
fn show_missing(mut files: Vec<PathBuf>) -> Result<()> {
if files.is_empty() {
files = files_with_extension([ftl_dir()], "ftl")?;
}
match find_missing_message_ids_in_files(default_ftl_file(), &files) {
Ok(None) => {
println!("No missing messages.");
}
Ok(Some(missing_message)) => {
println!("{missing_message}");
}
Err(e) => {
bail!("Error:\n{e}");
}
}
Ok(())
}
fn resolve_outdated(command: ResolveOutdatedCommand) -> Result<()> {
match command {
ResolveOutdatedCommand::Delete(resolve_outdated_args) => {
let paths = resolve_outdated_args.get_file_paths()?;
delete_message_from_paths(&resolve_outdated_args.message_id, &paths)
}
ResolveOutdatedCommand::Ignore(resolve_outdated_args) => {
let files = resolve_outdated_args.get_file_paths()?;
remove_annotation_from_messages(
Annotation::Outdated,
&HashSet::from_iter([resolve_outdated_args.message_id]),
&files,
)
}
ResolveOutdatedCommand::NeedsReview(resolve_outdated_args) => {
let files = resolve_outdated_args.get_file_paths()?;
modify_messages(
|message| {
message.add_annotation(Annotation::NeedsReview)?;
message.remove_annotation(Annotation::Outdated)
},
&HashSet::from_iter([resolve_outdated_args.message_id]),
&files,
)
}
ResolveOutdatedCommand::ShowUnresolved {
id: message_ids,
language: languages,
definitions,
} => {
let languages = if languages.is_empty() {
non_default_ftl_files()?
} else {
let mut language_paths = Vec::with_capacity(languages.len());
let ftl_dir = ftl_dir().canonicalize()?;
for lang in languages {
if std::fs::exists(&lang)? {
language_paths.push(lang);
continue;
}
if let Some(lang_name) = lang.to_str() {
let lang_name = lang_name.strip_suffix(".ftl").unwrap_or(lang_name);
let file_path = ftl_dir.join(lang_name).with_extension("ftl");
if std::fs::exists(&file_path)? {
language_paths.push(file_path);
continue;
}
}
bail!(
"Language {lang:?} is invalid. It must be a path to an FTL file or the name of a language as present in a file name of one of our FTL files."
)
}
language_paths
};
let id_set: HashSet<&String> = HashSet::from_iter(&message_ids);
for lang in &languages {
println!("{lang:?}:");
let mut resource =
filter_resource_messages(parse_as_syntax_resource(lang)?, |message| {
message.has_annotation(Annotation::Outdated)
})?;
if !id_set.is_empty() {
resource = filter_resource_messages(resource, |message| {
Ok(id_set.contains(&message.id.name))
})?;
}
for entry in &resource.body {
let Entry::Message(message) = entry else {
continue;
};
if definitions {
let mut serializer =
Serializer::new(fluent_syntax::serializer::Options { with_junk: true });
serializer.serialize_message(message);
let serialized_message = serializer.into_serialized_text();
println!("{serialized_message}");
} else {
println!("{}", message.id.name);
}
}
println!();
}
Ok(())
}
}
}
fn update(extraction_dir: Option<PathBuf>) -> Result<()> {
let old_default_resource = parse_as_syntax_resource(default_ftl_file())
.context("Failed to parse existing default FTL file")?;
let generated_resource = generate_default_resource(extraction_dir)
.context("Failed to generate new default FTL file")?;
let ids_of_outdated_messages =
get_message_ids_with_different_value(&old_default_resource, &generated_resource);
let non_default_ftl_paths = non_default_ftl_files()?;
remove_inconsistent_translations(
generated_resource.all_message_vars()?,
&non_default_ftl_paths,
)?;
add_annotation_to_messages(
Annotation::Outdated,
&ids_of_outdated_messages,
&non_default_ftl_paths,
)?;
serialize_resource_to_file(&generated_resource, default_ftl_file())
.context("Failed to update FTL file for default language.")?;
Ok(())
}
fn generate_default_resource(extraction_dir: Option<PathBuf>) -> Result<Resource<String>> {
fn concat_unique_file_content<P: AsRef<Path>>(dir: P) -> Result<String> {
let mut unique_file_contents = HashSet::new();
for dir_entry in std::fs::read_dir(&dir)
.with_context(|| format!("Failed to read from directory {:?}", dir.as_ref()))?
{
let dir_entry = dir_entry
.with_context(|| format!("Error traversing directory {:?}", dir.as_ref()))?;
if dir_entry
.file_type()
.with_context(|| format!("Could not get file type for {:?}", dir_entry.path()))?
.is_file()
{
unique_file_contents.insert(
std::fs::read_to_string(dir_entry.path()).with_context(|| {
format!("Could not read from file {:?}", dir_entry.path())
})?,
);
}
}
let mut concatenated_content = String::new();
unique_file_contents
.iter()
.for_each(|file_content| concatenated_content.push_str(file_content));
Ok(concatenated_content)
}
let id_file_content = match extraction_dir {
Some(dir) => concat_unique_file_content(dir).unwrap(),
None => {
let temp_dir = fish_tempfile::new_dir().unwrap();
Command::new(env!("CARGO"))
.args([
"check",
"--workspace",
"--all-targets",
"--features=fluent-extract",
])
.env("FISH_FLUENT_EXTRACTION_DIR", temp_dir.path().as_os_str())
.status()
.map_err(|e| format!("Failed to extract Fluent IDs: {e}"))
.unwrap();
concat_unique_file_content(temp_dir.path()).unwrap()
}
};
let resource = parse_str_as_syntax_resource(&id_file_content).map_err(|e| anyhow!("{e}"))?;
format_resource(resource).map_err(|e| anyhow!("{e}"))
}

View File

@@ -71,6 +71,7 @@ pub fn format(args: FormatArgs) -> Result<()> {
} }
} }
format_fish(&args)?; format_fish(&args)?;
format_fluent(&args)?;
format_python(&args)?; format_python(&args)?;
format_rust(&args)?; format_rust(&args)?;
Ok(()) Ok(())
@@ -127,6 +128,31 @@ fn format_fish(args: &FormatArgs) -> Result<()> {
run_formatter(&mut formatter, "fish_indent") run_formatter(&mut formatter, "fish_indent")
} }
fn format_fluent(args: &FormatArgs) -> Result<()> {
println!("=== Running {GREEN}Fluent FTL formatter (built-in){GREEN:#}");
let mut ftl_files = files_with_extension(&args.paths, "ftl")?;
if args.all {
ftl_files.extend(files_with_extension([fish_build_helper::ftl_dir()], "ftl")?);
}
let mode = if args.check {
fluent_ftl_tools::format::FormattingMode::Check
} else {
fluent_ftl_tools::format::FormattingMode::Rewrite
};
let errors = ftl_files
.iter()
.filter_map(|path| fluent_ftl_tools::format::format_path(path, mode).err())
.collect::<Vec<anyhow::Error>>();
if !errors.is_empty() {
let mut error_message = String::from("Found these errors:\n");
for e in errors {
error_message.push_str(&format!("{e}\n"));
}
bail!("{error_message}");
}
Ok(())
}
fn format_python(args: &FormatArgs) -> Result<()> { fn format_python(args: &FormatArgs) -> Result<()> {
let mut formatter = Command::new("ruff"); let mut formatter = Command::new("ruff");
formatter.arg("format"); formatter.arg("format");

View File

@@ -153,7 +153,11 @@ pub fn gettext(args: GettextArgs) -> Result<()> {
return Err(e); return Err(e);
} }
if found_diff { if found_diff {
bail!("Not all files are up to date"); bail!(
"Not all PO files are up to date.\n\
Run `cargo xtask gettext update` to bring them up to date automatically.\
"
);
} }
Ok(()) Ok(())
} }
@@ -303,7 +307,7 @@ pub fn new<P: AsRef<Path>>(rust_extraction_dir: P) -> Result<Self> {
// gettext-extraction output // gettext-extraction output
let msguniq_output = Command::new("msguniq") let msguniq_output = Command::new("msguniq")
.args(["--no-wrap"]) .args(["--no-wrap"])
.run_with_stdio(template.content)?; .run_with_stdio(&template.content)?;
Ok(Template { Ok(Template {
content: msguniq_output, content: msguniq_output,
}) })
@@ -345,7 +349,7 @@ fn add_rust_messages<P: AsRef<Path>>(&mut self, extraction_dir: P) -> Result<()>
let msguniq_output = Command::new("msguniq") let msguniq_output = Command::new("msguniq")
.args(["--no-wrap", "--sort-output"]) .args(["--no-wrap", "--sort-output"])
.env("LC_ALL", "C.UTF-8") .env("LC_ALL", "C.UTF-8")
.run_with_stdio(concatenated_content)?; .run_with_stdio(&concatenated_content)?;
// The Header entry needs to be removed again, // The Header entry needs to be removed again,
// because it is added outside of this function. // because it is added outside of this function.
let expected_prefix = MINIMAL_HEADER.as_bytes(); let expected_prefix = MINIMAL_HEADER.as_bytes();

View File

@@ -8,13 +8,14 @@
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use walkdir::WalkDir; use walkdir::WalkDir;
pub mod fluent;
pub mod format; pub mod format;
pub mod gettext; pub mod gettext;
pub mod shellcheck; pub mod shellcheck;
pub trait CommandExt { pub trait CommandExt {
fn run(&mut self) -> Result<()>; fn run(&mut self) -> Result<()>;
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>>; fn run_with_stdio(&mut self, stdin: &[u8]) -> Result<Vec<u8>>;
} }
impl CommandExt for Command { impl CommandExt for Command {
@@ -29,7 +30,7 @@ fn run(&mut self) -> Result<()> {
Ok(()) Ok(())
} }
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>> { fn run_with_stdio(&mut self, stdin: &[u8]) -> Result<Vec<u8>> {
let command_name = self.get_program().to_owned(); let command_name = self.get_program().to_owned();
let mut child = self let mut child = self
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@@ -40,7 +41,7 @@ fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>> {
.stdin .stdin
.take() .take()
.unwrap() .unwrap()
.write_all(&stdin) .write_all(stdin)
.with_context(|| format!("Failed to write to stdin of {command_name:?}"))?; .with_context(|| format!("Failed to write to stdin of {command_name:?}"))?;
let command_output = child let command_output = child
.wait_with_output() .wait_with_output()

View File

@@ -1,8 +1,12 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::{Parser, Subcommand}; use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::CompleteEnv;
use fish_build_helper::as_os_strs; use fish_build_helper::as_os_strs;
use std::{path::PathBuf, process::Command}; use std::{path::PathBuf, process::Command};
use xtask::{CommandExt, cargo, format::FormatArgs, gettext::GettextArgs, shellcheck::shellcheck}; use xtask::{
CommandExt, cargo, fluent::FluentCommandArgs, format::FormatArgs, gettext::GettextArgs,
shellcheck::shellcheck,
};
#[derive(Parser)] #[derive(Parser)]
#[command( #[command(
@@ -19,6 +23,9 @@ struct Cli {
enum Task { enum Task {
/// Run various checks on the repo. /// Run various checks on the repo.
Check, Check,
/// Run Fluent-related tools.
#[command(subcommand)]
Fluent(FluentCommandArgs),
/// Format files or check if they are correctly formatted. /// Format files or check if they are correctly formatted.
Format(FormatArgs), Format(FormatArgs),
/// Work on the gettext PO files. /// Work on the gettext PO files.
@@ -36,10 +43,33 @@ enum Task {
ShellCheck, ShellCheck,
} }
/// 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<()> { fn main() -> Result<()> {
CompleteEnv::with_factory(FakeCargoWrapperForCompletion::command).complete();
let cli = Cli::parse(); let cli = Cli::parse();
match cli.task { match cli.task {
Task::Check => run_checks(), Task::Check => run_checks(),
Task::Fluent(fluent_command_args) => xtask::fluent::fluent(fluent_command_args),
Task::Format(format_args) => xtask::format::format(format_args), Task::Format(format_args) => xtask::format::format(format_args),
Task::Gettext(gettext_args) => xtask::gettext::gettext(gettext_args), Task::Gettext(gettext_args) => xtask::gettext::gettext(gettext_args),
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent), Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),

View File

@@ -1,7 +1,7 @@
[licenses] [licenses]
# We want really high confidence when inferring licenses from text # We want really high confidence when inferring licenses from text
confidence-threshold = 0.93 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 = [ allow = [
"BSD-2-Clause", "BSD-2-Clause",
"BSD-3-Clause", "BSD-3-Clause",

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. 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:: Here are some examples of flag validations::

View File

@@ -6,7 +6,7 @@ Synopsis
.. synopsis:: .. synopsis::
cd [DIRECTORY] cd [( -L | --no-dereference ) | ( -P | --dereference )] [DIRECTORY]
Description Description
----------- -----------
@@ -18,14 +18,25 @@ Description
``cd`` changes the current working directory. ``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 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`. 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. 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>`. 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. 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. 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 cd /usr/src/fish-shell
# changes the working directory to /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 See Also
-------- --------

View File

@@ -81,7 +81,7 @@ The following options are available:
**-h** or **--help** **-h** or **--help**
Displays help about using this command. 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``). - 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. 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. or you can define your own.
It receives the full commandline as one argument per token, so $argv[1] contains the missing command. 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 function fish_command_not_found
__fish_default_command_not_found_handler $argv __fish_default_command_not_found_handler $argv
end 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

@@ -30,13 +30,13 @@ Options
The following options are available: The following options are available:
**-f** or **--no-functions** **-f** or **--no-functions**
Stops checking functions Skips checking functions
**-c** or **--no-completions** **-c** or **--no-completions**
Stops checking completions Skips checking completions
**-C** or **--no-config** **-C** or **--no-config**
Stops checking configuration files like config.fish or snippets in the conf.d directories. Skips checking configuration files like config.fish or snippets in the conf.d directories.
**-d** or **--no-diff** **-d** or **--no-diff**
Removes the diff display (this happens automatically if ``diff`` can't be found) Removes the diff display (this happens automatically if ``diff`` can't be found)

View File

@@ -27,16 +27,11 @@ Further information on how to use :ref:`vi mode <vi-mode>`.
Differences from Vim 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** **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. 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 Examples
-------- --------

View File

@@ -40,7 +40,7 @@ Run::
>_ funced fish_prompt >_ 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:: 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. If the wrapped command is the same as the function name, this will be ignored.
**-e** *EVENT_NAME* or **--on-event** *EVENT_NAME* **-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* **-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). 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,7 +56,7 @@ The following options are available:
**-V** or **--inherit-variable NAME** **-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. 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``. 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``.

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"``). 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 Notes
----- -----

View File

@@ -53,7 +53,7 @@ See also
``if`` is only as useful as the command used as the condition. ``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:`test` can compare numbers, strings and check paths
- :doc:`string` can perform string operations including wildcard and regular expression matches - :doc:`string` can perform string operations including wildcard and regular expression matches

View File

@@ -218,6 +218,6 @@ Examples
Compatibility notes 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. 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

@@ -57,7 +57,7 @@ The following subcommands are available.
``path basename`` returns the last path component of the given path, by removing the directory prefix and removing trailing slashes. In other words, it is the part that is not the dirname. For files you might call it the "filename". ``path basename`` returns the last path component of the given path, by removing the directory prefix and removing trailing slashes. In other words, it is the part that is not the dirname. For files you might call it the "filename".
If the ``-E`` or ``---no-extension`` option is used and the base name contained a period, the path is returned with the extension (or the last extension) removed, i.e. the "filename" without an extension (akin to calling ``path change-extension "" (path basename $path)``). If the ``-E`` or ``--no-extension`` option is used and the base name contained a period, the path is returned with the extension (or the last extension) removed, i.e. the "filename" without an extension (akin to calling ``path change-extension "" (path basename $path)``).
It returns 0 if there was a basename, i.e. if the path wasn't empty or just slashes. It returns 0 if there was a basename, i.e. if the path wasn't empty or just slashes.

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. Marks the end of the line with the NUL character, instead of newline. This also disables interactive mode.
**-L** or **--line** **-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. 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 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

@@ -31,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_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. 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. 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. However if :envvar:`fish_term256` is set to 0, fish prefers the first named color specified.
@@ -93,7 +93,7 @@ Notes
1. Using ``set_color normal`` will reset all colors and modes to the terminal's default. 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. 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``. 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. 5. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
6. If you use ``set_color`` in a command substitution or a pipe, these characters will also be captured. 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. This may or may not be desirable.

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``. ``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. 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. This is a limitation of Unix' argument passing.
.. END DESCRIPTION .. 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: 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 Example

View File

@@ -38,7 +38,7 @@ which offers yes/no in these cases::
> myprog -o <TAB> > myprog -o <TAB>
> myprog --output <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" complete -c myprog -s o -l output --no-files -ra "yes no"
@@ -112,7 +112,7 @@ As a more comprehensive example, here's a commented excerpt of the completions f
complete -c timedatectl -l version -d 'Print a short version string and exit' complete -c timedatectl -l version -d 'Print a short version string and exit'
complete -c timedatectl -l no-pager -d 'Do not pipe output into a pager' complete -c timedatectl -l no-pager -d 'Do not pipe output into a pager'
For examples of how to write your own complex completions, study the completions in ``/usr/share/fish/completions``. (The exact path depends on your chosen installation prefix and may be slightly different) For examples of how to write your own completions, study ``share/completions`` in the fish-shell source tree.
Useful functions for writing completions Useful functions for writing completions
---------------------------------------- ----------------------------------------
@@ -142,15 +142,15 @@ Functions beginning with the string ``__fish_print_`` print a newline separated
Where to put completions 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 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 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 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``; - 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). - 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. 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): def get_command_description(path, name):
"""Return the description for a command, by parsing its synopsis line""" """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: for line in opened:
if line.startswith(name + " - "): if line.startswith(name + " - "):
_, desc = line.split(" - ", 1) _, 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. 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. 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: 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. 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)? 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? 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: 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. - 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 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. - 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. 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 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:: 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 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, # Define $PAGER *g*lobal and e*x*ported,
# so this is like ``export PAGER=less`` # so this is like ``export PAGER=less``
@@ -41,7 +41,7 @@ or to erase variables::
PAGER=cat git log 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 For instance, here's bash
@@ -99,10 +99,19 @@ See :ref:`Shell variables <variables>` for more.
.. _bash-globs: .. _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) 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:: Globbing doesn't happen on expanded variables, so::
@@ -122,7 +131,7 @@ See :ref:`Wildcards <expand-wildcard>` for more.
Quoting 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*:: 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 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":: For example, to replace "bar" with "baz"::
@@ -199,7 +208,7 @@ as fish's :doc:`source <cmds/source>` can read from stdin.
Heredocs Heredocs
-------- --------
Fish does not have ``<<EOF`` "heredocs". Instead of fish does not have ``<<EOF`` "heredocs". Instead of
.. code-block:: sh .. code-block:: sh
@@ -271,14 +280,14 @@ So heredocs really are minor syntactical sugar that introduces a lot of special
Test (``test``, ``[``, ``[[``) 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]``). ``set -q`` can be used to determine if a variable exists or has a certain number of elements (``set -q foo[2]``).
Arithmetic Expansion 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 math $i + 1
@@ -301,7 +310,7 @@ Both ``*`` and ``x`` are valid ways to spell multiplication, but ``*`` needs to
Prompts 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: As an example, here's a relatively simple bash prompt:
@@ -323,18 +332,18 @@ and a rough fish equivalent::
This shows a few differences: 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. - 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``. 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 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 for i in 1 2 3; do
echo $i 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", # (bash allows the word "function",
# but this is an extension) # 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 Subshells
--------- ---------
@@ -415,7 +424,7 @@ This includes things like:
baz & baz &
done 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:: ``()`` 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 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 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``. 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. 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: 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:
@@ -61,7 +61,7 @@ Completion scripts are loaded on demand, like :ref:`functions are <syntax-functi
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: 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. 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:: Running just ``fish_config`` opens a browser interface, or you can use ``fish_config theme`` from fish::
# disable nearly all coloring # 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. 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``. 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. 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. 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. 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. 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. 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 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>`:: 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) 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. 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. 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 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. 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 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: 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``. 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. 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>`. 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 function fish_prompt
# A simple prompt. Displays the current directory # A simple prompt. Displays the current directory
@@ -413,7 +413,7 @@ Comments can also appear after a line like so::
Conditions 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: .. _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. 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 if test -e /etc/os-release
cat /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 [#]_. 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 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. 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. 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). 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. 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. .. [#] 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 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 >_ set -l foo x y z
>_ echo 1$foo >_ echo 1$foo
@@ -1348,7 +1348,7 @@ Note: Exporting is not a :ref:`scope <variables-scope>`, but an additional state
Lists 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 > set mylist first second third
> printf '%s\n' $mylist # prints each element on its own line > 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=blue small
smurf_PATH=forest:mushroom 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:: 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. 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:: _ .. envvar:: _
@@ -1739,7 +1739,7 @@ Fish also provides additional information through the values of certain environm
.. ENVVAR:: SHLVL .. 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 .. 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. 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: .. _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. 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: 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 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 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 - 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! echo Got WINCH signal!
end 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. - ``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 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. 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, 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. 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>`. 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. 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 - ``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_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) - ``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). 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. 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. 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. Control sequences are denoted in a fish-like syntax.
Special characters other than ``\`` are not escaped. Special characters other than ``\`` are not escaped.
@@ -333,7 +332,7 @@ Unicode Codepoints
By default, fish outputs the following non-ASCII characters:: By default, fish outputs the following non-ASCII characters::
× ► ¶ ⏎ • ● … μ “ ” ← → ↑ ↓ × ► ¶ ⏎ • ● … μ “ ”
as well as control pictures (U+2400 through U+241F), 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? 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! 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 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 > echo hello world
hello world hello world
@@ -332,7 +332,7 @@ For more on combining lists with strings (or even other lists), see :ref:`cartes
Wildcards Wildcards
--------- ---------
Fish supports the familiar wildcard ``*``. To list all JPEG files:: fish supports the familiar wildcard ``*``. To list all JPEG files::
> ls *.jpg > ls *.jpg
lena.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. ``$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:: 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?) 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: 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 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 > 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 # 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 LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8 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

@@ -0,0 +1,5 @@
argparse-exclusive-flag-invalid = Exklusive Option '{ $flag }' ist ungültig
argparse-exclusive-flag-string-invalid = Exklusive Option '{ $flag_string }' ist ungültig
argparse-implicit-int-flag-already-defined = Implizite Zahloption '{ $flag }' ist schon definiert
argparse-invalid-option-spec = Ungültige Options-Spezifikation '{ $option_spec }' bei Zeichen '{ $bad_char }'
fish-version = { $package_name }, Version { $version }

View File

@@ -0,0 +1,6 @@
argparse-exclusive-flag-invalid = exclusive flag '{ $flag }' is not valid
argparse-exclusive-flag-string-invalid = exclusive flag string '{ $flag_string }' is not valid
argparse-implicit-int-flag-already-defined = Implicit int flag '{ $flag }' already defined
argparse-invalid-option-spec = Invalid option spec '{ $option_spec }' at char '{ $bad_char }'
fish-version = { $package_name }, version { $version }
test-with-args = Two arguments: { $first }, { $second }

View File

@@ -0,0 +1,5 @@
argparse-exclusive-flag-invalid = la opción exclusiva '{ $flag }' no es válida
argparse-exclusive-flag-string-invalid = el string de opciones exclusivas '{ $flag_string }' no es válido
argparse-implicit-int-flag-already-defined = La opción entera implícita '{ $flag }' ya está definida
argparse-invalid-option-spec = Especificación de opción no válida '{ $option_spec }' en el carácter '{ $bad_char }'
fish-version = { $package_name }, versión { $version }

View File

@@ -0,0 +1,5 @@
argparse-exclusive-flag-invalid = le sémaphore exclusif « { $flag } » est invalide
argparse-exclusive-flag-string-invalid = le sémaphore texte exclusif « { $flag_string } » est invalide
argparse-implicit-int-flag-already-defined = Le sémaphore implicitement entier « { $flag } » est déjà défini
argparse-invalid-option-spec = Option invalide « { $option_spec } » au caractère « { $bad_char } »
fish-version = { $package_name }, version { $version }

View File

@@ -0,0 +1,5 @@
argparse-exclusive-flag-invalid = 排他的なフラグ '{ $flag }' は有効ではありません
argparse-exclusive-flag-string-invalid = 排他的なフラグ文字列 '{ $flag_string }' は有効ではありません
argparse-implicit-int-flag-already-defined = 暗黙的な整数フラグ '{ $flag }' はすでに定義されています
argparse-invalid-option-spec = オプション仕様 '{ $option_spec }' の文字 '{ $bad_char }' が無効です
fish-version = { $package_name }, バージョン { $version }

View File

@@ -0,0 +1 @@
fish-version = { $package_name }, wersja { $version }

View File

@@ -0,0 +1 @@
fish-version = { $package_name }, versão { $version }

View File

@@ -0,0 +1 @@
fish-version = { $package_name }, version { $version }

View File

@@ -0,0 +1,5 @@
argparse-exclusive-flag-invalid = 排他标识 '{ $flag }' 无效
argparse-exclusive-flag-string-invalid = 排他标识字符串 '{ $flag_string }' 无效
argparse-implicit-int-flag-already-defined = 隐式整形标识 '{ $flag }' 已被定义
argparse-invalid-option-spec = 在字符 '{ $bad_char }' 处发现无效选项规范 '{ $option_spec }'
fish-version = { $package_name },版本 { $version }

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