Compare commits

..

247 Commits

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

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

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

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

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

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

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

Fixes #10300

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

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

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

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

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

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

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

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

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

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

While at it, group some imports.

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

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

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

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

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

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

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

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

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

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

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

Fix this by letting __fish_print_help access the terminal.

Fixes #12612

Solved by Claude in a single prompt:

---

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

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

[...]

● Now I can give you a clear answer.

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

  The deferred execution order

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

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

  Where the paths diverge

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

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

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

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

  The fix

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

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

  to:

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

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

✻ Sautéed for 13m 0s
2026-04-07 20:21:38 +08:00
Kayce Basques
86c3778c2a docs: Format path as inline code
Closes #12609
2026-04-07 18:52:36 +08:00
Nahor
d2653b7cac tmux-set.fish: fix spurious CI failure
Part of #12556
2026-04-07 17:49:57 +08:00
Johannes Altmanninger
cf16949ce7 contrib/debian/control: remove insufficient mdoc dependency
On Debian, mandoc provides "/usr/bin/mman", not "/usr/bin/man", so that
package alone is not enough.  Users that want to use mandoc could use a
package that "Provides: man", for example by creating symlink to "mman".

See https://github.com/fish-shell/fish-shell/issues/12596#issuecomment-4188332803
2026-04-07 17:49:57 +08:00
Daniel Rainer
85311546de refactor: extract fish-feature-flags crate
Another step in splitting up the main library crate.

Note that this change requires removing the `#[cfg(test)]` annotations
around the `LOCAL_OVERRIDE_STACK` code, because otherwise the code would
be removed in test builds for other packages, making the `#[cfg(test)]`
functions unusable from other packages, and functions with such feature
gates in their body would have the code guarded by these gates removed
in test builds for tests in other packages.

Closes #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
c44aa32a15 cleanup: remove syntactic dependency on main crate
This is done in preparation for extracting this file into its own crate.

Part of #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
65bc9b9e3e refactor: stop aliasing feature_test function
Having a public function named `test` is quite unspecific. Exporting it
both as `test` and `feature_test` results in inconsistent usage. Fix
this by renaming the function to `feature_test` and removing the alias.

Part of #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
8125f78a84 refactor: use override stack for feature tests
Several features of fish can be toggled at runtime (in practice at
startup). To keep track of the active features, `FEATURES`, an array of
`AtomicBool` is used. This can safely be shared across threads without
requiring locks.

Some of our tests override certain features to test behavior with a
specific value of the feature. Prior to this commit, they did this by
using thread-local versions of `FEATURES` instead of the process-wide
version used in non-test builds. This approach has two downsides:
- It does not allow nested overrides.
- It prevents using the code across package boundaries.
The former is a fairly minor issue, since I don't think we need nested
overrides. The latter prevents splitting up our large library crate,
since `#[cfg(test)]`-guarded code can only be used within a single
package.

To resolve these issues, a new approach to feature overrides in
tests is introduced in this commit: Instead of having a thread-local
version of `FEATURES`, all code, whether test or not, uses the
process-wide `FEATURES`. For non-test code, there is no change. For test
code, `FEATURES` is now also used. To override features in tests, a new
`with_overridden_feature` function is added, which replaces
`scoped_test` and `set`. It works by maintaining a thread-local stack of
feature overrides (`LOCAL_OVERRIDE_STACK`). The overridden `FeatureFlag`
and its new value are pushed to the stack, then the code for which the
override should be active is run, and finally the stack is popped again.
Feature tests now have to scan the stack for the first appearance of the
`FeatureFlag`, or use the value in `FEATURES` if the stack does not
contain the `FeatureFlag`. In most cases, the stack will be empty or
contain very few elements, so scanning it should not take long. For now,
it's only active in test code, so non-test code is unaffected. The plan
is to change this when the feature flag code is extracted from the main
library crate. This would slightly slow down feature tests in non-test
code, but there the stack will always be empty, since we only override
features in tests.

Part of #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
f0f48b4859 cleanup: stop needlessly exporting struct fields
Part of #12494
2026-04-07 17:49:57 +08:00
David Adam
a009f87630 build_tools: add sh script to build linux packages 2026-04-07 10:57:41 +08:00
David Adam
edb66d4d4e remove dput_cf_gen, not actually helpful 2026-04-07 10:57:41 +08:00
Nahor
f3f675b4cc Standardized error messages: constant names
Part of #12556
2026-04-05 13:15:47 +08:00
Nahor
434610494f argparse: fix error status code
To homogenize error reporting format, use a new Error struct. Currently this
is used for builtins and ensuring a common cmd/subcmd prefix.

Part of #12556
2026-04-05 13:15:47 +08:00
Dennis Yildirim
3cce1f3f4c Added completions for git verify-commit and verify-tag
Closes #12607
2026-04-05 13:14:31 +08:00
Johannes Altmanninger
a5bde7954e Update changelog 2026-04-05 13:07:29 +08:00
Nahor
99d63c21f1 Add tests to exercise all builtin error messages
With a few exceptions, only one test is added for a given message, even
when there are multiple ways to trigger the same message (e.g. different
invalid option combinations, or triggered in shared functions such as
`builtin_unknown_option`)

Includes a few very minor fixes, such as missing a newline, or using the
wrong var name.

Closes #12603
2026-04-05 00:22:42 +08:00
Nahor
c3e3658157 ulimit: remove unreachable error message
When there is no limit value, ulimit will have printed the current one
and exited

Part of #12603
2026-04-05 00:22:41 +08:00
Nahor
8d92016e72 string: error messages fixes
- fix wrong pattern used in `string replace` error message
- replace unreachable error with `unreachable!` in `string`
- fix cmd being used in place of subcmd 

Part of #12603
2026-04-05 00:22:41 +08:00
Nahor
f6a72b4e19 status: replace unreachable code with an assert!
Part of #12603
2026-04-05 00:22:40 +08:00
Nahor
2f9c2df10d set: report an error when called with -a,-p and no NAME
Previously executing `set -a` or `set -p` would just list all the
variables, which does not make sense since the user specifically ask
for an action (append/prepend).

Update the help page synopsis

Part of #12603
2026-04-05 00:22:40 +08:00
Johannes Altmanninger
0367aaea7d Disable relocatable tree logic when DATADIR or SYSCONFDIR are non-default
If all of

	$PREFIX/bin/fish
	$PREFIX/share/fish
	$PREFIX/etc/fish

exist, then fish assumes it's in a relocatable directory tree.
This is used by homebrew (PREFIX=/usr/local) and maybe also nix(?).

Other Linux distros prefer to use /etc/fish instead of $PREFIX/etc/fish
[1].  To do so, they need to pass -DCMAKE_INSTALL_SYSCONFDIR=/etc.
The relocatable tree logic assumes default data and sysconf dirs
(relative to a potentially relocatable prefix). If the user changes
any of those, and the relocatable tree logic happens to kick in,
that'd overrule user preference, which is surprising.

So a non-default data or sysconf path is a strong enough signal that
we want to disable the relocatable tree logic. Do that.

Closes #10748

[1]: ff2f69cd56/PKGBUILD (L43)
2026-04-04 01:22:54 +08:00
Johannes Altmanninger
e25b4b6f05 tests/checks/realpath.fish: fix on macOS 2026-04-03 23:26:37 +08:00
Yakov Till
90cbfd288e Fix test_history_path_detection panic: call test_init()
test_history_path_detection calls add_pending_with_file_detection(),
which spawns a thread pool task via ThreadPool::perform(). This
requires threads::init() to have been called, otherwise
assert_is_background_thread() panics.

Add the missing test_init() call, matching other tests that use
subsystems requiring initialization.

Closes #12604
2026-04-03 14:48:53 +08:00
Nahor
68453843d4 set: fix unreachable error messages
- Remove unreachable error message in `handle_env_return()`
While we could have put an empty block in `handle_env_return()` and
removed the condition on `NotFound` in `erase()`, we prefered to use
`unreachable!` in case `handle_env_return()` gets called in new scenarios
in the future

- Make reachable the error message when asking to show a slice

Part of #12603
2026-04-03 13:53:42 +08:00
Nahor
fb57f95391 realpath: fix random error message
With empty argument, `realpath` skips all processing, so the error
message, based on `errno`, was unrelated and changed depending on what
failed before. E.g:

```
$ builtin realpath "" /tmp "" /no-exist ""
builtin realpath: : Resource temporarily unavailable
/tmp
builtin realpath: : Invalid argument
/dont-exist
builtin realpath: : No such file or directory
```

Part of #12603
2026-04-03 13:53:41 +08:00
Nahor
1ed276292b read: remove unnecessary code
`to_stdout` is set to `true` if and only if `argv` is not empty.
- `argv` length and `to_stdout` are redundant, so we can remove `to_stdout`
- some tests in `validate_read_args` are necessarily false

Part of #12603
2026-04-03 13:53:41 +08:00
Branch Vincent
09e46b00cc completions: update ngrok
Closes #12598
2026-04-03 13:53:41 +08:00
Johannes Altmanninger
695bc293a9 contrib/debian/control: allow any "man" virtual pkg (man-db/mandoc)
We support multiple "man" implementations; at least man-db's and
mandoc's.

So we can relax the mandoc dependency to a dependency on the virtual
package providing "man". Note that as of today, "mandoc" fails to
have a "Provides: man".

However since Debian policy says in
https://www.debian.org/doc/debian-policy/ch-relationships.html

> To specify which of a set of real packages should be the default
> to satisfy a particular dependency on a virtual package, list the
> real package as an alternative before the virtual one.

we want to list possible real packages anyway, so do that.

Closes #12596
2026-04-03 12:20:08 +08:00
Nahor
344ff7be88 printf: remove unreachable code
Remove an unreachable, yet translated, error string and make the code
more idiomatic

Closes #12594
2026-04-01 09:37:57 +08:00
Nahor
014e3b3aff math: fix error message
Fix badly formatted error message, and make it translatable

Part of #12594
2026-04-01 09:37:57 +08:00
Nahor
68c7baff90 read: remove deprecation error for -i
`-i` has been an error and undocumented for 8 years now (86362e7) but
still requires some translating today. Time to retire it fully.

Part of #12594
2026-04-01 09:37:56 +08:00
Johannes Altmanninger
6eaad2cd80 Remove some redundant translation sources 2026-03-31 14:55:04 +08:00
Johannes Altmanninger
b321e38f5a psub: add missing line endings to error messages
Fixes #12593
2026-03-31 14:43:34 +08:00
Daniel Rainer
a32dd63163 fix: simplify and correct trimming of features
The previous handling was unnecessarily complex and had a bug introduced
by porting from C++ to Rust: The substrings `\0x0B` and `\0x0C` in Rust
mean `\0` (the NUL character) followed by the regular characters `0B`
and `0C`, respectively, so feature names starting or ending with these
characters would have these characters stripped away.

Replace this handling by built-in functionality, and simplify some
syntax. We now trim all whitespace, instead of just certain ASCII
characters, but I think there is no reason to limit trimming to ASCII.

Closes #12592
2026-03-31 14:43:34 +08:00
Bacal Mesfin
ef90afa5b9 feat: add completions for dnf copr
Closes #12585
2026-03-31 14:43:34 +08:00
r-vdp
7bd37dfe55 create_manpage_completions: handle groff \X'...' device control escapes
help2man 1.50 added \X'tty: link URL' hyperlink escapes to generated
man pages. coreutils 9.10 is the first widely-deployed package to ship
these, and it broke completion generation for most of its commands
(only 17/106 man pages parsed successfully).

The escape wraps option text like this:

  \X'tty: link https://example.com/a'\fB\-a, \-\-all\fP\X'tty: link'

Two places needed fixing:

- remove_groff_formatting() didn't strip \X'...', so Type1-4 parsers
  extracted garbage option names like "--all\X'tty"

- Deroffer.esc_char_backslash() didn't recognize \X, falling through
  to the generic single-char escape which stripped only the \, leaving
  "X'tty: link ...'" as literal text. Option lines then started with
  X instead of -, so TypeDeroffManParser's is_option() check failed.

Also handle \Z'...' (zero-width string) which has identical syntax.

Closes #12578
2026-03-31 13:15:52 +08:00
Johannes Altmanninger
14ce56d2a5 Remove unused fish_wcwidth wrapper 2026-03-30 13:57:27 +08:00
Johannes Altmanninger
01ee6f968d Support backward-word-end when cursor is past end
Closes #12581
2026-03-30 13:57:27 +08:00
Johannes Altmanninger
7f6dcde5e0 Fix backward-delete-char not stopping after control characters
Fixes 146384abc6 (Stop using wcwidth entirely, 2026-03-15)

Fixes #12583
2026-03-30 13:57:27 +08:00
Johannes Altmanninger
34fc573668 Modernize wcwidth API
Return None rather than -1 for nonprintables.  We probably still
differ from wcwidth which is bad (given we use the same name), but
hopefully not in a way that matters.

Fixes 146384abc6 (Stop using wcwidth entirely, 2026-03-15).
2026-03-30 13:57:10 +08:00
Johannes Altmanninger
93cbf2a0e8 Reuse wcswidth logic for rendered characters 2026-03-29 17:04:14 +08:00
Nahor
8194c6eb79 cd: replace unreachable code with assert
Closes #12584
2026-03-29 16:49:24 +08:00
Nahor
3194572156 bind: replace fake enum (c_int) with a real Rust enum
Part of #12584
2026-03-29 16:49:24 +08:00
Johannes Altmanninger
e635816b7f Fix Vi mode dl deleting from wrong character
Fixes b9b32ad157 (Fix vi mode dl and dh regressions, 2026-02-25).

Closes #12580
(which describes only the issue already fixed by b9b32ad157).
2026-03-28 21:45:33 +08:00
Johannes Altmanninger
2b3ecf22da start new cycle
Created by ./build_tools/release.sh 4.6.0
2026-03-28 13:16:34 +08:00
Johannes Altmanninger
c7ecc3bd78 Release 4.6.0
Created by ./build_tools/release.sh 4.6.0
2026-03-28 12:56:37 +08:00
Johannes Altmanninger
828a20ef30 Use cfg!(apple) 2026-03-28 12:41:58 +08:00
Fabian Boehm
7f5692dfd3 Skip ttyname on apple systems
Not relevant there because they don't have a "console session" as
such, and the ttyname call may hang.

Fixes #12506
2026-03-27 17:36:08 +01:00
Fabian Boehm
454939d5ab Update docs for fish_emoji_width defaulting to 2 2026-03-27 16:23:30 +01:00
Johannes Altmanninger
8756bc3afb Update changelog 2026-03-27 22:24:03 +08:00
Johannes Altmanninger
272f5dda83 Revert "CI: disable failing ubuntu-asan job"
This reverts commit 23ce9de1c3.

The compilation failure on Rust nightly was fixed in rust-shellexpand
commit b6173f0 (Rename WstrExt and WstrRefExt methods, 2026-02-23).
2026-03-27 22:23:08 +08:00
Johannes Altmanninger
dde33bab7e Clean up word-end special case handling in forward-word 2026-03-27 22:22:29 +08:00
David Adam
63f642c9dd CHANGELOG: log #12562 2026-03-27 22:21:08 +08:00
Fabian Boehm
146384abc6 Stop using wcwidth entirely
wcwidth isn't a great idea - it returns "-1" for anything it doesn't
know and non-printables, which can easily break text.

It is also unclear that it would be accurate to the system console,
and that's a minority use-case over using ssh to access older systems.

Additionally, it means we use one less function from libc and
simplifies the code.

