mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-25 23:21:15 -03:00
Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c98fd886fd | ||
|
|
94fdb36f6b | ||
|
|
d1a40ace7d | ||
|
|
dd4d69a288 | ||
|
|
e16ea8df11 | ||
|
|
80e1942980 | ||
|
|
99109278a6 | ||
|
|
f924a880c8 | ||
|
|
5683d26d24 | ||
|
|
740aef06df | ||
|
|
557f6d1743 | ||
|
|
8d257f5c57 | ||
|
|
d880a14b1a | ||
|
|
d4fcc00821 | ||
|
|
6d8bb292ec | ||
|
|
c638401469 | ||
|
|
5930574d8a | ||
|
|
fdef7c8689 | ||
|
|
5c36a1be1b | ||
|
|
14f747019b | ||
|
|
d7d5d2a9be | ||
|
|
750955171a | ||
|
|
36fd93215b | ||
|
|
6e7353170a | ||
|
|
62cc117c12 | ||
|
|
af00695383 | ||
|
|
85ac91eb2b | ||
|
|
d1ed582919 | ||
|
|
06a14c4a76 | ||
|
|
400d5281f4 | ||
|
|
50778670fb | ||
|
|
9037cd779d | ||
|
|
c23a4cbd9f | ||
|
|
5d8f7801f7 | ||
|
|
756134cf2b | ||
|
|
c16677fd6f | ||
|
|
13bc514aa6 | ||
|
|
1c3403825c | ||
|
|
6f1ac7c949 | ||
|
|
f5d3fd8a82 | ||
|
|
0a23a78523 | ||
|
|
725cf33f1a | ||
|
|
2d6db3f980 | ||
|
|
41b9584bb3 | ||
|
|
c915435417 | ||
|
|
afcde1222b | ||
|
|
a3cb512628 | ||
|
|
fc71ba07da | ||
|
|
9c867225ee | ||
|
|
972355e2fc | ||
|
|
8f4c80699f | ||
|
|
e79b00d9d1 | ||
|
|
2f6b1eaaf9 | ||
|
|
3546ffa3ef | ||
|
|
30f96860a7 | ||
|
|
41d50f1a71 | ||
|
|
1e9c80f34c | ||
|
|
b88d2ed812 | ||
|
|
92dd37d3c7 | ||
|
|
f24cc6a8fc | ||
|
|
3117a488ec | ||
|
|
185b91de13 | ||
|
|
e20024f0f0 | ||
|
|
501ec1905e | ||
|
|
7ebd2011ff | ||
|
|
8004f354aa | ||
|
|
77e1aead40 | ||
|
|
e48a88a4b3 | ||
|
|
1154d9f663 | ||
|
|
810a707069 | ||
|
|
7fa9e9bfb9 | ||
|
|
48b0e7e695 | ||
|
|
848fa57144 | ||
|
|
4101e831af | ||
|
|
eb803ba6a7 | ||
|
|
248a8e7c54 | ||
|
|
2fa8c8cd7f | ||
|
|
8d5f5586dc | ||
|
|
c5bc7bd5f9 | ||
|
|
2b3bd29588 | ||
|
|
0be3f9e57e | ||
|
|
2524ece2cc | ||
|
|
b975472828 | ||
|
|
20427ff1f6 | ||
|
|
5b3b825ab2 | ||
|
|
ccd3348eed | ||
|
|
845b9be1f5 | ||
|
|
400f2b130a | ||
|
|
362f7cedf6 | ||
|
|
2c959469f0 | ||
|
|
6c34bcf8f6 | ||
|
|
f510b62b7f | ||
|
|
b31387416d | ||
|
|
941a6cb434 | ||
|
|
931072f5d1 | ||
|
|
f4f9db73da | ||
|
|
9ef3f30c56 | ||
|
|
d19c927760 | ||
|
|
22e5b21f10 | ||
|
|
f0d2444769 | ||
|
|
7975060e4a | ||
|
|
354dc3d272 | ||
|
|
7640e95bd7 | ||
|
|
767115a93d | ||
|
|
f0c8788a52 | ||
|
|
a3cbb01b27 | ||
|
|
d630b4ae8a | ||
|
|
a2c5b2a567 | ||
|
|
18295f4402 | ||
|
|
443fd604cc | ||
|
|
9e022ff7cf | ||
|
|
aba927054f |
@@ -29,6 +29,7 @@ freebsd_task:
|
||||
freebsd_instance:
|
||||
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
|
||||
tests_script:
|
||||
- pkg update
|
||||
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
|
||||
# libclang.so is a required build dependency for rust-c++ ffi bridge
|
||||
- pkg install -y llvm
|
||||
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,11 +1,8 @@
|
||||
## Description
|
||||
|
||||
Talk about your changes here.
|
||||
|
||||
Fixes issue #
|
||||
|
||||
## TODOs:
|
||||
<!-- Just check off what what we know been done so far. We can help you with this stuff. -->
|
||||
<!-- Check off what what has been done so far. -->
|
||||
- [ ] If addressing an issue, a commit message mentions `Fixes issue #<issue-number>`
|
||||
- [ ] Changes to fish usage are reflected in user documentation/manpages.
|
||||
- [ ] Tests have been added for regressions fixed
|
||||
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Don't document changes for completions inside CHANGELOG.rst, there are lot of such edits -->
|
||||
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Usually skipped for changes to completions -->
|
||||
|
||||
@@ -1,9 +1,47 @@
|
||||
fish 4.3.3 (released January 07, 2026)
|
||||
======================================
|
||||
|
||||
This release fixes the following problems identified in fish 4.3.0:
|
||||
|
||||
- Selecting a completion could insert only part of the token (:issue:`12249`).
|
||||
- Glitch with soft-wrapped autosuggestions and :doc:`fish_right_prompt <cmds/fish_right_prompt>` (:issue:`12255`).
|
||||
- Spurious echo in tmux when typing a command really fast (:issue:`12261`).
|
||||
- ``tomorrow`` theme always using the light variant (:issue:`12266`).
|
||||
- ``fish_config theme choose`` sometimes not shadowing themes set by e.g. webconfig (:issue:`12278`).
|
||||
- The sample prompts and themes are correctly installed (:issue:`12241`).
|
||||
- Last line of command output could be hidden when missing newline (:issue:`12246`).
|
||||
|
||||
Other improvements include:
|
||||
|
||||
- The ``abbr``, ``bind``, ``complete``, ``functions``, ``history`` and ``type`` commands now support a ``--color`` option to control syntax highlighting in their output. Valid values are ``auto`` (default), ``always``, or ``never``.
|
||||
- Existing file paths in redirection targets such as ``> file.txt`` are now highlighted using :envvar:`fish_color_valid_path`, indicating that ``file.txt`` will be clobbered (:issue:`12260`).
|
||||
|
||||
fish 4.3.2 (released December 30, 2025)
|
||||
=======================================
|
||||
|
||||
This release fixes the following problems identified in 4.3.0:
|
||||
|
||||
- Pre-built macOS packages failed to start due to a ``Malformed Mach-O file`` error (:issue:`12224`).
|
||||
- ``extra_functionsdir`` (usually ``vendor_functions.d``) and friends were not used (:issue:`12226`).
|
||||
- Sample config file ``~/.config/fish/config.fish/`` and config directories ``~/.config/fish/conf.d/``, ``~/.config/fish/completions/`` and ``~/.config/fish/functions/`` were recreated on every startup instead of only the first time fish runs on a system (:issue:`12230`).
|
||||
- Spurious echo of ``^[[I`` in some scenarios (:issue:`12232`).
|
||||
- Infinite prompt redraw loop on some prompts (:issue:`12233`).
|
||||
- The removal of pre-built HTML docs from tarballs revealed that cross compilation is broken because we use ``${CMAKE_BINARY_DIR}/fish_indent`` for building HTML docs.
|
||||
As a workaround, the new CMake build option ``FISH_INDENT_FOR_BUILDING_DOCS`` can be set to the path of a runnable ``fish_indent`` binary.
|
||||
|
||||
fish 4.3.1 (released December 28, 2025)
|
||||
=======================================
|
||||
|
||||
This release fixes the following problem identified in 4.3.0:
|
||||
|
||||
- Possible crash after expanding an abbreviation (:issue:`12223`).
|
||||
|
||||
fish 4.3.0 (released December 28, 2025)
|
||||
=======================================
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- fish no longer sets :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
|
||||
- fish no longer sets user-facing :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
|
||||
Specifically, the ``fish_color_*``, ``fish_pager_color_*`` and ``fish_key_bindings`` variables are now set in the global scope by default.
|
||||
After upgrading to 4.3.0, fish will (once and never again) migrate these universals to globals set at startup in the
|
||||
``~/.config/fish/conf.d/fish_frozen_theme.fish`` and
|
||||
@@ -30,7 +68,7 @@ Interactive improvements
|
||||
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
|
||||
- Prefix-matching completions are now shown even if they don't match the case typed by the user (:issue:`7944`).
|
||||
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
|
||||
- When using the exe name (``foo.exe``), fish will use to the description and completions for ``foo`` if there are none for ``foo.exe``.
|
||||
- When using the exe name (``foo.exe``), fish will use the description and completions for ``foo`` if there are none for ``foo.exe``.
|
||||
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
|
||||
|
||||
New or improved bindings
|
||||
@@ -45,8 +83,6 @@ Improved terminal support
|
||||
- The working directory is now reported on every fresh prompt (via OSC 7), fixing scenarios where a child process (like ``ssh``) left behind a stale working directory (:issue:`12191`).
|
||||
- OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`).
|
||||
- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS <status-terminal-os>`.
|
||||
- Focus reporting is enabled unconditionally, not just inside tmux.
|
||||
To use it, define functions that handle the ``fish_focus_in`` or ``fish_focus_out`` :ref:`events <event>`.
|
||||
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around some incompatible terminals.
|
||||
|
||||
For distributors and developers
|
||||
|
||||
121
Cargo.lock
generated
121
Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -40,9 +40,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.0"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
@@ -50,9 +50,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.41"
|
||||
version = "1.2.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
|
||||
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -83,9 +83,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
@@ -146,13 +146,13 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.4"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
||||
|
||||
[[package]]
|
||||
name = "fish"
|
||||
version = "4.3.0"
|
||||
version = "4.3.3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
@@ -176,7 +176,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"pcre2",
|
||||
"phf_codegen 0.12.1",
|
||||
"phf_codegen 0.13.1",
|
||||
"portable-atomic",
|
||||
"rand 0.9.2",
|
||||
"rsconf",
|
||||
@@ -209,7 +209,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"nix",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -221,7 +220,6 @@ dependencies = [
|
||||
"fish-wchar",
|
||||
"fish-widecharwidth",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"rsconf",
|
||||
"widestring",
|
||||
]
|
||||
@@ -231,8 +229,7 @@ name = "fish-gettext"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-gettext-maps",
|
||||
"once_cell",
|
||||
"phf 0.12.1",
|
||||
"phf 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -250,8 +247,8 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-build-helper",
|
||||
"fish-gettext-mo-file-parser",
|
||||
"phf 0.12.1",
|
||||
"phf_codegen 0.12.1",
|
||||
"phf 0.13.1",
|
||||
"phf_codegen 0.13.1",
|
||||
"rsconf",
|
||||
]
|
||||
|
||||
@@ -297,15 +294,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
@@ -336,9 +333,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
@@ -349,9 +346,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
@@ -370,15 +367,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
version = "0.2.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
@@ -395,15 +392,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.13.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
||||
checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
@@ -539,11 +536,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_shared 0.12.1",
|
||||
"phf_shared 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -558,12 +555,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
|
||||
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
||||
dependencies = [
|
||||
"phf_generator 0.12.1",
|
||||
"phf_shared 0.12.1",
|
||||
"phf_generator 0.13.1",
|
||||
"phf_shared 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -578,12 +575,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"phf_shared 0.12.1",
|
||||
"phf_shared 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -597,9 +594,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
@@ -612,9 +609,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
@@ -627,18 +624,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -732,9 +729,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rsconf"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd2af859f1af0401e7fc7577739c87b0d239d8a5da400d717183bca92336bcdc"
|
||||
checksum = "06cbd984e96cc891aa018958ac3d09986c0ea7635eedfff670b99a90970f159f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -897,9 +894,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.107"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -946,9 +943,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -1052,18 +1049,18 @@ checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
33
Cargo.toml
33
Cargo.toml
@@ -31,7 +31,7 @@ libc = "0.2.177"
|
||||
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
|
||||
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
||||
# files as of 22 April 2024.
|
||||
lru = "0.13.0"
|
||||
lru = "0.16.2"
|
||||
nix = { version = "0.30.1", default-features = false, features = [
|
||||
"event",
|
||||
"inotify",
|
||||
@@ -44,8 +44,8 @@ once_cell = "1.19.0"
|
||||
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
|
||||
"utf32",
|
||||
] }
|
||||
phf = { version = "0.12", default-features = false }
|
||||
phf_codegen = { version = "0.12" }
|
||||
phf = { version = "0.13", default-features = false }
|
||||
phf_codegen = "0.13"
|
||||
portable-atomic = { version = "1", default-features = false, features = [
|
||||
"fallback",
|
||||
] }
|
||||
@@ -54,7 +54,7 @@ rand = { version = "0.9.2", default-features = false, features = [
|
||||
"small_rng",
|
||||
"thread_rng",
|
||||
] }
|
||||
rsconf = "0.2.2"
|
||||
rsconf = "0.3.0"
|
||||
rust-embed = { version = "8.9.0", features = [
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
@@ -79,7 +79,7 @@ debug = true
|
||||
|
||||
[package]
|
||||
name = "fish"
|
||||
version = "4.3.0"
|
||||
version = "4.3.3"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
default-run = "fish"
|
||||
@@ -182,19 +182,24 @@ rust.non_upper_case_globals = "allow"
|
||||
rust.unknown_lints = "allow"
|
||||
rust.unstable_name_collisions = "allow"
|
||||
rustdoc.private_intra_doc_links = "allow"
|
||||
clippy.len_without_is_empty = "allow" # we're not a library crate
|
||||
clippy.let_and_return = "allow"
|
||||
clippy.manual_range_contains = "allow"
|
||||
clippy.map_unwrap_or = "warn"
|
||||
clippy.needless_lifetimes = "allow"
|
||||
clippy.new_without_default = "allow"
|
||||
clippy.option_map_unit_fn = "allow"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
assigning_clones = "warn"
|
||||
implicit_clone = "warn"
|
||||
cloned_instead_of_copied = "warn"
|
||||
len_without_is_empty = "allow" # we're not a library crate
|
||||
let_and_return = "allow"
|
||||
manual_range_contains = "allow"
|
||||
map_unwrap_or = "warn"
|
||||
needless_lifetimes = "allow"
|
||||
new_without_default = "allow"
|
||||
option_map_unit_fn = "allow"
|
||||
|
||||
# We do not want to use the e?print(ln)?! macros.
|
||||
# These lints flag their use.
|
||||
# In the future, they might change to flag other methods of printing.
|
||||
clippy.print_stdout = "deny"
|
||||
clippy.print_stderr = "deny"
|
||||
print_stdout = "deny"
|
||||
print_stderr = "deny"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -117,7 +117,7 @@ Dependencies
|
||||
|
||||
Compiling fish requires:
|
||||
|
||||
- Rust (version 1.85 or later)
|
||||
- Rust (version 1.85 or later), including cargo
|
||||
- CMake (version 3.15 or later)
|
||||
- a C compiler (for system feature detection and the test helper binary)
|
||||
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
|
||||
@@ -158,6 +158,11 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
|
||||
- Rust_CARGO=path - the path to cargo. If not set, cmake will check $PATH and ~/.cargo/bin
|
||||
- Rust_CARGO_TARGET=target - the target to pass to cargo. Set this for cross-compilation.
|
||||
- WITH_DOCS=ON|OFF - whether to build the documentation. By default, this is ON when Sphinx is installed.
|
||||
- FISH_INDENT_FOR_BUILDING_DOCS - useful for cross-compilation.
|
||||
Set this to the path to the ``fish_indent`` executable to use for building HTML docs.
|
||||
By default, ``${CMAKE_BINARY_DIR}/fish_indent`` will be used.
|
||||
If that's not runnable on the compile host,
|
||||
you can build a native one with ``cargo build --bin fish_indent`` and set this to ``$PWD/target/debug/fish_indent``.
|
||||
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
|
||||
- WITH_GETTEXT=ON|OFF - whether to include translations.
|
||||
- extra_functionsdir, extra_completionsdir and extra_confdir - to compile in an additional directory to be searched for functions, completions and configuration snippets
|
||||
|
||||
3
build.rs
3
build.rs
@@ -40,9 +40,6 @@ fn main() {
|
||||
// the source directory is the current working directory of the build script
|
||||
rsconf::set_env_value("FISH_BUILD_VERSION", version);
|
||||
|
||||
// safety: single-threaded code.
|
||||
unsafe { std::env::set_var("FISH_BUILD_VERSION", version) };
|
||||
|
||||
fish_build_helper::rebuild_if_embedded_path_changed("share");
|
||||
|
||||
let build = cc::Build::new();
|
||||
|
||||
@@ -33,8 +33,13 @@ fi
|
||||
cargo() {
|
||||
subcmd=$1
|
||||
shift
|
||||
# shellcheck disable=2086
|
||||
command cargo "$subcmd" $cargo_args "$@"
|
||||
if [ -n "$FISH_CHECK_RUST_TOOLCHAIN" ]; then
|
||||
# shellcheck disable=2086
|
||||
command cargo "+$FISH_CHECK_RUST_TOOLCHAIN" "$subcmd" $cargo_args "$@"
|
||||
else
|
||||
# shellcheck disable=2086
|
||||
command cargo "$subcmd" $cargo_args "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup () {
|
||||
|
||||
@@ -13,9 +13,9 @@ git_permission_failed=0
|
||||
|
||||
# First see if there is a version file (included in release tarballs),
|
||||
# then try git-describe, then default.
|
||||
if test -f version
|
||||
if test -f "$FISH_BASE_DIR"/version
|
||||
then
|
||||
VN=$(cat version) || VN="$DEF_VER"
|
||||
VN=$(cat "$FISH_BASE_DIR"/version) || VN="$DEF_VER"
|
||||
else
|
||||
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
|
||||
:
|
||||
|
||||
@@ -25,7 +25,7 @@ NOTARIZE=
|
||||
|
||||
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
|
||||
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.12'
|
||||
cmake_args=
|
||||
cmake_args=()
|
||||
|
||||
while getopts "c:sf:i:p:e:nj:" opt; do
|
||||
case $opt in
|
||||
@@ -82,17 +82,16 @@ do_cmake() {
|
||||
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
|
||||
}
|
||||
|
||||
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
|
||||
# Build for x86-64 but do not install; instead we will make a fat binary inside the root.
|
||||
{ cd "$PKGDIR/build_x86_64" \
|
||||
&& do_cmake -DRust_CARGO_TARGET=x86_64-apple-darwin \
|
||||
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
||||
|
||||
# Fatten them up.
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
# Fatten it up.
|
||||
FILE=$PKGDIR/root/usr/local/bin/fish
|
||||
X86_FILE=$PKGDIR/build_x86_64/$(basename "$FILE")
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
chmod 755 "$FILE"
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing executables"
|
||||
@@ -105,9 +104,7 @@ if test -n "$SIGN"; then
|
||||
if [ -n "$ENTITLEMENTS_FILE" ]; then
|
||||
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
|
||||
fi
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
|
||||
done
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "$PKGDIR"/root/usr/local/bin/fish)
|
||||
fi
|
||||
|
||||
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
|
||||
@@ -128,15 +125,13 @@ fi
|
||||
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||
|
||||
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||
# Make the app's /usr/local/bin/fish binary universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||
cd "$PKGDIR/build_arm64"
|
||||
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
|
||||
# macho-universal-create screws up the permissions.
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
FILE=fish.app/Contents/Resources/base/usr/local/bin/fish
|
||||
X86_FILE=$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
# macho-universal-create screws up the permissions.
|
||||
chmod 755 "$FILE"
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing app"
|
||||
|
||||
@@ -86,9 +86,10 @@ sed -i \
|
||||
CommitVersion() {
|
||||
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
|
||||
cargo fetch --offline
|
||||
# debchange is a Debian script to manage the Debian changelog, but
|
||||
# it's too annoying to install everywhere. Just do it by hand.
|
||||
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
|
||||
if [ "$1" = "$version" ]; then
|
||||
# debchange is a Debian script to manage the Debian changelog, but
|
||||
# it's too annoying to install everywhere. Just do it by hand.
|
||||
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
|
||||
fish (${version}-1) stable; urgency=medium
|
||||
|
||||
* Release of new version $version.
|
||||
@@ -98,8 +99,10 @@ fish (${version}-1) stable; urgency=medium
|
||||
-- $committer $(date -R)
|
||||
|
||||
EOF
|
||||
mv contrib/debian/changelog.new contrib/debian/changelog
|
||||
git add CHANGELOG.rst Cargo.toml Cargo.lock contrib/debian/changelog
|
||||
mv contrib/debian/changelog.new contrib/debian/changelog
|
||||
git add contrib/debian/changelog
|
||||
fi
|
||||
git add CHANGELOG.rst Cargo.toml Cargo.lock
|
||||
git commit -m "$2
|
||||
|
||||
Created by ./build_tools/release.sh $version"
|
||||
@@ -296,6 +299,8 @@ milestone_number() {
|
||||
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
|
||||
--method PATCH --raw-field state=closed
|
||||
|
||||
next_minor_version=$(echo "$minor_version" |
|
||||
awk -F. '{ printf "%s.%s", $1, $2+1 }')
|
||||
if [ -z "$(milestone_number "$next_minor_version")" ]; then
|
||||
gh_api_repo milestones --method POST \
|
||||
--raw-field title="fish $next_minor_version"
|
||||
|
||||
@@ -10,7 +10,8 @@ command -v updatecli
|
||||
command -v uv
|
||||
sort --version-sort </dev/null
|
||||
|
||||
uv lock --check
|
||||
# TODO This is copied from .github/actions/install-sphinx/action.yml
|
||||
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
|
||||
|
||||
updatecli "${@:-apply}"
|
||||
|
||||
|
||||
@@ -12,11 +12,21 @@ set(SPHINX_BUILD_DIR "${SPHINX_ROOT_DIR}/build")
|
||||
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
|
||||
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
|
||||
|
||||
# sphinx-docs uses fish_indent for highlighting.
|
||||
# Prepend the output dir of fish_indent to PATH.
|
||||
set(FISH_INDENT_FOR_BUILDING_DOCS "" CACHE FILEPATH "Path to fish_indent executable for building HTML docs")
|
||||
|
||||
if(FISH_INDENT_FOR_BUILDING_DOCS)
|
||||
get_filename_component(FISH_INDENT_DIR "${FISH_INDENT_FOR_BUILDING_DOCS}" DIRECTORY)
|
||||
set(SPHINX_HTML_FISH_INDENT_PATH ${FISH_INDENT_DIR})
|
||||
set(SPHINX_HTML_FISH_INDENT_DEP)
|
||||
else()
|
||||
set(SPHINX_HTML_FISH_INDENT_PATH ${CMAKE_BINARY_DIR})
|
||||
set(SPHINX_HTML_FISH_INDENT_DEP fish_indent)
|
||||
endif()
|
||||
|
||||
add_custom_target(sphinx-docs
|
||||
mkdir -p ${SPHINX_HTML_DIR}/_static/
|
||||
COMMAND env PATH="${CMAKE_BINARY_DIR}:$$PATH"
|
||||
COMMAND env FISH_BUILD_VERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/${FBVF}
|
||||
PATH="${SPHINX_HTML_FISH_INDENT_PATH}:$$PATH"
|
||||
${SPHINX_EXECUTABLE}
|
||||
-j auto
|
||||
-q -b html
|
||||
@@ -24,7 +34,10 @@ add_custom_target(sphinx-docs
|
||||
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
|
||||
"${SPHINX_SRC_DIR}"
|
||||
"${SPHINX_HTML_DIR}"
|
||||
DEPENDS ${SPHINX_SRC_DIR}/fish_indent_lexer.py fish_indent
|
||||
DEPENDS
|
||||
CHECK-FISH-BUILD-VERSION-FILE
|
||||
${SPHINX_SRC_DIR}/fish_indent_lexer.py
|
||||
${SPHINX_HTML_FISH_INDENT_DEP}
|
||||
COMMENT "Building HTML documentation with Sphinx")
|
||||
|
||||
add_custom_target(sphinx-manpages
|
||||
|
||||
@@ -131,6 +131,14 @@ install(DIRECTORY share/functions/
|
||||
DESTINATION ${rel_datadir}/fish/functions
|
||||
FILES_MATCHING PATTERN "*.fish")
|
||||
|
||||
install(DIRECTORY share/prompts/
|
||||
DESTINATION ${rel_datadir}/fish/prompts
|
||||
FILES_MATCHING PATTERN "*.fish")
|
||||
|
||||
install(DIRECTORY share/themes/
|
||||
DESTINATION ${rel_datadir}/fish/themes
|
||||
FILES_MATCHING PATTERN "*.theme")
|
||||
|
||||
# CONDEMNED_PAGE is managed by the conditional above
|
||||
# Building the man pages is optional: if sphinx isn't installed, they're not built
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
|
||||
@@ -149,9 +157,7 @@ install(DIRECTORY share/tools/web_config
|
||||
PATTERN "*.css"
|
||||
PATTERN "*.html"
|
||||
PATTERN "*.py"
|
||||
PATTERN "*.js"
|
||||
PATTERN "*.theme"
|
||||
PATTERN "*.fish")
|
||||
PATTERN "*.js")
|
||||
|
||||
# Building the man pages is optional: if Sphinx isn't installed, they're not built
|
||||
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
fish (4.3.3-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.3.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.3 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Wed, 07 Jan 2026 08:34:20 +0100
|
||||
|
||||
fish (4.3.2-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.2.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.2 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 30 Dec 2025 17:21:04 +0100
|
||||
|
||||
fish (4.3.1-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.1.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.1 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Sun, 28 Dec 2025 16:54:44 +0100
|
||||
|
||||
fish (4.3.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.0.
|
||||
|
||||
@@ -9,7 +9,6 @@ license.workspace = true
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
once_cell.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use libc::STDIN_FILENO;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::env;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// These are in the Unicode private-use range. We really shouldn't use this
|
||||
// range but have little choice in the matter given how our lexer/parser works.
|
||||
@@ -39,7 +39,7 @@ pub fn subslice_position<T: Eq>(a: &[T], b: &[T]) -> Option<usize> {
|
||||
/// session. We err on the side of assuming it's not a console session. This approach isn't
|
||||
/// bullet-proof and that's OK.
|
||||
pub fn is_console_session() -> bool {
|
||||
static IS_CONSOLE_SESSION: OnceCell<bool> = OnceCell::new();
|
||||
static IS_CONSOLE_SESSION: OnceLock<bool> = OnceLock::new();
|
||||
// TODO(terminal-workaround)
|
||||
*IS_CONSOLE_SESSION.get_or_init(|| {
|
||||
nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) })
|
||||
|
||||
@@ -11,7 +11,6 @@ fish-common.workspace = true
|
||||
fish-wchar.workspace = true
|
||||
fish-widecharwidth.workspace = true
|
||||
libc.workspace = true
|
||||
once_cell.workspace = true
|
||||
widestring.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
use fish_wchar::prelude::*;
|
||||
use fish_widecharwidth::{WcLookupTable, WcWidth};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cmp;
|
||||
use std::sync::atomic::{AtomicIsize, Ordering};
|
||||
use std::sync::{
|
||||
LazyLock,
|
||||
atomic::{AtomicIsize, Ordering},
|
||||
};
|
||||
|
||||
/// Width of ambiguous East Asian characters and, as of TR11, all private-use characters.
|
||||
/// 1 is the typical default, but we accept any non-negative override via `$fish_ambiguous_width`.
|
||||
@@ -25,7 +27,7 @@
|
||||
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
|
||||
pub static FISH_EMOJI_WIDTH: AtomicIsize = AtomicIsize::new(1);
|
||||
|
||||
static WC_LOOKUP_TABLE: Lazy<WcLookupTable> = Lazy::new(WcLookupTable::new);
|
||||
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
|
||||
|
||||
/// A safe wrapper around the system `wcwidth()` function
|
||||
#[cfg(not(cygwin))]
|
||||
|
||||
@@ -8,7 +8,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-gettext-maps.workspace = true
|
||||
once_cell.workspace = true
|
||||
phf.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,259 +1,19 @@
|
||||
use fish_gettext_maps::CATALOGS;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashSet, sync::Mutex};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
type Catalog = &'static phf::Map<&'static str, &'static str>;
|
||||
|
||||
pub struct SetLanguageLints<'a> {
|
||||
pub duplicates: Vec<&'a str>,
|
||||
pub non_existing: Vec<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LanguagePrecedenceOrigin {
|
||||
Default,
|
||||
LocaleVariable(LocaleVariable),
|
||||
LanguageEnvVar,
|
||||
StatusLanguage,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LocaleVariable {
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
LANG,
|
||||
#[allow(non_camel_case_types)]
|
||||
LC_MESSAGES,
|
||||
#[allow(non_camel_case_types)]
|
||||
LC_ALL,
|
||||
}
|
||||
|
||||
impl LocaleVariable {
|
||||
fn as_language_precedence_origin(&self) -> LanguagePrecedenceOrigin {
|
||||
LanguagePrecedenceOrigin::LocaleVariable(*self)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::LANG => "LANG",
|
||||
Self::LC_MESSAGES => "LC_MESSAGES",
|
||||
Self::LC_ALL => "LC_ALL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LocaleVariable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
struct InternalLocalizationState {
|
||||
precedence_origin: LanguagePrecedenceOrigin,
|
||||
language_precedence: Vec<(String, Catalog)>,
|
||||
}
|
||||
|
||||
pub struct PublicLocalizationState {
|
||||
pub precedence_origin: LanguagePrecedenceOrigin,
|
||||
pub language_precedence: Vec<String>,
|
||||
}
|
||||
|
||||
/// Stores the current localization status.
|
||||
/// `is_active` indicates whether localization is currently active, and the reason if it is
|
||||
/// not.
|
||||
/// The `origin` indicates where the values in `language_precedence` were taken from.
|
||||
/// `language_precedence` stores the catalogs in the order they should be used.
|
||||
///
|
||||
/// This struct should be updated when the relevant variables change or `status language` is used
|
||||
/// to modify the localization state.
|
||||
static LOCALIZATION_STATE: Lazy<Mutex<InternalLocalizationState>> =
|
||||
Lazy::new(|| Mutex::new(InternalLocalizationState::new()));
|
||||
|
||||
impl InternalLocalizationState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
precedence_origin: LanguagePrecedenceOrigin::Default,
|
||||
language_precedence: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn to_public(&self) -> PublicLocalizationState {
|
||||
PublicLocalizationState {
|
||||
precedence_origin: self.precedence_origin,
|
||||
language_precedence: self
|
||||
.language_precedence
|
||||
.iter()
|
||||
.map(|(lang, _)| lang.to_owned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_from_env(
|
||||
&mut self,
|
||||
message_locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
// Do not override values set via `status language`.
|
||||
if self.precedence_origin == LanguagePrecedenceOrigin::StatusLanguage {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((precedence_origin, locale)) = &message_locale {
|
||||
// Regular locale names start with lowercase letters (`ll_CC`, followed by some suffix).
|
||||
// The C or POSIX locale is special, and often used to disable localization.
|
||||
// Their names are upper-case, but variants with suffixes (`C.UTF-8`) exist.
|
||||
// To ensure that such variants are accounted for, we match on prefixes of the
|
||||
// locale name.
|
||||
// https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02
|
||||
fn is_c_locale(locale: &str) -> bool {
|
||||
locale.starts_with('C') || locale.starts_with("POSIX")
|
||||
}
|
||||
if is_c_locale(locale) {
|
||||
self.precedence_origin =
|
||||
LanguagePrecedenceOrigin::LocaleVariable(*precedence_origin);
|
||||
self.language_precedence.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let (precedence_origin, language_list) = if let Some(list) = language_var {
|
||||
(LanguagePrecedenceOrigin::LanguageEnvVar, list)
|
||||
} else if let Some((precedence_origin, locale)) = message_locale {
|
||||
let mut normalized_name = String::new();
|
||||
// Strip off encoding and modifier. (We always expect UTF-8 and don't support modifiers.)
|
||||
for c in locale.chars() {
|
||||
if c.is_alphabetic() || c == '_' {
|
||||
normalized_name.push(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// At this point, the normalized_name should have the shape `ll` or `ll_CC`.
|
||||
(
|
||||
precedence_origin.as_language_precedence_origin(),
|
||||
vec![normalized_name],
|
||||
)
|
||||
} else {
|
||||
(LanguagePrecedenceOrigin::Default, vec![])
|
||||
};
|
||||
|
||||
let mut seen_languages = HashSet::new();
|
||||
self.language_precedence = language_list
|
||||
.into_iter()
|
||||
.flat_map(|lang| find_existing_catalogs(&lang))
|
||||
.filter(|(lang, _)| seen_languages.insert(lang.to_owned()))
|
||||
.collect();
|
||||
self.precedence_origin = precedence_origin;
|
||||
}
|
||||
|
||||
fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
&mut self,
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
let mut seen = HashSet::new();
|
||||
let mut duplicates = vec![];
|
||||
for lang in langs {
|
||||
let lang = lang.as_ref();
|
||||
if !seen.insert(lang) {
|
||||
duplicates.push(lang)
|
||||
}
|
||||
}
|
||||
let mut existing_langs = vec![];
|
||||
let mut non_existing = vec![];
|
||||
for lang in langs {
|
||||
let lang = lang.as_ref();
|
||||
if let Some(catalog) = CATALOGS.get(lang) {
|
||||
existing_langs.push((lang.to_owned(), *catalog));
|
||||
} else {
|
||||
non_existing.push(lang);
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
let unique_langs = existing_langs
|
||||
.into_iter()
|
||||
.filter(|(lang, _)| seen.insert(lang.to_owned()))
|
||||
.collect();
|
||||
self.language_precedence = unique_langs;
|
||||
self.precedence_origin = LanguagePrecedenceOrigin::StatusLanguage;
|
||||
|
||||
SetLanguageLints {
|
||||
duplicates,
|
||||
non_existing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to find catalogs for `language`.
|
||||
/// `language` must be an ISO 639 language code, optionally followed by an underscore and an ISO
|
||||
/// 3166 country/territory code.
|
||||
/// Uses the catalog with the exact same name as `language` if it exists.
|
||||
/// If a country code is present (`ll_CC`), only the catalog named `ll` will be considered as a fallback.
|
||||
/// If no country code is present (`ll`), all catalogs whose names start with `ll_` will be used in
|
||||
/// arbitrary order.
|
||||
fn find_existing_catalogs(language: &str) -> Vec<(String, Catalog)> {
|
||||
// Try the exact name first.
|
||||
// If there already is a corresponding catalog return the language.
|
||||
if let Some(catalog) = CATALOGS.get(language) {
|
||||
return vec![(language.to_owned(), catalog)];
|
||||
}
|
||||
let language_without_country_code = language.split_once('_').map_or(language, |(ll, _cc)| ll);
|
||||
if language == language_without_country_code {
|
||||
// We have `ll` format. In this case, try to find any catalog whose name starts with `ll_`.
|
||||
// Note that it is important to include the underscore in the pattern, otherwise `ll` might
|
||||
// fall back to `llx_CC`, where `llx` is a 3-letter language identifier.
|
||||
let ll_prefix = format!("{language}_");
|
||||
let mut lang_catalogs = vec![];
|
||||
for (&lang_name, &catalog) in CATALOGS.entries() {
|
||||
if lang_name.starts_with(&ll_prefix) {
|
||||
lang_catalogs.push((lang_name.to_owned(), catalog));
|
||||
}
|
||||
}
|
||||
lang_catalogs
|
||||
} else {
|
||||
// If `language` contained a country code, we only try to fall back to a catalog
|
||||
// without a country code.
|
||||
if let Some(catalog) = CATALOGS.get(language_without_country_code) {
|
||||
vec![(language_without_country_code.to_owned(), catalog)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_from_env(
|
||||
locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.update_from_env(locale, language_var);
|
||||
}
|
||||
|
||||
pub fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.update_from_status_language_builtin(langs)
|
||||
}
|
||||
|
||||
pub fn unset_from_status_language_builtin(
|
||||
locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.precedence_origin = LanguagePrecedenceOrigin::Default;
|
||||
localization_state.update_from_env(locale, language_var);
|
||||
}
|
||||
|
||||
pub fn status_language() -> PublicLocalizationState {
|
||||
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.to_public()
|
||||
}
|
||||
static LANGUAGE_PRECEDENCE: LazyLock<Mutex<Vec<(&'static str, Catalog)>>> =
|
||||
LazyLock::new(|| Mutex::new(vec![]));
|
||||
|
||||
pub fn gettext(message_str: &'static str) -> Option<&'static str> {
|
||||
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
|
||||
|
||||
// Use the localization from the highest-precedence language that has one available.
|
||||
for (_, catalog) in localization_state.language_precedence.iter() {
|
||||
for (_, catalog) in language_precedence.iter() {
|
||||
if let Some(localized_str) = catalog.get(message_str) {
|
||||
return Some(localized_str);
|
||||
}
|
||||
@@ -261,8 +21,40 @@ pub fn gettext(message_str: &'static str) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn list_available_languages() -> Vec<&'static str> {
|
||||
let mut langs: Vec<_> = CATALOGS.entries().map(|(&lang, _)| lang).collect();
|
||||
langs.sort();
|
||||
langs
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GettextLocalizationLanguage {
|
||||
language: &'static str,
|
||||
}
|
||||
|
||||
static AVAILABLE_LANGUAGES: LazyLock<HashMap<&'static str, GettextLocalizationLanguage>> =
|
||||
LazyLock::new(|| {
|
||||
HashMap::from_iter(
|
||||
CATALOGS
|
||||
.entries()
|
||||
.map(|(&language, _)| (language, GettextLocalizationLanguage { language })),
|
||||
)
|
||||
});
|
||||
|
||||
pub fn get_available_languages() -> &'static HashMap<&'static str, GettextLocalizationLanguage> {
|
||||
&AVAILABLE_LANGUAGES
|
||||
}
|
||||
|
||||
pub fn set_language_precedence(new_precedence: &[GettextLocalizationLanguage]) {
|
||||
let catalogs = new_precedence
|
||||
.iter()
|
||||
.map(|lang| {
|
||||
(
|
||||
lang.language,
|
||||
*CATALOGS
|
||||
.get(lang.language)
|
||||
.expect("Only languages for which catalogs exist may be passed to gettext."),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
*LANGUAGE_PRECEDENCE.lock().unwrap() = catalogs;
|
||||
}
|
||||
|
||||
pub fn get_language_precedence() -> Vec<&'static str> {
|
||||
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
|
||||
language_precedence.iter().map(|&(lang, _)| lang).collect()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ Synopsis
|
||||
[--set-cursor[=MARKER]] ([-f | --function FUNCTION] | EXPANSION)
|
||||
abbr --erase [ [-c | --command COMMAND]... ] NAME ...
|
||||
abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD
|
||||
abbr --show
|
||||
abbr [--show] [--color WHEN]
|
||||
abbr --list
|
||||
abbr --query NAME ...
|
||||
|
||||
@@ -75,7 +75,6 @@ With **--set-cursor=MARKER**, the cursor is moved to the first occurrence of **M
|
||||
|
||||
With **-f FUNCTION** or **--function FUNCTION**, **FUNCTION** is treated as the name of a fish function instead of a literal replacement. When the abbreviation matches, the function will be called with the matching token as an argument. If the function's exit status is 0 (success), the token will be replaced by the function's output; otherwise the token will be left unchanged. No **EXPANSION** may be given separately.
|
||||
|
||||
|
||||
Examples
|
||||
########
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Synopsis
|
||||
.. synopsis::
|
||||
|
||||
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] KEYS COMMAND ...
|
||||
bind [(-M | --mode) MODE] [--preset] [--user] [KEYS]
|
||||
bind [-a | --all] [--preset] [--user]
|
||||
bind [(-M | --mode) MODE] [--preset] [--user] [--color WHEN] [KEYS]
|
||||
bind [-a | --all] [--preset] [--user] [--color WHEN]
|
||||
bind (-f | --function-names)
|
||||
bind (-K | --key-names)
|
||||
bind (-L | --list-modes)
|
||||
@@ -104,6 +104,10 @@ The following options are available:
|
||||
**-s** or **--silent**
|
||||
Silences some of the error messages, including for unknown key names and unbound sequences.
|
||||
|
||||
**--color** *WHEN*
|
||||
Controls when to use syntax highlighting colors when listing bindings.
|
||||
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Synopsis
|
||||
|
||||
.. synopsis::
|
||||
|
||||
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS]
|
||||
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS] [--color WHEN]
|
||||
complete (-C | --do-complete) [--escape] STRING
|
||||
|
||||
Description
|
||||
@@ -74,6 +74,10 @@ The following options are available:
|
||||
**--escape**
|
||||
When used with ``-C``, escape special characters in completions.
|
||||
|
||||
**--color** *WHEN*
|
||||
Controls when to use syntax highlighting colors when printing completions.
|
||||
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Synopsis
|
||||
|
||||
.. synopsis::
|
||||
|
||||
functions [-a | --all] [-n | --names]
|
||||
functions [-D | --details] [-v] FUNCTION
|
||||
functions [-a | --all] [-n | --names] [--color WHEN]
|
||||
functions [-D | --details] [-v] [--color WHEN] FUNCTION
|
||||
functions -c OLDNAME NEWNAME
|
||||
functions -d DESCRIPTION FUNCTION
|
||||
functions [-e | -q] FUNCTION ...
|
||||
@@ -60,6 +60,10 @@ The following options are available:
|
||||
**-t** or **--handlers-type** *TYPE*
|
||||
Show all event handlers matching the given *TYPE*.
|
||||
|
||||
**--color** *WHEN*
|
||||
Controls when to use syntax highlighting colors when printing function definitions.
|
||||
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@ These flags can appear before or immediately after one of the sub-commands liste
|
||||
**-R** or **--reverse**
|
||||
Causes the history search results to be ordered oldest to newest. Which is the order used by most shells. The default is newest to oldest.
|
||||
|
||||
**--color** *WHEN*
|
||||
Controls when to use syntax highlighting colors for the history entries.
|
||||
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help for this command.
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ The following options are available:
|
||||
**-q** or **--query**
|
||||
Suppresses all output; this is useful when testing the exit status. For compatibility with old fish versions this is also **--quiet**.
|
||||
|
||||
**--color** *WHEN*
|
||||
Controls when to use syntax highlighting colors when printing function definitions.
|
||||
*WHEN* can be ``auto`` (the default, colorize if the output :doc:`is a terminal <isatty>`), ``always``, or ``never``.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
|
||||
@@ -118,13 +118,12 @@ author = "fish-shell developers"
|
||||
issue_url = "https://github.com/fish-shell/fish-shell/issues"
|
||||
|
||||
# Parsing FISH-BUILD-VERSION-FILE is possible but hard to ensure that it is in the right place
|
||||
# fish_indent is guaranteed to be on PATH for the Pygments highlighter anyway
|
||||
if "FISH_BUILD_VERSION_FILE" in os.environ:
|
||||
# From Cmake
|
||||
f = open(os.environ["FISH_BUILD_VERSION_FILE"], "r")
|
||||
ret = f.readline().strip()
|
||||
elif "FISH_BUILD_VERSION" in os.environ:
|
||||
ret = os.environ["FISH_BUILD_VERSION"]
|
||||
else:
|
||||
# From Cargo, or no build system.
|
||||
ret = subprocess.check_output(
|
||||
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
|
||||
).decode("utf-8")
|
||||
|
||||
@@ -133,7 +133,7 @@ Variable Meaning
|
||||
.. envvar:: fish_color_end process separators like ``;`` and ``&``
|
||||
.. envvar:: fish_color_error syntax errors
|
||||
.. envvar:: fish_color_param ordinary command parameters
|
||||
.. envvar:: fish_color_valid_path parameters that are filenames (if the file exists)
|
||||
.. envvar:: fish_color_valid_path parameters and redirection targets that are filenames (if the file exists)
|
||||
.. envvar:: fish_color_option options starting with "-", up to the first "--" parameter
|
||||
.. envvar:: fish_color_comment comments like '# important'
|
||||
.. envvar:: fish_color_selection selected text in vi visual mode
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,12 @@ complete -c abbr -f -n $__fish_abbr_not_add_cond -l rename -d 'Rename an abbrevi
|
||||
complete -c abbr -f -n $__fish_abbr_not_add_cond -s e -l erase -d 'Erase abbreviation' -xa '(abbr --list)'
|
||||
complete -c abbr -f -n $__fish_abbr_not_add_cond -s s -l show -d 'Print all abbreviations'
|
||||
complete -c abbr -f -n $__fish_abbr_not_add_cond -s l -l list -d 'Print all abbreviation names'
|
||||
complete -c abbr -f -n $__fish_abbr_not_add_cond -l color -d 'When to colorize output' -xa 'always never auto'
|
||||
complete -c abbr -f -n $__fish_abbr_not_add_cond -s h -l help -d Help
|
||||
|
||||
complete -c abbr -f -n $__fish_abbr_add_cond -s p -l position -a 'command anywhere' -d 'Expand only as a command, or anywhere' -x
|
||||
complete -c abbr -f -n $__fish_abbr_add_cond -s f -l function -d 'Treat expansion argument as a fish function' -xa '(functions)'
|
||||
complete -c abbr -f -n $__fish_abbr_add_cond -s r -l regex -d 'Match a regular expression' -x
|
||||
complete -c abbr -f -n $__fish_abbr_add_cond -l set-cursor -d 'Position the cursor at % post-expansion'
|
||||
|
||||
complete -c abbr -f -n '__fish_seen_subcommand_from -s --show' -l color -d 'When to colorize output' -xa 'always never auto'
|
||||
|
||||
@@ -25,6 +25,7 @@ complete -c bind -s L -l list-modes -d 'Display a list of defined bind modes'
|
||||
complete -c bind -s s -l silent -d 'Operate silently'
|
||||
complete -c bind -l preset -d 'Operate on preset bindings'
|
||||
complete -c bind -l user -d 'Operate on user bindings'
|
||||
complete -c bind -l color -d 'When to colorize output' -xa 'always never auto'
|
||||
|
||||
complete -c bind -n '__fish_bind_has_keys (commandline -pcx)' -a '(bind --function-names)' -d 'Function name' -x
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ complete -c complete -l escape -d "Make -C escape special characters"
|
||||
complete -c complete -s n -l condition -d "Completion only used if command has zero exit status" -x
|
||||
complete -c complete -s w -l wraps -d "Inherit completions from specified command" -xa '(__fish_complete_command)'
|
||||
complete -c complete -s k -l keep-order -d "Keep order of arguments instead of sorting alphabetically"
|
||||
complete -c complete -l color -d "When to colorize output" -xa "always never auto"
|
||||
|
||||
# Deprecated options
|
||||
|
||||
|
||||
@@ -15,10 +15,72 @@ function __fish_fastboot_list_partition_or_file
|
||||
end
|
||||
|
||||
function __fish_fastboot_list_partition
|
||||
set -l partitions boot bootloader cache cust dtbo metadata misc modem odm odm_dlkm oem product pvmfw radio recovery system system_ext userdata vbmeta vendor vendor_dlkm vmbeta_system
|
||||
for i in $partitions
|
||||
echo $i
|
||||
end
|
||||
printf %s "\
|
||||
abl
|
||||
aop
|
||||
aop_config
|
||||
apdp
|
||||
bluetooth
|
||||
boot
|
||||
countrycode
|
||||
cpucp
|
||||
cpucp_dtb
|
||||
crclist
|
||||
dcp
|
||||
ddr
|
||||
devcfg
|
||||
dsp
|
||||
dtbo
|
||||
featenabler
|
||||
hyp
|
||||
hyp_ac_config
|
||||
idmanager
|
||||
imagefv
|
||||
init_boot
|
||||
keymaster
|
||||
logfs
|
||||
metadata
|
||||
misc
|
||||
modem
|
||||
modemfirmware
|
||||
multiimgoem
|
||||
multiimgqti
|
||||
pdp
|
||||
pdp_cdb
|
||||
pvmfw
|
||||
qtvm_dtbo
|
||||
qupfw
|
||||
recovery
|
||||
rescue
|
||||
secretkeeper
|
||||
shrm
|
||||
soccp
|
||||
soccp_dcd
|
||||
soccp_debug
|
||||
sparsecrclist
|
||||
spuservice
|
||||
storsec
|
||||
super
|
||||
tme_config
|
||||
tme_fw
|
||||
tme_seq_patch
|
||||
toolsfv
|
||||
tz
|
||||
tz_ac_config
|
||||
tz_qti_config
|
||||
uefi
|
||||
uefisecapp
|
||||
userdata
|
||||
vbmeta
|
||||
vbmeta_system
|
||||
vendor_boot
|
||||
vm-bootsys
|
||||
xbl
|
||||
xbl_ac_config
|
||||
xbl_config
|
||||
xbl_ramdump
|
||||
xbl_sc_test_mode
|
||||
"
|
||||
end
|
||||
|
||||
complete -c fastboot -s h -l help -d 'Show this message'
|
||||
|
||||
@@ -19,3 +19,4 @@ complete -c functions -s D -l details -d "Display information about the function
|
||||
complete -c functions -s v -l verbose -d "Print more output"
|
||||
complete -c functions -s H -l handlers -d "Show event handlers"
|
||||
complete -c functions -s t -l handlers-type -d "Show event handlers matching the given type" -x -a "signal variable exit job-id generic"
|
||||
complete -c functions -l color -d 'When to colorize output' -x -a 'always never auto'
|
||||
|
||||
@@ -1070,8 +1070,17 @@ complete -f -c git -n '__fish_git_using_command fetch' -l refetch -d 'Re-fetch w
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command fetch' -l negotiation-tip -d 'Only report commits reachable from these tips' -x
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l negotiate-only -d "Don't fetch, only show commits in common with the server"
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
|
||||
|
||||
# TODO other options
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -s 4 -l ipv4 -d 'Use IPv4 addresses only'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -s 6 -l ipv6 -d 'Use IPv6 addresses only'
|
||||
complete -x -c git -n '__fish_git_using_command fetch' -l depth -d 'Limit fetching to specified depth'
|
||||
complete -x -c git -n '__fish_git_using_command fetch' -l deepen -d 'Deepen history of shallow repository'
|
||||
complete -x -c git -n '__fish_git_using_command fetch' -l shallow-since -d 'Deepen history after specified date'
|
||||
complete -x -c git -n '__fish_git_using_command fetch' -l shallow-exclude -d 'Deepen history excluding branch'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l unshallow -d 'Convert shallow repository to complete one'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l update-shallow -d 'Accept refs that update .git/shallow'
|
||||
complete -x -c git -n '__fish_git_using_command fetch' -l refmap -d 'Specify refspec to map the refs to remote-tracking branches'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l write-fetch-head -d 'Write FETCH_HEAD file (default)'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l no-write-fetch-head -d 'Do not write FETCH_HEAD file'
|
||||
|
||||
#### filter-branch
|
||||
complete -f -c git -n __fish_git_needs_command -a filter-branch -d 'Rewrite branches'
|
||||
@@ -1151,7 +1160,18 @@ complete -f -c git -n '__fish_git_using_command show-branch' -l no-color -d "Tur
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l merge-base -d "Determine merge bases for the given commits"
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l independent -d "Show which refs can't be reached from any other"
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l topics -d "Show only commits that are not on the first given branch"
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l all -d 'Show all refs under refs/heads and refs/remotes'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -s r -l remotes -d 'Show all refs under refs/remotes'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l current -d 'Include the current branch to the list of revs'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l topo-order -d 'Do not show commits in reverse chronological order'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l date-order -d 'Show commits in chronological order'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l sparse -d 'Show merges only reachable from one tip'
|
||||
complete -x -c git -n '__fish_git_using_command show-branch' -l more -d 'Go N more commits beyond common ancestor'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l list -d 'Show branches and their commits'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l sha1-name -d 'Name commits with unique prefix of SHA-1'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l no-name -d 'Do not show naming strings for each commit'
|
||||
complete -x -c git -n '__fish_git_using_command show-branch' -l color -a 'always never auto' -d 'Color the status sign'
|
||||
complete -f -c git -n '__fish_git_using_command show-branch' -l no-color -d 'Turn off colored output'
|
||||
|
||||
### add
|
||||
complete -c git -n __fish_git_needs_command -a add -d 'Add file contents to the staging area'
|
||||
@@ -1170,10 +1190,10 @@ complete -c git -n '__fish_git_using_command add' -l ignore-errors -d 'Ignore er
|
||||
complete -c git -n '__fish_git_using_command add' -l ignore-missing -d 'Check if any of the given files would be ignored'
|
||||
# Renames also show up as untracked + deleted, and to get git to show it as a rename _both_ need to be added.
|
||||
# However, we can't do that as it is two tokens, so we don't need renamed here.
|
||||
complete -f -c git -n '__fish_git_using_command add; and test "$(__fish_git config --get bash.showUntrackedFiles)" != 0' -a '(__fish_git_files modified untracked deleted unmerged modified-staged-deleted)'
|
||||
complete -f -c git -n '__fish_git_using_command add; and test "$(__fish_git config --type bool --get status.showUntrackedFiles)" != false' -a '(__fish_git_files modified untracked deleted unmerged modified-staged-deleted)'
|
||||
# If we have so many files that you disable untrackedfiles, let's add file completions,
|
||||
# to avoid slurping megabytes of git output.
|
||||
complete -F -c git -n '__fish_git_using_command add; and test "$(__fish_git config --get bash.showUntrackedFiles)" = 0' -a '(__fish_git_files modified deleted unmerged modified-staged-deleted)'
|
||||
complete -F -c git -n '__fish_git_using_command add; and test "$(__fish_git config --type bool --get status.showUntrackedFiles)" = false' -a '(__fish_git_files modified deleted unmerged modified-staged-deleted)'
|
||||
|
||||
### am
|
||||
complete -c git -n __fish_git_needs_command -a am -d 'Apply patches from a mailbox'
|
||||
@@ -1201,6 +1221,7 @@ complete -f -c git -n '__fish_git_using_command am' -l ignore-date -d 'Treat aut
|
||||
complete -f -c git -n '__fish_git_using_command am' -l skip -d 'Skip current patch'
|
||||
complete -x -c git -n '__fish_git_using_command am' -s S -l gpg-sign -a '(type -q gpg && __fish_complete_gpg_key_id gpg)' -d 'Sign commits with gpg'
|
||||
complete -f -c git -n '__fish_git_using_command am' -l no-gpg-sign -d 'Do not sign commits'
|
||||
complete -f -c git -n '__fish_git_using_command am' -s n -l no-verify -d 'Do not run pre-applypatch and applypatch-msg hooks'
|
||||
complete -f -c git -n '__fish_git_using_command am' -s r -l resolved -l continue -d 'Mark patch failures as resolved'
|
||||
complete -x -c git -n '__fish_git_using_command am' -l resolvemsg -d 'Message to print after patch failure'
|
||||
complete -f -c git -n '__fish_git_using_command am' -l abort -d 'Abort patch operation and restore branch'
|
||||
@@ -1229,7 +1250,15 @@ complete -f -c git -n '__fish_git_using_command checkout' -l no-recurse-submodul
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l progress -d 'Report progress even if not connected to a terminal'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l no-progress -d "Don't report progress"
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -s f -l force -d 'Switch even if working tree differs or unmerged files exist'
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -s q -l quiet -d 'Suppress feedback messages'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l detach -d 'Detach HEAD at named commit'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l guess -d 'Guess remote tracking branch (default)'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l no-guess -d 'Do not guess remote tracking branch'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l overlay -d 'Never remove files from working tree in checkout'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l no-overlay -d 'Remove files from working tree not in tree-ish'
|
||||
complete -F -c git -n '__fish_git_using_command checkout' -l pathspec-from-file -d 'Read pathspec from file'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l pathspec-file-nul -d 'NUL separator for pathspec-from-file'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -l ignore-skip-worktree-bits -d 'Check out all files including sparse entries'
|
||||
|
||||
### sparse-checkout
|
||||
# `init` subcommand is deprecated
|
||||
@@ -1281,7 +1310,12 @@ complete -f -c git -n __fish_git_needs_command -a archive -d 'Create an archive
|
||||
complete -f -c git -n '__fish_git_using_command archive' -s l -l list -d "Show all available formats"
|
||||
complete -f -c git -n '__fish_git_using_command archive' -s v -l verbose -d "Be verbose"
|
||||
complete -f -c git -n '__fish_git_using_command archive' -l worktree-attributes -d "Look for attributes in .gitattributes files in the working tree as well"
|
||||
# TODO options
|
||||
complete -x -c git -n '__fish_git_using_command archive' -l format -a 'tar zip tar.gz tgz' -d 'Format of the resulting archive'
|
||||
complete -F -c git -n '__fish_git_using_command archive' -s o -l output -d 'Write the archive to file instead of stdout'
|
||||
complete -x -c git -n '__fish_git_using_command archive' -l prefix -d 'Prepend prefix to each pathname in the archive'
|
||||
complete -F -c git -n '__fish_git_using_command archive' -l add-file -d 'Add non-tracked file to the archive'
|
||||
complete -x -c git -n '__fish_git_using_command archive' -l remote -d 'Retrieve a tar archive from a remote repository'
|
||||
complete -x -c git -n '__fish_git_using_command archive' -l exec -d 'Path to git-upload-archive on the remote side'
|
||||
|
||||
### bisect
|
||||
complete -f -c git -n __fish_git_needs_command -a bisect -d 'Use binary search to find what introduced a bug'
|
||||
@@ -1442,7 +1476,21 @@ complete -f -c git -n '__fish_git_using_command commit' -l no-signoff -d 'Do not
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s C -l reuse-message -d 'Reuse log message and authorship of an existing commit'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s c -l reedit-message -d 'Like --reuse-message, but allow editing commit message'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s e -l edit -d 'Further edit the message taken from -m, -C, or -F'
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l no-edit -d 'Do not edit the commit message'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s q -l quiet -d 'Suppress commit summary message'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l dry-run -d 'Show what would be committed without committing'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l short -d 'Show short format output for dry-run'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l porcelain -d 'Show porcelain format output for dry-run'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l long -d 'Show long format output for dry-run (default)'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s z -l null -d 'Terminate entries with NUL in dry-run'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l status -d 'Include git status output in commit message template'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l no-status -d 'Do not include git status output in commit message template'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s i -l include -d 'Stage contents of given paths before committing'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -s o -l only -d 'Commit only from paths specified on command line'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l trailer -d 'Add trailer to commit message'
|
||||
complete -F -c git -n '__fish_git_using_command commit' -s t -l template -d 'Use file as commit message template'
|
||||
complete -F -c git -n '__fish_git_using_command commit' -l pathspec-from-file -d 'Read pathspec from file'
|
||||
complete -f -c git -n '__fish_git_using_command commit' -l pathspec-file-nul -d 'NUL separator for pathspec-from-file'
|
||||
|
||||
### count-objects
|
||||
complete -f -c git -n __fish_git_needs_command -a count-objects -d 'Count number of objects and disk consumption'
|
||||
@@ -1550,7 +1598,13 @@ complete -f -c git -n '__fish_git_using_command difftool' -l tool-help -d 'Print
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l trust-exit-code -d 'Exit when an invoked diff tool returns a non-zero exit code'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -s x -l extcmd -d 'Specify a custom command for viewing diffs'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l no-gui -d 'Overrides --gui setting'
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l tool-help -d 'Print a list of diff tools that may be used'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -s d -l dir-diff -d 'Copy modified files to a temporary location and perform dir diff'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l symlinks -d 'Use symlinks in dir-diff mode'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l no-symlinks -d 'Do not use symlinks in dir-diff mode'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l no-trust-exit-code -d 'Do not exit when diff tool returns non-zero'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -s y -l no-prompt -d 'Do not prompt before launching diff tool'
|
||||
complete -f -c git -n '__fish_git_using_command difftool' -l prompt -d 'Prompt before each invocation of diff tool'
|
||||
|
||||
### gc
|
||||
complete -f -c git -n __fish_git_needs_command -a gc -d 'Collect garbage (unreachable commits etc)'
|
||||
@@ -1603,13 +1657,29 @@ complete -f -c git -n '__fish_git_using_command grep' -l not -d 'Combine pattern
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l all-match -d 'Only match files that can match all the pattern expressions when giving multiple'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -s q -l quiet -d 'Just exit with status 0 when there is a match and with non-zero status when there isn\'t'
|
||||
complete -c git -n '__fish_git_using_command grep' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_refs)'
|
||||
# TODO options, including max-depth, h, open-files-in-pager, contexts, threads, file
|
||||
complete -x -c git -n '__fish_git_using_command grep' -l max-depth -d 'Maximum depth of directory traversal'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -s h -d 'Do not output the filename for each match'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -s H -d 'Show filename for each match (default)'
|
||||
complete -x -c git -n '__fish_git_using_command grep' -s O -l open-files-in-pager -d 'Open matching files in pager'
|
||||
complete -x -c git -n '__fish_git_using_command grep' -s C -l context -d 'Show context lines before and after matches'
|
||||
complete -x -c git -n '__fish_git_using_command grep' -s A -l after-context -d 'Show context lines after matches'
|
||||
complete -x -c git -n '__fish_git_using_command grep' -s B -l before-context -d 'Show context lines before matches'
|
||||
complete -x -c git -n '__fish_git_using_command grep' -l threads -d 'Number of grep worker threads to use'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l break -d 'Print empty line between matches from different files'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l heading -d 'Show filename above matches in that file'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l untracked -d 'Search in untracked files'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l no-index -d 'Search files in current directory that is not managed by Git'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l recurse-submodules -d 'Recursively search in each submodule'
|
||||
|
||||
### init
|
||||
complete -f -c git -n __fish_git_needs_command -a init -d 'Create an empty git repository'
|
||||
complete -f -c git -n '__fish_git_using_command init' -s q -l quiet -d 'Only print error and warning messages'
|
||||
complete -f -c git -n '__fish_git_using_command init' -l bare -d 'Create a bare repository'
|
||||
# TODO options
|
||||
complete -F -c git -n '__fish_git_using_command init' -l template -d 'Directory from which templates will be used'
|
||||
complete -F -c git -n '__fish_git_using_command init' -l separate-git-dir -d 'Create git dir at specified path'
|
||||
complete -x -c git -n '__fish_git_using_command init' -l object-format -a 'sha1 sha256' -d 'Specify hash algorithm for objects'
|
||||
complete -x -c git -n '__fish_git_using_command init' -s b -l initial-branch -d 'Use specified name for initial branch'
|
||||
complete -x -c git -n '__fish_git_using_command init' -l shared -a 'false true umask group all world everybody' -d 'Specify that the repository is shared'
|
||||
|
||||
### shortlog
|
||||
complete -c git -n __fish_git_needs_command -a shortlog -d 'Show commit shortlog'
|
||||
@@ -1951,7 +2021,7 @@ complete -f -c git -n __fish_git_needs_command -a prune -d 'Prune unreachable ob
|
||||
complete -f -c git -n '__fish_git_using_command prune' -s n -l dry-run -d 'Just report what it would remove'
|
||||
complete -f -c git -n '__fish_git_using_command prune' -s v -l verbose -d 'Report all removed objects'
|
||||
complete -f -c git -n '__fish_git_using_command prune' -l progress -d 'Show progress'
|
||||
# TODO options
|
||||
complete -x -c git -n '__fish_git_using_command prune' -l expire -d 'Only expire loose objects older than date'
|
||||
|
||||
### pull
|
||||
complete -f -c git -n __fish_git_needs_command -a pull -d 'Fetch from and merge with another repo or branch'
|
||||
@@ -1994,7 +2064,18 @@ complete -f -c git -n '__fish_git_using_command pull' -s r -l rebase -d 'Rebase
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l no-rebase -d 'Do not rebase the current branch on top of the upstream branch'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l autostash -d 'Before starting rebase, stash local changes, and apply stash when done'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l no-autostash -d 'Do not stash local changes before starting rebase'
|
||||
# TODO other options
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l verify -d 'Allow the pre-merge and commit-msg hooks to run (default)'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l no-verify -d 'Do not run pre-merge and commit-msg hooks'
|
||||
complete -x -c git -n '__fish_git_using_command pull' -l upload-pack -d 'Path to git-upload-pack on remote'
|
||||
complete -x -c git -n '__fish_git_using_command pull' -l depth -d 'Limit fetching to specified number of commits'
|
||||
complete -x -c git -n '__fish_git_using_command pull' -l deepen -d 'Deepen history of shallow repository by specified commits'
|
||||
complete -x -c git -n '__fish_git_using_command pull' -l shallow-since -d 'Deepen history after specified date'
|
||||
complete -x -c git -n '__fish_git_using_command pull' -l shallow-exclude -d 'Deepen history excluding commits reachable from branch'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l unshallow -d 'Convert shallow repository to complete one'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -l update-shallow -d 'Accept refs that update .git/shallow'
|
||||
complete -x -c git -n '__fish_git_using_command pull' -s j -l jobs -d 'Number of parallel children for fetching'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -s 4 -l ipv4 -d 'Use IPv4 addresses only'
|
||||
complete -f -c git -n '__fish_git_using_command pull' -s 6 -l ipv6 -d 'Use IPv6 addresses only'
|
||||
|
||||
### range-diff
|
||||
complete -f -c git -n __fish_git_needs_command -a range-diff -d 'Compare two commit ranges'
|
||||
@@ -2031,7 +2112,21 @@ complete -f -c git -n '__fish_git_using_command push' -s u -l set-upstream -d 'A
|
||||
complete -f -c git -n '__fish_git_using_command push' -s q -l quiet -d 'Be quiet'
|
||||
complete -f -c git -n '__fish_git_using_command push' -s v -l verbose -d 'Be verbose'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l progress -d 'Force progress status'
|
||||
# TODO --recurse-submodules=check|on-demand
|
||||
complete -f -c git -n '__fish_git_using_command push' -l verify -d 'Allow the pre-push hook to run (default)'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l no-verify -d 'Do not run the pre-push hook'
|
||||
complete -x -c git -n '__fish_git_using_command push' -l recurse-submodules -a 'check on-demand only no' -d 'Control recursive pushing of submodules'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l signed -d 'GPG-sign the push'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l no-signed -d 'Do not GPG-sign the push'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l atomic -d 'Request atomic transaction on remote side'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l no-atomic -d 'Do not request atomic transaction'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l thin -d 'Spend extra cycles to minimize number of objects'
|
||||
complete -f -c git -n '__fish_git_using_command push' -l no-thin -d 'Do not use thin pack'
|
||||
complete -f -c git -n '__fish_git_using_command push' -s 4 -l ipv4 -d 'Use IPv4 addresses only'
|
||||
complete -f -c git -n '__fish_git_using_command push' -s 6 -l ipv6 -d 'Use IPv6 addresses only'
|
||||
complete -x -c git -n '__fish_git_using_command push' -s o -l push-option -d 'Transmit string to server'
|
||||
complete -x -c git -n '__fish_git_using_command push' -l repo -d 'Override configured repository'
|
||||
complete -x -c git -n '__fish_git_using_command push' -l receive-pack -d 'Path to git-receive-pack on remote'
|
||||
complete -x -c git -n '__fish_git_using_command push' -l exec -d 'Same as --receive-pack'
|
||||
|
||||
### rebase
|
||||
complete -f -c git -n __fish_git_needs_command -a rebase -d 'Reapply commit sequence on a new base'
|
||||
@@ -2087,7 +2182,12 @@ __fish_git_add_revision_completion -n '__fish_git_using_command reset'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -n 'not contains -- -- (commandline -xpc)' -a '(__fish_git_files all-staged modified)'
|
||||
complete -F -c git -n '__fish_git_using_command reset' -n 'contains -- -- (commandline -xpc)'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -n 'not contains -- -- (commandline -xpc)' -a '(__fish_git_reflog)' -d Reflog
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command reset' -s q -l quiet -d 'Be quiet, only report errors'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -s p -l patch -d 'Interactively select hunks to reset'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -l merge -d 'Reset index and update files in working tree that differ'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -l keep -d 'Reset index but keep changes in working tree'
|
||||
complete -F -c git -n '__fish_git_using_command reset' -l pathspec-from-file -d 'Read pathspec from file'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -l pathspec-file-nul -d 'NUL separator for pathspec-from-file'
|
||||
|
||||
### restore and switch
|
||||
# restore options
|
||||
@@ -2155,7 +2255,17 @@ complete -f -c git -n '__fish_git_using_command revert' -s n -l no-commit -d 'Ap
|
||||
complete -f -c git -n '__fish_git_using_command revert' -s s -l signoff -d 'Add a Signed-off-by trailer at the end of the commit message'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l rerere-autoupdate -d 'Allow the rerere mechanism to update the index automatically'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l no-rerere-autoupdate -d 'Prevent the rerere mechanism from updating the index with auto-conflict resolution'
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l abort -d 'Cancel the operation and return to pre-sequence state'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l continue -d 'Continue the operation after resolving conflicts'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l quit -d 'Clear the sequencer state after a failed revert'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l skip -d 'Skip the current commit and continue with the rest'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -s e -l edit -d 'Edit the commit message before committing'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l no-edit -d 'Do not edit the commit message'
|
||||
complete -x -c git -n '__fish_git_using_command revert' -s m -l mainline -d 'Select parent number for reverting merge commits'
|
||||
complete -x -c git -n '__fish_git_using_command revert' -s S -l gpg-sign -a '(type -q gpg && __fish_complete_gpg_key_id gpg)' -d 'GPG-sign commits'
|
||||
complete -f -c git -n '__fish_git_using_command revert' -l no-gpg-sign -d 'Do not GPG-sign commits'
|
||||
complete -x -c git -n '__fish_git_using_command revert' -s s -l strategy -d 'Use the given merge strategy'
|
||||
complete -x -c git -n '__fish_git_using_command revert' -s X -l strategy-option -d 'Pass option to the merge strategy'
|
||||
|
||||
### rm
|
||||
complete -c git -n __fish_git_needs_command -a rm -d 'Remove files from the working tree and/or staging area'
|
||||
@@ -2167,7 +2277,8 @@ complete -c git -n '__fish_git_using_command rm' -s q -l quiet -d 'Be quiet'
|
||||
complete -c git -n '__fish_git_using_command rm' -s f -l force -d 'Override the up-to-date check'
|
||||
complete -c git -n '__fish_git_using_command rm' -s n -l dry-run -d 'Dry run'
|
||||
complete -c git -n '__fish_git_using_command rm' -l sparse -d 'Allow updating index entries outside of the sparse-checkout cone'
|
||||
# TODO options
|
||||
complete -F -c git -n '__fish_git_using_command rm' -l pathspec-from-file -d 'Read pathspec from file'
|
||||
complete -f -c git -n '__fish_git_using_command rm' -l pathspec-file-nul -d 'NUL separator for pathspec-from-file'
|
||||
|
||||
### status
|
||||
complete -f -c git -n __fish_git_needs_command -a status -d 'Show the working tree status'
|
||||
@@ -2181,7 +2292,13 @@ complete -f -c git -n '__fish_git_using_command status' -s v -l verbose -d 'Also
|
||||
complete -f -c git -n '__fish_git_using_command status' -l no-ahead-behind -d 'Do not display detailed ahead/behind upstream-branch counts'
|
||||
complete -f -c git -n '__fish_git_using_command status' -l renames -d 'Turn on rename detection regardless of user configuration'
|
||||
complete -f -c git -n '__fish_git_using_command status' -l no-renames -d 'Turn off rename detection regardless of user configuration'
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command status' -l ahead-behind -d 'Display detailed ahead/behind upstream-branch counts'
|
||||
complete -f -c git -n '__fish_git_using_command status' -l long -d 'Give the output in the long-format (default)'
|
||||
complete -f -c git -n '__fish_git_using_command status' -l show-stash -d 'Show the number of entries currently stashed'
|
||||
complete -x -c git -n '__fish_git_using_command status' -l column -d 'Display untracked files in columns'
|
||||
complete -f -c git -n '__fish_git_using_command status' -l no-column -d 'Do not display untracked files in columns'
|
||||
complete -F -c git -n '__fish_git_using_command status' -l pathspec-from-file -d 'Read pathspec from file'
|
||||
complete -f -c git -n '__fish_git_using_command status' -l pathspec-file-nul -d 'NUL separator for pathspec-from-file'
|
||||
|
||||
### stripspace
|
||||
complete -f -c git -n __fish_git_needs_command -a stripspace -d 'Remove unnecessary whitespace'
|
||||
@@ -2199,7 +2316,19 @@ complete -f -c git -n '__fish_git_using_command tag' -s f -l force -d 'Force ove
|
||||
complete -f -c git -n '__fish_git_using_command tag' -s l -l list -d 'List tags'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l contains -xka '(__fish_git_commits)' -d 'List tags that contain a commit'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -n '__fish_git_contains_opt -s d delete -s v verify -s f force' -ka '(__fish_git_tags)' -d Tag
|
||||
# TODO options
|
||||
complete -x -c git -n '__fish_git_using_command tag' -s m -l message -d 'Use the given tag message'
|
||||
complete -F -c git -n '__fish_git_using_command tag' -s F -l file -d 'Read tag message from file'
|
||||
complete -x -c git -n '__fish_git_using_command tag' -s u -l local-user -d 'Use this key to sign tag'
|
||||
complete -x -c git -n '__fish_git_using_command tag' -l cleanup -a 'strip whitespace verbatim' -d 'How to clean up the tag message'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l create-reflog -d 'Create reflog for the tag'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l no-create-reflog -d 'Do not create reflog for the tag'
|
||||
complete -x -c git -n '__fish_git_using_command tag' -l color -a 'always never auto' -d 'Respect any colors in format'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l column -d 'Display tag listing in columns'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l no-column -d 'Do not display tag listing in columns'
|
||||
complete -x -c git -n '__fish_git_using_command tag' -l sort -d 'Sort tags based on the given key'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l merged -d 'List tags whose commits are reachable from specified commit'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -l no-merged -d 'List tags whose commits are not reachable from specified commit'
|
||||
complete -x -c git -n '__fish_git_using_command tag' -l points-at -d 'List tags of the given object'
|
||||
|
||||
### update-index
|
||||
complete -c git -n __fish_git_needs_command -a update-index -d 'Register file contents in the working tree to the index'
|
||||
@@ -2303,7 +2432,37 @@ complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_usin
|
||||
|
||||
### config
|
||||
complete -f -c git -n __fish_git_needs_command -a config -d 'Set and read git configuration variables'
|
||||
# TODO options
|
||||
complete -f -c git -n '__fish_git_using_command config' -l local -d 'Write to repository .git/config'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l global -d 'Write to global ~/.gitconfig'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l system -d 'Write to system-wide /etc/gitconfig'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l worktree -d 'Write to .git/config.worktree'
|
||||
complete -F -c git -n '__fish_git_using_command config' -s f -l file -d 'Use given config file'
|
||||
complete -F -c git -n '__fish_git_using_command config' -l blob -d 'Read config from given blob object'
|
||||
complete -f -c git -n '__fish_git_using_command config' -s l -l list -d 'List all variables set in config'
|
||||
complete -f -c git -n '__fish_git_using_command config' -s e -l edit -d 'Open config file in editor'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l get -d 'Get value for given key'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l get-all -d 'Get all values for a multi-valued key'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l get-regexp -d 'Get values for keys matching regex'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l get-urlmatch -d 'Get value for URL-specific key'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l add -d 'Add new line without altering existing values'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l unset -d 'Remove line matching key'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l unset-all -d 'Remove all lines matching key'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l replace-all -d 'Replace all matching lines'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l rename-section -d 'Rename given section'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l remove-section -d 'Remove given section'
|
||||
complete -x -c git -n '__fish_git_using_command config' -l type -a 'bool int bool-or-int path expiry-date color' -d 'Ensure value is of given type'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l bool -d 'Value is true or false'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l int -d 'Value is a decimal number'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l bool-or-int -d 'Value is bool or int'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l path -d 'Value is a path'
|
||||
complete -f -c git -n '__fish_git_using_command config' -s z -l null -d 'Terminate values with NUL byte'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l name-only -d 'Output only names of config variables'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l show-origin -d 'Show origin of config'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l show-scope -d 'Show scope of config'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l includes -d 'Respect include directives'
|
||||
complete -f -c git -n '__fish_git_using_command config' -l no-includes -d 'Do not respect include directives'
|
||||
complete -x -c git -n '__fish_git_using_command config' -l default -d 'Use default value when variable is missing'
|
||||
complete -f -c git -n '__fish_git_using_command config' -a '(__fish_git_config_keys)' -d 'Config key'
|
||||
|
||||
### format-patch
|
||||
complete -f -c git -n __fish_git_needs_command -a format-patch -d 'Generate patch series to send upstream'
|
||||
@@ -2386,7 +2545,7 @@ complete -f -c git -n '__fish_git_using_command clean' -s q -l quiet -d 'Be quie
|
||||
complete -f -c git -n '__fish_git_using_command clean' -s d -d 'Remove untracked directories in addition to untracked files'
|
||||
complete -f -c git -n '__fish_git_using_command clean' -s x -d 'Remove ignored files, as well'
|
||||
complete -f -c git -n '__fish_git_using_command clean' -s X -d 'Remove only ignored files'
|
||||
# TODO -e option
|
||||
complete -x -c git -n '__fish_git_using_command clean' -s e -l exclude -d 'Add pattern to exclude from removal'
|
||||
|
||||
### git blame
|
||||
complete -f -c git -n __fish_git_needs_command -a blame -d 'Show what last modified each line of a file'
|
||||
@@ -2644,6 +2803,150 @@ function __fish_git_sort_keys
|
||||
end
|
||||
complete -f -c git -n "__fish_seen_subcommand_from $sortcommands" -l sort -d 'Sort results by' -a "(__fish_git_sort_keys)"
|
||||
|
||||
### Plumbing commands
|
||||
|
||||
### cat-file
|
||||
complete -f -c git -n __fish_git_needs_command -a cat-file -d 'Provide content or type info for repository objects'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -s t -d 'Show object type'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -s s -d 'Show object size'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -s e -d 'Exit with zero if object exists and is valid'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -s p -d 'Pretty-print object content'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -l textconv -d 'Show content as transformed by a textconv filter'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -l filters -d 'Show content as transformed by filters'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -l batch -d 'Read objects from stdin and print info'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -l batch-check -d 'Read objects from stdin and print type/size info'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -l batch-all-objects -d 'Print info for all objects'
|
||||
complete -f -c git -n '__fish_git_using_command cat-file' -l follow-symlinks -d 'Follow symlinks when using --batch'
|
||||
|
||||
### ls-remote
|
||||
complete -f -c git -n __fish_git_needs_command -a ls-remote -d 'List references in a remote repository'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -s h -l heads -d 'Limit to refs/heads'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -s t -l tags -d 'Limit to refs/tags'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -l refs -d 'Do not show peeled tags or pseudorefs'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -s q -l quiet -d 'Do not print remote URL'
|
||||
complete -x -c git -n '__fish_git_using_command ls-remote' -l upload-pack -d 'Path to git-upload-pack on remote'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -l exit-code -d 'Exit with status 2 if no matching refs are found'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -l get-url -d 'Print URL of remote'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -l symref -d 'Show underlying ref for symbolic refs'
|
||||
complete -x -c git -n '__fish_git_using_command ls-remote' -l sort -d 'Sort based on the given key'
|
||||
complete -f -c git -n '__fish_git_using_command ls-remote' -a '(__fish_git_remotes)' -d Remote
|
||||
|
||||
### ls-tree
|
||||
complete -f -c git -n __fish_git_needs_command -a ls-tree -d 'List contents of a tree object'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -s d -d 'Only show trees'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -s r -d 'Recurse into subtrees'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -s t -d 'Show tree entries even when recursing'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -l name-only -d 'Show only names'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -l name-status -d 'Show only names (same as name-only)'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -l full-name -d 'Show full path names'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -l full-tree -d 'Do not limit listing to current directory'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -s z -d 'NUL line termination on output'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -l long -d 'Show object size'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -l abbrev -d 'Show abbreviated object names'
|
||||
complete -f -c git -n '__fish_git_using_command ls-tree' -a '(__fish_git_refs)' -d Ref
|
||||
|
||||
### show-ref
|
||||
complete -f -c git -n __fish_git_needs_command -a show-ref -d 'List references in a local repository'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -l head -d 'Show HEAD reference'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -l heads -d 'Limit to refs/heads'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -l tags -d 'Limit to refs/tags'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -s d -l dereference -d 'Dereference tags'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -s s -l hash -d 'Only show SHA-1 hash'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -l verify -d 'Enable stricter reference checking'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -l abbrev -d 'Show abbreviated object names'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -s q -l quiet -d 'Do not print any results'
|
||||
complete -f -c git -n '__fish_git_using_command show-ref' -l exclude-existing -d 'Check refs from stdin that do not exist'
|
||||
|
||||
### symbolic-ref
|
||||
complete -f -c git -n __fish_git_needs_command -a symbolic-ref -d 'Read, modify, delete symbolic refs'
|
||||
complete -f -c git -n '__fish_git_using_command symbolic-ref' -s d -l delete -d 'Delete the symbolic ref'
|
||||
complete -f -c git -n '__fish_git_using_command symbolic-ref' -s q -l quiet -d 'Do not issue error if ref is not a symbolic ref'
|
||||
complete -f -c git -n '__fish_git_using_command symbolic-ref' -l short -d 'Shorten the ref name'
|
||||
complete -x -c git -n '__fish_git_using_command symbolic-ref' -s m -d 'Update reflog with given reason'
|
||||
|
||||
### check-ignore
|
||||
complete -f -c git -n __fish_git_needs_command -a check-ignore -d 'Debug gitignore / exclude files'
|
||||
complete -f -c git -n '__fish_git_using_command check-ignore' -s q -l quiet -d 'Do not output anything, just set exit status'
|
||||
complete -f -c git -n '__fish_git_using_command check-ignore' -s v -l verbose -d 'Show matching pattern for each file'
|
||||
complete -f -c git -n '__fish_git_using_command check-ignore' -l stdin -d 'Read pathnames from stdin'
|
||||
complete -f -c git -n '__fish_git_using_command check-ignore' -s z -d 'NUL line termination'
|
||||
complete -f -c git -n '__fish_git_using_command check-ignore' -s n -l non-matching -d 'Show given paths which do not match any pattern'
|
||||
complete -f -c git -n '__fish_git_using_command check-ignore' -l no-index -d 'Do not look in the index when undertaking checks'
|
||||
|
||||
### checkout-index
|
||||
complete -c git -n __fish_git_needs_command -a checkout-index -d 'Copy files from index to working tree'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -s a -l all -d 'Check out all files in the index'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -s f -l force -d 'Force overwrite existing files'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -s n -l no-create -d 'Do not create files that do not exist'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -s q -l quiet -d 'Be quiet if files exist or are not in the index'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -s u -l index -d 'Update stat information in the index'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -s z -d 'NUL line termination'
|
||||
complete -x -c git -n '__fish_git_using_command checkout-index' -l prefix -d 'Prefix to use when creating files'
|
||||
complete -x -c git -n '__fish_git_using_command checkout-index' -l stage -a 'all 1 2 3' -d 'Which stage to check out'
|
||||
complete -f -c git -n '__fish_git_using_command checkout-index' -l temp -d 'Write files to temporary files'
|
||||
|
||||
### commit-tree
|
||||
complete -f -c git -n __fish_git_needs_command -a commit-tree -d 'Create a new commit object'
|
||||
complete -x -c git -n '__fish_git_using_command commit-tree' -s p -d 'Parent commit object'
|
||||
complete -x -c git -n '__fish_git_using_command commit-tree' -s m -d 'Commit message'
|
||||
complete -F -c git -n '__fish_git_using_command commit-tree' -s F -d 'Read commit message from file'
|
||||
complete -x -c git -n '__fish_git_using_command commit-tree' -s S -l gpg-sign -d 'GPG-sign commit'
|
||||
complete -f -c git -n '__fish_git_using_command commit-tree' -l no-gpg-sign -d 'Do not GPG-sign commit'
|
||||
|
||||
### diff-index
|
||||
complete -f -c git -n __fish_git_needs_command -a diff-index -d 'Compare tree to working tree or index'
|
||||
complete -f -c git -n '__fish_git_using_command diff-index' -l cached -d 'Compare tree to index'
|
||||
complete -f -c git -n '__fish_git_using_command diff-index' -s m -d 'Ignore changes in submodules'
|
||||
complete -f -c git -n '__fish_git_using_command diff-index' -l raw -d 'Generate raw diff output'
|
||||
complete -f -c git -n '__fish_git_using_command diff-index' -s p -l patch -d 'Generate patch'
|
||||
complete -f -c git -n '__fish_git_using_command diff-index' -s q -l quiet -d 'Disable diff output, only set exit status'
|
||||
complete -f -c git -n '__fish_git_using_command diff-index' -a '(__fish_git_refs)' -d Ref
|
||||
|
||||
### hash-object
|
||||
complete -f -c git -n __fish_git_needs_command -a hash-object -d 'Compute object ID and optionally create an object'
|
||||
complete -x -c git -n '__fish_git_using_command hash-object' -s t -a 'blob tree commit tag' -d 'Specify object type'
|
||||
complete -f -c git -n '__fish_git_using_command hash-object' -s w -d 'Actually write object into object database'
|
||||
complete -f -c git -n '__fish_git_using_command hash-object' -l stdin -d 'Read object from stdin'
|
||||
complete -f -c git -n '__fish_git_using_command hash-object' -l stdin-paths -d 'Read file paths from stdin'
|
||||
complete -F -c git -n '__fish_git_using_command hash-object' -l path -d 'Hash object as if it were at the given path'
|
||||
complete -f -c git -n '__fish_git_using_command hash-object' -l no-filters -d 'Hash contents as-is, without any input filters'
|
||||
complete -f -c git -n '__fish_git_using_command hash-object' -l literally -d 'Allow hashing any garbage'
|
||||
|
||||
### read-tree
|
||||
complete -f -c git -n __fish_git_needs_command -a read-tree -d 'Read tree info into the index'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -s m -d 'Perform a merge'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -l reset -d 'Same as -m, but discard unmerged entries'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -s u -d 'Update working tree with merge result'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -s i -d 'Update only the index, leave working tree alone'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -s n -l dry-run -d 'Do not update index or working tree'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -s v -d 'Show progress'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -l trivial -d 'Restrict three-way merge to trivial cases'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -l aggressive -d 'Try harder to resolve trivial cases'
|
||||
complete -x -c git -n '__fish_git_using_command read-tree' -l prefix -d 'Read tree into subdirectory'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -l index-output -d 'Write index to specified file'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -l empty -d 'Instead of reading tree object, empty the index'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -l no-sparse-checkout -d 'Disable sparse checkout support'
|
||||
complete -f -c git -n '__fish_git_using_command read-tree' -a '(__fish_git_refs)' -d Ref
|
||||
|
||||
### update-ref
|
||||
complete -f -c git -n __fish_git_needs_command -a update-ref -d 'Update object name stored in a ref safely'
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -s d -d 'Delete the reference'
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -l no-deref -d 'Do not dereference symbolic refs'
|
||||
complete -x -c git -n '__fish_git_using_command update-ref' -s m -d 'Update reflog with given reason'
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -l create-reflog -d 'Create a reflog'
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -l stdin -d 'Read instructions from stdin'
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -s z -d 'NUL-terminated format for stdin'
|
||||
|
||||
### verify-pack
|
||||
complete -f -c git -n __fish_git_needs_command -a verify-pack -d 'Validate packed Git archive files'
|
||||
complete -f -c git -n '__fish_git_using_command verify-pack' -s v -l verbose -d 'Show objects contained in pack'
|
||||
complete -f -c git -n '__fish_git_using_command verify-pack' -s s -l stat-only -d 'Only show histogram of delta chain length'
|
||||
|
||||
### write-tree
|
||||
complete -f -c git -n __fish_git_needs_command -a write-tree -d 'Create a tree object from the current index'
|
||||
complete -f -c git -n '__fish_git_using_command write-tree' -l missing-ok -d 'Allow missing objects'
|
||||
complete -x -c git -n '__fish_git_using_command write-tree' -l prefix -d 'Write a tree object for a subdirectory'
|
||||
|
||||
## Custom commands (git-* commands installed in the PATH)
|
||||
complete -c git -n __fish_git_needs_command -a '(__fish_git_custom_commands)' -d 'Custom command'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
complete -c help -n __fish_is_first_arg -x -a '(
|
||||
{
|
||||
__fish_data_with_file help_sections (command -v cat) |
|
||||
status get-file help_sections |
|
||||
string replace -r "^index(#|\$)" introduction\$1
|
||||
printf cmds/%s\n ! . : \[ \{
|
||||
} |
|
||||
|
||||
@@ -22,6 +22,8 @@ complete -c history -n '__fish_seen_subcommand_from search; or not __fish_seen_s
|
||||
-s z -l null -d "Terminate entries with NUL character"
|
||||
complete -c history -n '__fish_seen_subcommand_from search; or not __fish_seen_subcommand_from $__fish_history_all_commands' \
|
||||
-s R -l reverse -d "Output the oldest results first" -x
|
||||
complete -c history -n '__fish_seen_subcommand_from search; or not __fish_seen_subcommand_from $__fish_history_all_commands' \
|
||||
-l color -d "When to colorize output" -xa "always never auto"
|
||||
|
||||
# We don't include a completion for the "save" subcommand because it should not be used
|
||||
# interactively.
|
||||
|
||||
@@ -21,8 +21,10 @@ function __fish_print_make_targets --argument-names directory file
|
||||
is_continuation = $0 ~ "^([^#]*[^#" bs_regex "])?(" bs_regex bs_regex ")*" bs_regex "$";
|
||||
}' 2>/dev/null
|
||||
else
|
||||
# BSD make
|
||||
make $makeflags -d g1 -rn >/dev/null 2>| awk -F, '/^#\*\*\* Input graph:/,/^$/ {if ($1 !~ "^#... ") {gsub(/# /,"",$1); print $1}}' 2>/dev/null
|
||||
# FreeBSD/NetBSD
|
||||
make $makeflags -V .ALLTARGETS 2>/dev/null | string split ' '
|
||||
# OpenBSD
|
||||
or make $makeflags -d g1 -rn 2>/dev/null | awk '/^[^#. \t].+:/ { gsub(/:.*/, "", $1); print $1 }'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,78 @@
|
||||
complete -c signify -n __fish_seen_subcommand_from -s C -d 'Verify a signed checksum list'
|
||||
complete -c signify -n __fish_seen_subcommand_from -s G -d 'Generate a new key pair'
|
||||
complete -c signify -n __fish_seen_subcommand_from -s S -d 'Sign specified message'
|
||||
complete -c signify -n __fish_seen_subcommand_from -s V -d 'Verify a signed message and sig'
|
||||
# Tab completion for openbsd-signify
|
||||
|
||||
set -l subcommands -C -G -S -V
|
||||
set -l subcommands_desc (echo -s \
|
||||
-C\t"Verify a signed checksum list."\n \
|
||||
-G\t"Generate a new key pair."\n \
|
||||
-S\t"Sign the specified message file."\n \
|
||||
-V\t"Verify the message and signature match."\n \
|
||||
| string escape)
|
||||
|
||||
complete -c signify -f
|
||||
|
||||
complete -c signify \
|
||||
-n "not __fish_seen_subcommand_from $subcommands" \
|
||||
-a "$subcommands_desc"
|
||||
|
||||
complete -c signify -F \
|
||||
-n '__fish_seen_subcommand_from -C'
|
||||
|
||||
complete -c signify -s c -f -r \
|
||||
-d 'Specify the comment to be added during key generation' \
|
||||
-n '__fish_seen_subcommand_from -G'
|
||||
|
||||
complete -c signify -s e -f \
|
||||
-d 'Use embedded signatures' \
|
||||
-n '__fish_seen_subcommand_from -S -V'
|
||||
|
||||
complete -c signify -s m -F -r \
|
||||
-d 'Message file to sign or verify' \
|
||||
-n '__fish_seen_subcommand_from -S -V'
|
||||
|
||||
# The -n option has two context-dependent meanings
|
||||
complete -c signify -s n -f \
|
||||
-d 'When generating a key pair, do not ask for a passphrase' \
|
||||
-n '__fish_seen_argument -s G'
|
||||
complete -c signify -s n -f \
|
||||
-d 'When signing with -z, store a zero timestamp in the gzip header' \
|
||||
-n '__fish_seen_subcommand_from -S && __fish_seen_argument -s z'
|
||||
|
||||
# This is deliberately split up to only add a description to the flag and not all its argument completions
|
||||
complete -c signify -s p -f -k -r \
|
||||
-a '(__fish_complete_suffix .pub)' \
|
||||
-n '__fish_seen_subcommand_from -C -G -V'
|
||||
complete -c signify -s p -f -k -r \
|
||||
-d 'Public key produced by -G, and used by -V to check a signature' \
|
||||
-n '__fish_seen_subcommand_from -C -G -V'
|
||||
|
||||
complete -c signify -s q -f \
|
||||
-d 'Quiet mode. Suppress informational output' \
|
||||
-n '__fish_seen_subcommand_from -C -V'
|
||||
|
||||
complete -c signify -s s -f -k -r \
|
||||
-a '(__fish_complete_suffix .sec)' \
|
||||
-n '__fish_seen_subcommand_from -G -S'
|
||||
complete -c signify -s s -f -k -r \
|
||||
-d 'Secret (private) key produced by -G, and used by -S to sign a message' \
|
||||
-n '__fish_seen_subcommand_from -G -S'
|
||||
|
||||
complete -c signify -s t -f -r \
|
||||
-a '(
|
||||
set -l files /etc/signify/*
|
||||
string replace -rf -- \'\\.pub$\' "" $files | string replace -r \'.*-\' ""
|
||||
)' \
|
||||
-n '__fish_seen_subcommand_from -C -V'
|
||||
complete -c signify -s t -f -r \
|
||||
-d 'When inferring a key to verify with, only use keys with this keytype suffix' \
|
||||
-n '__fish_seen_subcommand_from -C -V'
|
||||
|
||||
complete -c signify -s x -f -k -r \
|
||||
-a '(__fish_complete_suffix .sig)' \
|
||||
-n '__fish_seen_subcommand_from -C -S -V'
|
||||
complete -c signify -s x -f -k -r \
|
||||
-d 'The signature file to create or verify. The default is message.sig' \
|
||||
-n '__fish_seen_subcommand_from -C -S -V'
|
||||
|
||||
complete -c signify -s z -f \
|
||||
-d 'Sign and verify gzip (1) archives, embed signature in the header' \
|
||||
-n '__fish_seen_subcommand_from -S -V'
|
||||
|
||||
@@ -6,6 +6,7 @@ complete -c type -s p -l path -d "Print path to command, or nothing if name is n
|
||||
complete -c type -s P -l force-path -d "Print path to command"
|
||||
complete -c type -s q -l query -l quiet -d "Check if something exists without output"
|
||||
complete -c type -s s -l short -d "Don't print function definition"
|
||||
complete -c type -l color -d "When to colorize output" -xa "always never auto"
|
||||
|
||||
complete -c type -a "(builtin -n)" -d Builtin
|
||||
complete -c type -a "(functions -n)" -d Function
|
||||
|
||||
@@ -32,7 +32,7 @@ end
|
||||
set -l __extra_completionsdir
|
||||
set -l __extra_functionsdir
|
||||
set -l __extra_confdir
|
||||
__fish_data_with_file __fish_build_paths.fish source
|
||||
status get-file __fish_build_paths.fish | source
|
||||
|
||||
# Compute the directories for vendor configuration. We want to include
|
||||
# all of XDG_DATA_DIRS, as well as the __extra_* dirs defined above.
|
||||
@@ -205,7 +205,7 @@ if command -q kill
|
||||
end
|
||||
|
||||
if status is-interactive
|
||||
__fish_theme_migrate
|
||||
__fish_migrate
|
||||
end
|
||||
fish_config theme choose default --no-override
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ function __fish_complete_directories -d "Complete directory prefixes" --argument
|
||||
# If we have a --name=val option, we need to remove it,
|
||||
# or the complete -C below would keep it, and then whatever complete
|
||||
# called us would add it again (assuming it's in the current token)
|
||||
set comp (commandline -ct | string replace -r -- '^-[^=]*=' '' $comp)
|
||||
set comp (commandline -ct | string replace -r -- '^-[^=]*=' '')
|
||||
end
|
||||
|
||||
# HACK: We call into the file completions by using an empty command
|
||||
|
||||
@@ -6,15 +6,6 @@
|
||||
#
|
||||
function __fish_config_interactive -d "Initializations that should be performed when entering interactive mode"
|
||||
functions -e __fish_config_interactive
|
||||
# Create empty configuration directores if they do not already exist
|
||||
test -e $__fish_config_dir/completions/ -a -e $__fish_config_dir/conf.d/ -a -e $__fish_config_dir/functions/ ||
|
||||
mkdir -p $__fish_config_dir/{completions, conf.d, functions}
|
||||
|
||||
# Create config.fish with some boilerplate if it does not exist
|
||||
test -e $__fish_config_dir/config.fish || echo "\
|
||||
if status is-interactive
|
||||
# Commands to run in interactive sessions can go here
|
||||
end" >$__fish_config_dir/config.fish
|
||||
|
||||
set -g __fish_active_key_bindings
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# localization: skip(private)
|
||||
function __fish_data_with_file
|
||||
function __fish_config_with_file
|
||||
set -l path $argv[1]
|
||||
set -l cmd $argv[2..]
|
||||
if string match -rq -- ^/ $path
|
||||
@@ -1,9 +1,27 @@
|
||||
# localization: skip(private)
|
||||
function __fish_theme_migrate
|
||||
functions -e __fish_theme_migrate
|
||||
function __fish_migrate
|
||||
functions -e __fish_migrate
|
||||
|
||||
set -l migration_version 4300
|
||||
|
||||
# Maybe migrate.
|
||||
if not set -q __fish_initialized || test $__fish_initialized -ge 4300
|
||||
if set -q __fish_initialized && test $__fish_initialized -ge $migration_version
|
||||
return
|
||||
end
|
||||
|
||||
# Create empty configuration directories if they do not already exist
|
||||
test -e $__fish_config_dir/completions/ -a -e $__fish_config_dir/conf.d/ -a -e $__fish_config_dir/functions/ ||
|
||||
mkdir -p $__fish_config_dir/{completions, conf.d, functions}
|
||||
|
||||
# Create config.fish with some boilerplate if it does not exist
|
||||
test -e $__fish_config_dir/config.fish || echo "\
|
||||
if status is-interactive
|
||||
# Commands to run in interactive sessions can go here
|
||||
end" >$__fish_config_dir/config.fish
|
||||
|
||||
set -l mark_migration_done set -U __fish_initialized $migration_version
|
||||
if not set -q __fish_initialized
|
||||
$mark_migration_done
|
||||
return
|
||||
end
|
||||
|
||||
@@ -22,7 +40,7 @@ function __fish_theme_migrate
|
||||
for varname in $theme_uvars
|
||||
set -a theme_data "$(string escape -- $varname $$varname | string join " ")"
|
||||
end
|
||||
__fish_theme_freeze __fish_theme_migrate $theme_data
|
||||
__fish_theme_freeze __fish_migrate $theme_data
|
||||
set msg_suffix " by default."\n" Migrated them to global variables set in $(set_color --underline)$(
|
||||
__fish_unexpand_tilde $__fish_config_dir/conf.d/fish_frozen_theme.fish
|
||||
)$(set_color normal)"
|
||||
@@ -35,6 +53,7 @@ function __fish_theme_migrate
|
||||
set -l relative_filename conf.d/fish_frozen_key_bindings.fish
|
||||
set -l filename $__fish_config_dir/$relative_filename
|
||||
__fish_backup_config_files $relative_filename
|
||||
mkdir -p -- (path dirname -- $filename)
|
||||
echo >$filename "\
|
||||
# This file was created by fish when upgrading to version 4.3, to migrate
|
||||
# the 'fish_key_bindings' variable from its old default scope (universal)
|
||||
@@ -62,14 +81,14 @@ set --erase --universal fish_key_bindings"
|
||||
(set_color normal))
|
||||
source $__fish_config_dir/$relative_filename
|
||||
end
|
||||
set -U __fish_initialized 4300
|
||||
$mark_migration_done
|
||||
if $removing_uvars
|
||||
echo -s (set_color --bold) 'fish:' (set_color normal) " upgraded to version 4.3:"
|
||||
string join \n -- $msg
|
||||
echo 'See also the release notes (type `help relnotes`).'
|
||||
set -Ue fish_key_bindings $theme_uvars
|
||||
set -l sh (__fish_posix_shell)
|
||||
eval "$sh -c 'sleep 7 # Please read above notice about universal variables' &"
|
||||
eval "$sh -c 'sleep 7 # Please read above notice about universal variables' </dev/null &>/dev/null &"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,15 +6,16 @@ function __fish_theme_cat -a theme_name
|
||||
echo >&2 Searched (__fish_theme_dir) "and `status list-files themes`"
|
||||
return 1
|
||||
end
|
||||
set -l theme_data (__fish_data_with_file $theme_path cat)
|
||||
set -l theme_data (if string match -q '/*' -- $theme_path; cat $theme_path; else status get-file $theme_path; end)
|
||||
set -l allowed_lines \
|
||||
'\s*' \
|
||||
'\s*#.*' \
|
||||
'\[(dark|light|unknown)\]' \
|
||||
(__fish_theme_variable_filter)
|
||||
set allowed_lines "^($(string join -- '|' $allowed_lines))\$"
|
||||
for line in $theme_data
|
||||
string match -rq -- $allowed_lines $line
|
||||
printf '%s\n' $theme_data | string match -rvq -- $allowed_lines
|
||||
and for line in $theme_data
|
||||
string match -rq -- $allowed_lines $theme_data
|
||||
or printf >&2 "error: unsupported line in theme '%s': %s\n" $theme_name $line
|
||||
end
|
||||
string join \n $theme_data
|
||||
|
||||
@@ -6,11 +6,12 @@ function __fish_theme_freeze
|
||||
__fish_backup_config_files $relative_path
|
||||
|
||||
set -l help_section interactive#syntax-highlighting
|
||||
__fish_data_with_file help_sections $(command -v grep) -Fxq $help_section
|
||||
status get-file help_sections | string match -q $help_section
|
||||
or echo "fish: internal error: missing help section '$help_section'"
|
||||
|
||||
mkdir -p -- (path dirname -- $__fish_config_dir/conf.d)
|
||||
printf >$__fish_config_dir/$relative_path %s\n \
|
||||
$(test $data_source = __fish_theme_migrate &&
|
||||
$(test $data_source = __fish_migrate &&
|
||||
echo "\
|
||||
# This file was created by fish when upgrading to version 4.3, to migrate
|
||||
# theme variables from universal to global scope.") \
|
||||
@@ -21,7 +22,7 @@ function __fish_theme_freeze
|
||||
# or
|
||||
# man fish-interactive | less +/^SYNTAX.HIGHLIGHTING
|
||||
# for appropriate commands to add to ~/.config/fish/config.fish instead." \
|
||||
$(test $data_source = __fish_theme_migrate &&
|
||||
$(test $data_source = __fish_migrate &&
|
||||
echo '# See also the release notes for fish 4.3.0 (run `help relnotes`).') \
|
||||
"" \
|
||||
'set --global '$theme_data
|
||||
|
||||
@@ -91,7 +91,7 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
echo -s (set_color --underline) $promptname (set_color normal)
|
||||
$fish -c '
|
||||
functions -e fish_right_prompt
|
||||
__fish_data_with_file $argv[1] source
|
||||
__fish_config_with_file $argv[1] source
|
||||
false
|
||||
fish_prompt
|
||||
echo (set_color normal)
|
||||
@@ -120,9 +120,9 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
return 1
|
||||
end
|
||||
__fish_config_prompt_reset
|
||||
__fish_data_with_file $prompt_path source
|
||||
__fish_config_with_file $prompt_path source
|
||||
if not functions -q fish_mode_prompt
|
||||
__fish_data_with_file functions/fish_mode_prompt.fish source
|
||||
status get-file functions/fish_mode_prompt.fish | source
|
||||
end
|
||||
case save
|
||||
if begin
|
||||
@@ -142,7 +142,7 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
return 1
|
||||
end
|
||||
__fish_config_prompt_reset
|
||||
__fish_data_with_file $prompt_path source
|
||||
__fish_config_with_file $prompt_path source
|
||||
end
|
||||
|
||||
funcsave fish_prompt
|
||||
@@ -156,7 +156,7 @@ function fish_config --description "Launch fish's web based configuration"
|
||||
end
|
||||
end
|
||||
if not functions -q fish_mode_prompt
|
||||
__fish_data_with_file functions/fish_mode_prompt.fish source
|
||||
status get-file functions/fish_mode_prompt.fish | source
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -287,7 +287,7 @@ function __fish_config_theme_choose
|
||||
|
||||
set -l color_theme
|
||||
__fish_config_theme_canonicalize
|
||||
set -l theme_data (type -q cat && __fish_theme_cat $theme_name)
|
||||
set -l theme_data (__fish_theme_cat $theme_name)
|
||||
or return
|
||||
set -l color_themes dark light unknown
|
||||
set -l theme_is_color_theme_aware false
|
||||
@@ -321,10 +321,11 @@ function __fish_config_theme_choose
|
||||
else
|
||||
set desired_color_theme $fish_terminal_color_theme
|
||||
if not set -q desired_color_theme[1]
|
||||
echo >&2 "fish_config theme choose: internal error: \$fish_terminal_color_theme not yet initialized"
|
||||
return 1
|
||||
end
|
||||
if not contains -- "[$desired_color_theme]" $theme_data
|
||||
if test $scope = -U
|
||||
echo >&2 "fish_config theme save: error: \$fish_terminal_color_theme not yet initialized"
|
||||
return 1
|
||||
end
|
||||
else if not contains -- "[$desired_color_theme]" $theme_data
|
||||
__fish_config_theme_choose_bad_color_theme $theme_name "$desired_color_theme" \$fish_terminal_color_theme = $desired_color_theme
|
||||
echo >&2 "fish_config theme choose: hint: if your terminal does not report colors, pass --color-theme=light or --color-theme=dark when using color-theme-aware themes"
|
||||
return 1
|
||||
@@ -340,9 +341,9 @@ function __fish_config_theme_choose
|
||||
end
|
||||
|
||||
set -l color_theme
|
||||
string join \n -- $theme_data |
|
||||
string match -re -- (__fish_theme_variable_filter)'|^\[.*\]$' $theme_data |
|
||||
while read -lat toks
|
||||
if $theme_is_color_theme_aware
|
||||
if $theme_is_color_theme_aware && set -q desired_color_theme[1]
|
||||
for ct in $color_themes
|
||||
if test "$toks" = [$ct]
|
||||
set color_theme $ct
|
||||
@@ -354,15 +355,19 @@ function __fish_config_theme_choose
|
||||
end
|
||||
end
|
||||
set -l varname $toks[1]
|
||||
string match -rq -- (__fish_theme_variable_filter) "$varname"
|
||||
or continue
|
||||
string match -q '[*' -- $varname
|
||||
and continue
|
||||
# If we're supposed to set universally, remove any shadowing globals
|
||||
# so the change takes effect immediately (and there's no warning).
|
||||
if test $scope = -U; and set -qg $varname
|
||||
set -eg $varname
|
||||
end
|
||||
if $override || not set -q $varname || string match -rq -- '--theme=.*' $$varname
|
||||
set $scope $toks (test $scope != -U && echo --theme=$theme_name)
|
||||
set $scope $toks[1] (
|
||||
if not $theme_is_color_theme_aware || set -q desired_color_theme[1]
|
||||
string join \n -- $toks[2..]
|
||||
end
|
||||
) (test $scope != -U && echo --theme=$theme_name)
|
||||
end
|
||||
end
|
||||
if $override
|
||||
@@ -376,31 +381,38 @@ function __fish_config_theme_choose
|
||||
end
|
||||
end
|
||||
end
|
||||
if test -n "$fish_terminal_color_theme" || not $need_hook
|
||||
if set -q _flag_no_override[1]
|
||||
__fish_apply_theme
|
||||
else
|
||||
__fish_override=true __fish_apply_theme
|
||||
if not $need_hook || test -n "$fish_terminal_color_theme" ||
|
||||
# comment to work around fish_indent bug
|
||||
{
|
||||
$theme_is_color_theme_aware && test -z "$fish_terminal_color_theme"
|
||||
}
|
||||
if not set -q _flag_no_override[1]
|
||||
set -fx __fish_override true
|
||||
end
|
||||
__fish_apply_theme
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_config_theme_canonicalize --no-scope-shadowing
|
||||
# theme_name
|
||||
# color_theme
|
||||
if not path is (__fish_theme_dir)/$theme_name.theme
|
||||
switch $theme_name
|
||||
case 'fish default'
|
||||
set theme_name default
|
||||
case 'ayu Dark' 'ayu Light' 'ayu Mirage' \
|
||||
'Base16 Default Dark' 'Base16 Default Light' 'Base16 Eighties' \
|
||||
'Bay Cruise' Dracula Fairground 'Just a Touch' Lava \
|
||||
'Mono Lace' 'Mono Smoke' \
|
||||
None Nord 'Old School' Seaweed 'Snow Day' \
|
||||
'Solarized Dark' 'Solarized Light' \
|
||||
'Tomorrow Night Bright' 'Tomorrow Night' Tomorrow
|
||||
set theme_name (string lower (string replace -a " " "-" $theme_name))
|
||||
end
|
||||
if path is (__fish_theme_dir)/$theme_name.theme
|
||||
return
|
||||
end
|
||||
switch $theme_name
|
||||
case 'fish default'
|
||||
set theme_name default
|
||||
case 'ayu Dark' 'ayu Light' 'ayu Mirage' \
|
||||
'Base16 Default Dark' 'Base16 Default Light' 'Base16 Eighties' \
|
||||
'Bay Cruise' Dracula Fairground 'Just a Touch' Lava \
|
||||
'Mono Lace' 'Mono Smoke' \
|
||||
None Nord 'Old School' Seaweed 'Snow Day' \
|
||||
'Solarized Dark' 'Solarized Light' \
|
||||
'Tomorrow Night Bright' 'Tomorrow Night' Tomorrow
|
||||
if test $theme_name = Tomorrow
|
||||
set color_theme light
|
||||
end
|
||||
set theme_name (string lower (string replace -a " " "-" $theme_name))
|
||||
end
|
||||
switch $theme_name
|
||||
case \
|
||||
@@ -408,8 +420,6 @@ function __fish_config_theme_canonicalize --no-scope-shadowing
|
||||
base16-default-dark base16-default-light \
|
||||
solarized-dark solarized-light
|
||||
string match -rq -- '^(?<theme_name>.*)-(?<color_theme>dark|light)$' $theme_name
|
||||
case tomorrow
|
||||
set color_theme light
|
||||
case tomorrow-night
|
||||
set theme_name tomorrow
|
||||
set color_theme dark
|
||||
|
||||
@@ -131,21 +131,15 @@ function fish_delta
|
||||
printf (_ "%sUnmodified%s: %s\n") $colors[4] $colors[1] $file
|
||||
end
|
||||
end
|
||||
function __fish_delta_diff_maybe_file -a maybe_default_file
|
||||
# TODO Use "set -l foo (cat)" instead of the temp file.
|
||||
# https://github.com/fish-shell/fish-shell/issues/206
|
||||
if $default_exists
|
||||
set -l tmpfile (__fish_mktemp_relative fish-delta)
|
||||
cat $maybe_default_file >$tmpfile
|
||||
status get-file $dir/$bn >$tmpfile
|
||||
__fish_delta_diff $tmpfile
|
||||
command rm $tmpfile
|
||||
end
|
||||
if $default_exists
|
||||
__fish_data_with_file $dir/$bn __fish_delta_diff_maybe_file
|
||||
else
|
||||
__fish_delta_diff /dev/null
|
||||
end
|
||||
functions --erase __fish_delta_diff
|
||||
functions --erase __fish_delta_diff_maybe_file
|
||||
else
|
||||
# Without diff, we can't really tell if the contents are the same.
|
||||
printf (_ "%sPossibly changed%s: %s\n") $colors[3] $colors[1] $file
|
||||
|
||||
@@ -20,7 +20,7 @@ function fish_update_completions --description "Update man-page based completion
|
||||
--cleanup-in $__fish_user_data_dir/generated_completions \
|
||||
--cleanup-in $__fish_cache_dir/generated_completions
|
||||
|
||||
__fish_data_with_file tools/create_manpage_completions.py cat |
|
||||
status get-file tools/create_manpage_completions.py |
|
||||
if $detach
|
||||
# Run python directly in the background and swallow all output
|
||||
# Orphan the job so that it continues to run in case of an early exit (#6269)
|
||||
|
||||
@@ -140,7 +140,7 @@ chromium-browser
|
||||
switch "$fish_help_item"
|
||||
case ''
|
||||
set fish_help_page index.html
|
||||
case (__fish_data_with_file help_sections (command -v cat) | string replace -r "^index(#|\$)" introduction\$1)
|
||||
case (status get-file help_sections | string replace -r "^index(#|\$)" introduction\$1)
|
||||
set fish_help_page (
|
||||
printf %s $fish_help_item |
|
||||
string replace -r '^introduction(#|$)' 'index$1' |
|
||||
|
||||
@@ -6,7 +6,7 @@ function history --description "display or manipulate interactive command histor
|
||||
set -l cmd history
|
||||
set -l options --exclusive 'c,e,p' --exclusive 'S,D,M,V,X'
|
||||
set -a options h/help c/contains e/exact p/prefix
|
||||
set -a options C/case-sensitive R/reverse z/null 't/show-time=?' 'n#max'
|
||||
set -a options C/case-sensitive R/reverse z/null 't/show-time=?' 'n#max' 'color='
|
||||
# The following options are deprecated and will be removed in the next major release.
|
||||
# Note that they do not have usable short flags.
|
||||
set -a options S-search D-delete M-merge V-save X-clear
|
||||
@@ -22,9 +22,12 @@ function history --description "display or manipulate interactive command histor
|
||||
set -l show_time
|
||||
set -l max_count
|
||||
set -l search_mode
|
||||
set -l color_opt
|
||||
set -q _flag_max
|
||||
set max_count -n$_flag_max
|
||||
|
||||
set color_opt --color=$_flag_color
|
||||
|
||||
set -q _flag_with_time
|
||||
and set -l _flag_show_time $_flag_with_time
|
||||
if set -q _flag_show_time[1]
|
||||
@@ -78,7 +81,7 @@ function history --description "display or manipulate interactive command histor
|
||||
# If the user hasn't preconfigured less with the $LESS environment variable,
|
||||
# we do so to have it behave like cat if output fits on one screen.
|
||||
if not set -qx LESS
|
||||
set -fx LESS --quit-if-one-screen
|
||||
set -fx LESS --quit-if-one-screen --RAW-CONTROL-CHARS
|
||||
# Also set --no-init for less < v530, see #8157.
|
||||
if type -q less; and test (less --version | string match -r 'less (\d+)')[2] -lt 530 2>/dev/null
|
||||
set LESS $LESS --no-init
|
||||
@@ -87,9 +90,15 @@ function history --description "display or manipulate interactive command histor
|
||||
not set -qx LV # ask the pager lv not to strip colors
|
||||
and set -fx LV -c
|
||||
|
||||
builtin history search $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv | $pager
|
||||
if contains -- "$color_opt" '' '--color=auto'
|
||||
and test "$pager" = less
|
||||
and string match -rq -- '^(-\w*R|--RAW-CONTROL-CHARS$)' $LESS
|
||||
set color_opt --color=always
|
||||
end
|
||||
|
||||
builtin history search $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv | $pager
|
||||
else
|
||||
builtin history search $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
builtin history search $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
end
|
||||
|
||||
case delete # interactively delete history
|
||||
@@ -100,15 +109,15 @@ function history --description "display or manipulate interactive command histor
|
||||
end
|
||||
|
||||
if test "$search_mode" = --exact
|
||||
builtin history delete $search_mode $_flag_case_sensitive -- $searchterm
|
||||
builtin history delete $color_opt $search_mode $_flag_case_sensitive -- $searchterm
|
||||
builtin history save
|
||||
return
|
||||
end
|
||||
|
||||
# TODO: Fix this so that requesting history entries with a timestamp works:
|
||||
# set -l found_items (builtin history search $search_mode $show_time -- $argv)
|
||||
# set -l found_items (builtin history search $color_opt $search_mode $show_time -- $argv)
|
||||
set -l found_items
|
||||
set found_items (builtin history search $search_mode $_flag_case_sensitive --null -- $searchterm | string split0)
|
||||
set found_items (builtin history search $color_opt $search_mode $_flag_case_sensitive --null -- $searchterm | string split0)
|
||||
if set -q found_items[1]
|
||||
set -l found_items_count (count $found_items)
|
||||
for i in (seq $found_items_count)
|
||||
@@ -132,7 +141,7 @@ function history --description "display or manipulate interactive command histor
|
||||
if test "$choice" = all
|
||||
printf "Deleting all matching entries!\n"
|
||||
for item in $found_items
|
||||
builtin history delete --exact --case-sensitive -- $item
|
||||
builtin history delete $color_opt --exact --case-sensitive -- $item
|
||||
end
|
||||
builtin history save
|
||||
return
|
||||
@@ -173,15 +182,15 @@ function history --description "display or manipulate interactive command histor
|
||||
echo Deleting choices: $choices
|
||||
for x in $choices
|
||||
printf "Deleting history entry %s: \"%s\"\n" $x $found_items[$x]
|
||||
builtin history delete --exact --case-sensitive -- "$found_items[$x]"
|
||||
builtin history delete $color_opt --exact --case-sensitive -- "$found_items[$x]"
|
||||
end
|
||||
builtin history save
|
||||
end
|
||||
|
||||
case save # save our interactive command history to the persistent history
|
||||
builtin history save $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
builtin history save $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
case merge # merge the persistent interactive command history with our history
|
||||
builtin history merge $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
builtin history merge $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
case clear # clear the interactive command history
|
||||
if test -n "$search_mode"
|
||||
or set -q show_time[1]
|
||||
@@ -197,13 +206,13 @@ function history --description "display or manipulate interactive command histor
|
||||
read --local --prompt "echo 'Are you sure you want to clear history? (yes/no) '" choice
|
||||
or return $status
|
||||
if test "$choice" = yes
|
||||
builtin history clear $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
builtin history clear $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
and printf (_ "Command history cleared!\n")
|
||||
else
|
||||
printf (_ "You did not say 'yes' so I will not clear your command history\n")
|
||||
end
|
||||
case clear-session # clears only session
|
||||
builtin history clear-session $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
builtin history clear-session $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
and printf (_ "Command history for session cleared!\n")
|
||||
case append
|
||||
set -l newitem $argv
|
||||
@@ -212,7 +221,7 @@ function history --description "display or manipulate interactive command histor
|
||||
or return $status
|
||||
end
|
||||
|
||||
builtin history append $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
|
||||
builtin history append $color_opt $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
|
||||
case '*'
|
||||
printf "%s: unexpected subcommand '%s'\n" $cmd $hist_cmd
|
||||
return 2
|
||||
|
||||
@@ -5,7 +5,15 @@ body {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.prompt_demo, .prompt_demo_text, .data_table_row, .colorpicker_text_sample_tight, .colorpicker_text_sample, .history_text, pre, code, tt {
|
||||
.prompt_demo,
|
||||
.prompt_demo_text,
|
||||
.data_table_row,
|
||||
.colorpicker_text_sample_tight,
|
||||
.colorpicker_text_sample,
|
||||
.history_text,
|
||||
pre,
|
||||
code,
|
||||
tt {
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, "Ubuntu Mono", "Hack", "Noto Sans Mono", Liberation Mono, monospace;
|
||||
}
|
||||
|
||||
@@ -48,14 +56,14 @@ body {
|
||||
.tab:hover,
|
||||
#tab_contents .master_element:hover,
|
||||
.color_scheme_choice_container:hover,
|
||||
.prompt_choices_list > .ng-scope:hover
|
||||
{
|
||||
.prompt_choices_list > .ng-scope:hover {
|
||||
background-color: #DDE;
|
||||
}
|
||||
|
||||
#tab_parent{
|
||||
#tab_parent {
|
||||
border: red 1px;
|
||||
}
|
||||
|
||||
#tab_parent :first-child {
|
||||
border-top-left-radius: 8px;
|
||||
border-left: none;
|
||||
@@ -65,7 +73,8 @@ body {
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
.selected_tab, .selected_tab:hover {
|
||||
.selected_tab,
|
||||
.selected_tab:hover {
|
||||
background-color: #eeeefa;
|
||||
border-bottom: none;
|
||||
}
|
||||
@@ -139,30 +148,39 @@ body {
|
||||
.detail_function .fish_color_autosuggestion {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_command {
|
||||
color: #005fd7;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_param {
|
||||
color: #00afff;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_redirection {
|
||||
color: #00afff;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_comment {
|
||||
color: #990000;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_error {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_escape {
|
||||
color: #00a6b2;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_operator {
|
||||
color: #00a6b2;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_quote {
|
||||
color: #999900;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_statement_terminator {
|
||||
color: #009900;
|
||||
}
|
||||
@@ -221,6 +239,7 @@ body {
|
||||
.master_element > br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selected_master_elem > br {
|
||||
display: inherit;
|
||||
}
|
||||
@@ -267,8 +286,7 @@ body {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.data_table_row {
|
||||
}
|
||||
.data_table_row {}
|
||||
|
||||
.data_table_cell {
|
||||
padding-top: 5px;
|
||||
@@ -625,6 +643,7 @@ button.delete_button:hover {
|
||||
#parent {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#tab_contents {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -632,27 +651,29 @@ button.delete_button:hover {
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: linear-gradient(to top, #1f1f3f 0%,#051f3a 100%);
|
||||
background: linear-gradient(to top, #1f1f3f 0%, #051f3a 100%);
|
||||
color: #DDD;
|
||||
}
|
||||
|
||||
#ancestor {
|
||||
box-shadow: 0 0 5px 1px #000;
|
||||
}
|
||||
|
||||
.tab {
|
||||
background-color: black;
|
||||
border: 1px solid #222;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.tab:hover,
|
||||
#tab_contents .master_element:hover,
|
||||
.color_scheme_choice_container:hover,
|
||||
.prompt_choices_list > .ng-scope:hover
|
||||
{
|
||||
.prompt_choices_list > .ng-scope:hover {
|
||||
background-color: #223;
|
||||
}
|
||||
|
||||
.selected_tab, .selected_tab:hover {
|
||||
.selected_tab,
|
||||
.selected_tab:hover {
|
||||
background-color: #202028;
|
||||
border-bottom: none;
|
||||
}
|
||||
@@ -660,15 +681,63 @@ button.delete_button:hover {
|
||||
#tab_contents {
|
||||
background-color: #202028;
|
||||
}
|
||||
.detail, .selected_master_elem {
|
||||
|
||||
.detail,
|
||||
.selected_master_elem {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: #222;
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
/* Fix .function-body background in dark mode */
|
||||
.function-body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_autosuggestion {
|
||||
color: #808080; /* brblack */
|
||||
}
|
||||
|
||||
.detail_function .fish_color_command {
|
||||
color: #c397d8;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_param {
|
||||
color: #7aa6da;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_redirection {
|
||||
color: #70c0b1;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_comment {
|
||||
color: #e7c547;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_error {
|
||||
color: #d54e53;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_escape {
|
||||
color: #00a6b2;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_operator {
|
||||
color: #00a6b2;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_quote {
|
||||
color: #b9ca4a;
|
||||
}
|
||||
|
||||
.detail_function .fish_color_statement_terminator {
|
||||
color: #c397d8;
|
||||
}
|
||||
}
|
||||
|
||||
.print_only {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@ body {
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
.tab, .print_hidden {
|
||||
.tab,
|
||||
.print_hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -27,4 +28,4 @@ body {
|
||||
#ancestor {
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::{Mutex, MutexGuard},
|
||||
sync::{LazyLock, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::parse_constants::SourceRange;
|
||||
use pcre2::utf32::Regex;
|
||||
|
||||
static ABBRS: Lazy<Mutex<AbbreviationSet>> = Lazy::new(|| Mutex::new(Default::default()));
|
||||
static ABBRS: LazyLock<Mutex<AbbreviationSet>> = LazyLock::new(|| Mutex::new(Default::default()));
|
||||
|
||||
pub fn with_abbrs<R>(cb: impl FnOnce(&AbbreviationSet) -> R) -> R {
|
||||
let abbrs_g = ABBRS.lock().unwrap();
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
parse_constants::{ParseErrorList, ParseTreeFlags},
|
||||
parse_tree::ParsedSource,
|
||||
parse_util::parse_util_detect_errors_in_ast,
|
||||
parser::{BlockType, CancelBehavior, Parser},
|
||||
parser::{BlockType, CancelBehavior, Parser, ParserEnvSetMode},
|
||||
path::path_get_config,
|
||||
prelude::*,
|
||||
printf,
|
||||
@@ -385,7 +385,8 @@ fn throwing_main() -> i32 {
|
||||
set_libc_locales(/*log_ok=*/ false)
|
||||
};
|
||||
|
||||
fish::localization::initialize_gettext();
|
||||
#[cfg(feature = "localize-messages")]
|
||||
fish::localization::initialize_localization();
|
||||
|
||||
// Enable debug categories set in FISH_DEBUG.
|
||||
// This is in *addition* to the ones given via --debug.
|
||||
@@ -498,9 +499,9 @@ fn throwing_main() -> i32 {
|
||||
|
||||
if is_interactive_session() && opts.no_config && !opts.no_exec {
|
||||
// If we have no config, we default to the default key bindings.
|
||||
parser.vars().set_one(
|
||||
parser.set_one(
|
||||
L!("fish_key_bindings"),
|
||||
EnvMode::UNEXPORT,
|
||||
ParserEnvSetMode::new(EnvMode::UNEXPORT),
|
||||
L!("fish_default_key_bindings").to_owned(),
|
||||
);
|
||||
if function::exists(L!("fish_default_key_bindings"), parser) {
|
||||
@@ -545,9 +546,9 @@ fn throwing_main() -> i32 {
|
||||
// Pass additional args as $argv.
|
||||
// Note that we *don't* support setting argv[0]/$0, unlike e.g. bash.
|
||||
let list = &args[my_optind..];
|
||||
parser.vars().set(
|
||||
parser.set_var(
|
||||
L!("argv"),
|
||||
EnvMode::default(),
|
||||
ParserEnvSetMode::default(),
|
||||
list.iter().map(|s| s.to_owned()).collect(),
|
||||
);
|
||||
res = run_command_list(parser, &opts.batch_cmds);
|
||||
@@ -580,9 +581,9 @@ fn throwing_main() -> i32 {
|
||||
}
|
||||
Ok(f) => {
|
||||
let list = &args[my_optind..];
|
||||
parser.vars().set(
|
||||
parser.set_var(
|
||||
L!("argv"),
|
||||
EnvMode::default(),
|
||||
ParserEnvSetMode::default(),
|
||||
list.iter().map(|s| s.to_owned()).collect(),
|
||||
);
|
||||
let rel_filename = &args[my_optind - 1];
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::prelude::*;
|
||||
use crate::abbrs::{self, Abbreviation, Position};
|
||||
use crate::common::{EscapeStringStyle, escape, escape_string, valid_func_name};
|
||||
use crate::common::{EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_func_name};
|
||||
use crate::env::{EnvMode, EnvStackSetResult};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::re::{regex_make_anchored, to_boxed_chars};
|
||||
use pcre2::utf32::{Regex, RegexBuilder};
|
||||
|
||||
@@ -21,6 +23,7 @@ struct Options {
|
||||
position: Option<Position>,
|
||||
set_cursor_marker: Option<WString>,
|
||||
args: Vec<WString>,
|
||||
color: ColorEnabled,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
@@ -123,7 +126,7 @@ fn join(list: &[&wstr], sep: &wstr) -> WString {
|
||||
}
|
||||
|
||||
// Print abbreviations in a fish-script friendly way.
|
||||
fn abbr_show(streams: &mut IoStreams) -> BuiltinResult {
|
||||
fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &Parser) -> BuiltinResult {
|
||||
let style = EscapeStringStyle::Script(Default::default());
|
||||
|
||||
abbrs::with_abbrs(|abbrs| {
|
||||
@@ -172,7 +175,15 @@ fn abbr_show(streams: &mut IoStreams) -> BuiltinResult {
|
||||
));
|
||||
}
|
||||
result.push('\n');
|
||||
streams.out.append(&result);
|
||||
if opts.color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&result,
|
||||
&parser.context(),
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -460,7 +471,8 @@ fn abbr_erase(opts: &Options, parser: &Parser) -> BuiltinResult {
|
||||
let esc_src = escape(arg);
|
||||
if !esc_src.is_empty() {
|
||||
let var_name = WString::from_str("_fish_abbr_") + esc_src.as_utfstr();
|
||||
let ret = parser.vars().remove(&var_name, EnvMode::UNIVERSAL);
|
||||
let ret =
|
||||
parser.remove_var(&var_name, ParserEnvSetMode::new(EnvMode::UNIVERSAL));
|
||||
|
||||
if ret == EnvStackSetResult::Ok {
|
||||
result = Ok(SUCCESS)
|
||||
@@ -506,6 +518,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
wopt(L!("global"), ArgType::NoArgument, 'g'),
|
||||
wopt(L!("universal"), ArgType::NoArgument, 'U'),
|
||||
wopt(L!("help"), ArgType::NoArgument, 'h'),
|
||||
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||
];
|
||||
|
||||
let mut opts = Options::default();
|
||||
@@ -612,6 +625,9 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
COLOR_OPTION_CHAR => {
|
||||
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected retval from wgeopter.next()");
|
||||
}
|
||||
@@ -630,7 +646,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
return abbr_add(&opts, streams);
|
||||
};
|
||||
if opts.show {
|
||||
return abbr_show(streams);
|
||||
return abbr_show(&opts, streams, parser);
|
||||
};
|
||||
if opts.list {
|
||||
return abbr_list(&opts, streams);
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
use crate::env::{EnvMode, EnvStack};
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack};
|
||||
use crate::exec::exec_subshell;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::wutil::fish_iswalnum;
|
||||
|
||||
const VAR_NAME_PREFIX: &wstr = L!("_flag_");
|
||||
@@ -195,7 +196,7 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
|
||||
}
|
||||
|
||||
// Store the set of exclusive flags for use when parsing the supplied set of arguments.
|
||||
opts.exclusive_flag_sets.push(exclusive_set.to_vec());
|
||||
opts.exclusive_flag_sets.push(exclusive_set.clone());
|
||||
}
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
@@ -699,24 +700,31 @@ fn validate_arg<'opts>(
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
let vars = parser.vars();
|
||||
vars.push(true /* new_scope */);
|
||||
parser.vars().push(true /* new_scope */);
|
||||
|
||||
let env_mode = EnvMode::LOCAL | EnvMode::EXPORT;
|
||||
vars.set_one(L!("_argparse_cmd"), env_mode, opts_name.to_owned());
|
||||
let local_exported_mode = ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT);
|
||||
parser.set_one(
|
||||
L!("_argparse_cmd"),
|
||||
local_exported_mode,
|
||||
opts_name.to_owned(),
|
||||
);
|
||||
let flag_name = WString::from(VAR_NAME_PREFIX) + "name";
|
||||
if is_long_flag {
|
||||
vars.set_one(&flag_name, env_mode, opt_spec.long_flag.to_owned());
|
||||
} else {
|
||||
vars.set_one(
|
||||
parser.set_one(
|
||||
&flag_name,
|
||||
env_mode,
|
||||
local_exported_mode,
|
||||
opt_spec.long_flag.to_owned(),
|
||||
);
|
||||
} else {
|
||||
parser.set_one(
|
||||
&flag_name,
|
||||
local_exported_mode,
|
||||
WString::from_chars(vec![opt_spec.short_flag]),
|
||||
);
|
||||
}
|
||||
vars.set_one(
|
||||
parser.set_one(
|
||||
&(WString::from(VAR_NAME_PREFIX) + "value"),
|
||||
env_mode,
|
||||
local_exported_mode,
|
||||
woptarg.to_owned(),
|
||||
);
|
||||
|
||||
@@ -733,7 +741,7 @@ fn validate_arg<'opts>(
|
||||
streams.err.append(&output);
|
||||
streams.err.append_char('\n');
|
||||
}
|
||||
vars.pop();
|
||||
parser.vars().pop(parser.is_repainting());
|
||||
retval.map(|()| SUCCESS)
|
||||
}
|
||||
|
||||
@@ -1138,7 +1146,7 @@ fn check_min_max_args_constraints(
|
||||
}
|
||||
|
||||
/// Put the result of parsing the supplied args into the caller environment as local vars.
|
||||
fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
|
||||
fn set_argparse_result_vars(vars: &EnvStack, local_mode: EnvSetMode, opts: ArgParseCmdOpts) {
|
||||
for opt_spec in opts.options.values() {
|
||||
if opt_spec.num_seen == 0 {
|
||||
continue;
|
||||
@@ -1147,7 +1155,7 @@ fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
|
||||
if opt_spec.short_flag_valid {
|
||||
let mut var_name = WString::from(VAR_NAME_PREFIX);
|
||||
var_name.push(opt_spec.short_flag);
|
||||
vars.set(&var_name, EnvMode::LOCAL, opt_spec.vals.clone());
|
||||
vars.set(&var_name, local_mode, opt_spec.vals.clone());
|
||||
}
|
||||
|
||||
if !opt_spec.long_flag.is_empty() {
|
||||
@@ -1158,14 +1166,14 @@ fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
|
||||
.chars()
|
||||
.map(|c| if fish_iswalnum(c) { c } else { '_' });
|
||||
let var_name_long: WString = VAR_NAME_PREFIX.chars().chain(long_flag).collect();
|
||||
vars.set(&var_name_long, EnvMode::LOCAL, opt_spec.vals.clone());
|
||||
vars.set(&var_name_long, local_mode, opt_spec.vals.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let args = opts.args.into_iter().map(|s| s.into_owned()).collect();
|
||||
vars.set(L!("argv"), EnvMode::LOCAL, args);
|
||||
vars.set(L!("argv"), local_mode, args);
|
||||
let args_opts = opts.args_opts.into_iter().map(|s| s.into_owned()).collect();
|
||||
vars.set(L!("argv_opts"), EnvMode::LOCAL, args_opts);
|
||||
vars.set(L!("argv_opts"), local_mode, args_opts);
|
||||
}
|
||||
|
||||
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
|
||||
@@ -1213,7 +1221,11 @@ pub fn argparse(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
|
||||
check_min_max_args_constraints(&opts, streams)?;
|
||||
|
||||
set_argparse_result_vars(parser.vars(), opts);
|
||||
set_argparse_result_vars(
|
||||
parser.vars(),
|
||||
parser.convert_env_set_mode(ParserEnvSetMode::new(EnvMode::LOCAL)),
|
||||
opts,
|
||||
);
|
||||
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
use crate::common::{
|
||||
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
|
||||
};
|
||||
use crate::highlight::{colorize, highlight_shell};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::input::{InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings};
|
||||
use crate::key::{
|
||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||
};
|
||||
use crate::nix::isatty;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
const DEFAULT_BIND_MODE: &wstr = L!("default");
|
||||
@@ -32,6 +31,7 @@ struct Options {
|
||||
mode: c_int,
|
||||
bind_mode: WString,
|
||||
sets_bind_mode: Option<WString>,
|
||||
color: ColorEnabled,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
@@ -49,6 +49,7 @@ fn new() -> Options {
|
||||
mode: BIND_INSERT,
|
||||
bind_mode: DEFAULT_BIND_MODE.to_owned(),
|
||||
sets_bind_mode: None,
|
||||
color: ColorEnabled::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,11 +154,12 @@ fn list_one(
|
||||
}
|
||||
out.push('\n');
|
||||
|
||||
if !streams.out_is_redirected && isatty(libc::STDOUT_FILENO) {
|
||||
let mut colors = Vec::new();
|
||||
highlight_shell(&out, &mut colors, &parser.context(), false, None);
|
||||
let colored = colorize(&out, &colors, parser.vars());
|
||||
streams.out.append(&bytes2wcstring(&colored));
|
||||
if self.opts.color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&out,
|
||||
&parser.context(),
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&out);
|
||||
}
|
||||
@@ -370,8 +372,8 @@ fn insert(
|
||||
if self.add(
|
||||
seq,
|
||||
&argv[optind + 1..],
|
||||
self.opts.bind_mode.to_owned(),
|
||||
self.opts.sets_bind_mode.to_owned(),
|
||||
self.opts.bind_mode.clone(),
|
||||
self.opts.sets_bind_mode.clone(),
|
||||
self.opts.user,
|
||||
streams,
|
||||
) {
|
||||
@@ -408,7 +410,7 @@ fn parse_cmd_opts(
|
||||
) -> BuiltinResult {
|
||||
let cmd = argv[0];
|
||||
let short_options = L!("aehkKfM:Lm:s");
|
||||
const long_options: &[WOption] = &[
|
||||
let long_options: &[WOption] = &[
|
||||
wopt(L!("all"), NoArgument, 'a'),
|
||||
wopt(L!("erase"), NoArgument, 'e'),
|
||||
wopt(L!("function-names"), NoArgument, 'f'),
|
||||
@@ -421,9 +423,10 @@ fn parse_cmd_opts(
|
||||
wopt(L!("sets-mode"), RequiredArgument, 'm'),
|
||||
wopt(L!("silent"), NoArgument, 's'),
|
||||
wopt(L!("user"), NoArgument, 'u'),
|
||||
wopt(L!("color"), RequiredArgument, COLOR_OPTION_CHAR),
|
||||
];
|
||||
|
||||
let mut check_mode_name = |mode_name: &wstr| -> Result<(), ErrorCode> {
|
||||
let check_mode_name = |streams: &mut IoStreams, mode_name: &wstr| -> Result<(), ErrorCode> {
|
||||
if !valid_var_name(mode_name) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
BUILTIN_ERR_BIND_MODE,
|
||||
@@ -457,13 +460,13 @@ fn parse_cmd_opts(
|
||||
}
|
||||
'M' => {
|
||||
let applicable_mode = w.woptarg.unwrap();
|
||||
check_mode_name(applicable_mode)?;
|
||||
check_mode_name(streams, applicable_mode)?;
|
||||
opts.bind_mode = applicable_mode.to_owned();
|
||||
opts.bind_mode_given = true;
|
||||
}
|
||||
'm' => {
|
||||
let new_mode = w.woptarg.unwrap();
|
||||
check_mode_name(new_mode)?;
|
||||
check_mode_name(streams, new_mode)?;
|
||||
opts.sets_bind_mode = Some(new_mode.to_owned());
|
||||
}
|
||||
'p' => {
|
||||
@@ -487,6 +490,9 @@ fn parse_cmd_opts(
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
COLOR_OPTION_CHAR => {
|
||||
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected retval from WGetopter")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::{
|
||||
env::{EnvMode, Environment},
|
||||
fds::{BEST_O_SEARCH, wopen_dir},
|
||||
parser::ParserEnvSetMode,
|
||||
path::path_apply_cdpath,
|
||||
wutil::{normalize_path, wperror, wreadlink},
|
||||
};
|
||||
@@ -127,7 +128,11 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
||||
// Stash the fd for the cwd in the parser.
|
||||
parser.libdata_mut().cwd_fd = Some(dir_fd);
|
||||
|
||||
parser.set_var_and_fire(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, vec![norm_dir]);
|
||||
parser.set_var_and_fire(
|
||||
L!("PWD"),
|
||||
ParserEnvSetMode::new(EnvMode::EXPORT | EnvMode::GLOBAL),
|
||||
vec![norm_dir],
|
||||
);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
|
||||
let mut override_buffer = None;
|
||||
|
||||
const short_options: &wstr = L!("abijpctfxorhI:CBELSsP");
|
||||
let short_options = L!("abijpctfxorhI:CBELSsP");
|
||||
let long_options: &[WOption] = &[
|
||||
wopt(L!("append"), ArgType::NoArgument, 'a'),
|
||||
wopt(L!("insert"), ArgType::NoArgument, 'i'),
|
||||
@@ -399,7 +399,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
// Don't enqueue a repaint if we're currently in the middle of one,
|
||||
// because that's an infinite loop.
|
||||
if matches!(cmd, RL::RepaintMode | RL::ForceRepaint | RL::Repaint)
|
||||
&& parser.libdata().is_repaint
|
||||
&& parser.is_repainting()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
|
||||
use crate::highlight::colorize;
|
||||
use crate::highlight::highlight_shell;
|
||||
use crate::nix::isatty;
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::ParseErrorList;
|
||||
use crate::parse_util::parse_util_detect_errors_in_argument_list;
|
||||
@@ -18,7 +16,6 @@
|
||||
complete_remove, complete_remove_all,
|
||||
},
|
||||
};
|
||||
use libc::STDOUT_FILENO;
|
||||
|
||||
// builtin_complete_* are a set of rather silly looping functions that make sure that all the proper
|
||||
// combinations of complete_add or complete_remove get called. This is needed since complete allows
|
||||
@@ -221,16 +218,20 @@ fn builtin_complete_remove(
|
||||
}
|
||||
}
|
||||
|
||||
fn builtin_complete_print(cmd: &wstr, streams: &mut IoStreams, parser: &Parser) {
|
||||
fn builtin_complete_print(
|
||||
cmd: &wstr,
|
||||
streams: &mut IoStreams,
|
||||
parser: &Parser,
|
||||
color: ColorEnabled,
|
||||
) {
|
||||
let repr = complete_print(cmd);
|
||||
|
||||
// colorize if interactive
|
||||
if !streams.out_is_redirected && isatty(STDOUT_FILENO) {
|
||||
let mut colors = vec![];
|
||||
highlight_shell(&repr, &mut colors, &parser.context(), false, None);
|
||||
streams
|
||||
.out
|
||||
.append(&bytes2wcstring(&colorize(&repr, &colors, parser.vars())));
|
||||
if color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&repr,
|
||||
&parser.context(),
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&repr);
|
||||
}
|
||||
@@ -259,9 +260,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
let mut wrap_targets = vec![];
|
||||
let mut preserve_order = false;
|
||||
let mut unescape_output = true;
|
||||
let mut color = ColorEnabled::default();
|
||||
|
||||
const short_options: &wstr = L!("a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
|
||||
const long_options: &[WOption] = &[
|
||||
let short_options: &wstr = L!("a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
|
||||
let long_options: &[WOption] = &[
|
||||
wopt(L!("exclusive"), ArgType::NoArgument, 'x'),
|
||||
wopt(L!("no-files"), ArgType::NoArgument, 'f'),
|
||||
wopt(L!("force-files"), ArgType::NoArgument, 'F'),
|
||||
@@ -282,6 +284,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
wopt(L!("help"), ArgType::NoArgument, 'h'),
|
||||
wopt(L!("keep-order"), ArgType::NoArgument, 'k'),
|
||||
wopt(L!("escape"), ArgType::NoArgument, OPT_ESCAPE),
|
||||
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||
];
|
||||
|
||||
let mut have_x = false;
|
||||
@@ -401,6 +404,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
COLOR_OPTION_CHAR => {
|
||||
color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
_ => panic!("unexpected retval from WGetopter"),
|
||||
}
|
||||
}
|
||||
@@ -584,10 +590,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
// No arguments that would add or remove anything specified, so we print the definitions of
|
||||
// all matching completions.
|
||||
if cmd_to_complete.is_empty() {
|
||||
builtin_complete_print(L!(""), streams, parser);
|
||||
builtin_complete_print(L!(""), streams, parser, color);
|
||||
} else {
|
||||
for cmd in cmd_to_complete {
|
||||
builtin_complete_print(&cmd, streams, parser);
|
||||
builtin_complete_print(&cmd, streams, parser, color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Implementation of the fg builtin.
|
||||
|
||||
use crate::fds::make_fd_blocking;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::reader::{reader_save_screen_state, reader_write_title};
|
||||
use crate::tokenizer::tok_command;
|
||||
use crate::wutil::perror;
|
||||
@@ -123,7 +124,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
// Provide value for `status current-command`
|
||||
parser.libdata_mut().status_vars.command = ft.clone();
|
||||
// Also provide a value for the deprecated fish 2.0 $_ variable
|
||||
parser.set_var_and_fire(L!("_"), EnvMode::EXPORT, vec![ft]);
|
||||
parser.set_var_and_fire(L!("_"), ParserEnvSetMode::new(EnvMode::EXPORT), vec![ft]);
|
||||
// Provide value for `status current-commandline`
|
||||
parser.libdata_mut().status_vars.commandline = job.command().to_owned();
|
||||
}
|
||||
@@ -156,7 +157,6 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
if job.is_stopped() {
|
||||
handoff.save_tty_modes();
|
||||
}
|
||||
handoff.reclaim();
|
||||
if resumed {
|
||||
Ok(SUCCESS)
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
use super::prelude::*;
|
||||
use crate::ast::{self, AsNode, Ast, Kind, Leaf, Node, NodeVisitor, SourceRangeList, Traversal};
|
||||
use crate::common::{
|
||||
PROGRAM_NAME, UnescapeFlags, UnescapeStringStyle, bytes2wcstring, get_program_name,
|
||||
PROGRAM_NAME, ReadExt, UnescapeFlags, UnescapeStringStyle, bytes2wcstring, get_program_name,
|
||||
unescape_string, wcs2bytes,
|
||||
};
|
||||
use crate::env::EnvStack;
|
||||
@@ -756,6 +756,10 @@ fn brace_is_continuation(&self, node: &dyn ast::Token) -> bool {
|
||||
};
|
||||
|
||||
conj.decorator.is_some()
|
||||
|| matches!(
|
||||
self.traversal.parent(conj.as_node()).kind(),
|
||||
Kind::IfClause(_) | Kind::WhileHeader(_)
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_left_brace(&mut self, node: &dyn ast::Token) {
|
||||
@@ -910,10 +914,9 @@ pub fn main() {
|
||||
|
||||
fn throwing_main() -> i32 {
|
||||
// TODO: Duplicated with fish_key_reader
|
||||
use crate::io::FdOutputStream;
|
||||
use crate::io::IoChain;
|
||||
use crate::io::OutputStream::Fd;
|
||||
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||
use crate::fds::BorrowedFdFile;
|
||||
use crate::io::{FdOutputStream, IoChain, OutputStream::Fd};
|
||||
use libc::{STDERR_FILENO, STDOUT_FILENO};
|
||||
|
||||
topic_monitor_init();
|
||||
threads::init();
|
||||
@@ -922,12 +925,13 @@ fn throwing_main() -> i32 {
|
||||
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
||||
let io_chain = IoChain::new();
|
||||
let mut streams = IoStreams::new(&mut out, &mut err, &io_chain);
|
||||
streams.stdin_fd = STDIN_FILENO;
|
||||
streams.stdin_file = Some(BorrowedFdFile::stdin());
|
||||
// Safety: single-threaded.
|
||||
unsafe {
|
||||
set_libc_locales(/*log_ok=*/ false)
|
||||
};
|
||||
crate::localization::initialize_gettext();
|
||||
#[cfg(feature = "localize-messages")]
|
||||
crate::localization::initialize_localization();
|
||||
env_init(None, true, false);
|
||||
|
||||
// Only set these here so you can't set them via the builtin.
|
||||
@@ -940,15 +944,19 @@ fn throwing_main() -> i32 {
|
||||
let args: Vec<WString> = std::env::args_os()
|
||||
.map(|osstr| bytes2wcstring(osstr.as_bytes()))
|
||||
.collect();
|
||||
do_indent(&mut streams, args).builtin_status_code()
|
||||
do_indent(None, &mut streams, args).builtin_status_code()
|
||||
}
|
||||
|
||||
pub fn fish_indent(_parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
|
||||
pub fn fish_indent(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
|
||||
let args = args.iter_mut().map(|x| x.to_owned()).collect();
|
||||
do_indent(streams, args)
|
||||
do_indent(Some(parser), streams, args)
|
||||
}
|
||||
|
||||
fn do_indent(streams: &mut IoStreams, args: Vec<WString>) -> BuiltinResult {
|
||||
fn do_indent(
|
||||
parser: Option<&Parser>,
|
||||
streams: &mut IoStreams,
|
||||
args: Vec<WString>,
|
||||
) -> BuiltinResult {
|
||||
// Types of output we support
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum OutputType {
|
||||
@@ -988,7 +996,11 @@ enum OutputType {
|
||||
match c {
|
||||
'P' => DUMP_PARSE_TREE.store(true),
|
||||
'h' => {
|
||||
print_help("fish_indent");
|
||||
if let Some(parser) = parser {
|
||||
builtin_print_help(parser, streams, L!("fish_indent"));
|
||||
} else {
|
||||
print_help("fish_indent");
|
||||
}
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
'v' => {
|
||||
@@ -1042,18 +1054,24 @@ enum OutputType {
|
||||
));
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
use std::os::fd::FromRawFd;
|
||||
let mut fd = unsafe { std::fs::File::from_raw_fd(streams.stdin_fd) };
|
||||
let Some(stdin_file) = streams.stdin_file.as_mut() else {
|
||||
let cmd = "fish_indent";
|
||||
streams
|
||||
.err
|
||||
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
let mut buf = vec![];
|
||||
match fd.read_to_end(&mut buf) {
|
||||
match stdin_file.read_to_end_interruptible(&mut buf) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
// Don't close the fd
|
||||
std::mem::forget(fd);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
Err(err) => {
|
||||
return if err.kind() == std::io::ErrorKind::Interrupted {
|
||||
Err(128 + libc::SIGINT)
|
||||
} else {
|
||||
Err(STATUS_CMD_ERROR)
|
||||
};
|
||||
}
|
||||
}
|
||||
std::mem::forget(fd);
|
||||
src = bytes2wcstring(&buf);
|
||||
} else {
|
||||
let arg = args[i];
|
||||
@@ -1250,7 +1268,7 @@ struct TokenRange {
|
||||
}
|
||||
|
||||
let mut token_ranges: Vec<TokenRange> = vec![];
|
||||
for (i, color) in colors.iter().cloned().enumerate() {
|
||||
for (i, color) in colors.iter().copied().enumerate() {
|
||||
let role = color.foreground;
|
||||
// See if we can extend the last range.
|
||||
if let Some(last) = token_ranges.last_mut() {
|
||||
|
||||
@@ -172,6 +172,7 @@ fn setup_and_process_keys(
|
||||
}
|
||||
|
||||
fn parse_flags(
|
||||
parser: Option<&Parser>,
|
||||
streams: &mut IoStreams,
|
||||
args: Vec<WString>,
|
||||
continuous_mode: &mut bool,
|
||||
@@ -184,7 +185,6 @@ fn parse_flags(
|
||||
wopt(L!("version"), ArgType::NoArgument, 'v'),
|
||||
wopt(L!("verbose"), ArgType::NoArgument, 'V'),
|
||||
];
|
||||
|
||||
let mut shim_args: Vec<&wstr> = args.iter().map(|s| s.as_ref()).collect();
|
||||
let mut w = WGetopter::new(short_opts, long_opts, &mut shim_args);
|
||||
while let Some(opt) = w.next_opt() {
|
||||
@@ -193,7 +193,11 @@ fn parse_flags(
|
||||
*continuous_mode = true;
|
||||
}
|
||||
'h' => {
|
||||
print_help("fish_key_reader");
|
||||
if let Some(parser) = parser {
|
||||
builtin_print_help(parser, streams, L!("fish_key_reader"));
|
||||
} else {
|
||||
print_help("fish_key_reader");
|
||||
}
|
||||
return ControlFlow::Break(Ok(SUCCESS));
|
||||
}
|
||||
'v' => {
|
||||
@@ -239,7 +243,7 @@ fn parse_flags(
|
||||
}
|
||||
|
||||
pub fn fish_key_reader(
|
||||
_parser: &Parser,
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
args: &mut [&wstr],
|
||||
) -> BuiltinResult {
|
||||
@@ -247,11 +251,17 @@ pub fn fish_key_reader(
|
||||
let mut verbose = false;
|
||||
|
||||
let args = args.iter_mut().map(|x| x.to_owned()).collect();
|
||||
if let ControlFlow::Break(s) = parse_flags(streams, args, &mut continuous_mode, &mut verbose) {
|
||||
if let ControlFlow::Break(s) = parse_flags(
|
||||
Some(parser),
|
||||
streams,
|
||||
args,
|
||||
&mut continuous_mode,
|
||||
&mut verbose,
|
||||
) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if streams.stdin_fd < 0 || !isatty(streams.stdin_fd) {
|
||||
if streams.stdin_fd() < 0 || !isatty(streams.stdin_fd()) {
|
||||
streams.err.appendln("Stdin must be attached to a tty.");
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
@@ -261,7 +271,7 @@ pub fn fish_key_reader(
|
||||
continuous_mode,
|
||||
verbose,
|
||||
// Won't be querying, so no timeout value needed.
|
||||
InputEventQueue::new(streams.stdin_fd, None),
|
||||
InputEventQueue::new(streams.stdin_fd(), None),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -279,7 +289,8 @@ fn throwing_main() -> i32 {
|
||||
set_interactive_session(true);
|
||||
topic_monitor_init();
|
||||
threads::init();
|
||||
crate::localization::initialize_gettext();
|
||||
#[cfg(feature = "localize-messages")]
|
||||
crate::localization::initialize_localization();
|
||||
env_init(None, true, false);
|
||||
reader_init(false);
|
||||
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
||||
@@ -300,7 +311,7 @@ fn throwing_main() -> i32 {
|
||||
.map(|osstr| bytes2wcstring(osstr.as_bytes()))
|
||||
.collect();
|
||||
if let ControlFlow::Break(s) =
|
||||
parse_flags(&mut streams, args, &mut continuous_mode, &mut verbose)
|
||||
parse_flags(None, &mut streams, args, &mut continuous_mode, &mut verbose)
|
||||
{
|
||||
return s.builtin_status_code();
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ fn job_id_for_pid(pid: Pid, parser: &Parser) -> Option<u64> {
|
||||
/// Returns an exit status.
|
||||
fn parse_cmd_opts(
|
||||
opts: &mut FunctionCmdOpts,
|
||||
optind: &mut usize,
|
||||
argv: &mut [&wstr],
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
@@ -83,6 +82,34 @@ fn parse_cmd_opts(
|
||||
let print_hints = false;
|
||||
let mut handling_named_arguments = false;
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
|
||||
|
||||
let mut validate_variable_name =
|
||||
|streams: &mut IoStreams, varname: &wstr, read_only_ok: bool| {
|
||||
if !valid_var_name(varname) {
|
||||
streams.err.append(&varname_error(cmd, varname));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if !read_only_ok && is_read_only(varname) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: variable '%s' is read-only\n",
|
||||
cmd,
|
||||
varname
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
fn add_named_argument(
|
||||
validate_variable_name: &mut impl FnMut(&mut IoStreams, &wstr, bool) -> Result<(), i32>,
|
||||
streams: &mut IoStreams,
|
||||
opts: &mut FunctionCmdOpts,
|
||||
varname: &wstr,
|
||||
) -> Result<(), i32> {
|
||||
validate_variable_name(streams, varname, /*read_only_ok=*/ false)?;
|
||||
opts.named_arguments.push(varname.to_owned());
|
||||
Ok::<(), ErrorCode>(())
|
||||
}
|
||||
|
||||
while let Some(opt) = w.next_opt() {
|
||||
// NON_OPTION_CHAR is returned when we reach a non-permuted non-option.
|
||||
if opt != 'a' && opt != NON_OPTION_CHAR {
|
||||
@@ -91,17 +118,9 @@ fn parse_cmd_opts(
|
||||
match opt {
|
||||
NON_OPTION_CHAR => {
|
||||
// A positional argument we got because we use RETURN_IN_ORDER.
|
||||
let woptarg = w.woptarg.unwrap().to_owned();
|
||||
let woptarg = w.woptarg.unwrap();
|
||||
if handling_named_arguments {
|
||||
if is_read_only(&woptarg) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: variable '%s' is read-only\n",
|
||||
cmd,
|
||||
woptarg
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
opts.named_arguments.push(woptarg);
|
||||
add_named_argument(&mut validate_variable_name, streams, opts, woptarg)?;
|
||||
} else {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: unexpected positional argument",
|
||||
@@ -127,10 +146,7 @@ fn parse_cmd_opts(
|
||||
}
|
||||
'v' => {
|
||||
let name = w.woptarg.unwrap().to_owned();
|
||||
if !valid_var_name(&name) {
|
||||
streams.err.append(&varname_error(cmd, &name));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
validate_variable_name(streams, &name, /*read_only_ok=*/ true)?;
|
||||
opts.events.push(EventDescription::Variable { name });
|
||||
}
|
||||
'e' => {
|
||||
@@ -175,17 +191,13 @@ fn parse_cmd_opts(
|
||||
opts.events.push(e);
|
||||
}
|
||||
'a' => {
|
||||
let name = w.woptarg.unwrap().to_owned();
|
||||
if is_read_only(&name) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: variable '%s' is read-only\n",
|
||||
cmd,
|
||||
name
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
handling_named_arguments = true;
|
||||
opts.named_arguments.push(name);
|
||||
add_named_argument(
|
||||
&mut validate_variable_name,
|
||||
streams,
|
||||
opts,
|
||||
w.woptarg.unwrap(),
|
||||
)?;
|
||||
}
|
||||
'S' => {
|
||||
opts.shadow_scope = false;
|
||||
@@ -195,10 +207,7 @@ fn parse_cmd_opts(
|
||||
}
|
||||
'V' => {
|
||||
let woptarg = w.woptarg.unwrap();
|
||||
if !valid_var_name(woptarg) {
|
||||
streams.err.append(&varname_error(cmd, woptarg));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
validate_variable_name(streams, woptarg, /*read_only_ok=*/ false)?;
|
||||
opts.inherit_vars.push(woptarg.to_owned());
|
||||
}
|
||||
'h' => {
|
||||
@@ -228,7 +237,23 @@ fn parse_cmd_opts(
|
||||
}
|
||||
}
|
||||
|
||||
*optind = w.wopt_index;
|
||||
let optind = w.wopt_index;
|
||||
if argv.len() != optind {
|
||||
if !opts.named_arguments.is_empty() {
|
||||
// Remaining arguments are named arguments.
|
||||
for &arg in argv[optind..].iter() {
|
||||
add_named_argument(&mut validate_variable_name, streams, opts, arg)?;
|
||||
}
|
||||
} else {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: unexpected positional argument",
|
||||
cmd,
|
||||
argv[optind],
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
@@ -287,34 +312,13 @@ pub fn function(
|
||||
let argv = &mut argv[1..];
|
||||
|
||||
let mut opts = FunctionCmdOpts::default();
|
||||
let mut optind = 0;
|
||||
parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams)?;
|
||||
parse_cmd_opts(&mut opts, argv, parser, streams)?;
|
||||
|
||||
if opts.print_help {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
if argv.len() != optind {
|
||||
if !opts.named_arguments.is_empty() {
|
||||
// Remaining arguments are named arguments.
|
||||
for &arg in argv[optind..].iter() {
|
||||
if !valid_var_name(arg) {
|
||||
streams.err.append(&varname_error(cmd, arg));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
opts.named_arguments.push(arg.to_owned());
|
||||
}
|
||||
} else {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: unexpected positional argument",
|
||||
cmd,
|
||||
argv[optind],
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the current filename.
|
||||
let definition_file = parser.libdata().current_filename.clone();
|
||||
|
||||
@@ -331,13 +335,6 @@ pub fn function(
|
||||
})
|
||||
.collect();
|
||||
|
||||
for named in &opts.named_arguments {
|
||||
if !valid_var_name(named) {
|
||||
streams.err.append(&varname_error(cmd, named));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
// We have what we need to actually define the function.
|
||||
let props = function::FunctionProperties {
|
||||
func_node,
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle};
|
||||
use crate::event::{self};
|
||||
use crate::function;
|
||||
use crate::highlight::colorize;
|
||||
use crate::highlight::highlight_shell;
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parse_util::apply_indents;
|
||||
use crate::parse_util::parse_util_compute_indents;
|
||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||
@@ -25,6 +24,7 @@ struct FunctionsCmdOpts<'args> {
|
||||
no_metadata: bool,
|
||||
verbose: bool,
|
||||
handlers: bool,
|
||||
color: ColorEnabled,
|
||||
handlers_type: Option<&'args wstr>,
|
||||
description: Option<&'args wstr>,
|
||||
}
|
||||
@@ -46,6 +46,7 @@ struct FunctionsCmdOpts<'args> {
|
||||
wopt(L!("verbose"), ArgType::NoArgument, 'v'),
|
||||
wopt(L!("handlers"), ArgType::NoArgument, 'H'),
|
||||
wopt(L!("handlers-type"), ArgType::RequiredArgument, 't'),
|
||||
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||
];
|
||||
|
||||
/// Parses options to builtin function, populating opts.
|
||||
@@ -79,6 +80,9 @@ fn parse_cmd_opts<'args>(
|
||||
opts.handlers = true;
|
||||
opts.handlers_type = Some(w.woptarg.unwrap());
|
||||
}
|
||||
COLOR_OPTION_CHAR => {
|
||||
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
@@ -278,7 +282,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
if opts.list || args.is_empty() {
|
||||
let mut names = function::get_names(opts.show_hidden, parser.vars());
|
||||
names.sort();
|
||||
if streams.out_is_terminal() {
|
||||
if opts.color.enabled(streams) {
|
||||
let mut buff = WString::new();
|
||||
let mut first: bool = true;
|
||||
for name in names {
|
||||
@@ -414,12 +418,12 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
def = apply_indents(&def, &parse_util_compute_indents(&def));
|
||||
}
|
||||
|
||||
if streams.out_is_terminal() {
|
||||
let mut colors = vec![];
|
||||
highlight_shell(&def, &mut colors, &parser.context(), false, None);
|
||||
streams
|
||||
.out
|
||||
.append(&bytes2wcstring(&colorize(&def, &colors, parser.vars())));
|
||||
if opts.color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&def,
|
||||
&parser.context(),
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&def);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ struct HistoryCmdOpts {
|
||||
case_sensitive: bool,
|
||||
null_terminate: bool,
|
||||
reverse: bool,
|
||||
color: ColorEnabled,
|
||||
}
|
||||
|
||||
/// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to
|
||||
@@ -82,6 +83,7 @@ struct HistoryCmdOpts {
|
||||
wopt(L!("clear"), ArgType::NoArgument, '\x04'),
|
||||
wopt(L!("merge"), ArgType::NoArgument, '\x05'),
|
||||
wopt(L!("reverse"), ArgType::NoArgument, 'R'),
|
||||
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||
];
|
||||
|
||||
/// Remember the history subcommand and disallow selecting more than one history subcommand.
|
||||
@@ -226,6 +228,9 @@ fn parse_cmd_opts(
|
||||
}
|
||||
w.remaining_text = L!("");
|
||||
}
|
||||
COLOR_OPTION_CHAR => {
|
||||
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected retval from WGetopter");
|
||||
}
|
||||
@@ -278,6 +283,8 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
match opts.hist_cmd {
|
||||
HistCmd::None | HistCmd::Search => {
|
||||
if !history.search(
|
||||
parser,
|
||||
streams,
|
||||
opts.search_type
|
||||
.unwrap_or(history::SearchType::ContainsGlob),
|
||||
args,
|
||||
@@ -287,7 +294,7 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
opts.null_terminate,
|
||||
opts.reverse,
|
||||
&parser.context().cancel_checker,
|
||||
streams,
|
||||
opts.color.enabled(streams),
|
||||
) {
|
||||
status = Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
use crate::util::get_seeded_rng;
|
||||
use crate::wutil;
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::{Rng, RngCore};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
static RNG: Lazy<Mutex<SmallRng>> =
|
||||
Lazy::new(|| Mutex::new(get_seeded_rng(rand::rng().next_u64())));
|
||||
static RNG: LazyLock<Mutex<SmallRng>> =
|
||||
LazyLock::new(|| Mutex::new(get_seeded_rng(rand::rng().next_u64())));
|
||||
|
||||
pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
||||
let cmd = argv[0];
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
use crate::input_common::decode_one_codepoint_utf8;
|
||||
use crate::nix::isatty;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::reader::ReaderConfig;
|
||||
use crate::reader::commandline_set_buffer;
|
||||
use crate::reader::{reader_pop, reader_push, reader_readline, set_shell_modes_temporarily};
|
||||
@@ -42,7 +43,7 @@ pub(crate) enum TokenOutputMode {
|
||||
#[derive(Default)]
|
||||
struct Options {
|
||||
print_help: bool,
|
||||
place: EnvMode,
|
||||
place: ParserEnvSetMode,
|
||||
prompt: Option<WString>,
|
||||
prompt_str: Option<WString>,
|
||||
right_prompt: WString,
|
||||
@@ -63,7 +64,7 @@ struct Options {
|
||||
impl Options {
|
||||
fn new() -> Self {
|
||||
Options {
|
||||
place: EnvMode::USER,
|
||||
place: ParserEnvSetMode::user(EnvMode::empty()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -129,10 +130,10 @@ fn parse_cmd_opts(
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'f' => {
|
||||
opts.place |= EnvMode::FUNCTION;
|
||||
opts.place.mode |= EnvMode::FUNCTION;
|
||||
}
|
||||
'g' => {
|
||||
opts.place |= EnvMode::GLOBAL;
|
||||
opts.place.mode |= EnvMode::GLOBAL;
|
||||
}
|
||||
'h' => {
|
||||
opts.print_help = true;
|
||||
@@ -141,7 +142,7 @@ fn parse_cmd_opts(
|
||||
opts.one_line = true;
|
||||
}
|
||||
'l' => {
|
||||
opts.place |= EnvMode::LOCAL;
|
||||
opts.place.mode |= EnvMode::LOCAL;
|
||||
}
|
||||
'n' => {
|
||||
opts.nchars = match fish_wcstoi(w.woptarg.unwrap()) {
|
||||
@@ -205,13 +206,13 @@ fn parse_cmd_opts(
|
||||
opts.token_mode = Some(new_mode);
|
||||
}
|
||||
'U' => {
|
||||
opts.place |= EnvMode::UNIVERSAL;
|
||||
opts.place.mode |= EnvMode::UNIVERSAL;
|
||||
}
|
||||
'u' => {
|
||||
opts.place |= EnvMode::UNEXPORT;
|
||||
opts.place.mode |= EnvMode::UNEXPORT;
|
||||
}
|
||||
'x' => {
|
||||
opts.place |= EnvMode::EXPORT;
|
||||
opts.place.mode |= EnvMode::EXPORT;
|
||||
}
|
||||
'z' => {
|
||||
opts.split_null = true;
|
||||
@@ -483,7 +484,7 @@ fn validate_read_args(
|
||||
opts.prompt = Some(DEFAULT_READ_PROMPT.to_owned());
|
||||
}
|
||||
|
||||
if opts.place.contains(EnvMode::UNEXPORT) && opts.place.contains(EnvMode::EXPORT) {
|
||||
if opts.place.mode.contains(EnvMode::UNEXPORT) && opts.place.mode.contains(EnvMode::EXPORT) {
|
||||
streams
|
||||
.err
|
||||
.append(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
|
||||
@@ -493,7 +494,8 @@ fn validate_read_args(
|
||||
|
||||
if opts
|
||||
.place
|
||||
.intersection(EnvMode::LOCAL | EnvMode::FUNCTION | EnvMode::GLOBAL | EnvMode::UNIVERSAL)
|
||||
.mode
|
||||
.intersection(EnvMode::ANY_SCOPE)
|
||||
.iter()
|
||||
.count()
|
||||
> 1
|
||||
@@ -602,7 +604,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
validate_read_args(cmd, &mut opts, argv, parser, streams)?;
|
||||
|
||||
// stdin may have been explicitly closed
|
||||
if streams.stdin_fd < 0 {
|
||||
if streams.is_stdin_closed() {
|
||||
streams
|
||||
.err
|
||||
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
||||
@@ -620,12 +622,12 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
let vars_left = |var_ptr: usize| argc - var_ptr;
|
||||
let clear_remaining_vars = |var_ptr: &mut usize| {
|
||||
while vars_left(*var_ptr) != 0 {
|
||||
parser.vars().set_empty(argv[*var_ptr], opts.place);
|
||||
parser.set_empty(argv[*var_ptr], opts.place);
|
||||
*var_ptr += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let stream_stdin_is_a_tty = isatty(streams.stdin_fd);
|
||||
let stream_stdin_is_a_tty = streams.stdin_fd() >= 0 && isatty(streams.stdin_fd());
|
||||
|
||||
// Normally, we either consume a line of input or all available input. But if we are reading a
|
||||
// line at a time, we need a middle ground where we only consume as many lines as we need to
|
||||
@@ -644,7 +646,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
opts.prompt.as_ref().unwrap(),
|
||||
&opts.right_prompt,
|
||||
&opts.commandline,
|
||||
streams.stdin_fd,
|
||||
streams.stdin_fd(),
|
||||
);
|
||||
} else if opts.nchars.is_none() && !stream_stdin_is_a_tty &&
|
||||
// "one_line" is implemented as reading n-times to a new line,
|
||||
@@ -653,7 +655,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
!opts.one_line &&
|
||||
(
|
||||
streams.stdin_is_directly_redirected ||
|
||||
unsafe {libc::lseek(streams.stdin_fd, 0, SEEK_CUR)} != -1)
|
||||
unsafe {libc::lseek(streams.stdin_fd(), 0, SEEK_CUR)} != -1)
|
||||
{
|
||||
// We read in chunks when we either can seek (so we put the bytes back),
|
||||
// or we have the bytes to ourselves (because it's directly redirected).
|
||||
@@ -663,14 +665,18 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
// You don't rewind VHS tapes before throwing them in the trash.
|
||||
// TODO: Do this when nchars is set by seeking back.
|
||||
exit_res = read_in_chunks(
|
||||
streams.stdin_fd,
|
||||
streams.stdin_fd(),
|
||||
&mut buff,
|
||||
opts.split_null,
|
||||
!streams.stdin_is_directly_redirected,
|
||||
);
|
||||
} else {
|
||||
exit_res =
|
||||
read_one_char_at_a_time(streams.stdin_fd, &mut buff, opts.nchars, opts.split_null);
|
||||
exit_res = read_one_char_at_a_time(
|
||||
streams.stdin_fd(),
|
||||
&mut buff,
|
||||
opts.nchars,
|
||||
opts.split_null,
|
||||
);
|
||||
}
|
||||
|
||||
if exit_res.is_err() {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
use crate::history::History;
|
||||
use crate::history::history_session_id;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::{
|
||||
env::{EnvMode, EnvVar, Environment},
|
||||
wutil::wcstoi::wcstoi_partial,
|
||||
@@ -77,8 +78,8 @@ fn default() -> Self {
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn scope(&self) -> EnvMode {
|
||||
let mut scope = EnvMode::USER;
|
||||
fn env_mode(&self) -> EnvMode {
|
||||
let mut scope = EnvMode::empty();
|
||||
for (is_mode, mode) in [
|
||||
(self.local, EnvMode::LOCAL),
|
||||
(self.function, EnvMode::FUNCTION),
|
||||
@@ -367,15 +368,16 @@ fn env_set_reporting_errors(
|
||||
cmd: &wstr,
|
||||
opts: &Options,
|
||||
key: &wstr,
|
||||
scope: EnvMode,
|
||||
mode: EnvMode,
|
||||
list: Vec<WString>,
|
||||
streams: &mut IoStreams,
|
||||
parser: &Parser,
|
||||
) -> EnvStackSetResult {
|
||||
let mode = ParserEnvSetMode::user(mode);
|
||||
let retval = if opts.no_event {
|
||||
parser.set_var(key, scope | EnvMode::USER, list)
|
||||
parser.set_var(key, mode, list)
|
||||
} else {
|
||||
parser.set_var_and_fire(key, scope | EnvMode::USER, list)
|
||||
parser.set_var_and_fire(key, mode, list)
|
||||
};
|
||||
// If this returned OK, the parser already fired the event.
|
||||
handle_env_return(retval, cmd, key, streams);
|
||||
@@ -554,7 +556,7 @@ fn erased_at_indexes(mut input: Vec<WString>, mut indexes: Vec<isize>) -> Vec<WS
|
||||
/// `set --names` flag was used.
|
||||
fn list(opts: &Options, parser: &Parser, streams: &mut IoStreams) -> BuiltinResult {
|
||||
let names_only = opts.list;
|
||||
let mut names = parser.vars().get_names(opts.scope());
|
||||
let mut names = parser.vars().get_names(opts.env_mode());
|
||||
names.sort();
|
||||
|
||||
for key in names {
|
||||
@@ -573,7 +575,7 @@ fn list(opts: &Options, parser: &Parser, streams: &mut IoStreams) -> BuiltinResu
|
||||
}
|
||||
val += &expand_escape_string(history.item_at_index(i).unwrap().str())[..]
|
||||
}
|
||||
} else if let Some(var) = parser.vars().getf_unless_empty(&key, opts.scope()) {
|
||||
} else if let Some(var) = parser.vars().getf_unless_empty(&key, opts.env_mode()) {
|
||||
val = expand_escape_variable(&var);
|
||||
}
|
||||
if !val.is_empty() {
|
||||
@@ -606,7 +608,7 @@ fn query(
|
||||
args: &[&wstr],
|
||||
) -> BuiltinResult {
|
||||
let mut retval = 0;
|
||||
let scope = opts.scope();
|
||||
let mode = opts.env_mode();
|
||||
|
||||
// No variables given, this is an error.
|
||||
// 255 is the maximum return code we allow.
|
||||
@@ -615,7 +617,7 @@ fn query(
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
let Some(split) = split_var_and_indexes(arg, scope, parser.vars(), streams) else {
|
||||
let Some(split) = split_var_and_indexes(arg, mode, parser.vars(), streams) else {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
@@ -708,7 +710,7 @@ fn show(cmd: &wstr, parser: &Parser, streams: &mut IoStreams, args: &[&wstr]) ->
|
||||
let vars = parser.vars();
|
||||
if args.is_empty() {
|
||||
// show all vars
|
||||
let mut names = vars.get_names(EnvMode::USER);
|
||||
let mut names = vars.get_names(EnvMode::empty());
|
||||
names.sort();
|
||||
for name in names {
|
||||
if name == "history" {
|
||||
@@ -776,15 +778,9 @@ fn erase(
|
||||
args: &[&wstr],
|
||||
) -> BuiltinResult {
|
||||
let mut ret = Ok(SUCCESS);
|
||||
let scopes = opts.scope();
|
||||
// `set -e` is allowed to be called with multiple scopes.
|
||||
for bit in (0..).take_while(|bit| 1 << bit <= EnvMode::USER.bits()) {
|
||||
let scope = scopes.intersection(EnvMode::from_bits(1 << bit).unwrap());
|
||||
if scope.bits() == 0 || (scope == EnvMode::USER && scopes != EnvMode::USER) {
|
||||
continue;
|
||||
}
|
||||
let mut erase_with_mode = |mode| {
|
||||
for arg in args {
|
||||
let Some(split) = split_var_and_indexes(arg, scope, parser.vars(), streams) else {
|
||||
let Some(split) = split_var_and_indexes(arg, mode, parser.vars(), streams) else {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
@@ -797,7 +793,7 @@ fn erase(
|
||||
let retval;
|
||||
if split.indexes.is_empty() {
|
||||
// unset the var
|
||||
retval = parser.vars().remove(split.varname, scope);
|
||||
retval = parser.remove_var(split.varname, ParserEnvSetMode::new(mode));
|
||||
// When a non-existent-variable is unset, return NotFound as $status
|
||||
// but do not emit any errors at the console as a compromise between user
|
||||
// friendliness and correctness.
|
||||
@@ -817,7 +813,7 @@ fn erase(
|
||||
cmd,
|
||||
opts,
|
||||
split.varname,
|
||||
scope,
|
||||
mode,
|
||||
result,
|
||||
streams,
|
||||
parser,
|
||||
@@ -830,10 +826,49 @@ fn erase(
|
||||
ret = retval.into();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
// `set -e` is allowed to be called with multiple scopes.
|
||||
let mode = opts.env_mode();
|
||||
let any_scope = EnvMode::ANY_SCOPE;
|
||||
let scopes = mode.intersection(any_scope);
|
||||
if scopes.is_empty() {
|
||||
erase_with_mode(mode)?;
|
||||
} else {
|
||||
// Historical behavior is to go from inner to outer, which may be relevant for scopes that
|
||||
// collide with the function scope (i.e. local and global).
|
||||
assert!(is_subsequence(
|
||||
scopes.iter(),
|
||||
[
|
||||
EnvMode::LOCAL,
|
||||
EnvMode::FUNCTION,
|
||||
EnvMode::GLOBAL,
|
||||
EnvMode::UNIVERSAL
|
||||
]
|
||||
.into_iter()
|
||||
));
|
||||
for scope in scopes.iter() {
|
||||
let other_scopes = any_scope - scope;
|
||||
erase_with_mode(mode - other_scopes)?;
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn is_subsequence<T: Eq>(
|
||||
mut lhs: impl Iterator<Item = T>,
|
||||
mut rhs: impl Iterator<Item = T>,
|
||||
) -> bool {
|
||||
lhs.all(|l| {
|
||||
for r in rhs.by_ref() {
|
||||
if r == l {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a list of new values for the variable `varname`, respecting the `opts`.
|
||||
/// This handles the simple case where there are no indexes.
|
||||
fn new_var_values(
|
||||
@@ -855,7 +890,7 @@ fn new_var_values(
|
||||
// So do not use the given variable: we must re-fetch it.
|
||||
// TODO: this races under concurrent execution.
|
||||
if let Some(existing) = vars.get(varname) {
|
||||
result = existing.as_list().to_owned();
|
||||
existing.as_list().clone_into(&mut result);
|
||||
}
|
||||
|
||||
if opts.prepend {
|
||||
@@ -879,10 +914,12 @@ fn new_var_values_by_index(split: &SplitVar, argv: &[&wstr]) -> Vec<WString> {
|
||||
// Inherit any existing values.
|
||||
// Note unlike the append/prepend case, we start with a variable in the same scope as we are
|
||||
// setting.
|
||||
let mut result = vec![];
|
||||
if let Some(var) = split.var.as_ref() {
|
||||
result = var.as_list().to_owned();
|
||||
}
|
||||
let mut result = split
|
||||
.var
|
||||
.as_ref()
|
||||
.map(EnvVar::as_list)
|
||||
.unwrap_or_default()
|
||||
.to_owned();
|
||||
|
||||
// For each (index, argument) pair, set the element in our `result` to the replacement string.
|
||||
// Extend the list with empty strings as needed. The indexes are 1-based.
|
||||
@@ -916,11 +953,11 @@ fn set_internal(
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
let scope = opts.scope();
|
||||
let mode = opts.env_mode();
|
||||
let var_expr = argv[0];
|
||||
let argv = &argv[1..];
|
||||
|
||||
let Some(split) = split_var_and_indexes(var_expr, scope, parser.vars(), streams) else {
|
||||
let Some(split) = split_var_and_indexes(var_expr, mode, parser.vars(), streams) else {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
@@ -984,7 +1021,7 @@ fn set_internal(
|
||||
|
||||
// Set the value back in the variable stack and fire any events.
|
||||
let retval =
|
||||
env_set_reporting_errors(cmd, opts, split.varname, scope, new_values, streams, parser);
|
||||
env_set_reporting_errors(cmd, opts, split.varname, mode, new_values, streams, parser);
|
||||
|
||||
if retval == EnvStackSetResult::Ok {
|
||||
warn_if_uvar_shadows_global(cmd, opts, split.varname, streams, parser);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::{Named, bytes2wcstring, escape, get_by_sorted_name};
|
||||
use crate::fds::BorrowedFdFile;
|
||||
use crate::io::OutputStream;
|
||||
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
|
||||
use crate::parse_util::parse_util_argument_is_help;
|
||||
@@ -8,10 +9,7 @@
|
||||
use crate::{builtins::*, wutil};
|
||||
use errno::errno;
|
||||
use fish_wchar::L;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::os::fd::FromRawFd;
|
||||
|
||||
pub type BuiltinCmd = fn(&Parser, &mut IoStreams, &mut [&wstr]) -> BuiltinResult;
|
||||
|
||||
@@ -814,25 +812,26 @@ pub fn new(arg: Cow<'args, wstr>, want_newline: bool) -> Self {
|
||||
|
||||
/// A helper type for extracting arguments from either argv or stdin.
|
||||
pub struct Arguments<'args, 'iter> {
|
||||
/// The list of arguments passed to the string builtin.
|
||||
args: &'iter [&'args wstr],
|
||||
/// If using argv, index of the next argument to return.
|
||||
argidx: &'iter mut usize,
|
||||
split_behavior: SplitBehavior,
|
||||
/// Buffer to store what we read with the BufReader
|
||||
/// Is only here to avoid allocating every time
|
||||
buffer: Vec<u8>,
|
||||
/// If not using argv, we read with a buffer
|
||||
reader: Option<BufReader<File>>,
|
||||
source: ArgvSource<'args, 'iter>,
|
||||
}
|
||||
|
||||
impl Drop for Arguments<'_, '_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(r) = self.reader.take() {
|
||||
// we should not close stdin
|
||||
std::mem::forget(r.into_inner());
|
||||
}
|
||||
}
|
||||
/// Either the arguments from argv, or from stdin.
|
||||
enum ArgvSource<'args, 'iter> {
|
||||
/// Read arguments from argv.
|
||||
Args {
|
||||
// The list of arguments passed to the builtin.
|
||||
args: &'iter [&'args wstr],
|
||||
// Index of the next argument to return.
|
||||
argidx: &'iter mut usize,
|
||||
},
|
||||
/// Read arguments from stdin (possibly redirected).
|
||||
Stdin {
|
||||
/// Reused storage for reading.
|
||||
buffer: Vec<u8>,
|
||||
/// The reader to read from.
|
||||
reader: BufReader<BorrowedFdFile>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'args, 'iter> Arguments<'args, 'iter> {
|
||||
@@ -842,20 +841,21 @@ pub fn new(
|
||||
streams: &mut IoStreams,
|
||||
chunk_size: usize,
|
||||
) -> Self {
|
||||
let reader = streams.stdin_is_directly_redirected.then(|| {
|
||||
let stdin_fd = streams.stdin_fd;
|
||||
assert!(stdin_fd >= 0, "should have a valid fd");
|
||||
// safety: this should be a valid fd, and already open
|
||||
let fd = unsafe { File::from_raw_fd(stdin_fd) };
|
||||
BufReader::with_capacity(chunk_size, fd)
|
||||
});
|
||||
|
||||
let source: ArgvSource = if !streams.stdin_is_directly_redirected {
|
||||
ArgvSource::Args { args, argidx }
|
||||
} else {
|
||||
let stdin_file = streams
|
||||
.stdin_file
|
||||
.clone()
|
||||
.expect("should have stdin if redirected");
|
||||
ArgvSource::Stdin {
|
||||
buffer: Vec::new(),
|
||||
reader: BufReader::with_capacity(chunk_size, stdin_file),
|
||||
}
|
||||
};
|
||||
Arguments {
|
||||
args,
|
||||
argidx,
|
||||
split_behavior: SplitBehavior::Newline,
|
||||
buffer: Vec::new(),
|
||||
reader,
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,9 +864,23 @@ pub fn with_split_behavior(mut self, split_behavior: SplitBehavior) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
/// Return the next argument by reading from argv ArgvSource.
|
||||
fn get_arg_argv(&mut self) -> Option<InputValue<'args>> {
|
||||
let ArgvSource::Args { args, argidx } = &mut self.source else {
|
||||
panic!("Not reading from argv")
|
||||
};
|
||||
let arg = args.get(**argidx)?;
|
||||
**argidx += 1;
|
||||
let retval = InputValue::new(Cow::Borrowed(arg), /*want_newline=*/ true);
|
||||
Some(retval)
|
||||
}
|
||||
|
||||
/// Return the next argument by reading from stdin ArgvSource.
|
||||
fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
||||
use SplitBehavior::*;
|
||||
let reader = self.reader.as_mut().unwrap();
|
||||
let ArgvSource::Stdin { reader, buffer } = &mut self.source else {
|
||||
panic!("Not reading from stdin")
|
||||
};
|
||||
|
||||
if self.split_behavior == InferNull {
|
||||
// we must determine if the first `PATH_MAX` bytes contains a null.
|
||||
@@ -882,9 +896,9 @@ fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
||||
|
||||
// NOTE: C++ wrongly commented that read_blocked retries for EAGAIN
|
||||
let num_bytes: usize = match self.split_behavior {
|
||||
Newline => reader.read_until(b'\n', &mut self.buffer),
|
||||
Null => reader.read_until(b'\0', &mut self.buffer),
|
||||
Never => reader.read_to_end(&mut self.buffer),
|
||||
Newline => reader.read_until(b'\n', buffer),
|
||||
Null => reader.read_until(b'\0', buffer),
|
||||
Never => reader.read_to_end(buffer),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.ok()?;
|
||||
@@ -895,7 +909,7 @@ fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
||||
}
|
||||
|
||||
// assert!(num_bytes == self.buffer.len());
|
||||
let (end, want_newline) = match (&self.split_behavior, self.buffer.last()) {
|
||||
let (end, want_newline) = match (&self.split_behavior, buffer.last()) {
|
||||
// remove the newline — consumers do not expect it
|
||||
(Newline, Some(b'\n')) => (num_bytes - 1, true),
|
||||
// we are missing a trailing newline!
|
||||
@@ -909,8 +923,8 @@ fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let parsed = bytes2wcstring(&self.buffer[..end]);
|
||||
self.buffer.clear();
|
||||
let parsed = bytes2wcstring(&buffer[..end]);
|
||||
buffer.clear();
|
||||
|
||||
Some(InputValue::new(Cow::Owned(parsed), want_newline))
|
||||
}
|
||||
@@ -925,19 +939,10 @@ impl<'args> Iterator for Arguments<'args, '_> {
|
||||
type Item = InputValue<'args>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.reader.is_some() {
|
||||
return self.get_arg_stdin();
|
||||
match &mut self.source {
|
||||
ArgvSource::Args { .. } => self.get_arg_argv(),
|
||||
ArgvSource::Stdin { .. } => self.get_arg_stdin(),
|
||||
}
|
||||
|
||||
if *self.argidx >= self.args.len() {
|
||||
return None;
|
||||
}
|
||||
let retval = InputValue::new(
|
||||
Cow::Borrowed(self.args[*self.argidx]),
|
||||
/*want_newline=*/ true,
|
||||
);
|
||||
*self.argidx += 1;
|
||||
Some(retval)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1048,3 +1053,51 @@ pub fn builtin_break_continue(
|
||||
};
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
/// Option character for --color flag
|
||||
pub const COLOR_OPTION_CHAR: char = '\x10';
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ColorEnabled {
|
||||
#[default]
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
impl TryFrom<&wstr> for ColorEnabled {
|
||||
type Error = ();
|
||||
fn try_from(s: &wstr) -> Result<Self, Self::Error> {
|
||||
match s {
|
||||
s if s == "auto" => Ok(ColorEnabled::Auto),
|
||||
s if s == "always" => Ok(ColorEnabled::Always),
|
||||
s if s == "never" => Ok(ColorEnabled::Never),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorEnabled {
|
||||
pub fn enabled(&self, streams: &crate::io::IoStreams) -> bool {
|
||||
match self {
|
||||
ColorEnabled::Always => true,
|
||||
ColorEnabled::Never => false,
|
||||
ColorEnabled::Auto => streams.out_is_terminal(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_from_opt(
|
||||
streams: &mut IoStreams,
|
||||
cmd: &wstr,
|
||||
arg: &wstr,
|
||||
) -> Result<Self, ErrorCode> {
|
||||
Self::try_from(arg).map_err(|()| {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'\n",
|
||||
cmd,
|
||||
arg
|
||||
));
|
||||
STATUS_INVALID_ARGS
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
let optind = opts.optind;
|
||||
|
||||
if argc == optind || args[optind] == "-" {
|
||||
if streams.stdin_fd < 0 {
|
||||
if streams.is_stdin_closed() {
|
||||
streams
|
||||
.err
|
||||
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
// Either a bare `source` which means to implicitly read from stdin or an explicit `-`.
|
||||
if argc == optind && isatty(streams.stdin_fd) {
|
||||
if argc == optind && isatty(streams.stdin_fd()) {
|
||||
// Don't implicitly read from the terminal.
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: missing filename argument or input redirection\n",
|
||||
@@ -52,7 +52,7 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
func_filename = FilenameRef::new(L!("-").to_owned());
|
||||
fd = streams.stdin_fd;
|
||||
fd = streams.stdin_fd();
|
||||
} else {
|
||||
match wopen_cloexec(args[optind], OFlag::O_RDONLY, Mode::empty()) {
|
||||
Ok(file) => {
|
||||
@@ -87,7 +87,7 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
// points to the end of argv. Otherwise we want to skip the file name to get to the args if any.
|
||||
let remaining_args = &args[optind + if argc == optind { 0 } else { 1 }..];
|
||||
let argv_list = remaining_args.iter().map(|&arg| arg.to_owned()).collect();
|
||||
parser.vars().set_argv(argv_list);
|
||||
parser.vars().set_argv(argv_list, parser.is_repainting());
|
||||
|
||||
let retval = reader_read(parser, fd, streams.io_chain);
|
||||
|
||||
|
||||
@@ -202,10 +202,6 @@ fn parse_cmd_opts(
|
||||
streams: &mut IoStreams,
|
||||
) -> BuiltinResult {
|
||||
let cmd = args[0];
|
||||
|
||||
let mut args_read = Vec::with_capacity(args.len());
|
||||
args_read.extend_from_slice(args);
|
||||
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, args);
|
||||
while let Some(c) = w.next_opt() {
|
||||
match c {
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use super::*;
|
||||
use crate::env::{EnvMode, EnvVar, EnvVarFlags};
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::flog::flog;
|
||||
use crate::parse_util::parse_util_unescape_wildcards;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::wildcard::{ANY_STRING, wildcard_match};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -145,9 +146,8 @@ fn handle(
|
||||
..
|
||||
}) = matcher
|
||||
{
|
||||
let vars = parser.vars();
|
||||
for (name, vals) in first_match_captures.into_iter() {
|
||||
vars.set(&WString::from(name), EnvMode::default(), vals);
|
||||
parser.set_var(&WString::from(name), ParserEnvSetMode::default(), vals);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ fn populate_captures_from_match<'a>(
|
||||
// empty/null members so we're going to have to use an empty string as the
|
||||
// sentinel value.
|
||||
|
||||
if let Some(m) = cg.as_ref().and_then(|cg| cg.name(&name.to_string())) {
|
||||
if let Some(m) = cg.as_ref().and_then(|cg| cg.name(&name.clone())) {
|
||||
captures.push(WString::from(m.as_bytes()));
|
||||
} else if opts.all {
|
||||
captures.push(WString::new());
|
||||
|
||||
@@ -10,9 +10,9 @@ mod test_expressions {
|
||||
Error, Options, file_id_for_path, fish_wcswidth, lwstat, waccess, wcstod::wcstod,
|
||||
wcstoi_opts, wstat,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) enum Token {
|
||||
@@ -164,7 +164,7 @@ fn isatty(&self, streams: &mut IoStreams) -> bool {
|
||||
}
|
||||
let bint = self.base as i32;
|
||||
if bint == 0 {
|
||||
match streams.stdin_fd {
|
||||
match streams.stdin_fd() {
|
||||
-1 => false,
|
||||
fd => isatty(fd),
|
||||
}
|
||||
@@ -182,7 +182,7 @@ fn token_for_string(str: &wstr) -> Token {
|
||||
TOKEN_INFOS.get(str).copied().unwrap_or(Token::Unknown)
|
||||
}
|
||||
|
||||
static TOKEN_INFOS: Lazy<HashMap<&'static wstr, Token>> = Lazy::new(|| {
|
||||
static TOKEN_INFOS: LazyLock<HashMap<&'static wstr, Token>> = LazyLock::new(|| {
|
||||
let pairs = [
|
||||
(L!(""), Token::Unknown),
|
||||
(L!("!"), Token::UnaryBoolean(UnaryBooleanToken::Bang)),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::function;
|
||||
use crate::highlight::{colorize, highlight_shell};
|
||||
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parse_util::{apply_indents, parse_util_compute_indents};
|
||||
use crate::path::{path_get_path, path_get_paths};
|
||||
|
||||
@@ -15,6 +14,7 @@ struct type_cmd_opts_t {
|
||||
path: bool,
|
||||
force_path: bool,
|
||||
query: bool,
|
||||
color: ColorEnabled,
|
||||
}
|
||||
|
||||
pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
||||
@@ -34,6 +34,7 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
|
||||
wopt(L!("force-path"), ArgType::NoArgument, 'P'),
|
||||
wopt(L!("query"), ArgType::NoArgument, 'q'),
|
||||
wopt(L!("quiet"), ArgType::NoArgument, 'q'),
|
||||
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||
];
|
||||
|
||||
let mut w = WGetopter::new(shortopts, longopts, argv);
|
||||
@@ -68,6 +69,9 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
COLOR_OPTION_CHAR => {
|
||||
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected retval from wgeopter.next()");
|
||||
}
|
||||
@@ -143,17 +147,12 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
|
||||
def = apply_indents(&def, &parse_util_compute_indents(&def));
|
||||
}
|
||||
|
||||
if streams.out_is_terminal() {
|
||||
let mut color = vec![];
|
||||
highlight_shell(
|
||||
if opts.color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&def,
|
||||
&mut color,
|
||||
&parser.context(),
|
||||
/*io_ok=*/ false,
|
||||
/*cursor=*/ None,
|
||||
);
|
||||
let col = bytes2wcstring(&colorize(&def, &color, parser.vars()));
|
||||
streams.out.append(&col);
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&def);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::{cmp::Ordering, sync::LazyLock};
|
||||
|
||||
use libc::{RLIM_INFINITY, c_uint, rlim_t};
|
||||
use nix::errno::Errno;
|
||||
use nix::sys::resource::Resource as ResourceEnum;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::wutil::perror;
|
||||
use fish_fallback::{fish_wcswidth, wcscasecmp};
|
||||
@@ -434,7 +433,7 @@ fn new(
|
||||
}
|
||||
|
||||
/// Array of resource_t structs, describing all known resource types.
|
||||
static RESOURCE_ARR: Lazy<Box<[Resource]>> = Lazy::new(|| {
|
||||
static RESOURCE_ARR: LazyLock<Box<[Resource]>> = LazyLock::new(|| {
|
||||
let resources_info = [
|
||||
(
|
||||
limits::SBSIZE,
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
use fish_fallback::fish_wcwidth;
|
||||
use fish_wchar::{decode_byte_from_char, encode_byte_to_char};
|
||||
use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::env;
|
||||
use std::ffi::{CStr, CString, OsString};
|
||||
use std::io::Read;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::unix::prelude::*;
|
||||
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, MutexGuard};
|
||||
use std::sync::{Arc, MutexGuard, OnceLock};
|
||||
use std::time;
|
||||
|
||||
pub const BUILD_DIR: &str = env!("FISH_RESOLVED_BUILD_DIR");
|
||||
@@ -1030,10 +1030,6 @@ pub fn get_omitted_newline_str() -> &'static str {
|
||||
|
||||
static OMITTED_NEWLINE_STR: AtomicRef<str> = AtomicRef::new(&"");
|
||||
|
||||
pub fn get_omitted_newline_width() -> usize {
|
||||
OMITTED_NEWLINE_STR.load().len()
|
||||
}
|
||||
|
||||
static OBFUSCATION_READ_CHAR: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub fn get_obfuscation_read_char() -> char {
|
||||
@@ -1044,7 +1040,7 @@ pub fn get_obfuscation_read_char() -> char {
|
||||
pub static PROFILING_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
/// Name of the current program. Should be set at startup. Used by the debug function.
|
||||
pub static PROGRAM_NAME: OnceCell<&'static wstr> = OnceCell::new();
|
||||
pub static PROGRAM_NAME: OnceLock<&'static wstr> = OnceLock::new();
|
||||
|
||||
pub fn get_program_name() -> &'static wstr {
|
||||
PROGRAM_NAME.get().unwrap()
|
||||
@@ -1237,6 +1233,23 @@ pub fn read_blocked(fd: RawFd, buf: &mut [u8]) -> nix::Result<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadExt {
|
||||
/// Like [`std::io::Read::read_to_end`], but does not retry on EINTR.
|
||||
fn read_to_end_interruptible(&mut self, buf: &mut Vec<u8>) -> std::io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Read + ?Sized> ReadExt for T {
|
||||
fn read_to_end_interruptible(&mut self, buf: &mut Vec<u8>) -> std::io::Result<()> {
|
||||
let mut chunk = [0_u8; 4096];
|
||||
loop {
|
||||
match self.read(&mut chunk)? {
|
||||
0 => return Ok(()),
|
||||
n => buf.extend_from_slice(&chunk[..n]),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if the string is a valid function name.
|
||||
pub fn valid_func_name(name: &wstr) -> bool {
|
||||
!(name.is_empty()
|
||||
@@ -1951,7 +1964,7 @@ macro_rules! env_stack_set_from_env {
|
||||
if let Some(var) = std::env::var_os($var_name) {
|
||||
$vars.set_one(
|
||||
L!($var_name),
|
||||
$crate::env::EnvMode::GLOBAL,
|
||||
$crate::env::EnvSetMode::new_at_early_startup($crate::env::EnvMode::GLOBAL),
|
||||
$crate::common::bytes2wcstring(var.as_bytes()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{
|
||||
Mutex, MutexGuard,
|
||||
LazyLock, Mutex, MutexGuard,
|
||||
atomic::{self, AtomicUsize},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
@@ -32,7 +32,7 @@
|
||||
parse_util::{
|
||||
parse_util_cmdsubst_extent, parse_util_process_extent, parse_util_unescape_wildcards,
|
||||
},
|
||||
parser::{Block, Parser},
|
||||
parser::{Block, Parser, ParserEnvSetMode},
|
||||
parser_keywords::parser_keywords_is_subcommand,
|
||||
path::{path_get_path, path_try_get_path},
|
||||
prelude::*,
|
||||
@@ -55,7 +55,6 @@
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use fish_wchar::WExt;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
// Completion description strings, mostly for different types of files, such as sockets, block
|
||||
// devices, etc.
|
||||
@@ -207,7 +206,7 @@ pub fn rank(&self) -> u32 {
|
||||
|
||||
/// If this completion replaces the entire token, prepend a prefix. Otherwise do nothing.
|
||||
pub fn prepend_token_prefix(&mut self, prefix: &wstr) {
|
||||
if self.flags.contains(CompleteFlags::REPLACES_TOKEN) {
|
||||
if self.replaces_token() {
|
||||
self.completion.insert_utfstr(0, prefix)
|
||||
}
|
||||
}
|
||||
@@ -401,7 +400,7 @@ pub fn expected_dash_count(&self) -> usize {
|
||||
}
|
||||
|
||||
/// Last value used in the order field of [`CompletionEntry`].
|
||||
static complete_order: AtomicUsize = AtomicUsize::new(0);
|
||||
static COMPLETE_ORDER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
struct CompletionEntry {
|
||||
/// List of all options.
|
||||
@@ -415,7 +414,7 @@ impl CompletionEntry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
options: vec![],
|
||||
order: complete_order.fetch_add(1, atomic::Ordering::Relaxed),
|
||||
order: COMPLETE_ORDER.fetch_add(1, atomic::Ordering::Relaxed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +450,7 @@ struct CompletionEntryIndex {
|
||||
|
||||
/// Completion "wrapper" support. The map goes from wrapping-command to wrapped-command-list.
|
||||
type WrapperMap = HashMap<WString, Vec<WString>>;
|
||||
static WRAPPER_MAP: Lazy<Mutex<WrapperMap>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static WRAPPER_MAP: LazyLock<Mutex<WrapperMap>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
/// Clear the [`CompleteFlags::AUTO_SPACE`] flag, and set [`CompleteFlags::NO_SPACE`] appropriately
|
||||
/// depending on the suffix of the string.
|
||||
@@ -606,8 +605,8 @@ struct Completer<'ctx> {
|
||||
condition_cache: HashMap<WString, bool>,
|
||||
}
|
||||
|
||||
static completion_autoloader: Lazy<Mutex<Autoload>> =
|
||||
Lazy::new(|| Mutex::new(Autoload::new(L!("fish_complete_path"))));
|
||||
static COMPLETION_AUTOLOADER: LazyLock<Mutex<Autoload>> =
|
||||
LazyLock::new(|| Mutex::new(Autoload::new(L!("fish_complete_path"))));
|
||||
|
||||
impl<'ctx> Completer<'ctx> {
|
||||
pub fn new(ctx: &'ctx OperationContext<'ctx>, flags: CompletionRequestOptions) -> Self {
|
||||
@@ -1257,7 +1256,7 @@ fn complete_param_for_command(
|
||||
flog!(complete, "Skipping completions for non-existent command");
|
||||
} else if let Some(parser) = self.ctx.maybe_parser() {
|
||||
complete_load(&cmd, parser);
|
||||
} else if !completion_autoloader
|
||||
} else if !COMPLETION_AUTOLOADER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.has_attempted_autoload(&cmd)
|
||||
@@ -1793,7 +1792,7 @@ fn try_complete_user(&mut self, s: &wstr) -> bool {
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
static s_setpwent_lock: Mutex<()> = Mutex::new(());
|
||||
static SETPWENT_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
if s.char_at(0) != '~' || s.contains('/') {
|
||||
return false;
|
||||
@@ -1817,7 +1816,7 @@ fn getpwent_name() -> Option<WString> {
|
||||
Some(charptr2wcstring(pw.pw_name))
|
||||
}
|
||||
|
||||
let _guard = s_setpwent_lock.lock().unwrap();
|
||||
let _guard = SETPWENT_LOCK.lock().unwrap();
|
||||
|
||||
unsafe { libc::setpwent() };
|
||||
while let Some(pw_name) = getpwent_name() {
|
||||
@@ -1914,9 +1913,11 @@ fn apply_var_assignments<T: AsRef<wstr>>(
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
parser
|
||||
.vars()
|
||||
.set(variable_name, EnvMode::LOCAL | EnvMode::EXPORT, vals);
|
||||
parser.set_var(
|
||||
variable_name,
|
||||
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||
vals,
|
||||
);
|
||||
if self.ctx.check_cancel() {
|
||||
break;
|
||||
}
|
||||
@@ -2091,7 +2092,7 @@ fn escape_opening_brackets(completions: &mut [Completion], argument: &wstr) {
|
||||
return;
|
||||
};
|
||||
for comp in completions {
|
||||
if comp.flags.contains(CompleteFlags::REPLACES_TOKEN) {
|
||||
if comp.replaces_token() {
|
||||
continue;
|
||||
}
|
||||
comp.flags |= CompleteFlags::REPLACES_TOKEN;
|
||||
@@ -2126,7 +2127,7 @@ fn mark_completions_duplicating_arguments(
|
||||
let mut comp_str;
|
||||
for comp in self.completions.get_list_mut() {
|
||||
comp_str = comp.completion.clone();
|
||||
if !comp.flags.contains(CompleteFlags::REPLACES_TOKEN) {
|
||||
if !comp.replaces_token() {
|
||||
comp_str.insert_utfstr(0, prefix);
|
||||
}
|
||||
if arg_strs.binary_search(&comp_str).is_ok() {
|
||||
@@ -2479,14 +2480,14 @@ pub fn complete_load(cmd: &wstr, parser: &Parser) -> bool {
|
||||
// We need to take the lock to decide what to load, drop it to perform the load, then reacquire
|
||||
// it.
|
||||
// Note we only look at the global fish_function_path and fish_complete_path.
|
||||
let path_to_load = completion_autoloader
|
||||
let path_to_load = COMPLETION_AUTOLOADER
|
||||
.lock()
|
||||
.expect("mutex poisoned")
|
||||
.resolve_command(cmd, EnvStack::globals());
|
||||
match path_to_load {
|
||||
AutoloadResult::Path(path_to_load) => {
|
||||
Autoload::perform_autoload(&path_to_load, parser);
|
||||
completion_autoloader
|
||||
COMPLETION_AUTOLOADER
|
||||
.lock()
|
||||
.expect("mutex poisoned")
|
||||
.mark_autoload_finished(cmd);
|
||||
@@ -2547,7 +2548,7 @@ pub fn complete_invalidate_path() {
|
||||
// unload any completions that the user may specified on the command line. We should in
|
||||
// principle track those completions loaded by the autoloader alone.
|
||||
|
||||
let cmds = completion_autoloader
|
||||
let cmds = COMPLETION_AUTOLOADER
|
||||
.lock()
|
||||
.expect("mutex poisoned")
|
||||
.get_autoloaded_commands();
|
||||
@@ -2630,11 +2631,12 @@ mod tests {
|
||||
sort_and_prioritize,
|
||||
};
|
||||
use crate::abbrs::{self, Abbreviation, with_abbrs_mut};
|
||||
use crate::env::{EnvMode, Environment};
|
||||
use crate::env::{EnvMode, EnvSetMode, Environment};
|
||||
use crate::io::IoChain;
|
||||
use crate::operation_context::{
|
||||
EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT, OperationContext, no_cancel,
|
||||
};
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::prelude::*;
|
||||
use crate::reader::completion_apply_to_command_line;
|
||||
use crate::tests::prelude::*;
|
||||
@@ -2997,7 +2999,7 @@ macro_rules! unique_completion_applies_as {
|
||||
let completions = do_complete(L!("cat te"), CompletionRequestOptions::default());
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].completion, L!("stfile"));
|
||||
assert!(!(completions[0].flags.contains(CompleteFlags::REPLACES_TOKEN)));
|
||||
assert!(!completions[0].replaces_token());
|
||||
assert!(
|
||||
!(completions[0]
|
||||
.flags
|
||||
@@ -3014,7 +3016,7 @@ macro_rules! unique_completion_applies_as {
|
||||
let completions = do_complete(L!("cat testfile TE"), CompletionRequestOptions::default());
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].completion, L!("testfile"));
|
||||
assert!(completions[0].flags.contains(CompleteFlags::REPLACES_TOKEN));
|
||||
assert!(completions[0].replaces_token());
|
||||
assert!(
|
||||
completions[0]
|
||||
.flags
|
||||
@@ -3221,9 +3223,9 @@ macro_rules! perform_one_completion_cd_test {
|
||||
// This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia`
|
||||
// test below.
|
||||
// Fake out the home directory
|
||||
parser.vars().set_one(
|
||||
parser.set_one(
|
||||
L!("HOME"),
|
||||
EnvMode::LOCAL | EnvMode::EXPORT,
|
||||
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||
L!("test/test-home").to_owned(),
|
||||
);
|
||||
std::fs::create_dir_all("test/test-home/test_autosuggest_suggest_special/").unwrap();
|
||||
@@ -3333,9 +3335,10 @@ macro_rules! perform_one_completion_cd_test {
|
||||
perform_one_completion_cd_test!("cd ~absolutelynosuchus", "er/");
|
||||
perform_one_completion_cd_test!("cd ~absolutelynosuchuser/", "path1/");
|
||||
|
||||
parser
|
||||
.vars()
|
||||
.remove(L!("HOME"), EnvMode::LOCAL | EnvMode::EXPORT);
|
||||
parser.vars().remove(
|
||||
L!("HOME"),
|
||||
EnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT, false),
|
||||
);
|
||||
parser.popd();
|
||||
}
|
||||
|
||||
|
||||
4
src/env/config_paths.rs
vendored
4
src/env/config_paths.rs
vendored
@@ -1,10 +1,10 @@
|
||||
use crate::common::{BUILD_DIR, get_program_name};
|
||||
use crate::{flog, flogf};
|
||||
use fish_build_helper::workspace_root;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// A struct of configuration directories, determined in main() that fish will optionally pass to
|
||||
/// env_init.
|
||||
@@ -163,7 +163,7 @@ pub enum FishPath {
|
||||
LookUpInPath,
|
||||
}
|
||||
|
||||
static FISH_PATH: OnceCell<FishPath> = OnceCell::new();
|
||||
static FISH_PATH: OnceLock<FishPath> = OnceLock::new();
|
||||
|
||||
/// Get the absolute path to the fish executable itself
|
||||
pub fn get_fish_path() -> &'static FishPath {
|
||||
|
||||
262
src/env/environment.rs
vendored
262
src/env/environment.rs
vendored
@@ -7,8 +7,8 @@
|
||||
use crate::builtins::shared::{BuiltinResult, SUCCESS};
|
||||
use crate::common::{UnescapeStringStyle, bytes2wcstring, unescape_string, wcs2zstring};
|
||||
use crate::env::config_paths::ConfigPaths;
|
||||
use crate::env::{EnvMode, EnvVar, Statuses};
|
||||
use crate::env_dispatch::{env_dispatch_init, env_dispatch_var_change};
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvVar, Statuses};
|
||||
use crate::env_dispatch::{VarChangeMilieu, env_dispatch_init, env_dispatch_var_change};
|
||||
use crate::event::Event;
|
||||
use crate::flog::flog;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
@@ -28,13 +28,12 @@
|
||||
use crate::wutil::{fish_wcstol, wgetcwd};
|
||||
|
||||
use libc::{c_int, uid_t};
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock, OnceLock};
|
||||
|
||||
/// Set when a universal variable has been modified but not yet been written to disk via sync().
|
||||
static UVARS_LOCALLY_MODIFIED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
@@ -212,7 +211,7 @@ pub fn set_last_statuses(&self, statuses: Statuses) {
|
||||
}
|
||||
|
||||
/// Sets the variable with the specified name to the given values.
|
||||
pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStackSetResult {
|
||||
pub fn set(&self, key: &wstr, mode: EnvSetMode, mut vals: Vec<WString>) -> EnvStackSetResult {
|
||||
// Historical behavior.
|
||||
if vals.len() == 1 && (key == "PWD" || key == "HOME") {
|
||||
path_make_canonical(vals.first_mut().unwrap());
|
||||
@@ -238,7 +237,14 @@ pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStack
|
||||
// Dispatch changes if we modified the global state or have 'dispatches_var_changes' set.
|
||||
// Important to not hold the lock here.
|
||||
if ret.global_modified || self.dispatches_var_changes {
|
||||
env_dispatch_var_change(key, self);
|
||||
env_dispatch_var_change(
|
||||
VarChangeMilieu {
|
||||
is_repainting: mode.is_repainting,
|
||||
global_or_universal: ret.global_modified || ret.uvar_modified,
|
||||
},
|
||||
key,
|
||||
self,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Mark if we modified a uvar.
|
||||
@@ -249,12 +255,12 @@ pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStack
|
||||
}
|
||||
|
||||
/// Sets the variable with the specified name to a single value.
|
||||
pub fn set_one(&self, key: &wstr, mode: EnvMode, val: WString) -> EnvStackSetResult {
|
||||
pub fn set_one(&self, key: &wstr, mode: EnvSetMode, val: WString) -> EnvStackSetResult {
|
||||
self.set(key, mode, vec![val])
|
||||
}
|
||||
|
||||
/// Sets the variable with the specified name to no values.
|
||||
pub fn set_empty(&self, key: &wstr, mode: EnvMode) -> EnvStackSetResult {
|
||||
pub fn set_empty(&self, key: &wstr, mode: EnvSetMode) -> EnvStackSetResult {
|
||||
self.set(key, mode, Vec::new())
|
||||
}
|
||||
|
||||
@@ -269,24 +275,32 @@ pub fn set_pwd_from_getcwd(&self) {
|
||||
)
|
||||
);
|
||||
}
|
||||
self.set_one(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, cwd);
|
||||
let global_exported_mode =
|
||||
EnvSetMode::new_at_early_startup(EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
self.set_one(L!("PWD"), global_exported_mode, cwd);
|
||||
}
|
||||
|
||||
/// Remove environment variable.
|
||||
///
|
||||
/// \param key The name of the variable to remove
|
||||
/// \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If
|
||||
/// this is a user request, read-only variables can not be removed. The mode may also specify
|
||||
/// the scope of the variable that should be erased.
|
||||
/// \param mode If this is a user request, read-only variables can not be removed. The mode
|
||||
/// may also specify the scope of the variable that should be erased.
|
||||
///
|
||||
/// Return the set result.
|
||||
pub fn remove(&self, key: &wstr, mode: EnvMode) -> EnvStackSetResult {
|
||||
pub fn remove(&self, key: &wstr, mode: EnvSetMode) -> EnvStackSetResult {
|
||||
let ret = self.lock().remove(key, mode);
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if ret.status == EnvStackSetResult::Ok {
|
||||
if ret.global_modified || self.dispatches_var_changes {
|
||||
// Important to not hold the lock here.
|
||||
env_dispatch_var_change(key, self);
|
||||
env_dispatch_var_change(
|
||||
VarChangeMilieu {
|
||||
is_repainting: mode.is_repainting,
|
||||
global_or_universal: ret.global_modified || ret.uvar_modified,
|
||||
},
|
||||
key,
|
||||
self,
|
||||
);
|
||||
}
|
||||
}
|
||||
if ret.uvar_modified {
|
||||
@@ -307,14 +321,21 @@ pub fn push(&self, new_scope: bool) {
|
||||
}
|
||||
|
||||
/// Pop the variable stack. Used for implementing local variables for functions and for-loops.
|
||||
pub fn pop(&self) {
|
||||
pub fn pop(&self, is_repainting: bool) {
|
||||
assert!(self.can_push_pop, "push/pop not allowed on global stack");
|
||||
let popped = self.lock().pop();
|
||||
if self.dispatches_var_changes {
|
||||
// TODO: we would like to coalesce locale changes, so that we only re-initialize
|
||||
// once.
|
||||
for key in popped {
|
||||
env_dispatch_var_change(&key, self);
|
||||
env_dispatch_var_change(
|
||||
VarChangeMilieu {
|
||||
is_repainting,
|
||||
global_or_universal: false,
|
||||
},
|
||||
&key,
|
||||
self,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +356,7 @@ pub fn snapshot(&self) -> EnvDyn {
|
||||
/// If `always` is set, perform synchronization even if there's no pending changes from this
|
||||
/// instance (that is, look for changes from other fish instances).
|
||||
/// Return a list of events for changed variables.
|
||||
pub fn universal_sync(&self, always: bool) -> Vec<Event> {
|
||||
pub fn universal_sync(&self, always: bool, is_repainting: bool) -> Vec<Event> {
|
||||
if UVAR_SCOPE_IS_GLOBAL.load() {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -353,7 +374,14 @@ pub fn universal_sync(&self, always: bool) -> Vec<Event> {
|
||||
if let Some(callbacks) = callbacks {
|
||||
for callback in callbacks {
|
||||
let name = callback.key;
|
||||
env_dispatch_var_change(&name, self);
|
||||
env_dispatch_var_change(
|
||||
VarChangeMilieu {
|
||||
is_repainting,
|
||||
global_or_universal: true,
|
||||
},
|
||||
&name,
|
||||
self,
|
||||
);
|
||||
let evt = if callback.val.is_none() {
|
||||
Event::variable_erase(name)
|
||||
} else {
|
||||
@@ -378,8 +406,12 @@ pub fn globals() -> &'static EnvStack {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_argv(&self, argv: Vec<WString>) {
|
||||
self.set(L!("argv"), EnvMode::LOCAL, argv);
|
||||
pub fn set_argv(&self, argv: Vec<WString>, is_repainting: bool) {
|
||||
self.set(
|
||||
L!("argv"),
|
||||
EnvSetMode::new(EnvMode::LOCAL, is_repainting),
|
||||
argv,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +510,7 @@ pub fn get_home() -> Option<String> {
|
||||
}
|
||||
|
||||
/// Set up the USER and HOME variable.
|
||||
fn setup_user(vars: &EnvStack) {
|
||||
fn setup_user(global_exported_mode: EnvSetMode, vars: &EnvStack) {
|
||||
let uid: uid_t = geteuid();
|
||||
let user_var = vars.get_unless_empty(L!("USER"));
|
||||
|
||||
@@ -508,11 +540,11 @@ fn setup_user(vars: &EnvStack) {
|
||||
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
|
||||
vars.set_one(
|
||||
L!("HOME"),
|
||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
||||
global_exported_mode,
|
||||
bytes2wcstring(s.to_bytes()),
|
||||
);
|
||||
} else {
|
||||
vars.set_empty(L!("HOME"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
vars.set_empty(L!("HOME"), global_exported_mode);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -535,7 +567,7 @@ fn setup_user(vars: &EnvStack) {
|
||||
let userinfo = unsafe { userinfo.assume_init() };
|
||||
let s = unsafe { CStr::from_ptr(userinfo.pw_name) };
|
||||
let uname = bytes2wcstring(s.to_bytes());
|
||||
vars.set_one(L!("USER"), EnvMode::GLOBAL | EnvMode::EXPORT, uname);
|
||||
vars.set_one(L!("USER"), global_exported_mode, uname);
|
||||
// Only change $HOME if it's empty, so we allow e.g. `HOME=(mktemp -d)`.
|
||||
// This is okay with common `su` and `sudo` because they set $HOME.
|
||||
if vars.get_unless_empty(L!("HOME")).is_none() {
|
||||
@@ -543,22 +575,22 @@ fn setup_user(vars: &EnvStack) {
|
||||
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
|
||||
vars.set_one(
|
||||
L!("HOME"),
|
||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
||||
global_exported_mode,
|
||||
bytes2wcstring(s.to_bytes()),
|
||||
);
|
||||
} else {
|
||||
// We cannot get $HOME. This triggers warnings for history and config.fish already,
|
||||
// so it isn't necessary to warn here as well.
|
||||
vars.set_empty(L!("HOME"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
vars.set_empty(L!("HOME"), global_exported_mode);
|
||||
}
|
||||
}
|
||||
} else if vars.get_unless_empty(L!("HOME")).is_none() {
|
||||
// If $USER is empty as well (which we tried to set above), we can't get $HOME.
|
||||
vars.set_empty(L!("HOME"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
vars.set_empty(L!("HOME"), global_exported_mode);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static FALLBACK_PATH: Lazy<&[WString]> = Lazy::new(|| {
|
||||
pub(crate) static FALLBACK_PATH: LazyLock<&[WString]> = LazyLock::new(|| {
|
||||
// _CS_PATH: colon-separated paths to find POSIX utilities. Same as USER_CS_PATH.
|
||||
let cs_path = libc::_CS_PATH;
|
||||
|
||||
@@ -581,25 +613,24 @@ fn setup_user(vars: &EnvStack) {
|
||||
});
|
||||
|
||||
/// Make sure the PATH variable contains something.
|
||||
fn setup_path() {
|
||||
fn setup_path(global_exported_mode: EnvSetMode) {
|
||||
let vars = EnvStack::globals();
|
||||
let path = vars.get_unless_empty(L!("PATH"));
|
||||
if path.is_none() {
|
||||
vars.set(
|
||||
L!("PATH"),
|
||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
||||
FALLBACK_PATH.to_vec(),
|
||||
);
|
||||
vars.set(L!("PATH"), global_exported_mode, FALLBACK_PATH.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
/// The originally inherited variables and their values.
|
||||
/// This is a simple key->value map and not e.g. cut into paths.
|
||||
pub static INHERITED_VARS: OnceCell<HashMap<WString, WString>> = OnceCell::new();
|
||||
pub static INHERITED_VARS: OnceLock<HashMap<WString, WString>> = OnceLock::new();
|
||||
|
||||
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
|
||||
let vars = EnvStack::globals();
|
||||
|
||||
let global_mode = EnvSetMode::new_at_early_startup(EnvMode::GLOBAL);
|
||||
let global_exported_mode = EnvSetMode::new_at_early_startup(EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
|
||||
let env_iter: Vec<_> = std::env::vars_os()
|
||||
.map(|(k, v)| (bytes2wcstring(k.as_bytes()), bytes2wcstring(v.as_bytes())))
|
||||
.collect();
|
||||
@@ -619,7 +650,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
// a value we previously (due to user error) exported will cause impossibly
|
||||
// difficult to debug PATH problems.
|
||||
if key != "fish_user_paths" {
|
||||
vars.set(&key, EnvMode::EXPORT | EnvMode::GLOBAL, vec![val.clone()]);
|
||||
vars.set(&key, global_exported_mode, vec![val.clone()]);
|
||||
}
|
||||
}
|
||||
inherited_vars.insert(key, val);
|
||||
@@ -631,14 +662,14 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
|
||||
// Set $USER, $HOME and $EUID
|
||||
// This involves going to passwd and stuff.
|
||||
vars.set_one(L!("EUID"), EnvMode::GLOBAL, geteuid().to_wstring());
|
||||
setup_user(vars);
|
||||
vars.set_one(L!("EUID"), global_mode, geteuid().to_wstring());
|
||||
setup_user(global_exported_mode, vars);
|
||||
|
||||
if let Some(paths) = paths {
|
||||
let set_path = |key: &wstr, maybe_path: Option<&PathBuf>| {
|
||||
vars.set(
|
||||
key,
|
||||
EnvMode::GLOBAL,
|
||||
global_mode,
|
||||
maybe_path
|
||||
.map(|path| vec![bytes2wcstring(path.as_os_str().as_bytes())])
|
||||
.unwrap_or_default(),
|
||||
@@ -656,45 +687,49 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
let user_config_dir = path_get_config();
|
||||
vars.set_one(
|
||||
FISH_CONFIG_DIR,
|
||||
EnvMode::GLOBAL,
|
||||
global_mode,
|
||||
user_config_dir.unwrap_or_default(),
|
||||
);
|
||||
|
||||
let user_data_dir = path_get_data();
|
||||
vars.set_one(
|
||||
FISH_USER_DATA_DIR,
|
||||
EnvMode::GLOBAL,
|
||||
global_mode,
|
||||
user_data_dir.unwrap_or_default(),
|
||||
);
|
||||
|
||||
let user_cache_dir = path_get_cache();
|
||||
vars.set_one(
|
||||
FISH_CACHE_DIR,
|
||||
EnvMode::GLOBAL,
|
||||
global_mode,
|
||||
user_cache_dir.unwrap_or_default(),
|
||||
);
|
||||
// Set up a default PATH
|
||||
setup_path();
|
||||
setup_path(global_exported_mode);
|
||||
|
||||
// Set up $IFS - this used to be in share/config.fish, but really breaks if it isn't done.
|
||||
vars.set_one(L!("IFS"), EnvMode::GLOBAL, "\n \t".into());
|
||||
vars.set_one(L!("IFS"), global_mode, "\n \t".into());
|
||||
|
||||
// Ensure this var is present even before an interactive command is run so that if it is used
|
||||
// in a function like `fish_prompt` or `fish_right_prompt` it is defined at the time the first
|
||||
// prompt is written.
|
||||
vars.set_one(L!("CMD_DURATION"), EnvMode::UNEXPORT, "0".into());
|
||||
vars.set_one(
|
||||
L!("CMD_DURATION"),
|
||||
EnvSetMode::new_at_early_startup(EnvMode::UNEXPORT),
|
||||
"0".into(),
|
||||
);
|
||||
|
||||
// Set up the version variable.
|
||||
let version = bytes2wcstring(crate::BUILD_VERSION.as_bytes());
|
||||
vars.set_one(L!("version"), EnvMode::GLOBAL, version.clone());
|
||||
vars.set_one(L!("FISH_VERSION"), EnvMode::GLOBAL, version);
|
||||
vars.set_one(L!("version"), global_mode, version.clone());
|
||||
vars.set_one(L!("FISH_VERSION"), global_mode, version);
|
||||
|
||||
// Set the $fish_pid variable.
|
||||
vars.set_one(L!("fish_pid"), EnvMode::GLOBAL, getpid().to_wstring());
|
||||
vars.set_one(L!("fish_pid"), global_mode, getpid().to_wstring());
|
||||
|
||||
// Set the $hostname variable
|
||||
let hostname: WString = get_hostname_identifier().unwrap_or("fish".into());
|
||||
vars.set_one(L!("hostname"), EnvMode::GLOBAL, hostname);
|
||||
vars.set_one(L!("hostname"), global_mode, hostname);
|
||||
|
||||
// Set up SHLVL variable. Not we can't use vars.get() because SHLVL is read-only, and therefore
|
||||
// was not inherited from the environment.
|
||||
@@ -709,13 +744,13 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
} else {
|
||||
L!("1").to_owned()
|
||||
};
|
||||
vars.set_one(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT, nshlvl_str);
|
||||
vars.set_one(L!("SHLVL"), global_exported_mode, nshlvl_str);
|
||||
} else {
|
||||
// If we're not interactive, simply pass the value along.
|
||||
if let Some(shlvl_var) = std::env::var_os("SHLVL") {
|
||||
vars.set_one(
|
||||
L!("SHLVL"),
|
||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
||||
global_exported_mode,
|
||||
bytes2wcstring(shlvl_var.as_os_str().as_bytes()),
|
||||
);
|
||||
}
|
||||
@@ -736,7 +771,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
&& incoming_pwd.char_at(0) == '/'
|
||||
&& paths_are_same_file(&incoming_pwd, L!("."))
|
||||
{
|
||||
vars.set_one(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, incoming_pwd);
|
||||
vars.set_one(L!("PWD"), global_exported_mode, incoming_pwd);
|
||||
} else {
|
||||
vars.set_pwd_from_getcwd();
|
||||
}
|
||||
@@ -744,18 +779,14 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
// Initialize termsize variables.
|
||||
let termsize = termsize::SHARED_CONTAINER.initialize(vars as &dyn Environment);
|
||||
if vars.get_unless_empty(L!("COLUMNS")).is_none() {
|
||||
vars.set_one(
|
||||
L!("COLUMNS"),
|
||||
EnvMode::GLOBAL,
|
||||
termsize.width().to_wstring(),
|
||||
);
|
||||
vars.set_one(L!("COLUMNS"), global_mode, termsize.width().to_wstring());
|
||||
}
|
||||
if vars.get_unless_empty(L!("LINES")).is_none() {
|
||||
vars.set_one(L!("LINES"), EnvMode::GLOBAL, termsize.height().to_wstring());
|
||||
vars.set_one(L!("LINES"), global_mode, termsize.height().to_wstring());
|
||||
}
|
||||
|
||||
// Set fish_bind_mode to "default".
|
||||
vars.set_one(FISH_BIND_MODE_VAR, EnvMode::GLOBAL, "default".into());
|
||||
vars.set_one(FISH_BIND_MODE_VAR, global_mode, "default".into());
|
||||
|
||||
// Allow changes to variables to produce events.
|
||||
env_dispatch_init(vars);
|
||||
@@ -770,65 +801,74 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
|
||||
if !do_uvars {
|
||||
UVAR_SCOPE_IS_GLOBAL.store(true);
|
||||
} else {
|
||||
// Set up universal variables using the default path.
|
||||
let callbacks = uvars().initialize().unwrap_or_default();
|
||||
for callback in callbacks {
|
||||
env_dispatch_var_change(&callback.key, vars);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not import variables that have the same name and value as
|
||||
// an exported universal variable. See issues #5258 and #5348.
|
||||
let globals_to_skip = {
|
||||
let mut to_skip = vec![];
|
||||
let uvars_locked = uvars();
|
||||
for (name, uvar) in uvars_locked.get_table() {
|
||||
if !uvar.exports() {
|
||||
continue;
|
||||
}
|
||||
// Set up universal variables using the default path.
|
||||
let callbacks = uvars().initialize().unwrap_or_default();
|
||||
for callback in callbacks {
|
||||
env_dispatch_var_change(
|
||||
VarChangeMilieu {
|
||||
is_repainting: false,
|
||||
global_or_universal: true,
|
||||
},
|
||||
&callback.key,
|
||||
vars,
|
||||
);
|
||||
}
|
||||
|
||||
// Look for a global exported variable with the same name.
|
||||
let global = EnvStack::globals().getf(name, EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
if global.is_some_and(|x| x.as_string() == uvar.as_string()) {
|
||||
to_skip.push(name.to_owned());
|
||||
}
|
||||
}
|
||||
to_skip
|
||||
};
|
||||
for name in &globals_to_skip {
|
||||
EnvStack::globals().remove(name, EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
}
|
||||
|
||||
// Import any abbreviations from uvars.
|
||||
// Note we do not dynamically react to changes.
|
||||
let prefix = L!("_fish_abbr_");
|
||||
let prefix_len = prefix.char_count();
|
||||
let from_universal = true;
|
||||
let mut abbrs = abbrs_get_set();
|
||||
// Do not import variables that have the same name and value as
|
||||
// an exported universal variable. See issues #5258 and #5348.
|
||||
let globals_to_skip = {
|
||||
let mut to_skip = vec![];
|
||||
let uvars_locked = uvars();
|
||||
for (name, uvar) in uvars_locked.get_table() {
|
||||
if !name.starts_with(prefix) {
|
||||
if !uvar.exports() {
|
||||
continue;
|
||||
}
|
||||
let escaped_name = name.slice_from(prefix_len);
|
||||
if let Some(name) = unescape_string(escaped_name, UnescapeStringStyle::Var) {
|
||||
let key = name.clone();
|
||||
let replacement: WString = join_strings(uvar.as_list(), ' ');
|
||||
abbrs.add(Abbreviation::new(
|
||||
name,
|
||||
key,
|
||||
replacement,
|
||||
Position::Command,
|
||||
from_universal,
|
||||
));
|
||||
|
||||
// Look for a global exported variable with the same name.
|
||||
let global = EnvStack::globals().getf(name, EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
if global.is_some_and(|x| x.as_string() == uvar.as_string()) {
|
||||
to_skip.push(name.to_owned());
|
||||
}
|
||||
}
|
||||
to_skip
|
||||
};
|
||||
for name in &globals_to_skip {
|
||||
EnvStack::globals().remove(name, global_exported_mode);
|
||||
}
|
||||
|
||||
// Import any abbreviations from uvars.
|
||||
// Note we do not dynamically react to changes.
|
||||
let prefix = L!("_fish_abbr_");
|
||||
let prefix_len = prefix.char_count();
|
||||
let from_universal = true;
|
||||
let mut abbrs = abbrs_get_set();
|
||||
let uvars_locked = uvars();
|
||||
for (name, uvar) in uvars_locked.get_table() {
|
||||
if !name.starts_with(prefix) {
|
||||
continue;
|
||||
}
|
||||
let escaped_name = name.slice_from(prefix_len);
|
||||
if let Some(name) = unescape_string(escaped_name, UnescapeStringStyle::Var) {
|
||||
let key = name.clone();
|
||||
let replacement: WString = join_strings(uvar.as_list(), ' ');
|
||||
abbrs.add(Abbreviation::new(
|
||||
name,
|
||||
key,
|
||||
replacement,
|
||||
Position::Command,
|
||||
from_universal,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{EnvMode, EnvStack, Environment};
|
||||
use crate::env::EnvSetMode;
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
|
||||
@@ -844,19 +884,19 @@ fn test_env_snapshot() {
|
||||
let before_pwd = vars.get(L!("PWD")).unwrap().as_string();
|
||||
vars.set_one(
|
||||
L!("test_env_snapshot_var"),
|
||||
EnvMode::default(),
|
||||
EnvSetMode::default(),
|
||||
L!("before").to_owned(),
|
||||
);
|
||||
let snapshot = vars.snapshot();
|
||||
vars.set_one(L!("PWD"), EnvMode::default(), L!("/newdir").to_owned());
|
||||
vars.set_one(L!("PWD"), EnvSetMode::default(), L!("/newdir").to_owned());
|
||||
vars.set_one(
|
||||
L!("test_env_snapshot_var"),
|
||||
EnvMode::default(),
|
||||
EnvSetMode::default(),
|
||||
L!("after").to_owned(),
|
||||
);
|
||||
vars.set_one(
|
||||
L!("test_env_snapshot_var_2"),
|
||||
EnvMode::default(),
|
||||
EnvSetMode::default(),
|
||||
L!("after").to_owned(),
|
||||
);
|
||||
|
||||
@@ -885,7 +925,7 @@ fn test_env_snapshot() {
|
||||
// snapshots see global var changes except for perproc like PWD
|
||||
vars.set_one(
|
||||
L!("test_env_snapshot_var_3"),
|
||||
EnvMode::GLOBAL,
|
||||
EnvSetMode::new(EnvMode::GLOBAL, false),
|
||||
L!("reallyglobal").to_owned(),
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -900,7 +940,7 @@ fn test_env_snapshot() {
|
||||
L!("reallyglobal")
|
||||
);
|
||||
|
||||
vars.pop();
|
||||
vars.pop(false);
|
||||
parser.popd();
|
||||
}
|
||||
|
||||
@@ -914,6 +954,6 @@ fn test_no_global_push() {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_no_global_pop() {
|
||||
EnvStack::globals().pop();
|
||||
EnvStack::globals().pop(false);
|
||||
}
|
||||
}
|
||||
|
||||
38
src/env/environment_impl.rs
vendored
38
src/env/environment_impl.rs
vendored
@@ -1,6 +1,6 @@
|
||||
use crate::common::wcs2zstring;
|
||||
use crate::env::{
|
||||
ELECTRIC_VARIABLES, ElectricVar, EnvMode, EnvStackSetResult, EnvVar, EnvVarFlags,
|
||||
ELECTRIC_VARIABLES, ElectricVar, EnvMode, EnvSetMode, EnvStackSetResult, EnvVar, EnvVarFlags,
|
||||
PATH_ARRAY_SEP, Statuses, VarTable, is_read_only,
|
||||
};
|
||||
use crate::env_universal_common::EnvUniversal;
|
||||
@@ -15,13 +15,13 @@
|
||||
use crate::threads::{is_forked_child, is_main_thread};
|
||||
use crate::wutil::fish_wcstol_radix;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CString;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[cfg(not(target_has_atomic = "64"))]
|
||||
use portable_atomic::AtomicU64;
|
||||
@@ -116,11 +116,20 @@ struct Query {
|
||||
pub user: bool,
|
||||
}
|
||||
|
||||
impl From<EnvMode> for Query {
|
||||
fn from(mode: EnvMode) -> Self {
|
||||
Self::new(mode, false)
|
||||
}
|
||||
}
|
||||
impl From<EnvSetMode> for Query {
|
||||
fn from(mode: EnvSetMode) -> Self {
|
||||
Self::new(mode.mode, mode.user)
|
||||
}
|
||||
}
|
||||
impl Query {
|
||||
/// Creates a `Query` from env mode flags.
|
||||
fn new(mode: EnvMode) -> Self {
|
||||
let has_scope = mode
|
||||
.intersects(EnvMode::LOCAL | EnvMode::FUNCTION | EnvMode::GLOBAL | EnvMode::UNIVERSAL);
|
||||
fn new(mode: EnvMode, user: bool) -> Self {
|
||||
let has_scope = mode.intersects(EnvMode::ANY_SCOPE);
|
||||
let has_export_unexport = mode.intersects(EnvMode::EXPORT | EnvMode::UNEXPORT);
|
||||
Query {
|
||||
has_scope,
|
||||
@@ -138,7 +147,7 @@ fn new(mode: EnvMode) -> Self {
|
||||
pathvar: mode.contains(EnvMode::PATHVAR),
|
||||
unpathvar: mode.contains(EnvMode::UNPATHVAR),
|
||||
|
||||
user: mode.contains(EnvMode::USER),
|
||||
user,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +294,7 @@ fn next(&mut self) -> Option<EnvNodeRef> {
|
||||
}
|
||||
}
|
||||
|
||||
static GLOBAL_NODE: Lazy<EnvNodeRef> = Lazy::new(|| EnvNodeRef::new(false, None));
|
||||
static GLOBAL_NODE: LazyLock<EnvNodeRef> = LazyLock::new(|| EnvNodeRef::new(false, None));
|
||||
|
||||
/// Recursive helper to snapshot a series of nodes.
|
||||
fn copy_node_chain(node: &EnvNodeRef) -> EnvNodeRef {
|
||||
@@ -459,7 +468,7 @@ fn try_get_universal(&self, key: &wstr) -> Option<EnvVar> {
|
||||
}
|
||||
|
||||
pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
|
||||
let query = Query::new(mode);
|
||||
let query = Query::from(mode);
|
||||
let mut result: Option<EnvVar> = None;
|
||||
// Computed variables are effectively global and can't be shadowed.
|
||||
if query.global {
|
||||
@@ -489,7 +498,7 @@ pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
|
||||
}
|
||||
|
||||
pub fn get_names(&self, flags: EnvMode) -> Vec<WString> {
|
||||
let query = Query::new(flags);
|
||||
let query = Query::from(flags);
|
||||
let mut names: HashSet<WString> = HashSet::new();
|
||||
|
||||
// Helper to add the names of variables from `envs` to names, respecting show_exported and
|
||||
@@ -586,7 +595,7 @@ fn export_array_needs_regeneration(&self) -> bool {
|
||||
let mut cursor = self.export_array_generations.iter().fuse();
|
||||
let mut mismatch = false;
|
||||
self.enumerate_generations(|r#gen| {
|
||||
if cursor.next().cloned() != Some(r#gen) {
|
||||
if cursor.next().copied() != Some(r#gen) {
|
||||
mismatch = true;
|
||||
}
|
||||
});
|
||||
@@ -721,8 +730,8 @@ pub fn new() -> EnvMutex<EnvStackImpl> {
|
||||
}
|
||||
|
||||
/// Set a variable under the name `key`, using the given `mode`, setting its value to `val`.
|
||||
pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModResult {
|
||||
let query = Query::new(mode);
|
||||
pub fn set(&mut self, key: &wstr, mode: EnvSetMode, mut val: Vec<WString>) -> ModResult {
|
||||
let query = Query::from(mode);
|
||||
// Handle electric and read-only variables.
|
||||
if let Some(ret) = self.try_set_electric(key, &query, &mut val) {
|
||||
return ModResult::new(ret);
|
||||
@@ -754,6 +763,7 @@ pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModRe
|
||||
result.uvar_modified = true;
|
||||
} else if query.global || (query.universal && UVAR_SCOPE_IS_GLOBAL.load()) {
|
||||
Self::set_in_node(&mut self.base.globals, key, val, flags);
|
||||
result.global_modified = true;
|
||||
} else if query.local {
|
||||
assert!(
|
||||
!self.base.locals.ptr_eq(&self.base.globals),
|
||||
@@ -802,8 +812,8 @@ pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModRe
|
||||
}
|
||||
|
||||
/// Remove a variable under the name `key`.
|
||||
pub fn remove(&mut self, key: &wstr, mode: EnvMode) -> ModResult {
|
||||
let query = Query::new(mode);
|
||||
pub fn remove(&mut self, key: &wstr, mode: EnvSetMode) -> ModResult {
|
||||
let query = Query::from(mode);
|
||||
// Users can't remove read-only keys.
|
||||
if query.user && is_read_only(key) {
|
||||
return ModResult::new(EnvStackSetResult::Scope);
|
||||
|
||||
48
src/env/var.rs
vendored
48
src/env/var.rs
vendored
@@ -31,20 +31,51 @@ pub struct EnvMode: u16 {
|
||||
const PATHVAR = 1 << 6;
|
||||
/// Flag to unmark a variable as a path variable.
|
||||
const UNPATHVAR = 1 << 7;
|
||||
/// Flag for variable update request from the user. All variable changes that are made directly
|
||||
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
|
||||
/// serves one purpose: to indicate that an error should be returned if the user is attempting
|
||||
/// to modify a var that should not be modified by direct user action; e.g., a read-only var.
|
||||
const USER = 1 << 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvMode {
|
||||
pub const ANY_SCOPE: EnvMode = EnvMode::LOCAL
|
||||
.union(EnvMode::FUNCTION)
|
||||
.union(EnvMode::GLOBAL)
|
||||
.union(EnvMode::UNIVERSAL);
|
||||
}
|
||||
|
||||
impl From<EnvMode> for u16 {
|
||||
fn from(val: EnvMode) -> Self {
|
||||
val.bits()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct EnvSetMode {
|
||||
pub mode: EnvMode,
|
||||
|
||||
/// Flag for variable update request from the user. All variable changes that are made directly
|
||||
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
|
||||
/// serves to indicate that an error should be returned if the user is attempting to modify
|
||||
/// a var that should not be modified by direct user action; e.g., a read-only var.
|
||||
pub user: bool,
|
||||
|
||||
pub is_repainting: bool,
|
||||
}
|
||||
|
||||
impl EnvSetMode {
|
||||
pub fn new(mode: EnvMode, is_repainting: bool) -> Self {
|
||||
Self::new_with(mode, false, is_repainting)
|
||||
}
|
||||
pub fn new_with(mode: EnvMode, user: bool, is_repainting: bool) -> Self {
|
||||
Self {
|
||||
mode,
|
||||
user,
|
||||
is_repainting,
|
||||
}
|
||||
}
|
||||
pub fn new_at_early_startup(mode: EnvMode) -> Self {
|
||||
Self::new_with(mode, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of status and pipestatus.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Statuses {
|
||||
@@ -287,6 +318,7 @@ pub fn is_read_only(name: &wstr) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{EnvMode, EnvVar, EnvVarFlags};
|
||||
use crate::env::EnvSetMode;
|
||||
use crate::env::environment::{EnvStack, Environment};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
@@ -299,7 +331,11 @@ mod tests {
|
||||
fn return_timezone_hour(tstamp: SystemTime, timezone: &wstr) -> libc::c_int {
|
||||
let vars = EnvStack::globals().create_child(true /* dispatches_var_changes */);
|
||||
|
||||
vars.set_one(L!("TZ"), EnvMode::EXPORT, timezone.to_owned());
|
||||
vars.set_one(
|
||||
L!("TZ"),
|
||||
EnvSetMode::new(EnvMode::EXPORT, false),
|
||||
timezone.to_owned(),
|
||||
);
|
||||
|
||||
let _var = vars.get(L!("TZ"));
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{
|
||||
reader_change_cursor_end_mode, reader_change_cursor_selection_mode, reader_change_history,
|
||||
reader_schedule_prompt_repaint, reader_set_autosuggestion_enabled, reader_set_transient_prompt,
|
||||
reader_current_data, reader_schedule_prompt_repaint, reader_set_autosuggestion_enabled,
|
||||
reader_set_transient_prompt,
|
||||
};
|
||||
use crate::screen::{
|
||||
IS_DUMB, LAYOUT_CACHE_SHARED, ONLY_GRAYSCALE, screen_set_midnight_commander_hack,
|
||||
@@ -47,8 +48,14 @@
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
let mut table = VarDispatchTable::default();
|
||||
|
||||
macro_rules! vars {
|
||||
( $f:ident ) => {
|
||||
|vars: &EnvStack, _suppress_repaint: bool| $f(vars)
|
||||
};
|
||||
}
|
||||
|
||||
for name in LOCALE_VARIABLES {
|
||||
table.add_anon(name, handle_locale_change);
|
||||
table.add_anon(name, vars!(handle_locale_change));
|
||||
}
|
||||
|
||||
for name in CURSES_VARIABLES {
|
||||
@@ -59,43 +66,49 @@
|
||||
table.add_anon(L!("COLORTERM"), handle_fish_term_change);
|
||||
table.add_anon(L!("fish_term256"), handle_fish_term_change);
|
||||
table.add_anon(L!("fish_term24bit"), handle_fish_term_change);
|
||||
table.add_anon(L!("fish_escape_delay_ms"), update_wait_on_escape_ms);
|
||||
table.add_anon(L!("fish_escape_delay_ms"), vars!(update_wait_on_escape_ms));
|
||||
table.add_anon(
|
||||
L!("fish_sequence_key_delay_ms"),
|
||||
update_wait_on_sequence_key_ms,
|
||||
vars!(update_wait_on_sequence_key_ms),
|
||||
);
|
||||
table.add_anon(L!("fish_emoji_width"), guess_emoji_width);
|
||||
table.add_anon(L!("fish_ambiguous_width"), handle_change_ambiguous_width);
|
||||
table.add_anon(L!("LINES"), handle_term_size_change);
|
||||
table.add_anon(L!("COLUMNS"), handle_term_size_change);
|
||||
table.add_anon(L!("fish_complete_path"), handle_complete_path_change);
|
||||
table.add_anon(L!("fish_function_path"), handle_function_path_change);
|
||||
table.add_anon(L!("fish_read_limit"), handle_read_limit_change);
|
||||
table.add_anon(L!("fish_history"), handle_fish_history_change);
|
||||
table.add_anon(L!("fish_emoji_width"), vars!(guess_emoji_width));
|
||||
table.add_anon(
|
||||
L!("fish_ambiguous_width"),
|
||||
vars!(handle_change_ambiguous_width),
|
||||
);
|
||||
table.add_anon(L!("LINES"), vars!(handle_term_size_change));
|
||||
table.add_anon(L!("COLUMNS"), vars!(handle_term_size_change));
|
||||
table.add_anon(L!("fish_complete_path"), vars!(handle_complete_path_change));
|
||||
table.add_anon(L!("fish_function_path"), vars!(handle_function_path_change));
|
||||
table.add_anon(L!("fish_read_limit"), vars!(handle_read_limit_change));
|
||||
table.add_anon(L!("fish_history"), vars!(handle_fish_history_change));
|
||||
table.add_anon(
|
||||
L!("fish_autosuggestion_enabled"),
|
||||
handle_autosuggestion_change,
|
||||
vars!(handle_autosuggestion_change),
|
||||
);
|
||||
table.add_anon(
|
||||
L!("fish_transient_prompt"),
|
||||
vars!(handle_transient_prompt_change),
|
||||
);
|
||||
table.add_anon(L!("fish_transient_prompt"), handle_transient_prompt_change);
|
||||
table.add_anon(
|
||||
L!("fish_use_posix_spawn"),
|
||||
handle_fish_use_posix_spawn_change,
|
||||
vars!(handle_fish_use_posix_spawn_change),
|
||||
);
|
||||
table.add_anon(L!("fish_trace"), handle_fish_trace);
|
||||
table.add_anon(L!("fish_trace"), vars!(handle_fish_trace));
|
||||
table.add_anon(
|
||||
L!("fish_cursor_selection_mode"),
|
||||
handle_fish_cursor_selection_mode_change,
|
||||
vars!(handle_fish_cursor_selection_mode_change),
|
||||
);
|
||||
table.add_anon(
|
||||
L!("fish_cursor_end_mode"),
|
||||
handle_fish_cursor_end_mode_change,
|
||||
vars!(handle_fish_cursor_end_mode_change),
|
||||
);
|
||||
|
||||
table
|
||||
});
|
||||
|
||||
type NamedEnvCallback = fn(name: &wstr, env: &EnvStack);
|
||||
type AnonEnvCallback = fn(env: &EnvStack);
|
||||
type AnonEnvCallback = fn(env: &EnvStack, suppress_repaint: bool);
|
||||
|
||||
enum EnvCallback {
|
||||
Named(NamedEnvCallback),
|
||||
@@ -120,10 +133,10 @@ pub fn add_anon(&mut self, name: &'static wstr, callback: AnonEnvCallback) {
|
||||
assert!(prev.is_none(), "Already observing {}", name);
|
||||
}
|
||||
|
||||
pub fn dispatch(&self, key: &wstr, vars: &EnvStack) {
|
||||
pub fn dispatch(&self, key: &wstr, vars: &EnvStack, suppress_repaint: bool) {
|
||||
match self.table.get(key) {
|
||||
Some(EnvCallback::Named(named)) => (named)(key, vars),
|
||||
Some(EnvCallback::Anon(anon)) => (anon)(vars),
|
||||
Some(EnvCallback::Anon(anon)) => (anon)(vars, suppress_repaint),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
@@ -206,26 +219,41 @@ pub fn guess_emoji_width(vars: &EnvStack) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VarChangeMilieu {
|
||||
pub is_repainting: bool,
|
||||
pub global_or_universal: bool,
|
||||
}
|
||||
|
||||
/// React to modifying the given variable.
|
||||
pub fn env_dispatch_var_change(key: &wstr, vars: &EnvStack) {
|
||||
pub fn env_dispatch_var_change(milieu: VarChangeMilieu, key: &wstr, vars: &EnvStack) {
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
let suppress_repaint = milieu.is_repainting || !milieu.global_or_universal;
|
||||
|
||||
// We want to ignore variable changes until the dispatch table is explicitly initialized.
|
||||
// TODO(MSRV>=1.94): Use std::sync::LazyLock. (LazyLock::get is stabilized in Rust 1.94)
|
||||
if let Some(dispatch_table) = Lazy::get(&VAR_DISPATCH_TABLE) {
|
||||
dispatch_table.dispatch(key, vars);
|
||||
dispatch_table.dispatch(key, vars, suppress_repaint);
|
||||
}
|
||||
|
||||
if string_prefixes_string(L!("fish_color_"), key)
|
||||
// TODO Don't re-exec prompt when only pager color changed.
|
||||
|| string_prefixes_string(L!("fish_pager_color_"), key)
|
||||
{
|
||||
reader_schedule_prompt_repaint();
|
||||
// TODO(MSRV>=1.88): if-let
|
||||
if !suppress_repaint {
|
||||
if let Some(data) = reader_current_data() {
|
||||
if string_prefixes_string(L!("fish_color_"), key) || {
|
||||
// TODO Don't re-exec prompt when only pager color changed.
|
||||
string_prefixes_string(L!("fish_pager_color_"), key)
|
||||
} {
|
||||
data.schedule_prompt_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_fish_term_change(vars: &EnvStack) {
|
||||
fn handle_fish_term_change(vars: &EnvStack, suppress_repaint: bool) {
|
||||
update_fish_color_support(vars);
|
||||
reader_schedule_prompt_repaint();
|
||||
if !suppress_repaint {
|
||||
reader_schedule_prompt_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_change_ambiguous_width(vars: &EnvStack) {
|
||||
@@ -307,11 +335,13 @@ fn handle_locale_change(vars: &EnvStack) {
|
||||
init_locale(vars);
|
||||
}
|
||||
|
||||
fn handle_term_change(vars: &EnvStack) {
|
||||
fn handle_term_change(vars: &EnvStack, suppress_repaint: bool) {
|
||||
guess_emoji_width(vars);
|
||||
init_terminal(vars);
|
||||
read_terminfo_database(vars);
|
||||
reader_schedule_prompt_repaint();
|
||||
if !suppress_repaint {
|
||||
reader_schedule_prompt_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_fish_use_posix_spawn_change(vars: &EnvStack) {
|
||||
|
||||
@@ -862,7 +862,7 @@ fn test_universal() {
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for i in 0..threads {
|
||||
let path = test_path.to_owned();
|
||||
let path = test_path.clone();
|
||||
handles.push(std::thread::spawn(move || {
|
||||
test_universal_helper(i, &path);
|
||||
}));
|
||||
@@ -873,7 +873,7 @@ fn test_universal() {
|
||||
}
|
||||
|
||||
let mut uvars = EnvUniversal::new();
|
||||
uvars.initialize_at_path(test_path.to_owned());
|
||||
uvars.initialize_at_path(test_path.clone());
|
||||
|
||||
for i in 0..threads {
|
||||
for j in 0..UVARS_PER_THREAD {
|
||||
@@ -1037,11 +1037,11 @@ fn test_universal_callbacks() {
|
||||
let mut uvars1 = EnvUniversal::new();
|
||||
let mut uvars2 = EnvUniversal::new();
|
||||
let mut callbacks = uvars1
|
||||
.initialize_at_path(test_path.to_owned())
|
||||
.initialize_at_path(test_path.clone())
|
||||
.unwrap_or_default();
|
||||
callbacks.append(
|
||||
&mut uvars2
|
||||
.initialize_at_path(test_path.to_owned())
|
||||
.initialize_at_path(test_path.clone())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
@@ -1134,7 +1134,7 @@ fn test_universal_ok_to_save() {
|
||||
|
||||
let mut uvars = EnvUniversal::new();
|
||||
uvars
|
||||
.initialize_at_path(test_path.to_owned())
|
||||
.initialize_at_path(test_path.clone())
|
||||
.unwrap_or_default();
|
||||
assert!(!uvars.is_ok_to_save(), "Should not be OK to save");
|
||||
uvars.sync();
|
||||
|
||||
72
src/exec.rs
72
src/exec.rs
@@ -11,11 +11,12 @@
|
||||
ScopeGuard, bytes2wcstring, exit_without_destructors, truncate_at_nul, wcs2bytes, wcs2zstring,
|
||||
write_loop,
|
||||
};
|
||||
use crate::env::{EnvMode, EnvStack, Environment, READ_BYTE_LIMIT, Statuses};
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment, READ_BYTE_LIMIT, Statuses};
|
||||
#[cfg(have_posix_spawn)]
|
||||
use crate::env_dispatch::use_posix_spawn;
|
||||
use crate::fds::make_fd_blocking;
|
||||
use crate::fds::{PIPE_ERROR, make_autoclose_pipes, open_cloexec};
|
||||
use crate::fds::{
|
||||
BorrowedFdFile, PIPE_ERROR, make_autoclose_pipes, make_fd_blocking, open_cloexec,
|
||||
};
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::fork_exec::PATH_BSHELL;
|
||||
use crate::fork_exec::blocked_signals_for_job;
|
||||
@@ -32,7 +33,7 @@
|
||||
};
|
||||
use crate::nix::{getpid, isatty};
|
||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
|
||||
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser, ParserEnvSetMode};
|
||||
use crate::prelude::*;
|
||||
use crate::proc::Pid;
|
||||
use crate::proc::{
|
||||
@@ -57,7 +58,7 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::slice;
|
||||
use std::sync::{
|
||||
Arc, OnceLock,
|
||||
@@ -96,14 +97,14 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
||||
|
||||
// Apply foo=bar variable assignments
|
||||
for assignment in &job.processes()[0].variable_assignments {
|
||||
parser.vars().set(
|
||||
parser.set_var(
|
||||
&assignment.variable_name,
|
||||
EnvMode::LOCAL | EnvMode::EXPORT,
|
||||
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||
assignment.values.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
internal_exec(parser.vars(), job, block_io);
|
||||
internal_exec(parser.vars(), parser.is_repainting(), job, block_io);
|
||||
// internal_exec only returns if it failed to set up redirections.
|
||||
// In case of an successful exec, this code is not reached.
|
||||
let status = if job.flags().negate { 0 } else { 1 };
|
||||
@@ -234,11 +235,13 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
||||
// a pgroup, so error out before setting last_pid.
|
||||
if !job.is_foreground() {
|
||||
if let Some(last_pid) = job.get_last_pid() {
|
||||
parser
|
||||
.vars()
|
||||
.set_one(L!("last_pid"), EnvMode::GLOBAL, last_pid.to_wstring());
|
||||
parser.set_one(
|
||||
L!("last_pid"),
|
||||
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||
last_pid.to_wstring(),
|
||||
);
|
||||
} else {
|
||||
parser.vars().set_empty(L!("last_pid"), EnvMode::GLOBAL);
|
||||
parser.set_empty(L!("last_pid"), ParserEnvSetMode::new(EnvMode::GLOBAL));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +252,6 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
||||
if job.is_stopped() {
|
||||
handoff.save_tty_modes();
|
||||
}
|
||||
handoff.reclaim();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -325,8 +327,8 @@ fn exit_code_from_exec_error(err: libc::c_int) -> libc::c_int {
|
||||
STATUS_NOT_EXECUTABLE
|
||||
}
|
||||
#[cfg(apple)]
|
||||
libc::EBADARCH => {
|
||||
// This is for e.g. running ARM app on Intel Mac.
|
||||
libc::EBADARCH | libc::EBADMACHO => {
|
||||
// This is for e.g. running ARM app on Intel Mac or a bad Mach-O executable
|
||||
STATUS_NOT_EXECUTABLE
|
||||
}
|
||||
_ => {
|
||||
@@ -479,7 +481,7 @@ fn can_use_posix_spawn_for_job(job: &Job, dup2s: &Dup2List) -> bool {
|
||||
!wants_terminal
|
||||
}
|
||||
|
||||
fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
||||
fn internal_exec(vars: &EnvStack, is_repainting: bool, j: &Job, block_io: IoChain) {
|
||||
// Do a regular launch - but without forking first...
|
||||
let mut all_ios = block_io;
|
||||
if !all_ios.append_from_specs(j.processes()[0].redirection_specs(), &vars.get_pwd_slash()) {
|
||||
@@ -508,7 +510,8 @@ fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
||||
{
|
||||
// Decrement SHLVL as we're removing ourselves from the shell "stack".
|
||||
if is_interactive_session() {
|
||||
let shlvl_var = vars.getf(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||
let global_exported_mode = EnvMode::GLOBAL | EnvMode::EXPORT;
|
||||
let shlvl_var = vars.getf(L!("SHLVL"), global_exported_mode);
|
||||
let mut shlvl_str = L!("0").to_owned();
|
||||
if let Some(shlvl_var) = shlvl_var {
|
||||
if let Ok(shlvl) = fish_wcstol(&shlvl_var.as_string()) {
|
||||
@@ -517,7 +520,11 @@ fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
||||
}
|
||||
}
|
||||
}
|
||||
vars.set_one(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT, shlvl_str);
|
||||
vars.set_one(
|
||||
L!("SHLVL"),
|
||||
EnvSetMode::new(global_exported_mode, is_repainting),
|
||||
shlvl_str,
|
||||
);
|
||||
}
|
||||
|
||||
// launch_process _never_ returns.
|
||||
@@ -957,15 +964,17 @@ fn function_prepare_environment(
|
||||
// 2. inherited variables
|
||||
// 3. argv
|
||||
|
||||
let mode = parser.convert_env_set_mode(ParserEnvSetMode::user(EnvMode::LOCAL));
|
||||
|
||||
let mut overwrite_argv = false;
|
||||
for (idx, named_arg) in props.named_arguments.iter().enumerate() {
|
||||
if named_arg == L!("argv") {
|
||||
overwrite_argv = true
|
||||
};
|
||||
if idx < argv.len() {
|
||||
vars.set_one(named_arg, EnvMode::LOCAL | EnvMode::USER, argv[idx].clone());
|
||||
vars.set_one(named_arg, mode, argv[idx].clone());
|
||||
} else {
|
||||
vars.set_empty(named_arg, EnvMode::LOCAL | EnvMode::USER);
|
||||
vars.set_empty(named_arg, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -973,11 +982,11 @@ fn function_prepare_environment(
|
||||
if key == L!("argv") {
|
||||
overwrite_argv = true
|
||||
};
|
||||
vars.set(key, EnvMode::LOCAL | EnvMode::USER, value.clone());
|
||||
vars.set(key, mode, value.clone());
|
||||
}
|
||||
|
||||
if !overwrite_argv {
|
||||
vars.set_argv(argv);
|
||||
vars.set_argv(argv, mode.is_repainting);
|
||||
}
|
||||
fb
|
||||
}
|
||||
@@ -1151,23 +1160,28 @@ fn get_performer_for_builtin(p: &Process, j: &Job, io_chain: &IoChain) -> Box<Pr
|
||||
let err_io = io_chain.io_for_fd(STDERR_FILENO);
|
||||
|
||||
// Figure out what fd to use for the builtin's stdin.
|
||||
let mut local_builtin_stdin = STDIN_FILENO;
|
||||
let mut local_builtin_stdin = Some(BorrowedFdFile::stdin());
|
||||
if let Some(inp) = io_chain.io_for_fd(STDIN_FILENO) {
|
||||
// An fd of -1 is treated as closing stdin.
|
||||
// Ignore fd redirections from an fd other than the
|
||||
// standard ones. e.g. in source <&3 don't actually read from fd 3,
|
||||
// which is internal to fish. We still respect this redirection in
|
||||
// that we pass it on as a block IO to the code that source runs,
|
||||
// and therefore this is not an error.
|
||||
let ignore_redirect = inp.io_mode() == IoMode::Fd && inp.source_fd() >= 3;
|
||||
if !ignore_redirect {
|
||||
local_builtin_stdin = inp.source_fd();
|
||||
let fd = inp.source_fd();
|
||||
let ignore_redirect = fd >= 3 && inp.io_mode() == IoMode::Fd;
|
||||
if fd == -1 {
|
||||
local_builtin_stdin = None;
|
||||
} else if !ignore_redirect {
|
||||
// Safety: the fd may in principal be closed, but this only panics on negative values.
|
||||
local_builtin_stdin = Some(unsafe { BorrowedFdFile::from_raw_fd(fd) });
|
||||
}
|
||||
}
|
||||
|
||||
// Populate our IoStreams. This is a bag of information for the builtin.
|
||||
let mut streams = IoStreams::new(output_stream, errput_stream, &io_chain);
|
||||
streams.job_group = job_group;
|
||||
streams.stdin_fd = local_builtin_stdin;
|
||||
streams.stdin_file = local_builtin_stdin;
|
||||
streams.stdin_is_directly_redirected = stdin_is_directly_redirected;
|
||||
streams.out_is_redirected = out_io.is_some();
|
||||
streams.err_is_redirected = err_io.is_some();
|
||||
@@ -1308,9 +1322,9 @@ fn exec_process_in_job(
|
||||
}
|
||||
});
|
||||
for assignment in &p.variable_assignments {
|
||||
parser.vars().set(
|
||||
parser.set_var(
|
||||
&assignment.variable_name,
|
||||
EnvMode::LOCAL | EnvMode::EXPORT,
|
||||
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||
assignment.values.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,9 +297,9 @@ pub fn expand_tilde(input: &mut WString, vars: &dyn Environment) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the opposite of tilde expansion on the string, which is modified in place.
|
||||
pub fn replace_home_directory_with_tilde(s: &wstr, vars: &dyn Environment) -> WString {
|
||||
let mut result = s.to_owned();
|
||||
/// Perform the opposite of tilde expansion on the string.
|
||||
pub fn replace_home_directory_with_tilde(s: impl Into<WString>, vars: &dyn Environment) -> WString {
|
||||
let mut result = s.into();
|
||||
// Only absolute paths get this treatment.
|
||||
if result.starts_with(L!("/")) {
|
||||
let mut home_directory = L!("~").to_owned();
|
||||
@@ -737,7 +737,7 @@ fn expand_variables(
|
||||
// here, So tmp < 1 means it's definitely not in.
|
||||
// Note we are 1-based.
|
||||
if item_index >= 1 && item_index <= all_var_items.len() {
|
||||
var_item_list.push(all_var_items[item_index - 1].to_owned());
|
||||
var_item_list.push(all_var_items[item_index - 1].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1038,7 +1038,7 @@ pub fn expand_cmdsubst(
|
||||
continue;
|
||||
}
|
||||
// -1 to convert from 1-based slice index to 0-based vector index.
|
||||
sub_res2.push(sub_res[idx as usize - 1].to_owned());
|
||||
sub_res2.push(sub_res[idx as usize - 1].clone());
|
||||
}
|
||||
sub_res = sub_res2;
|
||||
}
|
||||
@@ -1597,6 +1597,7 @@ mod tests {
|
||||
use crate::expand::{ExpandResultCode, expand_to_receiver};
|
||||
use crate::operation_context::{EXPANSION_LIMIT_DEFAULT, no_cancel};
|
||||
use crate::parse_constants::ParseErrorList;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wildcard::ANY_STRING;
|
||||
use crate::{
|
||||
@@ -1957,7 +1958,7 @@ fn test_expand_overflow() {
|
||||
|
||||
let parser = TestParser::new();
|
||||
parser.vars().push(true);
|
||||
let set = parser.vars().set(L!("bigvar"), EnvMode::LOCAL, vals);
|
||||
let set = parser.set_var(L!("bigvar"), ParserEnvSetMode::new(EnvMode::LOCAL), vals);
|
||||
assert_eq!(set, EnvStackSetResult::Ok);
|
||||
|
||||
let mut errors = ParseErrorList::new();
|
||||
@@ -1977,7 +1978,7 @@ fn test_expand_overflow() {
|
||||
assert_ne!(errors, vec![]);
|
||||
assert_eq!(res, ExpandResultCode::error);
|
||||
|
||||
parser.vars().pop();
|
||||
parser.vars().pop(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2048,4 +2049,22 @@ fn test_abbreviations() {
|
||||
|
||||
assert_eq!(abbr_expand_1(L!("foo"), cmd), Some(L!("bar").into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_home_directory_with_tilde() {
|
||||
use super::replace_home_directory_with_tilde as rhdwt;
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack};
|
||||
let vars = EnvStack::new();
|
||||
vars.set_one(
|
||||
L!("HOME"),
|
||||
EnvSetMode::new(EnvMode::GLOBAL, false),
|
||||
L!("/home/testuser").to_owned(),
|
||||
);
|
||||
|
||||
assert_eq!(rhdwt("/home/testuser/", &vars), "~/");
|
||||
assert_eq!(rhdwt("/home/testuser/Documents/", &vars), "~/Documents/");
|
||||
assert_eq!(rhdwt("/home/testuser", &vars), "/home/testuser");
|
||||
assert_eq!(rhdwt("/other/path/", &vars), "/other/path/");
|
||||
assert_eq!(rhdwt("relative/path", &vars), "relative/path");
|
||||
}
|
||||
}
|
||||
|
||||
93
src/fds.rs
93
src/fds.rs
@@ -10,6 +10,9 @@
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
localizable_consts!(
|
||||
@@ -240,12 +243,84 @@ pub fn make_fd_blocking(fd: RawFd) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A helper type for a File that does not close on drop.
|
||||
/// Note the underlying file is never dropped; this is equivalent to mem::forget.
|
||||
pub struct BorrowedFdFile(ManuallyDrop<File>);
|
||||
|
||||
impl Deref for BorrowedFdFile {
|
||||
type Target = File;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BorrowedFdFile {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawFd for BorrowedFdFile {
|
||||
// Note this does NOT take ownership.
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
Self(ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for BorrowedFdFile {
|
||||
#[inline]
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for BorrowedFdFile {
|
||||
#[inline]
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
ManuallyDrop::into_inner(self.0).into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl BorrowedFdFile {
|
||||
/// Return a BorrowedFdFile from stdin.
|
||||
pub fn stdin() -> Self {
|
||||
unsafe { Self::from_raw_fd(libc::STDIN_FILENO) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BorrowedFdFile {
|
||||
// BorrowedFdFile may be cloned: this just shares the borrowed fd.
|
||||
// It does NOT duplicate the underlying fd.
|
||||
fn clone(&self) -> Self {
|
||||
// Safety: just re-borrow the same fd.
|
||||
unsafe { Self::from_raw_fd(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Read for BorrowedFdFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.deref_mut().read(buf)
|
||||
}
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.deref_mut().read_vectored(bufs)
|
||||
}
|
||||
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
|
||||
self.deref_mut().read_to_end(buf)
|
||||
}
|
||||
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
|
||||
self.deref_mut().read_to_string(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FIRST_HIGH_FD, make_autoclose_pipes};
|
||||
use super::{BorrowedFdFile, FIRST_HIGH_FD, make_autoclose_pipes};
|
||||
use crate::tests::prelude::*;
|
||||
use libc::{F_GETFD, FD_CLOEXEC};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::fd::{AsRawFd, FromRawFd};
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
@@ -269,4 +344,18 @@ fn test_pipes() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_borrowed_fd_file_does_not_close() {
|
||||
let file = std::fs::File::open("/dev/null").unwrap();
|
||||
let fd = file.as_raw_fd();
|
||||
let borrowed = unsafe { BorrowedFdFile::from_raw_fd(fd) };
|
||||
#[allow(clippy::drop_non_drop)]
|
||||
drop(borrowed);
|
||||
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
|
||||
assert!(flags >= 0);
|
||||
drop(file);
|
||||
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
|
||||
assert!(flags < 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +472,16 @@ fn err_or_no_exec_handling(interpreter: &CStr, actual_cmd: &CStr) {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(apple)]
|
||||
libc::EBADMACHO => {
|
||||
flog_safe!(
|
||||
exec,
|
||||
"Failed to execute process '",
|
||||
actual_cmd,
|
||||
"': Malformed Mach-O file."
|
||||
);
|
||||
}
|
||||
|
||||
err => {
|
||||
flog_safe!(
|
||||
exec,
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||
use crate::prelude::*;
|
||||
use crate::wutil::dir_iter::DirIter;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, LazyLock, Mutex};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionProperties {
|
||||
@@ -100,7 +99,7 @@ fn allow_autoload(&self, name: &wstr) -> bool {
|
||||
}
|
||||
|
||||
/// The big set of all functions.
|
||||
static FUNCTION_SET: Lazy<Mutex<FunctionSet>> = Lazy::new(|| {
|
||||
static FUNCTION_SET: LazyLock<Mutex<FunctionSet>> = LazyLock::new(|| {
|
||||
Mutex::new(FunctionSet {
|
||||
funcs: HashMap::new(),
|
||||
autoload_tombstones: HashSet::new(),
|
||||
|
||||
@@ -88,7 +88,7 @@ pub fn test_path(&self, token: &wstr, prefix: bool) -> bool {
|
||||
is_potential_path(
|
||||
&token,
|
||||
prefix,
|
||||
&[self.working_directory.to_owned()],
|
||||
std::slice::from_ref(&self.working_directory),
|
||||
self.ctx,
|
||||
PathFlags {
|
||||
expand_tilde: true,
|
||||
@@ -129,17 +129,17 @@ pub fn test_cd_path(&self, token: &wstr, is_prefix: bool) -> FileTestResult {
|
||||
}
|
||||
}
|
||||
|
||||
// Test if a the given string is a valid redirection target, given the mode.
|
||||
// Note we return bool, because we never underline redirection targets.
|
||||
pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> bool {
|
||||
// Test if a the given string is a valid redirection target, and if so, whether
|
||||
// it is a path to an existing file.
|
||||
pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> FileTestResult {
|
||||
// Skip targets exceeding PATH_MAX. See #7837.
|
||||
if target.len() > (PATH_MAX as usize) {
|
||||
return false;
|
||||
return Err(IsErr);
|
||||
}
|
||||
let mut target = target.to_owned();
|
||||
if !expand_one(&mut target, ExpandFlags::FAIL_ON_CMDSUBST, self.ctx, None) {
|
||||
// Could not be expanded.
|
||||
return false;
|
||||
return Err(IsErr);
|
||||
}
|
||||
// Ok, we successfully expanded our target. Now verify that it works with this
|
||||
// redirection. We will probably need it as a path (but not in the case of fd
|
||||
@@ -148,29 +148,33 @@ pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> b
|
||||
match mode {
|
||||
RedirectionMode::Fd => {
|
||||
if target == "-" {
|
||||
return true;
|
||||
return Ok(IsFile(false));
|
||||
}
|
||||
match fish_wcstoi(&target) {
|
||||
Ok(fd) => fd >= 0,
|
||||
Err(_) => false,
|
||||
Ok(fd) if fd >= 0 => Ok(IsFile(false)),
|
||||
_ => Err(IsErr),
|
||||
}
|
||||
}
|
||||
RedirectionMode::Input | RedirectionMode::TryInput => {
|
||||
// Input redirections must have a readable non-directory.
|
||||
// Note we color "try_input" files as errors if they are invalid,
|
||||
// even though it's possible to execute these (replaced via /dev/null).
|
||||
waccess(&target_path, libc::R_OK) == 0
|
||||
if waccess(&target_path, libc::R_OK) == 0
|
||||
&& wstat(&target_path).is_ok_and(|md| !md.file_type().is_dir())
|
||||
{
|
||||
Ok(IsFile(true))
|
||||
} else {
|
||||
Err(IsErr)
|
||||
}
|
||||
}
|
||||
RedirectionMode::Overwrite | RedirectionMode::Append | RedirectionMode::NoClob => {
|
||||
if string_suffixes_string(L!("/"), &target) {
|
||||
// Redirections to things that are directories is definitely not
|
||||
// allowed.
|
||||
return false;
|
||||
return Err(IsErr);
|
||||
}
|
||||
// Test whether the file exists, and whether it's writable (possibly after
|
||||
// creating it). access() returns failure if the file does not exist.
|
||||
// TODO: we do not need to compute file_exists for an 'overwrite' redirection.
|
||||
let file_exists;
|
||||
let file_is_writable;
|
||||
match wstat(&target_path) {
|
||||
@@ -206,7 +210,10 @@ pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> b
|
||||
}
|
||||
}
|
||||
// NoClob means that we must not overwrite files that exist.
|
||||
file_is_writable && !(file_exists && mode == RedirectionMode::NoClob)
|
||||
if !file_is_writable || (mode == RedirectionMode::NoClob && file_exists) {
|
||||
return Err(IsErr);
|
||||
}
|
||||
Ok(IsFile(file_exists))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -546,57 +553,57 @@ fn test_redirections() {
|
||||
|
||||
// Normal redirection.
|
||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Input);
|
||||
assert!(result);
|
||||
assert_eq!(result, Ok(IsFile(true)));
|
||||
|
||||
// Can't redirect from a missing file
|
||||
let result = tester.test_redirection_target(L!("notfile.txt"), RedirectionMode::Input);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
let result =
|
||||
tester.test_redirection_target(L!("bogus_path/file.txt"), RedirectionMode::Input);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
|
||||
// Can't redirect from a directory.
|
||||
let result = tester.test_redirection_target(L!("somedir"), RedirectionMode::Input);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
|
||||
// Can't redirect from an unreadable file.
|
||||
#[cfg(not(cygwin))] // Can't mark a file write-only on MSYS, this may work on true Cygwin
|
||||
{
|
||||
fs::set_permissions(&file_path, Permissions::from_mode(0o200)).unwrap();
|
||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Input);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
fs::set_permissions(&file_path, Permissions::from_mode(0o600)).unwrap();
|
||||
}
|
||||
|
||||
// try_input syntax highlighting reports an error even though the command will succeed.
|
||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::TryInput);
|
||||
assert!(result);
|
||||
assert_eq!(result, Ok(IsFile(true)));
|
||||
let result = tester.test_redirection_target(L!("notfile.txt"), RedirectionMode::TryInput);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
let result =
|
||||
tester.test_redirection_target(L!("bogus_path/file.txt"), RedirectionMode::TryInput);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
|
||||
// Test write redirections.
|
||||
// Overwrite an existing file.
|
||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Overwrite);
|
||||
assert!(result);
|
||||
assert_eq!(result, Ok(IsFile(true)));
|
||||
|
||||
// Append to an existing file.
|
||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Append);
|
||||
assert!(result);
|
||||
assert_eq!(result, Ok(IsFile(true)));
|
||||
|
||||
// Write to a missing file.
|
||||
let result = tester.test_redirection_target(L!("newfile.txt"), RedirectionMode::Overwrite);
|
||||
assert!(result);
|
||||
assert_eq!(result, Ok(IsFile(false)));
|
||||
|
||||
// No-clobber write to existing file should fail.
|
||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::NoClob);
|
||||
assert!(!result);
|
||||
assert_eq!(result, Err(IsErr));
|
||||
|
||||
// No-clobber write to missing file should succeed.
|
||||
let result = tester.test_redirection_target(L!("unique.txt"), RedirectionMode::NoClob);
|
||||
assert!(result);
|
||||
assert_eq!(result, Ok(IsFile(false)));
|
||||
|
||||
let write_modes = &[
|
||||
RedirectionMode::Overwrite,
|
||||
@@ -606,8 +613,9 @@ fn test_redirections() {
|
||||
|
||||
// Can't write to a directory.
|
||||
for mode in write_modes {
|
||||
assert!(
|
||||
!tester.test_redirection_target(L!("somedir"), *mode),
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("somedir"), *mode),
|
||||
Err(IsErr),
|
||||
"Should not be able to write to a directory with mode {:?}",
|
||||
mode
|
||||
);
|
||||
@@ -616,8 +624,9 @@ fn test_redirections() {
|
||||
// Can't write without write permissions.
|
||||
fs::set_permissions(&file_path, Permissions::from_mode(0o400)).unwrap(); // Read-only.
|
||||
for mode in write_modes {
|
||||
assert!(
|
||||
!tester.test_redirection_target(L!("file.txt"), *mode),
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("file.txt"), *mode),
|
||||
Err(IsErr),
|
||||
"Should not be able to write to a read-only file with mode {:?}",
|
||||
mode
|
||||
);
|
||||
@@ -629,8 +638,9 @@ fn test_redirections() {
|
||||
{
|
||||
fs::set_permissions(&dir_path, Permissions::from_mode(0o500)).unwrap(); // Read and execute, no write.
|
||||
for mode in write_modes {
|
||||
assert!(
|
||||
!tester.test_redirection_target(L!("somedir/newfile.txt"), *mode),
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("somedir/newfile.txt"), *mode),
|
||||
Err(IsErr),
|
||||
"Should not be able to create/write in a read-only directory with mode {:?}",
|
||||
mode
|
||||
);
|
||||
@@ -639,28 +649,82 @@ fn test_redirections() {
|
||||
}
|
||||
|
||||
// Test fd redirections.
|
||||
assert!(tester.test_redirection_target(L!("-"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("0"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("1"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("2"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("3"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("500"), RedirectionMode::Fd));
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("-"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("0"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("1"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("2"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("3"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("500"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
|
||||
// We are base 10, despite the leading 0.
|
||||
assert!(tester.test_redirection_target(L!("000"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("01"), RedirectionMode::Fd));
|
||||
assert!(tester.test_redirection_target(L!("07"), RedirectionMode::Fd));
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("000"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("01"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("07"), RedirectionMode::Fd),
|
||||
Ok(IsFile(false)),
|
||||
);
|
||||
|
||||
// Invalid fd redirections.
|
||||
assert!(!tester.test_redirection_target(L!("0x2"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("0x3F"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("0F"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("-1"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("-0009"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("--"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("derp"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("123boo"), RedirectionMode::Fd));
|
||||
assert!(!tester.test_redirection_target(L!("18446744073709551616"), RedirectionMode::Fd));
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("0x2"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("0x3F"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("0F"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("-1"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("-0009"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("--"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("derp"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("123boo"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
assert_eq!(
|
||||
tester.test_redirection_target(L!("18446744073709551616"), RedirectionMode::Fd),
|
||||
Err(IsErr),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user