Compare commits

..

58 Commits

Author SHA1 Message Date
Johannes Altmanninger
3fa252677a Release 4.1.2
Created by ./build_tools/release.sh 4.1.2
2025-10-07 22:56:06 +02:00
Johannes Altmanninger
e074b27abf release.sh: fix deployment approval logic 2025-10-07 22:55:04 +02:00
Johannes Altmanninger
a5727e59da tests/checks/fish_config: fix for non-embedded builds
(cherry picked from commit e3ebda3647)
2025-10-07 22:23:51 +02:00
Johannes Altmanninger
4e68f8a130 release.sh: check fish-site worktree staleness
(cherry picked from commit 092e7fa274)
2025-10-07 22:23:51 +02:00
Johannes Altmanninger
417755b6a7 tests/checks/config-paths-standalone.fish: fix bad assertion
Fixes #11906

(cherry picked from commit 724416125e)
2025-10-07 22:00:31 +02:00
Johannes Altmanninger
ef30feda12 Sanitize cursor position report on kitty click_events
This is easy to trigger by having a background process do "echo" to
move the terminal cursor to the next line, and then clicking anywhere.

Fixes #11905

(cherry picked from commit dd47c2baa2)
2025-10-07 21:54:40 +02:00
Johannes Altmanninger
20e3ed23b7 fish_config: fix regression "theme show" not showing custom themes
This regressed in 6f0532460a5~2..6f0532460a5 (fish_config: fix for
non-embedded builds, 2025-09-28).

Fixes #11903

(cherry picked from commit 15065255e9)
2025-10-07 21:54:40 +02:00
Johannes Altmanninger
7bfdb8460d Embedded builds to use $workspace_root/etc again if run from build dir
Commit f05ad46980 (config_paths: remove vestiges of installable
builds, 2025-09-06) removed a bunch of code paths for embed-data
builds, since those builds can do without most config paths.

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

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

	fish --profile-startup /dev/stdout

will not contain "builtin source".

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

Fixes #11900

(cherry picked from commit 3fec9c8145)
2025-10-07 15:26:09 +02:00
Johannes Altmanninger
8d9d5816ed Extract constant for resolved build directory
Also use a different name than for the CMake variable, to reduce
confusion.

(cherry picked from commit a0b22077a5)
2025-10-07 15:26:09 +02:00
Johannes Altmanninger
97f6129dea Fix regression on paste in non-interactive read
As reported in
https://github.com/fish-shell/fish-shell/issues/11836#issuecomment-3369973613,
running "fish -c read" and pasting something would result this error
from __fish_paste:

	commandline: Can not set commandline in non-interactive mode

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

But this is not the only case where fish might read from the TTY.
The notable other case is builtin read, which also works in
noninteractive shells.
Let's allow "commandline" at least after we have initialized the TTY
for the first reader, which restores the relevant historical behavior
(which is weird, e.g. « fish -c 'read; commandline foo' »).

(cherry picked from commit a958f23f63)
2025-10-06 15:12:13 +02:00
Xiretza
c19de5aeb9 Make command run by __fish_echo output to TTY for color detection
Without this, e.g. Alt-L shows the directory entries one per line and
without colors.

Closes #11888

(cherry picked from commit b7fabb11ac)
2025-10-06 15:12:13 +02:00
Johannes Altmanninger
874fba1108 Don't use kitty keyboard protocol support to decide timeout
As reported in
https://github.com/fish-shell/fish-shell/discussions/11868, some
terminals advertise support for the kitty keyboard protocol despite
it not necessarily being enabled.

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

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

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

This obsoletes and hence reverts commit 623c14aed0 (Kitty keyboard
protocol is non-functional on old versions of Zellij, 2025-10-04).

(cherry picked from commit 6accc475c9)
2025-10-06 15:12:12 +02:00
Johannes Altmanninger
695f225cb4 Kitty keyboard protocol is non-functional on old versions of Zellij
try_readb() uses a high timeout when the kitty keyboard protocol is
enabled, because in that case it should basically never be necessary
to interpret \e as escape key, see 30ff3710a0 (Increase timeout when
reading escape sequences inside paste/kitty kbd, 2025-07-24).

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

Closes #11868

(cherry picked from commit 623c14aed0)
2025-10-04 07:23:59 +02:00
Johannes Altmanninger
9f36adb731 Refresh TTY timestamps after firing focus events
Using a multi-line prompt with focus events on:

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

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

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

Closes #11870

(cherry picked from commit 7d83dc4758)
2025-10-03 22:41:20 +02:00
Johannes Altmanninger
3ff699d7ea Update changelog for patch release
(cherry picked from commit 493d0bca95)
2025-10-03 22:41:10 +02:00
Étienne Deparis
4b6ad751e8 web_config: Support long options separated with = from their value
Closes #11861

(cherry picked from commit 8adc598e90)
2025-10-03 22:38:25 +02:00
Étienne Deparis
8dd90085b6 web_config: Use None as default for underline style
Underline is no more a boolean and should be one of the accepted style,
or None. By keeping False as default value, web_config was generating
wrong --underline=False settings

Part of #11861

(cherry picked from commit c884c08257)
2025-10-03 22:38:25 +02:00
Johannes Altmanninger
47396e78ce Stop requesting modifyOtherKeys on old Midnight Commander again
This upstream issue was fixed in 0ea77d2ec (Ticket #4597: fix CSI
parser, 2024-10-09); for old mc's we had worked around this but the
workaround was accidentally removed. Add it back for all the versions
that don't have that fix.

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

Fixes #11869

(cherry picked from commit fcd246064b)
2025-10-03 22:38:25 +02:00
Johannes Altmanninger
77a038541a Harmonize temporary Midnight Commander workarounds a bit
(cherry picked from commit 86a0a348ee)
2025-10-03 22:38:25 +02:00
Johannes Altmanninger
84dbdf4e2a Unbreak hack to strip " (deleted)" suffix from executable path
Commit 49b88868df (Fix stripping of " (deleted)" from non-UTF8 paths
to fish, 2024-10-12) was wrong because Path::ends_with() considers
entire path components. Fix that.

Refs:
- https://matrix.to/#/!YLTeaulxSDauOOxBoR:matrix.org/$k2IQazfmztFUXrairmIQvx_seS1ZJ7HlFWhmNy479Dg
- https://matrix.to/#/!YLTeaulxSDauOOxBoR:matrix.org/$4pugfHejL9J9L89zuFU6Bfg41UMjA0y79orc3EaBego

(cherry picked from commit 7b59ae0d82)
2025-10-03 22:38:25 +02:00
Johannes Altmanninger
0a50ba9ef5 release.sh: add next patch milestone
This is still the common case.

(cherry picked from commit ed36e852d2)
2025-10-03 22:38:25 +02:00
Johannes Altmanninger
8b317a192b release.sh: close milestone when done
Don't fail early if this doesn't exist, because it's not needed for
testing this on fish-shell forks that live on GitHub.

(cherry picked from commit da5d93c1e2)
2025-10-03 22:38:25 +02:00
Fabian Boehm
42decbf1da Fix scp completions
Introduced when __fish_mktemp_relative was.

Fixes #11860

(cherry picked from commit 97acc12d62)
2025-10-02 18:40:57 +02:00
Johannes Altmanninger
79b0d24701 release.sh: sunset release announcement email
I'm not sure if our peer projects do this or if it's useful to have
on top of github releases (especially as most releases are patch
releases mainly).

We could certainly use "sphinx-build -b text" in
build_tools/release-notes.sh to extract a nice plaintext changelog
and send that, but that doesn't support links.  Not sure about HTML
email either.

(cherry picked from commit 62543b36a4)
2025-10-01 08:10:20 +02:00
Johannes Altmanninger
1f6cd5fc2d Release 4.1.1
Created by ./build_tools/release.sh 4.1.1
2025-09-30 19:27:13 +02:00
Johannes Altmanninger
635525be96 Changelog update for 4.1.1
(cherry picked from commit 412149a5de)
2025-09-30 19:26:51 +02:00
Johannes Altmanninger
280791c0f5 Increase escape sequence timeout while waiting for query response
Running "fish -d reader" inside SSH inside Windows terminal sometimes
results in hangs on startup (or whenever we run "scrollback-push"),
because not all of the Primary DA response is available for reading
at once:

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

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

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

Fixes #11841

(cherry picked from commit abd23d2a1b)
2025-09-30 19:05:04 +02:00
Johannes Altmanninger
91faa1be49 Changelog for translation fixes from #11833
(cherry picked from commit b774c54a6f)
2025-09-30 19:05:04 +02:00
王宇逸
24e529ea07 zh_CN: fix tier 1 translations
Closes #11833

(cherry picked from commit e4b797405b)
2025-09-30 19:05:04 +02:00
Johannes Altmanninger
595bfda331 release-notes.sh: fix sphinx warning for patch release notes
Integration_4.1.1 fails to generate release notes with

	CHANGELOG.rst:9: WARNING: Bullet list ends without a blank
	line; unexpected unindent. [docutils].

(cherry picked from commit 81a89a5dec)
2025-09-30 19:05:04 +02:00
Johannes Altmanninger
0f358756a2 Primary Device Attribute is a proper noun
We don't care about any specific attributes but we do very much care
about the specific query and response format associated with VT100's
primary device attribute query. Use a proper noun to emphasize that
we want that one and no other.

Ref: https://github.com/fish-shell/fish-shell/pull/11833#discussion_r2385659040
(cherry picked from commit 0da12a6b55)
2025-09-30 12:08:37 +02:00
Johannes Altmanninger
0ace46d127 build.rs: fix MSRV (1.70) clippy
(cherry picked from commit 86ec8994e6)
2025-09-30 12:08:37 +02:00
Johannes Altmanninger
a143a67318 po/de.po: copy-edit German translations
Go through all existing translations except for tier3.

(cherry picked from commit caf426ddb2)
2025-09-30 11:52:43 +02:00
Johannes Altmanninger
047b9d96a0 builtin commandline: fix completion description
(cherry picked from commit 508ae410a6)
2025-09-30 11:52:43 +02:00
Johannes Altmanninger
c942b034ba builtin function: remove dead code
(cherry picked from commit 993b977c9b)
2025-09-30 11:52:42 +02:00
Johannes Altmanninger
0f1caacae4 builtin function: fix a misleading error message
Issue introduced in 7914c92824 (replaced the functions '--rename'
option with '--copy'., 2010-09-09).

(cherry picked from commit a7f0138fc7)
2025-09-30 11:52:42 +02:00
Johannes Altmanninger
f332cdc757 builtin set: fix regression in error message description
(cherry picked from commit ab3c932903)
2025-09-30 11:52:42 +02:00
Johannes Altmanninger
c6912c6721 Remove translations for some error messages that basically never happen
Executable path is empty only in contrived circumstances.

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

The orphaned process error seems very unlikely, not worth translating.

(cherry picked from commit ae0fdadcff)
2025-09-30 11:52:42 +02:00
Johannes Altmanninger
678edbd223 Fix short description for builtin wait
(cherry picked from commit e3974989d8)
2025-09-30 11:52:42 +02:00
Johannes Altmanninger
1a78407efe Translation update implied by parent commit
Part of #11833

(cherry picked from commit 080b1e0e4f)
2025-09-30 11:52:41 +02:00
Johannes Altmanninger
3ecf1bc46d po: add section markers to indicate translation priority
Part of #11833

(cherry picked from commit a5db91dd85)
2025-09-30 11:52:41 +02:00
Johannes Altmanninger
4d99e51e62 Translation update implied by parent commit
Part of #11833

(cherry picked from commit b62f54631b)
2025-09-30 11:52:41 +02:00
Johannes Altmanninger
a9576d44e3 Prepare to not localize private function descriptions
The overwhelming majority of localizable messages comes from
completions:

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

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

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

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

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

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

Part of #11833

(cherry picked from commit d835c5252a)
2025-09-30 11:52:41 +02:00
Johannes Altmanninger
2c0e912fe1 Mark private functions that don't need localization
See the next commit.

Part of #11833

(cherry picked from commit a53db72564)
2025-09-30 11:52:41 +02:00
Johannes Altmanninger
1f96e58852 functions/realpath: remove weird wrapping
Wrapping the same thing is redundant and wrapping grealpath is kinda
pointless since we only provide completions for realpath.

(cherry picked from commit 61b0368dac)
2025-09-30 11:52:41 +02:00
Integral
03f4bf262a completions/help: correct the spelling of "redirection"
Closes #11839

(cherry picked from commit 568b4a22f9)
2025-09-30 11:52:41 +02:00
Jiangqiu Shen
bb61c948af Re-add translations for share/completions/cjpm.fish
As removed in the parent commit.
Cherry-picked from e4c55131c7 (Update translation, 2025-07-04)

(cherry picked from commit 8abba8a089)
2025-09-30 11:52:41 +02:00
王宇逸
fa59a736bf zh_CN: bad translations are worse than none
Part of #11833

(cherry picked from commit b3b789cd68)
2025-09-30 11:52:40 +02:00
Johannes Altmanninger
97dc8f69d9 functions/seq: use early return
(cherry picked from commit 425a166111)
2025-09-30 11:52:40 +02:00
Johannes Altmanninger
57753474e4 tests/checks/check-all-fish-files.fish: follow naming convention
(cherry picked from commit 1dcc290e29)
2025-09-30 11:52:40 +02:00
Johannes Altmanninger
d5f4cedeb5 build.rs: remove dead code
(cherry picked from commit 863204dbfa)
2025-09-30 11:52:40 +02:00
Sebastian Fleer
ec4700308e webconfig: Replace str.stripprefix with str.removeprefix
str.stripprefix doesn't exist in Python:
https://docs.python.org/3/library/stdtypes.html#str.removeprefix

Closes #11840

(cherry picked from commit 4b21e7c9c7)
2025-09-30 11:52:40 +02:00
Johannes Altmanninger
1d5ae08696 Reliably disable modifyOtherKeys on WezTerm
WezTerm allows applications to enable modifyOtherKeys by default.
Its implementation has issues on non-English or non-QWERTY layouts,
see https://github.com/wezterm/wezterm/issues/6087 and #11204.

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

WezTerm no longer advertises that if config.enable_kitty_keyboard
is false.  Let's work around it in another way.

(cherry picked from commit df5230ff4a)
2025-09-30 11:52:40 +02:00
Johannes Altmanninger
bb5b583d63 Tighten some screws for TTY-specific workarounds
(cherry picked from commit 7cd0943056)
2025-09-30 11:52:40 +02:00
Johannes Altmanninger
7d80a599f2 fish_config: fix for non-embedded builds
I only tested with embedded-builds; CMake tests were failing because
they use different code paths here.

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

Fixes 29a35a7951 (fish_config: fix "prompt/theme show" in embed-data
builds, 2025-09-28).

(cherry picked from commit 6f0532460a)
2025-09-28 12:31:00 +02:00
Johannes Altmanninger
284d8fa3d1 fish_config: fix "prompt/theme show" in embed-data builds
Fixes #11832

(cherry picked from commit 29a35a7951)
2025-09-28 11:08:14 +02:00
Johannes Altmanninger
a03f52add0 release workflow: install msgfmt for staticbuilds
This makes us actually embed localized messages.

Part of #11828

(cherry picked from commit 0ff0de7efe)
2025-09-28 11:06:21 +02:00
Johannes Altmanninger
89fa81123b build_tools/release.sh: actually add new docs
Not quite sure for which step this is actually needed.  While at it,
fix the errexit issue that caused this blunder.