Closes #12562
2026-03-27 21:31:19 +08:00
Fabian Boehm
8561008513 Unconditionally default emoji width to 2
"Emoji width" refers to the width of emoji codepoints. Since Unicode
9, they're classified as "wide" according to
TR11 (https://www.unicode.org/reports/tr11/).

Unicode 9 was released in 2016, and this slowly percolated into C
libraries and terminals. Glibc updated its default in 2.26, released
in August 2017.

Until now, we'd guess support for unicode 9 by checking the system
wcwidth function for an emoji - if it returned 2, we'd set our emoji
width to 2 as well.

However, that's a problem in the common case of using ssh to connect
to an old server - modern desktop OS, old server LTS OS, boom.

So now we instead just figure you've got a system that's *displaying*
the emoji that has been updated in the last 9 years.

In effect we're putting the burden on those who run old RHEL et al as
their client OS. They need to set $fish_emoji_width to 1.

Fixes #12500

Part of #12562
2026-03-27 21:31:19 +08:00
Remo Senekowitsch
88d01f7eb8 completions/cargo: avoid auto-installing toolchain via rustup
When cargo is installed via rustup, running cargo actually goes through
a proxy managed by rustup. This proxy determines the actual toolchain
to use, depending on environment variables, directory overrides etc. In
some cases, the proxy may automatically install the selected toolchain
if it's not yet installed, for example when first working on a project
that pins its rust toolchain via a `rust-toolchain.toml` file. In that
case, running cargo in the completion script can block the prompt for
a very long time. To avoid this, we instruct the rustup proxy not to
auto-install any toolchain with an environment variable.

Closes #12575
2026-03-27 20:11:22 +08:00
rohan436
4467822f6e docs: fix typo in MANPATH guidance string
Closes #12574
2026-03-27 18:55:38 +08:00
Johannes Altmanninger
5fe1cfb895 Bump initial Primary DA query timeout
Commit 7ef4e7dfe7 (Time out terminal queries after a while,
2025-09-21) though that "2 seconds ought to be enough for anyone".
But that's not true in practice: when rebooting a macOS system, it
can take longer. Let's see if 10 seconds is enough.  It should be fine
to have such a high timeout since this shouldn't happen in other cases.

Closes #12571
2026-03-26 16:31:24 +08:00
Daan De Meyer
484032fa9e Support SHELL_PROMPT_PREFIX, SHELL_PROMPT_SUFFIX, and SHELL_WELCOME
Add support for the SHELL_PROMPT_PREFIX, SHELL_PROMPT_SUFFIX, and
SHELL_WELCOME environment variables as standardized by systemd v257.

SHELL_PROMPT_PREFIX and SHELL_PROMPT_SUFFIX are automatically prepended
and appended to the left prompt at the shell level, so all prompts
(default, custom, and sample) pick them up without modification.

SHELL_WELCOME is displayed after the greeting when an interactive shell
starts.

These variables provide a standard interface for tools like systemd's
run0 to communicate session context to the shell.

Fixes https://github.com/fish-shell/fish-shell/issues/10924

Closes #12570
2026-03-26 15:45:50 +08:00
Johannes Altmanninger
fdd10ba9b2 Fix forward-word regression on single-char words followed by punctuation
The `forward-word` readline command on "a-a-a" is wrong (jumps to
"a"); on "aa-aa-aa" it's right (jumps to "-"); that's a regression
from bbb2f0de8d (feat(vi-mode): make word movements vi-compliant,
2026-01-10).

The is_word_end check for ForwardWordEmacs only tests for blank
(whitespace) after the current char. In the Punctuation style, words
also end before punctuation.

Fix this.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

See https://github.com/fish-shell/fish-shell/issues/12543#issuecomment-4125223455
2026-03-26 15:31:00 +08:00
Nahor
8679464689 color: prefer set_color --reset over set_color normal
`set_color normal` is too ambiguous and easily misinterpreted since
it actually reset all colors and modes instead of resetting just
the foreground color as one without prior knowledge might expect.

Closes #12548
2026-03-25 21:53:05 +08:00
Rodolfo Gatti
9ea760c401 Accept |& as an alias for &|
Closes #12565
2026-03-25 21:53:05 +08:00
Steffen Winter
d36c53c6e2 functions: dynamically query gentoo system paths
Use portageq to retrieve system paths instead of hardcoding them in.
This helps especially in Gentoo Prefix, where the installation is not
in / but rather offset inside a subdirectory (usually a users home
directory).

This only affects the "slow" path. When eix is installed it will be used
instead. It already accounts for Prefix installations.

Closes #12552
2026-03-25 21:53:05 +08:00
Steffen Winter
b047450cd0 functions: enable eix fast path for gentoo packages
The idea is taken from the deprecated/unused __fish_print_portage_packages.

Part of #12552
2026-03-25 21:53:05 +08:00
Johannes Altmanninger
0b93989080 fix ProcStatus::status_value in pipeline after ctrl-z
Repro (with default prompt):

	$ HOME=$PWD target/debug/fish -C '
	  function sleep_func; sleep 1; false; end
	  commandline "sleep 2 | sleep 3 | sleep 4 | sleep_func"
	  '
	Welcome to fish, the friendly interactive shell
	Type help for instructions on how to use fish
	johannes@e15 ~> sleep 2 | sleep 3 | sleep 4 | sleep_func
	^Zfish: Job 1, 'sleep 2 | sleep 3 | sleep 4 | s…' has stopped
	johannes@e15 ~ [0|SIGTSTP|SIGTSTP|1]> 

I'm not sure why the first sleep is not reported as stopped.

Co-authored-by: Lieuwe Rooijakkers <lieuwerooijakkers@gmail.com>

Fixes issue #12301

Closes #12550
2026-03-25 21:53:05 +08:00
rohan436
6b4ab05cbc docs: fix duplicated word in language docs
Closes #12554
2026-03-25 17:36:37 +08:00
Johannes Altmanninger
b5c367f8bf Bounds check backward-word-end
While at it, narrow the bounds check for forward movements,
no need to check for impossible cases.

Fixes #12555
2026-03-25 17:36:21 +08:00
Fabian Boehm
a6959aba2a Delete AGENTS.md again
This was an attempt at making vibecoded PRs slightly more palatable,
but it is itself an unpopular move.

Fixes #12526.
2026-03-19 18:41:36 +01:00
Noah Hellman
0a07e8dbdf pager: set col width only once
a bit hidden when column width is overwritten afterwards

Closes #12546
2026-03-16 16:13:21 +08:00
Noah Hellman
b2f350d235 pager: left-align descriptions
left aligned columns are usually easier to read in most cases (except for
e.g. numbers which descriptions are usually not)

Part of #12546
2026-03-16 16:13:21 +08:00
Noah Hellman
d1407cfde6 pager: add Column struct
to group column info, will add more

Part of #12546
2026-03-16 16:06:21 +08:00
Noah Hellman
f076aa1637 pager: take iterator for print_max
avoid unnecessary allocations just to create temporary wstrs

Part of #12546
2026-03-16 16:06:21 +08:00
Noah Hellman
96602f5096 pager tests: add multi-column tests
check alignment of descriptions

Part of #12546
2026-03-16 15:37:38 +08:00
Noah Hellman
a36fc0b316 pager tests: add completions helper
will be especially helpful for multi-completion tests

Part of #12546
2026-03-16 15:37:37 +08:00
Noah Hellman
d7f413bee9 add contrib/shell.nix
Closes #12544
2026-03-15 17:46:24 +08:00
Noah Hellman
81e9e0bd5c tests: use /bin/sh instead of /bin/ls, /bin/echo
/bin/ls and /bin/echo do not necessarily exist on all systems, e.g.
nixos.

/bin/sh should at least exist on more systems than /bin/ls and /bin/echo

Part of #12544
2026-03-15 17:46:24 +08:00
Noah Hellman
5c042575b0 highlight test: use /usr/bin/env instead of /bin/cat
/bin/cat doesn't exist on e.g. nixos, which only has /bin/sh and
/usr/bin/env.

/usr/bin/env should at least exist on more systems than /bin/cat

Part of #12544
2026-03-15 17:46:24 +08:00
Noah Hellman
a0d8d27f45 tests: use command instead of absolute paths
These binaries are not guaranteed to exist at /bin/X on all systems,
e.g. nixos does not place binaries there, but as long as they are in the
PATH we can find them with command.

Part of #12544
2026-03-15 17:46:24 +08:00
Johannes Altmanninger
d1d4cbf3f8 build_tools/update_translations.fish 2026-03-15 17:46:24 +08:00
Steffen Winter
7f41c8ba1f completions/coredumpctl: update for systemd 259
Closes #12545
2026-03-15 16:17:32 +08:00
Steffen Winter
c8989e1849 completions/run0: update for systemd 259
Part of #12545
2026-03-15 16:17:32 +08:00
Helen Chong
ae4e258884 chore(update-dependencies.sh): pin Catppuccin theme repository commit for fetching
See also https://github.com/catppuccin/fish/pull/41

Closes #12525
2026-03-15 16:14:39 +08:00
Nahor
3f0b4d38ff set_color: use only on/off for boolean options
This is done partly for consistency `underline` where we still need
`off` but where true/false doesn't make sense, and partly to simplify
user choices and the code.

See #12507

Closes #12541
2026-03-15 16:12:40 +08:00
t4k44
e9379904fb l10n: add Japanese translation
Closes #12499
2026-03-14 18:30:17 +01:00
Helen Chong
116671c577 docs(changelog): fix typo for Catppuccin color themes
Closes #12524
2026-03-13 15:04:05 +08:00
xtqqczze
53e1718a68 fix: pointer and signal handler casts
- avoid UB from `usize` → pointer cast
- use correct type for `sa_sigaction`

Closes #12488
2026-03-13 15:04:05 +08:00
Nahor
eab84c896e completions/docker: don't load if docker is not started
In WSL, if Docker is not started, the `docker` command is a script
that prints an error message to stdout instead of a valid script.

`docker.exe` is available and can return the completion script. However
any completion will end up calling that `docker` script anyway,
resulting further errors due to the unexpected output.

Closes #12538
2026-03-13 14:37:50 +08:00
JANMESH ARUN SHEWALE
ddf99b7063 Fix vi mode x and X keys to populate kill-ring
Closes #12420
Closes #12536
2026-03-13 14:29:06 +08:00
JANMESH ARUN SHEWALE
bcda4c5c4d fish_indent: preserve comments before brace blocks
Closes #12523
Closes #12505
2026-03-13 14:22:21 +08:00
rohan436
1244a47d86 docs: fix separator spelling in argparse docs
Closes #12533
2026-03-13 14:22:21 +08:00
Julien Gautier
4279a4f879 Update 'mount' completion script with the generated one, and update_translations
Closes #12531
2026-03-13 14:22:21 +08:00
Daniel Danner
efebb7bcdb docs/set: Fix mention of --export
Closes #12530
2026-03-13 14:22:21 +08:00
Daniel Rainer
226e818c25 resource usage: print KiB, not kb
The value shown is in KiB (2^{10} bytes), according to
`man 2 getrusage`, not kb (10^3 bits), so reflect this in the variable
name and output.

Closes #12529
2026-03-13 14:22:21 +08:00
Johannes Altmanninger
888e6d97f9 Address code review comments
See #12502
2026-03-13 14:22:21 +08:00
Nahor
eb7ea0ef9b set_color: add --foreground and --reset options
`--foreground` has two purposes:
- allow resetting the foreground color to its default, without also
resetting the other colors and modes
- improve readibility and unify the `set_color` arguments

`--reset` also has two purposes:
- provide a more intuitive way to reset the text formatting
- allow setting the colors and modes from a clean state without
requiring two calls to `set_color`

Part 3/3 of #12495

Closes #12507
2026-03-13 14:22:21 +08:00
Nahor
4e41d142fd set_color: allow resetting the underline style
Part 2/3 of #12495

Part of #12507
2026-03-13 14:22:00 +08:00
Nahor
a893dd10f4 set_color: allow resetting specific attributes
Add an optional `on`/`off`` value to italics/reverse/striketrough
to allow turning of the attribute without having to use the `normal`
color, i.e. reset the whole style

Part 1/3 of #12495

Part of #12507
2026-03-13 14:22:00 +08:00
Johannes Altmanninger
cba82a3c64 Remove code duplication 2026-03-13 14:22:00 +08:00
Nahor
6e8c32eb12 Remove the Default trait for text face/styling
The notion of default is context dependent: there is the default for
the state (default color, non-bold, no underline, ...) and the default
for a change (no color change, no underline change, ...).

Currently, using a single default works because either the style
attributes cannot be turned off individually (the user is expected
to reset to default then re-set necessary attributes), or the code
has special handling for specific scenarios (e.g. highlighting).

So in preparation for later commits, where attribute can be turned off
individually, make the two defaults explicit.

Part of #12507
2026-03-13 14:06:21 +08:00
Nahor
8ef9864c0c Cleaner fix for #[rustfmt::skip] on expressions
Part of #12507
2026-03-13 12:24:28 +08:00
Nahor
786ac339b8 Remove EnterStandoutMode
Now that terminfo has been removed, EnterStandoutMode is just a duplicate
of EnterReverseMode.

Part of #12507
2026-03-13 12:24:28 +08:00
Johannes Altmanninger
dbb6ae6cf5 Update changelog 2026-03-10 09:56:05 +08:00
Nahor
c4aa03a1fd complete: fix completion of commands starting with -
When trying to complete a command starting with `-`, and more
specifically when trying to get the description of possible commands,
the dash was interpreted as an option for `__fish_describe_command`,
resulting in an "unknown option" most of the time.

This is a regression introduced when adding option parsing to
`__fish_describe_command`

Fixes 7fc27e9e5 (cygwin: improve handling of `.exe` file extension, 2025-11-22)
Fixes #12510

Closes #12522
2026-03-10 09:56:05 +08:00
Nahor
e9340a3c43 CI: rebase MSYS2 dll
This fixes, or should make it less likely, spurious CI failures because
of:
`child_info_fork::abort: address space needed by <DLL> is already occupied`

The issue is that Unix `fork()`, given how it works, preserves
libraries' addresses. Windows does not have such a function, so Cygwin
needs emulate it by moving the libraries in a child process to match
the addresses in its parent. This leads to conflicts if Windows already
loaded something there.

As a workaround, Cygwin has a `rebase` application to assign specific
addresses to each DLL and forcing Windows to use those. This generally
fixes the issue (until a DLL is updated that is, but that's not
a concern for CI since everything is rebuilt from scratch every time).

In the case of #12515 though, the failing DLL is a temporary one built
during the compilation. So a rebase of MSYS2 packages will not quite
fix the problem. However, by moving other DLLs at specific locations,
it reduce the risk of collision to only be between the temporary ones.

Fixes #12515

Closes #12521
2026-03-10 09:56:05 +08:00
Volodymyr Chernetskyi
143a53d9c3 feat: add completions for tflint
Closes #12520
2026-03-10 09:56:05 +08:00
Volodymyr Chernetskyi
a282acf083 feat: add git completions for interpret-trailers
Closes #12519
2026-03-10 09:55:42 +08:00
Mike Yuan
cbd5d7640a functions/history: honor explicitly specified --color=
This partially reverts 324223ddff.

The offending commit broke the ability to set color mode via option
completely in interactive sessions.

Closes #12511
2026-03-09 17:15:55 +11:00
Delapouite
6f262afe8e doc: add link to not command from if command
It's very common to want to express negation in a `if` command.
Therefore a quick way to learn about the `not` command is handy.

Closes #12512
2026-03-09 17:15:55 +11:00
Daniel Rainer
310eba7156 cleanup: use nix version of getrusage
Change the behavior when `getrusage` fails. Previously, failure was
masked by using 0 values for everything. This is misleading. Instead, we
now panic on such failures, because they should never occur with our
usage of the function.

Closes #12502
2026-03-09 17:15:55 +11:00
Daniel Rainer
0223edc639 cleanup: use nix version of getpid
Alternatively, we could also use `nix::unistd::Pid::this()`.

Part of #12502
2026-03-09 16:52:08 +11:00
Daniel Rainer
6d0bb4a6b8 cleanup: replace custom umask wrapper by nix
Part of #12502
2026-03-09 16:52:08 +11:00
Daniel Rainer
7992fda9fe refactor: extract perror
Part of #12502
2026-03-09 16:52:08 +11:00
Daniel Rainer
bf5fa4f681 feat: implement perror_nix
Similar to `perror_io`, we don't need to make a libc call for `nix`
results, since the error variant contains the errno, from which a static
mapping to an error message exists. Avoid using `perror` and instead use
`perror_io` or `perror_nix` as appropriate where possible.

The `perror_io` and `perror_nix` functions could be combined by
implementing `fish_printf::ToArg` for `nix::errno::Errno`, but such a
function would violate type safety, as it would allow passing any
formattable argument, not necessarily limited to functions with a `%s`
formatting.

Part of #12502
2026-03-09 16:52:08 +11:00
Daniel Rainer
735f3ae6ad cleanup: remove obsolete wperror
Part of #12502
2026-03-09 16:52:08 +11:00
Daniel Rainer
131febed2a cleanup: remove unnecessary wstr usage
Part of #12502
2026-03-09 16:52:07 +11:00
Daniel Rainer
61dec20abd cleanup: inline wrename function
This function was only used in a single place and does not do anything
complicated, so inline it.

Part of #12502
2026-03-09 16:52:07 +11:00
Daniel Rainer
c0bb0d6584 refactor: extract write_to_fd into util crate
Part of #12502
2026-03-09 16:52:07 +11:00
Daniel Rainer
c5e4fed021 format: use 4-space indents in more files
Change some files which have lines whose indentation is not a multiple
of the 4 spaces specified in the editorconfig file.

Some of these changes are fixes or clear improvements (e.g. in Rust
macros which rustfmt can't format properly). Other changes don't clearly
improve the code style, and in some cases it might actually get worse.

The goal is to eventually be able to use our editorconfig for automated
style checks, but there are a lot of cases where conforming to the
limited editorconfig style spec does not make sense, so I'm not sure how
useful such automated checks can be.

Closes #12408
2026-03-09 16:52:07 +11:00
Daniel Rainer
1df7a8ba29 cleanup: remove obsolete ellipsis complexity
Previously, we chose the ellipsis character/string based on the locale.
We now assume a UTF-8 locale, and accordingly always use the Unicode
HORIZONTAL ELLIPSIS U+2026 `…`. When this was changed, some of the logic
for handling different ellipsis values was left behind. It no longer
serves a purpose, so remove it.

The functions returning constants are replaced by constants. Since the
ellipsis as a `wstr` is only used in a single file, make it a local
const there and define it via the `ELLIPSIS_CHAR` const.

Put the `ELLIPSIS_CHAR` definition into `fish-widestring`, removing the
dependency of `fish-wcstringutil` on `fish-common`, helping future
extraction efforts.

One localized message contains an ellipsis, which was inserted via a
placeholder, preventing translators from localizing it. Since the
ellipsis is a constant, put it directly into the localized string.

Closes #12493
2026-03-03 15:14:39 +11:00
Daniel Rainer
121b8fffa6 fix: version test on shallow, dirty git repo
In shallow, dirty git repo, the version identifier will look something
like `fish, version 4.5.0-g971e0b7-dirty`, with no commit counter
indicating the commits since the last version. Our regex did not handle
this case.

Make the commit counter optional, which also allows removing the second
alternative in the regex, since it's now subsumed by the first.

Fixes #12497

Closes #12498
2026-03-03 15:14:39 +11:00
Johannes Altmanninger
9c4190e40a Retry writes on signals other than INT/HUP
Fixes #12496
2026-03-03 15:14:39 +11:00
Daniel Rainer
f000149837 refactor: move FilenameRef into fish_common
Closes #12492
2026-03-03 15:14:39 +11:00
Daniel Rainer
f6f50df43d refactor: extract more from src/common.rs
This time, functions for decoding `wstr` into various types and the
`ToCString` trait are extracted.

Part of the wider goal of slimming down the main library to improve
incremental build performance and reduce dependency cycles.

Part of #12492
2026-03-03 15:14:39 +11:00
Daniel Rainer
29160a1592 gettext: support non-ASCII msgids for Rust
The `msguniq` call for deduplicating the msgids originating from Rust
previously did not get a header entry (empty msgid with msgstr
containing metadata). This works fine as long as all msgids are
ASCII-only. But when a non-ASCII character appears in a msgid, `msguniq`
errors out without a header specifying the encoding. To resolve this,
add the header to the input of this `msguniq` invocation and then remove
the header again using sed to prevent duplicating it for the outer
msguniq call at the end of the file.

Closes #12491
2026-03-03 15:14:39 +11:00
Julio Napurí
fcdcae72c5 l10n: add spanish translation
Closes #12489
2026-03-03 15:14:38 +11:00
Daniel Rainer
971e0b7d37 l10n: create common function for finding l10n/po dir
Reduce repetition and make it easier to relocate the directory. This
approach will also be useful for Fluent.

Closes #12484
2026-02-26 14:34:46 +11:00
Daniel Rainer
d6ac8a48c0 xtasks: make files_with_extension more accessible
This function is useful beyond the formatting module, so put it into the
top level of the library.

Closes #12483
2026-02-26 14:34:46 +11:00
Daniel Rainer
2ba031677f xtask: don't panic on failure
Panicking suggests that an assumption of our code was violated.
The current use of panics in xtasks is for expected failures, so it's
better to avoid panicking and instead just print the error message to
stderr and exit 1.

Closes #12482
2026-02-26 14:34:46 +11:00
Johannes Altmanninger
c15ea5d1e6 CI: deny unknown lints on Rust stable
Closes #12334
2026-02-25 18:22:48 +11:00
Johannes Altmanninger
78f3c95641 Reliably suppress warning about unknown Rust lints
Lint table order is unspecified, leading to spurious "unknown
lint" errors which ought to have been suppressed, see
https://github.com/rust-lang/cargo/issues/16518

From https://rust-lang.github.io/rfcs/3389-manifest-lint.html

> lower (particularly negative) numbers ... show up first on the
> command-line to tools like rustc

So we can use the priority property to make sure that unknown lints
are suppressed before rustc processes the other lint specifications.

Part of #12334
2026-02-25 18:22:48 +11:00
Aditya Giri
149fec8f02 string: accept --char alias for pad and shorten
Closes #12460
2026-02-25 18:18:21 +11:00
Daniel Rainer
1b0fa8f804 unicode: use new decoded_width function
For now, only add it in a single place. There are more instances where
width calculation could be improved, but this one has already been
converted to use the `unicode-width` crate before, so conversion is easy
and a strict improvement.

Closes #12457
2026-02-25 18:18:21 +11:00
Daniel Rainer
c38dd1f420 unicode: add function for width computation
Accurately computing the width of arbitrary strings is a non-trivial
problem. We outsource the logic for it to the `unicode-width` crate. But
directly passing our PUA-encoded strings to the crate would give
incorrect results whenever a PUA codepoint is encoded in our string,
since one input PUA codepoint is converted into 3 consecutive codepoints
in our encoding. Therefore, we need to decode before performing width
calculations. Our regular decoding decodes to raw bytes, which is
incompatible with the `unicode-width` crate, since it expects `char`s,
and the decoded bytes could be invalid UTF-8, making their width
undefined. We tackle this problem by building a custom iterator which
does on-the-fly decoding. Encoded PUA codepoints are turned back into
the original codepoints, and any other PUA-encoded bytes are replaced by
one replacement character (U+FFFD) per byte. The latter is not necessary
since PUA codepoints have a defined width of 1, so we could also forward
the PUA-encoded bytes which encode invalid UTF-8 input instead of
inserting the replacement character. The choice to use the replacement
character is made to avoid producing a char sequence where some PUA
codepoints represent themselves, whereas others still encode non-UTF-8
bytes. Such a mix of semantics would be confusing if the char sequence
is ever used for anything else. Replacement characters make it clear
that there are no remaining encoded semantics. Note that using the char
sequences produced in this way for any purpose other than width
computation is not intended. For output, our pre-existing decoding to
bytes should be used, which allows preserving non-UTF-8 bytes.

The implementation of the iterator is not entirely straightforward,
since we need to read up to 3 chars to be able to decide whether we have
an encoded PUA character. Therefore, we need to cache some chars across
invocations of the iterator's `next` and `next_back` invocations. This
is done via a custom buffer struct, which does not require dynamic
allocations.

The tests for the new functionality are only in the main crate because
the encoding function is not available in the `fish-widestring` crate.
Once that is resolved, the tests should be moved.

Part of #12457
2026-02-25 18:18:21 +11:00
Johannes Altmanninger
b9b32ad157 Fix vi mode dl and dh regressions
Also improve AGENTS.md though we should totally point to
CONTRIBUTING.rst instead.

Fixes #12461
2026-02-25 18:18:21 +11:00
Janne Pulkkinen
45ac6472e2 completions/protontricks: update options
New options were added and the help text updated for the old to better
tell them apart.

Closes #12477
2026-02-25 18:18:21 +11:00
Janne Pulkkinen
9235c5de6c completions/protontricks: use new flag for complete
`protontricks -l` will launch a graphical prompt to choose Steam
installation if multiple installations are found. `-L/--list-all`
is a new flag introduced in 1.14.0 that retrieves all games without user
interaction.

Also silence stderr, since it can cause warning messages to be printed.

Part of #12477
2026-02-25 16:47:54 +11:00
Johannes Altmanninger
6c091dbaf4 Remove spurious intermediate ⏎ symbol on prompt redraw
Commit 7ac9ce7ffb (Reduce the number of escape sequences for text
styles, 2026-02-06) includes a bad merge conflict resolution of
a conflict with 38513de954 (Remove duplicated code introduced in
commit 289057f, 2026-02-07). Fix that.

Fixes #12476
2026-02-25 16:47:25 +11:00
Daniel Rainer
3e7e57945c format: replace style.fish by xtask
Replace the `build_tools/style.fish` script by an xtask. This eliminates
the need for a fish binary for performing the formatting/checking. The
`fish_indent` binary is still needed. Eventually, this should be made
available as a library function, so the xtask can use that instead of
requiring a `fish_indent` binary in the `$PATH`.

The new xtask is called `format` rather than `style`, because that's a
more fitting description of what it does (and what the script it
replaces did).

The old script's behavior is not replicated exactly:
- Specifying `--all` and explicit paths is supported within a single
  invocation.
- Explicit arguments no longer have to be files. If a directory is
  specified, all files within it will be considered.
- The git check for un-staged changes is no longer filtered by file
  names, mainly to simplify the implementation.
- A warning is now printed if neither the `--all` flag nor a path are
  provided as arguments. The reason for this is that one might assume
  that omitting these arguments would default to formatting everything
  in the current directory, but instead no formatting will happen in
  this case.
- The wording of some messages is different.

The design of the new code tries to make it easy to add formatters for
additional languages, or change the ones we already have. This is
achieved by separating the code into one function per language, which
can be modified without touching the code for the other languages.
Adding support for a new formatter/language only requires adding a
function which builds the formatter command line based on the arguments
to the xtask, and calling that function from the main `format` function.

Closes #12467
2026-02-24 16:33:04 +11:00
Johannes Altmanninger
23ce9de1c3 CI: disable failing ubuntu-asan job
The rust-shellexpand dependency (via rust-embed)
fails to build on nightly Rust; Fix is at
https://gitlab.com/ijackson/rust-shellexpand/-/merge_requests/19
2026-02-23 15:49:34 +11:00
exploide
6aee7bf378 completions/ip: complete netns for ip l set dev netns, plus some option arguments
Closes #12464
2026-02-23 14:25:09 +11:00
David Adam
5b360238b2 dput_cf_gen: drop -x flag, only used for development 2026-02-21 10:58:12 +08:00
David Adam
5b44c9668f add script to generate a dput.cf for Ubuntu PPA uploads 2026-02-21 10:38:24 +08:00
Peter Ammon
d01a403c65 Cleanup ParserTestErrorBits harder
We don't need this bitwise operator override.
2026-02-17 19:13:13 -08:00
Peter Ammon
b65649725e Cleanup ParserTestErrorBits
This was a weird type that made sense in C++ but not so much in Rust.
Let's clean this up.
2026-02-16 22:29:30 -08:00
Johannes Altmanninger
89c2f1bd6b start new cycle
Created by ./build_tools/release.sh 4.5.0
2026-02-17 11:54:05 +11:00
Johannes Altmanninger
3478f78a05 Release 4.5.0
Created by ./build_tools/release.sh 4.5.0
2026-02-17 11:32:33 +11:00
Johannes Altmanninger
19cedb01bc changelog 2026-02-17 11:31:17 +11:00
xtqqczze
cb24c4a863 rust: use const_locks feature
Closes #12454
2026-02-17 11:28:58 +11:00
xtqqczze
93478e7c51 simplify serialize_with_vars
Closes #12453
2026-02-17 11:28:58 +11:00
xtqqczze
4eac5f4d9d clippy: fix unused_trait_names lint
https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names

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

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

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

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

Fixes #12444

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

fixes #11307

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

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

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

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

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

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

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

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

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

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

We should get rid of forward-jump implementation.

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

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

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

Fix that.

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

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

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

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

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

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

Signed-off-by: Tristan Partin <tristan@partin.io>
2026-02-05 07:14:21 +01:00
David Adam
6d60eaf04e CMake: use cargo variable to call cargo
Fixes the build where a custom cargo binary is passed in.
2026-02-03 11:10:34 +08:00
Johannes Altmanninger
7c1d8cf4f1 start new cycle
Created by ./build_tools/release.sh 4.4.0
2026-02-03 12:39:46 +11:00
398 changed files with 179480 additions and 14268 deletions

View File

@@ -21,7 +21,7 @@ indent_size = 4
[build_tools/release.sh]
max_line_length = 72
[{Dockerfile,Vagrantfile}]
[Vagrantfile]
indent_size = 2
[share/{completions,functions}/**.fish]

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,16 +9,16 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
with:
components: rustfmt
- name: install dependencies
run: pip install ruff
- name: build fish
run: cargo build
run: cargo build --bin fish_indent
- name: check format
run: PATH="target/debug:$PATH" build_tools/style.fish --all --check
run: PATH="target/debug:$PATH" cargo xtask format --all --check
- name: check rustfmt
run: find build.rs crates src -type f -name '*.rs' | xargs rustfmt --check
@@ -35,22 +35,31 @@ jobs:
- rust_version: "msrv"
features: ""
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: ${{ matrix.rust_version }}
components: clippy
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: |
sudo apt install gettext
- name: Patch Cargo.toml to deny unknown lints
run: |
if [ "${{ matrix.rust_version }}" = stable ]; then
sed -i /^rust.unknown_lints/d Cargo.toml
fi
- name: cargo clippy
run: cargo clippy --workspace --all-targets ${{ matrix.features }} -- --deny=warnings
rustdoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: |
sudo apt install gettext

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,94 @@
fish ?.?.? (released ???)
=========================
Notable improvements and fixes
------------------------------
Deprecations and removed features
---------------------------------
Interactive improvements
------------------------
Improved terminal support
-------------------------
Other improvements
------------------
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
- ``fish_update_completions`` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
For distributors and developers
-------------------------------
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden with ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
Regression fixes:
-----------------
fish 4.6.0 (released March 28, 2026)
====================================
Notable improvements and fixes
------------------------------
- New Spanish translations (:issue:`12489`).
- New Japanese translations (:issue:`12499`).
Deprecations and removed features
---------------------------------
- The default width for emoji is switched from 1 to 2, improving the experience for users connecting to old systems from modern desktops. Users of old desktops who notice that lines containing emoji are misaligned can set ``$fish_emoji_width`` back to 1 (:issue:`12562`).
Interactive improvements
------------------------
- The tab completion pager now left-justifies the description of each column (:issue:`12546`).
- fish now supports the ``SHELL_PROMPT_PREFIX``, ``SHELL_PROMPT_SUFFIX``, and ``SHELL_WELCOME`` environment variables. The prefix and suffix are automatically prepended and appended to the left prompt, and the welcome message is displayed on startup after the greeting.
These variables are set by systemd's ``run0`` for example (:issue:`10924`).
Improved terminal support
-------------------------
- ``set_color`` is able to turn off italics, reverse mode, strikethrough and underline individually (e.g. ``--italics=off``).
- ``set_color`` learned the foreground (``--foreground`` or ``-f``) and reset (``--reset``) options.
- An error caused by slow terminal responses at macOS startup has been addressed (:issue:`12571`).
Other improvements
------------------
- Signals like ``SIGWINCH`` (as sent on terminal resize) no longer interrupt builtin output (:issue:`12496`).
- For compatibility with Bash, fish now accepts ``|&`` as alternate spelling of ``&|``, for piping both standard output and standard error (:issue:`11516`).
- ``fish_indent`` now preserves comments and newlines immediately preceding a brace block (``{ }``) (:issue:`12505`).
- A crash when suspending certain pipelines with :kbd:`ctrl-z` has been fixed (:issue:`12301`).
For distributors and developers
-------------------------------
- ``cargo xtask`` subcommands no longer panic on test failures.
Regression fixes:
-----------------
- (from 4.5.0) Intermediate ```` artifact when redrawing prompt (:issue:`12476`).
- (from 4.4.0) ``history`` honors explicitly specified ``--color=`` again (:issue:`12512`).
- (from 4.4.0) Vi mode ``dl`` and ``dh`` (:issue:`12461`).
- (from 4.3.0) Error completing of commands starting with ``-`` (:issue:`12522`).
fish 4.5.0 (released February 17, 2026)
=======================================
This is mostly a patch release for Vi mode regressions in 4.4.0 but other minor behavior changes are included as well.
Interactive improvements
------------------------
- :kbd:`ctrl-l` no longer cancels history search (:issue:`12436`).
- History search cursor positioning now works correctly with characters of arbitrary width.
Deprecations and removed features
---------------------------------
- fish no longer reads the terminfo database to alter behaviour based on the :envvar:`TERM` environment variable, and does not depend on ncurses or terminfo. The ``ignore-terminfo`` feature flag, introduced and enabled by default in fish 4.1, is now permanently enabled. fish may no longer work correctly on Data General Dasher D220 and Wyse WY-350 terminals, but should continue to work on all known terminal emulators released in the 21st century.
Regression fixes:
-----------------
- (from 4.4.0) Vi mode ``d,f`` key binding did not work (:issue:`12417`).
- (from 4.4.0) Vi mode ``c,w`` key binding wrongly deleted trailing spaces (:issue:`12443`).
- (from 4.4.0) Vi mode crash on ``c,i,w`` after accepting autosuggestion (:issue:`12430`).
- (from 4.4.0) ``fish_vi_key_bindings`` called with a mode argument produced an error (:issue:`12413`).
- (from 4.0.0) Build on Illumos (:issue:`12410`).
fish 4.4.0 (released February 03, 2026)
=======================================
@@ -15,9 +106,9 @@ Interactive improvements
New or improved bindings
------------------------
- Vi mode word movements (``w``, ``W``, ``e``, and ``E``) are now largely in line with Vim. The only exception is that underscores are treated as word separators (:issue:`12269`).
- New special input functions to support these movements: ``forward-word-vi``, ``kill-word-vi``, ``forward-bigword-vi``, ``kill-bigword-vi``, ````forward-word-end``, ``backward-word-end``, ``forward-bigword-end``, ``backward-bigword-end``, ````kill-a-word``, ``kill-inner-word``, ``kill-a-bigword``, and ``kill-inner-bigword``.
- New special input functions to support these movements: ``forward-word-vi``, ``kill-word-vi``, ``forward-bigword-vi``, ``kill-bigword-vi``, ``forward-word-end``, ``backward-word-end``, ``forward-bigword-end``, ``backward-bigword-end``, ``kill-a-word``, ``kill-inner-word``, ``kill-a-bigword``, and ``kill-inner-bigword``.
- Vi mode key bindings now support counts for movement and deletion commands (e.g. `d3w` or `3l`), via a new operator mode (:issue:`2192`).
- New ``catpuccin-*`` color themes.
- New ``catppuccin-*`` color themes.
Improved terminal support
-------------------------

View File

@@ -53,36 +53,36 @@ add_definitions(-DCMAKE_SOURCE_DIR="${REAL_CMAKE_SOURCE_DIR}")
set(build_types Release RelWithDebInfo Debug "")
if(NOT "${CMAKE_BUILD_TYPE}" IN_LIST build_types)
message(WARNING "Unsupported build type ${CMAKE_BUILD_TYPE}. If this doesn't build, try one of Release, RelWithDebInfo or Debug")
message(WARNING "Unsupported build type ${CMAKE_BUILD_TYPE}. If this doesn't build, try one of Release, RelWithDebInfo or Debug")
endif()
add_custom_target(
fish ALL
COMMAND
"${CMAKE_COMMAND}" -E
env ${VARS_FOR_CARGO}
${Rust_CARGO}
build --bin fish
$<$<CONFIG:Release>:--release>
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
--target ${Rust_CARGO_TARGET}
--no-default-features
--features=${FISH_CARGO_FEATURES}
${CARGO_FLAGS}
&&
"${CMAKE_COMMAND}" -E
copy "${rust_target_dir}/${rust_profile}/fish" "${CMAKE_CURRENT_BINARY_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
USES_TERMINAL
fish ALL
COMMAND
"${CMAKE_COMMAND}" -E
env ${VARS_FOR_CARGO}
${Rust_CARGO}
build --bin fish
$<$<CONFIG:Release>:--release>
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
--target ${Rust_CARGO_TARGET}
--no-default-features
--features=${FISH_CARGO_FEATURES}
${CARGO_FLAGS}
&&
"${CMAKE_COMMAND}" -E
copy "${rust_target_dir}/${rust_profile}/fish" "${CMAKE_CURRENT_BINARY_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
USES_TERMINAL
)
function(CREATE_LINK target)
add_custom_target(
${target} ALL
DEPENDS fish
COMMAND ln -f fish ${target}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
)
add_custom_target(
${target} ALL
DEPENDS fish
COMMAND ln -f fish ${target}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
)
endfunction(CREATE_LINK)
# Define fish_indent.

View File

@@ -133,12 +133,12 @@ For formatting, we use:
- ``fish_indent`` (shipped with fish) for fish script
- ``ruff format`` for Python
To reformat files, there is a script
To reformat files, there is an xtask
::
build_tools/style.fish --all
build_tools/style.fish somefile.rs some.fish
cargo xtask format --all
cargo xtask format somefile.rs some.fish
Fish Script Style Guide
-----------------------

139
Cargo.lock generated
View File

@@ -260,7 +260,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fish"
version = "4.4.0"
version = "4.6.0"
dependencies = [
"assert_matches",
"bitflags",
@@ -272,6 +272,7 @@ dependencies = [
"fish-color",
"fish-common",
"fish-fallback",
"fish-feature-flags",
"fish-gettext",
"fish-gettext-extraction",
"fish-gettext-mo-file-parser",
@@ -290,13 +291,12 @@ dependencies = [
"num-traits",
"once_cell",
"pcre2",
"phf_codegen 0.13.1",
"phf_codegen",
"portable-atomic",
"rand 0.9.2",
"rand",
"rsconf",
"rust-embed",
"serial_test",
"terminfo",
"unix_path",
"xterm-color",
]
@@ -329,9 +329,12 @@ name = "fish-common"
version = "0.0.0"
dependencies = [
"bitflags",
"fish-build-helper",
"fish-feature-flags",
"fish-widestring",
"libc",
"nix",
"rsconf",
]
[[package]]
@@ -347,12 +350,19 @@ dependencies = [
"widestring",
]
[[package]]
name = "fish-feature-flags"
version = "0.0.0"
dependencies = [
"fish-widestring",
]
[[package]]
name = "fish-gettext"
version = "0.0.0"
dependencies = [
"fish-gettext-maps",
"phf 0.13.1",
"phf",
]
[[package]]
@@ -370,8 +380,8 @@ version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-gettext-mo-file-parser",
"phf 0.13.1",
"phf_codegen 0.13.1",
"phf",
"phf_codegen",
"rsconf",
]
@@ -395,15 +405,18 @@ name = "fish-tempfile"
version = "0.0.0"
dependencies = [
"nix",
"rand 0.9.2",
"rand",
]
[[package]]
name = "fish-util"
version = "0.0.0"
dependencies = [
"errno",
"fish-widestring",
"rand 0.9.2",
"libc",
"nix",
"rand",
]
[[package]]
@@ -411,7 +424,6 @@ name = "fish-wcstringutil"
version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-common",
"fish-fallback",
"fish-widestring",
"rsconf",
@@ -434,15 +446,11 @@ version = "0.0.0"
name = "fish-widestring"
version = "0.0.0"
dependencies = [
"libc",
"unicode-width",
"widestring",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -599,12 +607,6 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.31.1"
@@ -617,16 +619,6 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -703,32 +695,13 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_shared 0.13.1",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"phf_shared",
]
[[package]]
@@ -737,18 +710,8 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
dependencies = [
"phf_generator 0.13.1",
"phf_shared 0.13.1",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared 0.11.3",
"rand 0.8.5",
"phf_generator",
"phf_shared",
]
[[package]]
@@ -758,16 +721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
"phf_shared 0.13.1",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
"phf_shared",
]
[[package]]
@@ -824,15 +778,6 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
@@ -840,7 +785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core 0.9.5",
"rand_core",
]
[[package]]
@@ -850,15 +795,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.5"
@@ -1045,9 +984,9 @@ dependencies = [
[[package]]
name = "shellexpand"
version = "3.1.1"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8"
dependencies = [
"dirs",
]
@@ -1087,18 +1026,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "terminfo"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
dependencies = [
"fnv",
"nom",
"phf 0.11.3",
"phf_codegen 0.11.3",
]
[[package]]
name = "thiserror"
version = "2.0.18"
@@ -1235,9 +1162,11 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
name = "xtask"
version = "0.0.0"
dependencies = [
"anstyle",
"clap",
"fish-build-helper",
"fish-tempfile",
"walkdir",
]
[[package]]

View File

@@ -11,6 +11,7 @@ repository = "https://github.com/fish-shell/fish-shell"
license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
[workspace.dependencies]
anstyle = "1.0.13"
assert_matches = "1.5.0"
bitflags = "2.5.0"
cc = "1.0.94"
@@ -22,6 +23,7 @@ fish-build-man-pages = { path = "crates/build-man-pages" }
fish-color = { path = "crates/color" }
fish-common = { path = "crates/common" }
fish-fallback = { path = "crates/fallback" }
fish-feature-flags = { path = "crates/feature-flags" }
fish-gettext = { path = "crates/gettext" }
fish-gettext-extraction = { path = "crates/gettext-extraction" }
fish-gettext-maps = { path = "crates/gettext-maps" }
@@ -39,7 +41,17 @@ libc = "0.2.177"
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
# files as of 22 April 2024.
lru = "0.16.2"
nix = { version = "0.31.1", default-features = false, features = ["event", "fs", "inotify", "hostname", "resource", "process", "signal", "term", "user"] }
nix = { version = "0.31.1", default-features = false, features = [
"event",
"fs",
"inotify",
"hostname",
"resource",
"process",
"signal",
"term",
"user",
] }
num-traits = "0.2.19"
once_cell = "1.19.0"
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
@@ -62,12 +74,11 @@ rust-embed = { version = "8.11.0", features = [
"interpolate-folder-path",
] }
serial_test = { version = "3", default-features = false }
# We need 0.9.0 specifically for some crash fixes.
terminfo = "0.9.0"
widestring = "1.2.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"
unix_path = "1.0.1"
walkdir = "2.5.0"
xterm-color = "1.0.1"
[profile.release]
@@ -80,7 +91,7 @@ debug = true
[package]
name = "fish"
version = "4.4.0"
version = "4.6.0"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
@@ -98,6 +109,7 @@ fish-build-man-pages = { workspace = true, optional = true }
fish-color.workspace = true
fish-common.workspace = true
fish-fallback.workspace = true
fish-feature-flags.workspace = true
fish-gettext = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true }
fish-printf.workspace = true
@@ -116,7 +128,6 @@ num-traits.workspace = true
once_cell.workspace = true
pcre2.workspace = true
rand.workspace = true
terminfo.workspace = true
xterm-color.workspace = true
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
@@ -184,7 +195,7 @@ tsan = []
[workspace.lints]
rust.non_camel_case_types = "allow"
rust.non_upper_case_globals = "allow"
rust.unknown_lints = "allow"
rust.unknown_lints = { level = "allow", priority = -1 }
rust.unstable_name_collisions = "allow"
rustdoc.private_intra_doc_links = "allow"
@@ -194,7 +205,7 @@ cloned_instead_of_copied = "warn"
explicit_into_iter_loop = "warn"
format_push_string = "warn"
implicit_clone = "warn"
len_without_is_empty = "allow" # we're not a library crate
len_without_is_empty = "allow" # we're not a library crate
let_and_return = "allow"
manual_assert = "warn"
manual_range_contains = "allow"
@@ -207,7 +218,9 @@ ptr_offset_by_literal = "warn"
ref_option = "warn"
semicolon_if_nothing_returned = "warn"
stable_sort_primitive = "warn"
str_to_string = "warn"
unnecessary_semicolon = "warn"
unused_trait_names = "warn"
# We do not want to use the e?print(ln)?! macros.
# These lints flag their use.

View File

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

View File

@@ -159,16 +159,14 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
}
let prefix = overridable_path("PREFIX", |env_prefix| {
Some(PathBuf::from(
env_prefix.unwrap_or("/usr/local".to_string()),
))
Some(PathBuf::from(env_prefix.unwrap_or("/usr/local".to_owned())))
})
.unwrap();
overridable_path("SYSCONFDIR", |env_sysconfdir| {
Some(join_if_relative(
&prefix,
env_sysconfdir.unwrap_or("/etc/".to_string()),
env_sysconfdir.unwrap_or("/etc/".to_owned()),
))
});
@@ -199,5 +197,5 @@ fn get_version() -> String {
)
.unwrap()
.trim_ascii_end()
.to_string()
.to_owned()
}

View File

@@ -8,11 +8,29 @@ if [ "$FISH_CHECK_LINT" = false ]; then
lint=false
fi
case "$(uname)" in
MSYS*)
is_cygwin=true
cygwin_var=MSYS
;;
CYGWIN*)
is_cygwin=true
cygwin_var=CYGWIN
;;
*)
is_cygwin=false
;;
esac
check_dependency_versions=false
if [ "${FISH_CHECK_DEPENDENCY_VERSIONS:-false}" != false ]; then
check_dependency_versions=true
fi
green='\e[0;32m'
yellow='\e[1;33m'
reset='\e[m'
if $check_dependency_versions; then
command -v curl
command -v jq
@@ -78,17 +96,50 @@ if $lint; then
if command -v cargo-deny >/dev/null; then
cargo deny --all-features --locked --exclude-dev check licenses
fi
PATH="$build_dir:$PATH" "$workspace_root/build_tools/style.fish" --all --check
PATH="$build_dir:$PATH" cargo xtask format --all --check
for features in "" --no-default-features; do
cargo clippy --workspace --all-targets $features
done
fi
cargo test --no-default-features --workspace --all-targets
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
# are dynamically linked against Rust's `std-xxx.dll` instead of being
# statically link as they usually are.
# On Cygwin, `PATH`is not properly updated to point to the `std-xxx.dll`
# location, so we have to do it manually.
# See:
# - https://github.com/rust-lang/rust/issues/149050
# - https://github.com/msys2/MSYS2-packages/issues/5784
(
if $is_cygwin; then
export PATH="$PATH:$(rustc --print target-libdir)"
fi
cargo test --no-default-features --workspace --all-targets
)
cargo test --doc --workspace
if $lint; then
cargo doc --workspace --no-deps
fi
FISH_GETTEXT_EXTRACTION_DIR=$gettext_template_dir "$workspace_root/tests/test_driver.py" "$build_dir"
# Using "()" not "{}" because we do want a subshell (for the export)
system_tests() (
[ -n "$@" ] && export "$@"
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
"$workspace_root/tests/test_driver.py" "$build_dir"
)
test_cmd='FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir" "$workspace_root/tests/test_driver.py" "$build_dir"'
if $is_cygwin; then
echo -e "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}"
system_tests $cygwin_var=winsymlinks
echo -e "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}"
system_tests $cygwin_var=winsymlinks
else
echo -e "=== Running ${green}integration tests${reset}"
system_tests
fi
exit
}

View File

@@ -12,12 +12,8 @@ begin
# Note that this results in the file being overwritten.
# This is desired behavior, to get rid of the results of prior invocations
# of this script.
begin
echo 'msgid ""'
echo 'msgstr ""'
echo '"Content-Type: text/plain; charset=UTF-8\n"'
echo ""
end
set -l header 'msgid ""\nmsgstr "Content-Type: text/plain; charset=UTF-8\\\\n"\n\n'
printf $header
set -g workspace_root (path resolve (status dirname)/..)
@@ -41,8 +37,15 @@ begin
mark_section tier1-from-rust
# Get rid of duplicates and sort.
find $rust_extraction_dir -type f -exec cat {} + | msguniq --no-wrap --sort-output
or exit 1
begin
# Without providing this header, msguniq complains when a msgid is non-ASCII.
printf $header
find $rust_extraction_dir -type f -exec cat {} +
end |
msguniq --no-wrap --sort-output |
# Remove the header again. Otherwise it would appear twice, breaking the msguniq at the end
# of this file.
sed '/^msgid ""$/ {N; /\nmsgstr "Content-Type: text\/plain; charset=UTF-8\\\\n"$/ {N; d}}'
if not set -l --query _flag_use_existing_template
rm -r $rust_extraction_dir

View File

@@ -0,0 +1,83 @@
#!/bin/sh
# This script takes a source tarball (from build_tools/make_tarball.sh) and a vendor tarball (from
# build_tools/make_vendor_tarball.sh, generated if not present), and produces:
# * Appropriately-named symlinks to look like a Debian package
# * Debian .changes and .dsc files with plain names ($version-1) and supported Ubuntu prefixes
# ($version-1~somedistro)
# * An RPM spec file
# By default, input and output files go in ~/fish_built, but this can be controlled with the
# FISH_ARTEFACT_PATH environment variable.
{
set -e
version=$1
[ -n "$version" ] || { echo "Version number required as argument" >&2; exit 1; }
[ -n "$DEB_SIGN_KEYID$DEB_SIGN_KEYFILE" ] ||
echo "Warning: neither DEB_SIGN_KEYID or DEB_SIGN_KEYFILE environment variables are set; you
will need a signing key for the author of the most recent debian/changelog entry." >&2
workpath=${FISH_ARTEFACT_PATH:-~/fish_built}
source_tarball="$workpath"/fish-"$version".tar.xz
vendor_tarball="$workpath"/fish-"$version"-vendor.tar.xz
[ -e "$source_tarball" ] || { echo "Missing source tarball, expected at $source_tarball" >&2; exit 1; }
cd "$workpath"
# Unpack the sources
tar xf "$source_tarball"
sourcepath="$workpath"/fish-"$version"
# Generate the vendor tarball if it is not already present
[ -e "$vendor_tarball" ] || (cd "$sourcepath"; build_tools/make_vendor_tarball.sh;)
# This step requires network access, so do it early in case it fails
# sh has no real array support
ubuntu_versions=$(uv run --script "$sourcepath"/build_tools/supported_ubuntu_versions.py)
# Write the specfile
[ -e "$workpath"/fish.spec ] && { echo "Cowardly refusing to overwite an existing fish.spec" >&2;
exit 1; }
rpmversion=$(echo "$version" |sed -e 's/-/+/' -e 's/-/./g')
sed -e "s/@version@/$version/g" -e "s/@rpmversion@/$rpmversion/g" \
< "$sourcepath"/fish.spec.in > "$workpath"/fish.spec
# Make the symlinks for Debian
ln -s "$source_tarball" "$workpath"/fish_"$version".orig.tar.xz
ln -s "$vendor_tarball" "$workpath"/fish_"$version".orig-cargo-vendor.tar.xz
# Set up the Debian source tree
cd "$sourcepath"
mkdir cargo-vendor
tar -C cargo-vendor -x -f "$vendor_tarball"
cp -r contrib/debian debian
# The vendor tarball contains a new .cargo/config.toml, which has the
# vendoring overrides appended to it. dpkg-source will add this as a
# patch using the flags in debian/
cp cargo-vendor/.cargo/config.toml .cargo/config.toml
# Update the Debian changelog
# The release scripts do this for release builds - skip if it has already been done
if head -n1 debian/changelog | grep --invert-match --quiet --fixed-strings "$version"; then
debchange --newversion "$version-1" --distribution unstable "Snapshot build"
fi
# Builds the "plain" Debian package
# debuild runs lintian, which takes ten minutes to run over the vendor directories
# just use dpkg-buildpackage directly
dpkg-buildpackage --build=source -d
# Build the Ubuntu packages
# deb-reversion does not work on source packages, so do the whole thing ourselves
for series in $ubuntu_versions; do
sed -i -e "1 s/$version-1)/$version-1~$series)/" -e "1 s/unstable/$series/" debian/changelog
dpkg-buildpackage --build=source -d
sed -i -e "1 s/$version-1~$series)/$version-1)/" -e "1 s/$series/unstable/" debian/changelog
done
}

View File

@@ -15,8 +15,7 @@ tmpdir=$(mktemp -d)
manifest=$tmpdir/Cargo.toml
lockfile=$tmpdir/Cargo.lock
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml \
>"$manifest"
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml >"$manifest"
awk -v version=$VERSION '
/^name = "fish"$/ { ok=1 }
ok == 1 && /^version = ".*"$/ {

View File

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

View File

@@ -23,6 +23,6 @@
<p>
<code>chsh -s /usr/local/bin/fish</code>
</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.org/fish-shell/fish-shell/">GitHub</a>.</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.com/fish-shell/fish-shell/">GitHub</a>.</p>
</body>
</html>

View File

@@ -10,8 +10,8 @@ fi
scriptname=$(basename "$0")
if [ "$(id -u)" -ne 0 ]; then
echo "${scriptname} must be run as root"
exit 1
echo "${scriptname} must be run as root"
exit 1
fi
file=/etc/shells

View File

@@ -2,6 +2,6 @@
echo "Removing any previous installation"
pkgutil --pkg-info "${INSTALL_PKG_SESSION_ID}" && pkgutil --only-files --files "${INSTALL_PKG_SESSION_ID}" | while read -r installed
do rm -v "${DSTVOLUME}${installed}"
do rm -v "${DSTVOLUME}${installed}"
done
echo "... removed"

View File

@@ -47,7 +47,7 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
num_authors=$(wc -l <"$relnotes_tmp/committers-now")
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
printf %s \
"This release comprises $num_commits commits since $previous_version," \
"This release brings $num_commits new commits since $previous_version," \
" contributed by $num_authors authors, $num_new_authors of which are new committers."
echo
echo

View File

@@ -90,7 +90,7 @@ Created by ./build_tools/release.sh $version"
}
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline
cargo fetch --offline # bumps the version in Cargo.lock
if [ "$1" = "$version" ]; then
# debchange is a Debian script to manage the Debian changelog, but
# it's too annoying to install everywhere. Just do it by hand.
@@ -110,6 +110,8 @@ fi
git add CHANGELOG.rst Cargo.toml Cargo.lock
CreateCommit "Release $version"
# Tags must be full objects, not lightweight tags, for
# git_version-gen.sh to work.
git -c "user.signingKey=$committer" \
tag --sign --message="Release $version" $version

View File

@@ -1,123 +0,0 @@
#!/usr/bin/env fish
#
# This runs Python files, fish scripts (*.fish), and Rust files
# through their respective code formatting programs.
#
# `--all`: Format all eligible files instead of the ones specified as arguments.
# `--check`: Instead of reformatting, fail if a file is not formatted correctly.
# `--force`: Proceed without asking if uncommitted changes are detected.
# Only relevant if `--all` is specified but `--check` is not specified.
set -l fish_files
set -l python_files
set -l rust_files
set -l all no
argparse all check force -- $argv
or exit $status
if set -l -q _flag_all
set all yes
if set -q argv[1]
echo "Unexpected arguments: '$argv'"
exit 1
end
end
set -l workspace_root (realpath (status dirname)/..)
if test $all = yes
if not set -l -q _flag_force; and not set -l -q _flag_check
# Potential for false positives: Not all fish files are formatted, see the `fish_files`
# definition below.
set -l relevant_uncommitted_changes (git status --porcelain --short --untracked-files=all | sed -e 's/^ *[^ ]* *//' | grep -E '.*\.(fish|py|rs)$')
if set -q relevant_uncommitted_changes[1]
for changed_file in $relevant_uncommitted_changes
echo $changed_file
end
echo
echo 'You have uncommitted changes (listed above). Are you sure you want to restyle?'
read -P 'y/N? ' -n1 -l ans
if not string match -qi y -- $ans
exit 1
end
end
end
set fish_files $workspace_root/{benchmarks,build_tools,etc,share}/**.fish
set python_files $workspace_root
else
# Format the files specified as arguments.
set -l files $argv
set fish_files (string match -r '^.*\.fish$' -- $files)
set python_files (string match -r '^.*\.py$' -- $files)
set rust_files (string match -r '^.*\.rs$' -- $files)
end
set -l red (set_color red)
set -l green (set_color green)
set -l yellow (set_color yellow)
set -l normal (set_color normal)
function die -V red -V normal
echo $red$argv[1]$normal
exit 1
end
if set -q fish_files[1]
if not type -q fish_indent
echo
echo $yellow'Could not find `fish_indent` in `$PATH`.'$normal
exit 127
end
echo === Running "$green"fish_indent"$normal"
if set -l -q _flag_check
fish_indent --check -- $fish_files
or die "Fish files are not formatted correctly."
else
fish_indent -w -- $fish_files
end
end
if set -q python_files[1]
if not type -q ruff
echo
echo $yellow'Please install `ruff` to style python'$normal
exit 127
end
echo === Running "$green"ruff format"$normal"
if set -l -q _flag_check
ruff format --check $python_files
or die "Python files are not formatted correctly."
else
ruff format $python_files
end
end
if test $all = yes; or set -q rust_files[1]
if not cargo fmt --version >/dev/null
echo
echo $yellow'Please install "rustfmt" to style Rust, e.g. via:'
echo "rustup component add rustfmt"$normal
exit 127
end
set -l edition_spec string match -r '^edition\s*=.*'
test "$($edition_spec <Cargo.toml)" = "$($edition_spec <.rustfmt.toml)"
or die "Cargo.toml and .rustfmt.toml use different editions"
echo === Running "$green"rustfmt"$normal"
if set -l -q _flag_check
if test $all = yes
cargo fmt --all --check
else
rustfmt --check --files-with-diff $rust_files
end
or die "Rust files are not formatted correctly."
else
if test $all = yes
cargo fmt --all
else
rustfmt $rust_files
end
end
end

View File

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

View File

@@ -18,14 +18,14 @@ set(VARS_FOR_CARGO_SPHINX_WRAPPER
add_custom_target(sphinx-docs
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
cargo xtask html-docs --fish-indent=${FISH_INDENT_FOR_BUILDING_DOCS}
${Rust_CARGO} xtask html-docs --fish-indent=${FISH_INDENT_FOR_BUILDING_DOCS}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS ${SPHINX_HTML_FISH_INDENT_DEP}
COMMENT "Building HTML documentation with Sphinx")
add_custom_target(sphinx-manpages
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
cargo xtask man-pages
${Rust_CARGO} xtask man-pages
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Building man pages with Sphinx")
@@ -51,9 +51,10 @@ endif()
add_feature_info(Documentation WITH_DOCS "user manual and documentation")
if(WITH_DOCS)
add_custom_target(doc ALL
DEPENDS sphinx-docs sphinx-manpages)
add_custom_target(doc ALL DEPENDS sphinx-docs sphinx-manpages)
# Group docs targets into a DocsTargets folder
set_property(TARGET doc sphinx-docs sphinx-manpages
PROPERTY FOLDER cmake/DocTargets)
set_property(
TARGET doc sphinx-docs sphinx-manpages
PROPERTY FOLDER cmake/DocTargets
)
endif()

View File

@@ -22,17 +22,17 @@ foreach(_VAR ${_Rust_USER_VARS})
endforeach()
if (NOT DEFINED Rust_CARGO_CACHED)
find_program(Rust_CARGO_CACHED cargo PATHS "$ENV{HOME}/.cargo/bin")
find_program(Rust_CARGO_CACHED cargo PATHS "$ENV{HOME}/.cargo/bin")
endif()
if (NOT EXISTS "${Rust_CARGO_CACHED}")
message(FATAL_ERROR "The cargo executable ${Rust_CARGO_CACHED} was not found. "
"Consider setting `Rust_CARGO_CACHED` to the absolute path of `cargo`."
)
message(FATAL_ERROR "The cargo executable ${Rust_CARGO_CACHED} was not found. "
"Consider setting `Rust_CARGO_CACHED` to the absolute path of `cargo`."
)
endif()
if (NOT DEFINED Rust_COMPILER_CACHED)
find_program(Rust_COMPILER_CACHED rustc PATHS "$ENV{HOME}/.cargo/bin")
find_program(Rust_COMPILER_CACHED rustc PATHS "$ENV{HOME}/.cargo/bin")
endif()
@@ -45,31 +45,31 @@ endif()
# Figure out the target by just using the host target.
# If you want to cross-compile, you'll have to set Rust_CARGO_TARGET
if(NOT Rust_CARGO_TARGET_CACHED)
execute_process(
COMMAND "${Rust_COMPILER_CACHED}" --version --verbose
OUTPUT_VARIABLE _RUSTC_VERSION_RAW
RESULT_VARIABLE _RUSTC_VERSION_RESULT
)
if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" ))
message(FATAL_ERROR "Failed to get rustc version.\n"
"${Rust_COMPILER} --version failed with error: `${_RUSTC_VERSION_RESULT}`")
endif()
if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR
"Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}"
execute_process(
COMMAND "${Rust_COMPILER_CACHED}" --version --verbose
OUTPUT_VARIABLE _RUSTC_VERSION_RAW
RESULT_VARIABLE _RUSTC_VERSION_RESULT
)
endif()
if(CMAKE_CROSSCOMPILING)
message(FATAL_ERROR "CMake is in cross-compiling mode."
"Manually set `Rust_CARGO_TARGET`."
)
endif()
set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple")
if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" ))
message(FATAL_ERROR "Failed to get rustc version.\n"
"${Rust_COMPILER} --version failed with error: `${_RUSTC_VERSION_RESULT}`")
endif()
if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR
"Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}"
)
endif()
if(CMAKE_CROSSCOMPILING)
message(FATAL_ERROR "CMake is in cross-compiling mode."
"Manually set `Rust_CARGO_TARGET`."
)
endif()
set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple")
endif()
# Set the input variables as non-cache variables so that the variables are available after

View File

@@ -14,32 +14,36 @@ set(rel_completionsdir "fish/vendor_completions.d")
set(rel_functionsdir "fish/vendor_functions.d")
set(rel_confdir "fish/vendor_conf.d")
set(extra_completionsdir
"${datadir}/${rel_completionsdir}"
CACHE STRING "Path for extra completions")
set(
extra_completionsdir "${datadir}/${rel_completionsdir}"
CACHE STRING "Path for extra completions"
)
set(extra_functionsdir
"${datadir}/${rel_functionsdir}"
CACHE STRING "Path for extra functions")
set(
extra_functionsdir "${datadir}/${rel_functionsdir}"
CACHE STRING "Path for extra functions"
)
set(extra_confdir
"${datadir}/${rel_confdir}"
CACHE STRING "Path for extra configuration")
set(
extra_confdir "${datadir}/${rel_confdir}"
CACHE STRING "Path for extra configuration"
)
# These are the man pages that go in system manpath; all manpages go in the fish-specific manpath.
set(MANUALS ${SPHINX_OUTPUT_DIR}/man/man1/fish.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_indent.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_key_reader.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-doc.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-language.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-interactive.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-terminal-compatibility.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-completions.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-prompt-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-for-bash-users.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-faq.1
set(MANUALS
${SPHINX_OUTPUT_DIR}/man/man1/fish.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_indent.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_key_reader.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-doc.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-language.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-interactive.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-terminal-compatibility.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-completions.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-prompt-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-for-bash-users.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-faq.1
)
# Determine which man page we don't want to install.
@@ -48,40 +52,49 @@ set(MANUALS ${SPHINX_OUTPUT_DIR}/man/man1/fish.1
# On other operating systems, don't install a realpath man page, as they almost all have a realpath
# command, while macOS does not.
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CONDEMNED_PAGE "open.1")
set(CONDEMNED_PAGE "open.1")
else()
set(CONDEMNED_PAGE "realpath.1")
set(CONDEMNED_PAGE "realpath.1")
endif()
# Define a function to help us create directories.
function(FISH_CREATE_DIRS)
foreach(dir ${ARGV})
install(DIRECTORY DESTINATION ${dir})
endforeach(dir)
foreach(dir ${ARGV})
install(DIRECTORY DESTINATION ${dir})
endforeach(dir)
endfunction(FISH_CREATE_DIRS)
function(FISH_TRY_CREATE_DIRS)
foreach(dir ${ARGV})
if(NOT IS_ABSOLUTE ${dir})
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
else()
set(abs_dir "\$ENV{DESTDIR}${dir}")
endif()
install(SCRIPT CODE "EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
")
endforeach()
foreach(dir ${ARGV})
if(NOT IS_ABSOLUTE ${dir})
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
else()
set(abs_dir "\$ENV{DESTDIR}${dir}")
endif()
install(SCRIPT CODE "
EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
")
endforeach()
endfunction(FISH_TRY_CREATE_DIRS)
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/fish
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ
GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
DESTINATION ${bindir})
install(
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/fish
PERMISSIONS
OWNER_READ
OWNER_WRITE
OWNER_EXECUTE
GROUP_READ
GROUP_EXECUTE
WORLD_READ
WORLD_EXECUTE
DESTINATION ${bindir}
)
if(NOT IS_ABSOLUTE ${bindir})
set(abs_bindir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${bindir}")
set(abs_bindir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${bindir}")
else()
set(abs_bindir "\$ENV{DESTDIR}${bindir}")
set(abs_bindir "\$ENV{DESTDIR}${bindir}")
endif()
install(CODE "file(CREATE_LINK ${abs_bindir}/fish ${abs_bindir}/fish_indent)")
install(CODE "file(CREATE_LINK ${abs_bindir}/fish ${abs_bindir}/fish_key_reader)")
@@ -90,87 +103,116 @@ fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
${sysconfdir}/fish/functions)
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
fish_create_dirs(${rel_datadir}/fish ${rel_datadir}/fish/completions
${rel_datadir}/fish/functions
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
${rel_datadir}/fish/tools/web_config
${rel_datadir}/fish/tools/web_config/js
${rel_datadir}/fish/prompts
${rel_datadir}/fish/themes
)
fish_create_dirs(
${rel_datadir}/fish ${rel_datadir}/fish/completions
${rel_datadir}/fish/functions
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
${rel_datadir}/fish/tools/web_config
${rel_datadir}/fish/tools/web_config/js
${rel_datadir}/fish/prompts
${rel_datadir}/fish/themes
)
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)
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
DESTINATION ${rel_datadir}/fish
)
# Create only the vendor directories inside the prefix (#5029 / #6508)
fish_create_dirs(${rel_datadir}/fish/vendor_completions.d ${rel_datadir}/fish/vendor_functions.d
${rel_datadir}/fish/vendor_conf.d)
fish_create_dirs(
${rel_datadir}/fish/vendor_completions.d
${rel_datadir}/fish/vendor_functions.d
${rel_datadir}/fish/vendor_conf.d
)
fish_try_create_dirs(${rel_datadir}/pkgconfig)
configure_file(fish.pc.in fish.pc.noversion @ONLY)
add_custom_command(OUTPUT fish.pc
add_custom_command(
OUTPUT fish.pc
COMMAND sed '/Version/d' fish.pc.noversion > fish.pc
COMMAND printf "Version: " >> fish.pc
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh >> fish.pc
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion)
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion
)
add_custom_target(build_fish_pc ALL DEPENDS fish.pc)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
DESTINATION ${rel_datadir}/pkgconfig)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
DESTINATION ${rel_datadir}/pkgconfig
)
install(DIRECTORY share/completions/
DESTINATION ${rel_datadir}/fish/completions
FILES_MATCHING PATTERN "*.fish")
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/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/prompts/
DESTINATION ${rel_datadir}/fish/prompts
FILES_MATCHING PATTERN "*.fish"
)
install(DIRECTORY share/themes/
DESTINATION ${rel_datadir}/fish/themes
FILES_MATCHING PATTERN "*.theme")
install(
DIRECTORY share/themes/
DESTINATION ${rel_datadir}/fish/themes
FILES_MATCHING PATTERN "*.theme"
)
# CONDEMNED_PAGE is managed by the conditional above
# Building the man pages is optional: if sphinx isn't installed, they're not built
install(DIRECTORY ${SPHINX_OUTPUT_DIR}/man/man1/
DESTINATION ${rel_datadir}/fish/man/man1
FILES_MATCHING
PATTERN "*.1"
PATTERN ${CONDEMNED_PAGE} EXCLUDE)
install(
DIRECTORY ${SPHINX_OUTPUT_DIR}/man/man1/
DESTINATION ${rel_datadir}/fish/man/man1
FILES_MATCHING
PATTERN "*.1"
PATTERN ${CONDEMNED_PAGE} EXCLUDE
)
install(PROGRAMS share/tools/create_manpage_completions.py
DESTINATION ${rel_datadir}/fish/tools/)
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")
install(
DIRECTORY share/tools/web_config
DESTINATION ${rel_datadir}/fish/tools/
FILES_MATCHING
PATTERN "*.png"
PATTERN "*.css"
PATTERN "*.html"
PATTERN "*.py"
PATTERN "*.js"
)
# Building the man pages is optional: if Sphinx isn't installed, they're not built
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
install(DIRECTORY ${SPHINX_OUTPUT_DIR}/html/ # Trailing slash is important!
DESTINATION ${docdir} OPTIONAL)
install(
DIRECTORY ${SPHINX_OUTPUT_DIR}/html/ # Trailing slash is important!
DESTINATION ${docdir} OPTIONAL
)
install(FILES CHANGELOG.rst DESTINATION ${docdir})
# Group install targets into a InstallTargets folder
set_property(TARGET build_fish_pc
PROPERTY FOLDER cmake/InstallTargets)
set_property(
TARGET build_fish_pc
PROPERTY FOLDER cmake/InstallTargets
)
# Make a target build_root that installs into the buildroot directory, for testing.
set(BUILDROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/buildroot)
add_custom_target(build_root
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install)
add_custom_target(
build_root
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install
)

View File

@@ -1,9 +1,10 @@
set(FISH_USE_SYSTEM_PCRE2 ON CACHE BOOL
"Try to use PCRE2 from the system, instead of the pcre2-sys version")
"Try to use PCRE2 from the system, instead of the pcre2-sys version"
)
if(FISH_USE_SYSTEM_PCRE2)
message(STATUS "Trying to use PCRE2 from the system")
message(STATUS "Trying to use PCRE2 from the system")
else()
message(STATUS "Forcing static build of PCRE2")
set(FISH_PCRE2_BUILDFLAG "PCRE2_SYS_STATIC=1")
message(STATUS "Forcing static build of PCRE2")
set(FISH_PCRE2_BUILDFLAG "PCRE2_SYS_STATIC=1")
endif(FISH_USE_SYSTEM_PCRE2)

View File

@@ -1,27 +1,27 @@
FILE(GLOB FISH_CHECKS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/checks/*.fish)
foreach(CHECK ${FISH_CHECKS})
get_filename_component(CHECK_NAME ${CHECK} NAME)
add_custom_target(
test_${CHECK_NAME}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK_NAME}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
get_filename_component(CHECK_NAME ${CHECK} NAME)
add_custom_target(
test_${CHECK_NAME}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK_NAME}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
endforeach(CHECK)
FILE(GLOB PEXPECTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/pexpects/*.py)
foreach(PEXPECT ${PEXPECTS})
get_filename_component(PEXPECT ${PEXPECT} NAME)
add_custom_target(
test_${PEXPECT}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
get_filename_component(PEXPECT ${PEXPECT} NAME)
add_custom_target(
test_${PEXPECT}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
endforeach(PEXPECT)
# Rust stuff.
@@ -30,14 +30,20 @@ if(DEFINED ASAN)
# Rust w/ -Zsanitizer=address requires explicitly specifying the --target triple or else linker
# errors pertaining to asan symbols will ensue.
if(NOT DEFINED Rust_CARGO_TARGET)
message(FATAL_ERROR "ASAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple")
message(
FATAL_ERROR
"ASAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple"
)
endif()
endif()
if(DEFINED TSAN)
if(NOT DEFINED Rust_CARGO_TARGET)
message(FATAL_ERROR "TSAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple")
message(
FATAL_ERROR
"TSAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple"
)
endif()
endif()
@@ -53,10 +59,10 @@ endif()
# The top-level test target is "fish_run_tests".
add_custom_target(fish_run_tests
# TODO: This should be replaced with a unified solution, possibly build_tools/check.sh.
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${max_concurrency_flag} ${CMAKE_CURRENT_BINARY_DIR}
COMMAND env ${VARS_FOR_CARGO}
${Rust_CARGO}
# TODO: This should be replaced with a unified solution, possibly build_tools/check.sh.
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${max_concurrency_flag} ${CMAKE_CURRENT_BINARY_DIR}
COMMAND
env ${VARS_FOR_CARGO} ${Rust_CARGO}
test
--no-default-features
--features=${FISH_CARGO_FEATURES}
@@ -64,7 +70,7 @@ add_custom_target(fish_run_tests
--workspace
--target-dir ${rust_target_dir}
${cargo_test_flags}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)

View File

@@ -1,3 +1,19 @@
fish (4.6.0-1) stable; urgency=medium
* Release of new version 4.6.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.6.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Sat, 28 Mar 2026 12:56:37 +0800
fish (4.5.0-1) stable; urgency=medium
* Release of new version 4.5.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.5.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 17 Feb 2026 11:32:33 +1100
fish (4.4.0-1) stable; urgency=medium
* Release of new version 4.4.0.

View File

@@ -13,7 +13,7 @@ Build-Depends: debhelper-compat (= 13),
python3-sphinx,
# Test dependencies
locales-all,
man-db,
man-db | man,
python3
# 4.6.2 is Debian 12/Ubuntu Noble 24.04; Ubuntu Jammy is 4.6.0.1
Standards-Version: 4.6.2
@@ -26,8 +26,8 @@ Architecture: any
# for col and lock
Depends: bsdextrautils,
file,
# for man
man-db,
# for showing built-in help pages
man-db | man,
# for kill
procps,
python3 (>=3.5),

View File

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

33
contrib/shell.nix Normal file
View File

@@ -0,0 +1,33 @@
# Environment containing all dependencies needed for
# - building fish,
# - building documentation,
# - running all tests,
# - formatting and checking lints.
#
# enter interactive bash shell:
# nix-shell contrib/shell.nix
#
# using system nixpkgs (otherwise fetches pinned version):
# nix-shell contrib/shell.nix --arg pkgs 'import <nixpkgs> {}'
#
# run single command:
# nix-shell contrib/shell --run "cargo xtask check"
{ pkgs ? (import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.11.tar.gz";
sha256 = "1ia5kjykm9xmrpwbzhbaf4cpwi3yaxr7shl6amj8dajvgbyh2yh4";
}) { }), ... }:
pkgs.mkShell {
buildInputs = [
(pkgs.python3.withPackages (pyPkgs: [ pyPkgs.pexpect ]))
pkgs.cargo
pkgs.clippy
pkgs.cmake
pkgs.gettext
pkgs.pcre2
pkgs.procps # tests use pgrep/pkill
pkgs.ruff
pkgs.rustc
pkgs.rustfmt
pkgs.sphinx
];
}

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt, path::Path};
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt as _, path::Path};
pub fn env_var(name: &str) -> Option<String> {
let err = match env::var(name) {
@@ -38,6 +38,14 @@ pub fn fish_doc_dir() -> Cow<'static, Path> {
cargo_target_dir().join("fish-docs").into()
}
fn l10n_dir() -> Cow<'static, Path> {
workspace_root().join("localization").into()
}
pub fn po_dir() -> Cow<'static, Path> {
l10n_dir().join("po").into()
}
// TODO Move this to rsconf
pub fn rebuild_if_path_changed<P: AsRef<Path>>(path: P) {
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());

View File

@@ -43,7 +43,7 @@ fn build_man(sec1_dir: &Path) {
];
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
if env_var("FISH_BUILD_DOCS") == Some("0".to_owned()) {
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
@@ -62,7 +62,7 @@ fn build_man(sec1_dir: &Path) {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
assert_ne!(
env_var("FISH_BUILD_DOCS"),
Some("1".to_string()),
Some("1".to_owned()),
"Could not find sphinx-build required to build man pages.\n\
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
);

View File

@@ -8,9 +8,14 @@ license.workspace = true
[dependencies]
bitflags.workspace = true
fish-feature-flags.workspace = true
fish-widestring.workspace = true
libc.workspace = true
nix.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

5
crates/common/build.rs Normal file
View File

@@ -0,0 +1,5 @@
use fish_build_helper::target_os_is_apple;
fn main() {
rsconf::declare_cfg("apple", target_os_is_apple());
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,12 @@
use std::cmp;
use std::sync::{
LazyLock,
atomic::{AtomicIsize, Ordering},
atomic::{AtomicUsize, Ordering},
};
/// Width of ambiguous East Asian characters and, as of TR11, all private-use characters.
/// 1 is the typical default, but we accept any non-negative override via `$fish_ambiguous_width`.
pub static FISH_AMBIGUOUS_WIDTH: AtomicIsize = AtomicIsize::new(1);
pub static FISH_AMBIGUOUS_WIDTH: AtomicUsize = AtomicUsize::new(1);
/// Width of emoji characters.
///
@@ -25,68 +25,33 @@
/// Valid values are 1, and 2. 1 is the typical emoji width used in Unicode 8 while some newer
/// terminals use a width of 2 since Unicode 9.
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
pub static FISH_EMOJI_WIDTH: AtomicIsize = AtomicIsize::new(1);
pub static FISH_EMOJI_WIDTH: AtomicUsize = AtomicUsize::new(2);
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
/// A safe wrapper around the system `wcwidth()` function
#[cfg(not(cygwin))]
pub fn wcwidth(c: char) -> isize {
unsafe extern "C" {
pub unsafe fn wcwidth(c: libc::wchar_t) -> libc::c_int;
}
const {
assert!(size_of::<libc::wchar_t>() >= size_of::<char>());
}
let width = unsafe { wcwidth(c as libc::wchar_t) };
isize::try_from(width).unwrap()
}
// Big hack to use our versions of wcswidth where we know them to be broken, which is
// EVERYWHERE (https://github.com/fish-shell/fish-shell/issues/2199)
pub fn fish_wcwidth(c: char) -> isize {
// The system version of wcwidth should accurately reflect the ability to represent characters
// in the console session, but knows nothing about the capabilities of other terminal emulators
// or ttys. Use it from the start only if we are logged in to the physical console.
#[cfg(not(cygwin))]
if fish_common::is_console_session() {
return wcwidth(c);
}
pub fn fish_wcwidth(c: char) -> Option<usize> {
// Check for VS16 which selects emoji presentation. This "promotes" a character like U+2764
// (width 1) to an emoji (probably width 2). So treat it as width 1 so the sums work. See #2652.
// VS15 selects text presentation.
let variation_selector_16 = '\u{FE0F}';
let variation_selector_15 = '\u{FE0E}';
if c == variation_selector_16 {
return 1;
return Some(1);
} else if c == variation_selector_15 {
return 0;
return Some(0);
}
// Check for Emoji_Modifier property. Only the Fitzpatrick modifiers have this, in range
// 1F3FB..1F3FF. This is a hack because such an emoji appearing on its own would be drawn as
// width 2, but that's unlikely to be useful. See #8275.
if ('\u{1F3FB}'..='\u{1F3FF}').contains(&c) {
return 0;
return Some(0);
}
let width = WC_LOOKUP_TABLE.classify(c);
match width {
WcWidth::NonCharacter | WcWidth::NonPrint | WcWidth::Combining | WcWidth::Unassigned => {
#[cfg(not(cygwin))]
{
// Fall back to system wcwidth in this case.
wcwidth(c)
}
#[cfg(cygwin)]
{
// No system wcwidth for UTF-32 on cygwin.
0
}
}
Some(match width {
WcWidth::NonPrint => return None,
WcWidth::NonCharacter | WcWidth::Combining | WcWidth::Unassigned => 0,
WcWidth::Ambiguous | WcWidth::PrivateUse => {
// TR11: "All private-use characters are by default classified as Ambiguous".
FISH_AMBIGUOUS_WIDTH.load(Ordering::Relaxed)
@@ -94,26 +59,25 @@ pub fn fish_wcwidth(c: char) -> isize {
WcWidth::One => 1,
WcWidth::Two => 2,
WcWidth::WidenedIn9 => FISH_EMOJI_WIDTH.load(Ordering::Relaxed),
}
})
}
/// fish's internal versions of wcwidth and wcswidth, which can use an internal implementation if
/// the system one is busted.
pub fn fish_wcswidth(s: &wstr) -> isize {
// ascii fast path; empty iterator returns true for .all()
if s.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {
return s.len() as isize;
pub fn fish_wcswidth(s: &wstr) -> Option<usize> {
fish_wcswidth_canonicalizing(s, std::convert::identity)
}
pub fn fish_wcswidth_canonicalizing(s: &wstr, canonicalize: fn(char) -> char) -> Option<usize> {
let chars = s.chars().map(canonicalize);
// ascii fast path
if chars.clone().all(|c| c.is_ascii() && !c.is_ascii_control()) {
return Some(s.len());
}
let mut result = 0;
for c in s.chars() {
let w = fish_wcwidth(c);
if w < 0 {
return -1;
}
result += w;
for c in chars {
result += fish_wcwidth(c)?;
}
result
Some(result)
}
pub fn wcscasecmp(lhs: &wstr, rhs: &wstr) -> cmp::Ordering {

View File

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

View File

@@ -1,15 +1,14 @@
//! Flags to enable upcoming features
use crate::prelude::*;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
#[cfg(test)]
use std::cell::RefCell;
use fish_widestring::{L, WExt as _, wstr};
use std::{
cell::RefCell,
sync::atomic::{AtomicBool, Ordering},
};
/// The list of flags.
#[repr(u8)]
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FeatureFlag {
/// Whether ^ is supported for stderr redirection.
StderrNoCaret,
@@ -63,10 +62,10 @@ pub struct FeatureMetadata {
pub description: &'static wstr,
/// Default flag value.
pub default_value: bool,
default_value: bool,
/// Whether the value can still be changed or not.
pub read_only: bool,
read_only: bool,
}
/// The metadata, indexed by flag.
@@ -131,9 +130,11 @@ pub struct FeatureMetadata {
flag: FeatureFlag::IgnoreTerminfo,
name: L!("ignore-terminfo"),
groups: L!("4.1"),
description: L!("do not look up $TERM in terminfo database"),
description: L!(
"do not look up $TERM in terminfo database (historical, can no longer be changed)"
),
default_value: true,
read_only: false,
read_only: true,
},
FeatureMetadata {
flag: FeatureFlag::QueryTerm,
@@ -154,31 +155,26 @@ pub struct FeatureMetadata {
];
thread_local!(
#[cfg(test)]
static LOCAL_FEATURES: RefCell<Option<Features>> = const { RefCell::new(None) };
static LOCAL_OVERRIDE_STACK: RefCell<Vec<(FeatureFlag, bool)>> =
const { RefCell::new(Vec::new()) };
);
/// The singleton shared feature set.
static FEATURES: Features = Features::new();
/// Perform a feature test on the global set of features.
pub fn test(flag: FeatureFlag) -> bool {
#[cfg(test)]
{
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).test(flag))
pub fn feature_test(flag: FeatureFlag) -> bool {
if let Some(value) = LOCAL_OVERRIDE_STACK.with(|stack| {
for &(overridden_feature, value) in stack.borrow().iter().rev() {
if flag == overridden_feature {
return Some(value);
}
}
None
}) {
return value;
}
#[cfg(not(test))]
{
FEATURES.test(flag)
}
}
pub use test as feature_test;
/// Set a flag.
#[cfg(test)]
pub fn set(flag: FeatureFlag, value: bool) {
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).set(flag, value));
FEATURES.test(flag)
}
/// Parses a comma-separated feature-flag string, updating ourselves with the values.
@@ -186,38 +182,31 @@ pub fn set(flag: FeatureFlag, value: bool) {
/// The special group name "all" may be used for those who like to live on the edge.
/// Unknown features are silently ignored.
pub fn set_from_string<'a>(str: impl Into<&'a wstr>) {
let wstr: &wstr = str.into();
#[cfg(test)]
{
LOCAL_FEATURES.with(|fc| {
fc.borrow()
.as_ref()
.unwrap_or(&FEATURES)
.set_from_string(wstr);
});
}
#[cfg(not(test))]
{
FEATURES.set_from_string(wstr);
}
FEATURES.set_from_string(str.into());
}
impl Features {
const fn new() -> Self {
Features {
values: [
AtomicBool::new(METADATA[0].default_value),
AtomicBool::new(METADATA[1].default_value),
AtomicBool::new(METADATA[2].default_value),
AtomicBool::new(METADATA[3].default_value),
AtomicBool::new(METADATA[4].default_value),
AtomicBool::new(METADATA[5].default_value),
AtomicBool::new(METADATA[6].default_value),
AtomicBool::new(METADATA[7].default_value),
AtomicBool::new(METADATA[8].default_value),
AtomicBool::new(METADATA[9].default_value),
],
}
// TODO: feature(const_array): use std::array::from_fn()
use std::mem::{MaybeUninit, transmute};
let values = {
let mut data: [MaybeUninit<AtomicBool>; METADATA.len()] =
[const { MaybeUninit::uninit() }; METADATA.len()];
let mut i = 0;
while i < METADATA.len() {
data[i].write(AtomicBool::new(METADATA[i].default_value));
i += 1;
}
// SAFETY: `data` is guaranteed initialized by the loop
unsafe {
transmute::<[MaybeUninit<AtomicBool>; METADATA.len()], [AtomicBool; METADATA.len()]>(
data,
)
}
};
Features { values }
}
fn test(&self, flag: FeatureFlag) -> bool {
@@ -229,19 +218,14 @@ fn set(&self, flag: FeatureFlag, value: bool) {
}
fn set_from_string(&self, str: &wstr) {
let whitespace = L!("\t\n\0x0B\0x0C\r ").as_char_slice();
for entry in str.as_char_slice().split(|c| *c == ',') {
for entry in str.split(',') {
let entry = entry.trim();
if entry.is_empty() {
continue;
}
// Trim leading and trailing whitespace
let entry = &entry[entry.iter().take_while(|c| whitespace.contains(c)).count()..];
let entry =
&entry[..entry.len() - entry.iter().take_while(|c| whitespace.contains(c)).count()];
// A "no-" prefix inverts the sense.
let (name, value) = match entry.strip_prefix(L!("no-").as_char_slice()) {
let (name, value) = match entry.strip_prefix("no-") {
Some(suffix) => (suffix, false),
None => (entry, true),
};
@@ -267,28 +251,20 @@ fn set_from_string(&self, str: &wstr) {
}
}
#[cfg(test)]
pub fn scoped_test(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
LOCAL_FEATURES.with(|fc| {
assert!(
fc.borrow().is_none(),
"scoped_test() does not support nesting"
);
let f = Features::new();
f.set(flag, value);
*fc.borrow_mut() = Some(f);
/// Run code with a feature overridden.
/// This should only be used in tests.
pub fn with_overridden_feature(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
LOCAL_OVERRIDE_STACK.with(|stack| {
stack.borrow_mut().push((flag, value));
test_fn();
*fc.borrow_mut() = None;
stack.borrow_mut().pop();
});
}
#[cfg(test)]
mod tests {
use super::{FeatureFlag, Features, METADATA, scoped_test, set, test};
use crate::prelude::*;
use super::{FeatureFlag, Features, METADATA, feature_test, with_overridden_feature};
use fish_widestring::L;
#[test]
fn test_feature_flags() {
@@ -314,25 +290,19 @@ fn test_feature_flags() {
}
#[test]
fn test_scoped() {
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(test(FeatureFlag::QuestionMarkNoGlob));
fn test_overridden_feature() {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
});
set(FeatureFlag::QuestionMarkNoGlob, true);
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
assert!(!test(FeatureFlag::QuestionMarkNoGlob));
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
assert!(!feature_test(FeatureFlag::QuestionMarkNoGlob));
});
set(FeatureFlag::QuestionMarkNoGlob, false);
}
#[test]
#[should_panic]
fn test_nested_scopes_not_supported() {
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {});
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
});
});
}
}

View File

@@ -1,7 +1,7 @@
extern crate proc_macro;
use fish_tempfile::random_filename;
use proc_macro::TokenStream;
use std::{ffi::OsString, io::Write, path::PathBuf};
use std::{ffi::OsString, io::Write as _, path::PathBuf};
fn unescape_multiline_rust_string(s: String) -> String {
if !s.contains('\n') {

View File

@@ -11,24 +11,16 @@ fn main() {
PathBuf::from(fish_build_helper::fish_build_dir()).join("fish-localization-map-cache");
embed_localizations(&cache_dir);
fish_build_helper::rebuild_if_path_changed(
fish_build_helper::workspace_root()
.join("localization")
.join("po"),
);
fish_build_helper::rebuild_if_path_changed(fish_build_helper::po_dir());
}
fn embed_localizations(cache_dir: &Path) {
use fish_gettext_mo_file_parser::parse_mo_file;
use std::{
fs::File,
io::{BufWriter, Write},
io::{BufWriter, Write as _},
};
let po_dir = fish_build_helper::workspace_root()
.join("localization")
.join("po");
// Ensure that the directory is created, because clippy cannot compile the code if the
// directory does not exist.
std::fs::create_dir_all(cache_dir).unwrap();
@@ -56,7 +48,7 @@ fn embed_localizations(cache_dir: &Path) {
Ok(output) => {
let has_check_format =
String::from_utf8_lossy(&output.stdout).contains("--check-format");
for dir_entry_result in po_dir.read_dir().unwrap() {
for dir_entry_result in fish_build_helper::po_dir().read_dir().unwrap() {
let dir_entry = dir_entry_result.unwrap();
let po_file_path = dir_entry.path();
if po_file_path.extension() != Some(OsStr::new("po")) {

View File

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

View File

@@ -51,7 +51,7 @@ macro_rules! sprintf {
{
// May be no args!
#[allow(unused_imports)]
use $crate::ToArg;
use $crate::ToArg as _;
$crate::printf_c_locale(
$target,
$fmt,

View File

@@ -6,8 +6,8 @@
use std::fmt::{self, Write};
use std::mem;
use std::result::Result;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation as _;
use unicode_width::UnicodeWidthStr as _;
#[cfg(feature = "widestring")]
use widestring::Utf32Str as wstr;

View File

@@ -1,6 +1,6 @@
use crate::arg::ToArg;
use crate::locale::{C_LOCALE, EN_US_LOCALE, Locale};
use crate::{Error, FormatString, sprintf_locale};
use crate::{Error, FormatString as _, sprintf_locale};
use std::f64::consts::{E, PI, TAU};
use std::ffi::CStr;
use std::fmt;
@@ -13,7 +13,7 @@ macro_rules! sprintf_check {
$(,)? // optional trailing comma
) => {
{
use unicode_width::UnicodeWidthStr;
use unicode_width::UnicodeWidthStr as _;
let mut target = String::new();
let mut args = [$($arg.to_arg()),*];
let len = $crate::printf_c_locale(
@@ -400,8 +400,10 @@ fn test_char() {
#[test]
fn test_ptr() {
assert_fmt!("%p", core::ptr::null::<u8>() => "0");
assert_fmt!("%p", 0xDEADBEEF_usize as *const u8 => "0xdeadbeef");
assert_fmt!("%p", core::ptr::null::<()>() => "0");
let tmp = core::ptr::without_provenance::<()>(0xDEADBEEF);
assert_fmt!("%p", tmp => "0xdeadbeef");
}
#[test]

View File

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

View File

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

View File

@@ -1,9 +1,15 @@
//! Generic utilities library.
use errno::errno;
use fish_widestring::prelude::*;
use rand::{SeedableRng, rngs::SmallRng};
use std::cmp::Ordering;
use std::time;
use rand::{SeedableRng as _, rngs::SmallRng};
use std::{
cmp::Ordering,
ffi::CStr,
io::Write as _,
os::fd::{BorrowedFd, RawFd},
time,
};
/// Compares two wide character strings with an (arguably) intuitive ordering. This function tries
/// to order strings in a way which is intuitive to humans with regards to sorting strings
@@ -234,6 +240,26 @@ fn wcsfilecmp_leading_digits(a: &wstr, b: &wstr) -> (Ordering, usize, usize) {
(ret, ai, bi)
}
pub fn write_to_fd(input: &[u8], fd: RawFd) -> nix::Result<usize> {
nix::unistd::write(unsafe { BorrowedFd::borrow_raw(fd) }, input)
}
/// Prints the provided string, followed by a colon, space, and the string representation of the
/// current errno via [`libc::strerror`].
pub fn perror(s: &str) {
let e = errno().0;
let mut stderr = std::io::stderr().lock();
if !s.is_empty() {
let _ = write!(stderr, "{s}: ");
}
let slice = unsafe {
let msg = libc::strerror(e);
CStr::from_ptr(msg).to_bytes()
};
let _ = stderr.write_all(slice);
let _ = stderr.write_all(b"\n");
}
#[cfg(test)]
mod tests {
use super::wcsfilecmp;

View File

@@ -7,7 +7,6 @@ repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
fish-fallback.workspace = true
fish-widestring.workspace = true

View File

@@ -1,8 +1,7 @@
//! Helper functions for working with wcstring.
use fish_common::{get_ellipsis_char, get_ellipsis_str};
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
use fish_widestring::{decode_byte_from_char, prelude::*};
use fish_widestring::{ELLIPSIS_CHAR, prelude::*};
/// Return the number of newlines in a string.
pub fn count_newlines(s: &wstr) -> usize {
@@ -336,30 +335,6 @@ pub fn string_fuzzy_match_string(
StringFuzzyMatch::try_create(string, match_against, anchor_start)
}
/// Implementation of wcs2bytes that accepts a callback.
/// The first argument can be either a `&str` or `&wstr`.
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
/// input, doing one invocation per character.
/// If `func` returns false, it stops; otherwise it continues.
/// Return false if the callback returned false, otherwise true.
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
let mut converted = [0_u8; 4];
for c in input.chars() {
let bytes = if let Some(byte) = decode_byte_from_char(c) {
converted[0] = byte;
&converted[..=0]
} else {
c.encode_utf8(&mut converted).as_bytes()
};
if !func(bytes) {
return false;
}
}
true
}
/// Split a string by runs of any of the separator characters provided in `seps`.
/// Note the delimiters are the characters in `seps`, not `seps` itself.
/// `seps` may contain the NUL character.
@@ -473,32 +448,13 @@ pub fn split_about<'haystack>(
output
}
#[derive(Eq, PartialEq)]
pub enum EllipsisType {
None,
// Prefer niceness over minimalness
Prettiest,
// Make every character count ($ instead of ...)
Shortest,
}
pub fn truncate(input: &wstr, max_len: usize, etype: Option<EllipsisType>) -> WString {
let etype = etype.unwrap_or(EllipsisType::Prettiest);
// TODO: This should work on render width rather than the number of codepoints.
pub fn truncate(input: &wstr, max_len: usize) -> WString {
if input.len() <= max_len {
return input.to_owned();
}
if etype == EllipsisType::None {
return input[..max_len].to_owned();
}
if etype == EllipsisType::Prettiest {
let ellipsis_str = get_ellipsis_str();
let mut output = input[..max_len - ellipsis_str.len()].to_owned();
output += ellipsis_str;
return output;
}
let mut output = input[..max_len - 1].to_owned();
output.push(get_ellipsis_char());
output.push(ELLIPSIS_CHAR);
output
}
@@ -565,12 +521,12 @@ fn next(&mut self) -> Option<Self::Item> {
}
}
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of -1.
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of none.
pub fn fish_wcwidth_visible(c: char) -> isize {
if c == '\x08' {
return -1;
}
fish_wcwidth(c).max(0)
fish_wcwidth(c).unwrap_or_default().try_into().unwrap()
}
#[cfg(test)]

View File

@@ -7,6 +7,8 @@ repository.workspace = true
license.workspace = true
[dependencies]
libc.workspace = true
unicode-width.workspace = true
widestring.workspace = true
[lints]

View File

@@ -6,13 +6,34 @@
pub mod word_char;
use std::{iter, slice};
use std::{
ffi::{CStr, CString, OsStr, OsString},
iter,
os::unix::ffi::{OsStrExt as _, OsStringExt as _},
slice,
};
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utf32str as L, utfstr::CharsUtf32};
pub mod prelude {
pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr};
}
// Highest legal ASCII value.
pub const ASCII_MAX: char = 127 as char;
// Highest legal 16-bit Unicode value.
pub const UCS2_MAX: char = '\u{FFFF}';
// Highest legal byte value.
pub const BYTE_MAX: char = 0xFF as char;
// Unicode BOM value.
pub const UTF8_BOM_WCHAR: char = '\u{FEFF}';
/// The character to use where the text has been truncated.
pub const ELLIPSIS_CHAR: char = '\u{2026}'; // ('…')
pub const SPECIAL_KEY_ENCODE_BASE: char = '\u{F500}';
// These are in the Unicode private-use range. We really shouldn't use this
// range but have little choice in the matter given how our lexer/parser works.
// We can't use non-characters for these two ranges because there are only 66 of
@@ -25,9 +46,79 @@ pub mod prelude {
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
pub const ENCODE_DIRECT_BASE: char = '\u{F600}';
pub const ENCODE_DIRECT_BASE: char = char_offset(SPECIAL_KEY_ENCODE_BASE, 256);
pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256);
// Use Unicode "non-characters" for internal characters as much as we can. This
// gives us 32 "characters" for internal use that we can guarantee should not
// appear in our input stream. See http://www.unicode.org/faq/private_use.html.
pub const RESERVED_CHAR_BASE: char = '\u{FDD0}';
pub const RESERVED_CHAR_END: char = '\u{FDF0}';
// Split the available non-character values into two ranges to ensure there are
// no conflicts among the places we use these special characters.
pub const EXPAND_RESERVED_BASE: char = RESERVED_CHAR_BASE;
pub const EXPAND_RESERVED_END: char = char_offset(EXPAND_RESERVED_BASE, 16);
pub const WILDCARD_RESERVED_BASE: char = EXPAND_RESERVED_END;
pub const WILDCARD_RESERVED_END: char = char_offset(WILDCARD_RESERVED_BASE, 16);
// Make sure the ranges defined above don't exceed the range for non-characters.
// This is to make sure we didn't do something stupid in subdividing the
// Unicode range for our needs.
const _: () = assert!(WILDCARD_RESERVED_END <= RESERVED_CHAR_END);
/// Character representing any character except '/' (slash).
pub const ANY_CHAR: char = char_offset(WILDCARD_RESERVED_BASE, 0);
/// Character representing any character string not containing '/' (slash).
pub const ANY_STRING: char = char_offset(WILDCARD_RESERVED_BASE, 1);
/// Character representing any character string.
pub const ANY_STRING_RECURSIVE: char = char_offset(WILDCARD_RESERVED_BASE, 2);
/// This is a special pseudo-char that is not used other than to mark the
/// end of the special characters so we can sanity check the enum range.
#[allow(dead_code)]
pub const ANY_SENTINEL: char = char_offset(WILDCARD_RESERVED_BASE, 3);
/// Character representing a home directory.
pub const HOME_DIRECTORY: char = char_offset(EXPAND_RESERVED_BASE, 0);
/// Character representing process expansion for %self.
pub const PROCESS_EXPAND_SELF: char = char_offset(EXPAND_RESERVED_BASE, 1);
/// Character representing variable expansion.
pub const VARIABLE_EXPAND: char = char_offset(EXPAND_RESERVED_BASE, 2);
/// Character representing variable expansion into a single element.
pub const VARIABLE_EXPAND_SINGLE: char = char_offset(EXPAND_RESERVED_BASE, 3);
/// Character representing the start of a bracket expansion.
pub const BRACE_BEGIN: char = char_offset(EXPAND_RESERVED_BASE, 4);
/// Character representing the end of a bracket expansion.
pub const BRACE_END: char = char_offset(EXPAND_RESERVED_BASE, 5);
/// Character representing separation between two bracket elements.
pub const BRACE_SEP: char = char_offset(EXPAND_RESERVED_BASE, 6);
/// Character that takes the place of any whitespace within non-quoted text in braces
pub const BRACE_SPACE: char = char_offset(EXPAND_RESERVED_BASE, 7);
/// Separate subtokens in a token with this character.
pub const INTERNAL_SEPARATOR: char = char_offset(EXPAND_RESERVED_BASE, 8);
/// Character representing an empty variable expansion. Only used transitively while expanding
/// variables.
pub const VARIABLE_EXPAND_EMPTY: char = char_offset(EXPAND_RESERVED_BASE, 9);
const _: () = assert!(
EXPAND_RESERVED_END as u32 > VARIABLE_EXPAND_EMPTY as u32,
"Characters used in expansions must stay within private use area"
);
/// The string represented by PROCESS_EXPAND_SELF
pub const PROCESS_EXPAND_SELF_STR: &wstr = L!("%self");
/// Return true if the character is in a range reserved for fish's private use.
///
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
/// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted
/// string. We don't want external input to be able to feed reserved characters into our
/// lexer/parser or code evaluator.
//
// TODO: Actually implement the replacement as documented above.
pub fn fish_reserved_codepoint(c: char) -> bool {
(c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END)
|| (c >= SPECIAL_KEY_ENCODE_BASE && c < ENCODE_DIRECT_END)
}
/// Encode a literal byte in a UTF-32 character. This is required for e.g. the echo builtin, whose
/// escape sequences can be used to construct raw byte sequences which are then interpreted as e.g.
/// UTF-8 by the terminal. If we were to interpret each of those bytes as a codepoint and encode it
@@ -40,6 +131,86 @@ pub fn encode_byte_to_char(byte: u8) -> char {
.expect("private-use codepoint should be valid char")
}
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
/// string.
///
/// This function decodes illegal character sequences in a reversible way using the private use
/// area.
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
let mut result = vec![];
wcs2bytes_appending(&mut result, input);
result
}
pub fn wcs2osstring(input: &wstr) -> OsString {
if input.is_empty() {
return OsString::new();
}
let mut result = vec![];
wcs2bytes_appending(&mut result, input);
OsString::from_vec(result)
}
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
pub fn wcs2zstring(input: &wstr) -> CString {
if input.is_empty() {
return CString::default();
}
let mut vec = Vec::with_capacity(input.len() + 1);
str2bytes_callback(input, |buff| {
vec.extend_from_slice(buff);
true
});
vec.push(b'\0');
match CString::from_vec_with_nul(vec) {
Ok(cstr) => cstr,
Err(err) => {
// `input` contained a NUL in the middle; we can retrieve `vec`, though
let mut vec = err.into_bytes();
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
vec.truncate(pos + 1);
// Safety: We truncated after the first NUL
unsafe { CString::from_vec_with_nul_unchecked(vec) }
}
}
}
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
str2bytes_callback(input, |buff| {
output.extend_from_slice(buff);
true
});
}
/// Implementation of wcs2bytes that accepts a callback.
/// The first argument can be either a `&str` or `&wstr`.
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
/// input, doing one invocation per character.
/// If `func` returns false, it stops; otherwise it continues.
/// Return false if the callback returned false, otherwise true.
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
let mut converted = [0_u8; 4];
for c in input.chars() {
let bytes = if let Some(byte) = decode_byte_from_char(c) {
converted[0] = byte;
&converted[..=0]
} else {
c.encode_utf8(&mut converted).as_bytes()
};
if !func(bytes) {
return false;
}
}
true
}
/// Decode a literal byte from a UTF-32 character.
pub fn decode_byte_from_char(c: char) -> Option<u8> {
if c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END {
@@ -53,6 +224,278 @@ pub fn decode_byte_from_char(c: char) -> Option<u8> {
}
}
/// A trait to make it more convenient to pass ascii/Unicode strings to functions that can take
/// non-Unicode values. The result is nul-terminated and can be passed to OS functions.
///
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
/// left with the original item if we're going to allocate from scratch in all cases.
pub trait ToCString {
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
fn to_cstring(self) -> CString;
}
impl ToCString for CString {
fn to_cstring(self) -> CString {
self
}
}
impl ToCString for &CStr {
fn to_cstring(self) -> CString {
self.to_owned()
}
}
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
/// range by using [`wcs2zstring()`].
impl ToCString for &wstr {
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
fn to_cstring(self) -> CString {
self::wcs2zstring(self)
}
}
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
/// functions, taking into account non-Unicode values that have been shifted into the private-use
/// range by using [`wcs2zstring()`].
impl ToCString for &WString {
fn to_cstring(self) -> CString {
self.as_utfstr().to_cstring()
}
}
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
impl ToCString for Vec<u8> {
fn to_cstring(mut self) -> CString {
self.push(b'\0');
CString::from_vec_with_nul(self).unwrap()
}
}
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
impl ToCString for &[u8] {
fn to_cstring(self) -> CString {
CString::new(self).unwrap()
}
}
mod decoder {
use crate::{ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, char_offset, wstr};
use buffer::Buffer;
use std::{char::REPLACEMENT_CHARACTER, ops::Range};
use widestring::utfstr::CharsUtf32;
mod buffer {
// The size required for a PUA-encoded character from our special PUA range - 1,
// since that is the maximum number characters our look-ahead needs to check.
const MAX_SIZE: usize = 2;
pub(super) struct Buffer {
buffer: [char; MAX_SIZE],
length: usize,
}
impl Buffer {
pub(super) fn empty() -> Self {
Self {
buffer: ['\0'; MAX_SIZE],
length: 0,
}
}
pub(super) fn push(&mut self, c: char) {
self.buffer[self.length] = c;
self.length += 1;
}
pub(super) fn pop(&mut self) -> Option<char> {
if self.length == 0 {
return None;
}
self.length -= 1;
Some(self.buffer[self.length])
}
pub(super) fn pop_front(&mut self) -> Option<char> {
if self.length == 0 {
return None;
}
self.buffer.rotate_left(1);
self.length -= 1;
Some(self.buffer[MAX_SIZE - 1])
}
}
}
const PUA_ENCODE_RANGE: Range<char> = ENCODE_DIRECT_BASE..ENCODE_DIRECT_END;
const ENCODED_PUA_CHAR_FIRST_CHAR: char = char_offset(ENCODE_DIRECT_BASE, 0xef);
// The second UTF-8 byte of a character in our special PUA range is in the range
// 0x98..0x9c.
const ENCODED_PUA_CHAR_SECOND_CHAR_RANGE: Range<char> =
char_offset(ENCODE_DIRECT_BASE, 0x98)..char_offset(ENCODE_DIRECT_BASE, 0x9c);
/// This serves as the data container for building a double-ended iterator which decodes our
/// PUA-encoded chars into a char iterator where each encoded non-UTF-8 byte is replaced by the
/// replacement character, and each encoded PUA codepoint is turned back into a single char
/// whose value is the original PUA codepoint.
///
/// The latter part makes the decoding logic somewhat complicated, because encoded PUA chars
/// take up 3 chars in our encoding. Therefore, in some cases, we need to take more than 1 char
/// from the `encoded_chars` iterator before we know whether to decode 3 chars together into a
/// single char, or whether the chars should be replaced by the replacement char individually.
/// In cases where we took more than 1 char and then notice that individual replacement is
/// warranted, we return a replacement char for the first char we took from the iterator, and
/// cache the 1 or 2 other chars we read in `buffer_front` or `buffer_back`, depending on the
/// reading direction. Buffers store elements in such an order that getting the next character
/// requires `pop` when using the buffer associated with the current reading direction, and
/// `pop_front` when using the other buffer. This is done to optimize the common case of
/// iterating in a single direction.
///
/// The buffers have to be considered before taking more chars from `encoded_chars`.
/// If the iterator is only read in one direction, the buffer for the other direction will not
/// be used. But because it's possible that the iterator is read from both ends, it can happen
/// that when `encoded_chars` runs out, the buffer for the opposite reading direction is
/// non-empty. In the [`Self::next`] and [`Self::next_back`] implementations, this logic is
/// encapsulated into closures for getting the next char from the appropriate source.
/// At most 2 chars will ever be stored in a buffer, so they are implemented using a fixed-size
/// array, requiring no heap allocations.
///
/// Note that in most cases, we can avoid using the buffers, and simply forward the char
/// obtained from `encoded_chars`. Only chars in [`PUA_ENCODE_RANGE`] can possibly encode PUA
/// chars, so if we read any other char, we know that it's not part of such an encoding and can
/// return it directly. If we read in the forward direction, we can also exploit knowledge about
/// the possible values of our PUA encoding. Specifically, the first char in such an encoding
/// will always be [`ENCODED_PUA_CHAR_FIRST_CHAR`], and the second char will be in the range
/// [`ENCODED_PUA_CHAR_SECOND_CHAR_RANGE`].
pub(super) struct Decoder<'a> {
encoded_chars: CharsUtf32<'a>,
buffer_front: Buffer,
buffer_back: Buffer,
}
impl<'a> Decoder<'a> {
pub(super) fn new(encoded_str: &'a wstr) -> Self {
Self {
encoded_chars: encoded_str.chars(),
buffer_front: Buffer::empty(),
buffer_back: Buffer::empty(),
}
}
}
fn try_pua_decoding(encoding: &[char; 3]) -> Option<char> {
let mut bytes = [0u8; 3];
for (index, &c) in encoding.iter().enumerate() {
bytes[index] = super::decode_byte_from_char(c)?;
}
let first_decoded_char =
std::str::from_utf8(&bytes).ok()?.chars().next().expect(
"Non-empty byte slice which is valid UTF-8 must result in at least one char.",
);
// For strings whose width we compute, we only expect invalid UTF-8 and codepoints from the
// PUA encoding range to be PUA encoded.
// If we reach this point, the encoded bytes are valid UTF-8, so the only remaining
// expected case are codepoints from the PUA encoding range.
// These all take 3 bytes to represent in UTF-8, so if we check that the first parsed
// codepoint is in the expected range, we know that exactly 3 bytes were consumed for
// parsing this codepoint.
assert!(PUA_ENCODE_RANGE.contains(&first_decoded_char));
Some(first_decoded_char)
}
fn replace_if_pua_encoded(c: char) -> char {
if PUA_ENCODE_RANGE.contains(&c) {
REPLACEMENT_CHARACTER
} else {
c
}
}
impl<'a> Iterator for Decoder<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
let mut get_next_char = || {
self.buffer_front
.pop()
.or_else(|| self.encoded_chars.next())
.or_else(|| self.buffer_back.pop_front())
};
let c_0 = get_next_char()?;
if c_0 != ENCODED_PUA_CHAR_FIRST_CHAR {
return Some(replace_if_pua_encoded(c_0));
}
if let Some(c_1) = get_next_char() {
if ENCODED_PUA_CHAR_SECOND_CHAR_RANGE.contains(&c_1) {
if let Some(c_2) = get_next_char() {
if let Some(decoded_pua_char) = try_pua_decoding(&[c_0, c_1, c_2]) {
return Some(decoded_pua_char);
}
self.buffer_front.push(c_2);
}
}
self.buffer_front.push(c_1);
}
// If decoding 3 consecutive PUA chars into the encoded PUA char fails, `c_0` should be
// returned. `c_0` is `ENCODED_PUA_CHAR_FIRST_CHAR` if we reach this point, so return
// the `REPLACEMENT_CHARACTER` in these cases.
Some(REPLACEMENT_CHARACTER)
}
}
impl<'a> DoubleEndedIterator for Decoder<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let mut get_next_char = || {
self.buffer_back
.pop()
.or_else(|| self.encoded_chars.next_back())
.or_else(|| self.buffer_front.pop_front())
};
let c_2 = get_next_char()?;
if !PUA_ENCODE_RANGE.contains(&c_2) {
return Some(c_2);
}
if let Some(c_1) = get_next_char() {
if PUA_ENCODE_RANGE.contains(&c_1) {
if let Some(c_0) = get_next_char() {
if let Some(decoded_pua_char) = try_pua_decoding(&[c_0, c_1, c_2]) {
return Some(decoded_pua_char);
}
self.buffer_back.push(c_0);
}
self.buffer_back.push(c_1);
}
}
// If decoding 3 consecutive PUA chars into the encoded PUA char fails, `c_2` should be
// returned. `c_2` is in `PUA_ENCODE_RANGE` if we reach this point, so return the
// `REPLACEMENT_CHARACTER` in these cases.
Some(REPLACEMENT_CHARACTER)
}
}
}
/// Only exists for tests. Exported because the encoding functionality is not available in this
/// crate. Do not use for non-testing purposes.
pub fn decode_with_replacement(encoded_str: &wstr) -> impl DoubleEndedIterator<Item = char> {
decoder::Decoder::new(encoded_str)
}
/// Takes a PUA-encoded string, decodes it by restoring encoded PUA codepoints and replacing encoded
/// non-UTF-8 bytes by the replacement character U+FFFD.
/// The result is passed to the [`unicode_width`] crate, which will compute its width, which will be
/// the return value of this function.
pub fn decoded_width(encoded_str: &wstr) -> usize {
// TODO: Avoid constructing String by using `unicode_width::char_iter_width` once that is
// available in a released version of the crate (it's already on the crate's master branch).
use unicode_width::UnicodeWidthStr as _;
decoder::Decoder::new(encoded_str)
.collect::<String>()
.width()
}
pub const fn char_offset(base: char, offset: u32) -> char {
match char::from_u32(base as u32 + offset) {
Some(c) => c,
@@ -60,6 +503,82 @@ pub const fn char_offset(base: char, offset: u32) -> char {
}
}
/// Encodes the bytes in `input` into a [`WString`], encoding non-UTF-8 bytes into private-use-area
/// code-points. Bytes which would be parsed into our reserved PUA range are encoded individually,
/// to allow for correct round-tripping.
pub fn bytes2wcstring(mut input: &[u8]) -> WString {
if input.is_empty() {
return WString::new();
}
let mut result = WString::with_capacity(input.len());
fn append_escaped_str(output: &mut WString, input: &str) {
for (i, c) in input.char_indices() {
if fish_reserved_codepoint(c) {
for byte in &input.as_bytes()[i..i + c.len_utf8()] {
output.push(encode_byte_to_char(*byte));
}
} else {
output.push(c);
}
}
}
while !input.is_empty() {
match std::str::from_utf8(input) {
Ok(parsed_str) => {
append_escaped_str(&mut result, parsed_str);
// The entire remaining input could be parsed, so we are done.
break;
}
Err(e) => {
let (valid, after_valid) = input.split_at(e.valid_up_to());
// SAFETY: The previous `str::from_utf8` call established that the prefix `valid`
// is valid UTF-8. This prefix may be empty.
let parsed_str = unsafe { std::str::from_utf8_unchecked(valid) };
append_escaped_str(&mut result, parsed_str);
// The length of the prefix of `after_valid` which is invalid UTF-8.
// The remaining bytes of `input` (if any) will be parsed in subsequent iterations
// of the loop, starting from the first byte that starts a valid UTF-8-encoded codepoint.
// `error_len` can return `None`, if it sees a byte sequence that could be the
// prefix of a valid code-point encoding at the end of the byte slice.
// This is useful when the input is chunked, but we don't do that, so in this case
// we use our custom encoding for all remaining bytes (at most 3).
let error_len = e.error_len().unwrap_or(after_valid.len());
for byte in &after_valid[..error_len] {
result.push(encode_byte_to_char(*byte));
}
input = &after_valid[error_len..];
}
}
}
result
}
/// Use this rather than [`WString::from_str`] when the input could contain PUA bytes we use to
/// encode non-UTF-8 bytes. Otherwise, when decoding the resulting [`WString`], the PUA bytes in
/// the input would be converted to non-UTF-8 bytes.
pub fn str2wcstring<S: AsRef<str>>(input: S) -> WString {
bytes2wcstring(input.as_ref().as_bytes())
}
pub fn cstr2wcstring<C: AsRef<CStr>>(input: C) -> WString {
bytes2wcstring(input.as_ref().to_bytes())
}
pub fn osstr2wcstring<O: AsRef<OsStr>>(input: O) -> WString {
bytes2wcstring(input.as_ref().as_bytes())
}
/// # SAFETY
///
/// `input` must point to a valid NUL-terminated string.
pub unsafe fn charptr2wcstring(input: *const libc::c_char) -> WString {
let input: &[u8] = unsafe { CStr::from_ptr(input).to_bytes() };
bytes2wcstring(input)
}
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
///
/// # Examples

View File

@@ -6,6 +6,8 @@ edition.workspace = true
repository.workspace = true
[dependencies]
anstyle.workspace = true
clap.workspace = true
fish-build-helper.workspace = true
fish-tempfile.workspace = true
walkdir.workspace = true

170
crates/xtask/src/format.rs Normal file
View File

@@ -0,0 +1,170 @@
use anstyle::{AnsiColor, Style};
use clap::Args;
use std::{
io::{ErrorKind, Write},
path::PathBuf,
process::{Command, Stdio},
};
use crate::files_with_extension;
const GREEN: Style = AnsiColor::Green.on_default();
const YELLOW: Style = AnsiColor::Yellow.on_default();
#[derive(Args)]
pub struct FormatArgs {
/// Consider all eligible files.
#[arg(long)]
all: bool,
/// Report files which are not formatted as expected, without modifying any files.
#[arg(long)]
check: bool,
/// Format files even if uncommitted changes are detected.
#[arg(long)]
force: bool,
paths: Vec<PathBuf>,
}
pub fn format(args: FormatArgs) {
if !args.all && args.paths.is_empty() {
println!(
"{YELLOW}warning: No paths specified. Nothing to do. Use the \"--all\" flag to consider all eligible files.{YELLOW:#}"
);
return;
}
if !args.force && !args.check {
match Command::new("git")
.args(["status", "--porcelain", "--short", "--untracked-files=all"])
.output()
{
Ok(output) => {
if !output.stdout.is_empty() {
std::io::stdout().write_all(&output.stdout).unwrap();
print!(
"You have uncommitted changes (listed above). Are you sure you want to format? (y/N): "
);
std::io::stdout().flush().unwrap();
let mut response = String::new();
std::io::stdin().read_line(&mut response).unwrap();
if response.trim_end() != "y" {
println!("Exiting without formatting.");
return;
}
}
}
Err(e) => {
if e.kind() == ErrorKind::NotFound {
println!(
"{YELLOW}warning: Did not find git, will proceed without checking for unstaged changes.{YELLOW:#}"
)
} else {
fail!("Failed to run git status:\n{e}")
}
}
}
}
format_fish(&args);
format_python(&args);
format_rust(&args);
}
fn run_formatter(formatter: &mut Command, name: &str) {
println!("=== Running {GREEN}{name}{GREEN:#}");
match formatter.status() {
Ok(exit_status) => {
if !exit_status.success() {
fail!("{name:?}: Files are not formatted correctly.");
}
}
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
eprintln!(
"{YELLOW}Formatter not found: {name:?}. Skipping associated files.{YELLOW:#}"
);
} else {
fail!("Error occurred while running {name:?}:\n{e}")
}
}
}
}
fn format_fish(args: &FormatArgs) {
let mut fish_paths = files_with_extension(&args.paths, "fish");
if args.all {
let workspace_root = fish_build_helper::workspace_root();
let fish_formatting_dirs = ["benchmarks", "build_tools", "etc", "share"];
fish_paths.extend(files_with_extension(
fish_formatting_dirs
.iter()
.map(|dir_name| workspace_root.join(dir_name)),
"fish",
));
};
if fish_paths.is_empty() {
return;
}
// TODO: make `fish_indent` available as a Rust library function, to avoid needing a
// `fish_indent` binary in `$PATH`.
let mut formatter = Command::new("fish_indent");
if args.check {
formatter.arg("--check");
} else {
formatter.arg("-w");
}
formatter.arg("--");
formatter.args(fish_paths);
run_formatter(&mut formatter, "fish_indent");
}
fn format_python(args: &FormatArgs) {
let mut formatter = Command::new("ruff");
formatter.arg("format");
if args.check {
formatter.arg("--check");
}
let mut python_files = files_with_extension(&args.paths, "py");
if args.all {
python_files.push(fish_build_helper::workspace_root().to_owned());
};
if python_files.is_empty() {
return;
}
formatter.args(python_files);
run_formatter(&mut formatter, "ruff format");
}
fn format_rust(args: &FormatArgs) {
let rustfmt_status = Command::new("cargo")
.arg("fmt")
.arg("--version")
.stdout(Stdio::null())
.status()
.unwrap();
if !rustfmt_status.success() {
eprintln!(
"{YELLOW}Please install \"rustfmt\" to format Rust, e.g. via:\n\
rustup component add rustfmt{YELLOW:#}"
);
return;
}
if args.all {
let mut formatter = Command::new("cargo");
formatter.arg("fmt");
formatter.arg("--all");
if args.check {
formatter.arg("--check");
}
run_formatter(&mut formatter, "cargo fmt");
}
let rust_files = files_with_extension(&args.paths, "rs");
if !rust_files.is_empty() {
let mut formatter = Command::new("rustfmt");
if args.check {
formatter.arg("--check");
formatter.arg("--files-with-diff");
}
formatter.args(rust_files);
run_formatter(&mut formatter, "rustfmt");
}
}

View File

@@ -1,19 +1,34 @@
use std::{ffi::OsStr, process::Command};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::Command,
};
use walkdir::WalkDir;
macro_rules! fail {
($($arg:tt)+) => {{
eprintln!($($arg)+);
std::process::exit(1);
}}
}
pub mod format;
pub trait CommandExt {
fn run_or_panic(&mut self);
fn run_or_fail(&mut self);
}
impl CommandExt for Command {
fn run_or_panic(&mut self) {
fn run_or_fail(&mut self) {
match self.status() {
Ok(exit_status) => {
if !exit_status.success() {
panic!("Command did not run successfully: {:?}", self.get_program())
fail!("Command did not run successfully: {:?}", self.get_program())
}
}
Err(err) => {
panic!("Failed to run command: {err}");
fail!("Failed to run command: {err}")
}
}
}
@@ -24,5 +39,32 @@ pub fn cargo<I, S>(cargo_args: I)
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(env!("CARGO")).args(cargo_args).run_or_panic();
Command::new(env!("CARGO")).args(cargo_args).run_or_fail();
}
fn get_matching_files<P: AsRef<Path>, I: IntoIterator<Item = P>, M: Fn(&Path) -> bool>(
all_paths: I,
matcher: M,
) -> Vec<PathBuf> {
all_paths
.into_iter()
.flat_map(WalkDir::new)
.filter_map(|res| {
let entry = res.unwrap();
let path = entry.path();
if entry.file_type().is_file() && matcher(path) {
Some(path.to_owned())
} else {
None
}
})
.collect()
}
fn files_with_extension<P: AsRef<Path>, I: IntoIterator<Item = P>>(
all_paths: I,
extension: &str,
) -> Vec<PathBuf> {
let matcher = |p: &Path| p.extension().is_some_and(|e| e == extension);
get_matching_files(all_paths, matcher)
}

View File

@@ -1,7 +1,7 @@
use clap::{Parser, Subcommand};
use fish_build_helper::as_os_strs;
use std::{path::PathBuf, process::Command};
use xtask::{CommandExt, cargo};
use xtask::{CommandExt as _, cargo, format::FormatArgs};
#[derive(Parser)]
#[command(
@@ -18,6 +18,8 @@ struct Cli {
enum Task {
/// Run various checks on the repo.
Check,
/// Format files or check if they are correctly formatted.
Format(FormatArgs),
/// Build HTML docs
HtmlDocs {
/// Path to a fish_indent executable. If none is specified, fish_indent will be built.
@@ -32,6 +34,7 @@ fn main() {
let cli = Cli::parse();
match cli.task {
Task::Check => run_checks(),
Task::Format(format_args) => xtask::format::format(format_args),
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
}
@@ -40,7 +43,7 @@ fn main() {
fn run_checks() {
let repo_root_dir = fish_build_helper::workspace_root();
let check_script = repo_root_dir.join("build_tools").join("check.sh");
Command::new(check_script).run_or_panic();
Command::new(check_script).run_or_fail();
}
fn build_html_docs(fish_indent: Option<PathBuf>) {
@@ -91,5 +94,5 @@ fn build_html_docs(fish_indent: Option<PathBuf>) {
Command::new(option_env!("FISH_SPHINX").unwrap_or("sphinx-build"))
.env("PATH", new_path)
.args(args)
.run_or_panic();
.run_or_fail();
}

View File

@@ -46,7 +46,7 @@ The following ``argparse`` options are available. They must appear before all *O
In contrast, if the known option comes first (and does not take any arguments), the known option will be recognised (e.g. ``argparse --move-unknown h -- -ho`` *will* set ``$_flag_h`` to ``-h``)
**-i** or **--ignore-unknown**
Deprecated. This is like **--move-unknown**, except that unknown options and their arguments are kept in ``$argv`` and not moved to ``$argv_opts``. Unlike **--move-unknown**, this option makes it impossible to distinguish between an unknown option and non-option argument that starts with a ``-`` (since any ``--`` seperator in ``$argv`` will be removed).
Deprecated. This is like **--move-unknown**, except that unknown options and their arguments are kept in ``$argv`` and not moved to ``$argv_opts``. Unlike **--move-unknown**, this option makes it impossible to distinguish between an unknown option and non-option argument that starts with a ``-`` (since any ``--`` separator in ``$argv`` will be removed).
**-S** or **--strict-longopts**
This makes the parsing of long options more strict. In particular, *without* this flag, if ``long`` is a known long option flag, ``--long`` and ``--long=<value>`` can be abbreviated as:
@@ -251,7 +251,7 @@ Some *OPTION_SPEC* examples:
- ``n/name=?`` means that both ``-n`` and ``--name`` are valid. It accepts an optional value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurrence. Each value will be the value given to the option, or the empty string if no value was given.
- ``name=+`` means that only ``--name`` is valid. It requires a value and can be used more than once. If the flag is seen then ``_flag_name`` will be set with the values associated with each occurrence.

View File

@@ -102,7 +102,7 @@ The following options are available:
**--preset** should only be used in full binding sets (like when working on ``fish_vi_key_bindings``).
**-s** or **--silent**
Silences some of the error messages, including for unknown key names and unbound sequences.
Silences error message for unbound sequences.
**--color** *WHEN*
Controls when to use syntax highlighting colors when listing bindings.
@@ -401,6 +401,13 @@ The following special input functions are available:
``self-insert-notfirst``
inserts the matching sequence into the command line, unless the cursor is at the beginning
``get-key``
sets :envvar:`fish_key` to the key that was pressed to trigger this binding. Example use::
for i in (seq 0 9)
bind $i get-key 'commandline -i "#$fish_key"' 'set -eg fish_key'
end
``suppress-autosuggestion``
remove the current autosuggestion. Returns true if there was a suggestion to remove.

View File

@@ -34,6 +34,6 @@ A simple prompt that is a simplified version of the default debugging prompt::
set -l function (status current-function)
set -l line (status current-line-number)
set -l prompt "$function:$line >"
echo -ns (set_color $fish_color_status) "BP $prompt" (set_color normal) ' '
echo -ns (set_color $fish_color_status) "BP $prompt" (set_color --reset) ' '
end

View File

@@ -85,10 +85,10 @@ The format looks like this:
fish_color_command 5c5cff
[unknown]
fish_color_normal normal
fish_color_normal --reset
fish_color_autosuggestion brblack
fish_color_cancel -r
fish_color_command normal
fish_color_command --reset
The comments provide name and background color to the web config tool.

View File

@@ -22,6 +22,8 @@ When an interactive fish starts, it executes fish_greeting and displays its outp
The default fish_greeting is a function that prints a variable of the same name (``$fish_greeting``), so you can also just change that if you just want to change the text.
If :envvar:`SHELL_WELCOME` is set, it is displayed after the greeting. This is a standard environment variable that may be set by tools like systemd's ``run0`` to display session information.
While you could also just put ``echo`` calls into config.fish, fish_greeting takes care of only being used in interactive shells, so it won't be used e.g. with ``scp`` (which executes a shell), which prevents some errors.
Example
@@ -39,5 +41,5 @@ A simple greeting:
function fish_greeting
echo Hello friend!
echo The time is (set_color yellow)(date +%T)(set_color normal) and this machine is called $hostname
echo The time is (set_color yellow)(date +%T)(set_color --reset) and this machine is called $hostname
end

View File

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

View File

@@ -24,6 +24,8 @@ The exit status of commands within ``fish_prompt`` will not modify the value of
If :envvar:`fish_transient_prompt` is set to 1, ``fish_prompt --final-rendering`` is run before executing the commandline.
If :envvar:`SHELL_PROMPT_PREFIX` or :envvar:`SHELL_PROMPT_SUFFIX` are set, they are automatically prepended and appended to the left prompt. This applies to all prompts regardless of whether ``fish_prompt`` has been customized.
``fish`` ships with a number of example prompts that can be chosen with the ``fish_config`` command.
@@ -41,7 +43,7 @@ A simple prompt:
# $USER and $hostname are set by fish, so you can just use them
# instead of using `whoami` and `hostname`
printf '%s@%s %s%s%s > ' $USER $hostname \
(set_color $fish_color_cwd) (prompt_pwd) (set_color normal)
(set_color $fish_color_cwd) (prompt_pwd) (set_color --reset)
end

View File

@@ -16,7 +16,7 @@ Description
``if`` will execute the command ``CONDITION``. If the condition's exit status is 0, the commands ``COMMANDS_TRUE`` will execute. If the exit status is not 0 and :doc:`else <else>` is given, ``COMMANDS_FALSE`` will be executed.
You can use :doc:`and <and>` or :doc:`or <or>` in the condition. See the second example below.
You can use :doc:`not <not>`, :doc:`and <and>` or :doc:`or <or>` in the condition. See the second example below.
The exit status of the last foreground command to exit can always be accessed using the :ref:`$status <variables-status>` variable.

View File

@@ -62,7 +62,7 @@ The following options control the interactive mode:
Masks characters written to the terminal, replacing them with asterisks. This is useful for reading things like passwords or other sensitive information.
**-p** or **--prompt** *PROMPT_CMD*
Uses the output of the shell command *PROMPT_CMD* as the prompt for the interactive mode. The default prompt command is ``set_color green; echo -n read; set_color normal; echo -n "> "``
Uses the output of the shell command *PROMPT_CMD* as the prompt for the interactive mode. The default prompt command is ``set_color green; echo -n read; set_color --reset; echo -n "> "``
**-P** or **--prompt-str** *PROMPT_STR*
Uses the literal *PROMPT_STR* as the prompt for the interactive mode.

View File

@@ -6,16 +6,17 @@ Synopsis
.. synopsis::
set
set (-f | --function) (-l | --local) (-g | --global) (-U | --universal) [--no-event]
set [-Uflg] NAME [VALUE ...]
set [-Uflg] NAME[[INDEX ...]] [VALUE ...]
set (-x | --export) (-u | --unexport) [-Uflg] NAME [VALUE ...]
set (-a | --append) (-p | --prepend) [-Uflg] NAME VALUE ...
set (-e | --erase) [-Uflg] [-xu] [NAME][[INDEX]] ...]
set (-q | --query) [-Uflg] [-xu] [NAME][[INDEX]] ...]
set [(-f | --function) (-l | --local) (-g | --global) (-U | --universal)]
[(-x | --export) (-u | --unexport)]
set (-S | --show) (-L | --long) [NAME ...]
set [-Uflg] [-xu] [--no-event] NAME [VALUE ...]
set [-Uflg] [--no-event] NAME[[INDEX ...]] [VALUE ...]
set (-a | --append) (-p | --prepend) [-Uflg] [--no-event] NAME VALUE ...
set (-e | --erase) [-Uflg] [--no-event] NAME[[INDEX]] ...
set (-q | --query) [-Uflg] [-xu] NAME[[INDEX]] ...
Description
-----------
@@ -89,7 +90,7 @@ Further options:
**-q** or **--query** *NAME*\[*INDEX*\]
Test if the specified variable names are defined.
If an *INDEX* is provided, check for items at that slot.
With a given scope (like **--global**) or attribute (like **--exported** or **--path**) check only variables that match.
With a given scope (like **--global**) or attribute (like **--export** or **--path**) check only variables that match.
Does not output anything, but the shell status is set to the number of variables specified that were not defined, up to a maximum of 255.
If no variable was given, it also returns 255.

View File

@@ -6,12 +6,15 @@ Synopsis
.. synopsis::
set_color [OPTIONS] VALUE
set_color [OPTIONS] [VALUE]
Description
-----------
``set_color`` is used to control the color and styling of text in the terminal. *VALUE* describes that styling. *VALUE* can be a reserved color name like **red** or an RGB color value given as 3 or 6 hexadecimal digits ("F27" or "FF2277"). A special keyword **normal** resets text formatting to terminal defaults.
``set_color`` is used to control the color and styling of text in the terminal.
*VALUE* describes that styling.
*VALUE* can be a reserved color name like **red** or an RGB color value given as 3 or 6 hexadecimal digits ("F27" or "FF2277").
A special keyword **normal** resets text formatting to terminal defaults, however it is not recommended and the **--reset** option is preferred as it is less confusing and more future-proof.
Valid colors include:
@@ -33,6 +36,11 @@ However if :envvar:`fish_term256` is set to 0, fish prefers the first named colo
The following options are available:
**-f** or **--foreground** *COLOR*
Sets the foreground color.
This is equivalent to calling ``set_color COLOR`` with the exception that the keyword **normal** will only reset the foreground color to its default, instead of all colors and modes.
It cannot be used with *VALUE* or **--print-colors**.
**-b** or **--background** *COLOR*
Sets the background color.
@@ -41,6 +49,7 @@ The following options are available:
**-c** or **--print-colors**
Prints the given colors or a colored list of the 16 named colors.
It cannot be used with **--foreground**.
**-o** or **--bold**
Sets bold mode.
@@ -48,17 +57,21 @@ The following options are available:
**-d** or **--dim**
Sets dim mode.
**-i** or **--italics**
Sets italics mode.
**-i** or **--italics**, or **-iSTATE** or **--italics=STATE**
Sets italics mode. The state can be **on** (default), or **off**.
**-r** or **--reverse**
Sets reverse mode.
**-r** or **--reverse**, or **-iSTATE** or **--reverse=STATE**
Sets reverse mode. The state can be **on** (default), or **off**.
**-s** or **--strikethrough**
Sets strikethrough mode.
**-s** or **--strikethrough**, or **-sSTATE** or **--strikethrough=STATE**
Sets strikethrough mode. The state can be **on** (default), or **off**.
**-u** or **--underline**, or **-uSTYLE** or **--underline=STYLE**
Set the underline mode; supported styles are **single** (default), **double**, **curly**, **dotted** and **dashed**.
Set the underline mode; supported styles are **single** (default), **double**, **curly**, **dotted**, **dashed** and **off**.
**--reset**
Reset the text formatting to the terminal defaults before applying the new colors and modes.
This is equivalent to calling ``set_color normal`` except that it is possible to set the foreground color in the same call (e.g. ``set_color --reset green``)
**--theme=THEME**
Ignored.
@@ -75,10 +88,12 @@ The following options are available:
Notes
-----
1. Using **set_color normal** will reset all colors and modes to the terminal's default.
2. 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.
3. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
4. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
1. Using ``set_color normal`` will reset all colors and modes to the terminal's default.
2. In contrast, ``set_color --foreground normal`` will only reset the foreground color and leave all the other colors and modes unchanged.
3. Because of the risk of confusion, ``set_color --reset`` is recommended over ``set_color normal``.
4. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
5. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
6. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
Examples
--------

View File

@@ -91,7 +91,7 @@ The prompt is the output of the ``fish_prompt`` function. Put it in ``~/.config/
function fish_prompt
set_color $fish_color_cwd
echo -n (prompt_pwd)
set_color normal
set_color --reset
echo -n ' > '
end
@@ -329,7 +329,7 @@ Sometimes, there is disagreement on the width. There are numerous causes and fix
- 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.
- 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 the host system only supports Unicode 8, while you are running the terminal on a system that uses Unicode >= 9. In this case set $fish_emoji_width to 2.
- The character is an emoji and your system only supports Unicode 8. In this case set $fish_emoji_width to 1.
This also means that a few things are unsupportable:

View File

@@ -318,7 +318,7 @@ and a rough fish equivalent::
echo -s (prompt_hostname) \
(set_color blue) (prompt_pwd) \
(set_color yellow) $prompt_symbol (set_color normal)
(set_color yellow) $prompt_symbol (set_color --reset)
end
This shows a few differences:

View File

@@ -54,7 +54,7 @@ It also provides a large number of program specific scripted completions. Most o
You can also write your own completions or install some you got from someone else. For that, see :doc:`Writing your own completions <completions>`.
Completion scripts are loaded on demand, like :ref:`functions are <syntax-function-autoloading>`. The difference is the ``$fish_complete_path`` :ref:`list <variables-lists>` is used instead of ``$fish_function_path``. Typically you can drop new completions in ~/.config/fish/completions/name-of-command.fish and fish will find them automatically.
Completion scripts are loaded on demand, like :ref:`functions are <syntax-function-autoloading>`. The difference is the ``$fish_complete_path`` :ref:`list <variables-lists>` is used instead of ``$fish_function_path``. Typically you can drop new completions in ``~/.config/fish/completions/<name-of-command>.fish`` and fish will find them automatically.
.. _syntax-highlighting:
@@ -105,6 +105,7 @@ Syntax highlighting variables
The colors used by fish for syntax highlighting can be configured by changing the values of various variables. The value of these variables can be one of the colors accepted by the :doc:`set_color <cmds/set_color>` command.
Options accepted by ``set_color`` like
``--foreground=``,
``--background=``,
``--bold``,
``--dim``,

View File

@@ -235,7 +235,7 @@ It is possible to pipe a different output file descriptor by prepending its FD n
will attempt to build ``fish``, and any errors will be shown using the ``less`` pager. [#]_
As a convenience, the pipe ``&|`` redirects both stdout and stderr to the same process. This is different from bash, which uses ``|&``.
As a convenience, the pipe ``&|`` (as well as the ``|&`` alias which is also supported by Bash) both redirect stdout and stderr to the same process.
.. [#] A "pager" here is a program that takes output and "paginates" it. ``less`` doesn't just do pages, it allows arbitrary scrolling (even back!).
@@ -1573,7 +1573,7 @@ You can change the settings of fish by changing the values of certain variables.
.. envvar:: fish_emoji_width
controls whether fish assumes emoji render as 2 cells or 1 cell wide. This is necessary because the correct value changed from 1 to 2 in Unicode 9, and some terminals may not be aware. Set this if you see graphical glitching related to emoji (or other "special" characters). It should usually be auto-detected.
controls whether fish assumes emoji render as 2 cells or 1 cell wide. This is necessary because the correct value changed from 1 to 2 in Unicode 9, and some terminals may not be aware. Set this if you see graphical glitching related to emoji (or other "special" characters). It defaults to 2.
.. envvar:: fish_autosuggestion_enabled
@@ -1646,6 +1646,18 @@ You can change the settings of fish by changing the values of certain variables.
the current file creation mask. The preferred way to change the umask variable is through the :doc:`umask <cmds/umask>` function. An attempt to set umask to an invalid value will always fail.
.. envvar:: SHELL_PROMPT_PREFIX
if set, this string is automatically prepended to the left prompt. This is a standard environment variable that may be set by tools like systemd's ``run0`` to indicate special shell sessions.
.. envvar:: SHELL_PROMPT_SUFFIX
if set, this string is automatically appended to the left prompt. This is a standard environment variable that may be set by tools like systemd's ``run0`` to indicate special shell sessions.
.. envvar:: SHELL_WELCOME
if set, this string is displayed when an interactive shell starts, after the greeting. This is a standard environment variable that may be set by tools like systemd's ``run0`` to display session information.
.. envvar:: BROWSER
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.
@@ -2043,8 +2055,8 @@ Here is what they mean:
- ``ampersand-nobg-in-token`` was introduced in fish 3.4 (and made the default in 3.5). It makes it so a ``&`` is no longer interpreted as the backgrounding operator in the middle of a token, so dealing with URLs becomes easier. Either put spaces or a semicolon after the ``&``. This is recommended formatting anyway, and ``fish_indent`` will have done it for you already.
- ``remove-percent-self`` turns off the special ``%self`` expansion. It was introduced in 4.0. To get fish's pid, you can use the :envvar:`fish_pid` variable.
- ``test-require-arg`` removes :doc:`builtin test <cmds/test>`'s one-argument form (``test "string"``. It was introduced in 4.0. To test if a string is non-empty, use ``test -n "string"``. If disabled, any call to ``test`` that would change sends a :ref:`debug message <debugging-fish>` of category "deprecated-test", so starting fish with ``fish --debug=deprecated-test`` can be used to find offending calls.
- ``mark-prompt`` makes fish report to the terminal the beginning and and of both shell prompts and command output.
- ``ignore-terminfo`` disables lookup of $TERM in the terminfo database. Use ``no-ignore-terminfo`` to turn it back on.
- ``mark-prompt`` makes fish report to the terminal the beginning and end of both shell prompts and command output.
- ``ignore-terminfo`` was introduced in fish 4.1 and cannot be turned off since fish 4.5. It can still be tested for compatibility, but a ``no-ignore-terminfo`` value will be ignored. The flag disabled lookup of $TERM in the terminfo database.
- ``query-term`` allows fish to query the terminal by writing escape sequences and reading the terminal's response.
This enables features such as :ref:`scrolling <term-compat-cursor-position-report>`.
If you use an incompatible terminal, you can -- for the time being -- work around it by running (once) ``set -Ua fish_features no-query-term``.

View File

@@ -19,6 +19,8 @@ Unlike other shells, fish's prompt is built by running a function - :doc:`fish_p
These functions are run, and whatever they print is displayed as the prompt (minus one trailing newline).
If the :envvar:`SHELL_PROMPT_PREFIX` or :envvar:`SHELL_PROMPT_SUFFIX` environment variables are set, they are automatically prepended and appended to the left prompt.
Here, we will just be writing a simple fish_prompt.
Our first prompt
@@ -67,10 +69,10 @@ Fortunately, fish offers the :doc:`set_color <cmds/set_color>` command, so you c
So, taking our previous prompt and adding some color::
function fish_prompt
string join '' -- (set_color green) $PWD (set_color normal) '>'
string join '' -- (set_color green) $PWD (set_color --reset) '>'
end
A "normal" color tells the terminal to go back to its normal formatting options.
"--reset" tells the terminal to go back to its default formatting options.
``set_color`` works by producing an escape sequence, which is a special piece of text that terminals
interpret as instructions - for example, to change color. So ``set_color red`` produces the same
@@ -86,13 +88,13 @@ Shortening the working directory
This is fine, but our :envvar:`PWD` can be a bit long, and we are typically only interested in the last few directories. We can shorten this with the :doc:`prompt_pwd <cmds/prompt_pwd>` helper that will give us a shortened working directory::
function fish_prompt
string join '' -- (set_color green) (prompt_pwd) (set_color normal) '>'
string join '' -- (set_color green) (prompt_pwd) (set_color --reset) '>'
end
``prompt_pwd`` takes options to control how much to shorten. For instance, if we want to display the last two directories, we'd use ``prompt_pwd --full-length-dirs 2``::
function fish_prompt
string join '' -- (set_color green) (prompt_pwd --full-length-dirs 2) (set_color normal) '>'
string join '' -- (set_color green) (prompt_pwd --full-length-dirs 2) (set_color --reset) '>'
end
With a current directory of "/home/tutorial/Music/Lena Raine/Oneknowing", this would print
@@ -119,12 +121,12 @@ And after that, you can set a string if it is not zero::
# Prompt status only if it's not 0
set -l stat
if test $last_status -ne 0
set stat (set_color red)"[$last_status]"(set_color normal)
set stat (set_color red)"[$last_status]"(set_color --reset)
end
And to print it, we add it to our ``string join``::
string join '' -- (set_color green) (prompt_pwd) (set_color normal) $stat '>'
string join '' -- (set_color green) (prompt_pwd) (set_color --reset) $stat '>'
If ``$last_status`` was 0, ``$stat`` is empty, and so it will simply disappear.
@@ -135,10 +137,10 @@ So our entire prompt is now::
# Prompt status only if it's not 0
set -l stat
if test $last_status -ne 0
set stat (set_color red)"[$last_status]"(set_color normal)
set stat (set_color red)"[$last_status]"(set_color --reset)
end
string join '' -- (set_color green) (prompt_pwd) (set_color normal) $stat '>'
string join '' -- (set_color green) (prompt_pwd) (set_color --reset) $stat '>'
end
And it looks like:
@@ -176,11 +178,11 @@ So you can use it to declutter your old prompts. For example if you want to see
set pwd (prompt_pwd)
# Prompt status only if it's not 0
if test $last_status -ne 0
set stat (set_color red)"[$last_status]"(set_color normal)
set stat (set_color red)"[$last_status]"(set_color --reset)
end
end
string join '' -- (set_color green) $pwd (set_color normal) $stat '>'
string join '' -- (set_color green) $pwd (set_color --reset) $stat '>'
end
Now running two commands in the same directory could result in this screen:

View File

@@ -155,6 +155,9 @@ Optional Commands
* - ``\e[48;2; Ps ; Ps ; Ps m``
-
- Select background color from 24-bit RGB colors.
* - ``\e[39m``
-
- Reset foreground color to the terminal's default.
* - ``\e[49m``
-
- Reset background color to the terminal's default.

View File

@@ -633,7 +633,7 @@ Multiple lines are OK. Colors can be set via :doc:`set_color <cmds/set_color>` b
set_color purple
date "+%m/%d/%y"
set_color FF0000
echo (pwd) '>' (set_color normal)
echo (pwd) '>' (set_color --reset)
end

View File

@@ -37,8 +37,8 @@ RUN adduser \
fishuser
RUN mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
USER fishuser
WORKDIR /home/fishuser

View File

@@ -2,24 +2,24 @@ FROM fedora:latest
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
RUN dnf install --assumeyes \
diffutils \
gcc-c++ \
git-core \
pcre2-devel \
python3 \
python3-pip \
openssl \
procps \
sudo && \
diffutils \
gcc-c++ \
git-core \
pcre2-devel \
python3 \
python3-pip \
openssl \
procps \
sudo && \
dnf clean all
RUN pip3 install pexpect
RUN groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser -G wheel \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser -G wheel \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
USER fishuser
WORKDIR /home/fishuser

View File

@@ -5,28 +5,28 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN zypper --non-interactive install \
bash \
diffutils \
gcc-c++ \
git-core \
pcre2-devel \
python311 \
python311-pip \
python311-pexpect \
openssl \
procps \
tmux \
sudo \
rust \
cargo
bash \
diffutils \
gcc-c++ \
git-core \
pcre2-devel \
python311 \
python311-pip \
python311-pexpect \
openssl \
procps \
tmux \
sudo \
rust \
cargo
RUN usermod -p $(openssl passwd -1 fish) root
RUN groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:fishuser /home/fishuser /fish-source
USER fishuser
WORKDIR /home/fishuser

View File

@@ -6,37 +6,37 @@ 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
&& 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 userdel ubuntu \
&& groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
-G sudo \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:1000 /home/fishuser /fish-source
&& groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
-G sudo \
&& mkdir -p /home/fishuser/fish-build \
&& mkdir /fish-source \
&& chown -R fishuser:1000 /home/fishuser /fish-source
USER fishuser
WORKDIR /home/fishuser
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh \
&& sh /tmp/rustup.sh -y --no-modify-path
&& sh /tmp/rustup.sh -y --no-modify-path
ENV PATH=/home/fishuser/.cargo/bin:$PATH
COPY fish_run_tests.sh /

View File

@@ -6,36 +6,36 @@ 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
&& 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
&& 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
&& sh /tmp/rustup.sh -y --no-modify-path
ENV PATH=/home/fishuser/.cargo/bin:$PATH
COPY fish_run_tests.sh /

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

80820
localization/po/es.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

80823
localization/po/ja_JP.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -4,25 +4,31 @@ complete -c ansible-galaxy -s h -l help -d "Show help message"
complete -c ansible-galaxy -n __fish_use_subcommand -s v -l verbose -d "Verbose mode (-vvv for more, -vvvv for connection debugging)"
# first subcommand
complete -c ansible-galaxy -n __fish_use_subcommand -xa "collection\t'Manage a collection'
role\t'Manage a role'"
complete -c ansible-galaxy -n __fish_use_subcommand -xa "
collection\t'Manage a collection'
role\t'Manage a role'
"
# second subcommand (for collection)
complete -c ansible-galaxy -n '__fish_seen_subcommand_from collection' -a "download\t'Download collections as tarball'
init\t'Initialize new collection with the base structure'
build\t'Build collection artifact that can be published'
publish\t'Publish collection artifact to Ansible Galaxy'
install\t'Install collections'
list\t'Show collections installed'
verify\t'Compare checksums of local and remote collections'"
complete -c ansible-galaxy -n '__fish_seen_subcommand_from collection' -a "
download\t'Download collections as tarball'
init\t'Initialize new collection with the base structure'
build\t'Build collection artifact that can be published'
publish\t'Publish collection artifact to Ansible Galaxy'
install\t'Install collections'
list\t'Show collections installed'
verify\t'Compare checksums of local and remote collections'
"
# second subcommand (for role)
complete -c ansible-galaxy -n '__fish_seen_subcommand_from role' -a "init\t'Initialize new role with the base structure'
remove\t'Delete roles from roles_path'
delete\t'Removes the role from Galaxy'
list\t'Show roles installed'
search\t'Search the Galaxy database by keywords'
import\t'Import role into a galaxy server'
setup\t'Manage integration between Galaxy and the given source'
info\t'View details about a role'
install\t'Install roles'"
complete -c ansible-galaxy -n '__fish_seen_subcommand_from role' -a "
init\t'Initialize new role with the base structure'
remove\t'Delete roles from roles_path'
delete\t'Removes the role from Galaxy'
list\t'Show roles installed'
search\t'Search the Galaxy database by keywords'
import\t'Import role into a galaxy server'
setup\t'Manage integration between Galaxy and the given source'
info\t'View details about a role'
install\t'Install roles'
"

View File

@@ -3,7 +3,7 @@
## --- WRITTEN MANUALLY ---
function __fish_cargo
cargo --color=never $argv
RUSTUP_AUTO_INSTALL=0 cargo --color=never $argv
end
set -l __fish_cargo_subcommands (__fish_cargo --list 2>&1 | string replace -rf '^\s+([^\s]+)\s*(.*)' '$1\t$2' | string escape)

View File

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

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