mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-03 06:41:14 -03:00
Compare commits
78 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 |
@@ -29,6 +29,7 @@ freebsd_task:
|
|||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
|
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
|
||||||
tests_script:
|
tests_script:
|
||||||
|
- pkg update
|
||||||
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
|
- 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
|
# libclang.so is a required build dependency for rust-c++ ffi bridge
|
||||||
- pkg install -y llvm
|
- 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:
|
## 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.
|
- [ ] Changes to fish usage are reflected in user documentation/manpages.
|
||||||
- [ ] Tests have been added for regressions fixed
|
- [ ] 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,3 +1,21 @@
|
|||||||
|
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)
|
fish 4.3.2 (released December 30, 2025)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
|
|||||||
121
Cargo.lock
generated
121
Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -40,9 +40,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.12.0"
|
version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -50,9 +50,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.41"
|
version = "1.2.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
|
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -83,9 +83,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"typenum",
|
"typenum",
|
||||||
@@ -146,13 +146,13 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.4"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fish"
|
name = "fish"
|
||||||
version = "4.3.2"
|
version = "4.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -176,7 +176,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pcre2",
|
"pcre2",
|
||||||
"phf_codegen 0.12.1",
|
"phf_codegen 0.13.1",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"rsconf",
|
"rsconf",
|
||||||
@@ -209,7 +209,6 @@ version = "0.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"nix",
|
"nix",
|
||||||
"once_cell",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -221,7 +220,6 @@ dependencies = [
|
|||||||
"fish-wchar",
|
"fish-wchar",
|
||||||
"fish-widecharwidth",
|
"fish-widecharwidth",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
|
||||||
"rsconf",
|
"rsconf",
|
||||||
"widestring",
|
"widestring",
|
||||||
]
|
]
|
||||||
@@ -231,8 +229,7 @@ name = "fish-gettext"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fish-gettext-maps",
|
"fish-gettext-maps",
|
||||||
"once_cell",
|
"phf 0.13.1",
|
||||||
"phf 0.12.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -250,8 +247,8 @@ version = "0.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"fish-build-helper",
|
"fish-build-helper",
|
||||||
"fish-gettext-mo-file-parser",
|
"fish-gettext-mo-file-parser",
|
||||||
"phf 0.12.1",
|
"phf 0.13.1",
|
||||||
"phf_codegen 0.12.1",
|
"phf_codegen 0.13.1",
|
||||||
"rsconf",
|
"rsconf",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -297,15 +294,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.9"
|
version = "0.14.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
@@ -336,9 +333,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.16"
|
version = "0.4.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"bstr",
|
"bstr",
|
||||||
@@ -349,9 +346,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
"equivalent",
|
"equivalent",
|
||||||
@@ -370,15 +367,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.177"
|
version = "0.2.178"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.10"
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -395,15 +392,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.28"
|
version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.13.0"
|
version = "0.16.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
@@ -539,11 +536,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.12.1"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
|
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_shared 0.12.1",
|
"phf_shared 0.13.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -558,12 +555,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_codegen"
|
name = "phf_codegen"
|
||||||
version = "0.12.1"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
|
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_generator 0.12.1",
|
"phf_generator 0.13.1",
|
||||||
"phf_shared 0.12.1",
|
"phf_shared 0.13.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -578,12 +575,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_generator"
|
name = "phf_generator"
|
||||||
version = "0.12.1"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
|
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"phf_shared 0.12.1",
|
"phf_shared 0.13.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -597,9 +594,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.12.1"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
|
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
@@ -612,9 +609,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
@@ -627,18 +624,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.41"
|
version = "1.0.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -732,9 +729,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsconf"
|
name = "rsconf"
|
||||||
version = "0.2.2"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd2af859f1af0401e7fc7577739c87b0d239d8a5da400d717183bca92336bcdc"
|
checksum = "06cbd984e96cc891aa018958ac3d09986c0ea7635eedfff670b99a90970f159f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
@@ -897,9 +894,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.107"
|
version = "2.0.111"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
|
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -946,9 +943,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.20"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
@@ -1052,18 +1049,18 @@ checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.27"
|
version = "0.8.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.27"
|
version = "0.8.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"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.
|
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
|
||||||
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
||||||
# files as of 22 April 2024.
|
# files as of 22 April 2024.
|
||||||
lru = "0.13.0"
|
lru = "0.16.2"
|
||||||
nix = { version = "0.30.1", default-features = false, features = [
|
nix = { version = "0.30.1", default-features = false, features = [
|
||||||
"event",
|
"event",
|
||||||
"inotify",
|
"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 = [
|
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
|
||||||
"utf32",
|
"utf32",
|
||||||
] }
|
] }
|
||||||
phf = { version = "0.12", default-features = false }
|
phf = { version = "0.13", default-features = false }
|
||||||
phf_codegen = { version = "0.12" }
|
phf_codegen = "0.13"
|
||||||
portable-atomic = { version = "1", default-features = false, features = [
|
portable-atomic = { version = "1", default-features = false, features = [
|
||||||
"fallback",
|
"fallback",
|
||||||
] }
|
] }
|
||||||
@@ -54,7 +54,7 @@ rand = { version = "0.9.2", default-features = false, features = [
|
|||||||
"small_rng",
|
"small_rng",
|
||||||
"thread_rng",
|
"thread_rng",
|
||||||
] }
|
] }
|
||||||
rsconf = "0.2.2"
|
rsconf = "0.3.0"
|
||||||
rust-embed = { version = "8.9.0", features = [
|
rust-embed = { version = "8.9.0", features = [
|
||||||
"deterministic-timestamps",
|
"deterministic-timestamps",
|
||||||
"include-exclude",
|
"include-exclude",
|
||||||
@@ -79,7 +79,7 @@ debug = true
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "fish"
|
name = "fish"
|
||||||
version = "4.3.2"
|
version = "4.3.3"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
default-run = "fish"
|
default-run = "fish"
|
||||||
@@ -182,19 +182,24 @@ rust.non_upper_case_globals = "allow"
|
|||||||
rust.unknown_lints = "allow"
|
rust.unknown_lints = "allow"
|
||||||
rust.unstable_name_collisions = "allow"
|
rust.unstable_name_collisions = "allow"
|
||||||
rustdoc.private_intra_doc_links = "allow"
|
rustdoc.private_intra_doc_links = "allow"
|
||||||
clippy.len_without_is_empty = "allow" # we're not a library crate
|
|
||||||
clippy.let_and_return = "allow"
|
[workspace.lints.clippy]
|
||||||
clippy.manual_range_contains = "allow"
|
assigning_clones = "warn"
|
||||||
clippy.map_unwrap_or = "warn"
|
implicit_clone = "warn"
|
||||||
clippy.needless_lifetimes = "allow"
|
cloned_instead_of_copied = "warn"
|
||||||
clippy.new_without_default = "allow"
|
len_without_is_empty = "allow" # we're not a library crate
|
||||||
clippy.option_map_unit_fn = "allow"
|
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.
|
# We do not want to use the e?print(ln)?! macros.
|
||||||
# These lints flag their use.
|
# These lints flag their use.
|
||||||
# In the future, they might change to flag other methods of printing.
|
# In the future, they might change to flag other methods of printing.
|
||||||
clippy.print_stdout = "deny"
|
print_stdout = "deny"
|
||||||
clippy.print_stderr = "deny"
|
print_stderr = "deny"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ Dependencies
|
|||||||
|
|
||||||
Compiling fish requires:
|
Compiling fish requires:
|
||||||
|
|
||||||
- Rust (version 1.85 or later)
|
- Rust (version 1.85 or later), including cargo
|
||||||
- CMake (version 3.15 or later)
|
- CMake (version 3.15 or later)
|
||||||
- a C compiler (for system feature detection and the test helper binary)
|
- a C compiler (for system feature detection and the test helper binary)
|
||||||
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
|
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
|
||||||
|
|||||||
@@ -33,8 +33,13 @@ fi
|
|||||||
cargo() {
|
cargo() {
|
||||||
subcmd=$1
|
subcmd=$1
|
||||||
shift
|
shift
|
||||||
# shellcheck disable=2086
|
if [ -n "$FISH_CHECK_RUST_TOOLCHAIN" ]; then
|
||||||
command cargo "$subcmd" $cargo_args "$@"
|
# shellcheck disable=2086
|
||||||
|
command cargo "+$FISH_CHECK_RUST_TOOLCHAIN" "$subcmd" $cargo_args "$@"
|
||||||
|
else
|
||||||
|
# shellcheck disable=2086
|
||||||
|
command cargo "$subcmd" $cargo_args "$@"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup () {
|
cleanup () {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ command -v updatecli
|
|||||||
command -v uv
|
command -v uv
|
||||||
sort --version-sort </dev/null
|
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}"
|
updatecli "${@:-apply}"
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,14 @@ install(DIRECTORY share/functions/
|
|||||||
DESTINATION ${rel_datadir}/fish/functions
|
DESTINATION ${rel_datadir}/fish/functions
|
||||||
FILES_MATCHING PATTERN "*.fish")
|
FILES_MATCHING PATTERN "*.fish")
|
||||||
|
|
||||||
|
install(DIRECTORY share/prompts/
|
||||||
|
DESTINATION ${rel_datadir}/fish/prompts
|
||||||
|
FILES_MATCHING PATTERN "*.fish")
|
||||||
|
|
||||||
|
install(DIRECTORY share/themes/
|
||||||
|
DESTINATION ${rel_datadir}/fish/themes
|
||||||
|
FILES_MATCHING PATTERN "*.theme")
|
||||||
|
|
||||||
# CONDEMNED_PAGE is managed by the conditional above
|
# CONDEMNED_PAGE is managed by the conditional above
|
||||||
# Building the man pages is optional: if sphinx isn't installed, they're not built
|
# Building the man pages is optional: if sphinx isn't installed, they're not built
|
||||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
|
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
|
||||||
@@ -149,9 +157,7 @@ install(DIRECTORY share/tools/web_config
|
|||||||
PATTERN "*.css"
|
PATTERN "*.css"
|
||||||
PATTERN "*.html"
|
PATTERN "*.html"
|
||||||
PATTERN "*.py"
|
PATTERN "*.py"
|
||||||
PATTERN "*.js"
|
PATTERN "*.js")
|
||||||
PATTERN "*.theme"
|
|
||||||
PATTERN "*.fish")
|
|
||||||
|
|
||||||
# Building the man pages is optional: if Sphinx isn't installed, they're not built
|
# Building the man pages is optional: if Sphinx isn't installed, they're not built
|
||||||
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
|
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
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
|
fish (4.3.2-1) stable; urgency=medium
|
||||||
|
|
||||||
* Release of new version 4.3.2.
|
* Release of new version 4.3.2.
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ license.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
nix.workspace = true
|
nix.workspace = true
|
||||||
once_cell.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use libc::STDIN_FILENO;
|
use libc::STDIN_FILENO;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
// These are in the Unicode private-use range. We really shouldn't use this
|
// 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.
|
// 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
|
/// session. We err on the side of assuming it's not a console session. This approach isn't
|
||||||
/// bullet-proof and that's OK.
|
/// bullet-proof and that's OK.
|
||||||
pub fn is_console_session() -> bool {
|
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)
|
// TODO(terminal-workaround)
|
||||||
*IS_CONSOLE_SESSION.get_or_init(|| {
|
*IS_CONSOLE_SESSION.get_or_init(|| {
|
||||||
nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) })
|
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-wchar.workspace = true
|
||||||
fish-widecharwidth.workspace = true
|
fish-widecharwidth.workspace = true
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
once_cell.workspace = true
|
|
||||||
widestring.workspace = true
|
widestring.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
|
|
||||||
use fish_wchar::prelude::*;
|
use fish_wchar::prelude::*;
|
||||||
use fish_widecharwidth::{WcLookupTable, WcWidth};
|
use fish_widecharwidth::{WcLookupTable, WcWidth};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::cmp;
|
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.
|
/// 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`.
|
/// 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`.
|
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
|
||||||
pub static FISH_EMOJI_WIDTH: AtomicIsize = AtomicIsize::new(1);
|
pub static FISH_EMOJI_WIDTH: 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
|
/// A safe wrapper around the system `wcwidth()` function
|
||||||
#[cfg(not(cygwin))]
|
#[cfg(not(cygwin))]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fish-gettext-maps.workspace = true
|
fish-gettext-maps.workspace = true
|
||||||
once_cell.workspace = true
|
|
||||||
phf.workspace = true
|
phf.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -1,259 +1,19 @@
|
|||||||
use fish_gettext_maps::CATALOGS;
|
use fish_gettext_maps::CATALOGS;
|
||||||
use once_cell::sync::Lazy;
|
use std::{
|
||||||
use std::{collections::HashSet, sync::Mutex};
|
collections::HashMap,
|
||||||
|
sync::{LazyLock, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
type Catalog = &'static phf::Map<&'static str, &'static str>;
|
type Catalog = &'static phf::Map<&'static str, &'static str>;
|
||||||
|
|
||||||
pub struct SetLanguageLints<'a> {
|
static LANGUAGE_PRECEDENCE: LazyLock<Mutex<Vec<(&'static str, Catalog)>>> =
|
||||||
pub duplicates: Vec<&'a str>,
|
LazyLock::new(|| Mutex::new(vec![]));
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gettext(message_str: &'static str) -> Option<&'static str> {
|
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.
|
// 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) {
|
if let Some(localized_str) = catalog.get(message_str) {
|
||||||
return Some(localized_str);
|
return Some(localized_str);
|
||||||
}
|
}
|
||||||
@@ -261,8 +21,40 @@ pub fn gettext(message_str: &'static str) -> Option<&'static str> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_available_languages() -> Vec<&'static str> {
|
#[derive(Clone, Copy)]
|
||||||
let mut langs: Vec<_> = CATALOGS.entries().map(|(&lang, _)| lang).collect();
|
pub struct GettextLocalizationLanguage {
|
||||||
langs.sort();
|
language: &'static str,
|
||||||
langs
|
}
|
||||||
|
|
||||||
|
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)
|
[--set-cursor[=MARKER]] ([-f | --function FUNCTION] | EXPANSION)
|
||||||
abbr --erase [ [-c | --command COMMAND]... ] NAME ...
|
abbr --erase [ [-c | --command COMMAND]... ] NAME ...
|
||||||
abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD
|
abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD
|
||||||
abbr --show
|
abbr [--show] [--color WHEN]
|
||||||
abbr --list
|
abbr --list
|
||||||
abbr --query NAME ...
|
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.
|
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
|
Examples
|
||||||
########
|
########
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ Synopsis
|
|||||||
.. synopsis::
|
.. synopsis::
|
||||||
|
|
||||||
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] KEYS COMMAND ...
|
bind [(-M | --mode) MODE] [(-m | --sets-mode) NEW_MODE] [--preset | --user] [-s | --silent] KEYS COMMAND ...
|
||||||
bind [(-M | --mode) MODE] [--preset] [--user] [KEYS]
|
bind [(-M | --mode) MODE] [--preset] [--user] [--color WHEN] [KEYS]
|
||||||
bind [-a | --all] [--preset] [--user]
|
bind [-a | --all] [--preset] [--user] [--color WHEN]
|
||||||
bind (-f | --function-names)
|
bind (-f | --function-names)
|
||||||
bind (-K | --key-names)
|
bind (-K | --key-names)
|
||||||
bind (-L | --list-modes)
|
bind (-L | --list-modes)
|
||||||
@@ -104,6 +104,10 @@ The following options are available:
|
|||||||
**-s** or **--silent**
|
**-s** or **--silent**
|
||||||
Silences some of the error messages, including for unknown key names and unbound sequences.
|
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**
|
**-h** or **--help**
|
||||||
Displays help about using this command.
|
Displays help about using this command.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Synopsis
|
|||||||
|
|
||||||
.. synopsis::
|
.. synopsis::
|
||||||
|
|
||||||
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS]
|
complete ((-c | --command) | (-p | --path)) COMMAND [OPTIONS] [--color WHEN]
|
||||||
complete (-C | --do-complete) [--escape] STRING
|
complete (-C | --do-complete) [--escape] STRING
|
||||||
|
|
||||||
Description
|
Description
|
||||||
@@ -74,6 +74,10 @@ The following options are available:
|
|||||||
**--escape**
|
**--escape**
|
||||||
When used with ``-C``, escape special characters in completions.
|
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**
|
**-h** or **--help**
|
||||||
Displays help about using this command.
|
Displays help about using this command.
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ Synopsis
|
|||||||
|
|
||||||
.. synopsis::
|
.. synopsis::
|
||||||
|
|
||||||
functions [-a | --all] [-n | --names]
|
functions [-a | --all] [-n | --names] [--color WHEN]
|
||||||
functions [-D | --details] [-v] FUNCTION
|
functions [-D | --details] [-v] [--color WHEN] FUNCTION
|
||||||
functions -c OLDNAME NEWNAME
|
functions -c OLDNAME NEWNAME
|
||||||
functions -d DESCRIPTION FUNCTION
|
functions -d DESCRIPTION FUNCTION
|
||||||
functions [-e | -q] FUNCTION ...
|
functions [-e | -q] FUNCTION ...
|
||||||
@@ -60,6 +60,10 @@ The following options are available:
|
|||||||
**-t** or **--handlers-type** *TYPE*
|
**-t** or **--handlers-type** *TYPE*
|
||||||
Show all event handlers matching the given *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**
|
**-h** or **--help**
|
||||||
Displays help about using this command.
|
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**
|
**-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.
|
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**
|
**-h** or **--help**
|
||||||
Displays help for this command.
|
Displays help for this command.
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ The following options are available:
|
|||||||
**-q** or **--query**
|
**-q** or **--query**
|
||||||
Suppresses all output; this is useful when testing the exit status. For compatibility with old fish versions this is also **--quiet**.
|
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**
|
**-h** or **--help**
|
||||||
Displays help about using this command.
|
Displays help about using this command.
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ Variable Meaning
|
|||||||
.. envvar:: fish_color_end process separators like ``;`` and ``&``
|
.. envvar:: fish_color_end process separators like ``;`` and ``&``
|
||||||
.. envvar:: fish_color_error syntax errors
|
.. envvar:: fish_color_error syntax errors
|
||||||
.. envvar:: fish_color_param ordinary command parameters
|
.. 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_option options starting with "-", up to the first "--" parameter
|
||||||
.. envvar:: fish_color_comment comments like '# important'
|
.. envvar:: fish_color_comment comments like '# important'
|
||||||
.. envvar:: fish_color_selection selected text in vi visual mode
|
.. 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 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 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 -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_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 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 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 -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_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 -s s -l silent -d 'Operate silently'
|
||||||
complete -c bind -l preset -d 'Operate on preset bindings'
|
complete -c bind -l preset -d 'Operate on preset bindings'
|
||||||
complete -c bind -l user -d 'Operate on user 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
|
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 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 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 -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
|
# Deprecated options
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,72 @@ function __fish_fastboot_list_partition_or_file
|
|||||||
end
|
end
|
||||||
|
|
||||||
function __fish_fastboot_list_partition
|
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
|
printf %s "\
|
||||||
for i in $partitions
|
abl
|
||||||
echo $i
|
aop
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
complete -c fastboot -s h -l help -d 'Show this message'
|
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 v -l verbose -d "Print more output"
|
||||||
complete -c functions -s H -l handlers -d "Show event handlers"
|
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 -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
|
__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 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'
|
complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
|
||||||
|
complete -f -c git -n '__fish_git_using_command fetch' -s 4 -l ipv4 -d 'Use IPv4 addresses only'
|
||||||
# TODO other options
|
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
|
#### filter-branch
|
||||||
complete -f -c git -n __fish_git_needs_command -a filter-branch -d 'Rewrite branches'
|
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 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 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"
|
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
|
### add
|
||||||
complete -c git -n __fish_git_needs_command -a add -d 'Add file contents to the staging area'
|
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'
|
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.
|
# 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.
|
# 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,
|
# If we have so many files that you disable untrackedfiles, let's add file completions,
|
||||||
# to avoid slurping megabytes of git output.
|
# 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
|
### am
|
||||||
complete -c git -n __fish_git_needs_command -a am -d 'Apply patches from a mailbox'
|
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 -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 -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' -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 -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 -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'
|
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 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' -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'
|
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
|
### sparse-checkout
|
||||||
# `init` subcommand is deprecated
|
# `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 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' -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"
|
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
|
### bisect
|
||||||
complete -f -c git -n __fish_git_needs_command -a bisect -d 'Use binary search to find what introduced a bug'
|
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 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 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'
|
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
|
### count-objects
|
||||||
complete -f -c git -n __fish_git_needs_command -a count-objects -d 'Count number of objects and disk consumption'
|
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' -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' -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'
|
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
|
### gc
|
||||||
complete -f -c git -n __fish_git_needs_command -a gc -d 'Collect garbage (unreachable commits etc)'
|
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' -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 -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)'
|
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
|
### 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_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' -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'
|
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
|
### shortlog
|
||||||
complete -c git -n __fish_git_needs_command -a shortlog -d 'Show commit 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 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' -s v -l verbose -d 'Report all removed objects'
|
||||||
complete -f -c git -n '__fish_git_using_command prune' -l progress -d 'Show progress'
|
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
|
### pull
|
||||||
complete -f -c git -n __fish_git_needs_command -a pull -d 'Fetch from and merge with another repo or branch'
|
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 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 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'
|
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
|
### range-diff
|
||||||
complete -f -c git -n __fish_git_needs_command -a range-diff -d 'Compare two commit ranges'
|
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 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' -s v -l verbose -d 'Be verbose'
|
||||||
complete -f -c git -n '__fish_git_using_command push' -l progress -d 'Force progress status'
|
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
|
### rebase
|
||||||
complete -f -c git -n __fish_git_needs_command -a rebase -d 'Reapply commit sequence on a new base'
|
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 '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 'contains -- -- (commandline -xpc)'
|
||||||
complete -f -c git -n '__fish_git_using_command reset' -n 'not contains -- -- (commandline -xpc)' -a '(__fish_git_reflog)' -d Reflog
|
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 and switch
|
||||||
# restore options
|
# 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' -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 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'
|
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
|
### rm
|
||||||
complete -c git -n __fish_git_needs_command -a rm -d 'Remove files from the working tree and/or staging area'
|
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 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' -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'
|
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
|
### status
|
||||||
complete -f -c git -n __fish_git_needs_command -a status -d 'Show the working tree 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 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 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'
|
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
|
### stripspace
|
||||||
complete -f -c git -n __fish_git_needs_command -a stripspace -d 'Remove unnecessary whitespace'
|
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' -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' -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
|
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
|
### update-index
|
||||||
complete -c git -n __fish_git_needs_command -a update-index -d 'Register file contents in the working tree to the 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
|
### config
|
||||||
complete -f -c git -n __fish_git_needs_command -a config -d 'Set and read git configuration variables'
|
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
|
### format-patch
|
||||||
complete -f -c git -n __fish_git_needs_command -a format-patch -d 'Generate patch series to send upstream'
|
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 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 ignored files, as well'
|
||||||
complete -f -c git -n '__fish_git_using_command clean' -s X -d 'Remove only ignored files'
|
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
|
### git blame
|
||||||
complete -f -c git -n __fish_git_needs_command -a blame -d 'Show what last modified each line of a file'
|
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
|
end
|
||||||
complete -f -c git -n "__fish_seen_subcommand_from $sortcommands" -l sort -d 'Sort results by' -a "(__fish_git_sort_keys)"
|
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)
|
## Custom commands (git-* commands installed in the PATH)
|
||||||
complete -c git -n __fish_git_needs_command -a '(__fish_git_custom_commands)' -d 'Custom command'
|
complete -c git -n __fish_git_needs_command -a '(__fish_git_custom_commands)' -d 'Custom command'
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
-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' \
|
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
|
-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
|
# We don't include a completion for the "save" subcommand because it should not be used
|
||||||
# interactively.
|
# 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 "$";
|
is_continuation = $0 ~ "^([^#]*[^#" bs_regex "])?(" bs_regex bs_regex ")*" bs_regex "$";
|
||||||
}' 2>/dev/null
|
}' 2>/dev/null
|
||||||
else
|
else
|
||||||
# BSD make
|
# FreeBSD/NetBSD
|
||||||
make $makeflags -d g1 -rn >/dev/null 2>| awk -F, '/^#\*\*\* Input graph:/,/^$/ {if ($1 !~ "^#... ") {gsub(/# /,"",$1); print $1}}' 2>/dev/null
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,78 @@
|
|||||||
complete -c signify -n __fish_seen_subcommand_from -s C -d 'Verify a signed checksum list'
|
# Tab completion for openbsd-signify
|
||||||
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'
|
set -l subcommands -C -G -S -V
|
||||||
complete -c signify -n __fish_seen_subcommand_from -s V -d 'Verify a signed message and sig'
|
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 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 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 -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 "(builtin -n)" -d Builtin
|
||||||
complete -c type -a "(functions -n)" -d Function
|
complete -c type -a "(functions -n)" -d Function
|
||||||
|
|||||||
@@ -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,
|
# If we have a --name=val option, we need to remove it,
|
||||||
# or the complete -C below would keep it, and then whatever complete
|
# 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)
|
# 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
|
end
|
||||||
|
|
||||||
# HACK: We call into the file completions by using an empty command
|
# HACK: We call into the file completions by using an empty command
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function __fish_migrate
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create empty configuration directores if they do not already exist
|
# 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/ ||
|
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}
|
mkdir -p $__fish_config_dir/{completions, conf.d, functions}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ set --erase --universal fish_key_bindings"
|
|||||||
echo 'See also the release notes (type `help relnotes`).'
|
echo 'See also the release notes (type `help relnotes`).'
|
||||||
set -Ue fish_key_bindings $theme_uvars
|
set -Ue fish_key_bindings $theme_uvars
|
||||||
set -l sh (__fish_posix_shell)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -321,10 +321,11 @@ function __fish_config_theme_choose
|
|||||||
else
|
else
|
||||||
set desired_color_theme $fish_terminal_color_theme
|
set desired_color_theme $fish_terminal_color_theme
|
||||||
if not set -q desired_color_theme[1]
|
if not set -q desired_color_theme[1]
|
||||||
echo >&2 "fish_config theme choose: internal error: \$fish_terminal_color_theme not yet initialized"
|
if test $scope = -U
|
||||||
return 1
|
echo >&2 "fish_config theme save: error: \$fish_terminal_color_theme not yet initialized"
|
||||||
end
|
return 1
|
||||||
if not contains -- "[$desired_color_theme]" $theme_data
|
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
|
__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"
|
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
|
return 1
|
||||||
@@ -342,7 +343,7 @@ function __fish_config_theme_choose
|
|||||||
set -l color_theme
|
set -l color_theme
|
||||||
string match -re -- (__fish_theme_variable_filter)'|^\[.*\]$' $theme_data |
|
string match -re -- (__fish_theme_variable_filter)'|^\[.*\]$' $theme_data |
|
||||||
while read -lat toks
|
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
|
for ct in $color_themes
|
||||||
if test "$toks" = [$ct]
|
if test "$toks" = [$ct]
|
||||||
set color_theme $ct
|
set color_theme $ct
|
||||||
@@ -362,7 +363,11 @@ function __fish_config_theme_choose
|
|||||||
set -eg $varname
|
set -eg $varname
|
||||||
end
|
end
|
||||||
if $override || not set -q $varname || string match -rq -- '--theme=.*' $$varname
|
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
|
||||||
end
|
end
|
||||||
if $override
|
if $override
|
||||||
@@ -376,31 +381,38 @@ function __fish_config_theme_choose
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if test -n "$fish_terminal_color_theme" || not $need_hook
|
if not $need_hook || test -n "$fish_terminal_color_theme" ||
|
||||||
if set -q _flag_no_override[1]
|
# comment to work around fish_indent bug
|
||||||
__fish_apply_theme
|
{
|
||||||
else
|
$theme_is_color_theme_aware && test -z "$fish_terminal_color_theme"
|
||||||
__fish_override=true __fish_apply_theme
|
}
|
||||||
|
if not set -q _flag_no_override[1]
|
||||||
|
set -fx __fish_override true
|
||||||
end
|
end
|
||||||
|
__fish_apply_theme
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_config_theme_canonicalize --no-scope-shadowing
|
function __fish_config_theme_canonicalize --no-scope-shadowing
|
||||||
# theme_name
|
# theme_name
|
||||||
# color_theme
|
# color_theme
|
||||||
if not path is (__fish_theme_dir)/$theme_name.theme
|
if path is (__fish_theme_dir)/$theme_name.theme
|
||||||
switch $theme_name
|
return
|
||||||
case 'fish default'
|
end
|
||||||
set theme_name default
|
switch $theme_name
|
||||||
case 'ayu Dark' 'ayu Light' 'ayu Mirage' \
|
case 'fish default'
|
||||||
'Base16 Default Dark' 'Base16 Default Light' 'Base16 Eighties' \
|
set theme_name default
|
||||||
'Bay Cruise' Dracula Fairground 'Just a Touch' Lava \
|
case 'ayu Dark' 'ayu Light' 'ayu Mirage' \
|
||||||
'Mono Lace' 'Mono Smoke' \
|
'Base16 Default Dark' 'Base16 Default Light' 'Base16 Eighties' \
|
||||||
None Nord 'Old School' Seaweed 'Snow Day' \
|
'Bay Cruise' Dracula Fairground 'Just a Touch' Lava \
|
||||||
'Solarized Dark' 'Solarized Light' \
|
'Mono Lace' 'Mono Smoke' \
|
||||||
'Tomorrow Night Bright' 'Tomorrow Night' Tomorrow
|
None Nord 'Old School' Seaweed 'Snow Day' \
|
||||||
set theme_name (string lower (string replace -a " " "-" $theme_name))
|
'Solarized Dark' 'Solarized Light' \
|
||||||
end
|
'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
|
end
|
||||||
switch $theme_name
|
switch $theme_name
|
||||||
case \
|
case \
|
||||||
@@ -408,8 +420,6 @@ function __fish_config_theme_canonicalize --no-scope-shadowing
|
|||||||
base16-default-dark base16-default-light \
|
base16-default-dark base16-default-light \
|
||||||
solarized-dark solarized-light
|
solarized-dark solarized-light
|
||||||
string match -rq -- '^(?<theme_name>.*)-(?<color_theme>dark|light)$' $theme_name
|
string match -rq -- '^(?<theme_name>.*)-(?<color_theme>dark|light)$' $theme_name
|
||||||
case tomorrow
|
|
||||||
set color_theme light
|
|
||||||
case tomorrow-night
|
case tomorrow-night
|
||||||
set theme_name tomorrow
|
set theme_name tomorrow
|
||||||
set color_theme dark
|
set color_theme dark
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function history --description "display or manipulate interactive command histor
|
|||||||
set -l cmd history
|
set -l cmd history
|
||||||
set -l options --exclusive 'c,e,p' --exclusive 'S,D,M,V,X'
|
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 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.
|
# The following options are deprecated and will be removed in the next major release.
|
||||||
# Note that they do not have usable short flags.
|
# Note that they do not have usable short flags.
|
||||||
set -a options S-search D-delete M-merge V-save X-clear
|
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 show_time
|
||||||
set -l max_count
|
set -l max_count
|
||||||
set -l search_mode
|
set -l search_mode
|
||||||
|
set -l color_opt
|
||||||
set -q _flag_max
|
set -q _flag_max
|
||||||
set max_count -n$_flag_max
|
set max_count -n$_flag_max
|
||||||
|
|
||||||
|
set color_opt --color=$_flag_color
|
||||||
|
|
||||||
set -q _flag_with_time
|
set -q _flag_with_time
|
||||||
and set -l _flag_show_time $_flag_with_time
|
and set -l _flag_show_time $_flag_with_time
|
||||||
if set -q _flag_show_time[1]
|
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,
|
# 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.
|
# we do so to have it behave like cat if output fits on one screen.
|
||||||
if not set -qx LESS
|
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.
|
# 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
|
if type -q less; and test (less --version | string match -r 'less (\d+)')[2] -lt 530 2>/dev/null
|
||||||
set LESS $LESS --no-init
|
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
|
not set -qx LV # ask the pager lv not to strip colors
|
||||||
and set -fx LV -c
|
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
|
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
|
end
|
||||||
|
|
||||||
case delete # interactively delete history
|
case delete # interactively delete history
|
||||||
@@ -100,15 +109,15 @@ function history --description "display or manipulate interactive command histor
|
|||||||
end
|
end
|
||||||
|
|
||||||
if test "$search_mode" = --exact
|
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
|
builtin history save
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Fix this so that requesting history entries with a timestamp works:
|
# 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 -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]
|
if set -q found_items[1]
|
||||||
set -l found_items_count (count $found_items)
|
set -l found_items_count (count $found_items)
|
||||||
for i in (seq $found_items_count)
|
for i in (seq $found_items_count)
|
||||||
@@ -132,7 +141,7 @@ function history --description "display or manipulate interactive command histor
|
|||||||
if test "$choice" = all
|
if test "$choice" = all
|
||||||
printf "Deleting all matching entries!\n"
|
printf "Deleting all matching entries!\n"
|
||||||
for item in $found_items
|
for item in $found_items
|
||||||
builtin history delete --exact --case-sensitive -- $item
|
builtin history delete $color_opt --exact --case-sensitive -- $item
|
||||||
end
|
end
|
||||||
builtin history save
|
builtin history save
|
||||||
return
|
return
|
||||||
@@ -173,15 +182,15 @@ function history --description "display or manipulate interactive command histor
|
|||||||
echo Deleting choices: $choices
|
echo Deleting choices: $choices
|
||||||
for x in $choices
|
for x in $choices
|
||||||
printf "Deleting history entry %s: \"%s\"\n" $x $found_items[$x]
|
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
|
end
|
||||||
builtin history save
|
builtin history save
|
||||||
end
|
end
|
||||||
|
|
||||||
case save # save our interactive command history to the persistent history
|
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
|
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
|
case clear # clear the interactive command history
|
||||||
if test -n "$search_mode"
|
if test -n "$search_mode"
|
||||||
or set -q show_time[1]
|
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
|
read --local --prompt "echo 'Are you sure you want to clear history? (yes/no) '" choice
|
||||||
or return $status
|
or return $status
|
||||||
if test "$choice" = yes
|
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")
|
and printf (_ "Command history cleared!\n")
|
||||||
else
|
else
|
||||||
printf (_ "You did not say 'yes' so I will not clear your command history\n")
|
printf (_ "You did not say 'yes' so I will not clear your command history\n")
|
||||||
end
|
end
|
||||||
case clear-session # clears only session
|
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")
|
and printf (_ "Command history for session cleared!\n")
|
||||||
case append
|
case append
|
||||||
set -l newitem $argv
|
set -l newitem $argv
|
||||||
@@ -212,7 +221,7 @@ function history --description "display or manipulate interactive command histor
|
|||||||
or return $status
|
or return $status
|
||||||
end
|
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 '*'
|
case '*'
|
||||||
printf "%s: unexpected subcommand '%s'\n" $cmd $hist_cmd
|
printf "%s: unexpected subcommand '%s'\n" $cmd $hist_cmd
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
@@ -5,7 +5,15 @@ body {
|
|||||||
color: #222;
|
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;
|
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:hover,
|
||||||
#tab_contents .master_element:hover,
|
#tab_contents .master_element:hover,
|
||||||
.color_scheme_choice_container:hover,
|
.color_scheme_choice_container:hover,
|
||||||
.prompt_choices_list > .ng-scope:hover
|
.prompt_choices_list > .ng-scope:hover {
|
||||||
{
|
|
||||||
background-color: #DDE;
|
background-color: #DDE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_parent{
|
#tab_parent {
|
||||||
border: red 1px;
|
border: red 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_parent :first-child {
|
#tab_parent :first-child {
|
||||||
border-top-left-radius: 8px;
|
border-top-left-radius: 8px;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
@@ -65,7 +73,8 @@ body {
|
|||||||
border-top-right-radius: 8px;
|
border-top-right-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected_tab, .selected_tab:hover {
|
.selected_tab,
|
||||||
|
.selected_tab:hover {
|
||||||
background-color: #eeeefa;
|
background-color: #eeeefa;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
@@ -139,30 +148,39 @@ body {
|
|||||||
.detail_function .fish_color_autosuggestion {
|
.detail_function .fish_color_autosuggestion {
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_command {
|
.detail_function .fish_color_command {
|
||||||
color: #005fd7;
|
color: #005fd7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_param {
|
.detail_function .fish_color_param {
|
||||||
color: #00afff;
|
color: #00afff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_redirection {
|
.detail_function .fish_color_redirection {
|
||||||
color: #00afff;
|
color: #00afff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_comment {
|
.detail_function .fish_color_comment {
|
||||||
color: #990000;
|
color: #990000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_error {
|
.detail_function .fish_color_error {
|
||||||
color: #ff0000;
|
color: #ff0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_escape {
|
.detail_function .fish_color_escape {
|
||||||
color: #00a6b2;
|
color: #00a6b2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_operator {
|
.detail_function .fish_color_operator {
|
||||||
color: #00a6b2;
|
color: #00a6b2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_quote {
|
.detail_function .fish_color_quote {
|
||||||
color: #999900;
|
color: #999900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_function .fish_color_statement_terminator {
|
.detail_function .fish_color_statement_terminator {
|
||||||
color: #009900;
|
color: #009900;
|
||||||
}
|
}
|
||||||
@@ -221,6 +239,7 @@ body {
|
|||||||
.master_element > br {
|
.master_element > br {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected_master_elem > br {
|
.selected_master_elem > br {
|
||||||
display: inherit;
|
display: inherit;
|
||||||
}
|
}
|
||||||
@@ -267,8 +286,7 @@ body {
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data_table_row {
|
.data_table_row {}
|
||||||
}
|
|
||||||
|
|
||||||
.data_table_cell {
|
.data_table_cell {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
@@ -625,6 +643,7 @@ button.delete_button:hover {
|
|||||||
#parent {
|
#parent {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab_contents {
|
#tab_contents {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -632,27 +651,29 @@ button.delete_button:hover {
|
|||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background: linear-gradient(to top, #1f1f3f 0%,#051f3a 100%);
|
background: linear-gradient(to top, #1f1f3f 0%, #051f3a 100%);
|
||||||
color: #DDD;
|
color: #DDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ancestor {
|
#ancestor {
|
||||||
box-shadow: 0 0 5px 1px #000;
|
box-shadow: 0 0 5px 1px #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
border: 1px solid #222;
|
border: 1px solid #222;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:hover,
|
.tab:hover,
|
||||||
#tab_contents .master_element:hover,
|
#tab_contents .master_element:hover,
|
||||||
.color_scheme_choice_container:hover,
|
.color_scheme_choice_container:hover,
|
||||||
.prompt_choices_list > .ng-scope:hover
|
.prompt_choices_list > .ng-scope:hover {
|
||||||
{
|
|
||||||
background-color: #223;
|
background-color: #223;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected_tab, .selected_tab:hover {
|
.selected_tab,
|
||||||
|
.selected_tab:hover {
|
||||||
background-color: #202028;
|
background-color: #202028;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
@@ -660,15 +681,63 @@ button.delete_button:hover {
|
|||||||
#tab_contents {
|
#tab_contents {
|
||||||
background-color: #202028;
|
background-color: #202028;
|
||||||
}
|
}
|
||||||
.detail, .selected_master_elem {
|
|
||||||
|
.detail,
|
||||||
|
.selected_master_elem {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
color: #AAA;
|
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 {
|
.print_only {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,8 @@ body {
|
|||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab, .print_hidden {
|
.tab,
|
||||||
|
.print_hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,4 +28,4 @@ body {
|
|||||||
#ancestor {
|
#ancestor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sync::{Mutex, MutexGuard},
|
sync::{LazyLock, Mutex, MutexGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::parse_constants::SourceRange;
|
use crate::parse_constants::SourceRange;
|
||||||
use pcre2::utf32::Regex;
|
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 {
|
pub fn with_abbrs<R>(cb: impl FnOnce(&AbbreviationSet) -> R) -> R {
|
||||||
let abbrs_g = ABBRS.lock().unwrap();
|
let abbrs_g = ABBRS.lock().unwrap();
|
||||||
|
|||||||
@@ -385,7 +385,8 @@ fn throwing_main() -> i32 {
|
|||||||
set_libc_locales(/*log_ok=*/ false)
|
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.
|
// Enable debug categories set in FISH_DEBUG.
|
||||||
// This is in *addition* to the ones given via --debug.
|
// This is in *addition* to the ones given via --debug.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::abbrs::{self, Abbreviation, Position};
|
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::env::{EnvMode, EnvStackSetResult};
|
||||||
|
use crate::highlight::highlight_and_colorize;
|
||||||
use crate::parser::ParserEnvSetMode;
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::re::{regex_make_anchored, to_boxed_chars};
|
use crate::re::{regex_make_anchored, to_boxed_chars};
|
||||||
use pcre2::utf32::{Regex, RegexBuilder};
|
use pcre2::utf32::{Regex, RegexBuilder};
|
||||||
@@ -22,6 +23,7 @@ struct Options {
|
|||||||
position: Option<Position>,
|
position: Option<Position>,
|
||||||
set_cursor_marker: Option<WString>,
|
set_cursor_marker: Option<WString>,
|
||||||
args: Vec<WString>,
|
args: Vec<WString>,
|
||||||
|
color: ColorEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
@@ -124,7 +126,7 @@ fn join(list: &[&wstr], sep: &wstr) -> WString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print abbreviations in a fish-script friendly way.
|
// 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());
|
let style = EscapeStringStyle::Script(Default::default());
|
||||||
|
|
||||||
abbrs::with_abbrs(|abbrs| {
|
abbrs::with_abbrs(|abbrs| {
|
||||||
@@ -173,7 +175,15 @@ fn abbr_show(streams: &mut IoStreams) -> BuiltinResult {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
result.push('\n');
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -508,6 +518,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
wopt(L!("global"), ArgType::NoArgument, 'g'),
|
wopt(L!("global"), ArgType::NoArgument, 'g'),
|
||||||
wopt(L!("universal"), ArgType::NoArgument, 'U'),
|
wopt(L!("universal"), ArgType::NoArgument, 'U'),
|
||||||
wopt(L!("help"), ArgType::NoArgument, 'h'),
|
wopt(L!("help"), ArgType::NoArgument, 'h'),
|
||||||
|
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut opts = Options::default();
|
let mut opts = Options::default();
|
||||||
@@ -614,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);
|
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], false);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
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()");
|
panic!("unexpected retval from wgeopter.next()");
|
||||||
}
|
}
|
||||||
@@ -632,7 +646,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
return abbr_add(&opts, streams);
|
return abbr_add(&opts, streams);
|
||||||
};
|
};
|
||||||
if opts.show {
|
if opts.show {
|
||||||
return abbr_show(streams);
|
return abbr_show(&opts, streams, parser);
|
||||||
};
|
};
|
||||||
if opts.list {
|
if opts.list {
|
||||||
return abbr_list(&opts, streams);
|
return abbr_list(&opts, streams);
|
||||||
|
|||||||
@@ -196,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.
|
// 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)
|
Ok(SUCCESS)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@
|
|||||||
use crate::common::{
|
use crate::common::{
|
||||||
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
|
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::input::{InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings};
|
||||||
use crate::key::{
|
use crate::key::{
|
||||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||||
};
|
};
|
||||||
use crate::nix::isatty;
|
|
||||||
use std::sync::MutexGuard;
|
use std::sync::MutexGuard;
|
||||||
|
|
||||||
const DEFAULT_BIND_MODE: &wstr = L!("default");
|
const DEFAULT_BIND_MODE: &wstr = L!("default");
|
||||||
@@ -32,6 +31,7 @@ struct Options {
|
|||||||
mode: c_int,
|
mode: c_int,
|
||||||
bind_mode: WString,
|
bind_mode: WString,
|
||||||
sets_bind_mode: Option<WString>,
|
sets_bind_mode: Option<WString>,
|
||||||
|
color: ColorEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
@@ -49,6 +49,7 @@ fn new() -> Options {
|
|||||||
mode: BIND_INSERT,
|
mode: BIND_INSERT,
|
||||||
bind_mode: DEFAULT_BIND_MODE.to_owned(),
|
bind_mode: DEFAULT_BIND_MODE.to_owned(),
|
||||||
sets_bind_mode: None,
|
sets_bind_mode: None,
|
||||||
|
color: ColorEnabled::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,11 +154,12 @@ fn list_one(
|
|||||||
}
|
}
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
|
|
||||||
if !streams.out_is_redirected && isatty(libc::STDOUT_FILENO) {
|
if self.opts.color.enabled(streams) {
|
||||||
let mut colors = Vec::new();
|
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||||
highlight_shell(&out, &mut colors, &parser.context(), false, None);
|
&out,
|
||||||
let colored = colorize(&out, &colors, parser.vars());
|
&parser.context(),
|
||||||
streams.out.append(&bytes2wcstring(&colored));
|
parser.vars(),
|
||||||
|
)));
|
||||||
} else {
|
} else {
|
||||||
streams.out.append(&out);
|
streams.out.append(&out);
|
||||||
}
|
}
|
||||||
@@ -370,8 +372,8 @@ fn insert(
|
|||||||
if self.add(
|
if self.add(
|
||||||
seq,
|
seq,
|
||||||
&argv[optind + 1..],
|
&argv[optind + 1..],
|
||||||
self.opts.bind_mode.to_owned(),
|
self.opts.bind_mode.clone(),
|
||||||
self.opts.sets_bind_mode.to_owned(),
|
self.opts.sets_bind_mode.clone(),
|
||||||
self.opts.user,
|
self.opts.user,
|
||||||
streams,
|
streams,
|
||||||
) {
|
) {
|
||||||
@@ -408,7 +410,7 @@ fn parse_cmd_opts(
|
|||||||
) -> BuiltinResult {
|
) -> BuiltinResult {
|
||||||
let cmd = argv[0];
|
let cmd = argv[0];
|
||||||
let short_options = L!("aehkKfM:Lm:s");
|
let short_options = L!("aehkKfM:Lm:s");
|
||||||
const long_options: &[WOption] = &[
|
let long_options: &[WOption] = &[
|
||||||
wopt(L!("all"), NoArgument, 'a'),
|
wopt(L!("all"), NoArgument, 'a'),
|
||||||
wopt(L!("erase"), NoArgument, 'e'),
|
wopt(L!("erase"), NoArgument, 'e'),
|
||||||
wopt(L!("function-names"), NoArgument, 'f'),
|
wopt(L!("function-names"), NoArgument, 'f'),
|
||||||
@@ -421,9 +423,10 @@ fn parse_cmd_opts(
|
|||||||
wopt(L!("sets-mode"), RequiredArgument, 'm'),
|
wopt(L!("sets-mode"), RequiredArgument, 'm'),
|
||||||
wopt(L!("silent"), NoArgument, 's'),
|
wopt(L!("silent"), NoArgument, 's'),
|
||||||
wopt(L!("user"), NoArgument, 'u'),
|
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) {
|
if !valid_var_name(mode_name) {
|
||||||
streams.err.append(&wgettext_fmt!(
|
streams.err.append(&wgettext_fmt!(
|
||||||
BUILTIN_ERR_BIND_MODE,
|
BUILTIN_ERR_BIND_MODE,
|
||||||
@@ -457,13 +460,13 @@ fn parse_cmd_opts(
|
|||||||
}
|
}
|
||||||
'M' => {
|
'M' => {
|
||||||
let applicable_mode = w.woptarg.unwrap();
|
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 = applicable_mode.to_owned();
|
||||||
opts.bind_mode_given = true;
|
opts.bind_mode_given = true;
|
||||||
}
|
}
|
||||||
'm' => {
|
'm' => {
|
||||||
let new_mode = w.woptarg.unwrap();
|
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());
|
opts.sets_bind_mode = Some(new_mode.to_owned());
|
||||||
}
|
}
|
||||||
'p' => {
|
'p' => {
|
||||||
@@ -487,6 +490,9 @@ fn parse_cmd_opts(
|
|||||||
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
}
|
}
|
||||||
|
COLOR_OPTION_CHAR => {
|
||||||
|
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("unexpected retval from WGetopter")
|
panic!("unexpected retval from WGetopter")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||||
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
|
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
|
||||||
use crate::highlight::colorize;
|
use crate::highlight::highlight_and_colorize;
|
||||||
use crate::highlight::highlight_shell;
|
|
||||||
use crate::nix::isatty;
|
|
||||||
use crate::operation_context::OperationContext;
|
use crate::operation_context::OperationContext;
|
||||||
use crate::parse_constants::ParseErrorList;
|
use crate::parse_constants::ParseErrorList;
|
||||||
use crate::parse_util::parse_util_detect_errors_in_argument_list;
|
use crate::parse_util::parse_util_detect_errors_in_argument_list;
|
||||||
@@ -18,7 +16,6 @@
|
|||||||
complete_remove, complete_remove_all,
|
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
|
// 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
|
// 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);
|
let repr = complete_print(cmd);
|
||||||
|
|
||||||
// colorize if interactive
|
if color.enabled(streams) {
|
||||||
if !streams.out_is_redirected && isatty(STDOUT_FILENO) {
|
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||||
let mut colors = vec![];
|
&repr,
|
||||||
highlight_shell(&repr, &mut colors, &parser.context(), false, None);
|
&parser.context(),
|
||||||
streams
|
parser.vars(),
|
||||||
.out
|
)));
|
||||||
.append(&bytes2wcstring(&colorize(&repr, &colors, parser.vars())));
|
|
||||||
} else {
|
} else {
|
||||||
streams.out.append(&repr);
|
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 wrap_targets = vec![];
|
||||||
let mut preserve_order = false;
|
let mut preserve_order = false;
|
||||||
let mut unescape_output = true;
|
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");
|
let short_options: &wstr = L!("a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
|
||||||
const long_options: &[WOption] = &[
|
let long_options: &[WOption] = &[
|
||||||
wopt(L!("exclusive"), ArgType::NoArgument, 'x'),
|
wopt(L!("exclusive"), ArgType::NoArgument, 'x'),
|
||||||
wopt(L!("no-files"), ArgType::NoArgument, 'f'),
|
wopt(L!("no-files"), ArgType::NoArgument, 'f'),
|
||||||
wopt(L!("force-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!("help"), ArgType::NoArgument, 'h'),
|
||||||
wopt(L!("keep-order"), ArgType::NoArgument, 'k'),
|
wopt(L!("keep-order"), ArgType::NoArgument, 'k'),
|
||||||
wopt(L!("escape"), ArgType::NoArgument, OPT_ESCAPE),
|
wopt(L!("escape"), ArgType::NoArgument, OPT_ESCAPE),
|
||||||
|
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut have_x = false;
|
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);
|
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
}
|
}
|
||||||
|
COLOR_OPTION_CHAR => {
|
||||||
|
color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||||
|
}
|
||||||
_ => panic!("unexpected retval from WGetopter"),
|
_ => 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
|
// No arguments that would add or remove anything specified, so we print the definitions of
|
||||||
// all matching completions.
|
// all matching completions.
|
||||||
if cmd_to_complete.is_empty() {
|
if cmd_to_complete.is_empty() {
|
||||||
builtin_complete_print(L!(""), streams, parser);
|
builtin_complete_print(L!(""), streams, parser, color);
|
||||||
} else {
|
} else {
|
||||||
for cmd in cmd_to_complete {
|
for cmd in cmd_to_complete {
|
||||||
builtin_complete_print(&cmd, streams, parser);
|
builtin_complete_print(&cmd, streams, parser, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
|||||||
if job.is_stopped() {
|
if job.is_stopped() {
|
||||||
handoff.save_tty_modes();
|
handoff.save_tty_modes();
|
||||||
}
|
}
|
||||||
handoff.reclaim();
|
|
||||||
if resumed {
|
if resumed {
|
||||||
Ok(SUCCESS)
|
Ok(SUCCESS)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::ast::{self, AsNode, Ast, Kind, Leaf, Node, NodeVisitor, SourceRangeList, Traversal};
|
use crate::ast::{self, AsNode, Ast, Kind, Leaf, Node, NodeVisitor, SourceRangeList, Traversal};
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
PROGRAM_NAME, UnescapeFlags, UnescapeStringStyle, bytes2wcstring, get_program_name,
|
PROGRAM_NAME, ReadExt, UnescapeFlags, UnescapeStringStyle, bytes2wcstring, get_program_name,
|
||||||
unescape_string, wcs2bytes,
|
unescape_string, wcs2bytes,
|
||||||
};
|
};
|
||||||
use crate::env::EnvStack;
|
use crate::env::EnvStack;
|
||||||
@@ -756,6 +756,10 @@ fn brace_is_continuation(&self, node: &dyn ast::Token) -> bool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
conj.decorator.is_some()
|
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) {
|
fn visit_left_brace(&mut self, node: &dyn ast::Token) {
|
||||||
@@ -910,10 +914,9 @@ pub fn main() {
|
|||||||
|
|
||||||
fn throwing_main() -> i32 {
|
fn throwing_main() -> i32 {
|
||||||
// TODO: Duplicated with fish_key_reader
|
// TODO: Duplicated with fish_key_reader
|
||||||
use crate::io::FdOutputStream;
|
use crate::fds::BorrowedFdFile;
|
||||||
use crate::io::IoChain;
|
use crate::io::{FdOutputStream, IoChain, OutputStream::Fd};
|
||||||
use crate::io::OutputStream::Fd;
|
use libc::{STDERR_FILENO, STDOUT_FILENO};
|
||||||
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
|
||||||
|
|
||||||
topic_monitor_init();
|
topic_monitor_init();
|
||||||
threads::init();
|
threads::init();
|
||||||
@@ -922,12 +925,13 @@ fn throwing_main() -> i32 {
|
|||||||
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
||||||
let io_chain = IoChain::new();
|
let io_chain = IoChain::new();
|
||||||
let mut streams = IoStreams::new(&mut out, &mut err, &io_chain);
|
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.
|
// Safety: single-threaded.
|
||||||
unsafe {
|
unsafe {
|
||||||
set_libc_locales(/*log_ok=*/ false)
|
set_libc_locales(/*log_ok=*/ false)
|
||||||
};
|
};
|
||||||
crate::localization::initialize_gettext();
|
#[cfg(feature = "localize-messages")]
|
||||||
|
crate::localization::initialize_localization();
|
||||||
env_init(None, true, false);
|
env_init(None, true, false);
|
||||||
|
|
||||||
// Only set these here so you can't set them via the builtin.
|
// 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()
|
let args: Vec<WString> = std::env::args_os()
|
||||||
.map(|osstr| bytes2wcstring(osstr.as_bytes()))
|
.map(|osstr| bytes2wcstring(osstr.as_bytes()))
|
||||||
.collect();
|
.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();
|
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
|
// Types of output we support
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
enum OutputType {
|
enum OutputType {
|
||||||
@@ -988,7 +996,11 @@ enum OutputType {
|
|||||||
match c {
|
match c {
|
||||||
'P' => DUMP_PARSE_TREE.store(true),
|
'P' => DUMP_PARSE_TREE.store(true),
|
||||||
'h' => {
|
'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);
|
return Ok(SUCCESS);
|
||||||
}
|
}
|
||||||
'v' => {
|
'v' => {
|
||||||
@@ -1042,18 +1054,24 @@ enum OutputType {
|
|||||||
));
|
));
|
||||||
return Err(STATUS_CMD_ERROR);
|
return Err(STATUS_CMD_ERROR);
|
||||||
}
|
}
|
||||||
use std::os::fd::FromRawFd;
|
let Some(stdin_file) = streams.stdin_file.as_mut() else {
|
||||||
let mut fd = unsafe { std::fs::File::from_raw_fd(streams.stdin_fd) };
|
let cmd = "fish_indent";
|
||||||
|
streams
|
||||||
|
.err
|
||||||
|
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
||||||
|
return Err(STATUS_CMD_ERROR);
|
||||||
|
};
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
match fd.read_to_end(&mut buf) {
|
match stdin_file.read_to_end_interruptible(&mut buf) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(err) => {
|
||||||
// Don't close the fd
|
return if err.kind() == std::io::ErrorKind::Interrupted {
|
||||||
std::mem::forget(fd);
|
Err(128 + libc::SIGINT)
|
||||||
return Err(STATUS_CMD_ERROR);
|
} else {
|
||||||
|
Err(STATUS_CMD_ERROR)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::mem::forget(fd);
|
|
||||||
src = bytes2wcstring(&buf);
|
src = bytes2wcstring(&buf);
|
||||||
} else {
|
} else {
|
||||||
let arg = args[i];
|
let arg = args[i];
|
||||||
@@ -1250,7 +1268,7 @@ struct TokenRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut token_ranges: Vec<TokenRange> = vec![];
|
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;
|
let role = color.foreground;
|
||||||
// See if we can extend the last range.
|
// See if we can extend the last range.
|
||||||
if let Some(last) = token_ranges.last_mut() {
|
if let Some(last) = token_ranges.last_mut() {
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ fn setup_and_process_keys(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_flags(
|
fn parse_flags(
|
||||||
|
parser: Option<&Parser>,
|
||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
args: Vec<WString>,
|
args: Vec<WString>,
|
||||||
continuous_mode: &mut bool,
|
continuous_mode: &mut bool,
|
||||||
@@ -184,7 +185,6 @@ fn parse_flags(
|
|||||||
wopt(L!("version"), ArgType::NoArgument, 'v'),
|
wopt(L!("version"), ArgType::NoArgument, 'v'),
|
||||||
wopt(L!("verbose"), 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 shim_args: Vec<&wstr> = args.iter().map(|s| s.as_ref()).collect();
|
||||||
let mut w = WGetopter::new(short_opts, long_opts, &mut shim_args);
|
let mut w = WGetopter::new(short_opts, long_opts, &mut shim_args);
|
||||||
while let Some(opt) = w.next_opt() {
|
while let Some(opt) = w.next_opt() {
|
||||||
@@ -193,7 +193,11 @@ fn parse_flags(
|
|||||||
*continuous_mode = true;
|
*continuous_mode = true;
|
||||||
}
|
}
|
||||||
'h' => {
|
'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));
|
return ControlFlow::Break(Ok(SUCCESS));
|
||||||
}
|
}
|
||||||
'v' => {
|
'v' => {
|
||||||
@@ -239,7 +243,7 @@ fn parse_flags(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fish_key_reader(
|
pub fn fish_key_reader(
|
||||||
_parser: &Parser,
|
parser: &Parser,
|
||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
args: &mut [&wstr],
|
args: &mut [&wstr],
|
||||||
) -> BuiltinResult {
|
) -> BuiltinResult {
|
||||||
@@ -247,11 +251,17 @@ pub fn fish_key_reader(
|
|||||||
let mut verbose = false;
|
let mut verbose = false;
|
||||||
|
|
||||||
let args = args.iter_mut().map(|x| x.to_owned()).collect();
|
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;
|
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.");
|
streams.err.appendln("Stdin must be attached to a tty.");
|
||||||
return Err(STATUS_CMD_ERROR);
|
return Err(STATUS_CMD_ERROR);
|
||||||
}
|
}
|
||||||
@@ -261,7 +271,7 @@ pub fn fish_key_reader(
|
|||||||
continuous_mode,
|
continuous_mode,
|
||||||
verbose,
|
verbose,
|
||||||
// Won't be querying, so no timeout value needed.
|
// 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);
|
set_interactive_session(true);
|
||||||
topic_monitor_init();
|
topic_monitor_init();
|
||||||
threads::init();
|
threads::init();
|
||||||
crate::localization::initialize_gettext();
|
#[cfg(feature = "localize-messages")]
|
||||||
|
crate::localization::initialize_localization();
|
||||||
env_init(None, true, false);
|
env_init(None, true, false);
|
||||||
reader_init(false);
|
reader_init(false);
|
||||||
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
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()))
|
.map(|osstr| bytes2wcstring(osstr.as_bytes()))
|
||||||
.collect();
|
.collect();
|
||||||
if let ControlFlow::Break(s) =
|
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();
|
return s.builtin_status_code();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
use crate::common::{EscapeFlags, EscapeStringStyle};
|
use crate::common::{EscapeFlags, EscapeStringStyle};
|
||||||
use crate::event::{self};
|
use crate::event::{self};
|
||||||
use crate::function;
|
use crate::function;
|
||||||
use crate::highlight::colorize;
|
use crate::highlight::highlight_and_colorize;
|
||||||
use crate::highlight::highlight_shell;
|
|
||||||
use crate::parse_util::apply_indents;
|
use crate::parse_util::apply_indents;
|
||||||
use crate::parse_util::parse_util_compute_indents;
|
use crate::parse_util::parse_util_compute_indents;
|
||||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||||
@@ -25,6 +24,7 @@ struct FunctionsCmdOpts<'args> {
|
|||||||
no_metadata: bool,
|
no_metadata: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
handlers: bool,
|
handlers: bool,
|
||||||
|
color: ColorEnabled,
|
||||||
handlers_type: Option<&'args wstr>,
|
handlers_type: Option<&'args wstr>,
|
||||||
description: Option<&'args wstr>,
|
description: Option<&'args wstr>,
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,7 @@ struct FunctionsCmdOpts<'args> {
|
|||||||
wopt(L!("verbose"), ArgType::NoArgument, 'v'),
|
wopt(L!("verbose"), ArgType::NoArgument, 'v'),
|
||||||
wopt(L!("handlers"), ArgType::NoArgument, 'H'),
|
wopt(L!("handlers"), ArgType::NoArgument, 'H'),
|
||||||
wopt(L!("handlers-type"), ArgType::RequiredArgument, 't'),
|
wopt(L!("handlers-type"), ArgType::RequiredArgument, 't'),
|
||||||
|
wopt(L!("color"), ArgType::RequiredArgument, COLOR_OPTION_CHAR),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Parses options to builtin function, populating opts.
|
/// Parses options to builtin function, populating opts.
|
||||||
@@ -79,6 +80,9 @@ fn parse_cmd_opts<'args>(
|
|||||||
opts.handlers = true;
|
opts.handlers = true;
|
||||||
opts.handlers_type = Some(w.woptarg.unwrap());
|
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);
|
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
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() {
|
if opts.list || args.is_empty() {
|
||||||
let mut names = function::get_names(opts.show_hidden, parser.vars());
|
let mut names = function::get_names(opts.show_hidden, parser.vars());
|
||||||
names.sort();
|
names.sort();
|
||||||
if streams.out_is_terminal() {
|
if opts.color.enabled(streams) {
|
||||||
let mut buff = WString::new();
|
let mut buff = WString::new();
|
||||||
let mut first: bool = true;
|
let mut first: bool = true;
|
||||||
for name in names {
|
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));
|
def = apply_indents(&def, &parse_util_compute_indents(&def));
|
||||||
}
|
}
|
||||||
|
|
||||||
if streams.out_is_terminal() {
|
if opts.color.enabled(streams) {
|
||||||
let mut colors = vec![];
|
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||||
highlight_shell(&def, &mut colors, &parser.context(), false, None);
|
&def,
|
||||||
streams
|
&parser.context(),
|
||||||
.out
|
parser.vars(),
|
||||||
.append(&bytes2wcstring(&colorize(&def, &colors, parser.vars())));
|
)));
|
||||||
} else {
|
} else {
|
||||||
streams.out.append(&def);
|
streams.out.append(&def);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ struct HistoryCmdOpts {
|
|||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
null_terminate: bool,
|
null_terminate: bool,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
|
color: ColorEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note: Do not add new flags that represent subcommands. We're encouraging people to switch to
|
/// 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!("clear"), ArgType::NoArgument, '\x04'),
|
||||||
wopt(L!("merge"), ArgType::NoArgument, '\x05'),
|
wopt(L!("merge"), ArgType::NoArgument, '\x05'),
|
||||||
wopt(L!("reverse"), ArgType::NoArgument, 'R'),
|
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.
|
/// Remember the history subcommand and disallow selecting more than one history subcommand.
|
||||||
@@ -226,6 +228,9 @@ fn parse_cmd_opts(
|
|||||||
}
|
}
|
||||||
w.remaining_text = L!("");
|
w.remaining_text = L!("");
|
||||||
}
|
}
|
||||||
|
COLOR_OPTION_CHAR => {
|
||||||
|
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("unexpected retval from WGetopter");
|
panic!("unexpected retval from WGetopter");
|
||||||
}
|
}
|
||||||
@@ -278,6 +283,8 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
|||||||
match opts.hist_cmd {
|
match opts.hist_cmd {
|
||||||
HistCmd::None | HistCmd::Search => {
|
HistCmd::None | HistCmd::Search => {
|
||||||
if !history.search(
|
if !history.search(
|
||||||
|
parser,
|
||||||
|
streams,
|
||||||
opts.search_type
|
opts.search_type
|
||||||
.unwrap_or(history::SearchType::ContainsGlob),
|
.unwrap_or(history::SearchType::ContainsGlob),
|
||||||
args,
|
args,
|
||||||
@@ -287,7 +294,7 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
|||||||
opts.null_terminate,
|
opts.null_terminate,
|
||||||
opts.reverse,
|
opts.reverse,
|
||||||
&parser.context().cancel_checker,
|
&parser.context().cancel_checker,
|
||||||
streams,
|
opts.color.enabled(streams),
|
||||||
) {
|
) {
|
||||||
status = Err(STATUS_CMD_ERROR);
|
status = Err(STATUS_CMD_ERROR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
use crate::util::get_seeded_rng;
|
use crate::util::get_seeded_rng;
|
||||||
use crate::wutil;
|
use crate::wutil;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use rand::rngs::SmallRng;
|
use rand::rngs::SmallRng;
|
||||||
use rand::{Rng, RngCore};
|
use rand::{Rng, RngCore};
|
||||||
use std::sync::Mutex;
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
static RNG: Lazy<Mutex<SmallRng>> =
|
static RNG: LazyLock<Mutex<SmallRng>> =
|
||||||
Lazy::new(|| Mutex::new(get_seeded_rng(rand::rng().next_u64())));
|
LazyLock::new(|| Mutex::new(get_seeded_rng(rand::rng().next_u64())));
|
||||||
|
|
||||||
pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
||||||
let cmd = argv[0];
|
let cmd = argv[0];
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
validate_read_args(cmd, &mut opts, argv, parser, streams)?;
|
validate_read_args(cmd, &mut opts, argv, parser, streams)?;
|
||||||
|
|
||||||
// stdin may have been explicitly closed
|
// stdin may have been explicitly closed
|
||||||
if streams.stdin_fd < 0 {
|
if streams.is_stdin_closed() {
|
||||||
streams
|
streams
|
||||||
.err
|
.err
|
||||||
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
||||||
@@ -627,7 +627,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
// 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
|
// line at a time, we need a middle ground where we only consume as many lines as we need to
|
||||||
@@ -646,7 +646,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
opts.prompt.as_ref().unwrap(),
|
opts.prompt.as_ref().unwrap(),
|
||||||
&opts.right_prompt,
|
&opts.right_prompt,
|
||||||
&opts.commandline,
|
&opts.commandline,
|
||||||
streams.stdin_fd,
|
streams.stdin_fd(),
|
||||||
);
|
);
|
||||||
} else if opts.nchars.is_none() && !stream_stdin_is_a_tty &&
|
} else if opts.nchars.is_none() && !stream_stdin_is_a_tty &&
|
||||||
// "one_line" is implemented as reading n-times to a new line,
|
// "one_line" is implemented as reading n-times to a new line,
|
||||||
@@ -655,7 +655,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
!opts.one_line &&
|
!opts.one_line &&
|
||||||
(
|
(
|
||||||
streams.stdin_is_directly_redirected ||
|
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),
|
// 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).
|
// or we have the bytes to ourselves (because it's directly redirected).
|
||||||
@@ -665,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.
|
// You don't rewind VHS tapes before throwing them in the trash.
|
||||||
// TODO: Do this when nchars is set by seeking back.
|
// TODO: Do this when nchars is set by seeking back.
|
||||||
exit_res = read_in_chunks(
|
exit_res = read_in_chunks(
|
||||||
streams.stdin_fd,
|
streams.stdin_fd(),
|
||||||
&mut buff,
|
&mut buff,
|
||||||
opts.split_null,
|
opts.split_null,
|
||||||
!streams.stdin_is_directly_redirected,
|
!streams.stdin_is_directly_redirected,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
exit_res =
|
exit_res = read_one_char_at_a_time(
|
||||||
read_one_char_at_a_time(streams.stdin_fd, &mut buff, opts.nchars, opts.split_null);
|
streams.stdin_fd(),
|
||||||
|
&mut buff,
|
||||||
|
opts.nchars,
|
||||||
|
opts.split_null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if exit_res.is_err() {
|
if exit_res.is_err() {
|
||||||
|
|||||||
@@ -890,7 +890,7 @@ fn new_var_values(
|
|||||||
// So do not use the given variable: we must re-fetch it.
|
// So do not use the given variable: we must re-fetch it.
|
||||||
// TODO: this races under concurrent execution.
|
// TODO: this races under concurrent execution.
|
||||||
if let Some(existing) = vars.get(varname) {
|
if let Some(existing) = vars.get(varname) {
|
||||||
result = existing.as_list().to_owned();
|
existing.as_list().clone_into(&mut result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.prepend {
|
if opts.prepend {
|
||||||
@@ -914,10 +914,12 @@ fn new_var_values_by_index(split: &SplitVar, argv: &[&wstr]) -> Vec<WString> {
|
|||||||
// Inherit any existing values.
|
// Inherit any existing values.
|
||||||
// Note unlike the append/prepend case, we start with a variable in the same scope as we are
|
// Note unlike the append/prepend case, we start with a variable in the same scope as we are
|
||||||
// setting.
|
// setting.
|
||||||
let mut result = vec![];
|
let mut result = split
|
||||||
if let Some(var) = split.var.as_ref() {
|
.var
|
||||||
result = var.as_list().to_owned();
|
.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.
|
// 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.
|
// Extend the list with empty strings as needed. The indexes are 1-based.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::common::{Named, bytes2wcstring, escape, get_by_sorted_name};
|
use crate::common::{Named, bytes2wcstring, escape, get_by_sorted_name};
|
||||||
|
use crate::fds::BorrowedFdFile;
|
||||||
use crate::io::OutputStream;
|
use crate::io::OutputStream;
|
||||||
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
|
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
|
||||||
use crate::parse_util::parse_util_argument_is_help;
|
use crate::parse_util::parse_util_argument_is_help;
|
||||||
@@ -8,10 +9,7 @@
|
|||||||
use crate::{builtins::*, wutil};
|
use crate::{builtins::*, wutil};
|
||||||
use errno::errno;
|
use errno::errno;
|
||||||
use fish_wchar::L;
|
use fish_wchar::L;
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{BufRead, BufReader, Read};
|
use std::io::{BufRead, BufReader, Read};
|
||||||
use std::os::fd::FromRawFd;
|
|
||||||
|
|
||||||
pub type BuiltinCmd = fn(&Parser, &mut IoStreams, &mut [&wstr]) -> BuiltinResult;
|
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.
|
/// A helper type for extracting arguments from either argv or stdin.
|
||||||
pub struct Arguments<'args, 'iter> {
|
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,
|
split_behavior: SplitBehavior,
|
||||||
/// Buffer to store what we read with the BufReader
|
source: ArgvSource<'args, 'iter>,
|
||||||
/// Is only here to avoid allocating every time
|
|
||||||
buffer: Vec<u8>,
|
|
||||||
/// If not using argv, we read with a buffer
|
|
||||||
reader: Option<BufReader<File>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Arguments<'_, '_> {
|
/// Either the arguments from argv, or from stdin.
|
||||||
fn drop(&mut self) {
|
enum ArgvSource<'args, 'iter> {
|
||||||
if let Some(r) = self.reader.take() {
|
/// Read arguments from argv.
|
||||||
// we should not close stdin
|
Args {
|
||||||
std::mem::forget(r.into_inner());
|
// 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> {
|
impl<'args, 'iter> Arguments<'args, 'iter> {
|
||||||
@@ -842,20 +841,21 @@ pub fn new(
|
|||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
chunk_size: usize,
|
chunk_size: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let reader = streams.stdin_is_directly_redirected.then(|| {
|
let source: ArgvSource = if !streams.stdin_is_directly_redirected {
|
||||||
let stdin_fd = streams.stdin_fd;
|
ArgvSource::Args { args, argidx }
|
||||||
assert!(stdin_fd >= 0, "should have a valid fd");
|
} else {
|
||||||
// safety: this should be a valid fd, and already open
|
let stdin_file = streams
|
||||||
let fd = unsafe { File::from_raw_fd(stdin_fd) };
|
.stdin_file
|
||||||
BufReader::with_capacity(chunk_size, fd)
|
.clone()
|
||||||
});
|
.expect("should have stdin if redirected");
|
||||||
|
ArgvSource::Stdin {
|
||||||
|
buffer: Vec::new(),
|
||||||
|
reader: BufReader::with_capacity(chunk_size, stdin_file),
|
||||||
|
}
|
||||||
|
};
|
||||||
Arguments {
|
Arguments {
|
||||||
args,
|
|
||||||
argidx,
|
|
||||||
split_behavior: SplitBehavior::Newline,
|
split_behavior: SplitBehavior::Newline,
|
||||||
buffer: Vec::new(),
|
source,
|
||||||
reader,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,9 +864,23 @@ pub fn with_split_behavior(mut self, split_behavior: SplitBehavior) -> Self {
|
|||||||
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>> {
|
fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
||||||
use SplitBehavior::*;
|
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 {
|
if self.split_behavior == InferNull {
|
||||||
// we must determine if the first `PATH_MAX` bytes contains a null.
|
// 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
|
// NOTE: C++ wrongly commented that read_blocked retries for EAGAIN
|
||||||
let num_bytes: usize = match self.split_behavior {
|
let num_bytes: usize = match self.split_behavior {
|
||||||
Newline => reader.read_until(b'\n', &mut self.buffer),
|
Newline => reader.read_until(b'\n', buffer),
|
||||||
Null => reader.read_until(b'\0', &mut self.buffer),
|
Null => reader.read_until(b'\0', buffer),
|
||||||
Never => reader.read_to_end(&mut self.buffer),
|
Never => reader.read_to_end(buffer),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
.ok()?;
|
.ok()?;
|
||||||
@@ -895,7 +909,7 @@ fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// assert!(num_bytes == self.buffer.len());
|
// 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
|
// remove the newline — consumers do not expect it
|
||||||
(Newline, Some(b'\n')) => (num_bytes - 1, true),
|
(Newline, Some(b'\n')) => (num_bytes - 1, true),
|
||||||
// we are missing a trailing newline!
|
// we are missing a trailing newline!
|
||||||
@@ -909,8 +923,8 @@ fn get_arg_stdin(&mut self) -> Option<InputValue<'args>> {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let parsed = bytes2wcstring(&self.buffer[..end]);
|
let parsed = bytes2wcstring(&buffer[..end]);
|
||||||
self.buffer.clear();
|
buffer.clear();
|
||||||
|
|
||||||
Some(InputValue::new(Cow::Owned(parsed), want_newline))
|
Some(InputValue::new(Cow::Owned(parsed), want_newline))
|
||||||
}
|
}
|
||||||
@@ -925,19 +939,10 @@ impl<'args> Iterator for Arguments<'args, '_> {
|
|||||||
type Item = InputValue<'args>;
|
type Item = InputValue<'args>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.reader.is_some() {
|
match &mut self.source {
|
||||||
return self.get_arg_stdin();
|
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)
|
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;
|
let optind = opts.optind;
|
||||||
|
|
||||||
if argc == optind || args[optind] == "-" {
|
if argc == optind || args[optind] == "-" {
|
||||||
if streams.stdin_fd < 0 {
|
if streams.is_stdin_closed() {
|
||||||
streams
|
streams
|
||||||
.err
|
.err
|
||||||
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
.append(&wgettext_fmt!("%s: stdin is closed\n", cmd));
|
||||||
return Err(STATUS_CMD_ERROR);
|
return Err(STATUS_CMD_ERROR);
|
||||||
}
|
}
|
||||||
// Either a bare `source` which means to implicitly read from stdin or an explicit `-`.
|
// 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.
|
// Don't implicitly read from the terminal.
|
||||||
streams.err.append(&wgettext_fmt!(
|
streams.err.append(&wgettext_fmt!(
|
||||||
"%s: missing filename argument or input redirection\n",
|
"%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);
|
return Err(STATUS_CMD_ERROR);
|
||||||
}
|
}
|
||||||
func_filename = FilenameRef::new(L!("-").to_owned());
|
func_filename = FilenameRef::new(L!("-").to_owned());
|
||||||
fd = streams.stdin_fd;
|
fd = streams.stdin_fd();
|
||||||
} else {
|
} else {
|
||||||
match wopen_cloexec(args[optind], OFlag::O_RDONLY, Mode::empty()) {
|
match wopen_cloexec(args[optind], OFlag::O_RDONLY, Mode::empty()) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
|
|||||||
@@ -202,10 +202,6 @@ fn parse_cmd_opts(
|
|||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
) -> BuiltinResult {
|
) -> BuiltinResult {
|
||||||
let cmd = args[0];
|
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);
|
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, args);
|
||||||
while let Some(c) = w.next_opt() {
|
while let Some(c) = w.next_opt() {
|
||||||
match c {
|
match c {
|
||||||
|
|||||||
@@ -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
|
// empty/null members so we're going to have to use an empty string as the
|
||||||
// sentinel value.
|
// 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()));
|
captures.push(WString::from(m.as_bytes()));
|
||||||
} else if opts.all {
|
} else if opts.all {
|
||||||
captures.push(WString::new());
|
captures.push(WString::new());
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ mod test_expressions {
|
|||||||
Error, Options, file_id_for_path, fish_wcswidth, lwstat, waccess, wcstod::wcstod,
|
Error, Options, file_id_for_path, fish_wcswidth, lwstat, waccess, wcstod::wcstod,
|
||||||
wcstoi_opts, wstat,
|
wcstoi_opts, wstat,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::unix::prelude::*;
|
use std::os::unix::prelude::*;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub(super) enum Token {
|
pub(super) enum Token {
|
||||||
@@ -164,7 +164,7 @@ fn isatty(&self, streams: &mut IoStreams) -> bool {
|
|||||||
}
|
}
|
||||||
let bint = self.base as i32;
|
let bint = self.base as i32;
|
||||||
if bint == 0 {
|
if bint == 0 {
|
||||||
match streams.stdin_fd {
|
match streams.stdin_fd() {
|
||||||
-1 => false,
|
-1 => false,
|
||||||
fd => isatty(fd),
|
fd => isatty(fd),
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ fn token_for_string(str: &wstr) -> Token {
|
|||||||
TOKEN_INFOS.get(str).copied().unwrap_or(Token::Unknown)
|
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 = [
|
let pairs = [
|
||||||
(L!(""), Token::Unknown),
|
(L!(""), Token::Unknown),
|
||||||
(L!("!"), Token::UnaryBoolean(UnaryBooleanToken::Bang)),
|
(L!("!"), Token::UnaryBoolean(UnaryBooleanToken::Bang)),
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::common::bytes2wcstring;
|
use crate::common::bytes2wcstring;
|
||||||
use crate::function;
|
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::parse_util::{apply_indents, parse_util_compute_indents};
|
||||||
use crate::path::{path_get_path, path_get_paths};
|
use crate::path::{path_get_path, path_get_paths};
|
||||||
|
|
||||||
@@ -15,6 +14,7 @@ struct type_cmd_opts_t {
|
|||||||
path: bool,
|
path: bool,
|
||||||
force_path: bool,
|
force_path: bool,
|
||||||
query: bool,
|
query: bool,
|
||||||
|
color: ColorEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
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!("force-path"), ArgType::NoArgument, 'P'),
|
||||||
wopt(L!("query"), ArgType::NoArgument, 'q'),
|
wopt(L!("query"), ArgType::NoArgument, 'q'),
|
||||||
wopt(L!("quiet"), 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);
|
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);
|
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
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()");
|
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));
|
def = apply_indents(&def, &parse_util_compute_indents(&def));
|
||||||
}
|
}
|
||||||
|
|
||||||
if streams.out_is_terminal() {
|
if opts.color.enabled(streams) {
|
||||||
let mut color = vec![];
|
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||||
highlight_shell(
|
|
||||||
&def,
|
&def,
|
||||||
&mut color,
|
|
||||||
&parser.context(),
|
&parser.context(),
|
||||||
/*io_ok=*/ false,
|
parser.vars(),
|
||||||
/*cursor=*/ None,
|
)));
|
||||||
);
|
|
||||||
let col = bytes2wcstring(&colorize(&def, &color, parser.vars()));
|
|
||||||
streams.out.append(&col);
|
|
||||||
} else {
|
} else {
|
||||||
streams.out.append(&def);
|
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 libc::{RLIM_INFINITY, c_uint, rlim_t};
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
use nix::sys::resource::Resource as ResourceEnum;
|
use nix::sys::resource::Resource as ResourceEnum;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::wutil::perror;
|
use crate::wutil::perror;
|
||||||
use fish_fallback::{fish_wcswidth, wcscasecmp};
|
use fish_fallback::{fish_wcswidth, wcscasecmp};
|
||||||
@@ -434,7 +433,7 @@ fn new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Array of resource_t structs, describing all known resource types.
|
/// 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 = [
|
let resources_info = [
|
||||||
(
|
(
|
||||||
limits::SBSIZE,
|
limits::SBSIZE,
|
||||||
|
|||||||
@@ -20,15 +20,15 @@
|
|||||||
use fish_fallback::fish_wcwidth;
|
use fish_fallback::fish_wcwidth;
|
||||||
use fish_wchar::{decode_byte_from_char, encode_byte_to_char};
|
use fish_wchar::{decode_byte_from_char, encode_byte_to_char};
|
||||||
use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO};
|
use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO};
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{CStr, CString, OsString};
|
use std::ffi::{CStr, CString, OsString};
|
||||||
|
use std::io::Read;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::os::unix::prelude::*;
|
use std::os::unix::prelude::*;
|
||||||
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
|
||||||
use std::sync::{Arc, MutexGuard};
|
use std::sync::{Arc, MutexGuard, OnceLock};
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
pub const BUILD_DIR: &str = env!("FISH_RESOLVED_BUILD_DIR");
|
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(&"");
|
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);
|
static OBFUSCATION_READ_CHAR: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
pub fn get_obfuscation_read_char() -> char {
|
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);
|
pub static PROFILING_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||||
|
|
||||||
/// Name of the current program. Should be set at startup. Used by the debug function.
|
/// 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 {
|
pub fn get_program_name() -> &'static wstr {
|
||||||
PROGRAM_NAME.get().unwrap()
|
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.
|
/// Test if the string is a valid function name.
|
||||||
pub fn valid_func_name(name: &wstr) -> bool {
|
pub fn valid_func_name(name: &wstr) -> bool {
|
||||||
!(name.is_empty()
|
!(name.is_empty()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
mem,
|
mem,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
sync::{
|
sync::{
|
||||||
Mutex, MutexGuard,
|
LazyLock, Mutex, MutexGuard,
|
||||||
atomic::{self, AtomicUsize},
|
atomic::{self, AtomicUsize},
|
||||||
},
|
},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
@@ -55,7 +55,6 @@
|
|||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use fish_wchar::WExt;
|
use fish_wchar::WExt;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
// Completion description strings, mostly for different types of files, such as sockets, block
|
// Completion description strings, mostly for different types of files, such as sockets, block
|
||||||
// devices, etc.
|
// devices, etc.
|
||||||
@@ -207,7 +206,7 @@ pub fn rank(&self) -> u32 {
|
|||||||
|
|
||||||
/// If this completion replaces the entire token, prepend a prefix. Otherwise do nothing.
|
/// If this completion replaces the entire token, prepend a prefix. Otherwise do nothing.
|
||||||
pub fn prepend_token_prefix(&mut self, prefix: &wstr) {
|
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)
|
self.completion.insert_utfstr(0, prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,7 +450,7 @@ struct CompletionEntryIndex {
|
|||||||
|
|
||||||
/// Completion "wrapper" support. The map goes from wrapping-command to wrapped-command-list.
|
/// Completion "wrapper" support. The map goes from wrapping-command to wrapped-command-list.
|
||||||
type WrapperMap = HashMap<WString, Vec<WString>>;
|
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
|
/// Clear the [`CompleteFlags::AUTO_SPACE`] flag, and set [`CompleteFlags::NO_SPACE`] appropriately
|
||||||
/// depending on the suffix of the string.
|
/// depending on the suffix of the string.
|
||||||
@@ -606,8 +605,8 @@ struct Completer<'ctx> {
|
|||||||
condition_cache: HashMap<WString, bool>,
|
condition_cache: HashMap<WString, bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static COMPLETION_AUTOLOADER: Lazy<Mutex<Autoload>> =
|
static COMPLETION_AUTOLOADER: LazyLock<Mutex<Autoload>> =
|
||||||
Lazy::new(|| Mutex::new(Autoload::new(L!("fish_complete_path"))));
|
LazyLock::new(|| Mutex::new(Autoload::new(L!("fish_complete_path"))));
|
||||||
|
|
||||||
impl<'ctx> Completer<'ctx> {
|
impl<'ctx> Completer<'ctx> {
|
||||||
pub fn new(ctx: &'ctx OperationContext<'ctx>, flags: CompletionRequestOptions) -> Self {
|
pub fn new(ctx: &'ctx OperationContext<'ctx>, flags: CompletionRequestOptions) -> Self {
|
||||||
@@ -2093,7 +2092,7 @@ fn escape_opening_brackets(completions: &mut [Completion], argument: &wstr) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
for comp in completions {
|
for comp in completions {
|
||||||
if comp.flags.contains(CompleteFlags::REPLACES_TOKEN) {
|
if comp.replaces_token() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
comp.flags |= CompleteFlags::REPLACES_TOKEN;
|
comp.flags |= CompleteFlags::REPLACES_TOKEN;
|
||||||
@@ -2128,7 +2127,7 @@ fn mark_completions_duplicating_arguments(
|
|||||||
let mut comp_str;
|
let mut comp_str;
|
||||||
for comp in self.completions.get_list_mut() {
|
for comp in self.completions.get_list_mut() {
|
||||||
comp_str = comp.completion.clone();
|
comp_str = comp.completion.clone();
|
||||||
if !comp.flags.contains(CompleteFlags::REPLACES_TOKEN) {
|
if !comp.replaces_token() {
|
||||||
comp_str.insert_utfstr(0, prefix);
|
comp_str.insert_utfstr(0, prefix);
|
||||||
}
|
}
|
||||||
if arg_strs.binary_search(&comp_str).is_ok() {
|
if arg_strs.binary_search(&comp_str).is_ok() {
|
||||||
@@ -3000,7 +2999,7 @@ macro_rules! unique_completion_applies_as {
|
|||||||
let completions = do_complete(L!("cat te"), CompletionRequestOptions::default());
|
let completions = do_complete(L!("cat te"), CompletionRequestOptions::default());
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].completion, L!("stfile"));
|
assert_eq!(completions[0].completion, L!("stfile"));
|
||||||
assert!(!(completions[0].flags.contains(CompleteFlags::REPLACES_TOKEN)));
|
assert!(!completions[0].replaces_token());
|
||||||
assert!(
|
assert!(
|
||||||
!(completions[0]
|
!(completions[0]
|
||||||
.flags
|
.flags
|
||||||
@@ -3017,7 +3016,7 @@ macro_rules! unique_completion_applies_as {
|
|||||||
let completions = do_complete(L!("cat testfile TE"), CompletionRequestOptions::default());
|
let completions = do_complete(L!("cat testfile TE"), CompletionRequestOptions::default());
|
||||||
assert_eq!(completions.len(), 1);
|
assert_eq!(completions.len(), 1);
|
||||||
assert_eq!(completions[0].completion, L!("testfile"));
|
assert_eq!(completions[0].completion, L!("testfile"));
|
||||||
assert!(completions[0].flags.contains(CompleteFlags::REPLACES_TOKEN));
|
assert!(completions[0].replaces_token());
|
||||||
assert!(
|
assert!(
|
||||||
completions[0]
|
completions[0]
|
||||||
.flags
|
.flags
|
||||||
|
|||||||
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::common::{BUILD_DIR, get_program_name};
|
||||||
use crate::{flog, flogf};
|
use crate::{flog, flogf};
|
||||||
use fish_build_helper::workspace_root;
|
use fish_build_helper::workspace_root;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
/// A struct of configuration directories, determined in main() that fish will optionally pass to
|
/// A struct of configuration directories, determined in main() that fish will optionally pass to
|
||||||
/// env_init.
|
/// env_init.
|
||||||
@@ -163,7 +163,7 @@ pub enum FishPath {
|
|||||||
LookUpInPath,
|
LookUpInPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
static FISH_PATH: OnceCell<FishPath> = OnceCell::new();
|
static FISH_PATH: OnceLock<FishPath> = OnceLock::new();
|
||||||
|
|
||||||
/// Get the absolute path to the fish executable itself
|
/// Get the absolute path to the fish executable itself
|
||||||
pub fn get_fish_path() -> &'static FishPath {
|
pub fn get_fish_path() -> &'static FishPath {
|
||||||
|
|||||||
7
src/env/environment.rs
vendored
7
src/env/environment.rs
vendored
@@ -28,13 +28,12 @@
|
|||||||
use crate::wutil::{fish_wcstol, wgetcwd};
|
use crate::wutil::{fish_wcstol, wgetcwd};
|
||||||
|
|
||||||
use libc::{c_int, uid_t};
|
use libc::{c_int, uid_t};
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::unix::prelude::*;
|
use std::os::unix::prelude::*;
|
||||||
use std::path::PathBuf;
|
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().
|
/// 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);
|
static UVARS_LOCALLY_MODIFIED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||||
@@ -591,7 +590,7 @@ fn setup_user(global_exported_mode: EnvSetMode, vars: &EnvStack) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// _CS_PATH: colon-separated paths to find POSIX utilities. Same as USER_CS_PATH.
|
||||||
let cs_path = libc::_CS_PATH;
|
let cs_path = libc::_CS_PATH;
|
||||||
|
|
||||||
@@ -624,7 +623,7 @@ fn setup_path(global_exported_mode: EnvSetMode) {
|
|||||||
|
|
||||||
/// The originally inherited variables and their values.
|
/// The originally inherited variables and their values.
|
||||||
/// This is a simple key->value map and not e.g. cut into paths.
|
/// 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) {
|
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
|
||||||
let vars = EnvStack::globals();
|
let vars = EnvStack::globals();
|
||||||
|
|||||||
6
src/env/environment_impl.rs
vendored
6
src/env/environment_impl.rs
vendored
@@ -15,13 +15,13 @@
|
|||||||
use crate::threads::{is_forked_child, is_main_thread};
|
use crate::threads::{is_forked_child, is_main_thread};
|
||||||
use crate::wutil::fish_wcstol_radix;
|
use crate::wutil::fish_wcstol_radix;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::cell::{RefCell, UnsafeCell};
|
use std::cell::{RefCell, UnsafeCell};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
#[cfg(not(target_has_atomic = "64"))]
|
#[cfg(not(target_has_atomic = "64"))]
|
||||||
use portable_atomic::AtomicU64;
|
use portable_atomic::AtomicU64;
|
||||||
@@ -294,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.
|
/// Recursive helper to snapshot a series of nodes.
|
||||||
fn copy_node_chain(node: &EnvNodeRef) -> EnvNodeRef {
|
fn copy_node_chain(node: &EnvNodeRef) -> EnvNodeRef {
|
||||||
@@ -595,7 +595,7 @@ fn export_array_needs_regeneration(&self) -> bool {
|
|||||||
let mut cursor = self.export_array_generations.iter().fuse();
|
let mut cursor = self.export_array_generations.iter().fuse();
|
||||||
let mut mismatch = false;
|
let mut mismatch = false;
|
||||||
self.enumerate_generations(|r#gen| {
|
self.enumerate_generations(|r#gen| {
|
||||||
if cursor.next().cloned() != Some(r#gen) {
|
if cursor.next().copied() != Some(r#gen) {
|
||||||
mismatch = true;
|
mismatch = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ pub fn env_dispatch_var_change(milieu: VarChangeMilieu, key: &wstr, vars: &EnvSt
|
|||||||
let suppress_repaint = milieu.is_repainting || !milieu.global_or_universal;
|
let suppress_repaint = milieu.is_repainting || !milieu.global_or_universal;
|
||||||
|
|
||||||
// We want to ignore variable changes until the dispatch table is explicitly initialized.
|
// 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) {
|
if let Some(dispatch_table) = Lazy::get(&VAR_DISPATCH_TABLE) {
|
||||||
dispatch_table.dispatch(key, vars, suppress_repaint);
|
dispatch_table.dispatch(key, vars, suppress_repaint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -862,7 +862,7 @@ fn test_universal() {
|
|||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
|
|
||||||
for i in 0..threads {
|
for i in 0..threads {
|
||||||
let path = test_path.to_owned();
|
let path = test_path.clone();
|
||||||
handles.push(std::thread::spawn(move || {
|
handles.push(std::thread::spawn(move || {
|
||||||
test_universal_helper(i, &path);
|
test_universal_helper(i, &path);
|
||||||
}));
|
}));
|
||||||
@@ -873,7 +873,7 @@ fn test_universal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut uvars = EnvUniversal::new();
|
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 i in 0..threads {
|
||||||
for j in 0..UVARS_PER_THREAD {
|
for j in 0..UVARS_PER_THREAD {
|
||||||
@@ -1037,11 +1037,11 @@ fn test_universal_callbacks() {
|
|||||||
let mut uvars1 = EnvUniversal::new();
|
let mut uvars1 = EnvUniversal::new();
|
||||||
let mut uvars2 = EnvUniversal::new();
|
let mut uvars2 = EnvUniversal::new();
|
||||||
let mut callbacks = uvars1
|
let mut callbacks = uvars1
|
||||||
.initialize_at_path(test_path.to_owned())
|
.initialize_at_path(test_path.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
callbacks.append(
|
callbacks.append(
|
||||||
&mut uvars2
|
&mut uvars2
|
||||||
.initialize_at_path(test_path.to_owned())
|
.initialize_at_path(test_path.clone())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1134,7 +1134,7 @@ fn test_universal_ok_to_save() {
|
|||||||
|
|
||||||
let mut uvars = EnvUniversal::new();
|
let mut uvars = EnvUniversal::new();
|
||||||
uvars
|
uvars
|
||||||
.initialize_at_path(test_path.to_owned())
|
.initialize_at_path(test_path.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
assert!(!uvars.is_ok_to_save(), "Should not be OK to save");
|
assert!(!uvars.is_ok_to_save(), "Should not be OK to save");
|
||||||
uvars.sync();
|
uvars.sync();
|
||||||
|
|||||||
23
src/exec.rs
23
src/exec.rs
@@ -14,8 +14,9 @@
|
|||||||
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment, READ_BYTE_LIMIT, Statuses};
|
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment, READ_BYTE_LIMIT, Statuses};
|
||||||
#[cfg(have_posix_spawn)]
|
#[cfg(have_posix_spawn)]
|
||||||
use crate::env_dispatch::use_posix_spawn;
|
use crate::env_dispatch::use_posix_spawn;
|
||||||
use crate::fds::make_fd_blocking;
|
use crate::fds::{
|
||||||
use crate::fds::{PIPE_ERROR, make_autoclose_pipes, open_cloexec};
|
BorrowedFdFile, PIPE_ERROR, make_autoclose_pipes, make_fd_blocking, open_cloexec,
|
||||||
|
};
|
||||||
use crate::flog::{flog, flogf};
|
use crate::flog::{flog, flogf};
|
||||||
use crate::fork_exec::PATH_BSHELL;
|
use crate::fork_exec::PATH_BSHELL;
|
||||||
use crate::fork_exec::blocked_signals_for_job;
|
use crate::fork_exec::blocked_signals_for_job;
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc, OnceLock,
|
Arc, OnceLock,
|
||||||
@@ -251,7 +252,6 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
|||||||
if job.is_stopped() {
|
if job.is_stopped() {
|
||||||
handoff.save_tty_modes();
|
handoff.save_tty_modes();
|
||||||
}
|
}
|
||||||
handoff.reclaim();
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1160,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);
|
let err_io = io_chain.io_for_fd(STDERR_FILENO);
|
||||||
|
|
||||||
// Figure out what fd to use for the builtin's stdin.
|
// 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) {
|
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
|
// Ignore fd redirections from an fd other than the
|
||||||
// standard ones. e.g. in source <&3 don't actually read from fd 3,
|
// 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
|
// 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,
|
// that we pass it on as a block IO to the code that source runs,
|
||||||
// and therefore this is not an error.
|
// and therefore this is not an error.
|
||||||
let ignore_redirect = inp.io_mode() == IoMode::Fd && inp.source_fd() >= 3;
|
let fd = inp.source_fd();
|
||||||
if !ignore_redirect {
|
let ignore_redirect = fd >= 3 && inp.io_mode() == IoMode::Fd;
|
||||||
local_builtin_stdin = inp.source_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.
|
// Populate our IoStreams. This is a bag of information for the builtin.
|
||||||
let mut streams = IoStreams::new(output_stream, errput_stream, &io_chain);
|
let mut streams = IoStreams::new(output_stream, errput_stream, &io_chain);
|
||||||
streams.job_group = job_group;
|
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.stdin_is_directly_redirected = stdin_is_directly_redirected;
|
||||||
streams.out_is_redirected = out_io.is_some();
|
streams.out_is_redirected = out_io.is_some();
|
||||||
streams.err_is_redirected = err_io.is_some();
|
streams.err_is_redirected = err_io.is_some();
|
||||||
|
|||||||
@@ -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.
|
/// Perform the opposite of tilde expansion on the string.
|
||||||
pub fn replace_home_directory_with_tilde(s: &wstr, vars: &dyn Environment) -> WString {
|
pub fn replace_home_directory_with_tilde(s: impl Into<WString>, vars: &dyn Environment) -> WString {
|
||||||
let mut result = s.to_owned();
|
let mut result = s.into();
|
||||||
// Only absolute paths get this treatment.
|
// Only absolute paths get this treatment.
|
||||||
if result.starts_with(L!("/")) {
|
if result.starts_with(L!("/")) {
|
||||||
let mut home_directory = L!("~").to_owned();
|
let mut home_directory = L!("~").to_owned();
|
||||||
@@ -737,7 +737,7 @@ fn expand_variables(
|
|||||||
// here, So tmp < 1 means it's definitely not in.
|
// here, So tmp < 1 means it's definitely not in.
|
||||||
// Note we are 1-based.
|
// Note we are 1-based.
|
||||||
if item_index >= 1 && item_index <= all_var_items.len() {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
// -1 to convert from 1-based slice index to 0-based vector index.
|
// -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;
|
sub_res = sub_res2;
|
||||||
}
|
}
|
||||||
@@ -2049,4 +2049,22 @@ fn test_abbreviations() {
|
|||||||
|
|
||||||
assert_eq!(abbr_expand_1(L!("foo"), cmd), Some(L!("bar").into()));
|
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::ffi::CStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
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::*;
|
use std::os::unix::prelude::*;
|
||||||
|
|
||||||
localizable_consts!(
|
localizable_consts!(
|
||||||
@@ -240,12 +243,84 @@ pub fn make_fd_blocking(fd: RawFd) -> Result<(), io::Error> {
|
|||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{FIRST_HIGH_FD, make_autoclose_pipes};
|
use super::{BorrowedFdFile, FIRST_HIGH_FD, make_autoclose_pipes};
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
use libc::{F_GETFD, FD_CLOEXEC};
|
use libc::{F_GETFD, FD_CLOEXEC};
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::{AsRawFd, FromRawFd};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,9 @@
|
|||||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::wutil::dir_iter::DirIter;
|
use crate::wutil::dir_iter::DirIter;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, LazyLock, Mutex};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FunctionProperties {
|
pub struct FunctionProperties {
|
||||||
@@ -100,7 +99,7 @@ fn allow_autoload(&self, name: &wstr) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The big set of all functions.
|
/// 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 {
|
Mutex::new(FunctionSet {
|
||||||
funcs: HashMap::new(),
|
funcs: HashMap::new(),
|
||||||
autoload_tombstones: HashSet::new(),
|
autoload_tombstones: HashSet::new(),
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ pub fn test_path(&self, token: &wstr, prefix: bool) -> bool {
|
|||||||
is_potential_path(
|
is_potential_path(
|
||||||
&token,
|
&token,
|
||||||
prefix,
|
prefix,
|
||||||
&[self.working_directory.to_owned()],
|
std::slice::from_ref(&self.working_directory),
|
||||||
self.ctx,
|
self.ctx,
|
||||||
PathFlags {
|
PathFlags {
|
||||||
expand_tilde: true,
|
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.
|
// Test if a the given string is a valid redirection target, and if so, whether
|
||||||
// Note we return bool, because we never underline redirection targets.
|
// it is a path to an existing file.
|
||||||
pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> bool {
|
pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> FileTestResult {
|
||||||
// Skip targets exceeding PATH_MAX. See #7837.
|
// Skip targets exceeding PATH_MAX. See #7837.
|
||||||
if target.len() > (PATH_MAX as usize) {
|
if target.len() > (PATH_MAX as usize) {
|
||||||
return false;
|
return Err(IsErr);
|
||||||
}
|
}
|
||||||
let mut target = target.to_owned();
|
let mut target = target.to_owned();
|
||||||
if !expand_one(&mut target, ExpandFlags::FAIL_ON_CMDSUBST, self.ctx, None) {
|
if !expand_one(&mut target, ExpandFlags::FAIL_ON_CMDSUBST, self.ctx, None) {
|
||||||
// Could not be expanded.
|
// Could not be expanded.
|
||||||
return false;
|
return Err(IsErr);
|
||||||
}
|
}
|
||||||
// Ok, we successfully expanded our target. Now verify that it works with this
|
// 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
|
// 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 {
|
match mode {
|
||||||
RedirectionMode::Fd => {
|
RedirectionMode::Fd => {
|
||||||
if target == "-" {
|
if target == "-" {
|
||||||
return true;
|
return Ok(IsFile(false));
|
||||||
}
|
}
|
||||||
match fish_wcstoi(&target) {
|
match fish_wcstoi(&target) {
|
||||||
Ok(fd) => fd >= 0,
|
Ok(fd) if fd >= 0 => Ok(IsFile(false)),
|
||||||
Err(_) => false,
|
_ => Err(IsErr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RedirectionMode::Input | RedirectionMode::TryInput => {
|
RedirectionMode::Input | RedirectionMode::TryInput => {
|
||||||
// Input redirections must have a readable non-directory.
|
// Input redirections must have a readable non-directory.
|
||||||
// Note we color "try_input" files as errors if they are invalid,
|
// Note we color "try_input" files as errors if they are invalid,
|
||||||
// even though it's possible to execute these (replaced via /dev/null).
|
// 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())
|
&& wstat(&target_path).is_ok_and(|md| !md.file_type().is_dir())
|
||||||
|
{
|
||||||
|
Ok(IsFile(true))
|
||||||
|
} else {
|
||||||
|
Err(IsErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RedirectionMode::Overwrite | RedirectionMode::Append | RedirectionMode::NoClob => {
|
RedirectionMode::Overwrite | RedirectionMode::Append | RedirectionMode::NoClob => {
|
||||||
if string_suffixes_string(L!("/"), &target) {
|
if string_suffixes_string(L!("/"), &target) {
|
||||||
// Redirections to things that are directories is definitely not
|
// Redirections to things that are directories is definitely not
|
||||||
// allowed.
|
// allowed.
|
||||||
return false;
|
return Err(IsErr);
|
||||||
}
|
}
|
||||||
// Test whether the file exists, and whether it's writable (possibly after
|
// Test whether the file exists, and whether it's writable (possibly after
|
||||||
// creating it). access() returns failure if the file does not exist.
|
// 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_exists;
|
||||||
let file_is_writable;
|
let file_is_writable;
|
||||||
match wstat(&target_path) {
|
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.
|
// 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.
|
// Normal redirection.
|
||||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Input);
|
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
|
// Can't redirect from a missing file
|
||||||
let result = tester.test_redirection_target(L!("notfile.txt"), RedirectionMode::Input);
|
let result = tester.test_redirection_target(L!("notfile.txt"), RedirectionMode::Input);
|
||||||
assert!(!result);
|
assert_eq!(result, Err(IsErr));
|
||||||
let result =
|
let result =
|
||||||
tester.test_redirection_target(L!("bogus_path/file.txt"), RedirectionMode::Input);
|
tester.test_redirection_target(L!("bogus_path/file.txt"), RedirectionMode::Input);
|
||||||
assert!(!result);
|
assert_eq!(result, Err(IsErr));
|
||||||
|
|
||||||
// Can't redirect from a directory.
|
// Can't redirect from a directory.
|
||||||
let result = tester.test_redirection_target(L!("somedir"), RedirectionMode::Input);
|
let result = tester.test_redirection_target(L!("somedir"), RedirectionMode::Input);
|
||||||
assert!(!result);
|
assert_eq!(result, Err(IsErr));
|
||||||
|
|
||||||
// Can't redirect from an unreadable file.
|
// 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
|
#[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();
|
fs::set_permissions(&file_path, Permissions::from_mode(0o200)).unwrap();
|
||||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Input);
|
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();
|
fs::set_permissions(&file_path, Permissions::from_mode(0o600)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// try_input syntax highlighting reports an error even though the command will succeed.
|
// try_input syntax highlighting reports an error even though the command will succeed.
|
||||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::TryInput);
|
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);
|
let result = tester.test_redirection_target(L!("notfile.txt"), RedirectionMode::TryInput);
|
||||||
assert!(!result);
|
assert_eq!(result, Err(IsErr));
|
||||||
let result =
|
let result =
|
||||||
tester.test_redirection_target(L!("bogus_path/file.txt"), RedirectionMode::TryInput);
|
tester.test_redirection_target(L!("bogus_path/file.txt"), RedirectionMode::TryInput);
|
||||||
assert!(!result);
|
assert_eq!(result, Err(IsErr));
|
||||||
|
|
||||||
// Test write redirections.
|
// Test write redirections.
|
||||||
// Overwrite an existing file.
|
// Overwrite an existing file.
|
||||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Overwrite);
|
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Overwrite);
|
||||||
assert!(result);
|
assert_eq!(result, Ok(IsFile(true)));
|
||||||
|
|
||||||
// Append to an existing file.
|
// Append to an existing file.
|
||||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Append);
|
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::Append);
|
||||||
assert!(result);
|
assert_eq!(result, Ok(IsFile(true)));
|
||||||
|
|
||||||
// Write to a missing file.
|
// Write to a missing file.
|
||||||
let result = tester.test_redirection_target(L!("newfile.txt"), RedirectionMode::Overwrite);
|
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.
|
// No-clobber write to existing file should fail.
|
||||||
let result = tester.test_redirection_target(L!("file.txt"), RedirectionMode::NoClob);
|
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.
|
// No-clobber write to missing file should succeed.
|
||||||
let result = tester.test_redirection_target(L!("unique.txt"), RedirectionMode::NoClob);
|
let result = tester.test_redirection_target(L!("unique.txt"), RedirectionMode::NoClob);
|
||||||
assert!(result);
|
assert_eq!(result, Ok(IsFile(false)));
|
||||||
|
|
||||||
let write_modes = &[
|
let write_modes = &[
|
||||||
RedirectionMode::Overwrite,
|
RedirectionMode::Overwrite,
|
||||||
@@ -606,8 +613,9 @@ fn test_redirections() {
|
|||||||
|
|
||||||
// Can't write to a directory.
|
// Can't write to a directory.
|
||||||
for mode in write_modes {
|
for mode in write_modes {
|
||||||
assert!(
|
assert_eq!(
|
||||||
!tester.test_redirection_target(L!("somedir"), *mode),
|
tester.test_redirection_target(L!("somedir"), *mode),
|
||||||
|
Err(IsErr),
|
||||||
"Should not be able to write to a directory with mode {:?}",
|
"Should not be able to write to a directory with mode {:?}",
|
||||||
mode
|
mode
|
||||||
);
|
);
|
||||||
@@ -616,8 +624,9 @@ fn test_redirections() {
|
|||||||
// Can't write without write permissions.
|
// Can't write without write permissions.
|
||||||
fs::set_permissions(&file_path, Permissions::from_mode(0o400)).unwrap(); // Read-only.
|
fs::set_permissions(&file_path, Permissions::from_mode(0o400)).unwrap(); // Read-only.
|
||||||
for mode in write_modes {
|
for mode in write_modes {
|
||||||
assert!(
|
assert_eq!(
|
||||||
!tester.test_redirection_target(L!("file.txt"), *mode),
|
tester.test_redirection_target(L!("file.txt"), *mode),
|
||||||
|
Err(IsErr),
|
||||||
"Should not be able to write to a read-only file with mode {:?}",
|
"Should not be able to write to a read-only file with mode {:?}",
|
||||||
mode
|
mode
|
||||||
);
|
);
|
||||||
@@ -629,8 +638,9 @@ fn test_redirections() {
|
|||||||
{
|
{
|
||||||
fs::set_permissions(&dir_path, Permissions::from_mode(0o500)).unwrap(); // Read and execute, no write.
|
fs::set_permissions(&dir_path, Permissions::from_mode(0o500)).unwrap(); // Read and execute, no write.
|
||||||
for mode in write_modes {
|
for mode in write_modes {
|
||||||
assert!(
|
assert_eq!(
|
||||||
!tester.test_redirection_target(L!("somedir/newfile.txt"), *mode),
|
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 {:?}",
|
"Should not be able to create/write in a read-only directory with mode {:?}",
|
||||||
mode
|
mode
|
||||||
);
|
);
|
||||||
@@ -639,28 +649,82 @@ fn test_redirections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test fd redirections.
|
// Test fd redirections.
|
||||||
assert!(tester.test_redirection_target(L!("-"), RedirectionMode::Fd));
|
assert_eq!(
|
||||||
assert!(tester.test_redirection_target(L!("0"), RedirectionMode::Fd));
|
tester.test_redirection_target(L!("-"), RedirectionMode::Fd),
|
||||||
assert!(tester.test_redirection_target(L!("1"), RedirectionMode::Fd));
|
Ok(IsFile(false)),
|
||||||
assert!(tester.test_redirection_target(L!("2"), RedirectionMode::Fd));
|
);
|
||||||
assert!(tester.test_redirection_target(L!("3"), RedirectionMode::Fd));
|
assert_eq!(
|
||||||
assert!(tester.test_redirection_target(L!("500"), RedirectionMode::Fd));
|
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.
|
// We are base 10, despite the leading 0.
|
||||||
assert!(tester.test_redirection_target(L!("000"), RedirectionMode::Fd));
|
assert_eq!(
|
||||||
assert!(tester.test_redirection_target(L!("01"), RedirectionMode::Fd));
|
tester.test_redirection_target(L!("000"), RedirectionMode::Fd),
|
||||||
assert!(tester.test_redirection_target(L!("07"), 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.
|
// Invalid fd redirections.
|
||||||
assert!(!tester.test_redirection_target(L!("0x2"), RedirectionMode::Fd));
|
assert_eq!(
|
||||||
assert!(!tester.test_redirection_target(L!("0x3F"), RedirectionMode::Fd));
|
tester.test_redirection_target(L!("0x2"), RedirectionMode::Fd),
|
||||||
assert!(!tester.test_redirection_target(L!("0F"), RedirectionMode::Fd));
|
Err(IsErr),
|
||||||
assert!(!tester.test_redirection_target(L!("-1"), RedirectionMode::Fd));
|
);
|
||||||
assert!(!tester.test_redirection_target(L!("-0009"), RedirectionMode::Fd));
|
assert_eq!(
|
||||||
assert!(!tester.test_redirection_target(L!("--"), RedirectionMode::Fd));
|
tester.test_redirection_target(L!("0x3F"), RedirectionMode::Fd),
|
||||||
assert!(!tester.test_redirection_target(L!("derp"), RedirectionMode::Fd));
|
Err(IsErr),
|
||||||
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!("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]
|
#[test]
|
||||||
|
|||||||
@@ -106,6 +106,22 @@ pub fn highlight_shell(
|
|||||||
*color = highlighter.highlight();
|
*color = highlighter.highlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn highlight_and_colorize(
|
||||||
|
text: &wstr,
|
||||||
|
ctx: &OperationContext<'_>,
|
||||||
|
vars: &dyn Environment,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
let mut colors = Vec::new();
|
||||||
|
highlight_shell(
|
||||||
|
text,
|
||||||
|
&mut colors,
|
||||||
|
ctx,
|
||||||
|
/*io_ok=*/ false,
|
||||||
|
/*cursor=*/ None,
|
||||||
|
);
|
||||||
|
colorize(text, &colors, vars)
|
||||||
|
}
|
||||||
|
|
||||||
/// highlight_color_resolver_t resolves highlight specs (like "a command") to actual RGB colors.
|
/// highlight_color_resolver_t resolves highlight specs (like "a command") to actual RGB colors.
|
||||||
/// It maintains a cache with no invalidation mechanism. The lifetime of these should typically be
|
/// It maintains a cache with no invalidation mechanism. The lifetime of these should typically be
|
||||||
/// one screen redraw.
|
/// one screen redraw.
|
||||||
@@ -951,24 +967,29 @@ fn visit_redirection(&mut self, redir: &Redirection) {
|
|||||||
}
|
}
|
||||||
// No command substitution, so we can highlight the target file or fd. For example,
|
// No command substitution, so we can highlight the target file or fd. For example,
|
||||||
// disallow redirections into a non-existent directory.
|
// disallow redirections into a non-existent directory.
|
||||||
let target_is_valid = if !self.io_still_ok() {
|
let (role, file_exists) = if !self.io_still_ok() {
|
||||||
// I/O is disallowed, so we don't have much hope of catching anything but gross
|
// I/O is disallowed, so we don't have much hope of catching anything but gross
|
||||||
// errors. Assume it's valid.
|
// errors. Assume it's valid.
|
||||||
true
|
(HighlightRole::redirection, false)
|
||||||
} else if contains_pending_variable(&self.pending_variables, &target) {
|
} else if contains_pending_variable(&self.pending_variables, &target) {
|
||||||
true
|
// Target uses a variable defined by the current commandline. Assume it's valid.
|
||||||
|
(HighlightRole::redirection, false)
|
||||||
} else {
|
} else {
|
||||||
// Validate the redirection target..
|
// Validate the redirection target..
|
||||||
self.file_tester.test_redirection_target(&target, oper.mode)
|
if let Ok(IsFile(file_exists)) =
|
||||||
};
|
self.file_tester.test_redirection_target(&target, oper.mode)
|
||||||
self.color_node(
|
{
|
||||||
&redir.target,
|
(HighlightRole::redirection, file_exists)
|
||||||
HighlightSpec::with_fg(if target_is_valid {
|
|
||||||
HighlightRole::redirection
|
|
||||||
} else {
|
} else {
|
||||||
HighlightRole::error
|
(HighlightRole::error, false)
|
||||||
}),
|
}
|
||||||
);
|
};
|
||||||
|
self.color_node(&redir.target, HighlightSpec::with_fg(role));
|
||||||
|
if file_exists {
|
||||||
|
for i in redir.target.source_range().as_usize() {
|
||||||
|
self.color_array[i].valid_path = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_variable_assignment(&mut self, varas: &VariableAssignment) {
|
fn visit_variable_assignment(&mut self, varas: &VariableAssignment) {
|
||||||
@@ -1376,6 +1397,9 @@ macro_rules! validate {
|
|||||||
let mut param_valid_path = HighlightSpec::with_fg(HighlightRole::param);
|
let mut param_valid_path = HighlightSpec::with_fg(HighlightRole::param);
|
||||||
param_valid_path.valid_path = true;
|
param_valid_path.valid_path = true;
|
||||||
|
|
||||||
|
let mut redirection_valid_path = HighlightSpec::with_fg(HighlightRole::redirection);
|
||||||
|
redirection_valid_path.valid_path = true;
|
||||||
|
|
||||||
let saved_flag = future_feature_flags::test(FeatureFlag::AmpersandNoBgInToken);
|
let saved_flag = future_feature_flags::test(FeatureFlag::AmpersandNoBgInToken);
|
||||||
future_feature_flags::set(FeatureFlag::AmpersandNoBgInToken, true);
|
future_feature_flags::set(FeatureFlag::AmpersandNoBgInToken, true);
|
||||||
let _restore_saved_flag = ScopeGuard::new((), |_| {
|
let _restore_saved_flag = ScopeGuard::new((), |_| {
|
||||||
@@ -1513,7 +1537,7 @@ macro_rules! validate {
|
|||||||
("param1", fg(HighlightRole::param)),
|
("param1", fg(HighlightRole::param)),
|
||||||
// Input redirection.
|
// Input redirection.
|
||||||
("<", fg(HighlightRole::redirection)),
|
("<", fg(HighlightRole::redirection)),
|
||||||
("/bin/echo", fg(HighlightRole::redirection)),
|
("/dev/null", redirection_valid_path),
|
||||||
// Output redirection to a valid fd.
|
// Output redirection to a valid fd.
|
||||||
("1>&2", fg(HighlightRole::redirection)),
|
("1>&2", fg(HighlightRole::redirection)),
|
||||||
// Output redirection to an invalid fd.
|
// Output redirection to an invalid fd.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{Read, Write},
|
io::Read,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
os::fd::AsRawFd,
|
os::fd::AsRawFd,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
@@ -283,27 +283,30 @@ fn try_from(region: MmapRegion) -> std::io::Result<Self> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a history item to a buffer, in preparation for outputting it to the history file.
|
impl HistoryItem {
|
||||||
pub fn append_history_item_to_buffer(item: &HistoryItem, buffer: &mut Vec<u8>) {
|
/// Write this history item to some writer.
|
||||||
assert!(item.should_write_to_disk(), "Item should not be persisted");
|
pub fn write_to(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||||
|
assert!(self.should_write_to_disk(), "Item should not be persisted");
|
||||||
|
|
||||||
let mut cmd = wcs2bytes(item.str());
|
let mut cmd = wcs2bytes(self.str());
|
||||||
escape_yaml_fish_2_0(&mut cmd);
|
escape_yaml_fish_2_0(&mut cmd);
|
||||||
buffer.extend(b"- cmd: ");
|
writer.write_all(b"- cmd: ")?;
|
||||||
buffer.extend(&cmd);
|
writer.write_all(&cmd)?;
|
||||||
buffer.push(b'\n');
|
writer.write_all(b"\n")?;
|
||||||
writeln!(buffer, " when: {}", time_to_seconds(item.timestamp())).unwrap();
|
writeln!(writer, " when: {}", time_to_seconds(self.timestamp()))?;
|
||||||
|
|
||||||
let paths = item.get_required_paths();
|
let paths = self.get_required_paths();
|
||||||
if !paths.is_empty() {
|
if !paths.is_empty() {
|
||||||
writeln!(buffer, " paths:").unwrap();
|
writeln!(writer, " paths:")?;
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let mut path = wcs2bytes(path);
|
let mut path = wcs2bytes(path);
|
||||||
escape_yaml_fish_2_0(&mut path);
|
escape_yaml_fish_2_0(&mut path);
|
||||||
buffer.extend(b" - ");
|
writer.write_all(b" - ")?;
|
||||||
buffer.extend(&path);
|
writer.write_all(&path)?;
|
||||||
buffer.push(b'\n');
|
writer.write_all(b"\n")?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, Read, Write},
|
io::{BufRead, BufWriter, Read, Write},
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
@@ -50,12 +50,14 @@
|
|||||||
fds::wopen_cloexec,
|
fds::wopen_cloexec,
|
||||||
flog::{flog, flogf},
|
flog::{flog, flogf},
|
||||||
fs::fsync,
|
fs::fsync,
|
||||||
history::file::{HistoryFile, RawHistoryFile, append_history_item_to_buffer},
|
highlight::highlight_and_colorize,
|
||||||
|
history::file::{HistoryFile, RawHistoryFile},
|
||||||
io::IoStreams,
|
io::IoStreams,
|
||||||
localization::wgettext_fmt,
|
localization::wgettext_fmt,
|
||||||
operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext},
|
operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext},
|
||||||
parse_constants::{ParseTreeFlags, StatementDecoration},
|
parse_constants::{ParseTreeFlags, StatementDecoration},
|
||||||
parse_util::{parse_util_detect_errors, parse_util_unescape_wildcards},
|
parse_util::{parse_util_detect_errors, parse_util_unescape_wildcards},
|
||||||
|
parser::Parser,
|
||||||
path::{path_get_config, path_get_data, path_is_valid},
|
path::{path_get_config, path_get_data, path_is_valid},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
threads::assert_is_background_thread,
|
threads::assert_is_background_thread,
|
||||||
@@ -109,16 +111,6 @@ pub enum SearchDirection {
|
|||||||
|
|
||||||
pub const VACUUM_FREQUENCY: usize = 25;
|
pub const VACUUM_FREQUENCY: usize = 25;
|
||||||
|
|
||||||
/// Output the contents `buffer` to `file` and clear the `buffer`.
|
|
||||||
fn flush_to_file(buffer: &mut Vec<u8>, file: &mut File, min_size: usize) -> std::io::Result<()> {
|
|
||||||
if buffer.is_empty() || buffer.len() < min_size {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
file.write_all(buffer)?;
|
|
||||||
buffer.clear();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimeProfiler {
|
struct TimeProfiler {
|
||||||
what: &'static str,
|
what: &'static str,
|
||||||
start: SystemTime,
|
start: SystemTime,
|
||||||
@@ -289,7 +281,7 @@ fn merge(&mut self, item: &HistoryItem) -> bool {
|
|||||||
// Ok, merge this item.
|
// Ok, merge this item.
|
||||||
self.creation_timestamp = self.creation_timestamp.max(item.creation_timestamp);
|
self.creation_timestamp = self.creation_timestamp.max(item.creation_timestamp);
|
||||||
if self.required_paths.len() < item.required_paths.len() {
|
if self.required_paths.len() < item.required_paths.len() {
|
||||||
self.required_paths = item.required_paths.clone();
|
self.required_paths.clone_from(&item.required_paths);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -297,6 +289,13 @@ fn merge(&mut self, item: &HistoryItem) -> bool {
|
|||||||
|
|
||||||
static HISTORIES: Mutex<BTreeMap<WString, Arc<History>>> = Mutex::new(BTreeMap::new());
|
static HISTORIES: Mutex<BTreeMap<WString, Arc<History>>> = Mutex::new(BTreeMap::new());
|
||||||
|
|
||||||
|
/// When deleting, whether the deletion should be only for this session or for all sessions.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum DeletionScope {
|
||||||
|
SessionOnly,
|
||||||
|
AllSessions,
|
||||||
|
}
|
||||||
|
|
||||||
struct HistoryImpl {
|
struct HistoryImpl {
|
||||||
/// The name of this list. Used for picking a suitable filename and for switching modes.
|
/// The name of this list. Used for picking a suitable filename and for switching modes.
|
||||||
name: WString,
|
name: WString,
|
||||||
@@ -311,10 +310,8 @@ struct HistoryImpl {
|
|||||||
has_pending_item: bool, // false
|
has_pending_item: bool, // false
|
||||||
/// Whether we should disable saving to the file for a time.
|
/// Whether we should disable saving to the file for a time.
|
||||||
disable_automatic_save_counter: u32, // 0
|
disable_automatic_save_counter: u32, // 0
|
||||||
/// Deleted item contents.
|
/// Deleted item contents, and the scope of the deletion.
|
||||||
/// Boolean describes if it should be deleted only in this session or in all
|
deleted_items: HashMap<WString, DeletionScope>,
|
||||||
/// (used in deduplication).
|
|
||||||
deleted_items: HashMap<WString, bool>,
|
|
||||||
/// The history file contents.
|
/// The history file contents.
|
||||||
file_contents: Option<HistoryFile>,
|
file_contents: Option<HistoryFile>,
|
||||||
/// The file ID of the history file.
|
/// The file ID of the history file.
|
||||||
@@ -456,7 +453,7 @@ fn compact_new_items(&mut self) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !seen.insert(item.contents.to_owned()) {
|
if !seen.insert(item.contents.clone()) {
|
||||||
// This item was not inserted because it was already in the set, so delete the item at
|
// This item was not inserted because it was already in the set, so delete the item at
|
||||||
// this index.
|
// this index.
|
||||||
self.new_items.remove(idx);
|
self.new_items.remove(idx);
|
||||||
@@ -511,23 +508,21 @@ fn rewrite_to_temporary_file(
|
|||||||
let Some(old_item) = local_file.decode_item(offset) else {
|
let Some(old_item) = local_file.decode_item(offset) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
if old_item.is_empty() {
|
||||||
// If old item is newer than session always erase if in deleted.
|
continue;
|
||||||
if old_item.timestamp() > self.boundary_timestamp {
|
|
||||||
if old_item.is_empty() || self.deleted_items.contains_key(old_item.str()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lru.add_item(old_item);
|
|
||||||
} else {
|
|
||||||
// If old item is older and in deleted items don't erase if added by
|
|
||||||
// clear_session.
|
|
||||||
if old_item.is_empty() || self.deleted_items.get(old_item.str()) == Some(&false)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Add this old item.
|
|
||||||
lru.add_item(old_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this item should be deleted.
|
||||||
|
if let Some(&scope) = self.deleted_items.get(old_item.str()) {
|
||||||
|
// If old item is newer than session always erase if in deleted.
|
||||||
|
// If old item is older and in deleted items don't erase if added by clear_session.
|
||||||
|
let delete = old_item.timestamp() > self.boundary_timestamp
|
||||||
|
|| scope == DeletionScope::AllSessions;
|
||||||
|
if delete {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lru.add_item(old_item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,30 +546,12 @@ fn rewrite_to_temporary_file(
|
|||||||
/// Default buffer size for flushing to the history file.
|
/// Default buffer size for flushing to the history file.
|
||||||
const HISTORY_OUTPUT_BUFFER_SIZE: usize = 64 * 1024;
|
const HISTORY_OUTPUT_BUFFER_SIZE: usize = 64 * 1024;
|
||||||
// Write them out.
|
// Write them out.
|
||||||
let mut err = None;
|
let mut buffer = BufWriter::with_capacity(HISTORY_OUTPUT_BUFFER_SIZE + 128, dst);
|
||||||
let mut buffer = Vec::with_capacity(HISTORY_OUTPUT_BUFFER_SIZE + 128);
|
|
||||||
for item in items {
|
for item in items {
|
||||||
append_history_item_to_buffer(&item, &mut buffer);
|
item.write_to(&mut buffer)?;
|
||||||
if let Err(e) = flush_to_file(&mut buffer, dst, HISTORY_OUTPUT_BUFFER_SIZE) {
|
|
||||||
err = Some(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err.is_none() {
|
|
||||||
if let Err(e) = flush_to_file(&mut buffer, dst, 0) {
|
|
||||||
err = Some(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(err) = err {
|
|
||||||
flog!(
|
|
||||||
history_file,
|
|
||||||
"Error writing to temporary history file:",
|
|
||||||
err
|
|
||||||
);
|
|
||||||
Err(err)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
buffer.flush()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves history by rewriting the file.
|
/// Saves history by rewriting the file.
|
||||||
@@ -587,7 +564,15 @@ fn save_internal_via_rewrite(&mut self, history_path: &wstr) -> std::io::Result<
|
|||||||
|
|
||||||
let rewrite =
|
let rewrite =
|
||||||
|old_file: &File, tmp_file: &mut File| -> std::io::Result<PotentialUpdate<()>> {
|
|old_file: &File, tmp_file: &mut File| -> std::io::Result<PotentialUpdate<()>> {
|
||||||
self.rewrite_to_temporary_file(old_file, tmp_file)?;
|
let result = self.rewrite_to_temporary_file(old_file, tmp_file);
|
||||||
|
if let Err(err) = result {
|
||||||
|
flog!(
|
||||||
|
history_file,
|
||||||
|
"Error writing to temporary history file:",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
Ok(PotentialUpdate {
|
Ok(PotentialUpdate {
|
||||||
do_save: true,
|
do_save: true,
|
||||||
data: (),
|
data: (),
|
||||||
@@ -646,19 +631,20 @@ fn save_internal_via_appending(&mut self, history_path: &wstr) -> std::io::Resul
|
|||||||
// So far so good. Write all items at or after first_unwritten_new_item_index. Note that we
|
// So far so good. Write all items at or after first_unwritten_new_item_index. Note that we
|
||||||
// write even a pending item - pending items are ignored by history within the command
|
// write even a pending item - pending items are ignored by history within the command
|
||||||
// itself, but should still be written to the file.
|
// itself, but should still be written to the file.
|
||||||
// Use a small buffer size for appending, we usually only have 1 item
|
// Use a small buffer size for appending, as we usually only have 1 item.
|
||||||
|
// Buffer everything and then write it all at once to avoid tearing writes (O_APPEND).
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
let mut new_first_index = self.first_unwritten_new_item_index;
|
let mut new_first_index = self.first_unwritten_new_item_index;
|
||||||
while new_first_index < self.new_items.len() {
|
while new_first_index < self.new_items.len() {
|
||||||
let item = &self.new_items[new_first_index];
|
let item = &self.new_items[new_first_index];
|
||||||
if item.should_write_to_disk() {
|
if item.should_write_to_disk() {
|
||||||
append_history_item_to_buffer(item, &mut buffer);
|
// Can't error writing to a buffer.
|
||||||
|
item.write_to(&mut buffer).unwrap();
|
||||||
}
|
}
|
||||||
// We wrote or skipped this item, hooray.
|
// We wrote or skipped this item, hooray.
|
||||||
new_first_index += 1;
|
new_first_index += 1;
|
||||||
}
|
}
|
||||||
|
locked_history_file.get_mut().write_all(&buffer)?;
|
||||||
flush_to_file(&mut buffer, locked_history_file.get_mut(), 0)?;
|
|
||||||
fsync(locked_history_file.get())?;
|
fsync(locked_history_file.get())?;
|
||||||
self.first_unwritten_new_item_index = new_first_index;
|
self.first_unwritten_new_item_index = new_first_index;
|
||||||
|
|
||||||
@@ -808,7 +794,8 @@ fn is_empty(&mut self) -> bool {
|
|||||||
/// Remove a history item.
|
/// Remove a history item.
|
||||||
fn remove(&mut self, str_to_remove: &wstr) {
|
fn remove(&mut self, str_to_remove: &wstr) {
|
||||||
// Add to our list of deleted items.
|
// Add to our list of deleted items.
|
||||||
self.deleted_items.insert(str_to_remove.to_owned(), false);
|
self.deleted_items
|
||||||
|
.insert(str_to_remove.to_owned(), DeletionScope::AllSessions);
|
||||||
|
|
||||||
for idx in (0..self.new_items.len()).rev() {
|
for idx in (0..self.new_items.len()).rev() {
|
||||||
let matched = self.new_items[idx].str() == str_to_remove;
|
let matched = self.new_items[idx].str() == str_to_remove;
|
||||||
@@ -856,7 +843,8 @@ fn clear(&mut self) {
|
|||||||
/// Clears only session.
|
/// Clears only session.
|
||||||
fn clear_session(&mut self) {
|
fn clear_session(&mut self) {
|
||||||
for item in &self.new_items {
|
for item in &self.new_items {
|
||||||
self.deleted_items.insert(item.str().to_owned(), true);
|
self.deleted_items
|
||||||
|
.insert(item.str().to_owned(), DeletionScope::SessionOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.new_items.clear();
|
self.new_items.clear();
|
||||||
@@ -1356,6 +1344,8 @@ pub fn save(&self) {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn search(
|
pub fn search(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
|
parser: &Parser,
|
||||||
|
streams: &mut IoStreams,
|
||||||
search_type: SearchType,
|
search_type: SearchType,
|
||||||
search_args: &[&wstr],
|
search_args: &[&wstr],
|
||||||
show_time_format: Option<&str>,
|
show_time_format: Option<&str>,
|
||||||
@@ -1364,7 +1354,7 @@ pub fn search(
|
|||||||
null_terminate: bool,
|
null_terminate: bool,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
cancel_check: &CancelChecker,
|
cancel_check: &CancelChecker,
|
||||||
streams: &mut IoStreams,
|
color_enabled: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut remaining = max_items;
|
let mut remaining = max_items;
|
||||||
let mut collected = Vec::new();
|
let mut collected = Vec::new();
|
||||||
@@ -1376,7 +1366,17 @@ pub fn search(
|
|||||||
return ControlFlow::Break(());
|
return ControlFlow::Break(());
|
||||||
}
|
}
|
||||||
remaining -= 1;
|
remaining -= 1;
|
||||||
let formatted_record = format_history_record(item, show_time_format, null_terminate);
|
let mut formatted_record =
|
||||||
|
format_history_record(item, show_time_format, null_terminate);
|
||||||
|
|
||||||
|
if color_enabled {
|
||||||
|
formatted_record = bytes2wcstring(&highlight_and_colorize(
|
||||||
|
&formatted_record,
|
||||||
|
&parser.context(),
|
||||||
|
parser.vars(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if reverse {
|
if reverse {
|
||||||
// We need to collect this for later.
|
// We need to collect this for later.
|
||||||
collected.push(formatted_record);
|
collected.push(formatted_record);
|
||||||
|
|||||||
@@ -10,10 +10,9 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::reader::{Reader, reader_reset_interrupted};
|
use crate::reader::{Reader, reader_reset_interrupted};
|
||||||
use crate::threads::assert_is_main_thread;
|
use crate::threads::assert_is_main_thread;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Mutex, MutexGuard,
|
LazyLock, Mutex, MutexGuard,
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -228,8 +227,8 @@ pub struct InputMappingSet {
|
|||||||
|
|
||||||
/// Access the singleton input mapping set.
|
/// Access the singleton input mapping set.
|
||||||
pub fn input_mappings() -> MutexGuard<'static, InputMappingSet> {
|
pub fn input_mappings() -> MutexGuard<'static, InputMappingSet> {
|
||||||
static INPUT_MAPPINGS: Lazy<Mutex<InputMappingSet>> =
|
static INPUT_MAPPINGS: LazyLock<Mutex<InputMappingSet>> =
|
||||||
Lazy::new(|| Mutex::new(InputMappingSet::default()));
|
LazyLock::new(|| Mutex::new(InputMappingSet::default()));
|
||||||
INPUT_MAPPINGS.lock().unwrap()
|
INPUT_MAPPINGS.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +508,7 @@ fn next_is_char(
|
|||||||
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
|
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
|
||||||
} else if actual_seq
|
} else if actual_seq
|
||||||
.get(self.subidx + 1)
|
.get(self.subidx + 1)
|
||||||
.cloned()
|
.copied()
|
||||||
.map(|c| Key::from_single_char(c).codepoint)
|
.map(|c| Key::from_single_char(c).codepoint)
|
||||||
== Some(key.codepoint)
|
== Some(key.codepoint)
|
||||||
{
|
{
|
||||||
|
|||||||
31
src/io.rs
31
src/io.rs
@@ -1,7 +1,9 @@
|
|||||||
use crate::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_READ_TOO_MUCH};
|
use crate::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_READ_TOO_MUCH};
|
||||||
use crate::common::{EMPTY_STRING, bytes2wcstring, wcs2bytes};
|
use crate::common::{EMPTY_STRING, bytes2wcstring, wcs2bytes};
|
||||||
use crate::fd_monitor::{Callback, FdMonitor, FdMonitorItemId};
|
use crate::fd_monitor::{Callback, FdMonitor, FdMonitorItemId};
|
||||||
use crate::fds::{PIPE_ERROR, make_autoclose_pipes, make_fd_nonblocking, wopen_cloexec};
|
use crate::fds::{
|
||||||
|
BorrowedFdFile, PIPE_ERROR, make_autoclose_pipes, make_fd_nonblocking, wopen_cloexec,
|
||||||
|
};
|
||||||
use crate::flog::{flog, flogf, should_flog};
|
use crate::flog::{flog, flogf, should_flog};
|
||||||
use crate::nix::isatty;
|
use crate::nix::isatty;
|
||||||
use crate::path::path_apply_working_directory;
|
use crate::path::path_apply_working_directory;
|
||||||
@@ -16,11 +18,10 @@
|
|||||||
use libc::{EAGAIN, EINTR, ENOENT, ENOTDIR, EPIPE, EWOULDBLOCK, STDOUT_FILENO};
|
use libc::{EAGAIN, EINTR, ENOENT, ENOTDIR, EPIPE, EWOULDBLOCK, STDOUT_FILENO};
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, LazyLock, Mutex, MutexGuard};
|
||||||
|
|
||||||
/// separated_buffer_t represents a buffer of output from commands, prepared to be turned into a
|
/// separated_buffer_t represents a buffer of output from commands, prepared to be turned into a
|
||||||
/// variable. For example, command substitutions output into one of these. Most commands just
|
/// variable. For example, command substitutions output into one of these. Most commands just
|
||||||
@@ -859,9 +860,9 @@ pub struct IoStreams<'a> {
|
|||||||
pub out: &'a mut OutputStream,
|
pub out: &'a mut OutputStream,
|
||||||
pub err: &'a mut OutputStream,
|
pub err: &'a mut OutputStream,
|
||||||
|
|
||||||
// fd representing stdin. This is not closed by the destructor.
|
// File representing stdin.
|
||||||
// Note: if stdin is explicitly closed by `<&-` then this is -1!
|
// Note: if stdin is explicitly closed by `<&-` then this is None!
|
||||||
pub stdin_fd: RawFd,
|
pub stdin_file: Option<BorrowedFdFile>,
|
||||||
|
|
||||||
// Whether stdin is "directly redirected," meaning it is the recipient of a pipe (foo | cmd) or
|
// Whether stdin is "directly redirected," meaning it is the recipient of a pipe (foo | cmd) or
|
||||||
// direct redirection (cmd < foo.txt). An "indirect redirection" would be e.g.
|
// direct redirection (cmd < foo.txt). An "indirect redirection" would be e.g.
|
||||||
@@ -896,7 +897,7 @@ pub fn new(
|
|||||||
IoStreams {
|
IoStreams {
|
||||||
out,
|
out,
|
||||||
err,
|
err,
|
||||||
stdin_fd: -1,
|
stdin_file: None,
|
||||||
stdin_is_directly_redirected: false,
|
stdin_is_directly_redirected: false,
|
||||||
out_is_piped: false,
|
out_is_piped: false,
|
||||||
err_is_piped: false,
|
err_is_piped: false,
|
||||||
@@ -906,9 +907,23 @@ pub fn new(
|
|||||||
job_group: None,
|
job_group: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn out_is_terminal(&self) -> bool {
|
pub fn out_is_terminal(&self) -> bool {
|
||||||
!self.out_is_redirected && isatty(STDOUT_FILENO)
|
!self.out_is_redirected && isatty(STDOUT_FILENO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the fd for stdin, or -1 if stdin is closed.
|
||||||
|
pub fn stdin_fd(&self) -> RawFd {
|
||||||
|
self.stdin_file.as_ref().map_or(-1, |f| f.as_raw_fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return whether stdin is closed.
|
||||||
|
/// This is "closed in the fish sense" - i.e. `<&-` has been used.
|
||||||
|
/// This does not handle the case where a closed stdin was inherited - in that case
|
||||||
|
/// we'll have an stdin_fd of 0 and we'll just get syscall errors when we try to use it.
|
||||||
|
pub fn is_stdin_closed(&self) -> bool {
|
||||||
|
self.stdin_file.is_none()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// File redirection error message.
|
/// File redirection error message.
|
||||||
@@ -919,7 +934,7 @@ pub fn out_is_terminal(&self) -> bool {
|
|||||||
const OPEN_MASK: Mode = Mode::from_bits_truncate(0o666);
|
const OPEN_MASK: Mode = Mode::from_bits_truncate(0o666);
|
||||||
|
|
||||||
/// Provide the fd monitor used for background fillthread operations.
|
/// Provide the fd monitor used for background fillthread operations.
|
||||||
static FD_MONITOR: Lazy<FdMonitor> = Lazy::new(FdMonitor::new);
|
static FD_MONITOR: LazyLock<FdMonitor> = LazyLock::new(FdMonitor::new);
|
||||||
|
|
||||||
pub fn fd_monitor() -> &'static FdMonitor {
|
pub fn fd_monitor() -> &'static FdMonitor {
|
||||||
&FD_MONITOR
|
&FD_MONITOR
|
||||||
|
|||||||
@@ -3,15 +3,14 @@
|
|||||||
//! Works like the killring in emacs and readline. The killring is cut and paste with a memory of
|
//! Works like the killring in emacs and readline. The killring is cut and paste with a memory of
|
||||||
//! previous cuts.
|
//! previous cuts.
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::Mutex;
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
struct KillRing(VecDeque<WString>);
|
struct KillRing(VecDeque<WString>);
|
||||||
|
|
||||||
static KILL_RING: Lazy<Mutex<KillRing>> = Lazy::new(|| Mutex::new(KillRing::new()));
|
static KILL_RING: LazyLock<Mutex<KillRing>> = LazyLock::new(|| Mutex::new(KillRing::new()));
|
||||||
|
|
||||||
impl KillRing {
|
impl KillRing {
|
||||||
/// Create a new killring.
|
/// Create a new killring.
|
||||||
|
|||||||
214
src/localization/gettext.rs
Normal file
214
src/localization/gettext.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
use fish_wchar::{L, WString, wstr};
|
||||||
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
|
/// Use this function to localize a message.
|
||||||
|
/// The [`MaybeStatic`] wrapper type allows avoiding allocating and leaking a new [`wstr`] when no
|
||||||
|
/// localization is found and the input is returned, but as a static reference.
|
||||||
|
fn gettext(message: MaybeStatic) -> &'static wstr {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "localize-messages"))]
|
||||||
|
type NarrowMessage = ();
|
||||||
|
#[cfg(feature = "localize-messages")]
|
||||||
|
type NarrowMessage = &'static str;
|
||||||
|
|
||||||
|
let message_wstr = match message {
|
||||||
|
MaybeStatic::Static(s) => s,
|
||||||
|
MaybeStatic::Local(s) => s,
|
||||||
|
};
|
||||||
|
static MESSAGE_TO_NARROW: LazyLock<Mutex<HashMap<&'static wstr, NarrowMessage>>> =
|
||||||
|
LazyLock::new(|| Mutex::new(HashMap::default()));
|
||||||
|
let mut message_to_narrow = MESSAGE_TO_NARROW.lock().unwrap();
|
||||||
|
if !message_to_narrow.contains_key(message_wstr) {
|
||||||
|
let message_wstr: &'static wstr = match message {
|
||||||
|
MaybeStatic::Static(s) => s,
|
||||||
|
MaybeStatic::Local(l) => wstr::from_char_slice(Box::leak(l.as_char_slice().into())),
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "localize-messages"))]
|
||||||
|
let message_str = ();
|
||||||
|
#[cfg(feature = "localize-messages")]
|
||||||
|
let message_str = Box::leak(message_wstr.to_string().into_boxed_str());
|
||||||
|
message_to_narrow.insert(message_wstr, message_str);
|
||||||
|
}
|
||||||
|
let (message_static_wstr, message_str) = message_to_narrow.get_key_value(message_wstr).unwrap();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "localize-messages"))]
|
||||||
|
let () = message_str;
|
||||||
|
#[cfg(feature = "localize-messages")]
|
||||||
|
{
|
||||||
|
if let Some(localized_str) = fish_gettext::gettext(message_str) {
|
||||||
|
static LOCALIZATION_TO_WIDE: LazyLock<Mutex<HashMap<&'static str, &'static wstr>>> =
|
||||||
|
LazyLock::new(|| Mutex::new(HashMap::default()));
|
||||||
|
let mut localizations_to_wide = LOCALIZATION_TO_WIDE.lock().unwrap();
|
||||||
|
if !localizations_to_wide.contains_key(localized_str) {
|
||||||
|
let localization_wstr =
|
||||||
|
Box::leak(WString::from_str(localized_str).into_boxed_utfstr());
|
||||||
|
localizations_to_wide.insert(localized_str, localization_wstr);
|
||||||
|
}
|
||||||
|
return localizations_to_wide.get(localized_str).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No localization found.
|
||||||
|
message_static_wstr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type that can be either a static or local string.
|
||||||
|
enum MaybeStatic<'a> {
|
||||||
|
Static(&'static wstr),
|
||||||
|
Local(&'a wstr),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A string which can be localized.
|
||||||
|
/// The wrapped string itself is the original, unlocalized version.
|
||||||
|
/// Use [`LocalizableString::localize`] to obtain the localized version.
|
||||||
|
///
|
||||||
|
/// Do not construct this type directly.
|
||||||
|
/// For string literals defined in fish's Rust sources,
|
||||||
|
/// use the macros defined in this file.
|
||||||
|
/// For strings defined elsewhere, use [`LocalizableString::from_external_source`].
|
||||||
|
/// Use this function with caution. If the string is not extracted into the gettext PO files from
|
||||||
|
/// which fish obtains localizations, localization will not work.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum LocalizableString {
|
||||||
|
Static(&'static wstr),
|
||||||
|
Owned(WString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalizableString {
|
||||||
|
/// Create a [`LocalizableString`] from a string which is not from fish's own Rust sources.
|
||||||
|
/// Localizations will only work if this string is extracted into the localization files some
|
||||||
|
/// other way.
|
||||||
|
pub fn from_external_source(s: WString) -> Self {
|
||||||
|
Self::Owned(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the localization of a [`LocalizableString`].
|
||||||
|
/// If original string is empty, an empty `wstr` is returned,
|
||||||
|
/// instead of the gettext metadata.
|
||||||
|
pub fn localize(&self) -> &'static wstr {
|
||||||
|
match self {
|
||||||
|
Self::Static(s) => {
|
||||||
|
if s.is_empty() {
|
||||||
|
L!("")
|
||||||
|
} else {
|
||||||
|
gettext(MaybeStatic::Static(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Owned(s) => {
|
||||||
|
if s.is_empty() {
|
||||||
|
L!("")
|
||||||
|
} else {
|
||||||
|
gettext(MaybeStatic::Local(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for LocalizableString {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.localize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This macro takes a string literal and produces a [`LocalizableString`].
|
||||||
|
/// The essential part is the invocation of the proc macro,
|
||||||
|
/// which ensures that the string gets extracted for localization.
|
||||||
|
#[macro_export]
|
||||||
|
#[cfg(feature = "gettext-extract")]
|
||||||
|
macro_rules! localizable_string {
|
||||||
|
($string:literal) => {
|
||||||
|
$crate::localization::LocalizableString::Static(widestring::utf32str!(
|
||||||
|
fish_gettext_extraction::gettext_extract!($string)
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[macro_export]
|
||||||
|
#[cfg(not(feature = "gettext-extract"))]
|
||||||
|
macro_rules! localizable_string {
|
||||||
|
($string:literal) => {
|
||||||
|
$crate::localization::LocalizableString::Static(widestring::utf32str!($string))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub use localizable_string;
|
||||||
|
|
||||||
|
/// Macro for declaring string consts which should be localized.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! localizable_consts {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$vis:vis
|
||||||
|
$name:ident
|
||||||
|
$string:literal
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
$(#[$attr])*
|
||||||
|
$vis const $name: $crate::localization::LocalizableString =
|
||||||
|
localizable_string!($string);
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub use localizable_consts;
|
||||||
|
|
||||||
|
/// Takes a string literal of a [`LocalizableString`].
|
||||||
|
/// Given a string literal, it is extracted for localization.
|
||||||
|
/// Returns a possibly localized `&'static wstr`.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! wgettext {
|
||||||
|
(
|
||||||
|
$string:literal
|
||||||
|
) => {
|
||||||
|
localizable_string!($string).localize()
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$string:expr // format string (LocalizableString)
|
||||||
|
) => {
|
||||||
|
$string.localize()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub use wgettext;
|
||||||
|
|
||||||
|
/// Like wgettext, but applies a sprintf format string.
|
||||||
|
/// The result is a WString.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! wgettext_fmt {
|
||||||
|
(
|
||||||
|
$string:literal // format string
|
||||||
|
$(, $args:expr)* // list of expressions
|
||||||
|
$(,)? // optional trailing comma
|
||||||
|
) => {
|
||||||
|
$crate::wutil::sprintf!(
|
||||||
|
localizable_string!($string).localize(),
|
||||||
|
$($args),*
|
||||||
|
)
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$string:expr // format string (LocalizableString)
|
||||||
|
$(, $args:expr)* // list of expressions
|
||||||
|
$(,)? // optional trailing comma
|
||||||
|
) => {
|
||||||
|
$crate::wutil::sprintf!($string.localize(), $($args),*)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub use wgettext_fmt;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::tests::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_unlocalized() {
|
||||||
|
let _cleanup = test_init();
|
||||||
|
let abc_str = LocalizableString::from_external_source(WString::from("abc"));
|
||||||
|
let s: &'static wstr = wgettext!(abc_str);
|
||||||
|
assert_eq!(s, "abc");
|
||||||
|
let static_str = LocalizableString::from_external_source(WString::from("static"));
|
||||||
|
let s2: &'static wstr = wgettext!(static_str);
|
||||||
|
assert_eq!(s2, "static");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,405 +1,26 @@
|
|||||||
use std::sync::Mutex;
|
mod gettext;
|
||||||
|
pub use gettext::{
|
||||||
|
LocalizableString, localizable_consts, localizable_string, wgettext, wgettext_fmt,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "localize-messages")]
|
||||||
|
mod settings;
|
||||||
|
#[cfg(feature = "localize-messages")]
|
||||||
|
pub use settings::{
|
||||||
|
list_available_languages, status_language, unset_from_status_language_builtin, update_from_env,
|
||||||
|
update_from_status_language_builtin,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
#[cfg(feature = "localize-messages")]
|
||||||
use crate::env::{EnvStack, Environment};
|
/// This function only exists to provide a way for initializing gettext before an `EnvStack` is
|
||||||
use crate::prelude::*;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
fn get_message_locale(vars: &EnvStack) -> Option<(fish_gettext::LocaleVariable, String)> {
|
|
||||||
use fish_gettext::LocaleVariable;
|
|
||||||
let get = |var_str: &wstr, var: LocaleVariable| {
|
|
||||||
vars.get_unless_empty(var_str)
|
|
||||||
.map(|val| (var, val.as_string().to_string()))
|
|
||||||
};
|
|
||||||
get(L!("LC_ALL"), LocaleVariable::LC_ALL)
|
|
||||||
.or_else(|| get(L!("LC_MESSAGES"), LocaleVariable::LC_MESSAGES))
|
|
||||||
.or_else(|| get(L!("LANG"), LocaleVariable::LANG))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
fn get_language_var(vars: &EnvStack) -> Option<Vec<String>> {
|
|
||||||
let langs = vars.get_unless_empty(L!("LANGUAGE"))?;
|
|
||||||
let langs = langs.as_list();
|
|
||||||
let filtered_langs: Vec<String> = langs
|
|
||||||
.iter()
|
|
||||||
.filter(|lang| !lang.is_empty())
|
|
||||||
.map(|lang| lang.to_string())
|
|
||||||
.collect();
|
|
||||||
if filtered_langs.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(filtered_langs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call this when one of `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG` changes.
|
|
||||||
/// Updates internal state such that the correct localizations will be used in subsequent
|
|
||||||
/// localization requests.
|
|
||||||
///
|
|
||||||
/// For deciding how to localize, the following is done:
|
|
||||||
///
|
|
||||||
/// 1. If the language precedence was set via `status language`, env vars are ignored.
|
|
||||||
/// 2. Check the first non-empty value of the env vars `LC_ALL`, `LC_MESSAGES`, `LANG`. If it
|
|
||||||
/// starts with `C` we consider this a C locale and disable localization.
|
|
||||||
/// 3. Otherwise, the value of the `LANGUAGE` env var is used, if non-empty. This allows specifying
|
|
||||||
/// multiple languages, with languages specified first taking precedence, e.g.
|
|
||||||
/// `LANGUAGE=zh_TW:zh_CN:pt_BR`
|
|
||||||
/// 4. Otherwise, the first non-empty value of the env vars `LC_ALL`, `LC_MESSAGES`, `LANG` is
|
|
||||||
/// used. This can only specify a single language, e.g. `LANG=de_AT.UTF-8`.
|
|
||||||
/// There, we normalize locale names by stripping off the suffix, leaving only the `ll_CC` part.
|
|
||||||
/// 5. Otherwise, localization will not happen.
|
|
||||||
///
|
|
||||||
/// If users specify `ll_CC` as a language and we don't have a catalog for this language, but we
|
|
||||||
/// have one for `ll`, that will be used instead. If users specify `ll` (without specifying a
|
|
||||||
/// language variant), which we discourage, and we don't have a catalog for `ll`, but we do have
|
|
||||||
/// one for `ll_CC`, that will be used as a fallback. If we have multiple `ll_*` catalogs, all of
|
|
||||||
/// them will be used, in arbitrary order.
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
pub fn update_from_env(vars: &EnvStack) {
|
|
||||||
fish_gettext::update_from_env(get_message_locale(vars), get_language_var(vars));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
fn append_space_separated_list<S: AsRef<str>>(
|
|
||||||
string: &mut WString,
|
|
||||||
list: impl IntoIterator<Item = S>,
|
|
||||||
) {
|
|
||||||
for lang in list.into_iter() {
|
|
||||||
string.push(' ');
|
|
||||||
string.push_utfstr(&crate::common::escape(
|
|
||||||
WString::from_str(lang.as_ref()).as_utfstr(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
pub struct SetLanguageLints<'a> {
|
|
||||||
duplicates: Vec<&'a str>,
|
|
||||||
non_existing: Vec<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
impl<'a> From<fish_gettext::SetLanguageLints<'a>> for SetLanguageLints<'a> {
|
|
||||||
fn from(lints: fish_gettext::SetLanguageLints<'a>) -> Self {
|
|
||||||
Self {
|
|
||||||
duplicates: lints.duplicates,
|
|
||||||
non_existing: lints.non_existing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
impl<'a> SetLanguageLints<'a> {
|
|
||||||
pub fn display_duplicates(&self) -> WString {
|
|
||||||
let mut result = WString::new();
|
|
||||||
if self.duplicates.is_empty() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result.push_utfstr(wgettext!("Language specifiers appear repeatedly:"));
|
|
||||||
append_space_separated_list(&mut result, &self.duplicates);
|
|
||||||
result.push('\n');
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_non_existing(&self) -> WString {
|
|
||||||
let mut result = WString::new();
|
|
||||||
if self.non_existing.is_empty() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result.push_utfstr(wgettext!("No catalogs available for language specifiers:"));
|
|
||||||
append_space_separated_list(&mut result, &self.non_existing);
|
|
||||||
result.push('\n');
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_all(&self) -> WString {
|
|
||||||
let mut result = WString::new();
|
|
||||||
result.push_utfstr(&self.display_duplicates());
|
|
||||||
result.push_utfstr(&self.display_non_existing());
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Call this when the `status language` builtin should update the language precedence.
|
|
||||||
/// `langs` should be the list of languages the precedence should be set to.
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
pub fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
|
||||||
langs: &'b [S],
|
|
||||||
) -> SetLanguageLints<'a> {
|
|
||||||
fish_gettext::update_from_status_language_builtin(langs).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
pub fn unset_from_status_language_builtin(vars: &EnvStack) {
|
|
||||||
fish_gettext::unset_from_status_language_builtin(
|
|
||||||
get_message_locale(vars),
|
|
||||||
get_language_var(vars),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
pub fn status_language() -> WString {
|
|
||||||
use fish_gettext::LanguagePrecedenceOrigin;
|
|
||||||
let localization_state = fish_gettext::status_language();
|
|
||||||
let mut result = WString::new();
|
|
||||||
localizable_consts!(
|
|
||||||
LANGUAGE_LIST_VARIABLE_ORIGIN "%s variable"
|
|
||||||
);
|
|
||||||
let origin_string = match localization_state.precedence_origin {
|
|
||||||
LanguagePrecedenceOrigin::Default => wgettext!("default").to_owned(),
|
|
||||||
LanguagePrecedenceOrigin::LocaleVariable(var) => {
|
|
||||||
wgettext_fmt!(LANGUAGE_LIST_VARIABLE_ORIGIN, var.as_str())
|
|
||||||
}
|
|
||||||
LanguagePrecedenceOrigin::LanguageEnvVar => {
|
|
||||||
wgettext_fmt!(LANGUAGE_LIST_VARIABLE_ORIGIN, "LANGUAGE")
|
|
||||||
}
|
|
||||||
LanguagePrecedenceOrigin::StatusLanguage => {
|
|
||||||
wgettext_fmt!("%s command", "`status language set`")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
result.push_utfstr(&wgettext_fmt!(
|
|
||||||
"Active languages (source: %s):",
|
|
||||||
origin_string
|
|
||||||
));
|
|
||||||
append_space_separated_list(&mut result, &localization_state.language_precedence);
|
|
||||||
result.push('\n');
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
pub fn list_available_languages() -> WString {
|
|
||||||
let mut languages = WString::new();
|
|
||||||
for lang in fish_gettext::list_available_languages() {
|
|
||||||
languages.push_str(lang);
|
|
||||||
languages.push('\n');
|
|
||||||
}
|
|
||||||
languages
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "localize-messages"))]
|
|
||||||
pub fn initialize_gettext() {}
|
|
||||||
|
|
||||||
/// This function only exists to provide a way for initializing gettext before an [`EnvStack`] is
|
|
||||||
/// available. Without this, early error messages cannot be localized.
|
/// available. Without this, early error messages cannot be localized.
|
||||||
#[cfg(feature = "localize-messages")]
|
pub fn initialize_localization() {
|
||||||
pub fn initialize_gettext() {
|
use crate::env::EnvStack;
|
||||||
let vars = EnvStack::new();
|
use fish_wchar::L;
|
||||||
env_stack_set_from_env!(vars, "LANGUAGE");
|
|
||||||
env_stack_set_from_env!(vars, "LC_ALL");
|
|
||||||
env_stack_set_from_env!(vars, "LC_MESSAGES");
|
|
||||||
env_stack_set_from_env!(vars, "LANG");
|
|
||||||
|
|
||||||
fish_gettext::update_from_env(get_message_locale(&vars), get_language_var(&vars));
|
let env = EnvStack::new();
|
||||||
}
|
env_stack_set_from_env!(env, "LANGUAGE");
|
||||||
|
env_stack_set_from_env!(env, "LC_ALL");
|
||||||
/// Use this function to localize a message.
|
env_stack_set_from_env!(env, "LC_MESSAGES");
|
||||||
/// The [`MaybeStatic`] wrapper type allows avoiding allocating and leaking a new [`wstr`] when no
|
env_stack_set_from_env!(env, "LANG");
|
||||||
/// localization is found and the input is returned, but as a static reference.
|
update_from_env(&env);
|
||||||
fn gettext(message: MaybeStatic) -> &'static wstr {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "localize-messages"))]
|
|
||||||
type NarrowMessage = ();
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
type NarrowMessage = &'static str;
|
|
||||||
|
|
||||||
let message_wstr = match message {
|
|
||||||
MaybeStatic::Static(s) => s,
|
|
||||||
MaybeStatic::Local(s) => s,
|
|
||||||
};
|
|
||||||
static MESSAGE_TO_NARROW: Lazy<Mutex<HashMap<&'static wstr, NarrowMessage>>> =
|
|
||||||
Lazy::new(|| Mutex::new(HashMap::default()));
|
|
||||||
let mut message_to_narrow = MESSAGE_TO_NARROW.lock().unwrap();
|
|
||||||
if !message_to_narrow.contains_key(message_wstr) {
|
|
||||||
let message_wstr: &'static wstr = match message {
|
|
||||||
MaybeStatic::Static(s) => s,
|
|
||||||
MaybeStatic::Local(l) => wstr::from_char_slice(Box::leak(l.as_char_slice().into())),
|
|
||||||
};
|
|
||||||
#[cfg(not(feature = "localize-messages"))]
|
|
||||||
let message_str = ();
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
let message_str = Box::leak(message_wstr.to_string().into_boxed_str());
|
|
||||||
message_to_narrow.insert(message_wstr, message_str);
|
|
||||||
}
|
|
||||||
let (message_static_wstr, message_str) = message_to_narrow.get_key_value(message_wstr).unwrap();
|
|
||||||
|
|
||||||
#[cfg(not(feature = "localize-messages"))]
|
|
||||||
let () = message_str;
|
|
||||||
#[cfg(feature = "localize-messages")]
|
|
||||||
{
|
|
||||||
if let Some(localized_str) = fish_gettext::gettext(message_str) {
|
|
||||||
static LOCALIZATION_TO_WIDE: Lazy<Mutex<HashMap<&'static str, &'static wstr>>> =
|
|
||||||
Lazy::new(|| Mutex::new(HashMap::default()));
|
|
||||||
let mut localizations_to_wide = LOCALIZATION_TO_WIDE.lock().unwrap();
|
|
||||||
if !localizations_to_wide.contains_key(localized_str) {
|
|
||||||
let localization_wstr =
|
|
||||||
Box::leak(WString::from_str(localized_str).into_boxed_utfstr());
|
|
||||||
localizations_to_wide.insert(localized_str, localization_wstr);
|
|
||||||
}
|
|
||||||
return localizations_to_wide.get(localized_str).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No localization found.
|
|
||||||
message_static_wstr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type that can be either a static or local string.
|
|
||||||
enum MaybeStatic<'a> {
|
|
||||||
Static(&'static wstr),
|
|
||||||
Local(&'a wstr),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A string which can be localized.
|
|
||||||
/// The wrapped string itself is the original, unlocalized version.
|
|
||||||
/// Use [`LocalizableString::localize`] to obtain the localized version.
|
|
||||||
///
|
|
||||||
/// Do not construct this type directly.
|
|
||||||
/// For string literals defined in fish's Rust sources,
|
|
||||||
/// use the macros defined in this file.
|
|
||||||
/// For strings defined elsewhere, use [`LocalizableString::from_external_source`].
|
|
||||||
/// Use this function with caution. If the string is not extracted into the gettext PO files from
|
|
||||||
/// which fish obtains localizations, localization will not work.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum LocalizableString {
|
|
||||||
Static(&'static wstr),
|
|
||||||
Owned(WString),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalizableString {
|
|
||||||
/// Create a [`LocalizableString`] from a string which is not from fish's own Rust sources.
|
|
||||||
/// Localizations will only work if this string is extracted into the localization files some
|
|
||||||
/// other way.
|
|
||||||
pub fn from_external_source(s: WString) -> Self {
|
|
||||||
Self::Owned(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the localization of a [`LocalizableString`].
|
|
||||||
/// If original string is empty, an empty `wstr` is returned,
|
|
||||||
/// instead of the gettext metadata.
|
|
||||||
pub fn localize(&self) -> &'static wstr {
|
|
||||||
match self {
|
|
||||||
Self::Static(s) => {
|
|
||||||
if s.is_empty() {
|
|
||||||
L!("")
|
|
||||||
} else {
|
|
||||||
gettext(MaybeStatic::Static(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Owned(s) => {
|
|
||||||
if s.is_empty() {
|
|
||||||
L!("")
|
|
||||||
} else {
|
|
||||||
gettext(MaybeStatic::Local(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for LocalizableString {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.localize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This macro takes a string literal and produces a [`LocalizableString`].
|
|
||||||
/// The essential part is the invocation of the proc macro,
|
|
||||||
/// which ensures that the string gets extracted for localization.
|
|
||||||
#[macro_export]
|
|
||||||
#[cfg(feature = "gettext-extract")]
|
|
||||||
macro_rules! localizable_string {
|
|
||||||
($string:literal) => {
|
|
||||||
$crate::localization::LocalizableString::Static(widestring::utf32str!(
|
|
||||||
fish_gettext_extraction::gettext_extract!($string)
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#[macro_export]
|
|
||||||
#[cfg(not(feature = "gettext-extract"))]
|
|
||||||
macro_rules! localizable_string {
|
|
||||||
($string:literal) => {
|
|
||||||
$crate::localization::LocalizableString::Static(widestring::utf32str!($string))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub use localizable_string;
|
|
||||||
|
|
||||||
/// Macro for declaring string consts which should be localized.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! localizable_consts {
|
|
||||||
(
|
|
||||||
$(
|
|
||||||
$(#[$attr:meta])*
|
|
||||||
$vis:vis
|
|
||||||
$name:ident
|
|
||||||
$string:literal
|
|
||||||
)*
|
|
||||||
) => {
|
|
||||||
$(
|
|
||||||
$(#[$attr])*
|
|
||||||
$vis const $name: $crate::localization::LocalizableString =
|
|
||||||
localizable_string!($string);
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub use localizable_consts;
|
|
||||||
|
|
||||||
/// Takes a string literal of a [`LocalizableString`].
|
|
||||||
/// Given a string literal, it is extracted for localization.
|
|
||||||
/// Returns a possibly localized `&'static wstr`.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! wgettext {
|
|
||||||
(
|
|
||||||
$string:literal
|
|
||||||
) => {
|
|
||||||
localizable_string!($string).localize()
|
|
||||||
};
|
|
||||||
(
|
|
||||||
$string:expr // format string (LocalizableString)
|
|
||||||
) => {
|
|
||||||
$string.localize()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub use wgettext;
|
|
||||||
|
|
||||||
/// Like wgettext, but applies a sprintf format string.
|
|
||||||
/// The result is a WString.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! wgettext_fmt {
|
|
||||||
(
|
|
||||||
$string:literal // format string
|
|
||||||
$(, $args:expr)* // list of expressions
|
|
||||||
$(,)? // optional trailing comma
|
|
||||||
) => {
|
|
||||||
$crate::wutil::sprintf!(
|
|
||||||
localizable_string!($string).localize(),
|
|
||||||
$($args),*
|
|
||||||
)
|
|
||||||
};
|
|
||||||
(
|
|
||||||
$string:expr // format string (LocalizableString)
|
|
||||||
$(, $args:expr)* // list of expressions
|
|
||||||
$(,)? // optional trailing comma
|
|
||||||
) => {
|
|
||||||
$crate::wutil::sprintf!($string.localize(), $($args),*)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub use wgettext_fmt;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::tests::prelude::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[serial]
|
|
||||||
fn test_unlocalized() {
|
|
||||||
let _cleanup = test_init();
|
|
||||||
let abc_str = LocalizableString::from_external_source(WString::from("abc"));
|
|
||||||
let s: &'static wstr = wgettext!(abc_str);
|
|
||||||
assert_eq!(s, "abc");
|
|
||||||
let static_str = LocalizableString::from_external_source(WString::from("static"));
|
|
||||||
let s2: &'static wstr = wgettext!(static_str);
|
|
||||||
assert_eq!(s2, "static");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
413
src/localization/settings.rs
Normal file
413
src/localization/settings.rs
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
use super::{localizable_consts, localizable_string, wgettext, wgettext_fmt};
|
||||||
|
use crate::env::{EnvStack, Environment};
|
||||||
|
use fish_wchar::{L, WString, wstr};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
enum LanguagePrecedenceOrigin {
|
||||||
|
Default,
|
||||||
|
LocaleVariable(LocaleVariable),
|
||||||
|
LanguageEnvVar,
|
||||||
|
StatusLanguage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 LocalizationVariables {
|
||||||
|
message_locale: Option<(LocaleVariable, String)>,
|
||||||
|
language: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalizationVariables {
|
||||||
|
fn get_message_locale(env: &EnvStack) -> Option<(LocaleVariable, String)> {
|
||||||
|
let get = |var_str: &wstr, var: LocaleVariable| {
|
||||||
|
env.get_unless_empty(var_str)
|
||||||
|
.map(|val| (var, val.as_string().to_string()))
|
||||||
|
};
|
||||||
|
get(L!("LC_ALL"), LocaleVariable::LC_ALL)
|
||||||
|
.or_else(|| get(L!("LC_MESSAGES"), LocaleVariable::LC_MESSAGES))
|
||||||
|
.or_else(|| get(L!("LANG"), LocaleVariable::LANG))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_language_var(env: &EnvStack) -> Option<Vec<String>> {
|
||||||
|
let langs = env.get_unless_empty(L!("LANGUAGE"))?;
|
||||||
|
let langs = langs.as_list();
|
||||||
|
let filtered_langs: Vec<String> = langs
|
||||||
|
.iter()
|
||||||
|
.filter(|lang| !lang.is_empty())
|
||||||
|
.map(|lang| lang.to_string())
|
||||||
|
.collect();
|
||||||
|
if filtered_langs.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(filtered_langs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_env(env: &EnvStack) -> Self {
|
||||||
|
Self {
|
||||||
|
message_locale: Self::get_message_locale(env),
|
||||||
|
language: Self::get_language_var(env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_space_separated_list<S: AsRef<str>>(
|
||||||
|
string: &mut WString,
|
||||||
|
list: impl IntoIterator<Item = S>,
|
||||||
|
) {
|
||||||
|
for lang in list.into_iter() {
|
||||||
|
string.push(' ');
|
||||||
|
string.push_utfstr(&crate::common::escape(
|
||||||
|
WString::from_str(lang.as_ref()).as_utfstr(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetLanguageLints<'a> {
|
||||||
|
duplicates: Vec<&'a str>,
|
||||||
|
non_existing: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SetLanguageLints<'a> {
|
||||||
|
fn display_duplicates(&self) -> WString {
|
||||||
|
let mut result = WString::new();
|
||||||
|
if self.duplicates.is_empty() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push_utfstr(wgettext!("Language specifiers appear repeatedly:"));
|
||||||
|
append_space_separated_list(&mut result, &self.duplicates);
|
||||||
|
result.push('\n');
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_non_existing(&self) -> WString {
|
||||||
|
let mut result = WString::new();
|
||||||
|
if self.non_existing.is_empty() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push_utfstr(wgettext!("No catalogs available for language specifiers:"));
|
||||||
|
append_space_separated_list(&mut result, &self.non_existing);
|
||||||
|
result.push('\n');
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_all(&self) -> WString {
|
||||||
|
let mut result = WString::new();
|
||||||
|
result.push_utfstr(&self.display_duplicates());
|
||||||
|
result.push_utfstr(&self.display_non_existing());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LocalizationState {
|
||||||
|
precedence_origin: LanguagePrecedenceOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalizationState {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
precedence_origin: LanguagePrecedenceOrigin::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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_best_matches<'a, 'b: 'a, L: Copy>(
|
||||||
|
language: &str,
|
||||||
|
available_languages: &'a HashMap<&'b str, L>,
|
||||||
|
) -> Vec<(&'b str, L)> {
|
||||||
|
// Try the exact name first.
|
||||||
|
// If there already is a corresponding catalog return the language.
|
||||||
|
if let Some((&lang_str, &lang_value)) = available_languages.get_key_value(language) {
|
||||||
|
return vec![(lang_str, lang_value)];
|
||||||
|
}
|
||||||
|
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_str, &localization_lang) in available_languages.iter() {
|
||||||
|
if lang_str.starts_with(&ll_prefix) {
|
||||||
|
lang_catalogs.push((lang_str, localization_lang));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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((&lang_str, &lang_value)) =
|
||||||
|
available_languages.get_key_value(language_without_country_code)
|
||||||
|
{
|
||||||
|
vec![(lang_str, lang_value)]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_from_env(&mut self, localization_vars: LocalizationVariables) {
|
||||||
|
// Do not override values set via `status language`.
|
||||||
|
if self.precedence_origin == LanguagePrecedenceOrigin::StatusLanguage {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((precedence_origin, locale)) = &localization_vars.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);
|
||||||
|
fish_gettext::set_language_precedence(&[]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (precedence_origin, language_list) = if let Some(list) = localization_vars.language {
|
||||||
|
(LanguagePrecedenceOrigin::LanguageEnvVar, list)
|
||||||
|
} else if let Some((precedence_origin, locale)) = &localization_vars.message_locale {
|
||||||
|
// Strip off encoding and modifier. (We always expect UTF-8 and don't support modifiers.)
|
||||||
|
let normalized_name = locale
|
||||||
|
.split_once(|c: char| !(c.is_ascii_alphabetic() || c == '_'))
|
||||||
|
.map_or(locale.as_str(), |(lang_name, _)| lang_name)
|
||||||
|
.to_owned();
|
||||||
|
// 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![])
|
||||||
|
};
|
||||||
|
fn update_precedence<'a, 'b: 'a, LocalizationLanguage: Copy + 'a>(
|
||||||
|
language_list: &[String],
|
||||||
|
get_available_languages: fn() -> &'a HashMap<&'b str, LocalizationLanguage>,
|
||||||
|
set_language_precedence: fn(&[LocalizationLanguage]),
|
||||||
|
) {
|
||||||
|
let available_langs = get_available_languages();
|
||||||
|
let mut seen_languages = HashSet::new();
|
||||||
|
let language_precedence: Vec<_> = language_list
|
||||||
|
.iter()
|
||||||
|
.flat_map(|lang| LocalizationState::find_best_matches(lang, available_langs))
|
||||||
|
.filter(|&(lang_str, _)| seen_languages.insert(lang_str))
|
||||||
|
.map(|(_, localization_lang)| localization_lang)
|
||||||
|
.collect();
|
||||||
|
set_language_precedence(&language_precedence);
|
||||||
|
}
|
||||||
|
update_precedence(
|
||||||
|
&language_list,
|
||||||
|
fish_gettext::get_available_languages,
|
||||||
|
fish_gettext::set_language_precedence,
|
||||||
|
);
|
||||||
|
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_in_input = HashSet::new();
|
||||||
|
let mut unique_lang_strs = vec![];
|
||||||
|
let mut duplicates = vec![];
|
||||||
|
for lang in langs {
|
||||||
|
let lang = lang.as_ref();
|
||||||
|
if seen_in_input.insert(lang) {
|
||||||
|
unique_lang_strs.push(lang);
|
||||||
|
} else {
|
||||||
|
duplicates.push(lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut all_available_langs = HashSet::new();
|
||||||
|
fn update_precedence<'a, 'b, 'c: 'a + 'b, LocalizationLanguage: Copy + 'a>(
|
||||||
|
unique_lang_strs: &[&str],
|
||||||
|
get_available_languages: fn() -> &'a HashMap<&'c str, LocalizationLanguage>,
|
||||||
|
set_language_precedence: fn(&[LocalizationLanguage]),
|
||||||
|
all_available_langs: &'b mut HashSet<&'c str>,
|
||||||
|
) {
|
||||||
|
let available_langs = get_available_languages();
|
||||||
|
for &lang in available_langs.keys() {
|
||||||
|
all_available_langs.insert(lang);
|
||||||
|
}
|
||||||
|
let mut existing_langs = vec![];
|
||||||
|
for lang in unique_lang_strs {
|
||||||
|
if let Some((&lang_str, &lang_value)) = available_langs.get_key_value(lang) {
|
||||||
|
existing_langs.push((lang_str, lang_value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let unique_langs: Vec<_> = existing_langs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|&(lang, _)| seen.insert(lang))
|
||||||
|
.map(|(_, localization_lang)| localization_lang)
|
||||||
|
.collect();
|
||||||
|
set_language_precedence(&unique_langs);
|
||||||
|
}
|
||||||
|
update_precedence(
|
||||||
|
&unique_lang_strs,
|
||||||
|
fish_gettext::get_available_languages,
|
||||||
|
fish_gettext::set_language_precedence,
|
||||||
|
&mut all_available_langs,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.precedence_origin = LanguagePrecedenceOrigin::StatusLanguage;
|
||||||
|
|
||||||
|
let mut seen_non_existing = HashSet::new();
|
||||||
|
let non_existing: Vec<&str> = langs
|
||||||
|
.iter()
|
||||||
|
.map(|lang| lang.as_ref())
|
||||||
|
.filter(|&lang| !all_available_langs.contains(lang) && seen_non_existing.insert(lang))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
SetLanguageLints {
|
||||||
|
duplicates,
|
||||||
|
non_existing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: LazyLock<Mutex<LocalizationState>> =
|
||||||
|
LazyLock::new(|| Mutex::new(LocalizationState::new()));
|
||||||
|
|
||||||
|
/// Call this when one of `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG` changes.
|
||||||
|
/// Updates internal state such that the correct localizations will be used in subsequent
|
||||||
|
/// localization requests.
|
||||||
|
///
|
||||||
|
/// For deciding how to localize, the following is done:
|
||||||
|
///
|
||||||
|
/// 1. If the language precedence was set via `status language`, env vars are ignored.
|
||||||
|
/// 2. Check the first non-empty value of the env vars `LC_ALL`, `LC_MESSAGES`, `LANG`. If it
|
||||||
|
/// starts with `C` or `POSIX` we consider this a C locale and disable localization.
|
||||||
|
/// 3. Otherwise, the value of the `LANGUAGE` env var is used, if non-empty. This allows specifying
|
||||||
|
/// multiple languages, with languages specified first taking precedence, e.g.
|
||||||
|
/// `LANGUAGE=zh_TW:zh_CN:pt_BR`
|
||||||
|
/// 4. Otherwise, the first non-empty value of the env vars `LC_ALL`, `LC_MESSAGES`, `LANG` is
|
||||||
|
/// used. This can only specify a single language, e.g. `LANG=de_AT.UTF-8`.
|
||||||
|
/// There, we normalize locale names by stripping off the suffix, leaving only the `ll_CC` part.
|
||||||
|
/// 5. Otherwise, localization will not happen.
|
||||||
|
///
|
||||||
|
/// If users specify `ll_CC` as a language and we don't have a catalog for this language, but we
|
||||||
|
/// have one for `ll`, that will be used instead. If users specify `ll` (without specifying a
|
||||||
|
/// language variant), which we discourage, and we don't have a catalog for `ll`, but we do have
|
||||||
|
/// one for `ll_CC`, that will be used as a fallback. If we have multiple `ll_*` catalogs, all of
|
||||||
|
/// them will be used, in arbitrary order.
|
||||||
|
pub fn update_from_env(env: &EnvStack) {
|
||||||
|
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||||
|
localization_state.update_from_env(LocalizationVariables::from_env(env));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this when the `status language` builtin should update the language precedence.
|
||||||
|
/// `langs` should be the list of languages the precedence should be set to.
|
||||||
|
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(env: &EnvStack) {
|
||||||
|
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||||
|
localization_state.precedence_origin = LanguagePrecedenceOrigin::Default;
|
||||||
|
localization_state.update_from_env(LocalizationVariables::from_env(env));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_language() -> WString {
|
||||||
|
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||||
|
let mut result = WString::new();
|
||||||
|
localizable_consts!(
|
||||||
|
LANGUAGE_LIST_VARIABLE_ORIGIN "%s variable"
|
||||||
|
);
|
||||||
|
let origin_string = match localization_state.precedence_origin {
|
||||||
|
LanguagePrecedenceOrigin::Default => wgettext!("default").to_owned(),
|
||||||
|
LanguagePrecedenceOrigin::LocaleVariable(var) => {
|
||||||
|
wgettext_fmt!(LANGUAGE_LIST_VARIABLE_ORIGIN, var.as_str())
|
||||||
|
}
|
||||||
|
LanguagePrecedenceOrigin::LanguageEnvVar => {
|
||||||
|
wgettext_fmt!(LANGUAGE_LIST_VARIABLE_ORIGIN, "LANGUAGE")
|
||||||
|
}
|
||||||
|
LanguagePrecedenceOrigin::StatusLanguage => {
|
||||||
|
wgettext_fmt!("%s command", "`status language set`")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
result.push_utfstr(&wgettext_fmt!(
|
||||||
|
"Active languages (source: %s):",
|
||||||
|
origin_string
|
||||||
|
));
|
||||||
|
let gettext_language_precedence = fish_gettext::get_language_precedence();
|
||||||
|
append_space_separated_list(&mut result, &gettext_language_precedence);
|
||||||
|
result.push('\n');
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_available_languages() -> WString {
|
||||||
|
let mut language_set = HashSet::new();
|
||||||
|
fn add_languages<'a, 'b: 'a, LocalizationLanguage: 'a>(
|
||||||
|
language_set: &mut HashSet<&'b str>,
|
||||||
|
get_available_languages: fn() -> &'a HashMap<&'b str, LocalizationLanguage>,
|
||||||
|
) {
|
||||||
|
for &lang in get_available_languages().keys() {
|
||||||
|
language_set.insert(lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_languages(&mut language_set, fish_gettext::get_available_languages);
|
||||||
|
let mut language_list = Vec::from_iter(language_set);
|
||||||
|
language_list.sort();
|
||||||
|
let mut languages = WString::new();
|
||||||
|
for lang in language_list {
|
||||||
|
languages.push_str(lang);
|
||||||
|
languages.push('\n');
|
||||||
|
}
|
||||||
|
languages
|
||||||
|
}
|
||||||
24
src/pager.rs
24
src/pager.rs
@@ -1,5 +1,6 @@
|
|||||||
//! Pager support.
|
//! Pager support.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ pub struct Pager {
|
|||||||
// then we definitely need to re-render.
|
// then we definitely need to re-render.
|
||||||
have_unrendered_completions: bool,
|
have_unrendered_completions: bool,
|
||||||
|
|
||||||
prefix: WString,
|
prefix: Cow<'static, wstr>,
|
||||||
highlight_prefix: bool,
|
highlight_prefix: bool,
|
||||||
|
|
||||||
// The text of the search field.
|
// The text of the search field.
|
||||||
@@ -390,8 +391,12 @@ fn completion_info_passes_filter(&self, info: &PagerComp) -> bool {
|
|||||||
|
|
||||||
// Match against the completion strings.
|
// Match against the completion strings.
|
||||||
for candidate in &info.comp {
|
for candidate in &info.comp {
|
||||||
if string_fuzzy_match_string(needle, &(self.prefix.clone() + &candidate[..]), false)
|
if string_fuzzy_match_string(
|
||||||
.is_some()
|
needle,
|
||||||
|
&(self.prefix.clone().into_owned() + &candidate[..]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -429,7 +434,7 @@ fn completion_print(
|
|||||||
let effective_selected_idx = self.visual_selected_completion_index(rows, cols);
|
let effective_selected_idx = self.visual_selected_completion_index(rows, cols);
|
||||||
|
|
||||||
for row in row_start..row_stop {
|
for row in row_start..row_stop {
|
||||||
for (col, col_width) in width_by_column.iter().cloned().enumerate() {
|
for (col, col_width) in width_by_column.iter().copied().enumerate() {
|
||||||
let idx = col * rows + row;
|
let idx = col * rows + row;
|
||||||
if lst.len() <= idx {
|
if lst.len() <= idx {
|
||||||
continue;
|
continue;
|
||||||
@@ -639,7 +644,7 @@ pub fn set_completions(&mut self, raw_completions: &[Completion], enable_refilte
|
|||||||
self.unfiltered_completion_infos = process_completions_into_infos(raw_completions);
|
self.unfiltered_completion_infos = process_completions_into_infos(raw_completions);
|
||||||
|
|
||||||
// Maybe join them.
|
// Maybe join them.
|
||||||
if self.prefix == "-" {
|
if *self.prefix == "-" {
|
||||||
join_completions(&mut self.unfiltered_completion_infos);
|
join_completions(&mut self.unfiltered_completion_infos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,8 +661,8 @@ pub fn set_completions(&mut self, raw_completions: &[Completion], enable_refilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sets the prefix.
|
// Sets the prefix.
|
||||||
pub fn set_prefix(&mut self, pref: &wstr, highlight: bool /* = true */) {
|
pub fn set_prefix(&mut self, prefix: Cow<'static, wstr>, highlight: bool /* = true */) {
|
||||||
self.prefix = pref.to_owned();
|
self.prefix = prefix;
|
||||||
self.highlight_prefix = highlight;
|
self.highlight_prefix = highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,7 +1000,7 @@ pub fn is_empty(&self) -> bool {
|
|||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.unfiltered_completion_infos.clear();
|
self.unfiltered_completion_infos.clear();
|
||||||
self.completion_infos.clear();
|
self.completion_infos.clear();
|
||||||
self.prefix.clear();
|
self.prefix = Cow::Borrowed(L!(""));
|
||||||
self.highlight_prefix = false;
|
self.highlight_prefix = false;
|
||||||
self.selected_completion_idx = None;
|
self.selected_completion_idx = None;
|
||||||
self.fully_disclosed = false;
|
self.fully_disclosed = false;
|
||||||
@@ -1283,6 +1288,7 @@ mod tests {
|
|||||||
use crate::termsize::Termsize;
|
use crate::termsize::Termsize;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
use crate::wcstringutil::StringFuzzyMatch;
|
use crate::wcstringutil::StringFuzzyMatch;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::num::NonZeroU16;
|
use std::num::NonZeroU16;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1484,7 +1490,7 @@ macro_rules! validate {
|
|||||||
StringFuzzyMatch::exact_match(),
|
StringFuzzyMatch::exact_match(),
|
||||||
CompleteFlags::default(),
|
CompleteFlags::default(),
|
||||||
)];
|
)];
|
||||||
pager.set_prefix(L!("{\\\n"), false); // }
|
pager.set_prefix(Cow::Borrowed(L!("{\\\n")), false); // }
|
||||||
pager.set_completions(&c4s, true);
|
pager.set_completions(&c4s, true);
|
||||||
validate!(&mut pager, 30, L!("{\\␊Hello")); // }
|
validate!(&mut pager, 30, L!("{\\␊Hello")); // }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
panic::{UnwindSafe, set_hook, take_hook},
|
panic::{UnwindSafe, set_hook, take_hook},
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::{
|
||||||
|
OnceLock,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use libc::STDIN_FILENO;
|
use libc::STDIN_FILENO;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{get_program_name, read_blocked},
|
common::{get_program_name, read_blocked},
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
threads::{asan_maybe_exit, is_main_thread},
|
threads::{asan_maybe_exit, is_main_thread},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static AT_EXIT: OnceCell<Box<dyn Fn() + Send + Sync>> = OnceCell::new();
|
pub static AT_EXIT: OnceLock<Box<dyn Fn() + Send + Sync>> = OnceLock::new();
|
||||||
|
|
||||||
pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
||||||
// The isatty() check will stop us from hanging in most fish tests, but not those
|
// The isatty() check will stop us from hanging in most fish tests, but not those
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ fn infinite_recursive_statement_in_job_list<'b>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if infinite_recursive_statement.is_some() {
|
if infinite_recursive_statement.is_some() {
|
||||||
*out_func_name = forbidden_function_name.to_owned();
|
forbidden_function_name.clone_into(out_func_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// may be none
|
// may be none
|
||||||
@@ -1865,7 +1865,7 @@ fn setup_group(&self, ctx: &OperationContext<'_>, j: &mut Job) {
|
|||||||
.is_some_and(|job_group| job_group.has_job_id() || !j.wants_job_id())
|
.is_some_and(|job_group| job_group.has_job_id() || !j.wants_job_id())
|
||||||
&& !j.is_initially_background()
|
&& !j.is_initially_background()
|
||||||
{
|
{
|
||||||
j.group = ctx.job_group.clone();
|
j.group.clone_from(&ctx.job_group);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/path.rs
20
src/path.rs
@@ -10,11 +10,11 @@
|
|||||||
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wstat};
|
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wstat};
|
||||||
use errno::{Errno, errno, set_errno};
|
use errno::{Errno, errno, set_errno};
|
||||||
use libc::{EACCES, ENOENT, ENOTDIR, F_OK, X_OK};
|
use libc::{EACCES, ENOENT, ENOTDIR, F_OK, X_OK};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::unix::prelude::*;
|
use std::os::unix::prelude::*;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
/// Returns the user configuration directory for fish. If the directory or one of its parents
|
/// Returns the user configuration directory for fish. If the directory or one of its parents
|
||||||
/// doesn't exist, they are first created.
|
/// doesn't exist, they are first created.
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
pub fn path_get_config() -> Option<WString> {
|
pub fn path_get_config() -> Option<WString> {
|
||||||
let dir = get_config_directory();
|
let dir = get_config_directory();
|
||||||
if dir.success() {
|
if dir.success() {
|
||||||
Some(dir.path.to_owned())
|
Some(dir.path.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ pub fn path_get_config() -> Option<WString> {
|
|||||||
pub fn path_get_data() -> Option<WString> {
|
pub fn path_get_data() -> Option<WString> {
|
||||||
let dir = get_data_directory();
|
let dir = get_data_directory();
|
||||||
if dir.success() {
|
if dir.success() {
|
||||||
Some(dir.path.to_owned())
|
Some(dir.path.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ pub fn path_get_data() -> Option<WString> {
|
|||||||
pub fn path_get_cache() -> Option<WString> {
|
pub fn path_get_cache() -> Option<WString> {
|
||||||
let dir = get_cache_directory();
|
let dir = get_cache_directory();
|
||||||
if dir.success() {
|
if dir.success() {
|
||||||
Some(dir.path.to_owned())
|
Some(dir.path.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -729,20 +729,20 @@ pub fn path_remoteness(path: &wstr) -> DirRemoteness {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_data_directory() -> &'static BaseDirectory {
|
fn get_data_directory() -> &'static BaseDirectory {
|
||||||
static DIR: Lazy<BaseDirectory> =
|
static DIR: LazyLock<BaseDirectory> =
|
||||||
Lazy::new(|| make_base_directory(L!("XDG_DATA_HOME"), L!("/.local/share/fish")));
|
LazyLock::new(|| make_base_directory(L!("XDG_DATA_HOME"), L!("/.local/share/fish")));
|
||||||
&DIR
|
&DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cache_directory() -> &'static BaseDirectory {
|
fn get_cache_directory() -> &'static BaseDirectory {
|
||||||
static DIR: Lazy<BaseDirectory> =
|
static DIR: LazyLock<BaseDirectory> =
|
||||||
Lazy::new(|| make_base_directory(L!("XDG_CACHE_HOME"), L!("/.cache/fish")));
|
LazyLock::new(|| make_base_directory(L!("XDG_CACHE_HOME"), L!("/.cache/fish")));
|
||||||
&DIR
|
&DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_directory() -> &'static BaseDirectory {
|
fn get_config_directory() -> &'static BaseDirectory {
|
||||||
static DIR: Lazy<BaseDirectory> =
|
static DIR: LazyLock<BaseDirectory> =
|
||||||
Lazy::new(|| make_base_directory(L!("XDG_CONFIG_HOME"), L!("/.config/fish")));
|
LazyLock::new(|| make_base_directory(L!("XDG_CONFIG_HOME"), L!("/.config/fish")));
|
||||||
&DIR
|
&DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGSYS, SIGTTOU, STDOUT_FILENO, WCONTINUED,
|
SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGSYS, SIGTTOU, STDOUT_FILENO, WCONTINUED,
|
||||||
WEXITSTATUS, WIFCONTINUED, WIFEXITED, WIFSIGNALED, WIFSTOPPED, WNOHANG, WTERMSIG, WUNTRACED,
|
WEXITSTATUS, WIFCONTINUED, WIFEXITED, WIFSIGNALED, WIFSTOPPED, WNOHANG, WTERMSIG, WUNTRACED,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
#[cfg(not(target_has_atomic = "64"))]
|
#[cfg(not(target_has_atomic = "64"))]
|
||||||
use portable_atomic::AtomicU64;
|
use portable_atomic::AtomicU64;
|
||||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||||
@@ -40,7 +39,7 @@
|
|||||||
#[cfg(target_has_atomic = "64")]
|
#[cfg(target_has_atomic = "64")]
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::{Arc, Mutex, OnceLock};
|
use std::sync::{Arc, LazyLock, Mutex, OnceLock};
|
||||||
|
|
||||||
/// Types of processes.
|
/// Types of processes.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -1615,8 +1614,8 @@ fn process_clean_after_marking(parser: &Parser, interactive: bool) -> bool {
|
|||||||
|
|
||||||
pub fn have_proc_stat() -> bool {
|
pub fn have_proc_stat() -> bool {
|
||||||
// Check for /proc/self/stat to see if we are running with Linux-style procfs.
|
// Check for /proc/self/stat to see if we are running with Linux-style procfs.
|
||||||
static HAVE_PROC_STAT_RESULT: Lazy<bool> =
|
static HAVE_PROC_STAT_RESULT: LazyLock<bool> =
|
||||||
Lazy::new(|| fs::metadata("/proc/self/stat").is_ok());
|
LazyLock::new(|| fs::metadata("/proc/self/stat").is_ok());
|
||||||
*HAVE_PROC_STAT_RESULT
|
*HAVE_PROC_STAT_RESULT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
};
|
};
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
#[cfg(not(target_has_atomic = "64"))]
|
#[cfg(not(target_has_atomic = "64"))]
|
||||||
use portable_atomic::AtomicU64;
|
use portable_atomic::AtomicU64;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
#[cfg(target_has_atomic = "64")]
|
#[cfg(target_has_atomic = "64")]
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::atomic::{AtomicI32, AtomicU8, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicI32, AtomicU8, AtomicU32, Ordering};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, LazyLock, Mutex, MutexGuard, OnceLock};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use errno::{Errno, errno};
|
use errno::{Errno, errno};
|
||||||
@@ -175,17 +174,16 @@ enum ExitState {
|
|||||||
|
|
||||||
static EXIT_STATE: AtomicU8 = AtomicU8::new(ExitState::None as u8);
|
static EXIT_STATE: AtomicU8 = AtomicU8::new(ExitState::None as u8);
|
||||||
|
|
||||||
pub static SHELL_MODES: Lazy<Mutex<libc::termios>> =
|
pub static SHELL_MODES: LazyLock<Mutex<libc::termios>> =
|
||||||
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
LazyLock::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
||||||
|
|
||||||
/// The valid terminal modes on startup.
|
/// The valid terminal modes on startup.
|
||||||
/// Warning: this is read from the SIGTERM handler! Hence the raw global.
|
/// Warning: this is read from the SIGTERM handler! Hence the raw global.
|
||||||
static TERMINAL_MODE_ON_STARTUP: once_cell::sync::OnceCell<libc::termios> =
|
static TERMINAL_MODE_ON_STARTUP: OnceLock<libc::termios> = OnceLock::new();
|
||||||
once_cell::sync::OnceCell::new();
|
|
||||||
|
|
||||||
/// Mode we use to execute programs.
|
/// Mode we use to execute programs.
|
||||||
static TTY_MODES_FOR_EXTERNAL_CMDS: Lazy<Mutex<libc::termios>> =
|
static TTY_MODES_FOR_EXTERNAL_CMDS: LazyLock<Mutex<libc::termios>> =
|
||||||
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
LazyLock::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
||||||
|
|
||||||
static RUN_COUNT: AtomicU64 = AtomicU64::new(0);
|
static RUN_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
@@ -560,6 +558,12 @@ enum JumpPrecision {
|
|||||||
To,
|
To,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum CompletionAction {
|
||||||
|
ShownAmbiguous,
|
||||||
|
InsertedUnique,
|
||||||
|
}
|
||||||
|
|
||||||
/// readline_loop_state_t encapsulates the state used in a readline loop.
|
/// readline_loop_state_t encapsulates the state used in a readline loop.
|
||||||
struct ReadlineLoopState {
|
struct ReadlineLoopState {
|
||||||
/// The last command that was executed.
|
/// The last command that was executed.
|
||||||
@@ -569,10 +573,7 @@ struct ReadlineLoopState {
|
|||||||
yank_len: usize,
|
yank_len: usize,
|
||||||
|
|
||||||
/// If the last "complete" readline command has inserted text into the command line.
|
/// If the last "complete" readline command has inserted text into the command line.
|
||||||
complete_did_insert: bool,
|
completion_action: Option<CompletionAction>,
|
||||||
|
|
||||||
/// List of completions.
|
|
||||||
comp: Vec<Completion>,
|
|
||||||
|
|
||||||
/// Whether the loop has finished, due to reaching the character limit or through executing a
|
/// Whether the loop has finished, due to reaching the character limit or through executing a
|
||||||
/// command.
|
/// command.
|
||||||
@@ -587,8 +588,7 @@ fn new() -> Self {
|
|||||||
Self {
|
Self {
|
||||||
last_cmd: None,
|
last_cmd: None,
|
||||||
yank_len: 0,
|
yank_len: 0,
|
||||||
complete_did_insert: true,
|
completion_action: None,
|
||||||
comp: vec![],
|
|
||||||
finished: false,
|
finished: false,
|
||||||
nchars: None,
|
nchars: None,
|
||||||
}
|
}
|
||||||
@@ -2472,7 +2472,6 @@ fn eval_bind_cmd(&mut self, cmd: &wstr) {
|
|||||||
|
|
||||||
self.parser.eval(cmd, &IoChain::new());
|
self.parser.eval(cmd, &IoChain::new());
|
||||||
self.parser.set_last_statuses(last_statuses);
|
self.parser.set_last_statuses(last_statuses);
|
||||||
scoped_tty.reclaim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a sequence of commands from an input binding.
|
/// Run a sequence of commands from an input binding.
|
||||||
@@ -2558,7 +2557,7 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
|||||||
if self.reset_loop_state {
|
if self.reset_loop_state {
|
||||||
self.reset_loop_state = false;
|
self.reset_loop_state = false;
|
||||||
self.rls_mut().last_cmd = None;
|
self.rls_mut().last_cmd = None;
|
||||||
self.rls_mut().complete_did_insert = false;
|
self.rls_mut().completion_action = None;
|
||||||
}
|
}
|
||||||
// Perhaps update the termsize. This is cheap if it has not changed.
|
// Perhaps update the termsize. This is cheap if it has not changed.
|
||||||
reader_update_termsize(self.parser);
|
reader_update_termsize(self.parser);
|
||||||
@@ -2920,7 +2919,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
|||||||
// but never complete{,_and_search})
|
// but never complete{,_and_search})
|
||||||
//
|
//
|
||||||
// Also paging is already cancelled above.
|
// Also paging is already cancelled above.
|
||||||
if self.rls().complete_did_insert
|
if self.rls().completion_action == Some(CompletionAction::InsertedUnique)
|
||||||
&& matches!(
|
&& matches!(
|
||||||
self.rls().last_cmd,
|
self.rls().last_cmd,
|
||||||
Some(rl::Complete | rl::CompleteAndSearch)
|
Some(rl::Complete | rl::CompleteAndSearch)
|
||||||
@@ -2969,8 +2968,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if self.is_navigating_pager_contents()
|
if self.is_navigating_pager_contents()
|
||||||
|| (!self.rls().comp.is_empty()
|
|| (self.rls().completion_action == Some(CompletionAction::ShownAmbiguous)
|
||||||
&& !self.rls().complete_did_insert
|
|
||||||
&& self.rls().last_cmd == Some(rl::Complete))
|
&& self.rls().last_cmd == Some(rl::Complete))
|
||||||
{
|
{
|
||||||
// The user typed complete more than once in a row. If we are not yet fully
|
// The user typed complete more than once in a row. If we are not yet fully
|
||||||
@@ -2994,7 +2992,6 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
|||||||
let mut tty = TtyHandoff::new(reader_save_screen_state);
|
let mut tty = TtyHandoff::new(reader_save_screen_state);
|
||||||
tty.disable_tty_protocols();
|
tty.disable_tty_protocols();
|
||||||
self.compute_and_apply_completions(c);
|
self.compute_and_apply_completions(c);
|
||||||
tty.reclaim();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rl::PagerToggleSearch => {
|
rl::PagerToggleSearch => {
|
||||||
@@ -3295,7 +3292,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
|||||||
self.history_pager = Some(0..1);
|
self.history_pager = Some(0..1);
|
||||||
// Update the pager data.
|
// Update the pager data.
|
||||||
self.pager.set_search_field_shown(true);
|
self.pager.set_search_field_shown(true);
|
||||||
self.pager.set_prefix(L!("► "), false);
|
self.pager.set_prefix(Cow::Borrowed(L!("► ")), false);
|
||||||
// Update the search field, which triggers the actual history search.
|
// Update the search field, which triggers the actual history search.
|
||||||
let search_string = if !self.history_search.active()
|
let search_string = if !self.history_search.active()
|
||||||
|| self.history_search.search_string().is_empty()
|
|| self.history_search.search_string().is_empty()
|
||||||
@@ -5988,14 +5985,10 @@ fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
|
|||||||
|
|
||||||
// Provide values for `status current-command` and `status current-commandline`
|
// Provide values for `status current-command` and `status current-commandline`
|
||||||
if !ft.is_empty() {
|
if !ft.is_empty() {
|
||||||
parser.libdata_mut().status_vars.command = ft.to_owned();
|
parser.libdata_mut().status_vars.command = ft.clone();
|
||||||
parser.libdata_mut().status_vars.commandline = cmd.to_owned();
|
parser.libdata_mut().status_vars.commandline = cmd.to_owned();
|
||||||
// Also provide a value for the deprecated fish 2.0 $_ variable
|
// Also provide a value for the deprecated fish 2.0 $_ variable
|
||||||
parser.set_one(
|
parser.set_one(L!("_"), ParserEnvSetMode::new(EnvMode::GLOBAL), ft.clone());
|
||||||
L!("_"),
|
|
||||||
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
|
||||||
ft.to_owned(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_write_title(cmd, parser, true);
|
reader_write_title(cmd, parser, true);
|
||||||
@@ -6116,19 +6109,20 @@ fn add_to_history(&mut self) {
|
|||||||
|
|
||||||
// Remove ephemeral items - even if the text is empty.
|
// Remove ephemeral items - even if the text is empty.
|
||||||
self.history.remove_ephemeral_items();
|
self.history.remove_ephemeral_items();
|
||||||
|
if text.is_empty() {
|
||||||
if !text.is_empty() {
|
return;
|
||||||
// Mark this item as ephemeral if should_add_to_history says no (#615).
|
|
||||||
let mode = if !self.should_add_to_history(&text) {
|
|
||||||
PersistenceMode::Ephemeral
|
|
||||||
} else if in_private_mode(self.vars()) {
|
|
||||||
PersistenceMode::Memory
|
|
||||||
} else {
|
|
||||||
PersistenceMode::Disk
|
|
||||||
};
|
|
||||||
self.history
|
|
||||||
.add_pending_with_file_detection(&text, &self.parser.variables, mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark this item as ephemeral if should_add_to_history says no (#615).
|
||||||
|
let mode = if !self.should_add_to_history(&text) {
|
||||||
|
PersistenceMode::Ephemeral
|
||||||
|
} else if in_private_mode(self.vars()) {
|
||||||
|
PersistenceMode::Memory
|
||||||
|
} else {
|
||||||
|
PersistenceMode::Disk
|
||||||
|
};
|
||||||
|
self.history
|
||||||
|
.add_pending_with_file_detection(&text, &self.parser.variables, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if we have background jobs that we have not warned about.
|
/// Check if we have background jobs that we have not warned about.
|
||||||
@@ -6519,15 +6513,6 @@ fn reader_can_replace(s: &wstr, flags: CompleteFlags) -> bool {
|
|||||||
.any(|c| matches!(c, '$' | '*' | '?' | '(' | '{' | '}' | ')'))
|
.any(|c| matches!(c, '$' | '*' | '?' | '(' | '{' | '}' | ')'))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the best (lowest) match rank for a set of completions.
|
|
||||||
fn get_best_rank(comp: &[Completion]) -> u32 {
|
|
||||||
let mut best_rank = u32::MAX;
|
|
||||||
for c in comp {
|
|
||||||
best_rank = best_rank.min(c.rank());
|
|
||||||
}
|
|
||||||
best_rank
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Reader<'a> {
|
impl<'a> Reader<'a> {
|
||||||
/// Compute completions and update the pager and/or commandline as needed.
|
/// Compute completions and update the pager and/or commandline as needed.
|
||||||
fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
||||||
@@ -6590,8 +6575,7 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ExpandResultCode::ok => {
|
ExpandResultCode::ok => {
|
||||||
self.rls_mut().comp.clear();
|
self.rls_mut().completion_action = None;
|
||||||
self.rls_mut().complete_did_insert = false;
|
|
||||||
self.push_edit(
|
self.push_edit(
|
||||||
EditableLineTag::Commandline,
|
EditableLineTag::Commandline,
|
||||||
Edit::new(token_range, wc_expanded),
|
Edit::new(token_range, wc_expanded),
|
||||||
@@ -6604,12 +6588,11 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
|||||||
// up to the end of the token we're completing.
|
// up to the end of the token we're completing.
|
||||||
let cmdsub = &el.text()[cmdsub_range.start..token_range.end];
|
let cmdsub = &el.text()[cmdsub_range.start..token_range.end];
|
||||||
|
|
||||||
let (comp, _needs_load) = complete(
|
let (mut comp, _needs_load) = complete(
|
||||||
cmdsub,
|
cmdsub,
|
||||||
CompletionRequestOptions::normal(),
|
CompletionRequestOptions::normal(),
|
||||||
&self.parser.context(),
|
&self.parser.context(),
|
||||||
);
|
);
|
||||||
self.rls_mut().comp = comp;
|
|
||||||
|
|
||||||
let el = &self.command_line;
|
let el = &self.command_line;
|
||||||
// User-supplied completions may have changed the commandline - prevent buffer
|
// User-supplied completions may have changed the commandline - prevent buffer
|
||||||
@@ -6618,23 +6601,22 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
|||||||
token_range.end = std::cmp::min(token_range.end, el.text().len());
|
token_range.end = std::cmp::min(token_range.end, el.text().len());
|
||||||
|
|
||||||
// Munge our completions.
|
// Munge our completions.
|
||||||
sort_and_prioritize(
|
sort_and_prioritize(&mut comp, CompletionRequestOptions::default());
|
||||||
&mut self.rls_mut().comp,
|
|
||||||
CompletionRequestOptions::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let el = &self.command_line;
|
let el = &self.command_line;
|
||||||
// Record our cycle_command_line.
|
// Record our cycle_command_line.
|
||||||
self.cycle_command_line = el.text().to_owned();
|
self.cycle_command_line = el.text().to_owned();
|
||||||
self.cycle_cursor_pos = token_range.end;
|
self.cycle_cursor_pos = token_range.end;
|
||||||
|
|
||||||
self.rls_mut().complete_did_insert = self.handle_completions(token_range);
|
let inserted_unique = self.handle_completions(token_range, comp);
|
||||||
|
self.rls_mut().completion_action = if inserted_unique {
|
||||||
|
Some(CompletionAction::InsertedUnique)
|
||||||
|
} else {
|
||||||
|
(!self.pager.is_empty()).then_some(CompletionAction::ShownAmbiguous)
|
||||||
|
};
|
||||||
|
|
||||||
// Show the search field if requested and if we printed a list of completions.
|
// Show the search field if requested and if we printed a list of completions.
|
||||||
if c == ReadlineCmd::CompleteAndSearch
|
if c == ReadlineCmd::CompleteAndSearch && !inserted_unique && !self.pager.is_empty() {
|
||||||
&& !self.rls().complete_did_insert
|
|
||||||
&& !self.pager.is_empty()
|
|
||||||
{
|
|
||||||
self.pager.set_search_field_shown(true);
|
self.pager.set_search_field_shown(true);
|
||||||
self.select_completion_in_direction(SelectionMotion::Next, false);
|
self.select_completion_in_direction(SelectionMotion::Next, false);
|
||||||
}
|
}
|
||||||
@@ -6643,7 +6625,7 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
|||||||
fn try_insert(&mut self, c: Completion, tok: &wstr, token_range: Range<usize>) {
|
fn try_insert(&mut self, c: Completion, tok: &wstr, token_range: Range<usize>) {
|
||||||
// If this is a replacement completion, check that we know how to replace it, e.g. that
|
// If this is a replacement completion, check that we know how to replace it, e.g. that
|
||||||
// the token doesn't contain evil operators like {}.
|
// the token doesn't contain evil operators like {}.
|
||||||
if !c.flags.contains(CompleteFlags::REPLACES_TOKEN) || reader_can_replace(tok, c.flags) {
|
if !c.replaces_token() || reader_can_replace(tok, c.flags) {
|
||||||
self.completion_insert(
|
self.completion_insert(
|
||||||
&c.completion,
|
&c.completion,
|
||||||
token_range.end,
|
token_range.end,
|
||||||
@@ -6668,11 +6650,30 @@ fn try_insert(&mut self, c: Completion, tok: &wstr, token_range: Range<usize>) {
|
|||||||
/// \param token_end the position after the token to complete
|
/// \param token_end the position after the token to complete
|
||||||
///
|
///
|
||||||
/// Return true if we inserted text into the command line, false if we did not.
|
/// Return true if we inserted text into the command line, false if we did not.
|
||||||
fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
|
fn handle_completions(&mut self, token_range: Range<usize>, mut comp: Vec<Completion>) -> bool {
|
||||||
let tok = self.command_line.text()[token_range.clone()].to_owned();
|
let tok = self.command_line.text()[token_range.clone()].to_owned();
|
||||||
|
|
||||||
let comp = &self.rls().comp;
|
comp.retain({
|
||||||
// Check trivial cases.
|
let best_rank = comp.iter().map(|c| c.rank()).min().unwrap_or(u32::MAX);
|
||||||
|
move |c| {
|
||||||
|
// Ignore completions with a less suitable match rank than the best.
|
||||||
|
assert!(c.rank() >= best_rank);
|
||||||
|
c.rank() == best_rank
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine whether we are going to replace the token or not. If any commands of the best
|
||||||
|
// rank do not require replacement, then ignore all those that want to use replacement.
|
||||||
|
let will_replace_token = comp.iter().all(|c| c.replaces_token());
|
||||||
|
|
||||||
|
comp.retain(|c| !c.replaces_token() || reader_can_replace(&tok, c.flags));
|
||||||
|
|
||||||
|
for c in &mut comp {
|
||||||
|
if !will_replace_token && c.replaces_token() {
|
||||||
|
c.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let len = comp.len();
|
let len = comp.len();
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
// No suitable completions found, flash screen and return.
|
// No suitable completions found, flash screen and return.
|
||||||
@@ -6684,85 +6685,27 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
} else if len == 1 {
|
} else if len == 1 {
|
||||||
// Exactly one suitable completion found - insert it.
|
// Exactly one suitable completion found - insert it.
|
||||||
let c = &comp[0];
|
let c = std::mem::take(&mut comp[0]);
|
||||||
self.try_insert(c.clone(), &tok, token_range);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let best_rank = get_best_rank(comp);
|
|
||||||
|
|
||||||
// Determine whether we are going to replace the token or not. If any commands of the best
|
|
||||||
// rank do not require replacement, then ignore all those that want to use replacement.
|
|
||||||
let mut will_replace_token = true;
|
|
||||||
for c in comp {
|
|
||||||
if c.rank() <= best_rank && !c.flags.contains(CompleteFlags::REPLACES_TOKEN) {
|
|
||||||
will_replace_token = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decide which completions survived. There may be a lot of them; it would be nice if we could
|
|
||||||
// figure out how to avoid copying them here.
|
|
||||||
let mut surviving_completions = vec![];
|
|
||||||
let mut all_matches_exact_or_prefix = true;
|
|
||||||
for c in comp {
|
|
||||||
// Ignore completions with a less suitable match rank than the best.
|
|
||||||
if c.rank() > best_rank {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only use completions that match replace_token.
|
|
||||||
let completion_replaces_token = c.flags.contains(CompleteFlags::REPLACES_TOKEN);
|
|
||||||
let replaces_only_due_to_case_mismatch = {
|
|
||||||
c.flags.contains(CompleteFlags::REPLACES_TOKEN)
|
|
||||||
&& c.r#match.is_exact_or_prefix()
|
|
||||||
&& !matches!(c.r#match.case_fold, CaseSensitivity::Sensitive)
|
|
||||||
};
|
|
||||||
if completion_replaces_token != will_replace_token {
|
|
||||||
// Keep smart/samecase results even if we prefer not to replace the token.
|
|
||||||
if will_replace_token || !replaces_only_due_to_case_mismatch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't use completions that want to replace, if we cannot replace them.
|
|
||||||
if completion_replaces_token && !reader_can_replace(&tok, c.flags) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
all_matches_exact_or_prefix &= c.r#match.is_exact_or_prefix();
|
|
||||||
|
|
||||||
let mut completion = c.clone();
|
|
||||||
if replaces_only_due_to_case_mismatch && !will_replace_token {
|
|
||||||
completion.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
|
|
||||||
}
|
|
||||||
surviving_completions.push(completion);
|
|
||||||
}
|
|
||||||
|
|
||||||
if surviving_completions.len() == 1 {
|
|
||||||
// After sorting and stuff only one completion is left, use it.
|
|
||||||
//
|
|
||||||
// TODO: This happens when smartcase kicks in, e.g.
|
|
||||||
// the token is "cma" and the options are "cmake/" and "CMakeLists.txt"
|
|
||||||
// it would be nice if we could figure
|
|
||||||
// out how to use it more.
|
|
||||||
let c = std::mem::take(&mut surviving_completions[0]);
|
|
||||||
|
|
||||||
self.try_insert(c, &tok, token_range);
|
self.try_insert(c, &tok, token_range);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut use_prefix = false;
|
let mut use_prefix = false;
|
||||||
let mut common_prefix = L!("").to_owned();
|
let mut common_prefix = L!("");
|
||||||
|
let all_matches_exact_or_prefix = comp.iter().all(|c| c.r#match.is_exact_or_prefix());
|
||||||
|
assert!(will_replace_token || all_matches_exact_or_prefix);
|
||||||
if all_matches_exact_or_prefix {
|
if all_matches_exact_or_prefix {
|
||||||
// Try to find a common prefix to insert among the surviving completions.
|
// Try to find a common prefix to insert among the surviving completions.
|
||||||
let mut flags = CompleteFlags::empty();
|
let mut flags = CompleteFlags::empty();
|
||||||
let mut prefix_is_partial_completion = false;
|
let mut prefix_is_partial_completion = false;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for c in &surviving_completions {
|
for c in &comp {
|
||||||
|
if c.flags.contains(CompleteFlags::SUPPRESS_PAGER_PREFIX) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if first {
|
if first {
|
||||||
// First entry, use the whole string.
|
// First entry, use the whole string.
|
||||||
common_prefix = c.completion.clone();
|
common_prefix = &c.completion;
|
||||||
flags = c.flags;
|
flags = c.flags;
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -6777,7 +6720,7 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// idx is now the length of the new common prefix.
|
// idx is now the length of the new common prefix.
|
||||||
common_prefix.truncate(idx);
|
common_prefix = common_prefix.slice_to(idx);
|
||||||
prefix_is_partial_completion = true;
|
prefix_is_partial_completion = true;
|
||||||
|
|
||||||
// Early out if we decide there's no common prefix.
|
// Early out if we decide there's no common prefix.
|
||||||
@@ -6801,7 +6744,7 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
|
|||||||
flags |= CompleteFlags::NO_SPACE;
|
flags |= CompleteFlags::NO_SPACE;
|
||||||
}
|
}
|
||||||
self.completion_insert(
|
self.completion_insert(
|
||||||
&common_prefix,
|
common_prefix,
|
||||||
token_range.end,
|
token_range.end,
|
||||||
flags,
|
flags,
|
||||||
/*is_unique=*/ false,
|
/*is_unique=*/ false,
|
||||||
@@ -6811,48 +6754,53 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print the completion list.
|
||||||
|
let prefix = if will_replace_token && !use_prefix {
|
||||||
|
Cow::Borrowed(L!(""))
|
||||||
|
} else {
|
||||||
|
let mut prefix = WString::new();
|
||||||
|
let full = if will_replace_token {
|
||||||
|
common_prefix.to_owned()
|
||||||
|
} else {
|
||||||
|
tok + common_prefix
|
||||||
|
};
|
||||||
|
if full.len() <= PREFIX_MAX_LEN {
|
||||||
|
prefix = full;
|
||||||
|
} else {
|
||||||
|
// Collapse parent directories and append end of string
|
||||||
|
prefix.push(get_ellipsis_char());
|
||||||
|
|
||||||
|
let truncated = &full[full.len() - PREFIX_MAX_LEN..];
|
||||||
|
let (i, last_component) = truncated.split('/').enumerate().last().unwrap();
|
||||||
|
if i == 0 {
|
||||||
|
// No path separators were found in the common prefix, so we can't collapse
|
||||||
|
// any further
|
||||||
|
prefix.push_utfstr(&truncated);
|
||||||
|
} else {
|
||||||
|
// Discard any parent directories and include whats left
|
||||||
|
prefix.push('/');
|
||||||
|
prefix.push_utfstr(last_component);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Cow::Owned(prefix)
|
||||||
|
};
|
||||||
|
|
||||||
if use_prefix {
|
if use_prefix {
|
||||||
for c in &mut surviving_completions {
|
let common_prefix_len = common_prefix.len();
|
||||||
|
for c in &mut comp {
|
||||||
if c.flags.contains(CompleteFlags::SUPPRESS_PAGER_PREFIX) {
|
if c.flags.contains(CompleteFlags::SUPPRESS_PAGER_PREFIX) {
|
||||||
// Keep replacement semantics and the original prefix so these completions can
|
// Keep replacement semantics and the original prefix so these completions can
|
||||||
// fix casing when selected.
|
// fix casing when selected.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
c.flags &= !CompleteFlags::REPLACES_TOKEN;
|
c.flags &= !CompleteFlags::REPLACES_TOKEN;
|
||||||
c.completion.replace_range(0..common_prefix.len(), L!(""));
|
c.completion.replace_range(0..common_prefix_len, L!(""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the completion list.
|
|
||||||
let mut prefix = WString::new();
|
|
||||||
if will_replace_token || !all_matches_exact_or_prefix {
|
|
||||||
if use_prefix {
|
|
||||||
prefix.push_utfstr(&common_prefix);
|
|
||||||
}
|
|
||||||
} else if tok.len() + common_prefix.len() <= PREFIX_MAX_LEN {
|
|
||||||
prefix.push_utfstr(&tok);
|
|
||||||
prefix.push_utfstr(&common_prefix);
|
|
||||||
} else {
|
|
||||||
// Collapse parent directories and append end of string
|
|
||||||
prefix.push(get_ellipsis_char());
|
|
||||||
|
|
||||||
let full = tok + &common_prefix[..];
|
|
||||||
let truncated = &full[full.len() - PREFIX_MAX_LEN..];
|
|
||||||
let (i, last_component) = truncated.split('/').enumerate().last().unwrap();
|
|
||||||
if i == 0 {
|
|
||||||
// No path separators were found in the common prefix, so we can't collapse
|
|
||||||
// any further
|
|
||||||
prefix.push_utfstr(&truncated);
|
|
||||||
} else {
|
|
||||||
// Discard any parent directories and include whats left
|
|
||||||
prefix.push('/');
|
|
||||||
prefix.push_utfstr(last_component);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the pager data.
|
// Update the pager data.
|
||||||
self.pager.set_prefix(&prefix, true);
|
self.pager.set_prefix(prefix, true);
|
||||||
self.pager.set_completions(&surviving_completions, true);
|
self.pager.set_completions(&comp, true);
|
||||||
// Modify the command line to reflect the new pager.
|
// Modify the command line to reflect the new pager.
|
||||||
self.pager_selection_changed();
|
self.pager_selection_changed();
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
use libc::{ONLCR, STDERR_FILENO, STDOUT_FILENO};
|
use libc::{ONLCR, STDERR_FILENO, STDOUT_FILENO};
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
get_ellipsis_char, get_omitted_newline_str, get_omitted_newline_width,
|
get_ellipsis_char, get_omitted_newline_str, has_working_tty_timestamps, shell_modes, wcs2bytes,
|
||||||
has_working_tty_timestamps, shell_modes, wcs2bytes, write_loop,
|
write_loop,
|
||||||
};
|
};
|
||||||
use crate::env::Environment;
|
use crate::env::Environment;
|
||||||
use crate::flog::{flog, flogf};
|
use crate::flog::{flog, flogf};
|
||||||
@@ -687,10 +687,12 @@ fn abandon_line_string(screen_width: Option<usize>) -> Vec<u8> {
|
|||||||
|
|
||||||
let mut abandon_line_string = Vec::with_capacity(screen_width + 32);
|
let mut abandon_line_string = Vec::with_capacity(screen_width + 32);
|
||||||
|
|
||||||
|
let omitted_newline_str = get_omitted_newline_str();
|
||||||
|
|
||||||
// Do the PROMPT_SP hack.
|
// Do the PROMPT_SP hack.
|
||||||
// Don't need to check for fish_wcwidth errors; this is done when setting up
|
// Don't need to check for fish_wcwidth errors; this is done when setting up
|
||||||
// omitted_newline_char in common.rs.
|
// omitted_newline_char in common.rs.
|
||||||
let non_space_width = get_omitted_newline_width();
|
let non_space_width = omitted_newline_str.chars().count();
|
||||||
// We do `>` rather than `>=` because the code below might require one extra space.
|
// We do `>` rather than `>=` because the code below might require one extra space.
|
||||||
if screen_width > non_space_width {
|
if screen_width > non_space_width {
|
||||||
if use_terminfo() {
|
if use_terminfo() {
|
||||||
@@ -732,13 +734,13 @@ fn abandon_line_string(screen_width: Option<usize>) -> Vec<u8> {
|
|||||||
abandon_line_string.write_command(EnterDimMode);
|
abandon_line_string.write_command(EnterDimMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
abandon_line_string.extend_from_slice(get_omitted_newline_str().as_bytes());
|
abandon_line_string.extend_from_slice(omitted_newline_str.as_bytes());
|
||||||
abandon_line_string.write_command(ExitAttributeMode);
|
abandon_line_string.write_command(ExitAttributeMode);
|
||||||
abandon_line_string.extend(repeat_n(b' ', screen_width - non_space_width));
|
abandon_line_string.extend(repeat_n(b' ', screen_width - non_space_width));
|
||||||
}
|
}
|
||||||
|
|
||||||
abandon_line_string.push(b'\r');
|
abandon_line_string.push(b'\r');
|
||||||
abandon_line_string.extend_from_slice(get_omitted_newline_str().as_bytes());
|
abandon_line_string.extend_from_slice(omitted_newline_str.as_bytes());
|
||||||
// Now we are certainly on a new line. But we may have dropped the omitted newline char on
|
// Now we are certainly on a new line. But we may have dropped the omitted newline char on
|
||||||
// it. So append enough spaces to overwrite the omitted newline char, and then clear all the
|
// it. So append enough spaces to overwrite the omitted newline char, and then clear all the
|
||||||
// spaces from the new line.
|
// spaces from the new line.
|
||||||
@@ -1978,7 +1980,7 @@ fn compute_layout(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let left_prompt_width = left_prompt_layout.last_line_width;
|
let left_prompt_width = left_prompt_layout.last_line_width;
|
||||||
let mut right_prompt_width = right_prompt_layout.last_line_width;
|
let right_prompt_width = right_prompt_layout.last_line_width;
|
||||||
|
|
||||||
// Get the width of the first line, and if there is more than one line.
|
// Get the width of the first line, and if there is more than one line.
|
||||||
let first_command_line_width: usize = line_at_cursor(commandline_before_suggestion, 0)
|
let first_command_line_width: usize = line_at_cursor(commandline_before_suggestion, 0)
|
||||||
@@ -2014,24 +2016,14 @@ fn compute_layout(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hide the right prompt if it doesn't fit on the first line.
|
|
||||||
if left_prompt_width + first_command_line_width + right_prompt_width < screen_width {
|
|
||||||
result.right_prompt = right_prompt;
|
|
||||||
} else {
|
|
||||||
right_prompt_width = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we should definitely fit.
|
|
||||||
assert!(left_prompt_width + right_prompt_width <= screen_width);
|
|
||||||
|
|
||||||
// Track each logical line from the autosuggestion so we can determine how much of it fits
|
// Track each logical line from the autosuggestion so we can determine how much of it fits
|
||||||
// on screen. We allow the lines to soft wrap naturally and we only truncate vertically if
|
// on screen. We allow the lines to soft wrap naturally and we only truncate vertically if
|
||||||
// we would exceed the screen height.
|
// we would exceed the screen height.
|
||||||
let cursor_y = left_prompt_layout.line_starts.len() - 1
|
let commandline_before_suggestion_lines = commandline_before_suggestion
|
||||||
+ commandline_before_suggestion
|
.chars()
|
||||||
.chars()
|
.filter(|&c| c == '\n')
|
||||||
.filter(|&c| c == '\n')
|
.count();
|
||||||
.count();
|
let cursor_y = left_prompt_layout.line_starts.len() - 1 + commandline_before_suggestion_lines;
|
||||||
|
|
||||||
let mut suggestion_lines = vec![];
|
let mut suggestion_lines = vec![];
|
||||||
|
|
||||||
@@ -2060,7 +2052,8 @@ fn compute_layout(
|
|||||||
+ commandline_before_suggestion
|
+ commandline_before_suggestion
|
||||||
.chars()
|
.chars()
|
||||||
.rposition(|c| c == '\n')
|
.rposition(|c| c == '\n')
|
||||||
.map_or(right_prompt_width, indent_width)
|
.map(indent_width)
|
||||||
|
.unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
indent_width(suggestion_start - "\n".len())
|
indent_width(suggestion_start - "\n".len())
|
||||||
};
|
};
|
||||||
@@ -2120,6 +2113,24 @@ fn consumed_lines_or_truncated_suggestion(
|
|||||||
|
|
||||||
let mut autosuggestion = WString::new();
|
let mut autosuggestion = WString::new();
|
||||||
let mut displayed_len = 0;
|
let mut displayed_len = 0;
|
||||||
|
{
|
||||||
|
// Hide the right prompt if it doesn't fit on the first line.
|
||||||
|
let first_command_line_suggestion_width = if commandline_before_suggestion_lines == 0 {
|
||||||
|
suggestion_lines.first().map_or(0, |line| {
|
||||||
|
line.chars().map(wcwidth_rendered_min_0).sum::<usize>()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
if left_prompt_width
|
||||||
|
+ first_command_line_width
|
||||||
|
+ first_command_line_suggestion_width
|
||||||
|
+ right_prompt_width
|
||||||
|
<= screen_width
|
||||||
|
{
|
||||||
|
result.right_prompt = right_prompt;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (line_idx, autosuggestion_line) in suggestion_lines.iter().enumerate() {
|
for (line_idx, autosuggestion_line) in suggestion_lines.iter().enumerate() {
|
||||||
if line_idx != 0 {
|
if line_idx != 0 {
|
||||||
autosuggestion.push('\n');
|
autosuggestion.push('\n');
|
||||||
@@ -2419,20 +2430,19 @@ fn test_prompt_truncation() {
|
|||||||
fn test_compute_layout() {
|
fn test_compute_layout() {
|
||||||
macro_rules! validate {
|
macro_rules! validate {
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
$screen_width:expr,
|
$screen_width:expr,
|
||||||
$left_untrunc_prompt:literal,
|
$left_untrunc_prompt:literal,
|
||||||
$right_untrunc_prompt:literal,
|
$right_untrunc_prompt:literal,
|
||||||
$commandline_before_suggestion:literal,
|
$commandline_before_suggestion:literal,
|
||||||
$autosuggestion_str:literal,
|
$autosuggestion_str:literal,
|
||||||
$commandline_after_suggestion:literal
|
$commandline_after_suggestion:literal
|
||||||
)
|
) -> (
|
||||||
-> (
|
$left_prompt:literal,
|
||||||
$left_prompt:literal,
|
$left_prompt_space:expr,
|
||||||
$left_prompt_space:expr,
|
$right_prompt:literal,
|
||||||
$right_prompt:literal,
|
$autosuggestion:literal $(,)?
|
||||||
$autosuggestion:literal $(,)?
|
)
|
||||||
)
|
|
||||||
) => {{
|
) => {{
|
||||||
let full_commandline = L!($commandline_before_suggestion).to_owned()
|
let full_commandline = L!($commandline_before_suggestion).to_owned()
|
||||||
+ L!($autosuggestion_str)
|
+ L!($autosuggestion_str)
|
||||||
@@ -2480,7 +2490,7 @@ macro_rules! validate {
|
|||||||
) -> (
|
) -> (
|
||||||
"left>",
|
"left>",
|
||||||
5,
|
5,
|
||||||
"<right",
|
"",
|
||||||
" autosuggesTION",
|
" autosuggesTION",
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -2602,7 +2612,7 @@ macro_rules! validate {
|
|||||||
) -> (
|
) -> (
|
||||||
"left>",
|
"left>",
|
||||||
5,
|
5,
|
||||||
"",
|
"<RIGHT",
|
||||||
"and AUTOSUGGESTION",
|
"and AUTOSUGGESTION",
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -2612,7 +2622,7 @@ macro_rules! validate {
|
|||||||
) -> (
|
) -> (
|
||||||
"left>",
|
"left>",
|
||||||
5,
|
5,
|
||||||
"",
|
"<RIGHT",
|
||||||
"AUTOSUGGESTION",
|
"AUTOSUGGESTION",
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -2622,7 +2632,7 @@ macro_rules! validate {
|
|||||||
) -> (
|
) -> (
|
||||||
"left>",
|
"left>",
|
||||||
5,
|
5,
|
||||||
"",
|
"<RIGHT",
|
||||||
"utosuggestion sofT WRAP",
|
"utosuggestion sofT WRAP",
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,8 +11,10 @@
|
|||||||
use crate::tty_handoff::{safe_deactivate_tty_protocols, safe_mark_tty_invalid};
|
use crate::tty_handoff::{safe_deactivate_tty_protocols, safe_mark_tty_invalid};
|
||||||
use crate::wutil::{fish_wcstoi, perror};
|
use crate::wutil::{fish_wcstoi, perror};
|
||||||
use errno::{errno, set_errno};
|
use errno::{errno, set_errno};
|
||||||
use once_cell::sync::Lazy;
|
use std::sync::{
|
||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
LazyLock,
|
||||||
|
atomic::{AtomicI32, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
/// Store the "main" pid. This allows us to reliably determine if we are in a forked child.
|
/// Store the "main" pid. This allows us to reliably determine if we are in a forked child.
|
||||||
static MAIN_PID: AtomicI32 = AtomicI32::new(0);
|
static MAIN_PID: AtomicI32 = AtomicI32::new(0);
|
||||||
@@ -278,7 +280,7 @@ pub fn signal_handle(sig: Signal) {
|
|||||||
sigaction(sig, &act, std::ptr::null_mut());
|
sigaction(sig, &act, std::ptr::null_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static signals_to_default: Lazy<libc::sigset_t> = Lazy::new(|| {
|
pub static signals_to_default: LazyLock<libc::sigset_t> = LazyLock::new(|| {
|
||||||
let mut set = MaybeUninit::uninit();
|
let mut set = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigemptyset(set.as_mut_ptr()) };
|
unsafe { libc::sigemptyset(set.as_mut_ptr()) };
|
||||||
for data in SIGNAL_TABLE.iter() {
|
for data in SIGNAL_TABLE.iter() {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
use crate::text_face::{TextFace, TextStyling, UnderlineStyle};
|
use crate::text_face::{TextFace, TextStyling, UnderlineStyle};
|
||||||
use crate::threads::MainThread;
|
use crate::threads::MainThread;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
@@ -17,9 +16,8 @@
|
|||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
@@ -397,7 +395,7 @@ fn osc_133_prompt_start(out: &mut impl Output) -> bool {
|
|||||||
if !future_feature_flags::test(FeatureFlag::MarkPrompt) {
|
if !future_feature_flags::test(FeatureFlag::MarkPrompt) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
static TEST_BALLOON: OnceCell<()> = OnceCell::new();
|
static TEST_BALLOON: OnceLock<()> = OnceLock::new();
|
||||||
if TEST_BALLOON.set(()).is_ok() {
|
if TEST_BALLOON.set(()).is_ok() {
|
||||||
write_to_output!(out, "\x1b]133;A;click_events=1\x1b\\");
|
write_to_output!(out, "\x1b]133;A;click_events=1\x1b\\");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,16 +9,16 @@
|
|||||||
use crate::topic_monitor::topic_monitor_init;
|
use crate::topic_monitor::topic_monitor_init;
|
||||||
use crate::wutil::wgetcwd;
|
use crate::wutil::wgetcwd;
|
||||||
use crate::{env::EnvStack, proc::proc_init};
|
use crate::{env::EnvStack, proc::proc_init};
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env::set_current_dir;
|
use std::env::set_current_dir;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
pub use serial_test::serial;
|
pub use serial_test::serial;
|
||||||
|
|
||||||
pub fn test_init() -> impl ScopeGuarding<Target = ()> {
|
pub fn test_init() -> impl ScopeGuarding<Target = ()> {
|
||||||
static DONE: OnceCell<()> = OnceCell::new();
|
static DONE: OnceLock<()> = OnceLock::new();
|
||||||
DONE.get_or_init(|| {
|
DONE.get_or_init(|| {
|
||||||
// If we are building with `cargo build` and have build w/ `cmake`, this might not
|
// If we are building with `cargo build` and have build w/ `cmake`, this might not
|
||||||
// yet exist.
|
// yet exist.
|
||||||
|
|||||||
@@ -20,19 +20,21 @@
|
|||||||
use crate::wutil::{perror, wcstoi};
|
use crate::wutil::{perror, wcstoi};
|
||||||
use fish_wchar::ToWString;
|
use fish_wchar::ToWString;
|
||||||
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
|
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
use std::sync::{
|
||||||
|
OnceLock,
|
||||||
|
atomic::{AtomicBool, AtomicPtr, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
/// Whether kitty keyboard protocol support is present in the TTY.
|
/// Whether kitty keyboard protocol support is present in the TTY.
|
||||||
static KITTY_KEYBOARD_SUPPORTED: OnceCell<bool> = OnceCell::new();
|
static KITTY_KEYBOARD_SUPPORTED: OnceLock<bool> = OnceLock::new();
|
||||||
|
|
||||||
/// Set that the TTY supports the kitty keyboard protocol.
|
/// Set that the TTY supports the kitty keyboard protocol.
|
||||||
pub fn maybe_set_kitty_keyboard_capability() {
|
pub fn maybe_set_kitty_keyboard_capability() {
|
||||||
KITTY_KEYBOARD_SUPPORTED.get_or_init(|| true);
|
KITTY_KEYBOARD_SUPPORTED.get_or_init(|| true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) static SCROLL_CONTENT_UP_SUPPORTED: OnceCell<bool> = OnceCell::new();
|
pub(crate) static SCROLL_CONTENT_UP_SUPPORTED: OnceLock<bool> = OnceLock::new();
|
||||||
pub(crate) const SCROLL_CONTENT_UP_TERMINFO_CODE: &str = "indn";
|
pub(crate) const SCROLL_CONTENT_UP_TERMINFO_CODE: &str = "indn";
|
||||||
|
|
||||||
// Get the support capability for kitty keyboard protocol.
|
// Get the support capability for kitty keyboard protocol.
|
||||||
@@ -47,10 +49,10 @@ pub fn maybe_set_scroll_content_up_capability() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static TERMINAL_OS_NAME: OnceCell<Option<WString>> = OnceCell::new();
|
pub static TERMINAL_OS_NAME: OnceLock<Option<WString>> = OnceLock::new();
|
||||||
pub(crate) const XTGETTCAP_QUERY_OS_NAME: &str = "query-os-name";
|
pub(crate) const XTGETTCAP_QUERY_OS_NAME: &str = "query-os-name";
|
||||||
|
|
||||||
pub static XTVERSION: OnceCell<WString> = OnceCell::new();
|
pub static XTVERSION: OnceLock<WString> = OnceLock::new();
|
||||||
|
|
||||||
pub fn xtversion() -> Option<&'static wstr> {
|
pub fn xtversion() -> Option<&'static wstr> {
|
||||||
XTVERSION.get().as_ref().map(|s| s.as_utfstr())
|
XTVERSION.get().as_ref().map(|s| s.as_utfstr())
|
||||||
@@ -65,7 +67,7 @@ pub enum TtyQuirks {
|
|||||||
// Running in iTerm2 before 3.5.12, which causes issues when using the kitty keyboard protocol.
|
// Running in iTerm2 before 3.5.12, which causes issues when using the kitty keyboard protocol.
|
||||||
PreKittyIterm2,
|
PreKittyIterm2,
|
||||||
// Whether we are running under tmux.
|
// Whether we are running under tmux.
|
||||||
Tmux,
|
Tmux((u32, u32)),
|
||||||
// Whether we are running under WezTerm.
|
// Whether we are running under WezTerm.
|
||||||
Wezterm,
|
Wezterm,
|
||||||
}
|
}
|
||||||
@@ -80,8 +82,8 @@ fn detect(vars: &dyn Environment, xtversion: &wstr) -> Self {
|
|||||||
PreCsiMidnightCommander
|
PreCsiMidnightCommander
|
||||||
} else if get_iterm2_version(xtversion).is_some_and(|v| v < (3, 5, 12)) {
|
} else if get_iterm2_version(xtversion).is_some_and(|v| v < (3, 5, 12)) {
|
||||||
PreKittyIterm2
|
PreKittyIterm2
|
||||||
} else if xtversion.starts_with(L!("tmux ")) {
|
} else if let Some(version) = get_tmux_version(xtversion) {
|
||||||
Tmux
|
Tmux(version)
|
||||||
} else if xtversion.starts_with(L!("WezTerm ")) {
|
} else if xtversion.starts_with(L!("WezTerm ")) {
|
||||||
Wezterm
|
Wezterm
|
||||||
} else {
|
} else {
|
||||||
@@ -180,12 +182,18 @@ fn get_protocols(self) -> TtyProtocolsSet {
|
|||||||
let mut off_chain = vec![];
|
let mut off_chain = vec![];
|
||||||
|
|
||||||
// Enable focus reporting under tmux
|
// Enable focus reporting under tmux
|
||||||
if self == TtyQuirks::Tmux {
|
if matches!(self, TtyQuirks::Tmux(_)) {
|
||||||
on_chain.push(DecsetFocusReporting);
|
on_chain.push(DecsetFocusReporting);
|
||||||
off_chain.push(DecrstFocusReporting);
|
off_chain.push(DecrstFocusReporting);
|
||||||
}
|
}
|
||||||
on_chain.extend_from_slice(&[DecsetBracketedPaste, DecsetColorThemeReporting]);
|
on_chain.push(DecsetBracketedPaste);
|
||||||
off_chain.extend_from_slice(&[DecrstBracketedPaste, DecrstColorThemeReporting]);
|
off_chain.push(DecrstBracketedPaste);
|
||||||
|
if !matches!(
|
||||||
|
self, TtyQuirks::Tmux(version) if version < (3, 7)
|
||||||
|
) {
|
||||||
|
on_chain.push(DecsetColorThemeReporting);
|
||||||
|
off_chain.push(DecrstColorThemeReporting);
|
||||||
|
}
|
||||||
|
|
||||||
let on_chain = || on_chain.clone().into_iter();
|
let on_chain = || on_chain.clone().into_iter();
|
||||||
let off_chain = || off_chain.clone().into_iter();
|
let off_chain = || off_chain.clone().into_iter();
|
||||||
@@ -349,14 +357,12 @@ pub struct TtyHandoff {
|
|||||||
// The job group which owns the tty, or empty if none.
|
// The job group which owns the tty, or empty if none.
|
||||||
owner: Option<JobGroupRef>,
|
owner: Option<JobGroupRef>,
|
||||||
// Whether terminal protocols were initially enabled.
|
// Whether terminal protocols were initially enabled.
|
||||||
// reclaim() restores the state to this.
|
// Restored on drop.
|
||||||
tty_protocols_initial: bool,
|
tty_protocols_initial: bool,
|
||||||
// The state of terminal protocols that we set.
|
// The state of terminal protocols that we set.
|
||||||
// Note we track this separately from TTY_PROTOCOLS_ACTIVE. We undo the changes
|
// Note we track this separately from TTY_PROTOCOLS_ACTIVE. We undo the changes
|
||||||
// we make.
|
// we make.
|
||||||
tty_protocols_applied: bool,
|
tty_protocols_applied: bool,
|
||||||
// Whether reclaim was called, restoring the tty to its pre-scoped value.
|
|
||||||
reclaimed: bool,
|
|
||||||
// Called after writing to the TTY.
|
// Called after writing to the TTY.
|
||||||
on_write: fn(),
|
on_write: fn(),
|
||||||
}
|
}
|
||||||
@@ -368,7 +374,6 @@ pub fn new(on_write: fn()) -> Self {
|
|||||||
owner: None,
|
owner: None,
|
||||||
tty_protocols_initial: protocols_active,
|
tty_protocols_initial: protocols_active,
|
||||||
tty_protocols_applied: protocols_active,
|
tty_protocols_applied: protocols_active,
|
||||||
reclaimed: false,
|
|
||||||
on_write,
|
on_write,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,40 +405,6 @@ pub fn to_job_group(&mut self, jg: &JobGroupRef) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reclaim the tty if we transferred it.
|
|
||||||
pub fn reclaim(mut self) {
|
|
||||||
self.reclaim_impl()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Release the tty, meaning no longer restore anything in Drop - similar to `mem::forget`.
|
|
||||||
pub fn release(mut self) {
|
|
||||||
self.reclaimed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of reclaim, factored out for use in Drop.
|
|
||||||
fn reclaim_impl(&mut self) {
|
|
||||||
assert!(!self.reclaimed, "Terminal already reclaimed");
|
|
||||||
self.reclaimed = true;
|
|
||||||
if self.owner.is_some() {
|
|
||||||
flog!(proc_pgroup, "fish reclaiming terminal");
|
|
||||||
if unsafe { libc::tcsetpgrp(STDIN_FILENO, libc::getpgrp()) } == -1 {
|
|
||||||
flog!(
|
|
||||||
warning,
|
|
||||||
"Could not return shell to foreground:",
|
|
||||||
errno::errno()
|
|
||||||
);
|
|
||||||
perror("tcsetpgrp");
|
|
||||||
}
|
|
||||||
self.owner = None;
|
|
||||||
}
|
|
||||||
// Restore the terminal protocols. Note this does nothing if they were unchanged.
|
|
||||||
if self.tty_protocols_initial {
|
|
||||||
self.enable_tty_protocols();
|
|
||||||
} else {
|
|
||||||
self.disable_tty_protocols();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save the current tty modes into the owning job group, if we are transferred.
|
/// Save the current tty modes into the owning job group, if we are transferred.
|
||||||
pub fn save_tty_modes(&mut self) {
|
pub fn save_tty_modes(&mut self) {
|
||||||
if let Some(ref mut owner) = self.owner {
|
if let Some(ref mut owner) = self.owner {
|
||||||
@@ -591,11 +562,25 @@ fn try_transfer(jg: &JobGroup) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The destructor will assert if reclaim() has not been called.
|
|
||||||
impl Drop for TtyHandoff {
|
impl Drop for TtyHandoff {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !self.reclaimed {
|
if self.owner.is_some() {
|
||||||
self.reclaim_impl();
|
flog!(proc_pgroup, "fish reclaiming terminal");
|
||||||
|
if unsafe { libc::tcsetpgrp(STDIN_FILENO, libc::getpgrp()) } == -1 {
|
||||||
|
flog!(
|
||||||
|
warning,
|
||||||
|
"Could not return shell to foreground:",
|
||||||
|
errno::errno()
|
||||||
|
);
|
||||||
|
perror("tcsetpgrp");
|
||||||
|
}
|
||||||
|
self.owner = None;
|
||||||
|
}
|
||||||
|
// Restore the terminal protocols. Note this does nothing if they were unchanged.
|
||||||
|
if self.tty_protocols_initial {
|
||||||
|
self.enable_tty_protocols();
|
||||||
|
} else {
|
||||||
|
self.disable_tty_protocols();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,15 +589,24 @@ fn drop(&mut self) {
|
|||||||
fn get_iterm2_version(xtversion: &wstr) -> Option<(u32, u32, u32)> {
|
fn get_iterm2_version(xtversion: &wstr) -> Option<(u32, u32, u32)> {
|
||||||
// TODO split_once
|
// TODO split_once
|
||||||
let mut xtversion = xtversion.split(' ');
|
let mut xtversion = xtversion.split(' ');
|
||||||
let name = xtversion.next().unwrap();
|
if xtversion.next().unwrap() != "iTerm2" {
|
||||||
let version = xtversion.next()?;
|
|
||||||
if name != "iTerm2" {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut parts = version.split('.');
|
let mut version = xtversion.next()?.split('.');
|
||||||
Some((
|
Some((
|
||||||
wcstoi(parts.next()?).ok()?,
|
wcstoi(version.next()?).ok()?,
|
||||||
wcstoi(parts.next()?).ok()?,
|
wcstoi(version.next()?).ok()?,
|
||||||
wcstoi(parts.next()?).ok()?,
|
wcstoi(version.next()?).ok()?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are running under iTerm2, get the version as a tuple of (major, minor, patch).
|
||||||
|
fn get_tmux_version(xtversion: &wstr) -> Option<(u32, u32)> {
|
||||||
|
// TODO split_once
|
||||||
|
let mut xtversion = xtversion.split(' ');
|
||||||
|
if xtversion.next().unwrap() != "tmux" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut version = xtversion.next()?.split('.');
|
||||||
|
Some((wcstoi(version.next()?).ok()?, wcstoi(version.next()?).ok()?))
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user