(cherry picked from commit 97ae05b69d)
2025-09-28 11:06:21 +02:00
51 changed files with 940 additions and 715 deletions

View File

@@ -25,7 +25,10 @@ linux_task:
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- FISH_TEST_MAX_CONCURRENCY=6 build_tools/check.sh
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
linux_arm_task:
@@ -41,7 +44,11 @@ linux_arm_task:
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- FISH_TEST_MAX_CONCURRENCY=6 build_tools/check.sh
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- file ./fish
- ninja fish_run_tests
# CI task disabled during RIIR transition
only_if: false && $CIRRUS_REPO_OWNER == 'fish-shell'
@@ -61,7 +68,10 @@ freebsd_task:
- pw user add -n fish-user -s /bin/csh -d /home/fish-user
- mkdir -p /home/fish-user
- chown -R fish-user /home/fish-user
- chown -R fish-user .
- mkdir build && cd build
- chown -R fish-user ..
- sudo -u fish-user -s whoami
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 build_tools/check.sh
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- sudo -u fish-user -s ninja -j 6 fish
- sudo -u fish-user -s ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'

View File

@@ -163,7 +163,7 @@ jobs:
# --break-system-packages because homebrew has now declared itself "externally managed".
# this is CI so we don't actually care.
sudo pip3 install --break-system-packages pexpect
brew install gettext tmux
brew install tmux
- name: cmake
run: |
mkdir build && cd build

View File

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

View File

@@ -1,16 +1,15 @@
fish ?.?.? (released ???)
=========================
fish 4.1.2 (released ???)
=========================
fish 4.1.2 (released October 07, 2025)
======================================
This release fixes the following regressions identified in 4.1.0:
- Fixed spurious error output when completing remote file paths for ``scp`` (:issue:`11860`).
- Fixed the :kbd:`alt-l` binding not formatting ``ls`` output correctly (one entry per line, no colors) (:issue:`11888`).
- Fixed an issue where focus events (currently only enabled in ``tmux``) would cause multiline prompts to be redrawn in the wrong line (:issue:`11870`).
- Stopped printing output that would cause a glitch on old versions of Midnight Commander (:issue:`11869`).
- Added a workaround for old versions of Zellij where :kbd:`escape` processing was delayed (:issue:`11868`).
- Added a fix for some configurations of Zellij where :kbd:`escape` key processing was delayed (:issue:`11868`).
- Fixed a case where the :doc:`web-based configuration tool <cmds/fish_config>` would generate invalid configuration (:issue:`11861`).
- Fixed a case where pasting into ``fish -c read`` would fail with a noisy error (:issue:`11836`).
- Fixed a case where upgrading fish would break old versions of fish that were still running.
In general, fish still needs to be restarted after it is upgraded,

25
Cargo.lock generated
View File

@@ -105,7 +105,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fish"
version = "4.1.0-snapshot"
version = "4.1.2"
dependencies = [
"bitflags",
"cc",
@@ -118,7 +118,6 @@ dependencies = [
"fish-printf",
"libc",
"lru",
"macro_rules_attribute",
"nix",
"num-traits",
"once_cell",
@@ -255,22 +254,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "macro_rules_attribute"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
dependencies = [
"macro_rules_attribute-proc_macro",
"paste",
]
[[package]]
name = "macro_rules_attribute-proc_macro"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "memchr"
version = "2.7.4"
@@ -343,12 +326,6 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pcre2"
version = "0.2.9"

View File

@@ -64,7 +64,7 @@ debug = true
[package]
name = "fish"
version = "4.1.0-snapshot"
version = "4.1.2"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
@@ -84,7 +84,6 @@ fish-gettext-maps = { workspace = true, optional = true }
fish-printf.workspace = true
libc.workspace = true
lru.workspace = true
macro_rules_attribute = "0.2.2"
nix.workspace = true
num-traits.workspace = true
once_cell.workspace = true

View File

@@ -16,7 +16,7 @@ fn main() {
// language server.
rsconf::set_env_value(
"FISH_BUILD_DIR",
"FISH_RESOLVED_BUILD_DIR",
// If set by CMake, this might include symlinks. Since we want to compare this to the
// dir fish is executed in we need to canonicalize it.
canonicalize(fish_build_dir()).to_str().unwrap(),
@@ -50,6 +50,9 @@ fn main() {
#[cfg(feature = "gettext-extract")]
rsconf::rebuild_if_env_changed("FISH_GETTEXT_EXTRACTION_FILE");
rsconf::rebuild_if_path_changed("src/libc.c");
cc::Build::new().file("src/libc.c").compile("flibc.a");
let build = cc::Build::new();
let mut target = Target::new_from(build).unwrap();
// Keep verbose mode on until we've ironed out rust build script stuff
@@ -169,7 +172,10 @@ fn has_small_stack(_: &Target) -> bool {
// Modern macOS versions default to an 8 MiB main stack but legacy OS X have a 0.5 MiB one.
let stack_size = unsafe { pthread_get_stacksize_np(pthread_self()) };
const TWO_MIB: usize = 2 * 1024 * 1024 - 1;
stack_size <= TWO_MIB
match stack_size {
0..=TWO_MIB => true,
_ => false,
}
}
}

View File

@@ -43,11 +43,6 @@ fi
# Currently, all builds are debug builds.
build_dir="$target_dir/debug"
if [ -n "$FISH_TEST_MAX_CONCURRENCY" ]; then
export RUST_TEST_THREADS="$FISH_TEST_MAX_CONCURRENCY"
export CARGO_BUILD_JOBS="$FISH_TEST_MAX_CONCURRENCY"
fi
template_file=$(mktemp)
FISH_GETTEXT_EXTRACTION_FILE=$template_file cargo build --workspace --all-targets --features=gettext-extract
if $lint; then

View File

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

View File

@@ -32,6 +32,7 @@ done
repo_root="$(dirname "$0")/.."
fish_site=$repo_root/../fish-site
fish_site_repo=git@github.com:$repository_owner/fish-site
for path in . "$fish_site"
do
@@ -42,6 +43,13 @@ do
fi
done
(
cd "$fish_site"
[ "$(git rev-parse HEAD)" = \
"$(git ls-remote "$fish_site_repo" refs/heads/master |
awk '{print $1}')" ]
)
if git tag | grep -qxF "$version"; then
echo >&2 "$0: tag $version already exists"
exit 1
@@ -153,7 +161,7 @@ gh_api_repo() {
command gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/$repository_owner/fish-shell/$1" \
"/repos/$repository_owner/fish-shell/$path" \
"$@"
}
@@ -204,7 +212,7 @@ done
" | sed 's,^\s*| \?,,')"
# This takes care to support remote names that are different from
# fish-shell remote name. Also, support detached HEAD state.
git push git@github.com:$repository_owner/fish-site HEAD:master
git push "$fish_site_repo" HEAD:master
)
if [ -n "$integration_branch" ]; then {

View File

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

View File

@@ -12,7 +12,7 @@ fn cargo_target_dir() -> Cow<'static, Path> {
}
pub fn fish_build_dir() -> Cow<'static, Path> {
// FISH_BUILD_DIR is set by CMake, if we are using it.
// This is set if using CMake.
option_env!("FISH_BUILD_DIR")
.map(|d| Cow::Borrowed(Path::new(d)))
.unwrap_or(cargo_target_dir())

View File

@@ -2,7 +2,7 @@
env,
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Stdio},
process::Command,
};
fn main() {
@@ -34,11 +34,7 @@ fn embed_localizations(cache_dir: &Path) {
// for the respective language.
let mut catalogs = phf_codegen::Map::new();
match Command::new("msgfmt")
.arg("-h")
.stdout(Stdio::null())
.status()
{
match Command::new("msgfmt").arg("-h").status() {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
rsconf::warn!(
"Cannot find msgfmt to build gettext message catalogs. Localization will not work."
@@ -101,12 +97,6 @@ fn embed_localizations(cache_dir: &Path) {
.arg(&po_file_path)
.output()
.unwrap();
if !output.status.success() {
panic!(
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
}
let mo_data = output.stdout;
// Extract map from MO data.

View File

@@ -1,4 +1,4 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
# translation of de.po to deutsch
@@ -21,9 +21,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.5\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
@@ -32,6 +29,7 @@ msgstr ""
" PID Befehl\n"
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr " (%ls)\n"
@@ -202,6 +200,7 @@ msgstr "%ls: %ls: ungültiger Unterbefehl\n"
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
@@ -410,6 +409,7 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
@@ -702,6 +702,7 @@ msgstr ""
msgid "%ls: expected a numeric value"
msgstr "%ls: Erwartete numerischen Wert"
#
#, c-format
msgid "%ls: fish was not built with embedded files"
msgstr ""
@@ -798,14 +799,17 @@ msgstr ""
msgid "%s"
msgstr ""
#
#, c-format
msgid "%s %s: unrecognized feature '%ls'"
msgstr ""
#
#, c-format
msgid "%s and %s are mutually exclusive"
msgstr ""
#
#, c-format
msgid "%s could not read response to Primary Device Attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
msgstr ""
@@ -1113,6 +1117,7 @@ msgstr ""
msgid "Failed to assign shell to its own process group"
msgstr "Konnte Shell nicht einer eigenen Prozessgruppe zuweisen"
#
#, c-format
msgid "Failed to set terminal mode (%s)"
msgstr ""
@@ -1190,6 +1195,7 @@ msgstr "Illegale Instruktion"
msgid "Incomplete escape sequence '%ls'"
msgstr "Unvollständige Escapesequenz '%ls'"
#
msgid "Information request"
msgstr ""
@@ -1516,6 +1522,7 @@ msgstr "Derzeit ausführende Funktion stoppen"
msgid "Stop the innermost loop"
msgstr "Innerste Schleife beenden"
#
msgid "Synchronized file access"
msgstr ""
@@ -1703,6 +1710,7 @@ msgstr "Nicht passender Platzhalter"
msgid "Unsupported use of '='. In fish, please use 'set %ls %ls'."
msgstr ""
#
msgid "Unused signal"
msgstr ""
@@ -1730,6 +1738,7 @@ msgstr ""
msgid "Variables cannot be bracketed. In fish, please use {$%ls}."
msgstr ""
#
msgid "Virtual timer expired"
msgstr ""
@@ -1893,9 +1902,7 @@ msgstr "unbegrenzt\n"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& ist ungültig. In fish, nutze &| um stdout und stderr gleichzeitig zu pipen"
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr ""
@@ -2118,9 +2125,7 @@ msgstr "oder die Datei war leer"
msgid "python executable not found"
msgstr ""
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr ""
@@ -3822,12 +3827,7 @@ msgstr ""
msgid "~ expansion"
msgstr ""
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr ""

View File

@@ -1,4 +1,4 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
# SOME DESCRIPTIVE TITLE.
@@ -21,15 +21,13 @@ msgstr ""
"X-Poedit-Basepath: /home/david/src/fish-shell\n"
"X-Generator: Poedit 1.8.11\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
msgstr ""
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr ""
@@ -200,6 +198,7 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
@@ -380,6 +379,7 @@ msgstr "%ls: Expected exactly two names (current function name, and new function
msgid "%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n"
msgstr ""
#
#, c-format
msgid "%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n"
msgstr ""
@@ -408,6 +408,7 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
@@ -700,6 +701,7 @@ msgstr ""
msgid "%ls: expected a numeric value"
msgstr "%ls: expected a numeric value"
#
#, c-format
msgid "%ls: fish was not built with embedded files"
msgstr ""
@@ -796,14 +798,17 @@ msgstr ""
msgid "%s"
msgstr ""
#
#, c-format
msgid "%s %s: unrecognized feature '%ls'"
msgstr ""
#
#, c-format
msgid "%s and %s are mutually exclusive"
msgstr ""
#
#, c-format
msgid "%s could not read response to Primary Device Attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
msgstr ""
@@ -907,6 +912,7 @@ msgstr "An error occurred while setting up pipe"
msgid "Argument is not a number: '%ls'"
msgstr ""
#
msgid "Await background process completion"
msgstr ""
@@ -1111,6 +1117,7 @@ msgstr ""
msgid "Failed to assign shell to its own process group"
msgstr ""
#
#, c-format
msgid "Failed to set terminal mode (%s)"
msgstr ""
@@ -1514,6 +1521,7 @@ msgstr "Stop the currently evaluated function"
msgid "Stop the innermost loop"
msgstr "Stop the innermost loop"
#
msgid "Synchronized file access"
msgstr ""
@@ -1728,6 +1736,7 @@ msgstr ""
msgid "Variables cannot be bracketed. In fish, please use {$%ls}."
msgstr ""
#
msgid "Virtual timer expired"
msgstr ""
@@ -1891,9 +1900,7 @@ msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr ""
@@ -2116,9 +2123,7 @@ msgstr ""
msgid "python executable not found"
msgstr ""
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr ""
@@ -3820,12 +3825,7 @@ msgstr ""
msgid "~ expansion"
msgstr ""
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr ""

View File

@@ -1,4 +1,4 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
# translation of fr.po to Français
@@ -120,9 +120,6 @@ msgstr ""
"X-Generator: Gtranslator 2.91.7\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
@@ -131,6 +128,7 @@ msgstr ""
" PID Commande\n"
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr ""
@@ -301,6 +299,7 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
@@ -481,6 +480,7 @@ msgstr "%ls : Exactement deux noms attendus (noms de fonctions actuel et projet
msgid "%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n"
msgstr ""
#
#, c-format
msgid "%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n"
msgstr ""
@@ -509,6 +509,7 @@ msgstr "%ls : Valeur --max-args '%ls' invalide\n"
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr "%ls : Valeur --min-args '%ls' invalide\n"
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
@@ -801,6 +802,7 @@ msgstr ""
msgid "%ls: expected a numeric value"
msgstr "%ls : valeur numérique attendue"
#
#, c-format
msgid "%ls: fish was not built with embedded files"
msgstr ""
@@ -897,14 +899,17 @@ msgstr ""
msgid "%s"
msgstr ""
#
#, c-format
msgid "%s %s: unrecognized feature '%ls'"
msgstr ""
#
#, c-format
msgid "%s and %s are mutually exclusive"
msgstr ""
#
#, c-format
msgid "%s could not read response to Primary Device Attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
msgstr ""
@@ -1008,6 +1013,7 @@ msgstr "Une erreur est survenue lors du paramétrage du tube"
msgid "Argument is not a number: '%ls'"
msgstr ""
#
msgid "Await background process completion"
msgstr ""
@@ -1212,6 +1218,7 @@ msgstr ""
msgid "Failed to assign shell to its own process group"
msgstr ""
#
#, c-format
msgid "Failed to set terminal mode (%s)"
msgstr ""
@@ -1615,6 +1622,7 @@ msgstr "Arrêter la fonction actuellement en évaluation"
msgid "Stop the innermost loop"
msgstr "Arrêter la boucle interne"
#
msgid "Synchronized file access"
msgstr ""
@@ -1829,6 +1837,7 @@ msgstr "Les variables ne peuvent être placées entre crochets. Dans fish, veuil
msgid "Variables cannot be bracketed. In fish, please use {$%ls}."
msgstr "Les variables ne peuvent être placées entre crochets. Dans fish, veuillez utiliser {$%ls}."
#
msgid "Virtual timer expired"
msgstr ""
@@ -1992,9 +2001,7 @@ msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr ""
@@ -2217,9 +2224,7 @@ msgstr "ou le fichier était vide"
msgid "python executable not found"
msgstr "Exécutable python introuvable"
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr ""
@@ -3921,12 +3926,7 @@ msgstr ""
msgid "~ expansion"
msgstr ""
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr ""

View File

@@ -1,4 +1,4 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
# Translation of fish in Polish
@@ -17,15 +17,13 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: GlotPress/2.2.2\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
msgstr ""
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr ""
@@ -196,6 +194,7 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
@@ -376,6 +375,7 @@ msgstr "%ls: Oczekiwano dokładnie dwóch nazw (nazwa obecnej funkcji i nazwa no
msgid "%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n"
msgstr ""
#
#, c-format
msgid "%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n"
msgstr ""
@@ -404,6 +404,7 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
@@ -696,6 +697,7 @@ msgstr ""
msgid "%ls: expected a numeric value"
msgstr "%ls: oczekiwano wartości liczbowej"
#
#, c-format
msgid "%ls: fish was not built with embedded files"
msgstr ""
@@ -792,14 +794,17 @@ msgstr ""
msgid "%s"
msgstr ""
#
#, c-format
msgid "%s %s: unrecognized feature '%ls'"
msgstr ""
#
#, c-format
msgid "%s and %s are mutually exclusive"
msgstr ""
#
#, c-format
msgid "%s could not read response to Primary Device Attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
msgstr ""
@@ -903,6 +908,7 @@ msgstr ""
msgid "Argument is not a number: '%ls'"
msgstr ""
#
msgid "Await background process completion"
msgstr ""
@@ -1107,6 +1113,7 @@ msgstr ""
msgid "Failed to assign shell to its own process group"
msgstr ""
#
#, c-format
msgid "Failed to set terminal mode (%s)"
msgstr ""
@@ -1510,6 +1517,7 @@ msgstr "Zatrzymaj obecnie używaną funkcję"
msgid "Stop the innermost loop"
msgstr ""
#
msgid "Synchronized file access"
msgstr ""
@@ -1724,6 +1732,7 @@ msgstr "Zmienne nie mogą znajdować się w nawiasach. W fish używane jest \"$%
msgid "Variables cannot be bracketed. In fish, please use {$%ls}."
msgstr "Zmienne nie mogą znajdować się w nawiasach. W fish używane jest {$%ls}."
#
msgid "Virtual timer expired"
msgstr ""
@@ -1887,9 +1896,7 @@ msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr ""
@@ -2112,9 +2119,7 @@ msgstr ""
msgid "python executable not found"
msgstr ""
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr ""
@@ -3816,12 +3821,7 @@ msgstr ""
msgid "~ expansion"
msgstr ""
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr ""

View File

@@ -1,4 +1,4 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
# Portuguese translations for fish package.
@@ -22,15 +22,13 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.4\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
msgstr ""
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr ""
@@ -201,6 +199,7 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
@@ -381,6 +380,7 @@ msgstr "%ls: Esperava exatamente dois nomes (nome atual da função, e novo nome
msgid "%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n"
msgstr ""
#
#, c-format
msgid "%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n"
msgstr ""
@@ -409,6 +409,7 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
@@ -701,6 +702,7 @@ msgstr ""
msgid "%ls: expected a numeric value"
msgstr "%ls: esperava valor numérico"
#
#, c-format
msgid "%ls: fish was not built with embedded files"
msgstr ""
@@ -797,14 +799,17 @@ msgstr ""
msgid "%s"
msgstr ""
#
#, c-format
msgid "%s %s: unrecognized feature '%ls'"
msgstr ""
#
#, c-format
msgid "%s and %s are mutually exclusive"
msgstr ""
#
#, c-format
msgid "%s could not read response to Primary Device Attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
msgstr ""
@@ -908,6 +913,7 @@ msgstr "Ocorreu um erro ao preparar pipe"
msgid "Argument is not a number: '%ls'"
msgstr ""
#
msgid "Await background process completion"
msgstr ""
@@ -1112,6 +1118,7 @@ msgstr ""
msgid "Failed to assign shell to its own process group"
msgstr ""
#
#, c-format
msgid "Failed to set terminal mode (%s)"
msgstr ""
@@ -1515,6 +1522,7 @@ msgstr "Pára a função em execução"
msgid "Stop the innermost loop"
msgstr "Pára o laço mais interno"
#
msgid "Synchronized file access"
msgstr ""
@@ -1729,6 +1737,7 @@ msgstr ""
msgid "Variables cannot be bracketed. In fish, please use {$%ls}."
msgstr ""
#
msgid "Virtual timer expired"
msgstr ""
@@ -1892,9 +1901,7 @@ msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr ""
@@ -2117,9 +2124,7 @@ msgstr ""
msgid "python executable not found"
msgstr "executável python não encontrado"
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr ""
@@ -3821,12 +3826,7 @@ msgstr ""
msgid "~ expansion"
msgstr ""
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr ""
@@ -19604,6 +19604,11 @@ msgstr ""
msgid "Do not list files that match the given pattern"
msgstr ""
# Notas:
# Adicionar nota
#
# Caminhos:
# share/functions/__fish_complete_ls.fish:49
msgid "Do not list implied entries matching specified shell pattern"
msgstr "Não lista entradas com o padrão especificado"
@@ -26012,6 +26017,11 @@ msgstr ""
msgid "File is on filesystem of specified type"
msgstr ""
# Notas:
# Adicionar nota
#
# Caminhos:
# share/functions/__fish_complete_ls.fish:49
msgid "File is symlink matching specified case insensitive pattern"
msgstr ""
@@ -36548,6 +36558,12 @@ msgstr ""
msgid "Machine-readable description of custom origin"
msgstr ""
# Notas:
# Adicionar nota
#
# Caminhos:
# proc.cpp:1279
# proc.cpp:1305
msgid "Mail address to send alerts to"
msgstr ""
@@ -46046,6 +46062,12 @@ msgstr ""
msgid "Process last file first"
msgstr ""
# Notas:
# Adicionar nota
#
# Caminhos:
# proc.cpp:1279
# proc.cpp:1305
msgid "Process log file"
msgstr ""

View File

@@ -1,4 +1,4 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
# Copyright © 2006
@@ -18,15 +18,13 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
msgstr ""
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr ""
@@ -197,6 +195,7 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
@@ -377,6 +376,7 @@ msgstr ""
msgid "%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n"
msgstr ""
#
#, c-format
msgid "%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n"
msgstr ""
@@ -405,6 +405,7 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
@@ -697,6 +698,7 @@ msgstr ""
msgid "%ls: expected a numeric value"
msgstr ""
#
#, c-format
msgid "%ls: fish was not built with embedded files"
msgstr ""
@@ -793,14 +795,17 @@ msgstr ""
msgid "%s"
msgstr ""
#
#, c-format
msgid "%s %s: unrecognized feature '%ls'"
msgstr ""
#
#, c-format
msgid "%s and %s are mutually exclusive"
msgstr ""
#
#, c-format
msgid "%s could not read response to Primary Device Attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
msgstr ""
@@ -904,6 +909,7 @@ msgstr "Ett fel inträffade under skapandet av ett rör"
msgid "Argument is not a number: '%ls'"
msgstr ""
#
msgid "Await background process completion"
msgstr ""
@@ -1108,6 +1114,7 @@ msgstr ""
msgid "Failed to assign shell to its own process group"
msgstr ""
#
#, c-format
msgid "Failed to set terminal mode (%s)"
msgstr ""
@@ -1511,6 +1518,7 @@ msgstr "Avbryt den nuvarande funktionen"
msgid "Stop the innermost loop"
msgstr "Avbryt den innersta loopen"
#
msgid "Synchronized file access"
msgstr ""
@@ -1725,6 +1733,7 @@ msgstr ""
msgid "Variables cannot be bracketed. In fish, please use {$%ls}."
msgstr ""
#
msgid "Virtual timer expired"
msgstr ""
@@ -1888,9 +1897,7 @@ msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr ""
@@ -2062,12 +2069,14 @@ msgstr ""
msgid "Too many args for cd command"
msgstr ""
#
msgid "Type %shelp%s for instructions on how to use fish"
msgstr "Skriv %shelp%s för instruktioner om hur man använder fish"
msgid "Warning: the file containing this function has not been saved. Changes may be lost when fish is closed."
msgstr ""
#
msgid "Welcome to fish, the friendly interactive shell"
msgstr "Välkommen till fish, det vänliga interaktiva skalet"
@@ -2113,9 +2122,7 @@ msgstr ""
msgid "python executable not found"
msgstr ""
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr ""
@@ -3817,12 +3824,7 @@ msgstr ""
msgid "~ expansion"
msgstr ""
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr ""

View File

@@ -1,7 +1,20 @@
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* pseudo-message.
# fish-note-sections: Translations are divided into sections, each starting with a fish-section-* comment.
# fish-note-sections: The first few sections are more important.
# fish-note-sections: Ignore the tier3 sections unless you have a lot of time.
#
msgid ""
msgstr ""
"Project-Id-Version: fish 1.21.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-10 16:38+0100\n"
"PO-Revision-Date: 2025-09-30 19:14+0800\n"
"Last-Translator: Yuyi Wang <Strawberry_Str@hotmail.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.7\n"
# General Notices 凡例
# abort: 中止
# argument: 参数
@@ -28,23 +41,6 @@
# token: 记号
# universal (variable): 通用
# valid & invalid: 有效 & 无效
msgid ""
msgstr ""
"Project-Id-Version: fish 1.21.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-10 16:38+0100\n"
"PO-Revision-Date: 2025-09-30 19:14+0800\n"
"Last-Translator: Yuyi Wang <Strawberry_Str@hotmail.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.7\n"
msgid "fish-section-tier1-from-rust"
msgstr ""
msgid ""
"\n"
" PID Command\n"
@@ -53,6 +49,7 @@ msgstr ""
" PID 命令\n"
#, c-format
# fish-section-tier1-from-rust
msgid " (%ls)\n"
msgstr " (%ls)\n"
@@ -1920,9 +1917,7 @@ msgstr "无限制\n"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& 无效。在 fish 中,用 &| 来同时管道链接 stdout 和 stderr。"
msgid "fish-section-tier1-from-script-explicitly-added"
msgstr ""
# fish-section-tier1-from-script-explicitly-added
msgid "%ls: %ls: expected %d arguments; got %d\\n"
msgstr "%ls: %ls: 期望 %d 个参数;收到 %d 个\\n"
@@ -2145,9 +2140,7 @@ msgstr "或文件为空"
msgid "python executable not found"
msgstr "找不到 python 可执行文件"
msgid "fish-section-tier1-from-script-implicitly-added"
msgstr ""
# fish-section-tier1-from-script-implicitly-added
msgid "# comments"
msgstr "# 注释"
@@ -3658,7 +3651,7 @@ msgid "Vi mode commands"
msgstr "Vi 模式命令"
msgid "Vi-style bindings that inherit emacs-style bindings in all modes"
msgstr "所有模式中的继承 emacs 样式绑定的 vi 样式绑定"
msgstr "所有模式中的继承 emacs 样式绑定的 Vi 样式绑定"
msgid "View and pick from the sample prompts"
msgstr "查看并从示例提示中选择"
@@ -3673,7 +3666,7 @@ msgid "What characters are allowed in names"
msgstr "名称中允许什么字符"
msgid "What set -x does"
msgstr "`set -x` 做什么"
msgstr "设置 -x 做什么"
msgid "Where to direct debug output to"
msgstr "将调试输出导向何处"
@@ -3849,12 +3842,7 @@ msgstr "{a,b} 大括号展开"
msgid "~ expansion"
msgstr "~ 展开"
msgid "fish-section-tier3-from-script-explicitly-added"
msgstr ""
msgid "fish-section-tier3-from-script-implicitly-added"
msgstr ""
# fish-section-tier3-from-script-implicitly-added
msgid " "
msgstr " "
@@ -40060,7 +40048,7 @@ msgid "Operate quietly and do not report progress"
msgstr "安静操作,不报告进度"
msgid "Operate recursively"
msgstr "递归操"
msgstr "递归操"
msgid "Operate recursively on datasets"
msgstr "在数据集上递归操作"

View File

@@ -10,7 +10,8 @@ set -l erase_line "$(
function __fish_echo --inherit-variable erase_line --description 'run the given command after the current commandline and redraw the prompt'
set -l line (commandline --line)
string >&2 repeat -N \n --count=(math (commandline | count) - $line + 1)
printf %s\n $erase_line($argv) >&2
printf %s $erase_line >&2
$argv >&2
string >&2 repeat -N \n --count=(math (count (fish_prompt)) - 1)
string >&2 repeat -N \n --count=(math $line - 1)
commandline -f repaint

View File

@@ -87,11 +87,11 @@ function fish_config --description "Launch fish's web based configuration"
return 1
end
set -l prompt_dir $__fish_data_dir/tools/web_config/sample_prompts
set -l prompt_dir $__fish_data_dir/sample_prompts $__fish_data_dir/tools/web_config/sample_prompts
switch $cmd
case show
set -l fish (status fish-path)
set -l prompts (__fish_config_matching tools/web_config/sample_prompts .fish $argv)
set -l prompts (dirs=$prompt_dir __fish_config_matching tools/web_config/sample_prompts .fish $argv)
for p in $prompts
set -l promptname (string replace -r '.*/([^/]*).fish$' '$1' $p)
echo -s (set_color --underline) $promptname (set_color normal)
@@ -240,8 +240,7 @@ function fish_config --description "Launch fish's web based configuration"
case show
set -l fish (status fish-path)
set -l themes \
(path filter $dirs/$argv.theme) \
(__fish_config_matching tools/web_config/themes .theme $argv)
(dirs=$dirs __fish_config_matching tools/web_config/themes .theme $argv)
set -l used_themes
echo -s (set_color normal; set_color --underline) Current (set_color normal)
@@ -384,17 +383,16 @@ function __fish_config_matching
set -l suffix $argv[2]
set -e argv[1..2]
set -l paths
if set -q __fish_data_dir[1]
if not set -q argv[1]
set paths $__fish_data_dir/$prefix/*$suffix
else
set paths (path filter $__fish_data_dir/$prefix/$argv$suffix)
end
if not set -q argv[1]
set paths $dirs/*$suffix
else
set paths (path filter $dirs/$argv$suffix)
end
if not set -q __fish_data_dir[1]
if not set -q argv[1]
set paths (status list-files $prefix)
set -a paths (status list-files $prefix)
else
set paths (status list-files $prefix | grep -Fx -e"$prefix/"$argv$suffix)
set -a paths (status list-files $prefix | grep -Fx -e"$prefix/"$argv$suffix)
end
end
string join \n $paths

View File

@@ -24,7 +24,6 @@
TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
};
use crate::wchar::prelude::*;
use macro_rules_attribute::derive;
use std::borrow::Cow;
use std::convert::AsMut;
use std::ops::{ControlFlow, Deref};
@@ -405,8 +404,8 @@ trait CheckParse: Default {
}
/// Implement the node trait.
macro_rules! Node {
($name:ident) => {
macro_rules! implement_node {
( $name:ident ) => {
impl Node for $name {
fn kind(&self) -> Kind<'_> {
Kind::$name(self)
@@ -426,19 +425,11 @@ fn cast(node: &dyn Node) -> Option<&Self> {
}
}
};
( $(#[$_m:meta])* $_v:vis struct $name:ident $_:tt $(;)? ) => {
Node!($name);
};
( $(#[$_m:meta])* $_v:vis enum $name:ident $_:tt ) => {
Node!($name);
};
}
/// Implement the leaf trait.
macro_rules! Leaf {
($name:ident) => {
macro_rules! implement_leaf {
( $name:ident ) => {
impl Leaf for $name {
fn range(&self) -> Option<SourceRange> {
self.range
@@ -459,20 +450,17 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
}
}
};
( $(#[$_m:meta])* $_v:vis struct $name:ident $_:tt $(;)? ) => {
Leaf!($name);
};
}
/// Define a node that implements the keyword trait.
macro_rules! define_keyword_node {
( $name:ident, $($allowed:ident),* $(,)? ) => {
#[derive(Default, Debug, Leaf!)]
#[derive(Default, Debug)]
pub struct $name {
range: Option<SourceRange>,
keyword: ParseKeyword,
}
implement_leaf!($name);
impl Node for $name {
fn kind(&self) -> Kind<'_> {
Kind::Keyword(self)
@@ -501,7 +489,7 @@ fn as_leaf(&self) -> &dyn Leaf {
/// Define a node that implements the token trait.
macro_rules! define_token_node {
( $name:ident, $($allowed:ident),* $(,)? ) => {
#[derive(Default, Debug, Leaf!)]
#[derive(Default, Debug)]
pub struct $name {
range: Option<SourceRange>,
parse_token_type: ParseTokenType,
@@ -514,6 +502,7 @@ fn kind_mut(&mut self) -> KindMut<'_> {
KindMut::Token(self)
}
}
implement_leaf!($name);
impl Token for $name {
fn token_type(&self) -> ParseTokenType {
self.parse_token_type
@@ -546,9 +535,11 @@ macro_rules! define_list_node {
$name:ident,
$contents:ident
) => {
#[derive(Default, Debug, Node!)]
#[derive(Default, Debug)]
pub struct $name(Box<[$contents]>);
implement_node!($name);
impl Deref for $name {
type Target = Box<[$contents]>;
fn deref(&self) -> &Self::Target {
@@ -591,15 +582,11 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
}
/// Implement the acceptor trait for the given branch node.
macro_rules! Acceptor {
macro_rules! implement_acceptor_for_branch {
(
$(#[$_m:meta])*
$_v:vis struct $name:ident {
$(
$(#[$_fm:meta])*
$_fv:vis $field_name:ident : $_ft:ty
),* $(,)?
}
$name:ident
$(, $field_name:ident )*
$(,)?
) => {
impl Acceptor for $name {
#[allow(unused_variables)]
@@ -625,16 +612,18 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
visitor.did_visit_fields_of(self, flow);
}
}
};
}
}
/// A redirection has an operator like > or 2>, and a target like /dev/null or &1.
/// Note that pipes are not redirections.
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct Redirection {
pub oper: TokenRedirection,
pub target: String_,
}
implement_node!(Redirection);
implement_acceptor_for_branch!(Redirection, oper, target);
impl CheckParse for Redirection {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
@@ -644,7 +633,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(VariableAssignmentList, VariableAssignment);
#[derive(Debug, Node!)]
#[derive(Debug)]
pub enum ArgumentOrRedirection {
Argument(Argument),
Redirection(Box<Redirection>), // Boxed because it's bigger
@@ -700,6 +689,8 @@ pub fn redirection(&self) -> &Redirection {
}
}
implement_node!(ArgumentOrRedirection);
impl CheckParse for ArgumentOrRedirection {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
let typ = pop.peek_type(0);
@@ -710,7 +701,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(ArgumentOrRedirectionList, ArgumentOrRedirection);
/// A statement is a normal command, or an if / while / etc
#[derive(Debug, Node!)]
#[derive(Debug)]
pub enum Statement {
Decorated(DecoratedStatement),
Not(Box<NotStatement>),
@@ -719,6 +710,7 @@ pub enum Statement {
If(Box<IfStatement>),
Switch(Box<SwitchStatement>),
}
implement_node!(Statement);
impl Default for Statement {
fn default() -> Self {
@@ -764,7 +756,7 @@ fn accept_mut<V: NodeVisitorMut>(&mut self, visitor: &mut V) {
/// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
/// like if statements, where we require a command).
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct JobPipeline {
/// Maybe the time keyword.
pub time: Option<KeywordTime>,
@@ -777,9 +769,11 @@ pub struct JobPipeline {
/// Maybe backgrounded.
pub bg: Option<TokenBackground>,
}
implement_node!(JobPipeline);
implement_acceptor_for_branch!(JobPipeline, time, variables, statement, continuation, bg);
/// A job_conjunction is a job followed by a && or || continuations.
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct JobConjunction {
/// The job conjunction decorator.
pub decorator: Option<JobConjunctionDecorator>,
@@ -792,6 +786,8 @@ pub struct JobConjunction {
/// only fail to be present if we ran out of tokens.
pub semi_nl: Option<SemiNl>,
}
implement_node!(JobConjunction);
implement_acceptor_for_branch!(JobConjunction, decorator, job, continuations, semi_nl);
impl CheckParse for JobConjunction {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
@@ -806,7 +802,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
}
}
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct ForHeader {
/// 'for'
pub kw_for: KeywordFor,
@@ -819,16 +815,20 @@ pub struct ForHeader {
/// newline or semicolon
pub semi_nl: SemiNl,
}
implement_node!(ForHeader);
implement_acceptor_for_branch!(ForHeader, kw_for, var_name, kw_in, args, semi_nl);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct WhileHeader {
/// 'while'
pub kw_while: KeywordWhile,
pub condition: JobConjunction,
pub andor_tail: AndorJobList,
}
implement_node!(WhileHeader);
implement_acceptor_for_branch!(WhileHeader, kw_while, condition, andor_tail);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct FunctionHeader {
pub kw_function: KeywordFunction,
/// functions require at least one argument.
@@ -836,16 +836,20 @@ pub struct FunctionHeader {
pub args: ArgumentList,
pub semi_nl: SemiNl,
}
implement_node!(FunctionHeader);
implement_acceptor_for_branch!(FunctionHeader, kw_function, first_arg, args, semi_nl);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct BeginHeader {
pub kw_begin: KeywordBegin,
/// Note that 'begin' does NOT require a semi or nl afterwards.
/// This is valid: begin echo hi; end
pub semi_nl: Option<SemiNl>,
}
implement_node!(BeginHeader);
implement_acceptor_for_branch!(BeginHeader, kw_begin, semi_nl);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct BlockStatement {
/// A header like for, while, etc.
pub header: BlockStatementHeader,
@@ -856,8 +860,10 @@ pub struct BlockStatement {
/// Arguments and redirections associated with the block.
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(BlockStatement);
implement_acceptor_for_branch!(BlockStatement, header, jobs, end, args_or_redirs);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct BraceStatement {
/// The opening brace, in command position.
pub left_brace: TokenLeftBrace,
@@ -868,8 +874,16 @@ pub struct BraceStatement {
/// Arguments and redirections associated with the block.
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(BraceStatement);
implement_acceptor_for_branch!(
BraceStatement,
left_brace,
jobs,
right_brace,
args_or_redirs
);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct IfClause {
/// The 'if' keyword.
pub kw_if: KeywordIf,
@@ -880,14 +894,18 @@ pub struct IfClause {
/// The body to execute if the condition is true.
pub body: JobList,
}
implement_node!(IfClause);
implement_acceptor_for_branch!(IfClause, kw_if, condition, andor_tail, body);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct ElseifClause {
/// The 'else' keyword.
pub kw_else: KeywordElse,
/// The 'if' clause following it.
pub if_clause: IfClause,
}
implement_node!(ElseifClause);
implement_acceptor_for_branch!(ElseifClause, kw_else, if_clause);
impl CheckParse for ElseifClause {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_token(0).keyword == ParseKeyword::Else
@@ -897,20 +915,22 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(ElseifClauseList, ElseifClause);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct ElseClause {
/// else ; body
pub kw_else: KeywordElse,
pub semi_nl: Option<SemiNl>,
pub body: JobList,
}
implement_node!(ElseClause);
implement_acceptor_for_branch!(ElseClause, kw_else, semi_nl, body);
impl CheckParse for ElseClause {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_token(0).keyword == ParseKeyword::Else
}
}
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct IfStatement {
/// if part
pub if_clause: IfClause,
@@ -923,8 +943,17 @@ pub struct IfStatement {
/// block args / redirs
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(IfStatement);
implement_acceptor_for_branch!(
IfStatement,
if_clause,
elseif_clauses,
else_clause,
end,
args_or_redirs
);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct CaseItem {
/// case \<arguments\> ; body
pub kw_case: KeywordCase,
@@ -932,13 +961,15 @@ pub struct CaseItem {
pub semi_nl: SemiNl,
pub body: JobList,
}
implement_node!(CaseItem);
implement_acceptor_for_branch!(CaseItem, kw_case, arguments, semi_nl, body);
impl CheckParse for CaseItem {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_token(0).keyword == ParseKeyword::Case
}
}
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct SwitchStatement {
/// switch \<argument\> ; body ; end args_redirs
pub kw_switch: KeywordSwitch,
@@ -948,10 +979,20 @@ pub struct SwitchStatement {
pub end: KeywordEnd,
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(SwitchStatement);
implement_acceptor_for_branch!(
SwitchStatement,
kw_switch,
argument,
semi_nl,
cases,
end,
args_or_redirs
);
/// A decorated_statement is a command with a list of arguments_or_redirections, possibly with
/// "builtin" or "command" or "exec"
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct DecoratedStatement {
/// An optional decoration (command, builtin, exec, etc).
pub opt_decoration: Option<DecoratedStatementDecorator>,
@@ -960,9 +1001,11 @@ pub struct DecoratedStatement {
/// Args and redirs
pub args_or_redirs: ArgumentOrRedirectionList,
}
implement_node!(DecoratedStatement);
implement_acceptor_for_branch!(DecoratedStatement, opt_decoration, command, args_or_redirs);
/// A not statement like `not true` or `! true`
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct NotStatement {
/// Keyword, either not or exclam.
pub kw: KeywordNot,
@@ -970,14 +1013,18 @@ pub struct NotStatement {
pub variables: VariableAssignmentList,
pub contents: Statement,
}
implement_node!(NotStatement);
implement_acceptor_for_branch!(NotStatement, kw, time, variables, contents);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct JobContinuation {
pub pipe: TokenPipe,
pub newlines: MaybeNewlines,
pub variables: VariableAssignmentList,
pub statement: Statement,
}
implement_node!(JobContinuation);
implement_acceptor_for_branch!(JobContinuation, pipe, newlines, variables, statement);
impl CheckParse for JobContinuation {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_type(0) == ParseTokenType::pipe
@@ -986,7 +1033,7 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
define_list_node!(JobContinuationList, JobContinuation);
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct JobConjunctionContinuation {
/// The && or || token.
pub conjunction: TokenConjunction,
@@ -994,6 +1041,8 @@ pub struct JobConjunctionContinuation {
/// The job itself.
pub job: JobPipeline,
}
implement_node!(JobConjunctionContinuation);
implement_acceptor_for_branch!(JobConjunctionContinuation, conjunction, newlines, job);
impl CheckParse for JobConjunctionContinuation {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
let typ = pop.peek_type(0);
@@ -1004,10 +1053,12 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
/// An andor_job just wraps a job, but requires that the job have an 'and' or 'or' job_decorator.
/// Note this is only used for andor_job_list; jobs that are not part of an andor_job_list are not
/// instances of this.
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct AndorJob {
pub job: JobConjunction,
}
implement_node!(AndorJob);
implement_acceptor_for_branch!(AndorJob, job);
impl CheckParse for AndorJob {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
let keyword = pop.peek_token(0).keyword;
@@ -1029,10 +1080,12 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
/// A freestanding_argument_list is equivalent to a normal argument list, except it may contain
/// TOK_END (newlines, and even semicolons, for historical reasons).
/// In practice the tok_ends are ignored by fish code so we do not bother to store them.
#[derive(Default, Debug, Node!, Acceptor!)]
#[derive(Default, Debug)]
pub struct FreestandingArgumentList {
pub arguments: ArgumentList,
}
implement_node!(FreestandingArgumentList);
implement_acceptor_for_branch!(FreestandingArgumentList, arguments);
define_list_node!(JobConjunctionContinuationList, JobConjunctionContinuation);
@@ -1044,10 +1097,12 @@ pub struct FreestandingArgumentList {
define_list_node!(CaseItemList, CaseItem);
/// A variable_assignment contains a source range like FOO=bar.
#[derive(Default, Debug, Node!, Leaf!)]
#[derive(Default, Debug)]
pub struct VariableAssignment {
range: Option<SourceRange>,
}
implement_node!(VariableAssignment);
implement_leaf!(VariableAssignment);
impl CheckParse for VariableAssignment {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
// Do we have a variable assignment at all?
@@ -1069,17 +1124,21 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
}
/// Zero or more newlines.
#[derive(Default, Debug, Node!, Leaf!)]
#[derive(Default, Debug)]
pub struct MaybeNewlines {
range: Option<SourceRange>,
}
implement_node!(MaybeNewlines);
implement_leaf!(MaybeNewlines);
/// An argument is just a node whose source range determines its contents.
/// This is a separate type because it is sometimes useful to find all arguments.
#[derive(Default, Debug, Node!, Leaf!)]
#[derive(Default, Debug)]
pub struct Argument {
range: Option<SourceRange>,
}
implement_node!(Argument);
implement_leaf!(Argument);
impl CheckParse for Argument {
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
pop.peek_type(0) == ParseTokenType::string
@@ -1167,13 +1226,14 @@ pub fn decoration(&self) -> StatementDecoration {
}
}
#[derive(Debug, Node!)]
#[derive(Debug)]
pub enum BlockStatementHeader {
Begin(BeginHeader),
For(ForHeader),
While(WhileHeader),
Function(FunctionHeader),
}
implement_node!(BlockStatementHeader);
impl Default for BlockStatementHeader {
fn default() -> Self {

View File

@@ -635,7 +635,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
transient = parser.libdata().transient_commandline.clone().unwrap();
current_buffer = &transient;
current_cursor_pos = transient.len();
} else if is_interactive_session() {
} else if parser.interactive_initialized.load() || is_interactive_session() {
current_buffer = &rstate.text;
current_cursor_pos = rstate.cursor_pos;
} else {

View File

@@ -458,7 +458,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
None => {
// No argument given, try to use the current commandline.
let commandline_state = commandline_get_state(true);
if !is_interactive_session() {
if !parser.interactive_initialized.load() && !is_interactive_session() {
streams.err.append(cmd);
streams
.err

View File

@@ -246,18 +246,20 @@ fn handle_sprintf_error(&mut self, err: fish_printf::Error) {
}
}
/// Evaluate a printf conversion specification.
/// `spec` is the start of the directive, and `conversion` specifies the type of conversion.
/// `spec` does not include any length modifier or the conversion specifier itself.
/// `field_width` and `precision` are the field width and precision for '*' values, if any.
/// `argument` is the argument to be formatted.
/// Evaluate a printf conversion specification. SPEC is the start of the directive, and CONVERSION
/// specifies the type of conversion. SPEC does not include any length modifier or the
/// conversion specifier itself. FIELD_WIDTH and PRECISION are the field width and
/// precision for '*' values, if HAVE_FIELD_WIDTH and HAVE_PRECISION are true, respectively.
/// ARGUMENT is the argument to be formatted.
#[allow(clippy::collapsible_else_if, clippy::too_many_arguments)]
fn print_directive(
fn print_direc(
&mut self,
spec: &wstr,
conversion: char,
field_width: Option<i64>,
precision: Option<i64>,
have_field_width: bool,
field_width: i32,
have_precision: bool,
precision: i32,
argument: &wstr,
) {
/// Printf macro helper which provides our locale.
@@ -281,6 +283,21 @@ macro_rules! append_output_fmt {
// Start with everything except the conversion specifier.
let mut fmt = spec.to_owned();
// Create a copy of the % directive, with a width modifier substituted for any
// existing integer length modifier.
match conversion {
'x' | 'X' | 'd' | 'i' | 'o' | 'u' => {
fmt.push_str("ll");
}
'a' | 'e' | 'f' | 'g' | 'A' | 'E' | 'F' | 'G' => {
fmt.push_str("L");
}
's' | 'c' => {
fmt.push_str("l");
}
_ => {}
}
// Append the conversion itself.
fmt.push(conversion);
@@ -289,96 +306,77 @@ macro_rules! append_output_fmt {
match conversion {
'd' | 'i' => {
let arg: i64 = string_to_scalar_type(argument, self);
match field_width {
Some(field_width) => match precision {
Some(precision) => {
append_output_fmt!(fmt, field_width, precision, arg);
}
None => {
append_output_fmt!(fmt, field_width, arg);
}
},
None => match precision {
Some(precision) => {
append_output_fmt!(fmt, precision, arg);
}
None => {
append_output_fmt!(fmt, arg);
}
},
if !have_field_width {
if !have_precision {
append_output_fmt!(fmt, arg);
} else {
append_output_fmt!(fmt, precision, arg);
}
} else {
if !have_precision {
append_output_fmt!(fmt, field_width, arg);
} else {
append_output_fmt!(fmt, field_width, precision, arg);
}
}
}
'o' | 'u' | 'x' | 'X' => {
let arg: u64 = string_to_scalar_type(argument, self);
match field_width {
Some(field_width) => match precision {
Some(precision) => {
append_output_fmt!(fmt, field_width, precision, arg);
}
None => {
append_output_fmt!(fmt, field_width, arg);
}
},
None => match precision {
Some(precision) => {
append_output_fmt!(fmt, precision, arg);
}
None => {
append_output_fmt!(fmt, arg);
}
},
if !have_field_width {
if !have_precision {
append_output_fmt!(fmt, arg);
} else {
append_output_fmt!(fmt, precision, arg);
}
} else {
if !have_precision {
append_output_fmt!(fmt, field_width, arg);
} else {
append_output_fmt!(fmt, field_width, precision, arg);
}
}
}
'a' | 'A' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' => {
let arg: f64 = string_to_scalar_type(argument, self);
match field_width {
Some(field_width) => match precision {
Some(precision) => {
append_output_fmt!(fmt, field_width, precision, arg);
}
None => {
append_output_fmt!(fmt, field_width, arg);
}
},
None => match precision {
Some(precision) => {
append_output_fmt!(fmt, precision, arg);
}
None => {
append_output_fmt!(fmt, arg);
}
},
if !have_field_width {
if !have_precision {
append_output_fmt!(fmt, arg);
} else {
append_output_fmt!(fmt, precision, arg);
}
} else {
if !have_precision {
append_output_fmt!(fmt, field_width, arg);
} else {
append_output_fmt!(fmt, field_width, precision, arg);
}
}
}
'c' => match field_width {
Some(field_width) => {
'c' => {
if !have_field_width {
append_output_fmt!(fmt, argument.char_at(0));
} else {
append_output_fmt!(fmt, field_width, argument.char_at(0));
}
None => {
append_output_fmt!(fmt, argument.char_at(0));
}
},
}
's' => match field_width {
Some(field_width) => match precision {
Some(precision) => {
append_output_fmt!(fmt, field_width, precision, argument);
}
None => {
append_output_fmt!(fmt, field_width, argument);
}
},
None => match precision {
Some(precision) => {
's' => {
if !have_field_width {
if !have_precision {
append_output_fmt!(fmt, argument);
} else {
append_output_fmt!(fmt, precision, argument);
}
None => {
append_output_fmt!(fmt, argument);
} else {
if !have_precision {
append_output_fmt!(fmt, field_width, argument);
} else {
append_output_fmt!(fmt, field_width, precision, argument);
}
},
},
}
}
_ => {
panic!("unexpected opt: {}", conversion);
@@ -386,16 +384,18 @@ macro_rules! append_output_fmt {
}
}
/// Print the text in `format`, using `argv` for arguments to any `%' directives.
/// Return the number of elements of `argv` used.
/// Print the text in FORMAT, using ARGV for arguments to any `%' directives.
/// Return the number of elements of ARGV used.
fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
let mut argc = argv.len();
let save_argc = argc; /* Preserve original value. */
let mut f: &wstr; /* Pointer into `format'. */
let mut directive_start: &wstr; /* Start of % directive. */
let mut directive_length: usize; /* Length of % directive. */
let mut field_width: Option<i64>; /* Arg to first '*'. */
let mut precision: Option<i64>; /* Arg to second '*'. */
let mut direc_start: &wstr; /* Start of % directive. */
let mut direc_length: usize; /* Length of % directive. */
let mut have_field_width: bool; /* True if FIELD_WIDTH is valid. */
let mut field_width: c_int = 0; /* Arg to first '*'. */
let mut have_precision: bool; /* True if PRECISION is valid. */
let mut precision = 0; /* Arg to second '*'. */
let mut ok = [false; 256]; /* ok['x'] is true if %x is allowed. */
// N.B. this was originally written as a loop like so:
@@ -414,11 +414,11 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
match f.char_at(0) {
'%' => {
directive_start = f;
direc_start = f;
f = &f[1..];
directive_length = 1;
field_width = None;
precision = None;
direc_length = 1;
have_field_width = false;
have_precision = false;
if f.char_at(0) == '%' {
self.append_output('%');
continue;
@@ -460,17 +460,17 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
}
if continue_looking_for_flags {
f = &f[1..];
directive_length += 1;
direc_length += 1;
}
}
if f.char_at(0) == '*' {
f = &f[1..];
directive_length += 1;
direc_length += 1;
if argc > 0 {
let width: i64 = string_to_scalar_type(argv[0], self);
if (c_int::MIN as i64) <= width && width <= (c_int::MAX as i64) {
field_width = Some(width);
field_width = width as c_int;
} else {
self.fatal_error(wgettext_fmt!(
"invalid field width: %ls",
@@ -480,45 +480,47 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
argv = &argv[1..];
argc -= 1;
} else {
field_width = Some(0);
field_width = 0;
}
have_field_width = true;
} else {
while iswdigit(f.char_at(0)) {
f = &f[1..];
directive_length += 1;
direc_length += 1;
}
}
if f.char_at(0) == '.' {
f = &f[1..];
directive_length += 1;
direc_length += 1;
modify_allowed_format_specifiers(&mut ok, "c", false);
if f.char_at(0) == '*' {
f = &f[1..];
directive_length += 1;
direc_length += 1;
if argc > 0 {
let prec: i64 = string_to_scalar_type(argv[0], self);
if prec < 0 {
// A negative precision is taken as if the precision were omitted,
// so -1 is safe here even if prec < INT_MIN.
precision = Some(-1);
precision = -1;
} else if (c_int::MAX as i64) < prec {
self.fatal_error(wgettext_fmt!(
"invalid precision: %ls",
argv[0]
));
} else {
precision = Some(prec);
precision = prec as c_int;
}
argv = &argv[1..];
argc -= 1;
} else {
precision = Some(0);
precision = 0;
}
have_precision = true;
} else {
while iswdigit(f.char_at(0)) {
f = &f[1..];
directive_length += 1;
direc_length += 1;
}
}
}
@@ -531,8 +533,8 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
if (conversion as usize) > 0xFF || !ok[conversion as usize] {
self.fatal_error(wgettext_fmt!(
"%.*ls: invalid conversion specification",
wstr_offset_in(f, directive_start) + 1,
directive_start
wstr_offset_in(f, direc_start) + 1,
direc_start
));
return 0;
}
@@ -543,10 +545,12 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
argv = &argv[1..];
argc -= 1;
}
self.print_directive(
&directive_start[..directive_length],
self.print_direc(
&direc_start[..direc_length],
f.char_at(0),
have_field_width,
field_width,
have_precision,
precision,
argument,
);

View File

@@ -5,88 +5,11 @@
use once_cell::sync::Lazy;
use crate::fallback::{fish_wcswidth, wcscasecmp};
use crate::libc::*;
use crate::wutil::perror;
use super::prelude::*;
pub mod limits {
/// Constants that exist everywhere (Linux, macOS, BSD).
/// Note these are uints on Linux but ints everywhere else - we use -1 as a sentinel
/// so cast to int.
pub mod common {
use libc;
pub const CORE: libc::c_int = libc::RLIMIT_CORE as _;
pub const DATA: libc::c_int = libc::RLIMIT_DATA as _;
pub const FSIZE: libc::c_int = libc::RLIMIT_FSIZE as _;
pub const MEMLOCK: libc::c_int = libc::RLIMIT_MEMLOCK as _;
pub const NOFILE: libc::c_int = libc::RLIMIT_NOFILE as _;
pub const STACK: libc::c_int = libc::RLIMIT_STACK as _;
pub const CPU: libc::c_int = libc::RLIMIT_CPU as _;
pub const NPROC: libc::c_int = libc::RLIMIT_NPROC as _;
pub const AS: libc::c_int = libc::RLIMIT_AS as _;
}
pub use self::common::*;
#[cfg(target_os = "linux")]
pub mod linux {
use libc;
pub const SIGPENDING: libc::c_int = libc::RLIMIT_SIGPENDING as _;
pub const MSGQUEUE: libc::c_int = libc::RLIMIT_MSGQUEUE as _;
pub const RTPRIO: libc::c_int = libc::RLIMIT_RTPRIO as _;
pub const RTTIME: libc::c_int = libc::RLIMIT_RTTIME as _;
pub const RSS: libc::c_int = libc::RLIMIT_RSS as _;
pub const SBSIZE: libc::c_int = -1;
pub const NICE: libc::c_int = -1;
pub const SWAP: libc::c_int = -1;
pub const KQUEUES: libc::c_int = -1;
pub const NPTS: libc::c_int = -1;
pub const NTHR: libc::c_int = -1;
}
#[cfg(target_os = "linux")]
pub use self::linux::*;
#[cfg(any(target_os = "ios", target_os = "macos"))]
pub mod macos {
use libc;
pub const SIGPENDING: libc::c_int = -1;
pub const MSGQUEUE: libc::c_int = -1;
pub const RTPRIO: libc::c_int = -1;
pub const RTTIME: libc::c_int = -1;
pub const SBSIZE: libc::c_int = -1;
pub const NICE: libc::c_int = -1;
pub const RSS: libc::c_int = -1;
pub const SWAP: libc::c_int = -1;
pub const KQUEUES: libc::c_int = -1;
pub const NPTS: libc::c_int = -1;
pub const NTHR: libc::c_int = -1;
}
#[cfg(any(target_os = "ios", target_os = "macos"))]
pub use self::macos::*;
#[cfg(bsd)]
pub mod bsd {
use libc;
pub const SBSIZE: libc::c_int = libc::RLIMIT_SBSIZE;
pub const NICE: libc::c_int = libc::RLIMIT_NICE;
pub const RSS: libc::c_int = libc::RLIMIT_RSS;
pub const NTHR: libc::c_int = libc::RLIMIT_NTHR;
pub const SWAP: libc::c_int = libc::RLIMIT_SWAP;
pub const KQUEUES: libc::c_int = libc::RLIMIT_KQUEUES;
pub const NPTS: libc::c_int = libc::RLIMIT_NPTS;
pub const SIGPENDING: libc::c_int = -1;
pub const MSGQUEUE: libc::c_int = -1;
pub const RTPRIO: libc::c_int = -1;
pub const RTTIME: libc::c_int = -1;
}
#[cfg(bsd)]
pub use self::bsd::*;
}
/// Calls getrlimit.
fn getrlimit(resource: c_uint) -> Option<(rlim_t, rlim_t)> {
let resource: i32 = resource.try_into().unwrap();
@@ -134,7 +57,7 @@ fn print_all(hard: bool, streams: &mut IoStreams) {
};
let l = if hard { rlim_max } else { rlim_cur };
let unit = if resource.resource == limits::CPU as c_uint {
let unit = if resource.resource == RLIMIT_CPU() as c_uint {
"(seconds, "
} else if get_multiplier(resource.resource) == 1 {
"("
@@ -237,7 +160,7 @@ struct Options {
impl Default for Options {
fn default() -> Self {
Options {
what: limits::FSIZE,
what: RLIMIT_FSIZE(),
report_all: false,
hard: false,
soft: false,
@@ -286,26 +209,26 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
'a' => opts.report_all = true,
'H' => opts.hard = true,
'S' => opts.soft = true,
'b' => opts.what = limits::SBSIZE,
'c' => opts.what = limits::CORE,
'd' => opts.what = limits::DATA,
'e' => opts.what = limits::NICE,
'f' => opts.what = limits::FSIZE,
'i' => opts.what = limits::SIGPENDING,
'l' => opts.what = limits::MEMLOCK,
'm' => opts.what = limits::RSS,
'n' => opts.what = limits::NOFILE,
'q' => opts.what = limits::MSGQUEUE,
'r' => opts.what = limits::RTPRIO,
's' => opts.what = limits::STACK,
't' => opts.what = limits::CPU,
'u' => opts.what = limits::NPROC,
'v' => opts.what = limits::AS,
'w' => opts.what = limits::SWAP,
'y' => opts.what = limits::RTTIME,
'K' => opts.what = limits::KQUEUES,
'P' => opts.what = limits::NPTS,
'T' => opts.what = limits::NTHR,
'b' => opts.what = RLIMIT_SBSIZE(),
'c' => opts.what = RLIMIT_CORE(),
'd' => opts.what = RLIMIT_DATA(),
'e' => opts.what = RLIMIT_NICE(),
'f' => opts.what = RLIMIT_FSIZE(),
'i' => opts.what = RLIMIT_SIGPENDING(),
'l' => opts.what = RLIMIT_MEMLOCK(),
'm' => opts.what = RLIMIT_RSS(),
'n' => opts.what = RLIMIT_NOFILE(),
'q' => opts.what = RLIMIT_MSGQUEUE(),
'r' => opts.what = RLIMIT_RTPRIO(),
's' => opts.what = RLIMIT_STACK(),
't' => opts.what = RLIMIT_CPU(),
'u' => opts.what = RLIMIT_NPROC(),
'v' => opts.what = RLIMIT_AS(),
'w' => opts.what = RLIMIT_SWAP(),
'y' => opts.what = RLIMIT_RTTIME(),
'K' => opts.what = RLIMIT_KQUEUES(),
'P' => opts.what = RLIMIT_NPTS(),
'T' => opts.what = RLIMIT_NTHR(),
'h' => {
builtin_print_help(parser, streams, cmd);
return Ok(SUCCESS);
@@ -437,96 +360,101 @@ fn new(
static RESOURCE_ARR: Lazy<Box<[Resource]>> = Lazy::new(|| {
let resources_info = [
(
limits::SBSIZE,
RLIMIT_SBSIZE(),
L!("Maximum size of socket buffers"),
'b',
1024,
),
(
limits::CORE,
RLIMIT_CORE(),
L!("Maximum size of core files created"),
'c',
1024,
),
(
limits::DATA,
RLIMIT_DATA(),
L!("Maximum size of a processs data segment"),
'd',
1024,
),
(limits::NICE, L!("Control of maximum nice priority"), 'e', 1),
(
limits::FSIZE,
RLIMIT_NICE(),
L!("Control of maximum nice priority"),
'e',
1,
),
(
RLIMIT_FSIZE(),
L!("Maximum size of files created by the shell"),
'f',
1024,
),
(
limits::SIGPENDING,
RLIMIT_SIGPENDING(),
L!("Maximum number of pending signals"),
'i',
1,
),
(
limits::MEMLOCK,
RLIMIT_MEMLOCK(),
L!("Maximum size that may be locked into memory"),
'l',
1024,
),
(limits::RSS, L!("Maximum resident set size"), 'm', 1024),
(RLIMIT_RSS(), L!("Maximum resident set size"), 'm', 1024),
(
limits::NOFILE,
RLIMIT_NOFILE(),
L!("Maximum number of open file descriptors"),
'n',
1,
),
(
limits::MSGQUEUE,
RLIMIT_MSGQUEUE(),
L!("Maximum bytes in POSIX message queues"),
'q',
1024,
),
(
limits::RTPRIO,
RLIMIT_RTPRIO(),
L!("Maximum realtime scheduling priority"),
'r',
1,
),
(limits::STACK, L!("Maximum stack size"), 's', 1024),
(RLIMIT_STACK(), L!("Maximum stack size"), 's', 1024),
(
limits::CPU,
RLIMIT_CPU(),
L!("Maximum amount of CPU time in seconds"),
't',
1,
),
(
limits::NPROC,
RLIMIT_NPROC(),
L!("Maximum number of processes available to current user"),
'u',
1,
),
(
limits::AS,
RLIMIT_AS(),
L!("Maximum amount of virtual memory available to each process"),
'v',
1024,
),
(limits::SWAP, L!("Maximum swap space"), 'w', 1024),
(RLIMIT_SWAP(), L!("Maximum swap space"), 'w', 1024),
(
limits::RTTIME,
RLIMIT_RTTIME(),
L!("Maximum contiguous realtime CPU time"),
'y',
1,
),
(limits::KQUEUES, L!("Maximum number of kqueues"), 'K', 1),
(RLIMIT_KQUEUES(), L!("Maximum number of kqueues"), 'K', 1),
(
limits::NPTS,
RLIMIT_NPTS(),
L!("Maximum number of pseudo-terminals"),
'P',
1,
),
(
limits::NTHR,
RLIMIT_NTHR(),
L!("Maximum number of simultaneous threads"),
'T',
1,

View File

@@ -9,16 +9,14 @@
use crate::global_safety::AtomicRef;
use crate::global_safety::RelaxedAtomicBool;
use crate::key;
use crate::locale::invalidate_numeric_locale;
use crate::libc::MB_CUR_MAX;
use crate::parse_util::parse_util_escape_string_with_quote;
use crate::terminal::Output;
use crate::termsize::Termsize;
use crate::wchar::{decode_byte_from_char, encode_byte_to_char, prelude::*};
use crate::wcstringutil::wcs2string_callback;
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use crate::wutil::encoding::{
mbrtowc, probe_is_multibyte_locale, wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX,
};
use crate::wutil::encoding::{mbrtowc, wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX};
use crate::wutil::fish_iswalnum;
use bitflags::bitflags;
use libc::{SIGTTOU, SIG_IGN, STDIN_FILENO};
@@ -34,6 +32,8 @@
use std::sync::{Arc, MutexGuard};
use std::time;
pub const BUILD_DIR: &str = env!("FISH_RESOLVED_BUILD_DIR");
pub const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME");
// Highest legal ASCII value.
@@ -188,7 +188,7 @@ fn escape_string_script(input: &wstr, flags: EscapeFlags) -> WString {
let no_quoted = flags.contains(EscapeFlags::NO_QUOTED);
let no_tilde = flags.contains(EscapeFlags::NO_TILDE);
let no_qmark = feature_test(FeatureFlag::qmark_noglob);
let symbolic = flags.contains(EscapeFlags::SYMBOLIC) && get_is_multibyte_locale();
let symbolic = flags.contains(EscapeFlags::SYMBOLIC) && MB_CUR_MAX() > 1;
assert!(
!symbolic || !escape_printables,
@@ -1062,13 +1062,6 @@ pub fn get_obfuscation_read_char() -> char {
char::from_u32(OBFUSCATION_READ_CHAR.load(Ordering::Relaxed)).unwrap()
}
static IS_MB_LOCALE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
/// Whether we believe we are in a multibyte locale.
pub fn get_is_multibyte_locale() -> bool {
IS_MB_LOCALE.load()
}
/// Profiling flag. True if commands should be profiled.
pub static PROFILING_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
@@ -1287,9 +1280,6 @@ pub fn should_suppress_stderr_for_tests() -> bool {
/// This function should be called after calling `setlocale()` to perform fish specific locale
/// initialization.
pub fn fish_setlocale() {
// Invalidate the cached numeric locale.
invalidate_numeric_locale();
// Helper to make a static reference to a static &'wstr, from a string literal.
// This is necessary to store them in global atomics, as these can't handle fat pointers.
macro_rules! LL {
@@ -1299,9 +1289,6 @@ macro_rules! LL {
}};
}
// Mark if we are a multibyte locale.
IS_MB_LOCALE.store(probe_is_multibyte_locale());
// Use various Unicode symbols if they can be encoded using the current locale, else a simple
// ASCII char alternative. All of the can_be_encoded() invocations should return the same
// true/false value since the code points are in the BMP but we're going to be paranoid. This

View File

@@ -1,7 +1,7 @@
use crate::common::wcs2string;
use crate::common::BUILD_DIR;
use crate::wchar::prelude::*;
use crate::{common::get_executable_path, FLOG, FLOGF};
#[cfg(not(feature = "embed-data"))]
use fish_build_helper::workspace_root;
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
@@ -63,7 +63,14 @@ fn static_paths() -> Self {
fn from_exec_path(exec_path: PathBuf) -> Self {
FLOG!(config, "embed-data feature is active, ignoring data paths");
Self {
sysconf: PathBuf::from(SYSCONF_DIR).join("fish"),
sysconf: if exec_path
.canonicalize()
.is_ok_and(|exec_path| exec_path.starts_with(BUILD_DIR))
{
workspace_root().join("etc")
} else {
PathBuf::from(SYSCONF_DIR).join("fish")
},
bin: exec_path.parent().map(|x| x.to_path_buf()),
}
}
@@ -116,7 +123,7 @@ fn from_exec_path(unresolved_exec_path: PathBuf) -> Self {
}
// If we're in Cargo's target directory or in CMake's build directory, use the source files.
if exec_path.starts_with(env!("FISH_BUILD_DIR")) {
if exec_path.starts_with(BUILD_DIR) {
FLOG!(
config,
format!(

View File

@@ -13,7 +13,8 @@
use crate::flog::FLOG;
use crate::global_safety::RelaxedAtomicBool;
use crate::input::{init_input, FISH_BIND_MODE_VAR};
use crate::nix::{geteuid, getpid};
use crate::libc::{stdout_stream, C_PATH_BSHELL, _PATH_BSHELL};
use crate::nix::{geteuid, getpid, isatty};
use crate::null_terminated_array::OwningNullTerminatedArray;
use crate::path::{
path_emit_config_directory_messages, path_get_cache, path_get_config, path_get_data,
@@ -25,11 +26,13 @@
use crate::wchar::prelude::*;
use crate::wcstringutil::join_strings;
use crate::wutil::{fish_wcstol, wgetcwd, wgettext};
use std::sync::atomic::Ordering;
use libc::{c_int, confstr, uid_t};
use libc::{c_int, confstr, uid_t, STDOUT_FILENO, _IONBF};
use once_cell::sync::{Lazy, OnceCell};
use std::collections::HashMap;
use std::ffi::CStr;
use std::io::Write;
use std::mem::MaybeUninit;
use std::os::unix::prelude::*;
use std::sync::Arc;
@@ -566,12 +569,13 @@ fn setup_user(vars: &EnvStack) {
}
pub(crate) static FALLBACK_PATH: Lazy<&[WString]> = Lazy::new(|| {
use crate::libc::_CS_PATH;
// _CS_PATH: colon-separated paths to find POSIX utilities
let buf_size = unsafe { confstr(libc::_CS_PATH, std::ptr::null_mut(), 0) };
let buf_size = unsafe { confstr(_CS_PATH(), std::ptr::null_mut(), 0) };
Box::leak(
(if buf_size > 0 {
let mut buf = vec![b'\0' as libc::c_char; buf_size];
unsafe { confstr(libc::_CS_PATH, buf.as_mut_ptr(), buf_size) };
unsafe { confstr(_CS_PATH(), buf.as_mut_ptr(), buf_size) };
let buf = buf;
// safety: buf should contain a null-byte, and is not mutable unless we move ownership
let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
@@ -854,4 +858,13 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
/// Various things we need to initialize at run-time that don't really fit any of the other init
/// routines.
pub fn misc_init() {}
pub fn misc_init() {
// If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might
// be intermixed with `write()` calls and we need to ensure the writes are not reordered. See
// issue #3748.
if isatty(STDOUT_FILENO) {
let _ = std::io::stdout().flush();
unsafe { libc::setvbuf(stdout_stream(), std::ptr::null_mut(), _IONBF, 0) };
}
_PATH_BSHELL.store(unsafe { C_PATH_BSHELL().cast_mut() }, Ordering::SeqCst);
}

View File

@@ -14,7 +14,6 @@
use crate::terminal::ColorSupport;
use crate::tty_handoff::xtversion;
use crate::wchar::prelude::*;
use crate::wutil::encoding::probe_is_multibyte_locale;
use crate::wutil::fish_wcstoi;
use crate::{function, terminal};
use std::borrow::Cow;
@@ -552,20 +551,21 @@ fn init_locale(vars: &EnvStack) {
.map(|allow_c| !crate::wcstringutil::bool_from_string(&allow_c))
.unwrap_or(true);
if fix_locale && !probe_is_multibyte_locale() {
FLOG!(env_locale, "Have single byte locale, trying to fix.");
let mut fixed = false;
if fix_locale && crate::libc::MB_CUR_MAX() == 1 {
FLOG!(env_locale, "Have singlebyte locale, trying to fix.");
for locale in UTF8_LOCALES {
let locale_cstr = CString::new(*locale).unwrap();
// this can fail, that is fine
unsafe { libc::setlocale(libc::LC_CTYPE, locale_cstr.as_ptr()) };
if probe_is_multibyte_locale() {
{
let locale = CString::new(locale.to_owned()).unwrap();
// this can fail, that is fine
unsafe { libc::setlocale(libc::LC_CTYPE, locale.as_ptr()) };
}
if crate::libc::MB_CUR_MAX() > 1 {
FLOG!(env_locale, "Fixed locale:", locale);
fixed = true;
break;
}
}
if !fixed {
if crate::libc::MB_CUR_MAX() == 1 {
FLOG!(env_locale, "Failed to fix locale.");
}
}
@@ -575,7 +575,8 @@ fn init_locale(vars: &EnvStack) {
// should never fail, the C locale should always be defined
assert_ne!(loc_ptr, ptr::null_mut());
// Update cached locale information.
// See that we regenerate our special locale for numbers
crate::locale::invalidate_numeric_locale();
crate::common::fish_setlocale();
FLOG!(
env_locale,

View File

@@ -24,12 +24,12 @@
};
#[cfg(FISH_USE_POSIX_SPAWN)]
use crate::fork_exec::spawn::PosixSpawner;
use crate::fork_exec::PATH_BSHELL;
use crate::function::{self, FunctionProperties};
use crate::io::{
BufferedOutputStream, FdOutputStream, IoBufferfill, IoChain, IoClose, IoMode, IoPipe,
IoStreams, OutputStream, SeparatedBuffer, StringOutputStream,
};
use crate::libc::_PATH_BSHELL;
use crate::nix::{getpid, isatty};
use crate::null_terminated_array::OwningNullTerminatedArray;
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
@@ -49,7 +49,7 @@
use crate::wutil::{fish_wcstol, perror};
use errno::{errno, set_errno};
use libc::{
c_char, EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
STDIN_FILENO, STDOUT_FILENO,
};
use nix::fcntl::OFlag;
@@ -407,14 +407,13 @@ fn safe_launch_process(
if nargs <= maxargs {
// +1 for /bin/sh, +1 for terminating nullptr
let mut argv2 = [std::ptr::null(); 1 + maxargs + 1];
let bshell = PATH_BSHELL.as_ptr() as *const c_char;
argv2[0] = bshell as *mut c_char;
argv2[0] = _PATH_BSHELL.load(Ordering::Relaxed);
argv2[1..argv.len() + 1].copy_from_slice(argv);
// The command to call should use the full path,
// not what we would pass as argv0.
argv2[1] = actual_cmd.as_ptr();
unsafe {
libc::execve(bshell, &argv2[0], envv.get());
libc::execve(_PATH_BSHELL.load(Ordering::Relaxed), &argv2[0], envv.get());
}
}
}

View File

@@ -22,10 +22,3 @@ pub fn blocked_signals_for_job(job: &Job, sigmask: &mut libc::sigset_t) -> bool
}
false
}
// Bravely define _PATH_BSHELL. On practice it's /bin/sh everywhere, except on Android.
#[cfg(not(target_os = "android"))]
pub static PATH_BSHELL: &[u8] = b"/bin/sh\0";
#[cfg(target_os = "android")]
pub static PATH_BSHELL: &[u8] = b"/system/bin/sh\0";

View File

@@ -1,8 +1,8 @@
//! Wrappers around posix_spawn.
use super::blocked_signals_for_job;
use super::PATH_BSHELL;
use crate::exec::{is_thompson_shell_script, PgroupPolicy};
use crate::libc::_PATH_BSHELL;
use crate::proc::Job;
use crate::redirection::Dup2List;
use crate::signal::signals_to_default;
@@ -10,6 +10,7 @@
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
use std::ffi::{CStr, CString};
use std::mem::MaybeUninit;
use std::sync::atomic::Ordering;
// The posix_spawn family of functions is unusual in that it returns errno codes directly in the return value, not via errno.
// This converts to an error if nonzero.
@@ -172,7 +173,7 @@ pub(crate) fn spawn(
let cmdcstr = unsafe { CStr::from_ptr(cmd) };
if spawn_err.0 == libc::ENOEXEC && is_thompson_shell_script(cmdcstr) {
// Create a new argv with /bin/sh prepended.
let mut argv2 = vec![PATH_BSHELL.as_ptr() as *mut c_char];
let mut argv2 = vec![_PATH_BSHELL.load(Ordering::Relaxed) as *mut c_char];
// The command to call should use the full path,
// not what we would pass as argv0.
@@ -189,7 +190,7 @@ pub(crate) fn spawn(
check_fail(unsafe {
libc::posix_spawn(
&mut pid,
PATH_BSHELL.as_ptr() as *const c_char,
_PATH_BSHELL.load(Ordering::Relaxed),
&self.actions.0,
&self.attr.0,
argv2.as_ptr(),

View File

@@ -8,6 +8,7 @@
VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE,
};
use crate::expand::{expand_tilde, ExpandFlags, HOME_DIRECTORY};
use crate::libc::_PC_CASE_SENSITIVE;
use crate::operation_context::OperationContext;
use crate::path::path_apply_working_directory;
use crate::redirection::RedirectionMode;
@@ -22,6 +23,7 @@
dir_iter::DirIter, fish_wcstoi, normalize_path, waccess, wbasename, wdirname, wstat,
};
use libc::PATH_MAX;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::os::fd::RawFd;
@@ -389,30 +391,27 @@ pub fn is_potential_cd_path(
/// false: the filesystem is not case insensitive
/// true: the file system is case insensitive
pub type CaseSensitivityCache = HashMap<WString, bool>;
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn fs_is_case_insensitive(
path: &wstr,
fd: RawFd,
case_sensitivity_cache: &mut CaseSensitivityCache,
) -> bool {
if let Some(cached) = case_sensitivity_cache.get(path) {
return *cached;
};
// Ask the system. A -1 value means error (so assume case sensitive), a 1 value means case
// sensitive, and a 0 value means case insensitive.
let ret = unsafe { libc::fpathconf(fd, libc::_PC_CASE_SENSITIVE) };
let icase = ret == 0;
case_sensitivity_cache.insert(path.to_owned(), icase);
icase
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub fn fs_is_case_insensitive(
_path: &wstr,
_fd: RawFd,
_case_sensitivity_cache: &mut CaseSensitivityCache,
) -> bool {
// Other platforms dont have _PC_CASE_SENSITIVE.
false
let mut result = false;
if *_PC_CASE_SENSITIVE != 0 {
// Try the cache first.
match case_sensitivity_cache.entry(path.to_owned()) {
Entry::Occupied(e) => {
/* Use the cached value */
result = *e.get();
}
Entry::Vacant(e) => {
// Ask the system. A -1 value means error (so assume case sensitive), a 1 value means case
// sensitive, and a 0 value means case insensitive.
let ret = unsafe { libc::fpathconf(fd, *_PC_CASE_SENSITIVE) };
result = ret == 0;
e.insert(result);
}
}
}
result
}

View File

@@ -1,6 +1,6 @@
use crate::common::{
fish_reserved_codepoint, get_is_multibyte_locale, is_windows_subsystem_for_linux, read_blocked,
shell_modes, str2wcstring, WSL,
fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked, shell_modes,
str2wcstring, WSL,
};
use crate::env::{EnvStack, Environment};
use crate::fd_readable_set::{FdReadableSet, Timeout};
@@ -12,8 +12,8 @@
use crate::reader::reader_test_and_clear_interrupted;
use crate::threads::iothread_port;
use crate::tty_handoff::{
get_kitty_keyboard_capability, maybe_set_kitty_keyboard_capability,
maybe_set_scroll_content_up_capability, SCROLL_CONTENT_UP_TERMINFO_CODE, XTVERSION,
maybe_set_kitty_keyboard_capability, maybe_set_scroll_content_up_capability,
SCROLL_CONTENT_UP_TERMINFO_CODE, XTVERSION,
};
use crate::universal_notifier::default_notifier;
use crate::wchar::{encode_byte_to_char, prelude::*};
@@ -983,16 +983,13 @@ fn try_readb(&mut self, buffer: &mut Vec<u8>) -> Option<u8> {
let fd = self.get_in_fd();
if !check_fd_readable(
fd,
Duration::from_millis(
if self.paste_is_buffering()
|| self.is_blocked_querying()
|| get_kitty_keyboard_capability() == Some(&true)
{
300
} else {
1
},
),
Duration::from_millis(if self.paste_is_buffering() || self.is_blocked_querying() {
300
} else if buffer == b"\x1b" {
1 // distinguish legacy escape
} else {
30
}),
) {
FLOG!(
reader,
@@ -1699,8 +1696,10 @@ pub(crate) fn decode_input_byte(
use DecodeState::*;
let mut res: char = '\0';
let read_byte = *buffer.last().unwrap();
if !get_is_multibyte_locale() {
if crate::libc::MB_CUR_MAX() == 1 {
// single-byte locale, all values are legal
// FIXME: this looks wrong, this falsely assumes that
// the single-byte locale is compatible with Unicode upper-ASCII.
res = read_byte.into();
out_seq.push(res);
return Complete;

View File

@@ -64,6 +64,8 @@
pub mod job_group;
pub mod key;
pub mod kill;
#[allow(non_snake_case)]
pub mod libc;
pub mod locale;
pub mod nix;
pub mod null_terminated_array;

167
src/libc.c Normal file
View File

@@ -0,0 +1,167 @@
#include <locale.h>
#include <paths.h> // _PATH_BSHELL
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h> // MB_CUR_MAX
#include <sys/mount.h> // MNT_LOCAL
#include <sys/resource.h>
#include <sys/statvfs.h> // ST_LOCAL
#include <unistd.h> // _CS_PATH, _PC_CASE_SENSITIVE
size_t C_MB_CUR_MAX() { return MB_CUR_MAX; }
uint64_t C_ST_LOCAL() {
#if defined(ST_LOCAL)
return ST_LOCAL;
#else
return 0;
#endif
}
int C_CS_PATH() {
#if defined(_CS_PATH)
return _CS_PATH;
#else
return -1;
#endif
}
uint64_t C_MNT_LOCAL() {
#if defined(MNT_LOCAL)
return MNT_LOCAL;
#else
return 0;
#endif
}
const char* C_PATH_BSHELL() { return _PATH_BSHELL; }
int C_PC_CASE_SENSITIVE() {
#ifdef _PC_CASE_SENSITIVE
return _PC_CASE_SENSITIVE;
#else
return 0;
#endif
}
FILE* stdout_stream() { return stdout; }
int C_RLIMIT_CORE() { return RLIMIT_CORE; }
int C_RLIMIT_DATA() { return RLIMIT_DATA; }
int C_RLIMIT_FSIZE() { return RLIMIT_FSIZE; }
int C_RLIMIT_NOFILE() { return RLIMIT_NOFILE; }
int C_RLIMIT_STACK() { return RLIMIT_STACK; }
int C_RLIMIT_CPU() { return RLIMIT_CPU; }
int C_RLIMIT_SBSIZE() {
#ifdef RLIMIT_SBSIZE
return RLIMIT_SBSIZE;
#else
return -1;
#endif
}
int C_RLIMIT_NICE() {
#ifdef RLIMIT_NICE
return RLIMIT_NICE;
#else
return -1;
#endif
}
int C_RLIMIT_SIGPENDING() {
#ifdef RLIMIT_SIGPENDING
return RLIMIT_SIGPENDING;
#else
return -1;
#endif
}
int C_RLIMIT_MEMLOCK() {
#ifdef RLIMIT_MEMLOCK
return RLIMIT_MEMLOCK;
#else
return -1;
#endif
}
int C_RLIMIT_RSS() {
#ifdef RLIMIT_RSS
return RLIMIT_RSS;
#else
return -1;
#endif
}
int C_RLIMIT_MSGQUEUE() {
#ifdef RLIMIT_MSGQUEUE
return RLIMIT_MSGQUEUE;
#else
return -1;
#endif
}
int C_RLIMIT_RTPRIO() {
#ifdef RLIMIT_RTPRIO
return RLIMIT_RTPRIO;
#else
return -1;
#endif
}
int C_RLIMIT_NPROC() {
#ifdef RLIMIT_NPROC
return RLIMIT_NPROC;
#else
return -1;
#endif
}
int C_RLIMIT_AS() {
#ifdef RLIMIT_AS
return RLIMIT_AS;
#else
return -1;
#endif
}
int C_RLIMIT_SWAP() {
#ifdef RLIMIT_SWAP
return RLIMIT_SWAP;
#else
return -1;
#endif
}
int C_RLIMIT_RTTIME() {
#ifdef RLIMIT_RTTIME
return RLIMIT_RTTIME;
#else
return -1;
#endif
}
int C_RLIMIT_KQUEUES() {
#ifdef RLIMIT_KQUEUES
return RLIMIT_KQUEUES;
#else
return -1;
#endif
}
int C_RLIMIT_NPTS() {
#ifdef RLIMIT_NPTS
return RLIMIT_NPTS;
#else
return -1;
#endif
}
int C_RLIMIT_NTHR() {
#ifdef RLIMIT_NTHR
return RLIMIT_NTHR;
#else
return -1;
#endif
}

56
src/libc.rs Normal file
View File

@@ -0,0 +1,56 @@
use std::sync::atomic::AtomicPtr;
use libc::{c_char, c_int};
use once_cell::sync::Lazy;
pub static _PATH_BSHELL: AtomicPtr<c_char> = AtomicPtr::new(std::ptr::null_mut());
extern "C" {
pub fn C_PATH_BSHELL() -> *const c_char;
}
pub static _PC_CASE_SENSITIVE: Lazy<c_int> = Lazy::new(|| unsafe { C_PC_CASE_SENSITIVE() });
extern "C" {
fn C_PC_CASE_SENSITIVE() -> c_int;
}
extern "C" {
pub fn stdout_stream() -> *mut libc::FILE;
pub fn setlinebuf(stream: *mut libc::FILE);
}
macro_rules! CVAR {
($cfn:ident, $cvar:ident, $type:ident) => {
pub fn $cvar() -> $type {
extern "C" {
fn $cfn() -> $type;
}
unsafe { $cfn() }
}
};
}
CVAR!(C_MB_CUR_MAX, MB_CUR_MAX, usize);
CVAR!(C_ST_LOCAL, ST_LOCAL, u64);
CVAR!(C_MNT_LOCAL, MNT_LOCAL, u64);
CVAR!(C_CS_PATH, _CS_PATH, i32);
CVAR!(C_RLIMIT_SBSIZE, RLIMIT_SBSIZE, i32);
CVAR!(C_RLIMIT_CORE, RLIMIT_CORE, i32);
CVAR!(C_RLIMIT_DATA, RLIMIT_DATA, i32);
CVAR!(C_RLIMIT_NICE, RLIMIT_NICE, i32);
CVAR!(C_RLIMIT_FSIZE, RLIMIT_FSIZE, i32);
CVAR!(C_RLIMIT_SIGPENDING, RLIMIT_SIGPENDING, i32);
CVAR!(C_RLIMIT_MEMLOCK, RLIMIT_MEMLOCK, i32);
CVAR!(C_RLIMIT_RSS, RLIMIT_RSS, i32);
CVAR!(C_RLIMIT_NOFILE, RLIMIT_NOFILE, i32);
CVAR!(C_RLIMIT_MSGQUEUE, RLIMIT_MSGQUEUE, i32);
CVAR!(C_RLIMIT_RTPRIO, RLIMIT_RTPRIO, i32);
CVAR!(C_RLIMIT_STACK, RLIMIT_STACK, i32);
CVAR!(C_RLIMIT_CPU, RLIMIT_CPU, i32);
CVAR!(C_RLIMIT_NPROC, RLIMIT_NPROC, i32);
CVAR!(C_RLIMIT_AS, RLIMIT_AS, i32);
CVAR!(C_RLIMIT_SWAP, RLIMIT_SWAP, i32);
CVAR!(C_RLIMIT_RTTIME, RLIMIT_RTTIME, i32);
CVAR!(C_RLIMIT_KQUEUES, RLIMIT_KQUEUES, i32);
CVAR!(C_RLIMIT_NPTS, RLIMIT_NPTS, i32);
CVAR!(C_RLIMIT_NTHR, RLIMIT_NTHR, i32);

View File

@@ -4,14 +4,14 @@
use std::collections::HashMap;
use crate::common::{
escape_string, get_ellipsis_char, get_ellipsis_str, get_is_multibyte_locale, EscapeFlags,
EscapeStringStyle,
escape_string, get_ellipsis_char, get_ellipsis_str, EscapeFlags, EscapeStringStyle,
};
use crate::complete::Completion;
use crate::editable_line::EditableLine;
#[allow(unused_imports)]
use crate::future::IsSomeAnd;
use crate::highlight::{highlight_shell, HighlightRole, HighlightSpec};
use crate::libc::MB_CUR_MAX;
use crate::operation_context::OperationContext;
use crate::screen::{wcswidth_rendered, wcwidth_rendered, CharOffset, Line, ScreenData};
use crate::termsize::Termsize;
@@ -1243,7 +1243,7 @@ fn process_completions_into_infos(lst: &[Completion]) -> Vec<PagerComp> {
// We should probably fix this by first highlighting the original completion, and
// then writing a variant of escape_string() that adjusts highlighting according
// so it matches the escaped string.
&& get_is_multibyte_locale()
&& MB_CUR_MAX() > 1
{
highlight_shell(
&comp.completion,

View File

@@ -585,10 +585,10 @@ fn make_base_directory(xdg_var: &wstr, non_xdg_homepath: &wstr) -> BaseDirectory
// the actual $HOME or $XDG_XXX directories. This prevents the tests from failing and/or stops
// the tests polluting the user's actual $HOME if a sandbox environment has not been set up.
{
use crate::common::str2wcstring;
use crate::common::{str2wcstring, BUILD_DIR};
use std::path::PathBuf;
let mut build_dir = PathBuf::from(env!("FISH_BUILD_DIR"));
let mut build_dir = PathBuf::from(BUILD_DIR);
build_dir.push("fish-test-home");
let err = match std::fs::create_dir_all(&build_dir) {
@@ -694,20 +694,49 @@ pub fn path_remoteness(path: &wstr) -> DirRemoteness {
}
#[cfg(not(any(target_os = "linux", cygwin)))]
{
let mut buf = MaybeUninit::uninit();
if unsafe { libc::statfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 0 {
return DirRemoteness::unknown;
}
let buf = unsafe { buf.assume_init() };
// statfs::f_flag is hard-coded as 64-bits on 32/64-bit FreeBSD but it's a (4-byte)
// long on 32-bit NetBSD.. and always 4-bytes on macOS (even on 64-bit builds).
#[allow(clippy::useless_conversion)]
let flags = u64::from(buf.f_flags);
if flags & (libc::MNT_LOCAL as u64) != 0 {
DirRemoteness::local
} else {
DirRemoteness::remote
fn remoteness_via_statfs<StatFS, Flags>(
statfn: unsafe extern "C" fn(*const libc::c_char, *mut StatFS) -> libc::c_int,
flagsfn: fn(&StatFS) -> Flags,
is_local_flag: u64,
path: &std::ffi::CStr,
) -> DirRemoteness
where
u64: From<Flags>,
{
if is_local_flag == 0 {
return DirRemoteness::unknown;
}
let mut buf = MaybeUninit::uninit();
if unsafe { (statfn)(path.as_ptr(), buf.as_mut_ptr()) } < 0 {
return DirRemoteness::unknown;
}
let buf = unsafe { buf.assume_init() };
// statfs::f_flag is hard-coded as 64-bits on 32/64-bit FreeBSD but it's a (4-byte)
// long on 32-bit NetBSD.. and always 4-bytes on macOS (even on 64-bit builds).
#[allow(clippy::useless_conversion)]
if u64::from((flagsfn)(&buf)) & is_local_flag != 0 {
DirRemoteness::local
} else {
DirRemoteness::remote
}
}
// ST_LOCAL is a flag to statvfs, which is itself standardized.
// In practice the only system to define it is NetBSD.
#[cfg(target_os = "netbsd")]
let remoteness = remoteness_via_statfs(
libc::statvfs,
|stat: &libc::statvfs| stat.f_flag,
crate::libc::ST_LOCAL(),
&narrow,
);
#[cfg(not(target_os = "netbsd"))]
let remoteness = remoteness_via_statfs(
libc::statfs,
|stat: &libc::statfs| stat.f_flags,
crate::libc::MNT_LOCAL(),
&narrow,
);
remoteness
}
}

View File

@@ -54,10 +54,9 @@
use crate::builtins::shared::STATUS_CMD_OK;
use crate::common::ScopeGuarding;
use crate::common::{
escape, escape_string, exit_without_destructors, get_ellipsis_char, get_is_multibyte_locale,
get_obfuscation_read_char, restore_term_foreground_process_group_for_exit, shell_modes,
str2wcstring, write_loop, EscapeFlags, EscapeStringStyle, ScopeGuard, PROGRAM_NAME,
UTF8_BOM_WCHAR,
escape, escape_string, exit_without_destructors, get_ellipsis_char, get_obfuscation_read_char,
restore_term_foreground_process_group_for_exit, shell_modes, str2wcstring, write_loop,
EscapeFlags, EscapeStringStyle, ScopeGuard, PROGRAM_NAME, UTF8_BOM_WCHAR,
};
use crate::complete::{
complete, complete_load, sort_and_prioritize, CompleteFlags, Completion, CompletionList,
@@ -98,6 +97,7 @@
use crate::io::IoChain;
use crate::key::ViewportPosition;
use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate};
use crate::libc::MB_CUR_MAX;
use crate::nix::{getpgrp, getpid, isatty};
use crate::operation_context::{get_bg_context, OperationContext};
use crate::pager::{PageRendering, Pager, SelectionMotion};
@@ -1587,7 +1587,7 @@ pub fn mouse_left_click(&mut self, cursor: ViewportPosition, click_position: Vie
self.pager.selected_completion_idx = Some(idx);
self.pager_selection_changed();
}
_ => {}
CharOffset::Pager(_) | CharOffset::None => {}
}
}
}
@@ -3226,7 +3226,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
// Update the pager data.
self.pager.set_search_field_shown(true);
self.pager.set_prefix(
if get_is_multibyte_locale() {
if MB_CUR_MAX() > 1 {
L!("")
} else {
L!("> ")

View File

@@ -629,8 +629,26 @@ pub fn offset_in_cmdline_given_cursor(
0
});
let y = y.min(self.actual.line_count() - 1);
let viewport_prompt_x = viewport_cursor.x - self.actual.cursor.x;
let x = viewport_position.x - viewport_prompt_x;
let Some(viewport_prompt_x) = viewport_cursor.x.checked_sub(self.actual.cursor.x) else {
FLOGF!(
reader,
"Actual cursor x=%d exceeds reported cursor x=%d, \
was the cursor moved by printing to the TTY?",
self.actual.cursor.x,
viewport_cursor.x
);
return CharOffset::None;
};
let Some(x) = viewport_position.x.checked_sub(viewport_prompt_x) else {
FLOGF!(
reader,
"Computed prompt x=%d exceeds mouse click x=%d, \
was the cursor moved by printing to the TTY?",
viewport_prompt_x,
viewport_position.x
);
return CharOffset::None;
};
let line = self.actual.line(y);
let x = x.max(line.indentation);
let offset = line

View File

@@ -29,7 +29,7 @@
mod wgetopt;
pub mod prelude {
use crate::common::{ScopeGuard, ScopeGuarding};
use crate::common::{ScopeGuard, ScopeGuarding, BUILD_DIR};
use crate::env::{env_init, misc_init};
use crate::parser::{CancelBehavior, Parser};
use crate::reader::{reader_deinit, reader_init};
@@ -88,9 +88,9 @@ fn deref(&self) -> &Self::Target {
pub fn test_init() -> impl ScopeGuarding<Target = ()> {
static DONE: OnceCell<()> = OnceCell::new();
DONE.get_or_init(|| {
// If we are building with `cargo build` and have build w/ `cmake`, FISH_BUILD_DIR might
// not yet exist.
let mut test_dir = PathBuf::from(env!("FISH_BUILD_DIR"));
// If we are building with `cargo build` and have build w/ `cmake`, this might not
// yet exist.
let mut test_dir = PathBuf::from(BUILD_DIR);
test_dir.push("fish-test");
std::fs::create_dir_all(&test_dir).unwrap();
set_current_dir(&test_dir).unwrap();

View File

@@ -1,15 +1,13 @@
use std::sync::MutexGuard;
use crate::common::{
escape_string, fish_setlocale, str2wcstring, unescape_string, wcs2string, EscapeFlags,
EscapeStringStyle, UnescapeStringStyle, ENCODE_DIRECT_BASE, ENCODE_DIRECT_END,
escape_string, str2wcstring, unescape_string, wcs2string, EscapeFlags, EscapeStringStyle,
UnescapeStringStyle, ENCODE_DIRECT_BASE, ENCODE_DIRECT_END,
};
use crate::locale::LOCALE_LOCK;
use crate::util::{get_rng_seed, get_seeded_rng};
use crate::wchar::{wstr, WString, L};
use crate::wutil::encoding::{
probe_is_multibyte_locale, wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX,
};
use crate::wutil::encoding::{wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX};
use rand::{Rng, RngCore};
/// wcs2string is locale-dependent, so ensure we have a multibyte locale
@@ -21,14 +19,13 @@ fn setlocale() -> MutexGuard<'static, ()> {
const UTF8_LOCALES: &[&str] = &[
"C.UTF-8", "en_US.UTF-8", "en_GB.UTF-8", "de_DE.UTF-8", "C.utf8", "UTF-8",
];
if probe_is_multibyte_locale() {
if crate::libc::MB_CUR_MAX() > 1 {
return guard;
}
for locale in UTF8_LOCALES {
let locale = std::ffi::CString::new(locale.to_owned()).unwrap();
unsafe { libc::setlocale(libc::LC_CTYPE, locale.as_ptr()) };
if probe_is_multibyte_locale() {
fish_setlocale(); // Update cached locale information.
if crate::libc::MB_CUR_MAX() > 1 {
return guard;
}
}

View File

@@ -18,7 +18,7 @@
use crate::threads::assert_is_main_thread;
use crate::wchar::prelude::*;
use crate::wchar_ext::ToWString;
use crate::wutil::{fish_wcstoul, perror, wcstoi};
use crate::wutil::{perror, wcstoi};
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
use once_cell::sync::OnceCell;
use std::mem::MaybeUninit;
@@ -27,11 +27,6 @@
/// Whether kitty keyboard protocol support is present in the TTY.
static KITTY_KEYBOARD_SUPPORTED: OnceCell<bool> = OnceCell::new();
/// Get whether the TTY supports the kitty keyboard protocol.
pub fn get_kitty_keyboard_capability() -> Option<&'static bool> {
KITTY_KEYBOARD_SUPPORTED.get()
}
/// Set that the TTY supports the kitty keyboard protocol.
pub fn maybe_set_kitty_keyboard_capability() {
KITTY_KEYBOARD_SUPPORTED.get_or_init(|| true);
@@ -66,8 +61,6 @@ pub enum TtyQuirks {
PreCsiMidnightCommander,
// Running in iTerm2 before 3.5.12, which causes issues when using the kitty keyboard protocol.
PreKittyIterm2,
// Running in Zellij before 0.42.0 which fails to activate the kitty keyboard protocol.
PreKittyZellij,
// Whether we are running under tmux.
Tmux,
// Whether we are running under WezTerm.
@@ -84,8 +77,6 @@ fn detect(vars: &dyn Environment, xtversion: &wstr) -> Self {
PreCsiMidnightCommander
} else if get_iterm2_version(xtversion).is_some_and(|v| v < (3, 5, 12)) {
PreKittyIterm2
} else if get_zellij_version(xtversion).is_some_and(|v| v < 4200) {
PreKittyZellij
} else if xtversion.starts_with(L!("tmux ")) {
Tmux
} else if xtversion.starts_with(L!("WezTerm ")) {
@@ -160,11 +151,11 @@ impl TtyQuirks {
// Determine which keyboard protocol.
// This is used from a signal handler.
fn safe_get_supported_protocol(&self) -> ProtocolKind {
use TtyQuirks::{PreCsiMidnightCommander, PreKittyIterm2, PreKittyZellij, Wezterm};
use TtyQuirks::{PreCsiMidnightCommander, PreKittyIterm2, Wezterm};
if *self == PreCsiMidnightCommander {
return ProtocolKind::None;
}
if matches!(*self, PreKittyIterm2 | PreKittyZellij) {
if *self == PreKittyIterm2 {
return ProtocolKind::Other;
}
match KITTY_KEYBOARD_SUPPORTED.get() {
@@ -635,8 +626,3 @@ fn get_iterm2_version(xtversion: &wstr) -> Option<(u32, u32, u32)> {
wcstoi(parts.next()?).ok()?,
))
}
fn get_zellij_version(xtversion: &wstr) -> Option<u64> {
let number = xtversion.strip_prefix("Zellij(")?.strip_suffix(")")?;
fish_wcstoul(number).ok()
}

View File

@@ -297,14 +297,6 @@ fn ends_with<Suffix: IntoCharIter>(&self, suffix: Suffix) -> bool {
)
}
fn strip_suffix<Suffix: IntoCharIter>(&self, suffix: Suffix) -> Option<&wstr> {
// Note the above trait requires a double ended iterator.
let iter = suffix.chars().rev();
let suffix_len = iter.clone().count();
iter_prefixes_iter(iter, self.as_char_slice().iter().rev().copied())
.then(|| self.slice_to(self.char_count() - suffix_len))
}
fn trim_matches(&self, pat: char) -> &wstr {
let slice = self.as_char_slice();
let leading_count = slice.chars().take_while(|&c| c == pat).count();

View File

@@ -1,9 +1,10 @@
//! Helper functions for working with wcstring.
use crate::common::{get_ellipsis_char, get_ellipsis_str, get_is_multibyte_locale};
use crate::common::{get_ellipsis_char, get_ellipsis_str};
use crate::expand::INTERNAL_SEPARATOR;
use crate::fallback::{fish_wcwidth, wcscasecmp, wcscasecmp_fuzzy};
use crate::flog::FLOGF;
use crate::libc::MB_CUR_MAX;
use crate::wchar::{decode_byte_from_char, prelude::*};
use crate::wutil::encoding::{wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX};
@@ -311,7 +312,7 @@ pub fn wcs2string_callback(input: &wstr, mut func: impl FnMut(&[u8]) -> bool) ->
let mut state = zero_mbstate();
let mut converted = [0_u8; AT_LEAST_MB_LEN_MAX];
let is_singlebyte_locale = !get_is_multibyte_locale();
let is_singlebyte_locale = MB_CUR_MAX() == 1;
for c in input.chars() {
// TODO: this doesn't seem sound.

View File

@@ -8,43 +8,9 @@
// HACK This should be mbstate_t from libc but that's not exposed. Since it's only written by
// libc, we define it as opaque type that should be large enough for all implementations.
pub type mbstate_t = [u64; 16];
#[inline]
pub fn zero_mbstate() -> mbstate_t {
[0; 16]
}
// HACK This should be the MB_LEN_MAX macro from libc but that's not easy to get.
pub const AT_LEAST_MB_LEN_MAX: usize = 32;
/// Return true if we believe we are in a multibyte locale.
/// Note this reads the current locale and is modestly expensive - prefer the cached
/// values in `common.rs` which is set by `fish_setlocale`.
pub fn probe_is_multibyte_locale() -> bool {
// In general we would like to read MB_CUR_MAX, but that is not exposed by Rust libc.
// Instead, check if mbrtowc for any byte in the range 0-255 returns (size_t)(-2) which indicates
// the presence of a multibyte locale.
#[inline]
fn is_mb_lead(b: u8) -> bool {
let mut st = zero_mbstate();
let mut wc: libc::wchar_t = 0;
let c = b as libc::c_char;
let n = unsafe {
mbrtowc(
std::ptr::addr_of_mut!(wc).cast::<u32>(),
std::ptr::addr_of!(c),
1,
std::ptr::addr_of_mut!(st),
)
};
n == (-2_i64 as libc::size_t)
}
// Fast path: check common lead bytes.
if is_mb_lead(0xE0) || is_mb_lead(0xC2) {
return true;
}
// Scan non-ASCII high bytes.
(0x80_u8..=0xFF).any(is_mb_lead)
}

View File

@@ -0,0 +1,12 @@
# RUN: %fish -d config | grep -v ^Debug.enabled
# REQUIRES: %fish -c 'status build-info' | grep '^Features:.*embed-data'
# CHECKERR: config: executable path: {{.*}}/fish
# CHECKERR: config: embed-data feature is active, ignoring data paths
# NOTE: When our executable is located outside the workspace, this is "/etc".
# CHECKERR: config: paths.sysconf: {{.+}}/etc
# CHECKERR: config: paths.bin: {{.*}}
# CHECKERR: config: sourcing {{.+}}/etc/config.fish
# CHECKERR: config: not sourcing {{.*}}/xdg_config_home/fish/config.fish (not readable or does not exist)

View File

@@ -1,13 +1,13 @@
#RUN: %fish %s
# This still demos the current theme.
fish_config theme show non-existent-theme
fish_config theme show non-existant-theme
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}Current{{\x1b\[m}}
# CHECK: /bright/vixens{{\x1b\[m}} jump{{\x1b\[m}} |{{\x1b\[m}} "fowl"{{\x1b\[m}} > quack{{\x1b\[m}} &{{\x1b\[m}} # This is a comment
# CHECK: {{\x1b\[m}}echo{{\x1b\[m}} 'Errors are the portal to discovery
# CHECK: {{\x1b\[m}}Th{{\x1b\[m}}is an autosuggestion
fish_config prompt show non-existent-prompt
fish_config prompt show non-existant-prompt
fish_config prompt show default
# CHECK: {{\x1b\[4m}}default{{\x1b\[m}}
@@ -23,6 +23,21 @@ fish_config theme show "fish default"
# CHECK: {{\x1b\[m}}{{\x1b\[m}}echo{{\x1b\[m}} {{\x1b\[91m}}'{{\x1b\[33m}}Errors are the portal to discovery
# CHECK: {{\x1b\[m}}{{\x1b\[m}}Th{{\x1b\[m}}{{\x1b\[90m}}is an autosuggestion
fish_config theme show | grep -E 'fish default|Default Dark'
mkdir $__fish_config_dir/themes
touch $__fish_config_dir/themes/custom-from-userconf.theme
fish_config theme show | grep -E 'fish default|Default Dark|custom-from-userconf'
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}custom-from-userconf{{\x1b\[m}}
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}Base16 Default Dark{{\x1b\[m}}
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}fish default{{\x1b\[m}}
# Override a default theme with different colors.
{
status get-file tools/web_config/themes/None.theme 2>/dev/null ||
cat $__fish_data_dir/tools/web_config/themes/None.theme
} >$__fish_config_dir/themes/"fish default.theme"
fish_config theme show | grep -E 'fish default|Base16 Default Dark' -A1
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}fish default{{\x1b\[m}}
# CHECK: {{\x1b\[m}}/bright/vixens{{\x1b\[m}} {{\x1b\[m}}jump{{\x1b\[m}}{{.*}}
# CHECK: --
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}Base16 Default Dark{{\x1b\[m}}
# CHECK: {{.*}}/bright/vixens{{.*}}