mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-24 22:21:15 -03:00
Compare commits
205 Commits
update-def
...
faster-git
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e987df15 | ||
|
|
a6ee9be72a | ||
|
|
ad28e17b50 | ||
|
|
a7d9f2293e | ||
|
|
df92c93332 | ||
|
|
aebae23533 | ||
|
|
69b5ad110a | ||
|
|
17446ad20f | ||
|
|
8c5de9acfb | ||
|
|
7a79728df3 | ||
|
|
4cbd1b83f1 | ||
|
|
3ab6fcf21c | ||
|
|
08c8afcb12 | ||
|
|
3951a858dd | ||
|
|
c7a19a00ab | ||
|
|
e5fdd77b09 | ||
|
|
3fcdbe1a19 | ||
|
|
2071df126c | ||
|
|
4b5650ee4f | ||
|
|
5657f093e7 | ||
|
|
a7c04890c9 | ||
|
|
52f23b9752 | ||
|
|
6737872fb7 | ||
|
|
f88f7e8dd6 | ||
|
|
b3dbdb90c2 | ||
|
|
dc129add9e | ||
|
|
19c3bebdd9 | ||
|
|
093b468ac1 | ||
|
|
88bbf5f3ac | ||
|
|
ec8fa7485c | ||
|
|
c2e2237e7c | ||
|
|
98df97d317 | ||
|
|
7ca57894cc | ||
|
|
c7391d1026 | ||
|
|
1963b0830d | ||
|
|
74ce965f32 | ||
|
|
27420aaf8b | ||
|
|
52cdb7fd62 | ||
|
|
18c4debbc0 | ||
|
|
8617964d4d | ||
|
|
0d99859add | ||
|
|
0b8e0b8835 | ||
|
|
3867163193 | ||
|
|
4d84e68dd4 | ||
|
|
285a810814 | ||
|
|
541a069a91 | ||
|
|
e8864ef441 | ||
|
|
2f708a7c0b | ||
|
|
b7a73710e2 | ||
|
|
7a54ed66fb | ||
|
|
99f4c09ed3 | ||
|
|
e26b585ce5 | ||
|
|
7c2c7f5874 | ||
|
|
7b3a2900e9 | ||
|
|
7a79366f91 | ||
|
|
223b98f2ff | ||
|
|
01560bf195 | ||
|
|
7fe34ea401 | ||
|
|
36f035b52c | ||
|
|
0e8edab872 | ||
|
|
448d630d0c | ||
|
|
38fb2cfd6d | ||
|
|
d68f8bdd3b | ||
|
|
80bafd5a22 | ||
|
|
ae8c5eaab7 | ||
|
|
329d190fbf | ||
|
|
e5fa047412 | ||
|
|
cb31887941 | ||
|
|
04fd697ac9 | ||
|
|
2558d13361 | ||
|
|
02ccf25443 | ||
|
|
a88acb9715 | ||
|
|
8d3ad0c3c3 | ||
|
|
660f52ee4f | ||
|
|
594b8730d8 | ||
|
|
107e4d11de | ||
|
|
50500ec5b9 | ||
|
|
3ccce609f0 | ||
|
|
bd26d4b61b | ||
|
|
7f4998ad9b | ||
|
|
122f39de66 | ||
|
|
1df8fbff67 | ||
|
|
ff5ff50183 | ||
|
|
c0d93e4740 | ||
|
|
55752729d6 | ||
|
|
41dfb5147f | ||
|
|
156fa8081c | ||
|
|
3081d0157b | ||
|
|
13e4736113 | ||
|
|
80e30ac756 | ||
|
|
a86a4dfabf | ||
|
|
22bc8e12c9 | ||
|
|
6c23c6f29b | ||
|
|
39fd959eea | ||
|
|
cc2ca60baa | ||
|
|
83f74f9332 | ||
|
|
58af4fa34c | ||
|
|
f23501dbdc | ||
|
|
035cd369c2 | ||
|
|
3bef4863cf | ||
|
|
2d58cfe4cb | ||
|
|
df591a2e0f | ||
|
|
ecefce2ea8 | ||
|
|
786239d280 | ||
|
|
7a668fb17e | ||
|
|
bf2f7ee6c0 | ||
|
|
2f762e2da1 | ||
|
|
11d8b83838 | ||
|
|
c2eaef7273 | ||
|
|
2f278f4bfa | ||
|
|
1e61e6492d | ||
|
|
c993fd022c | ||
|
|
fe10f65587 | ||
|
|
b98c5ee897 | ||
|
|
9ccff5ad5d | ||
|
|
55f70cbb6d | ||
|
|
b7005e8378 | ||
|
|
1f79d48a48 | ||
|
|
e9036774cb | ||
|
|
2cd185a4f1 | ||
|
|
bb92d82c3b | ||
|
|
f9ba834788 | ||
|
|
d23b8af60d | ||
|
|
82eacb6d50 | ||
|
|
c62b09d5d1 | ||
|
|
ccfe949514 | ||
|
|
ccc75d08f3 | ||
|
|
dfac66082a | ||
|
|
9ae01ae00d | ||
|
|
51784b090d | ||
|
|
8115982485 | ||
|
|
d88a656e9e | ||
|
|
d6ee4ec698 | ||
|
|
dbae271fe7 | ||
|
|
a4ec30f298 | ||
|
|
ee9cf33689 | ||
|
|
11b6bf31c0 | ||
|
|
4f0e11383e | ||
|
|
bf78309f79 | ||
|
|
01bd854f25 | ||
|
|
0348389195 | ||
|
|
e05ecd6c7d | ||
|
|
325232bec1 | ||
|
|
b78d168050 | ||
|
|
31edcf029b | ||
|
|
27dc4b3c8a | ||
|
|
77a4f38a13 | ||
|
|
e9d396615b | ||
|
|
79ec558d08 | ||
|
|
719a5d2909 | ||
|
|
93962c82df | ||
|
|
111922b60f | ||
|
|
cb92a5530f | ||
|
|
dd4c04e2ff | ||
|
|
6fec5ab320 | ||
|
|
ada9aff63e | ||
|
|
1d63c1f188 | ||
|
|
a12298152f | ||
|
|
83b10c3919 | ||
|
|
4a3fc5211f | ||
|
|
9f80e1f225 | ||
|
|
9a8d578142 | ||
|
|
09eae92888 | ||
|
|
af6c3eb69f | ||
|
|
dd5864ce13 | ||
|
|
d31dc9ffd8 | ||
|
|
d5e80d43d9 | ||
|
|
0d59e89374 | ||
|
|
8b1f72c54b | ||
|
|
54a5ade57d | ||
|
|
7c25d6a1ba | ||
|
|
a5a5dc46e4 | ||
|
|
1687b3fe7a | ||
|
|
f3ddf793a3 | ||
|
|
647ae7da8c | ||
|
|
0950cd1598 | ||
|
|
a1b1bff97b | ||
|
|
a95be351fb | ||
|
|
91b9bbf651 | ||
|
|
c14e8c1939 | ||
|
|
7e4c3b9fa7 | ||
|
|
8048e38ea4 | ||
|
|
8a5a547d88 | ||
|
|
48704dc612 | ||
|
|
8abab0e2cc | ||
|
|
bd178c8ba8 | ||
|
|
cb719cd418 | ||
|
|
1ff8f983c4 | ||
|
|
e3517f69b3 | ||
|
|
f7bde1354d | ||
|
|
1d69226c58 | ||
|
|
d5e71bc46e | ||
|
|
0d5ab2514c | ||
|
|
bf0a30b9a8 | ||
|
|
b54042e512 | ||
|
|
b7b1753716 | ||
|
|
c0f5fcb089 | ||
|
|
b9f2275349 | ||
|
|
0b97fa7114 | ||
|
|
1a2958d42b | ||
|
|
3e8308f6eb | ||
|
|
ff987f5f76 | ||
|
|
a6fdb41940 | ||
|
|
54971621de | ||
|
|
b6c5f3dc38 |
20
.github/workflows/main.yml
vendored
20
.github/workflows/main.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@1.70
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt install gettext libpcre2-dev python3-pexpect tmux
|
||||
sudo apt install gettext libpcre2-dev python3-pexpect python3-sphinx tmux
|
||||
# Generate a locale that uses a comma as decimal separator.
|
||||
sudo locale-gen fr_FR.UTF-8
|
||||
- name: cmake
|
||||
@@ -32,6 +32,17 @@ jobs:
|
||||
- name: make fish_run_tests
|
||||
run: |
|
||||
make -C build VERBOSE=1 fish_run_tests
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: translation updates
|
||||
run: |
|
||||
# Required for our custom xgettext implementation.
|
||||
cargo install --locked --version 1.0.106 cargo-expand
|
||||
# Generate PO files. This should not result it a change in the repo if all translations are
|
||||
# up to date.
|
||||
# Ensure that fish is available as an executable.
|
||||
PATH="$PWD/build:$PATH" build_tools/update_translations.fish --no-mo
|
||||
# Show diff output. Fail if there is any.
|
||||
git --no-pager diff --exit-code || { echo 'There are uncommitted changes after regenerating the gettext PO files. Make sure to update them via `build_tools/update_translations.fish --no-mo` after changing source files.'; exit 1; }
|
||||
|
||||
ubuntu-32bit-static-pcre2:
|
||||
|
||||
@@ -82,13 +93,14 @@ jobs:
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt install gettext libpcre2-dev python3-pexpect tmux
|
||||
sudo apt install llvm # for llvm-symbolizer
|
||||
- name: cmake
|
||||
env:
|
||||
CC: clang
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
# Rust's ASAN requires the build system to explicitly pass a --target triple. We read that
|
||||
# value from CMake variable Rust_CARGO_TARGET (shared with corrosion).
|
||||
# value from CMake variable Rust_CARGO_TARGET.
|
||||
cmake .. -DASAN=1 -DRust_CARGO_TARGET=x86_64-unknown-linux-gnu -DCMAKE_BUILD_TYPE=Debug
|
||||
- name: make
|
||||
run: |
|
||||
@@ -104,8 +116,8 @@ jobs:
|
||||
# UPDATE: this can cause spurious leak reports for __cxa_thread_atexit_impl() under glibc.
|
||||
LSAN_OPTIONS: verbosity=0:log_threads=0:use_tls=1:print_suppressions=0
|
||||
run: |
|
||||
llvm_version=$(clang --version | awk 'NR==1 { split($NF, version, "."); print version[1] }')
|
||||
export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-$llvm_version
|
||||
set -x
|
||||
export ASAN_SYMBOLIZER_PATH=$(command -v /usr/bin/llvm-symbolizer* | sort -n | head -1)
|
||||
export LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$PWD/build_tools/lsan_suppressions.txt"
|
||||
make -C build VERBOSE=1 fish_run_tests
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -38,7 +38,8 @@ Desktop.ini
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
messages.pot
|
||||
po/template.po
|
||||
*.mo
|
||||
.directory
|
||||
.fuse_hidden*
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
fish 4.1.0 (released ???)
|
||||
=========================
|
||||
|
||||
.. ignore for 4.1: 10929 10940 10948 10955 10965 10975 10989 10990 10998 11028 11052 11055 11069 11071 11079 11092 11098 11104 11106 11110 11140 11146 11148 11150 11214 11218 11259 11288 11299 11328 11350 11373 11395 11417 11419
|
||||
|
||||
Notable improvements and fixes
|
||||
------------------------------
|
||||
- Compound commands (``begin; echo 1; echo 2; end``) can now be now be abbreviated using braces (``{ echo1; echo 2 }``), like in other shells.
|
||||
@@ -19,7 +21,7 @@ Notable improvements and fixes
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- Tokens like ``{ echo, echo }`` in command position are no longer interpreted as brace expansion but as compound command.
|
||||
- Tokens like ``{echo,echo}`` or ``{ echo, echo }`` in command position are no longer interpreted as brace expansion but as compound command.
|
||||
- Terminfo-style key names (``bind -k``) are no longer supported. They had been superseded by the native notation since 4.0,
|
||||
and currently they would map back to information from terminfo, which does not match what terminals would send with the kitty keyboard protocol (:issue:`11342`).
|
||||
- fish no longer reads the terminfo database, so its behavior is no longer affected by the :envvar:`TERM` environment variable (:issue:`11344`).
|
||||
@@ -64,10 +66,12 @@ New or improved bindings
|
||||
|
||||
Completions
|
||||
^^^^^^^^^^^
|
||||
- ``git`` completions now show the remote url as a description when completing remotes.
|
||||
- ``systemctl`` completions no longer print escape codes if ``SYSTEMD_COLORS`` is set (:issue:`11465`).
|
||||
|
||||
Improved terminal support
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Support for curly underlines in `fish_color_*` variables and :doc:`set_color <cmds/set_color>` (:issue:`10957`).
|
||||
- Support for double, curly, dotted and dashed underlines in `fish_color_*` variables and :doc:`set_color <cmds/set_color>` (:issue:`10957`).
|
||||
- Underlines can now be colored independent of text (:issue:`7619`).
|
||||
- New documentation page `Terminal Compatibility <terminal-compatibility.html>`_ (also accessible via ``man fish-terminal-compatibility``) lists required and optional terminal control sequences used by fish.
|
||||
|
||||
|
||||
@@ -286,37 +286,68 @@ Contributing Translations
|
||||
Fish uses the GNU gettext library to translate messages from English to
|
||||
other languages.
|
||||
|
||||
Creating and updating translations requires the Gettext tools, including
|
||||
``xgettext``, ``msgfmt`` and ``msgmerge``. Translation sources are
|
||||
Translation sources are
|
||||
stored in the ``po`` directory, named ``LANG.po``, where ``LANG`` is the
|
||||
two letter ISO 639-1 language code of the target language (eg ``de`` for
|
||||
German).
|
||||
two letter ISO 639-1 language code of the target language (e.g. ``de`` for
|
||||
German). A region specifier can also be used (e.g. ``pt_BR`` for Brazilian Portuguese).
|
||||
|
||||
To create a new translation:
|
||||
Adding translations for a new language
|
||||
--------------------------------------
|
||||
|
||||
* generate a ``messages.pot`` file by running ``build_tools/fish_xgettext.fish`` from
|
||||
the source tree
|
||||
* copy ``messages.pot`` to ``po/LANG.po``
|
||||
Creating new translations requires the Gettext tools.
|
||||
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
|
||||
language.
|
||||
In addition, the ``cargo-expand`` tool is required.
|
||||
If you have ``cargo`` installed, run::
|
||||
|
||||
To update a translation:
|
||||
cargo install --locked --version 1.0.106 cargo-expand
|
||||
|
||||
* generate a ``messages.pot`` file by running
|
||||
``build_tools/fish_xgettext.fish`` from the source tree
|
||||
to install ``cargo-expand`` (Note that other versions might not work correctly with our scripts).
|
||||
To create a new translation, run::
|
||||
|
||||
* update the existing translation by running
|
||||
``msgmerge --update --no-fuzzy-matching po/LANG.po messages.pot``
|
||||
build_tools/update_translations.fish po/LANG.po
|
||||
|
||||
The ``--no-fuzzy-matching`` is important as we have had terrible experiences with gettext's "fuzzy" translations in the past.
|
||||
By default, this also creates ``mo`` files, which contain the information from the ``po`` files in a
|
||||
binary format.
|
||||
Fish uses these files for translating at runtime.
|
||||
They are not tracked in version control, but they can help translators check if their translations
|
||||
show up correctly.
|
||||
If you build fish locally (``cargo build``), and then run the resulting binary,
|
||||
it will make use of the ``mo`` files generated by the script.
|
||||
Use the ``LANG`` environment variable to tell fish which language to use, e.g.::
|
||||
|
||||
LANG=pt_BR.utf8 target/debug/fish
|
||||
|
||||
If you do not care about the ``mo`` files you can pass the ``--no-mo`` flag to the
|
||||
``update_translations.fish`` script.
|
||||
|
||||
Modifying existing translations
|
||||
-------------------------------
|
||||
|
||||
If you want to work on translations for a language which already has a corresponding ``po`` file, it
|
||||
is sufficient to edit this file. No other changes are necessary.
|
||||
|
||||
To see your translations in action you can run::
|
||||
|
||||
build_tools/update_translations.fish --only-mo po/LANG.po
|
||||
|
||||
to update the binary ``mo`` used by fish. Check the information for adding new languages for a
|
||||
description on how you can get fish to use these files.
|
||||
Running this script requires a fish executable and the gettext ``msgfmt`` tool.
|
||||
|
||||
Editing PO files
|
||||
----------------
|
||||
|
||||
Many tools are available for editing translation files, including
|
||||
command-line and graphical user interface programs. For simple use, you can just use your text editor.
|
||||
command-line and graphical user interface programs. For simple use, you can use your text editor.
|
||||
|
||||
Open up the po file, for example ``po/sv.po``, and you'll see something like::
|
||||
|
||||
msgid "%ls: No suitable job\n"
|
||||
msgstr ""
|
||||
msgstr ""
|
||||
|
||||
The ``msgid`` here is the "name" of the string to translate, typically the english string to translate. The second line (``msgstr``) is where your translation goes.
|
||||
The ``msgid`` here is the "name" of the string to translate, typically the English string to translate.
|
||||
The second line (``msgstr``) is where your translation goes.
|
||||
|
||||
For example::
|
||||
|
||||
@@ -329,11 +360,17 @@ Also any escaped characters, like that ``\n`` newline at the end, should be kept
|
||||
|
||||
Our tests run ``msgfmt --check-format /path/to/file``, so they would catch mismatched placeholders - otherwise fish would crash at runtime when the string is about to be used.
|
||||
|
||||
Be cautious about blindly updating an existing translation file. Trivial
|
||||
changes to an existing message (eg changing the punctuation) will cause
|
||||
existing translations to be removed, since the tools do literal string
|
||||
matching. Therefore, in general, you need to carefully review any
|
||||
recommended deletions.
|
||||
Be cautious about blindly updating an existing translation file.
|
||||
``msgid`` strings should never be updated manually, only by running the appropriate script.
|
||||
|
||||
Modifications to strings in source files
|
||||
----------------------------------------
|
||||
|
||||
If a string changes in the sources, the old translations will no longer work.
|
||||
They will be preserved in the ``po`` files, but commented-out (starting with ``#~``).
|
||||
If you add/remove/change a translatable strings in a source file,
|
||||
run ``build_tools/update_translations.fish`` to propagate this to all translation files (``po/*.po``).
|
||||
This is only relevant for developers modifying the source files of fish or fish scripts.
|
||||
|
||||
Setting Code Up For Translations
|
||||
--------------------------------
|
||||
|
||||
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -89,9 +89,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
@@ -126,6 +126,8 @@ name = "fish-printf"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
@@ -173,9 +175,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@@ -216,9 +218,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -546,6 +548,18 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "unix_path"
|
||||
version = "1.0.1"
|
||||
|
||||
@@ -40,7 +40,7 @@ libc = "0.2"
|
||||
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
||||
# files as of 22 April 2024.
|
||||
lru = "0.13.0"
|
||||
nix = { version = "0.29.0", default-features = false, features = [
|
||||
nix = { version = "0.30.1", default-features = false, features = [
|
||||
"event",
|
||||
"inotify",
|
||||
"resource",
|
||||
@@ -99,7 +99,7 @@ embed-data = ["dep:rust-embed"]
|
||||
asan = []
|
||||
tsan = []
|
||||
|
||||
[lints]
|
||||
[workspace.lints]
|
||||
rust.non_camel_case_types = "allow"
|
||||
rust.non_upper_case_globals = "allow"
|
||||
rust.unknown_lints = "allow"
|
||||
@@ -113,3 +113,6 @@ clippy.needless_lifetimes = "allow"
|
||||
# In the future, they might change to flag other methods of printing.
|
||||
clippy.print_stdout = "deny"
|
||||
clippy.print_stderr = "deny"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
35
SECURITY.md
Normal file
35
SECURITY.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Security Reporting
|
||||
|
||||
If you wish to report a security vulnerability privately, we appreciate your diligence. Please follow the guidelines below to submit your report.
|
||||
|
||||
## Reporting
|
||||
|
||||
To report a security vulnerability, please provide the following information:
|
||||
|
||||
1. **PROJECT**
|
||||
|
||||
- Include the URL of the project repository - Example: <https://github.com/fish-shell/fish-shell>
|
||||
|
||||
2. **PUBLIC**
|
||||
|
||||
- Indicate whether this vulnerability has already been publicly discussed or disclosed.
|
||||
- If so, provide relevant links.
|
||||
|
||||
3. **DESCRIPTION**
|
||||
- Provide a detailed description of the security vulnerability.
|
||||
- Include as much information as possible to help us understand and address the issue.
|
||||
|
||||
Send this information, along with any additional relevant details, to <rf@fishshell.com>.
|
||||
|
||||
## Confidentiality
|
||||
|
||||
We kindly ask you to keep the report confidential until a public announcement is made.
|
||||
|
||||
## Notes
|
||||
|
||||
- Vulnerabilities will be handled on a best-effort basis.
|
||||
- You may request an advance copy of the patched release, but we cannot guarantee early access before the public release.
|
||||
- You will be notified via email simultaneously with the public announcement.
|
||||
- We will respond within a few weeks to confirm whether your report has been accepted or rejected.
|
||||
|
||||
Thank you for helping to improve the security of our project!
|
||||
4
build.rs
4
build.rs
@@ -143,9 +143,9 @@ fn detect_apple(_: &Target) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(cfg!(any(target_os = "ios", target_os = "macos")))
|
||||
}
|
||||
|
||||
#[allow(unexpected_cfgs)]
|
||||
fn detect_cygwin(_: &Target) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(cfg!(target_os = "cygwin"))
|
||||
// Cygwin target is usually cross-compiled.
|
||||
Ok(std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin")
|
||||
}
|
||||
|
||||
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
|
||||
|
||||
14
build_tools/diff_profiles.fish
Normal file → Executable file
14
build_tools/diff_profiles.fish
Normal file → Executable file
@@ -5,6 +5,12 @@
|
||||
#
|
||||
# Usage: ./diff_profiles.fish profile1.log profile2.log > profile_diff.log
|
||||
|
||||
if test (count $argv) -ne 2;
|
||||
echo "Incorrect number of arguments."
|
||||
echo "Usage: "(status filename)" profile1.log profile2.log"
|
||||
exit 1
|
||||
end
|
||||
|
||||
set -l profile1 (cat $argv[1])
|
||||
set -l profile2 (cat $argv[2])
|
||||
|
||||
@@ -15,13 +21,13 @@ while set -l next_line_no (math $line_no + 1) && set -q profile1[$next_line_no]
|
||||
set -l line1 $profile1[$line_no]
|
||||
set -l line2 $profile2[$line_no]
|
||||
|
||||
if not string match -qr '^\d+\t\d+' $line1
|
||||
if not string match -qr '^\s*\d+\s+\d+' $line1
|
||||
echo $line1
|
||||
continue
|
||||
end
|
||||
|
||||
set -l results1 (string match -r '^(\d+)\t(\d+)\s+(.*)' $line1)
|
||||
set -l results2 (string match -r '^(\d+)\t(\d+)\s+(.*)' $line2)
|
||||
set -l results1 (string match -r '^\s*(\d+)\s+(\d+)\s+(.*)' $line1)
|
||||
set -l results2 (string match -r '^\s*(\d+)\s+(\d+)\s+(.*)' $line2)
|
||||
|
||||
# times from both files
|
||||
set -l time1 $results1[2..3]
|
||||
@@ -42,5 +48,5 @@ while set -l next_line_no (math $line_no + 1) && set -q profile1[$next_line_no]
|
||||
set diff[1] (math $time1[1] - $time2[1])
|
||||
set diff[2] (math $time1[2] - $time2[2])
|
||||
|
||||
echo $diff[1] $diff[2] $remainder1
|
||||
printf '%10d %10d %s\n' $diff[1] $diff[2] $remainder1
|
||||
end
|
||||
|
||||
@@ -1,68 +1,101 @@
|
||||
#!/usr/bin/env fish
|
||||
#
|
||||
# Tool to generate messages.pot
|
||||
# Tool to generate gettext messages template file.
|
||||
# Writes to stdout.
|
||||
|
||||
# Create temporary directory for these operations. OS X `mktemp` is somewhat restricted, so this block
|
||||
# works around that - based on share/functions/funced.fish.
|
||||
set -q TMPDIR
|
||||
or set -l TMPDIR /tmp
|
||||
set -l tmpdir (mktemp -d $TMPDIR/fish.XXXXXX)
|
||||
or exit 1
|
||||
begin
|
||||
# Write header. This is required by msguniq.
|
||||
# Note that this results in the file being overwritten.
|
||||
# This is desired behavior, to get rid of the results of prior invocations
|
||||
# of this script.
|
||||
begin
|
||||
echo 'msgid ""'
|
||||
echo 'msgstr ""'
|
||||
echo '"Content-Type: text/plain; charset=UTF-8\n"'
|
||||
echo ""
|
||||
end
|
||||
|
||||
# This is a gigantic crime.
|
||||
# xgettext still does not support rust *at all*, so we use cargo-expand to get all our wgettext invocations.
|
||||
set -l expanded (cargo expand --lib; for f in fish{,_indent,_key_reader}; cargo expand --bin $f; end)
|
||||
set -l cargo_expanded_file (mktemp)
|
||||
# This is a gigantic crime.
|
||||
# We use cargo-expand to get all our wgettext invocations.
|
||||
# This might be replaced once we have a tool which properly handles macro expansions.
|
||||
begin
|
||||
cargo expand --lib
|
||||
for f in fish fish_indent fish_key_reader
|
||||
cargo expand --bin $f
|
||||
end
|
||||
end >$cargo_expanded_file
|
||||
|
||||
# Extract any gettext call
|
||||
set -l strs (printf '%s\n' $expanded | grep -A1 wgettext_static_str |
|
||||
grep 'widestring::internals::core::primitive::str =' |
|
||||
string match -rg '"(.*)"' | string match -rv '^%ls$|^$' |
|
||||
# escaping difference between gettext and cargo-expand: single-quotes
|
||||
string replace -a "\'" "'" | sort -u)
|
||||
set -l rust_string_file (mktemp)
|
||||
|
||||
# Extract any constants
|
||||
set -a strs (string match -rv 'BUILD_VERSION:|PACKAGE_NAME' -- $expanded |
|
||||
string match -rg 'const [A-Z_]*: &str = "(.*)"' | string replace -a "\'" "'")
|
||||
# Extract any gettext call
|
||||
grep -A1 wgettext_static_str <$cargo_expanded_file |
|
||||
grep 'widestring::internals::core::primitive::str =' |
|
||||
string match -rg '"(.*)"' |
|
||||
string match -rv '^%ls$|^$' |
|
||||
# escaping difference between gettext and cargo-expand: single-quotes
|
||||
string replace -a "\'" "'" >$rust_string_file
|
||||
|
||||
# We construct messages.pot ourselves instead of forcing this into msgmerge or whatever.
|
||||
# The escaping so far works out okay.
|
||||
for str in $strs
|
||||
# grep -P needed for string escape to be compatible (PCRE-style),
|
||||
# -H gives the filename, -n the line number.
|
||||
# If you want to run this on non-GNU grep: Don't.
|
||||
echo "#:" (grep -PHn -r -- (string escape --style=regex -- $str) src/ |
|
||||
head -n1 | string replace -r ':\s.*' '')
|
||||
echo "msgid \"$str\""
|
||||
echo 'msgstr ""'
|
||||
end >messages.pot
|
||||
# Extract any constants
|
||||
grep -Ev 'BUILD_VERSION:|PACKAGE_NAME' <$cargo_expanded_file |
|
||||
grep -E 'const [A-Z_]*: &str = "(.*)"' |
|
||||
sed -E -e 's/^.*const [A-Z_]*: &str = "(.*)".*$/\1/' -e "s_\\\'_'_g" >>$rust_string_file
|
||||
|
||||
# This regex handles descriptions for `complete` and `function` statements. These messages are not
|
||||
# particularly important to translate. Hence the "implicit" label.
|
||||
set -l implicit_regex '(?:^| +)(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
|
||||
rm $cargo_expanded_file
|
||||
|
||||
# This regex handles explicit requests to translate a message. These are more important to translate
|
||||
# than messages which should be implicitly translated.
|
||||
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
|
||||
# Sort the extracted strings and remove duplicates.
|
||||
# Then, transform them into the po format.
|
||||
# If a string contains a '%' it is considered a format string and marked with a '#, c-format'.
|
||||
# This allows msgfmt to identify issues with translations whose format string does not match the
|
||||
# original.
|
||||
sort -u $rust_string_file |
|
||||
sed -E -e '/%/ i\
|
||||
#, c-format
|
||||
' -e 's/^(.*)$/msgid "\1"\nmsgstr ""\n/'
|
||||
|
||||
mkdir -p $tmpdir/implicit/share/completions $tmpdir/implicit/share/functions
|
||||
mkdir -p $tmpdir/explicit/share/completions $tmpdir/explicit/share/functions
|
||||
rm $rust_string_file
|
||||
|
||||
for f in share/config.fish share/completions/*.fish share/functions/*.fish
|
||||
# Extract explicit attempts to translate a message. That is, those that are of the form
|
||||
# `(_ "message")`.
|
||||
string replace --filter --regex $explicit_regex '$1' <$f | string unescape \
|
||||
| string replace --all '"' '\\"' | string replace -r '(.*)' 'N_ "$1"' >$tmpdir/explicit/$f
|
||||
function extract_fish_script_messages --argument-names regex
|
||||
|
||||
# Handle `complete` / `function` description messages. The `| fish` is subtle. It basically
|
||||
# avoids the need to use `source` with a command substitution that could affect the current
|
||||
# shell.
|
||||
string replace --filter --regex $implicit_regex '$1' <$f | string unescape \
|
||||
| string replace --all '"' '\\"' | string replace -r '(.*)' 'N_ "$1"' >$tmpdir/implicit/$f
|
||||
end
|
||||
# Using xgettext causes more trouble than it helps.
|
||||
# This is due to handling of escaping in fish differing from formats xgettext understands
|
||||
# (e.g. POSIX shell strings).
|
||||
# We work around this issue by manually writing the file content.
|
||||
|
||||
xgettext -j -k -kN_ -LShell --from-code=UTF-8 -cDescription --no-wrap -o messages.pot $tmpdir/{ex,im}plicit/share/*/*.fish
|
||||
# Steps:
|
||||
# 1. We extract strings to be translated from the relevant files and drop the rest. This step
|
||||
# depends on the regex matching the entire line, and the first capture group matching the
|
||||
# string.
|
||||
# 2. We unescape. This gets rid of some escaping necessary in fish strings.
|
||||
# 3. The resulting strings are sorted alphabetically. This step is optional. Not sorting would
|
||||
# result in strings from the same file appearing together. Removing duplicates is also
|
||||
# optional, since msguniq takes care of that later on as well.
|
||||
# 4. Single backslashes are replaced by double backslashes. This results in the backslashes
|
||||
# being interpreted as literal backslashes by gettext tooling.
|
||||
# 5. Double quotes are escaped, such that they are not interpreted as the start or end of
|
||||
# a msgid.
|
||||
# 6. We transform the string into the format expected in a PO file.
|
||||
cat share/config.fish share/completions/*.fish share/functions/*.fish |
|
||||
string replace --filter --regex $regex '$1' |
|
||||
string unescape |
|
||||
sort -u |
|
||||
sed -E -e 's_\\\\_\\\\\\\\_g' -e 's_"_\\\\"_g' -e 's_^(.*)$_msgid "\1"\nmsgstr ""\n_'
|
||||
end
|
||||
|
||||
# Remove the tmpdir from the location to avoid churn
|
||||
sed -i 's_^#: /.*/share/_#: share/_' messages.pot
|
||||
# This regex handles explicit requests to translate a message. These are more important to translate
|
||||
# than messages which should be implicitly translated.
|
||||
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
|
||||
extract_fish_script_messages $explicit_regex
|
||||
|
||||
rm -r $tmpdir
|
||||
# This regex handles descriptions for `complete` and `function` statements. These messages are not
|
||||
# particularly important to translate. Hence the "implicit" label.
|
||||
set -l implicit_regex '^(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
|
||||
extract_fish_script_messages $implicit_regex
|
||||
end |
|
||||
# At this point, all extracted strings have been written to stdout,
|
||||
# starting with the ones taken from the Rust sources,
|
||||
# followed by strings explicitly marked for translation in fish scripts,
|
||||
# and finally the strings from fish scripts which get translated implicitly.
|
||||
# Because we do not eliminate duplicates across these categories,
|
||||
# we do it here, since other gettext tools expect no duplicates.
|
||||
msguniq --no-wrap
|
||||
|
||||
88
build_tools/update_translations.fish
Executable file
88
build_tools/update_translations.fish
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
# Updates the files used for gettext translations.
|
||||
# By default, the whole xgettext, msgmerge, msgfmt pipeline runs,
|
||||
# which extracts the messages from the source files into $template_file,
|
||||
# updates the PO files for each language from that
|
||||
# (changed line numbers, added messages, removed messages),
|
||||
# and finally generates a machine-readable MO file for each language,
|
||||
# which is stored in share/locale/$LANG/LC_MESSAGES/fish.mo (relative to the repo root).
|
||||
#
|
||||
# Use cases:
|
||||
# For developers:
|
||||
# - Run with args `--no-mo` to update all PO files after making changes to Rust/fish
|
||||
# sources.
|
||||
# For translators:
|
||||
# - Run with `--no-mo` first, to ensure that the strings you are translating are up to date.
|
||||
# - Specify the language you want to work on as an argument, which must be a file in the po/
|
||||
# directory. You can specify a language which does not have translations yet by specifying the
|
||||
# name of a file which does not yet exist. Make sure to follow the naming convention.
|
||||
|
||||
# The sort utility is locale-sensitive.
|
||||
# Ensure that sorting output is consistent by setting LC_ALL here.
|
||||
set -gx LC_ALL C.UTF-8
|
||||
|
||||
set -l build_tools (status dirname)
|
||||
set -l template_file $build_tools/../po/template.po
|
||||
set -l po_dir $build_tools/../po
|
||||
|
||||
set -l extract
|
||||
set -l po
|
||||
set -l mo
|
||||
|
||||
argparse --exclusive 'no-mo,only-mo' 'no-mo' 'only-mo' -- $argv
|
||||
or exit $status
|
||||
|
||||
if test -z $argv[1]
|
||||
# Update everything if not specified otherwise.
|
||||
set -g po_files $po_dir/*.po
|
||||
else
|
||||
set -l po_dir_id (stat --format='%d:%i' -- $po_dir)
|
||||
for arg in $argv
|
||||
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg))
|
||||
if test $po_dir_id != $arg_dir_id
|
||||
echo "Argument $arg is not a file in the directory $(realpath $po_dir)."
|
||||
echo "Non-option arguments must specify paths to files in this directory."
|
||||
echo ""
|
||||
echo "If you want to add a new language to the translations not the following:"
|
||||
echo "The filename must identify a language, with a two letter ISO 639-1 language code of the target language (e.g. 'pt' for Portuguese), and use the file extension '.po'."
|
||||
echo "Optionally, you can specify a regional variant (e.g. 'pt_BR')."
|
||||
echo "So valid filenames are of the shape 'll.po' or 'll_CC.po'."
|
||||
exit 1
|
||||
end
|
||||
if not basename $arg | grep -qE '^[a-z]{2}(_[A-Z]{2})?\.po$'
|
||||
echo "Filename does not match the expected format ('ll.po' or 'll_CC.po')."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
set -g po_files $argv
|
||||
end
|
||||
|
||||
if set -l --query _flag_no_mo
|
||||
set -l --erase mo
|
||||
end
|
||||
if set -l --query _flag_only_mo
|
||||
set -l --erase extract
|
||||
set -l --erase po
|
||||
end
|
||||
|
||||
if set -l --query extract
|
||||
$build_tools/fish_xgettext.fish >$template_file
|
||||
or exit 1
|
||||
end
|
||||
|
||||
for po_file in $po_files
|
||||
if set -l --query po
|
||||
if test -e $po_file
|
||||
msgmerge --update --no-fuzzy-matching --no-wrap --backup=none $po_file $template_file
|
||||
else
|
||||
cp $template_file $po_file
|
||||
end
|
||||
end
|
||||
if set -l --query mo
|
||||
set -l locale_dir $build_tools/../share/locale
|
||||
set -l out_dir $locale_dir/(basename $po_file .po)/LC_MESSAGES
|
||||
mkdir -p $out_dir
|
||||
msgfmt --check-format --output-file=$out_dir/fish.mo $po_file
|
||||
end
|
||||
end
|
||||
1
debian/control
vendored
1
debian/control
vendored
@@ -27,7 +27,6 @@ Depends: bsdextrautils | bsdmainutils,
|
||||
gettext-base,
|
||||
# for nroff and preconv
|
||||
groff-base,
|
||||
man-db,
|
||||
# for terminal definitions
|
||||
ncurses-base,
|
||||
# for kill
|
||||
|
||||
@@ -425,6 +425,24 @@ Launch ``git diff`` and repaint the commandline afterwards when :kbd:`ctrl-g` is
|
||||
|
||||
bind ctrl-g 'git diff' repaint
|
||||
|
||||
Swap :kbd:`tab` and :kbd:`shift-tab`, making tab focus the search field.
|
||||
But if the search field is already active, keep the behavior (:kbd:`tab` cycles forward, :kbd:`shift-tab` backward).::
|
||||
|
||||
bind tab '
|
||||
if commandline --search-field >/dev/null
|
||||
commandline -f complete
|
||||
else
|
||||
commandline -f complete-and-search
|
||||
end
|
||||
'
|
||||
bind shift-tab '
|
||||
if commandline --search-field >/dev/null
|
||||
commandline -f complete-and-search
|
||||
else
|
||||
commandline -f complete
|
||||
end
|
||||
'
|
||||
|
||||
.. _cmd-bind-termlimits:
|
||||
|
||||
Terminal Limitations
|
||||
|
||||
@@ -74,6 +74,9 @@ The following options change what part of the commandline is printed or updated:
|
||||
**--search-field**
|
||||
Use the pager search field instead of the command line. Returns false if the search field is not shown.
|
||||
|
||||
**--input=INPUT**
|
||||
Operate on this string instead of the commandline. Useful for using options like **--tokens-expanded**.
|
||||
|
||||
The following options change the way ``commandline`` prints the current commandline buffer:
|
||||
|
||||
**-c** or **--cut-at-cursor**
|
||||
@@ -87,10 +90,7 @@ The following options change the way ``commandline`` prints the current commandl
|
||||
Perform argument expansion on the selection and print one argument per line.
|
||||
Command substitutions are not expanded but forwarded as-is.
|
||||
|
||||
**--tokens-raw**
|
||||
Print arguments in the selection as they appear on the command line, one per line.
|
||||
|
||||
**-o** or **tokenize**
|
||||
**-o**, **tokenize**, **--tokens-raw**
|
||||
Deprecated; do not use.
|
||||
|
||||
If ``commandline`` is called during a call to complete a given string using ``complete -C STRING``, ``commandline`` will consider the specified string to be the current contents of the command line.
|
||||
|
||||
@@ -22,7 +22,7 @@ Description
|
||||
|
||||
The ``fish_mode_prompt`` function outputs the mode indicator for use in vi mode.
|
||||
|
||||
The default ``fish_mode_prompt`` function will output indicators about the current vi editor mode displayed to the left of the regular prompt. Define your own function to customize the appearance of the mode indicator. The ``$fish_bind_mode variable`` can be used to determine the current mode. It will be one of ``default``, ``insert``, ``replace_one``, or ``visual``.
|
||||
The default ``fish_mode_prompt`` function will output indicators about the current vi editor mode displayed to the left of the regular prompt. Define your own function to customize the appearance of the mode indicator. The ``$fish_bind_mode variable`` can be used to determine the current mode. It will be one of ``default``, ``insert``, ``replace_one``, ``replace``, or ``visual``.
|
||||
|
||||
You can also define an empty ``fish_mode_prompt`` function to remove the vi mode indicators::
|
||||
|
||||
@@ -51,6 +51,9 @@ Example
|
||||
case replace_one
|
||||
set_color --bold green
|
||||
echo 'R'
|
||||
case replace
|
||||
set_color --bold bryellow
|
||||
echo 'R'
|
||||
case visual
|
||||
set_color --bold brmagenta
|
||||
echo 'V'
|
||||
|
||||
@@ -57,7 +57,7 @@ The following options are available:
|
||||
Sets reverse mode.
|
||||
|
||||
**-u** or **--underline**, or **-uSTYLE** or **--underline=STYLE**
|
||||
Set the underline mode; supported styles are **single** (default) and **curly**.
|
||||
Set the underline mode; supported styles are **single** (default), **double**, **curly**, **dotted** and **dashed**.
|
||||
|
||||
**-h** or **--help**
|
||||
Displays help about using this command.
|
||||
|
||||
@@ -8,8 +8,8 @@ Synopsis
|
||||
|
||||
.. synopsis::
|
||||
|
||||
string join [-q | --quiet] SEP [STRING ...]
|
||||
string join0 [-q | --quiet] [STRING ...]
|
||||
string join [-q | --quiet] [-n | --no-empty] [--] SEP [STRING ...]
|
||||
string join0 [-q | --quiet] [-n | --no-empty] [--] [STRING ...]
|
||||
|
||||
.. END SYNOPSIS
|
||||
|
||||
@@ -18,11 +18,28 @@ Description
|
||||
|
||||
.. BEGIN DESCRIPTION
|
||||
|
||||
``string join`` joins its *STRING* arguments into a single string separated by *SEP*, which can be an empty string. Exit status: 0 if at least one join was performed, or 1 otherwise. If ``-n`` or ``--no-empty`` is specified, empty strings are excluded from consideration (e.g. ``string join -n + a b "" c`` would expand to ``a+b+c`` not ``a+b++c``).
|
||||
Joins its *STRING* arguments into a single string separated by *SEP* (for ``string join``) or by the
|
||||
zero byte (NUL) (for ``string join0``).
|
||||
Exit status: 0 if at least one join was performed, or 1 otherwise.
|
||||
|
||||
``string join0`` joins its *STRING* arguments into a single string separated by the zero byte (NUL), and adds a trailing NUL. This is most useful in conjunction with tools that accept NUL-delimited input, such as ``sort -z``. Exit status: 0 if at least one join was performed, or 1 otherwise.
|
||||
**-n**, **--no-empty**
|
||||
Exclude empty strings from consideration (e.g. ``string join -n + a b "" c`` would expand to ``a+b+c`` not ``a+b++c``).
|
||||
|
||||
Because Unix uses NUL as the string terminator, passing the output of ``string join0`` as an *argument* to a command (via a :ref:`command substitution <expand-command-substitution>`) won't actually work. Fish will pass the correct bytes along, but the command won't be able to tell where the argument ends. This is a limitation of Unix' argument passing.
|
||||
**-q**, **--quiet**
|
||||
Do not print the strings, only set the exit status as described above.
|
||||
|
||||
**WARNING**:
|
||||
Insert a ``--`` before positional arguments to prevent them from being interpreted as flags.
|
||||
Otherwise, any strings starting with ``-`` will be treated as flag arguments, meaning they will most likely result in the command failing.
|
||||
This is also true if you specify a variable which expands to such a string instead of a literal string.
|
||||
If you don't need to append flag arguments at the end of the command,
|
||||
just always use ``--`` to avoid unwelcome surprises.
|
||||
|
||||
``string join0`` adds a trailing NUL. This is most useful in conjunction with tools that accept NUL-delimited input, such as ``sort -z``.
|
||||
|
||||
Because Unix uses NUL as the string terminator, passing the output of ``string join0`` as an *argument* to a command (via a :ref:`command substitution <expand-command-substitution>`) won't actually work.
|
||||
Fish will pass the correct bytes along, but the command won't be able to tell where the argument ends.
|
||||
This is a limitation of Unix' argument passing.
|
||||
|
||||
.. END DESCRIPTION
|
||||
|
||||
@@ -43,4 +60,14 @@ Examples
|
||||
>_ string join '' a b c
|
||||
abc
|
||||
|
||||
>_ set -l markdown_list '- first' '- second' '- third'
|
||||
# Strings with leading hyphens (also in variable expansions) are interpreted as flag arguments by default.
|
||||
>_ string join \n $markdown_list
|
||||
string join: - first: unknown option
|
||||
# Use '--' to prevent this.
|
||||
>_ string join -- \n $markdown_list
|
||||
- first
|
||||
- second
|
||||
- third
|
||||
|
||||
.. END EXAMPLES
|
||||
|
||||
@@ -11,7 +11,7 @@ Synopsis
|
||||
string collect [-a | --allow-empty] [-N | --no-trim-newlines] [STRING ...]
|
||||
string escape [-n | --no-quoted] [--style=] [STRING ...]
|
||||
string join [-q | --quiet] [-n | --no-empty] SEP [STRING ...]
|
||||
string join0 [-q | --quiet] [STRING ...]
|
||||
string join0 [-q | --quiet] [-n | --no-empty] [STRING ...]
|
||||
string length [-q | --quiet] [STRING ...]
|
||||
string lower [-q | --quiet] [STRING ...]
|
||||
string match [-a | --all] [-e | --entire] [-i | --ignore-case]
|
||||
@@ -27,7 +27,7 @@ Synopsis
|
||||
[-r | --regex] [-q | --quiet] PATTERN REPLACE [STRING ...]
|
||||
string shorten [(-c | --char) CHARS] [(-m | --max) INTEGER]
|
||||
[-N | --no-newline] [-l | --left] [-q | --quiet] [STRING ...]
|
||||
string split [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
|
||||
string split [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
|
||||
[-q | --quiet] [-r | --right] SEP [STRING ...]
|
||||
string split0 [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
|
||||
[-q | --quiet] [-r | --right] [STRING ...]
|
||||
|
||||
@@ -354,7 +354,7 @@ Some bindings are common across Emacs and vi mode, because they aren't text edit
|
||||
|
||||
- :kbd:`alt-o` opens the file at the cursor in a pager. If the cursor is in command position and the command is a script, it will instead open that script in your editor. The editor is chosen from the first available of the ``$VISUAL`` or ``$EDITOR`` variables.
|
||||
|
||||
- :kbd:`alt-p` adds the string ``&| less;`` to the end of the job under the cursor. The result is that the output of the command will be paged.
|
||||
- :kbd:`alt-p` adds the string ``&| less;`` to the end of the job under the cursor. The result is that the output of the command will be paged. If you set the ``PAGER`` variable, its value is used instead of ``less``.
|
||||
|
||||
- :kbd:`alt-w` prints a short description of the command under the cursor.
|
||||
|
||||
|
||||
@@ -917,6 +917,12 @@ If there is nothing between a brace and a comma or two commas, it's interpreted
|
||||
|
||||
To use a "," as an element, :ref:`quote <quotes>` or :ref:`escape <escapes>` it.
|
||||
|
||||
The very first character of a command token is never interpreted as expanding brace, because it's the beginning of a :ref:`compound statement <cmd-begin>`::
|
||||
|
||||
> {echo hello, && echo world}
|
||||
hello,
|
||||
world
|
||||
|
||||
.. _cartesian-product:
|
||||
|
||||
Combining lists
|
||||
|
||||
@@ -120,10 +120,22 @@ Optional Commands
|
||||
- smul
|
||||
- Enter underline mode.
|
||||
-
|
||||
* - ``\e[4:2m``
|
||||
- Su
|
||||
- Enter double underline mode.
|
||||
- kitty
|
||||
* - ``\e[4:3m``
|
||||
- Su
|
||||
- Enter curly underline mode.
|
||||
- kitty
|
||||
* - ``\e[4:4m``
|
||||
- Su
|
||||
- Enter dotted underline mode.
|
||||
- kitty
|
||||
* - ``\e[4:5m``
|
||||
- Su
|
||||
- Enter dashed underline mode.
|
||||
- kitty
|
||||
* - ``\e[7m``
|
||||
- rev
|
||||
- Enter reverse video mode (swap foreground and background colors).
|
||||
|
||||
@@ -115,7 +115,7 @@ These colors, and many more, can be changed by running ``fish_config``, or by mo
|
||||
|
||||
For example, if you want to disable (almost) all coloring::
|
||||
|
||||
fish_config theme choose none
|
||||
fish_config theme choose None
|
||||
|
||||
This picks the "none" theme. To see all themes::
|
||||
|
||||
@@ -631,12 +631,12 @@ You can define your own prompt from the command line:
|
||||
|
||||
Then, if you are happy with it, you can save it to disk by typing ``funcsave fish_prompt``. This saves the prompt in ``~/.config/fish/functions/fish_prompt.fish``. (Or, if you want, you can create that file manually from the start.)
|
||||
|
||||
Multiple lines are OK. Colors can be set via :doc:`set_color <cmds/set_color>`, passing it named ANSI colors, or hex RGB values::
|
||||
Multiple lines are OK. Colors can be set via :doc:`set_color <cmds/set_color>` by passing it named ANSI colors, or hex RGB values::
|
||||
|
||||
function fish_prompt
|
||||
set_color purple
|
||||
date "+%m/%d/%y"
|
||||
set_color F00
|
||||
set_color FF0000
|
||||
echo (pwd) '>' (set_color normal)
|
||||
end
|
||||
|
||||
|
||||
@@ -31,13 +31,12 @@ BuildRequires: glibc-langpack-en
|
||||
BuildRequires: python3 procps
|
||||
|
||||
%if 0%{?suse_version}
|
||||
Requires: terminfo-base
|
||||
Requires: terminfo-base groff
|
||||
%else
|
||||
Requires: ncurses-base
|
||||
Requires: ncurses-base groff-base
|
||||
%endif
|
||||
Requires: file
|
||||
Requires: python3
|
||||
Requires: man
|
||||
Requires: procps
|
||||
|
||||
# Although the build scripts mangle the version number to be RPM compatible
|
||||
|
||||
180882
po/pt_BR.po
180882
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
185464
po/zh_CN.po
185464
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "fish-printf"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.2.1"
|
||||
repository = "https://github.com/fish-shell/fish-shell"
|
||||
description = "printf implementation, based on musl"
|
||||
@@ -9,3 +10,8 @@ license = "MIT"
|
||||
[dependencies]
|
||||
libc = "0.2.155"
|
||||
widestring = { version = "1.0.2", optional = true }
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
1
printf/clippy.toml
Normal file
1
printf/clippy.toml
Normal file
@@ -0,0 +1 @@
|
||||
allow-print-in-tests = true
|
||||
@@ -71,7 +71,7 @@ macro_rules! sprintf {
|
||||
/// - `args`: Iterator over the arguments to format.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Result` which is `Ok` containing the number of characters written on success, or an `Error`.
|
||||
/// A `Result` which is `Ok` containing the width of the string written on success, or an `Error`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
use std::fmt::{self, Write};
|
||||
use std::mem;
|
||||
use std::result::Result;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
#[cfg(feature = "widestring")]
|
||||
use widestring::Utf32Str as wstr;
|
||||
@@ -382,7 +384,7 @@ pub fn sprintf_locale(
|
||||
}
|
||||
|
||||
// Read field width. We do not support $.
|
||||
let width = if s.at(0) == Some('*') {
|
||||
let desired_width = if s.at(0) == Some('*') {
|
||||
let arg_width = args.next().ok_or(Error::MissingArg)?.as_sint()?;
|
||||
s.advance_by(1);
|
||||
if arg_width < 0 {
|
||||
@@ -397,7 +399,7 @@ pub fn sprintf_locale(
|
||||
};
|
||||
|
||||
// Optionally read precision. We do not support $.
|
||||
let mut prec: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
|
||||
let mut desired_precision: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
|
||||
// "A negative precision is treated as though it were missing."
|
||||
// Here we assume the precision is always signed.
|
||||
s.advance_by(2);
|
||||
@@ -410,7 +412,7 @@ pub fn sprintf_locale(
|
||||
None
|
||||
};
|
||||
// Disallow precisions larger than i32::MAX, in keeping with C.
|
||||
if prec.unwrap_or(0) > i32::MAX as usize {
|
||||
if desired_precision.unwrap_or(0) > i32::MAX as usize {
|
||||
return Err(Error::Overflow);
|
||||
}
|
||||
|
||||
@@ -429,7 +431,7 @@ pub fn sprintf_locale(
|
||||
// "If a precision is given with a numeric conversion (d, i, o, u, i, x, and X),
|
||||
// the 0 flag is ignored." p is included here.
|
||||
let spec_is_numeric = matches!(conv_spec, CS::d | CS::u | CS::o | CS::p | CS::x | CS::X);
|
||||
if spec_is_numeric && prec.is_some() {
|
||||
if spec_is_numeric && desired_precision.is_some() {
|
||||
flags.zero_pad = false;
|
||||
}
|
||||
|
||||
@@ -443,13 +445,22 @@ pub fn sprintf_locale(
|
||||
CS::e | CS::f | CS::g | CS::a | CS::E | CS::F | CS::G | CS::A => {
|
||||
// Floating point types handle output on their own.
|
||||
let float = arg.as_float()?;
|
||||
let len = format_float(f, float, width, prec, flags, locale, conv_spec, buf)?;
|
||||
let len = format_float(
|
||||
f,
|
||||
float,
|
||||
desired_width,
|
||||
desired_precision,
|
||||
flags,
|
||||
locale,
|
||||
conv_spec,
|
||||
buf,
|
||||
)?;
|
||||
out_len = out_len.checked_add(len).ok_or(Error::Overflow)?;
|
||||
continue 'main;
|
||||
}
|
||||
CS::p => {
|
||||
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
|
||||
prec = prec.map(|p| p.max(PTR_HEX_DIGITS));
|
||||
desired_precision = desired_precision.map(|p| p.max(PTR_HEX_DIGITS));
|
||||
let uint = arg.as_uint()?;
|
||||
if uint != 0 {
|
||||
prefix = "0x";
|
||||
@@ -479,8 +490,8 @@ pub fn sprintf_locale(
|
||||
if uint != 0 {
|
||||
write!(buf, "{:o}", uint)?;
|
||||
}
|
||||
if flags.alt_form && prec.unwrap_or(0) <= buf.len() + 1 {
|
||||
prec = Some(buf.len() + 1);
|
||||
if flags.alt_form && desired_precision.unwrap_or(0) <= buf.len() + 1 {
|
||||
desired_precision = Some(buf.len() + 1);
|
||||
}
|
||||
buf
|
||||
}
|
||||
@@ -514,10 +525,38 @@ pub fn sprintf_locale(
|
||||
CS::s => {
|
||||
// also 'S'
|
||||
let s = arg.as_str(buf)?;
|
||||
let p = prec.unwrap_or(s.len()).min(s.len());
|
||||
prec = Some(p);
|
||||
flags.zero_pad = false;
|
||||
&s[..p]
|
||||
match desired_precision {
|
||||
Some(precision) => {
|
||||
// from man printf(3)
|
||||
// "the maximum number of characters to be printed from a string"
|
||||
// We interpret this to mean the maximum width when printed, as defined by
|
||||
// Unicode grapheme cluster width.
|
||||
let mut byte_len = 0;
|
||||
let mut width = 0;
|
||||
let mut graphemes = s.graphemes(true);
|
||||
// Iteratively add single grapheme clusters as long as the fit within the
|
||||
// width limited by precision.
|
||||
while width < precision {
|
||||
match graphemes.next() {
|
||||
Some(grapheme) => {
|
||||
let grapheme_width = grapheme.width();
|
||||
if width + grapheme_width <= precision {
|
||||
byte_len += grapheme.len();
|
||||
width += grapheme_width;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
let p = precision.min(width);
|
||||
desired_precision = Some(p);
|
||||
&s[..byte_len]
|
||||
}
|
||||
None => s,
|
||||
}
|
||||
}
|
||||
};
|
||||
// Numeric output should be empty iff the value is 0.
|
||||
@@ -528,23 +567,26 @@ pub fn sprintf_locale(
|
||||
// Decide if we want to apply thousands grouping to the body, and compute its size.
|
||||
// Note we have already errored out if grouped is set and this is non-numeric.
|
||||
let wants_grouping = flags.grouped && locale.thousands_sep.is_some();
|
||||
let body_len = match wants_grouping {
|
||||
let body_width = match wants_grouping {
|
||||
// We assume that text representing numbers is ASCII, so len == width.
|
||||
true => body.len() + locale.separator_count(body.len()),
|
||||
false => body.len(),
|
||||
false => body.width(),
|
||||
};
|
||||
|
||||
// Resolve the precision.
|
||||
// In the case of a non-numeric conversion, update the precision to at least the
|
||||
// length of the string.
|
||||
let prec = if !spec_is_numeric {
|
||||
prec.unwrap_or(body_len)
|
||||
let desired_precision = if !spec_is_numeric {
|
||||
desired_precision.unwrap_or(body_width)
|
||||
} else {
|
||||
prec.unwrap_or(1).max(body_len)
|
||||
desired_precision.unwrap_or(1).max(body_width)
|
||||
};
|
||||
|
||||
let prefix_len = prefix.len();
|
||||
let unpadded_width = prefix_len.checked_add(prec).ok_or(Error::Overflow)?;
|
||||
let width = width.max(unpadded_width);
|
||||
let prefix_width = prefix.width();
|
||||
let unpadded_width = prefix_width
|
||||
.checked_add(desired_precision)
|
||||
.ok_or(Error::Overflow)?;
|
||||
let width = desired_width.max(unpadded_width);
|
||||
|
||||
// Pad on the left with spaces to the desired width?
|
||||
if !flags.left_adj && !flags.zero_pad {
|
||||
@@ -560,7 +602,8 @@ pub fn sprintf_locale(
|
||||
}
|
||||
|
||||
// Pad on the left to the given precision?
|
||||
pad(f, '0', prec, body_len)?;
|
||||
// TODO: why pad with 0 here?
|
||||
pad(f, '0', desired_precision, body_width)?;
|
||||
|
||||
// Output the actual value, perhaps with grouping.
|
||||
if wants_grouping {
|
||||
|
||||
@@ -13,6 +13,7 @@ macro_rules! sprintf_check {
|
||||
$(,)? // optional trailing comma
|
||||
) => {
|
||||
{
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
let mut target = String::new();
|
||||
let mut args = [$($arg.to_arg()),*];
|
||||
let len = $crate::printf_c_locale(
|
||||
@@ -20,7 +21,7 @@ macro_rules! sprintf_check {
|
||||
$fmt.as_ref() as &str,
|
||||
&mut args,
|
||||
).expect("printf failed");
|
||||
assert!(len == target.len(), "Wrong length returned: {} vs {}", len, target.len());
|
||||
assert_eq!(len, target.width(), "Wrong length returned");
|
||||
target
|
||||
}
|
||||
};
|
||||
@@ -735,6 +736,18 @@ fn test_huge_precision_g() {
|
||||
sprintf_err!("%.2147483648g", f => Error::Overflow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_ascii() {
|
||||
assert_fmt!("%3s", "ö" => " ö");
|
||||
assert_fmt!("%3s", "🇺🇳" => " 🇺🇳");
|
||||
assert_fmt!("%.3s", "🇺🇳🇺🇳" => "🇺🇳");
|
||||
assert_fmt!("%.3s", "a🇺🇳" => "a🇺🇳");
|
||||
assert_fmt!("%.3s", "aa🇺🇳" => "aa");
|
||||
assert_fmt!("%3.3s", "aa🇺🇳" => " aa");
|
||||
assert_fmt!("%.1s", "𒈙a" => "𒈙");
|
||||
assert_fmt!("%3.3s", "👨👨👧👧" => " 👨👨👧👧");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_errors() {
|
||||
use Error::*;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
function __fish_apt_no_subcommand -d 'Test if apt has yet to be given the subcommand'
|
||||
for i in (commandline -xpc)
|
||||
if contains -- $i update upgrade dselect-upgrade dist-upgrade install remove purge source build-dep check clean autoclean changelog
|
||||
if contains -- $i update upgrade dselect-upgrade dist-upgrade install remove purge autoremove autopurge source build-dep check clean autoclean changelog
|
||||
return 1
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@ end
|
||||
|
||||
function __fish_apt_use_package -d 'Test if apt command should have packages as potential completion'
|
||||
for i in (commandline -xpc)
|
||||
if contains -- $i contains install remove purge build-dep changelog
|
||||
if contains -- $i contains install remove purge autoremove autopurge build-dep changelog
|
||||
return 0
|
||||
end
|
||||
end
|
||||
@@ -34,7 +34,8 @@ complete -f -n __fish_apt_no_subcommand -c apt-get -a build-dep -d 'Install/remo
|
||||
complete -f -n __fish_apt_no_subcommand -c apt-get -a check -d 'Update cache and check dependencies'
|
||||
complete -f -n __fish_apt_no_subcommand -c apt-get -a clean -d 'Clean local caches and packages'
|
||||
complete -f -n __fish_apt_no_subcommand -c apt-get -a autoclean -d 'Clean packages no longer be downloaded'
|
||||
complete -f -n __fish_apt_no_subcommand -c apt-get -a autoremove -d 'Remove automatically installed packages'
|
||||
complete -f -n __fish_apt_no_subcommand -c apt-get -a autoremove -d 'Remove packages no longer needed as dependencies'
|
||||
complete -f -n __fish_apt_no_subcommand -c apt-get -a autopurge -d 'Remove packages no longer needed as dependencies and delete their config files'
|
||||
complete -c apt-get -l no-install-recommends -d 'Do not install recommended packages'
|
||||
complete -c apt-get -l no-install-suggests -d 'Do not install suggested packages'
|
||||
complete -c apt-get -s d -l download-only -d 'Download Only'
|
||||
|
||||
@@ -6,8 +6,8 @@ if [ "$(uname -s)" = Darwin -a "$(command -s apt)" = /usr/bin/apt ]
|
||||
exit 1
|
||||
end
|
||||
|
||||
set -l all_subcmds update upgrade full-upgrade search list install show remove edit-sources purge changelog autoremove depends rdepends
|
||||
set -l pkg_subcmds install upgrade full-upgrade show search purge changelog policy depends rdepends autoremove
|
||||
set -l all_subcmds update upgrade full-upgrade search list install show remove edit-sources purge changelog autoremove autopurge depends rdepends
|
||||
set -l pkg_subcmds install upgrade full-upgrade show search purge changelog policy depends rdepends autoremove autopurge
|
||||
set -l installed_pkg_subcmds remove
|
||||
set -l handle_file_pkg_subcmds install
|
||||
|
||||
@@ -114,7 +114,7 @@ __fish_apt_subcommand changelog -r -d 'Download and display package changelog'
|
||||
# Autoremove
|
||||
__fish_apt_subcommand autoremove -d 'Remove packages no longer needed as dependencies'
|
||||
|
||||
# Autoremove
|
||||
# Autopurge
|
||||
__fish_apt_subcommand autopurge -d 'Remove packages no longer needed as dependencies and delete their config files'
|
||||
|
||||
# Clean
|
||||
|
||||
@@ -11,7 +11,7 @@ end
|
||||
|
||||
function __fish_apt_use_package -d 'Test if aptitude command should have packages as potential completion'
|
||||
for i in (commandline -xpc)
|
||||
if contains -- $i changelog full-upgrade download forbid-version hold install keep-all markauto purge reinstall remove show unhold unmarkauto
|
||||
if contains -- $i changelog full-upgrade download forbid-version hold install keep-all markauto purge reinstall remove show showrc unhold unmarkauto why why-not versions
|
||||
return 0
|
||||
end
|
||||
end
|
||||
@@ -38,15 +38,19 @@ complete -f -n __fish_apt_no_subcommand -c aptitude -a markauto -d 'Mark package
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a purge -d 'Remove and delete all associated configuration and data files'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a reinstall -d 'Reinstall the packages'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a remove -d 'Remove the packages'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a showsrc -d 'Display detailed information about the source packages (apt wrapper)'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a show -d 'Display detailed information about the packages'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a unhold -d 'Consider the packages by future upgrade commands'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a unmarkauto -d 'Mark packages as manually installed'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a search -d 'Search for packages matching one of the patterns'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a help -d 'Display brief summary of the available commands and options'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a why -d 'Explain why a particular package should be installed'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a why-not -d 'Explain why a particular package cannot be installed'
|
||||
complete -f -n __fish_apt_no_subcommand -c aptitude -a versions -d 'Displays the versions of specified packages'
|
||||
|
||||
complete -c aptitude -s D -l show-deps -d 'Show explanations of automatic installations and removals'
|
||||
complete -c aptitude -s d -l download-only -d 'Download Only'
|
||||
complete -c aptitude -s f -l fix-broken -d 'Correct broken dependencies'
|
||||
complete -c aptitude -s d -l download-only -d 'Download only'
|
||||
complete -c aptitude -s f -l fix-broken -d 'Aggressively try to fix broken packages'
|
||||
complete -c aptitude -l purge-unused -d 'Purge packages that are not required by any installed package'
|
||||
complete -c aptitude -s P -l prompt -d 'Always display a prompt'
|
||||
complete -c aptitude -s R -l without-recommends -d 'Do not treat recommendations as dependencies'
|
||||
@@ -60,6 +64,7 @@ complete -c aptitude -l version -d 'Display the version of aptitude and compile
|
||||
complete -c aptitude -l visual-preview -d 'Start up the visual interface and display its preview screen'
|
||||
complete -c aptitude -s y -l assume-yes -d 'Assume the answer yes for all question prompts'
|
||||
complete -c aptitude -s Z -d 'Show how much disk space will be used or freed'
|
||||
complete -c aptitude -s u -d 'Download new package lists on startup (terminal interface only)'
|
||||
complete -r -c aptitude -s F -l display-format -d 'Specify the format to be used by the search command'
|
||||
complete -r -c aptitude -s t -l target-release -d 'Set the release from which packages should be installed'
|
||||
complete -r -c aptitude -s O -l sort -d 'Specify the order for the output from the search command'
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
# Tab completion for cargo (https://github.com/rust-lang/cargo).
|
||||
|
||||
## --- WRITTEN MANUALLY ---
|
||||
set -l __fish_cargo_subcommands (cargo --list 2>&1 | string replace -rf '^\s+([^\s]+)\s*(.*)' '$1\t$2' | string escape)
|
||||
|
||||
function __fish_cargo
|
||||
cargo --color=never $argv
|
||||
end
|
||||
|
||||
set -l __fish_cargo_subcommands (__fish_cargo --list 2>&1 | string replace -rf '^\s+([^\s]+)\s*(.*)' '$1\t$2' | string escape)
|
||||
|
||||
complete -c cargo -f -c cargo -n __fish_use_subcommand -a "$__fish_cargo_subcommands"
|
||||
complete -c cargo -x -c cargo -n '__fish_seen_subcommand_from help' -a "$__fish_cargo_subcommands"
|
||||
|
||||
for x in bench b build c check rustc t test
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l bench -a "(cargo bench --bench 2>&1 | string replace -rf '^\s+' '')"
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l bench -a "(__fish_cargo bench --bench 2>&1 | string replace -rf '^\s+' '')"
|
||||
complete -c cargo -n "__fish_seen_subcommand_from $x" -l lib -d 'Only this package\'s library'
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l test -a "(cargo test --test 2>&1 | string replace -rf '^\s+' '')"
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l test -a "(__fish_cargo test --test 2>&1 | string replace -rf '^\s+' '')"
|
||||
end
|
||||
|
||||
for x in bench b build c check r run rustc t test
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l bin -a "(cargo run --bin 2>&1 | string replace -rf '^\s+' '')"
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l example -a "(cargo run --example 2>&1 | string replace -rf '^\s+' '')"
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l bin -a "(__fish_cargo run --bin 2>&1 | string replace -rf '^\s+' '')"
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l example -a "(__fish_cargo run --example 2>&1 | string replace -rf '^\s+' '')"
|
||||
end
|
||||
|
||||
# If using rustup, get the list of installed targets from there. Otherwise print all targets.
|
||||
@@ -38,18 +43,18 @@ end
|
||||
|
||||
function __fish_cargo_features
|
||||
if command -q jq
|
||||
cargo read-manifest | jq -r '.features | keys | .[]' | __fish_concat_completions
|
||||
__fish_cargo read-manifest | jq -r '.features | keys | .[]' | __fish_concat_completions
|
||||
else if set -l python (__fish_anypython)
|
||||
cargo read-manifest | command $python -Sc "import sys, json"\n"print(*json.load(sys.stdin)['features'].keys(), sep='\n')" | __fish_concat_completions
|
||||
__fish_cargo read-manifest | command $python -Sc "import sys, json"\n"print(*json.load(sys.stdin)['features'].keys(), sep='\n')" | __fish_concat_completions
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_cargo_packages
|
||||
if command -q jq
|
||||
cargo metadata --no-deps --format-version 1 | jq -r '.packages | .[] | .name' | __fish_concat_completions
|
||||
__fish_cargo metadata --no-deps --format-version 1 | jq -r '.packages | .[] | .name' | __fish_concat_completions
|
||||
else if set -l python (__fish_anypython)
|
||||
cargo metadata --no-deps --format-version 1 |
|
||||
command $python -Sc "import sys, json"\n"print(*[x['name'] for x in json.load(sys.stdin)['packages']], sep='\n')"
|
||||
__fish_cargo metadata --no-deps --format-version 1 |
|
||||
command $python -Sc "import sys, json"\n"print(*[x['name'] for x in json.load(sys.stdin)['packages']], sep='\n')"
|
||||
end
|
||||
end
|
||||
complete -c cargo -n '__fish_seen_subcommand_from run test build debug check clippy' -s p -l package \
|
||||
@@ -60,7 +65,7 @@ complete -c cargo -n '__fish_seen_subcommand_from run test build debug check cli
|
||||
complete -c cargo -n __fish_use_subcommand -l explain -d 'Run `rustc --explain CODE`'
|
||||
complete -c cargo -n __fish_use_subcommand -l color -d 'Coloring: auto, always, never'
|
||||
complete -c cargo -n __fish_use_subcommand -l config -d 'Override a configuration value (unstable)'
|
||||
complete -c cargo -n __fish_use_subcommand -s Z -d 'Unstable (nightly-only) flags to Cargo, see \'cargo -Z help\' for details' -xa '(cargo -Z help | string replace -rf \'^\s*-Z (\S+)\s+(.*)\' \'$1\t$2\')'
|
||||
complete -c cargo -n __fish_use_subcommand -s Z -d 'Unstable (nightly-only) flags to Cargo, see \'cargo -Z help\' for details' -xa '(__fish_cargo -Z help | string replace -rf \'^\s*-Z (\S+)\s+(.*)\' \'$1\t$2\')'
|
||||
complete -c cargo -n __fish_use_subcommand -s V -l version -d 'Print version info and exit'
|
||||
complete -c cargo -n __fish_use_subcommand -l list -d 'List installed commands'
|
||||
complete -c cargo -n __fish_use_subcommand -s v -l verbose -d 'Use verbose output (-vv very verbose/build.rs output)'
|
||||
@@ -718,7 +723,7 @@ complete -c cargo -n "__fish_seen_subcommand_from tree" -s v -l verbose -d 'Use
|
||||
complete -c cargo -n "__fish_seen_subcommand_from tree" -l frozen -d 'Require Cargo.lock and cache are up to date'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from tree" -l locked -d 'Require Cargo.lock is up to date'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from tree" -l offline -d 'Run without accessing the network'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from uninstall" -fa '(cargo install --list | string replace -rf "(\S+) (.*):" \'$1\t$2\')'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from uninstall" -fa '(__fish_cargo install --list | string replace -rf "(\S+) (.*):" \'$1\t$2\')'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from uninstall" -s p -l package -d 'Package to uninstall'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from uninstall" -l bin -d 'Only uninstall the binary NAME'
|
||||
complete -c cargo -n "__fish_seen_subcommand_from uninstall" -l root -d 'Directory to uninstall packages from'
|
||||
@@ -841,8 +846,8 @@ if command -q cargo-asm
|
||||
# Warning: this will build the project and can take time! We make sure to only call it if it's not a switch so completions
|
||||
# for --foo will always be fast.
|
||||
if command -q timeout
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(timeout 1 cargo asm)"
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(timeout 1 __fish_cargo asm)"
|
||||
else
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(cargo asm)"
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(__fish_cargo asm)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,6 @@ complete -c commandline -s b -l current-buffer -d "Select entire command line (d
|
||||
complete -c commandline -s c -l cut-at-cursor -d "Only return that part of the command line before the cursor"
|
||||
complete -c commandline -s f -l function -d "Inject readline functions to reader"
|
||||
complete -c commandline -s x -l tokens-expanded -d "Print a list of expanded tokens"
|
||||
complete -c commandline -l tokens-raw -d "Print a list of raw tokens"
|
||||
|
||||
complete -c commandline -s I -l input -d "Specify command to operate on"
|
||||
complete -c commandline -s C -l cursor -d "Set/get cursor position, not buffer contents"
|
||||
|
||||
@@ -16,7 +16,8 @@ function __fish_git
|
||||
end
|
||||
end
|
||||
# Using 'command git' to avoid interactions for aliases from git to (e.g.) hub
|
||||
command git $global_args $saved_args 2>/dev/null
|
||||
set -l git $__fish_git_timeout git
|
||||
command $git $global_args $saved_args 2>/dev/null
|
||||
end
|
||||
|
||||
# Print an optspec for argparse to handle git's options that are independent of any subcommand.
|
||||
@@ -52,8 +53,8 @@ end
|
||||
function __fish_git_branches
|
||||
# This is much faster than using `git branch` and avoids dealing with localized "detached HEAD" messages.
|
||||
# We intentionally only sort local branches by recency. See discussion in #9248.
|
||||
__fish_git for-each-ref --format='%(refname:strip=2)%09Local Branch' --sort=-committerdate refs/heads/ 2>/dev/null
|
||||
__fish_git for-each-ref --format='%(refname:strip=2)%09Remote Branch' refs/remotes/ 2>/dev/null
|
||||
__fish_git_local_branches
|
||||
__fish_git_remote_branches
|
||||
end
|
||||
|
||||
function __fish_git_submodules
|
||||
@@ -65,14 +66,18 @@ function __fish_git_local_branches
|
||||
__fish_git for-each-ref --format='%(refname:strip=2)%09Local Branch' --sort=-committerdate refs/heads/ 2>/dev/null
|
||||
end
|
||||
|
||||
function __fish_git_remote_branches
|
||||
__fish_git_timeout=(string split ' ' -- (command -v timeout)' 0.200') __fish_git for-each-ref --format='%(refname:strip=2)%09Remote Branch' refs/remotes/ 2>/dev/null
|
||||
end
|
||||
|
||||
function __fish_git_unique_remote_branches
|
||||
# `git checkout` accepts remote branches without the remote part
|
||||
# if they are unambiguous.
|
||||
# E.g. if only alice has a "frobulate" branch
|
||||
# `git checkout frobulate` is equivalent to `git checkout -b frobulate --track alice/frobulate`.
|
||||
__fish_git for-each-ref --format="%(refname:strip=3)" \
|
||||
__fish_git_timeout=(string split ' ' -- (command -v timeout)' 0.200') __fish_git for-each-ref --format="%(refname:strip=3)" \
|
||||
--sort="refname:strip=3" \
|
||||
"refs/remotes/*/$match*" "refs/remotes/*/*/**" 2>/dev/null | uniq -u
|
||||
refs/remotes/ 2>/dev/null | uniq -u
|
||||
end
|
||||
|
||||
function __fish_git_tags
|
||||
@@ -82,7 +87,8 @@ end
|
||||
function __fish_git_heads
|
||||
set -l gitdir (__fish_git rev-parse --git-dir 2>/dev/null)
|
||||
or return # No git dir, no need to even test.
|
||||
for head in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD
|
||||
for head in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD REVERT_HEAD \
|
||||
CHERRY_PICK_HEAD BISECT_HEAD AUTO_MERGE
|
||||
if test -f $gitdir/$head
|
||||
echo $head
|
||||
end
|
||||
@@ -96,7 +102,35 @@ function __fish_git_refs
|
||||
end
|
||||
|
||||
function __fish_git_remotes
|
||||
__fish_git remote 2>/dev/null
|
||||
# Example of output parsed:
|
||||
# "remote.upstream.url git@github.com:fish-shell/fish-shell.git" -> "upstream\tgit@github.com:fish-shell/fish-shell.git"
|
||||
__fish_git config --get-regexp 'remote\.[a-z]+\.url' | string replace -rf 'remote\.(.*)\.url (.*)' '$1\t$2'
|
||||
end
|
||||
|
||||
set -g __fish_git_extra_recent_commits false
|
||||
set -g __fish_git_unqualified_unique_remote_branches false
|
||||
set -g __fish_git_filter_non_pushable 'string join \n'
|
||||
|
||||
function __fish_git_add_revision_completion
|
||||
set -l c complete -f -c git $argv -n 'not contains -- -- (commandline -xpc)' -ka
|
||||
# The following dynamic, order-preserved (-k) completions will be shown in reverse order (see #9221)
|
||||
$c "(__fish_git_recent_commits \$(
|
||||
if $__fish_git_extra_recent_commits
|
||||
begin
|
||||
echo HEAD
|
||||
git for-each-ref --sort=-committerdate --format='%(refname)' 2>/dev/null \
|
||||
refs/tags refs/heads
|
||||
end | string join ' '
|
||||
end
|
||||
) | $__fish_git_filter_non_pushable)"
|
||||
$c "(__fish_git_tags)" -d Tag
|
||||
$c "(__fish_git_heads | $__fish_git_filter_non_pushable)" -d Head
|
||||
$c "(__fish_git_remotes | $__fish_git_filter_non_pushable)" -d 'Remote alias'
|
||||
$c "(__fish_git_remote_branches | $__fish_git_filter_non_pushable)"
|
||||
if $__fish_git_unqualified_unique_remote_branches
|
||||
$c "(__fish_git_unique_remote_branches | $__fish_git_filter_non_pushable)" -d 'Unique Remote Branch'
|
||||
end
|
||||
$c "(__fish_git_local_branches)" -d 'Local Branch'
|
||||
end
|
||||
|
||||
function __fish_git_files
|
||||
@@ -647,6 +681,7 @@ function __fish_git_aliased_command
|
||||
end
|
||||
end
|
||||
|
||||
set -g __fish_git_aliases
|
||||
git config -z --get-regexp 'alias\..*' | while read -lz alias cmdline
|
||||
set -l command (__fish_git_aliased_command $cmdline)
|
||||
string match -q --regex '\w+' -- $command; or continue
|
||||
@@ -784,7 +819,7 @@ end
|
||||
|
||||
# Suggest branches for the specified remote - returns 1 if no known remote is specified
|
||||
function __fish_git_branch_for_remote
|
||||
set -l remotes (__fish_git_remotes)
|
||||
set -l remotes (__fish_git remote 2>/dev/null)
|
||||
set -l remote
|
||||
set -l cmd (commandline -xpc)
|
||||
for r in $remotes
|
||||
@@ -1034,7 +1069,7 @@ complete -f -c git -n '__fish_git_using_command fetch' -l shallow-since -d 'Deep
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l shallow-exclude -d 'Deepen history of shallow clone, excluding rev'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l unshallow -d 'Convert to a complete repository'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l refetch -d 'Re-fetch without negotiating common commits'
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l negotiation-tip -d 'Only report commits reachable from these tips' -kxa '(__fish_git_commits; __fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command fetch' -l negotiation-tip -d 'Only report commits reachable from these tips' -x
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l negotiate-only -d "Don't fetch, only show commits in common with the server"
|
||||
complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
|
||||
|
||||
@@ -1087,10 +1122,8 @@ complete -f -c git -n "__fish_git_using_command remote" -n "__fish_seen_subcomma
|
||||
|
||||
### show
|
||||
complete -f -c git -n __fish_git_needs_command -a show -d 'Show the last commit of a branch'
|
||||
complete -f -c git -n '__fish_git_using_command show' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_branches)'
|
||||
complete -f -c git -n '__fish_git_using_command show' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_tags)' -d Tag
|
||||
complete -f -c git -n '__fish_git_using_command show' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_commits)'
|
||||
complete -f -c git -n '__fish_git_using_command show' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_complete_stashes)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command show'
|
||||
complete -f -c git -n __fish_git_needs_rev_files -n 'not contains -- -- (commandline -xpc)' -xa '(__fish_git_complete_rev_files)'
|
||||
complete -F -c git -n '__fish_git_using_command show' -n 'contains -- -- (commandline -xpc)'
|
||||
complete -f -c git -n '__fish_git_using_command show' -l format -d 'Pretty-print the contents of the commit logs in a given format' -a '(__fish_git_show_opt format)'
|
||||
@@ -1179,13 +1212,12 @@ complete -c git -n '__fish_git_using_command am' -l show-current-patch -a 'diff
|
||||
### checkout
|
||||
complete -F -c git -n '__fish_git_using_command checkout' -n 'contains -- -- (commandline -xpc)'
|
||||
complete -f -c git -n __fish_git_needs_command -a checkout -d 'Checkout and switch to a branch'
|
||||
begin
|
||||
set -lx __fish_git_extra_recent_commits true
|
||||
set -lx __fish_git_unqualified_unique_remote_branches true
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command checkout'
|
||||
end
|
||||
|
||||
# The following dynamic, order-preserved (-k) completions will be shown in reverse order (see #9221)
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_recent_commits --all)'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_tags)' -d Tag
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_heads)' -d Head
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_unique_remote_branches)' -d 'Unique Remote Branch'
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_branches)'
|
||||
# In the presence of changed files, `git checkout ...` assumes highest likelihood is intent to restore so this comes last (aka shown first).
|
||||
complete -f -c git -n '__fish_git_using_command checkout' -ka '(__fish_git_files modified deleted modified-staged-deleted)'
|
||||
|
||||
@@ -1422,9 +1454,7 @@ complete -x -c git -n '__fish_git_using_command daemon' -l access-hook -d 'Hook
|
||||
|
||||
### describe
|
||||
complete -c git -n __fish_git_needs_command -a describe -d 'Give an object a human readable name'
|
||||
complete -f -c git -n '__fish_git_using_command describe' -ka '(__fish_git_tags)' -d Tag
|
||||
complete -f -c git -n '__fish_git_using_command describe' -ka '(__fish_git_branches)'
|
||||
complete -f -c git -n '__fish_git_using_command describe' -ka '(__fish_git_heads)' -d Head
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command describe'
|
||||
complete -f -c git -n '__fish_git_using_command describe' -l dirty -d 'Describe the state of the working tree, append dirty if there are local changes'
|
||||
complete -f -c git -n '__fish_git_using_command describe' -l broken -d 'Describe the state of the working tree, append -broken instead of erroring'
|
||||
complete -f -c git -n '__fish_git_using_command describe' -l all -d 'Use all tags, not just annotated'
|
||||
@@ -1444,7 +1474,10 @@ complete -f -c git -n '__fish_git_using_command describe' -l first-parent -d 'Fo
|
||||
complete -c git -n __fish_git_needs_command -a diff -d 'Show changes between commits and working tree'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_ranges)'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_complete_stashes)'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_recent_commits --all)'
|
||||
begin
|
||||
set -lx __fish_git_extra_recent_commits true
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command diff'
|
||||
end
|
||||
complete -c git -n '__fish_git_using_command diff' -l cached -d 'Show diff of changes in the index'
|
||||
complete -c git -n '__fish_git_using_command diff' -l staged -d 'Show diff of changes in the index'
|
||||
complete -c git -n '__fish_git_using_command diff' -l no-index -d 'Compare two paths on the filesystem'
|
||||
@@ -1775,7 +1808,7 @@ complete -f -c git -n '__fish_git_using_command maintenance' -l schedule -d 'Run
|
||||
|
||||
### merge
|
||||
complete -f -c git -n __fish_git_needs_command -a merge -d 'Join multiple development histories'
|
||||
complete -f -c git -n '__fish_git_using_command merge' -ka '(__fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command merge'
|
||||
complete -f -c git -n '__fish_git_using_command merge' -l commit -d "Autocommit the merge"
|
||||
complete -f -c git -n '__fish_git_using_command merge' -l no-commit -d "Don't autocommit the merge"
|
||||
complete -f -c git -n '__fish_git_using_command merge' -s e -l edit -d 'Edit auto-generated merge message'
|
||||
@@ -1811,7 +1844,7 @@ complete -f -c git -n '__fish_git_using_command merge' -l no-autostash -d 'Do no
|
||||
|
||||
### merge-base
|
||||
complete -f -c git -n __fish_git_needs_command -a merge-base -d 'Find a common ancestor for a merge'
|
||||
complete -f -c git -n '__fish_git_using_command merge-base' -ka '(__fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command merge-base'
|
||||
complete -f -c git -n '__fish_git_using_command merge-base' -s a -l all -d 'Output all merge bases for the commits, instead of just one'
|
||||
complete -f -c git -n '__fish_git_using_command merge-base' -l octopus -d 'Compute the best common ancestors of all supplied commits'
|
||||
complete -f -c git -n '__fish_git_using_command merge-base' -l independent -d 'Print a minimal subset of the supplied commits with the same ancestors'
|
||||
@@ -1929,9 +1962,11 @@ complete -f -c git -n '__fish_git_using_command range-diff' -l no-dual-color -d
|
||||
### push
|
||||
complete -f -c git -n __fish_git_needs_command -a push -d 'Push changes elsewhere'
|
||||
complete -f -c git -n '__fish_git_using_command push' -n 'not __fish_git_branch_for_remote' -a '(__fish_git_remotes)' -d 'Remote alias'
|
||||
complete -f -c git -n '__fish_git_using_command push' -n __fish_git_branch_for_remote -ka '(__fish_git_tags)' -d Tag
|
||||
complete -f -c git -n '__fish_git_using_command push' -n __fish_git_branch_for_remote -ka '(__fish_git_branches)'
|
||||
complete -f -c git -n '__fish_git_using_command push' -n __fish_git_branch_for_remote -ka '(__fish_git_heads)'
|
||||
begin
|
||||
# TODO
|
||||
set -lx __fish_git_filter_non_pushable 'string replace -r "(\t.*)?\$" ":\$1"'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command push' -n __fish_git_branch_for_remote
|
||||
end
|
||||
# The "refspec" here is an optional "+" to signify a force-push
|
||||
complete -f -c git -n '__fish_git_using_command push' -n __fish_git_branch_for_remote -n 'string match -q "+*" -- (commandline -ct)' -ka '+(__fish_git_branches | string replace -r \t".*" "")' -d 'Force-push branch'
|
||||
# git push REMOTE :BRANCH deletes BRANCH on remote REMOTE
|
||||
@@ -1958,11 +1993,7 @@ complete -f -c git -n '__fish_git_using_command push' -l progress -d 'Force prog
|
||||
|
||||
### 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_using_command rebase' -a '(__fish_git_remotes)' -d 'Remote alias'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -ka '(__fish_git_branches)'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -a '(__fish_git_heads)' -d Head
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -ka '(__fish_git_tags)' -d Tag -k
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -a '(__fish_git_recent_commits)' -k
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command rebase'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l continue -d 'Restart the rebasing process'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l abort -d 'Abort the rebase operation'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l edit-todo -d 'Edit the todo list'
|
||||
@@ -1987,7 +2018,7 @@ complete -f -c git -n '__fish_git_using_command rebase' -l no-autosquash -d 'No
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -l autostash -d 'Before starting rebase, stash local changes, and apply stash when done'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -l no-autostash -d 'Do not stash local changes before starting rebase'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -l no-ff -d 'No fast-forward'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -l onto -d 'Rebase current branch onto given upstream or newbase' -ka '(__fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command rebase' -l onto -d 'Rebase current branch onto given upstream or newbase' -r
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -l update-refs -d 'Update any branches that point to commits being rebased'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -l no-update-refs -d 'Don\'t update any branches that point to commits being rebased'
|
||||
# This actually takes script for $SHELL, but completing that is... complicated.
|
||||
@@ -2006,7 +2037,7 @@ complete -c git -n __fish_git_needs_command -a reset -d 'Reset current HEAD to t
|
||||
complete -f -c git -n '__fish_git_using_command reset' -l hard -d 'Reset the index and the working tree'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -l soft -d 'Reset head without touching the index or the working tree'
|
||||
complete -f -c git -n '__fish_git_using_command reset' -l mixed -d 'The default: reset the index but not the working tree'
|
||||
complete -c git -n '__fish_git_using_command reset' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command reset'
|
||||
# reset can either undo changes to versioned modified files,
|
||||
# or remove files from the staging area.
|
||||
# Deleted files seem to need a "--" separator.
|
||||
@@ -2038,7 +2069,10 @@ complete -f -c git -n '__fish_git_using_command switch' -ka '(__fish_git_unique_
|
||||
complete -f -c git -n '__fish_git_using_command switch' -ka '(__fish_git_branches)'
|
||||
complete -f -c git -n '__fish_git_using_command switch' -s c -l create -d 'Create a new branch'
|
||||
complete -f -c git -n '__fish_git_using_command switch' -s C -l force-create -d 'Force create a new branch'
|
||||
complete -f -c git -n '__fish_git_using_command switch' -s d -l detach -rka '(__fish_git_recent_commits --all)'
|
||||
begin
|
||||
set -lx __fish_git_extra_recent_commits true
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command switch' -s d -l detach -r
|
||||
end
|
||||
complete -f -c git -n '__fish_git_using_command switch' -s d -l detach -d 'Switch to a commit for inspection and discardable experiment' -rka '(__fish_git_refs)'
|
||||
complete -f -c git -n '__fish_git_using_command switch' -l guess -d 'Guess branch name from remote branch (default)'
|
||||
complete -f -c git -n '__fish_git_using_command switch' -l no-guess -d 'Do not guess branch name from remote branch'
|
||||
@@ -2063,9 +2097,7 @@ complete -f -c git -n __fish_git_needs_command -a rev-list -d 'List commits in c
|
||||
|
||||
### rev-parse
|
||||
complete -f -c git -n __fish_git_needs_command -a rev-parse -d 'Parse revision names or give repo information'
|
||||
complete -f -c git -n '__fish_git_using_command rev-parse' -ka '(__fish_git_branches)'
|
||||
complete -f -c git -n '__fish_git_using_command rev-parse' -a '(__fish_git_heads)' -d Head
|
||||
complete -c git -n '__fish_git_using_command rev-parse' -ka '(__fish_git_tags)' -d Tag
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command rev-parse'
|
||||
complete -c git -n '__fish_git_using_command rev-parse' -l abbrev-ref -d 'Output non-ambiguous short object names'
|
||||
|
||||
### revert
|
||||
@@ -2115,7 +2147,7 @@ complete -f -c git -n '__fish_git_using_command stripspace' -s c -l comment-line
|
||||
|
||||
### tag
|
||||
complete -f -c git -n __fish_git_needs_command -a tag -d 'Create, list, delete or verify a tag object signed with GPG'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -n '__fish_not_contain_opt -s d' -n '__fish_not_contain_opt -s v' -n 'test (count (commandline -xpc | string match -r -v \'^-\')) -eq 3' -ka '(__fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command tag' -n '__fish_not_contain_opt -s d' -n '__fish_not_contain_opt -s v' -n 'test (count (commandline -xpc | string match -r -v \'^-\')) -eq 3'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -s a -l annotate -d 'Make an unsigned, annotated tag object'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -s s -l sign -d 'Make a GPG-signed tag'
|
||||
complete -f -c git -n '__fish_git_using_command tag' -s d -l delete -d 'Remove a tag'
|
||||
@@ -2176,11 +2208,10 @@ complete -f -c git -n "__fish_git_using_command worktree" -n "not __fish_seen_su
|
||||
complete -f -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add move remove' -s f -l force -d 'Override safeguards'
|
||||
|
||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add'
|
||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_branches)'
|
||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_heads)' -d Head
|
||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_tags)' -d Tag
|
||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_unique_remote_branches)' -d 'Unique Remote Branch'
|
||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_local_branches)'
|
||||
begin
|
||||
set -lx __fish_git_unqualified_unique_remote_branches true
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add'
|
||||
end
|
||||
complete -x -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -s b -d 'Create a new branch'
|
||||
complete -x -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -s B -d 'Create a new branch even if it already exists'
|
||||
complete -f -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -l detach -d 'Detach HEAD in the new working tree'
|
||||
@@ -2216,11 +2247,7 @@ complete -f -c git -n '__fish_git_using_command stash' -n __fish_git_stash_not_u
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n __fish_git_stash_not_using_subcommand -a branch -d 'Create a new branch from a stash'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n __fish_git_stash_not_using_subcommand -a push -d 'Create a new stash with given files'
|
||||
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command apply' -ka '(__fish_git_complete_stashes)'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command branch' -ka '(__fish_git_complete_stashes)'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command drop' -ka '(__fish_git_complete_stashes)'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command pop' -ka '(__fish_git_complete_stashes)'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command show' -ka '(__fish_git_complete_stashes)'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command apply branch drop pop show' -ka '(__fish_git_complete_stashes)'
|
||||
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command push' -a '(__fish_git_files modified deleted modified-staged-deleted)'
|
||||
complete -f -c git -n '__fish_git_using_command stash' -n '__fish_git_stash_using_command push' -s a -l all -d 'Stash ignored and untracked files'
|
||||
@@ -2237,7 +2264,7 @@ complete -f -c git -n __fish_git_needs_command -a config -d 'Set and read git co
|
||||
|
||||
### 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_using_command format-patch' -ka '(__fish_git_branches)'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command format-patch'
|
||||
complete -c git -n '__fish_git_using_command format-patch' -s o -l output-directory -xa '(__fish_complete_directories)'
|
||||
complete -f -c git -n '__fish_git_using_command format-patch' -s p -l no-stat -d "Generate plain patches without diffstat"
|
||||
complete -f -c git -n '__fish_git_using_command format-patch' -s s -l no-patch -d "Suppress diff output"
|
||||
@@ -2599,3 +2626,8 @@ for file in (path filter -xZ $PATH/git-* | path basename)
|
||||
complete -c git -f -n "__fish_git_using_command $cmd" -a "(__fish_git_complete_custom_command $cmd)"
|
||||
set -a __fish_git_custom_commands_completion $file
|
||||
end
|
||||
|
||||
functions --erase __fish_git_add_revision_completion
|
||||
set -eg __fish_git_extra_recent_commits
|
||||
set -eg __fish_git_unqualified_unique_remote_branches
|
||||
set -eg __fish_git_filter_non_pushable
|
||||
|
||||
@@ -260,10 +260,12 @@ function __fish_complete_ip
|
||||
case address
|
||||
# We're still _on_ the second word, which is the subcommand
|
||||
if not set -q cmd[3]
|
||||
printf '%s\t%s\n' add "Add new protocol address" \
|
||||
printf '%s\t%s\n' \
|
||||
add "Add new protocol address" \
|
||||
delete "Delete protocol address" \
|
||||
show "Look at protocol addresses" \
|
||||
flush "Flush protocol addresses"
|
||||
flush "Flush protocol addresses" \
|
||||
help "Display help"
|
||||
else
|
||||
switch $cmd[2]
|
||||
# Change and replace are undocumented (apart from mentions in the BNF)
|
||||
@@ -339,7 +341,8 @@ function __fish_complete_ip
|
||||
end
|
||||
case link
|
||||
if not set -q cmd[3]
|
||||
printf '%s\t%s\n' add "Add virtual link" \
|
||||
printf '%s\t%s\n' \
|
||||
add "Add virtual link" \
|
||||
delete "Delete virtual link" \
|
||||
set "Change device attributes" \
|
||||
show "Display device attributes" \
|
||||
@@ -430,18 +433,20 @@ function __fish_complete_ip
|
||||
end
|
||||
case show
|
||||
case help
|
||||
__fish_ip_types
|
||||
end
|
||||
end
|
||||
case neighbour
|
||||
if not set -q cmd[3]
|
||||
printf '%s\t%s\n' help "Show help" \
|
||||
printf '%s\t%s\n' \
|
||||
add "Add new neighbour entry" \
|
||||
delete "Delete neighbour entry" \
|
||||
change "Change neighbour entry" \
|
||||
replace "Add or change neighbour entry" \
|
||||
show "List neighbour entries" \
|
||||
flush "Flush neighbour entries" \
|
||||
get "Lookup neighbour entry"
|
||||
get "Lookup neighbour entry" \
|
||||
help "Display help"
|
||||
else
|
||||
switch $cmd[2]
|
||||
case add del delete change replace
|
||||
@@ -505,7 +510,8 @@ function __fish_complete_ip
|
||||
get "Get a single route" \
|
||||
save "Save routing table to stdout" \
|
||||
showdump "Show saved routing table from stdin" \
|
||||
restore "Restore routing table from stdin"
|
||||
restore "Restore routing table from stdin" \
|
||||
help "Display help"
|
||||
else
|
||||
# TODO: switch on $cmd[2] and complete subcommand specific arguments
|
||||
# for now just complete most useful arguments for the last token
|
||||
@@ -524,7 +530,8 @@ function __fish_complete_ip
|
||||
flush "Flush rules" \
|
||||
show "List rules" \
|
||||
save "Save rules to stdout" \
|
||||
restore "Restore rules from stdin"
|
||||
restore "Restore rules from stdin" \
|
||||
help "Display help"
|
||||
else
|
||||
# TODO: switch on $cmd[2] and complete subcommand specific arguments
|
||||
# for now just complete most useful arguments for the last token
|
||||
@@ -579,6 +586,27 @@ function __fish_complete_ip
|
||||
end
|
||||
end
|
||||
end
|
||||
case tuntap
|
||||
if not set -q cmd[3]
|
||||
printf '%s\t%s\n' \
|
||||
add "Add a new tun or tap device" \
|
||||
del "Delete a tun or tap device" \
|
||||
show "Show tun and tap devices" \
|
||||
help "Display help"
|
||||
else
|
||||
switch $cmd[-2]
|
||||
case dev
|
||||
__fish_ip_device
|
||||
case mode
|
||||
printf '%s\n' tun tap
|
||||
case user
|
||||
__fish_complete_users
|
||||
case group
|
||||
__fish_complete_groups
|
||||
case '*'
|
||||
printf '%s\n' dev mode user group one_queue pi vnet_hdr multi_queue name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#Keybase 5.6.1
|
||||
# Keybase 6.4.0
|
||||
|
||||
function __fish_keybase_line_ends_with
|
||||
set -l line (commandline -pxc | string match -v -r '^-')
|
||||
@@ -9,123 +9,132 @@ function __fish_keybase_line_ends_with
|
||||
end
|
||||
end
|
||||
|
||||
#variables
|
||||
# variables
|
||||
set -l ends __fish_keybase_line_ends_with
|
||||
set -l seen __fish_seen_subcommand_from
|
||||
#L1
|
||||
set -l keybase account blocks bot chat config ctl currency decrypt deprovision device encrypt follow fs git h help id kvstore list-followers list-following log login logout oneshot paperkey passphrase pgp ping prove rekey selfprovision service sign signup sigs status team track unfollow untrack update verify version wallet whoami
|
||||
#L2
|
||||
set -l keybase_account contact-settings delete email h help lockdown recover-username upload-avatar
|
||||
set -l keybase_blocks h help list-users list-teams
|
||||
# 1
|
||||
set -l keybase account apicall audit base62 blocks bot btc ca chat config ctl currency db decrypt deprovision device dir dismiss dismiss-category dump-keyfamily dump-push-notifications encrypt fnmr follow fs git h help home id interesting-people kvstore list-followers list-following log login logout network-stats oneshot paperkey passphrase pgp ping pprof prove push rekey riit script selfprovision service sign signup sigs status team track unfollow unlock untrack upak update upload-avatar verify version wallet web-auth-token whoami wot
|
||||
# 2
|
||||
set -l keybase_account contact-settings delete email h help lockdown phonenumber recover-username reset-cancel upload-avatar
|
||||
set -l keybase_audit box h help
|
||||
set -l keybase_base62 decode encode h help
|
||||
set -l keybase_blocks h help list-teams list-users
|
||||
set -l keybase_bot h help signup token
|
||||
set -l keybase_chat add-bot-member add-to-channel api api-listen bot-member-settings clear-commands conv-info create-channel default-channels delete-channel delete-history download edit-bot-member featured-bots h help hide join-channel leave-channel list list-channels list-members list-unread ls lsur min-writer-role mute notification-settings read readd-member remove-bot-member rename-channel report retention-policy search search-bots search-regexp send upload
|
||||
set -l keybase_chat add-bot-member add-to-channel api api-listen archive archive-delete archive-list archive-pause archive-resume bot-member-settings clear-commands conv-info create-channel default-channels delete-channel delete-history download edit-bot-member emoji-add emoji-addalias emoji-list emoji-remove featured-bots forward-message h help hide join-channel leave-channel list list-channels list-members list-unread ls lsur mark-as-read min-writer-role mute notification-settings read readd-member remove-bot-member remove-from-channel rename-channel report retention-policy search search-bots search-regexp send upload
|
||||
set -l keybase_config get h help info set
|
||||
set -l keybase_ctl app-exit autostart h help init log-rotate redirector reload restart start stop wait watchdog watchdog2
|
||||
set -l keybase_ctl app-exit autostart h help init log-rotate redirector reload restart start stop wait wants-systemd watchdog watchdog2
|
||||
set -l keybase_currency add h help
|
||||
set -l keybase_db clean delete get h help keys-with-prefix nuke put
|
||||
set -l keybase_device add h help list remove
|
||||
set -l keybase_fs clear-conflicts cp debug finish-resolving-conflicts get-status h help history kill ln ls mkdir mv ps quota read recover reset rm set-debug-level stat sync uploads write
|
||||
set -l keybase_git create delete gc h help lfs-config list settings
|
||||
set -l keybase_fs archive cancel-uploads clear-conflicts cp debug finish-resolving-conflicts get-status h help history index-progress kill ln ls mkdir mv ps quota read recover reset reset-index rm search set-debug-level stat sync uploads write
|
||||
set -l keybase_git archive create delete gc h help lfs-config list settings
|
||||
set -l keybase_help advanced gpg keyring tor
|
||||
set -l keybase_kvstore api h help
|
||||
set -l keybase_log h help send
|
||||
set -l keybase_log h help profile send
|
||||
set -l keybase_passphrase change check h help recover remember set
|
||||
set -l keybase_pgp decrypt drop encrypt export gen h help import list pull pull-private purge push-private select sign update verify
|
||||
set -l keybase_pprof cpu h heap help trace
|
||||
set -l keybase_rekey h help paper status
|
||||
set -l keybase_sigs h help list revoke
|
||||
set -l keybase_team accept-invite add-member add-members-bulk api bot-settings create delete edit-member h help ignore-request leave list-members list-memberships list-requests remove-member rename request-access search settings show-tree
|
||||
set -l keybase_team accept-invite add-member add-members-bulk api bot-settings create delete edit-member ftl generate-invite-token generate-invitelink h help ignore-request leave list-members list-memberships list-requests profile-load remove-member rename request-access search settings show-tree
|
||||
set -l keybase_update check check-in-use notify
|
||||
set -l keybase_wallet accounts add-trustline api asset-search balances cancel cancel-request change-trustline-limit delete-trustline detail details export get-inflation get-started h help history import list lookup merge popular-assets reclaim rename request send send-path-payment set-currency set-inflation set-primary sign
|
||||
#L3
|
||||
set -l keybase_wallet accounts balances detail details export h help list
|
||||
set -l keybase_wot accept h help list reject vouch
|
||||
# 3
|
||||
set -l keybase_account_email add delete edit h help list send-verification-email set-primary set-visibility
|
||||
set -l keybase_account_phonenumber add delete edit h help list set-visibility verify
|
||||
set -l keybase_bot_token create delete h help list
|
||||
set -l keybase_fs_archive cancel check check dismiss h help start status
|
||||
set -l keybase_fs_debug deobfuscate dump h help obfuscate
|
||||
set -l keybase_fs_sync disable enable h help show
|
||||
#...
|
||||
|
||||
#global options
|
||||
complete -c keybase -f -n "$ends keybase" -l api-dump-unsafe
|
||||
complete -c keybase -f -n "$ends keybase" -l api-timeout
|
||||
complete -c keybase -f -n "$ends keybase" -l api-uri-path-prefix
|
||||
complete -c keybase -f -n "$ends keybase" -l app-start-mode
|
||||
complete -c keybase -f -n "$ends keybase" -l auto-fork
|
||||
complete -c keybase -f -n "$ends keybase" -l bg-identifier-disabled
|
||||
complete -c keybase -f -n "$ends keybase" -l chat-db
|
||||
complete -c keybase -f -n "$ends keybase" -l code-signing-kids
|
||||
complete -c keybase -f -n "$ends keybase" -l config-file -s c
|
||||
complete -c keybase -f -n "$ends keybase" -l db
|
||||
complete -c keybase -f -n "$ends keybase" -l debug -s d
|
||||
complete -c keybase -f -n "$ends keybase" -l debug-journeycard
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-bg-conv-loader
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-cert-pinning
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-merkle-auditor
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-search-indexer
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-team-auditor
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-team-box-auditor
|
||||
complete -c keybase -f -n "$ends keybase" -l display-raw-untrusted-output
|
||||
complete -c keybase -f -n "$ends keybase" -l ek-log-file
|
||||
complete -c keybase -f -n "$ends keybase" -l enable-bot-lite-mode
|
||||
complete -c keybase -f -n "$ends keybase" -l extra-net-logging
|
||||
complete -c keybase -f -n "$ends keybase" -l features
|
||||
complete -c keybase -f -n "$ends keybase" -l force-linux-keyring
|
||||
complete -c keybase -f -n "$ends keybase" -l generate-bash-completion
|
||||
complete -c keybase -f -n "$ends keybase" -l gpg
|
||||
complete -c keybase -f -n "$ends keybase" -l gpg-options
|
||||
complete -c keybase -f -n "$ends keybase" -l gpgdir
|
||||
complete -c keybase -f -n "$ends keybase" -l gui-config-file
|
||||
complete -c keybase -f -n "$ends keybase" -l help -s h
|
||||
complete -c keybase -f -n "$ends keybase" -l home -s H
|
||||
complete -c keybase -f -n "$ends keybase" -l leveldb-num-files
|
||||
complete -c keybase -f -n "$ends keybase" -l local-rpc-debug-unsafe
|
||||
complete -c keybase -f -n "$ends keybase" -l log-file
|
||||
complete -c keybase -f -n "$ends keybase" -l log-format
|
||||
complete -c keybase -f -n "$ends keybase" -l log-prefix
|
||||
complete -c keybase -f -n "$ends keybase" -l merkle-kids
|
||||
complete -c keybase -f -n "$ends keybase" -l no-auto-fork -s F
|
||||
complete -c keybase -f -n "$ends keybase" -l no-debug
|
||||
complete -c keybase -f -n "$ends keybase" -l paramproof-kit
|
||||
complete -c keybase -f -n "$ends keybase" -l pgpdir
|
||||
complete -c keybase -f -n "$ends keybase" -l pid-file
|
||||
complete -c keybase -f -n "$ends keybase" -l pinentry
|
||||
complete -c keybase -f -n "$ends keybase" -l proof-cache-size
|
||||
complete -c keybase -f -n "$ends keybase" -l prove-bypass
|
||||
complete -c keybase -f -n "$ends keybase" -l proxy
|
||||
complete -c keybase -f -n "$ends keybase" -l proxy-type
|
||||
complete -c keybase -f -n "$ends keybase" -l push-disabled
|
||||
complete -c keybase -f -n "$ends keybase" -l push-save-interval
|
||||
complete -c keybase -f -n "$ends keybase" -l push-server-uri
|
||||
complete -c keybase -f -n "$ends keybase" -l pvl-kit
|
||||
complete -c keybase -f -n "$ends keybase" -l read-deleted-sigchain
|
||||
complete -c keybase -f -n "$ends keybase" -l remember-passphrase
|
||||
complete -c keybase -f -n "$ends keybase" -l run-mode
|
||||
complete -c keybase -f -n "$ends keybase" -l scraper-timeout
|
||||
complete -c keybase -f -n "$ends keybase" -l secret-keyring
|
||||
complete -c keybase -f -n "$ends keybase" -l server -s s
|
||||
complete -c keybase -f -n "$ends keybase" -l session-file
|
||||
complete -c keybase -f -n "$ends keybase" -l slow-gregor-conn
|
||||
complete -c keybase -f -n "$ends keybase" -l socket-file
|
||||
complete -c keybase -f -n "$ends keybase" -l standalone
|
||||
complete -c keybase -f -n "$ends keybase" -l timers
|
||||
complete -c keybase -f -n "$ends keybase" -l tor-hidden-address
|
||||
complete -c keybase -f -n "$ends keybase" -l tor-mode
|
||||
complete -c keybase -f -n "$ends keybase" -l tor-proxy
|
||||
complete -c keybase -f -n "$ends keybase" -l updater-config-file
|
||||
complete -c keybase -f -n "$ends keybase" -l use-default-log-file
|
||||
complete -c keybase -f -n "$ends keybase" -l use-root-config-file
|
||||
complete -c keybase -f -n "$ends keybase" -l user-cache-size
|
||||
complete -c keybase -f -n "$ends keybase" -l vdebug
|
||||
complete -c keybase -f -n "$ends keybase" -l version -s v
|
||||
# global options
|
||||
complete -c keybase -f -n "$ends keybase" -l api-dump-unsafe -d "Dump API call internals (may leak secrets)"
|
||||
complete -c keybase -f -n "$ends keybase" -l api-timeout -d "Set the HTTP timeout for API calls to the keybase API server"
|
||||
complete -c keybase -f -n "$ends keybase" -l api-uri-path-prefix -d "Specify an alternate API URI path prefix"
|
||||
complete -c keybase -f -n "$ends keybase" -l app-start-mode -d "Specify 'service' to auto-start UI app, or anything else to disable"
|
||||
complete -c keybase -f -n "$ends keybase" -l auto-fork -d "Enable auto-fork of background service"
|
||||
complete -c keybase -f -n "$ends keybase" -l bg-identifier-disabled -d "Supply to disable the BG identifier loop"
|
||||
complete -c keybase -f -n "$ends keybase" -l chat-db -d "Specify an alternate local Chat DB location"
|
||||
complete -c keybase -f -n "$ends keybase" -l code-signing-kids -d "Set of code signing key IDs (colon-separated)"
|
||||
complete -c keybase -f -n "$ends keybase" -l config-file -s c -d "Specify an (alternate) master config file"
|
||||
complete -c keybase -f -n "$ends keybase" -l db -d "Specify an alternate local DB location"
|
||||
complete -c keybase -f -n "$ends keybase" -l debug -s d -d "Enable debugging mode"
|
||||
complete -c keybase -f -n "$ends keybase" -l debug-journeycard -d "Enable experimental journey cards"
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-bg-conv-loader -d "Disable background conversation loading"
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-cert-pinning -d "WARNING: Do not use unless necessary!"
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-merkle-auditor -d "Disable background probabilistic merkle audit"
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-search-indexer -d "Disable chat search background indexer"
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-team-auditor -d "Disable auditing of teams"
|
||||
complete -c keybase -f -n "$ends keybase" -l disable-team-box-auditor -d "Disable box auditing of teams"
|
||||
complete -c keybase -f -n "$ends keybase" -l display-raw-untrusted-output -d "Display output from users without escaping terminal codes"
|
||||
complete -c keybase -f -n "$ends keybase" -l ek-log-file -d "Specify a log file for the keybase ephemeral key log"
|
||||
complete -c keybase -f -n "$ends keybase" -l enable-bot-lite-mode -d "Enable bot lite mode. Disables non-critical background services"
|
||||
complete -c keybase -f -n "$ends keybase" -l extra-net-logging -d "Do additional debug logging during network requests"
|
||||
complete -c keybase -f -n "$ends keybase" -l features -d "Specify experimental feature flags"
|
||||
complete -c keybase -f -n "$ends keybase" -l force-linux-keyring -d "Require the use of the OS keyring"
|
||||
complete -c keybase -f -n "$ends keybase" -l generate-bash-completion -d ""
|
||||
complete -c keybase -f -n "$ends keybase" -l gpg -d "Path to GPG client (optional for exporting keys)"
|
||||
complete -c keybase -f -n "$ends keybase" -l gpg-options -d "Options to use when calling GPG"
|
||||
complete -c keybase -f -n "$ends keybase" -l gpgdir -d "Specify a PGP directory (default is ~/.gnupg)"
|
||||
complete -c keybase -f -n "$ends keybase" -l gui-config-file -d "Specify a path to the GUI config file"
|
||||
complete -c keybase -f -n "$ends keybase" -l help -s h -d "Show help"
|
||||
complete -c keybase -f -n "$ends keybase" -l home -s H -d "Specify an (alternate) home directory"
|
||||
complete -c keybase -f -n "$ends keybase" -l leveldb-num-files -d "Specify the max number of files LevelDB may open"
|
||||
complete -c keybase -f -n "$ends keybase" -l local-rpc-debug-unsafe -d "Use to debug local RPC (may leak secrets)"
|
||||
complete -c keybase -f -n "$ends keybase" -l log-file -d "Specify a log file for the keybase service"
|
||||
complete -c keybase -f -n "$ends keybase" -l log-format -d "Log format (default, plain, file, fancy)"
|
||||
complete -c keybase -f -n "$ends keybase" -l log-prefix -d "Specify a prefix for a unique log file name"
|
||||
complete -c keybase -f -n "$ends keybase" -l merkle-kids -d "Set of admissible Merkle Tree fingerprints (colon-separated)"
|
||||
complete -c keybase -f -n "$ends keybase" -l no-auto-fork -s F -d "Disable auto-fork of background service"
|
||||
complete -c keybase -f -n "$ends keybase" -l no-debug -d "Suppress debugging mode; takes precedence over --debug"
|
||||
complete -c keybase -f -n "$ends keybase" -l paramproof-kit -d "Specify an alternate local parameterized proof kit file location"
|
||||
complete -c keybase -f -n "$ends keybase" -l pgpdir -d "Specify a PGP directory (default is ~/.gnupg)"
|
||||
complete -c keybase -f -n "$ends keybase" -l pid-file -d "Location of the keybased pid-file (to ensure only one running daemon)"
|
||||
complete -c keybase -f -n "$ends keybase" -l pinentry -d "Specify a path to find a pinentry program"
|
||||
complete -c keybase -f -n "$ends keybase" -l proof-cache-size -d "Number of proof entries to cache"
|
||||
complete -c keybase -f -n "$ends keybase" -l prove-bypass -d "Prove even disabled proof services"
|
||||
complete -c keybase -f -n "$ends keybase" -l proxy -d "Specify a proxy to ship all Web requests over"
|
||||
complete -c keybase -f -n "$ends keybase" -l proxy-type -d "Set the proxy type; One of: socks,http_connect"
|
||||
complete -c keybase -f -n "$ends keybase" -l push-disabled -d "Disable push server connection (which is on by default)"
|
||||
complete -c keybase -f -n "$ends keybase" -l push-save-interval -d "Set the interval between saves of the push cache (in seconds)"
|
||||
complete -c keybase -f -n "$ends keybase" -l push-server-uri -d "Specify a URI for contacting the Keybase push server"
|
||||
complete -c keybase -f -n "$ends keybase" -l pvl-kit -d "Specify an alternate local PVL kit file location"
|
||||
complete -c keybase -f -n "$ends keybase" -l read-deleted-sigchain -d "Allow admins to read deleted sigchains for debugging"
|
||||
complete -c keybase -f -n "$ends keybase" -l remember-passphrase -d "Remember keybase passphrase"
|
||||
complete -c keybase -f -n "$ends keybase" -l run-mode -d "Run mode (devel, staging, prod)"
|
||||
complete -c keybase -f -n "$ends keybase" -l scraper-timeout -d "Set the HTTP timeout for external proof scrapers"
|
||||
complete -c keybase -f -n "$ends keybase" -l secret-keyring -d "Location of the Keybase secret-keyring (P3SKB-encoded)"
|
||||
complete -c keybase -f -n "$ends keybase" -l server -s s -d "Specify server API"
|
||||
complete -c keybase -f -n "$ends keybase" -l session-file -d "Specify an alternate session data file"
|
||||
complete -c keybase -f -n "$ends keybase" -l slow-gregor-conn -d "Slow responses from gregor for testing"
|
||||
complete -c keybase -f -n "$ends keybase" -l socket-file -d "Location of the keybased socket-file"
|
||||
complete -c keybase -f -n "$ends keybase" -l standalone -d "Use the client without any daemon support"
|
||||
complete -c keybase -f -n "$ends keybase" -l timers -d "Specify 'a' for API; 'r' for RPCs; and 'x' for eXternal API calls"
|
||||
complete -c keybase -f -n "$ends keybase" -l tor-hidden-address -d "Set TOR address of keybase server"
|
||||
complete -c keybase -f -n "$ends keybase" -l tor-mode -d "Set TOR mode to be 'leaky', 'none', or 'strict'"
|
||||
complete -c keybase -f -n "$ends keybase" -l tor-proxy -d "Set TOR proxy; when Tor mode is on; defaults to localhost:9050"
|
||||
complete -c keybase -f -n "$ends keybase" -l updater-config-file -d "Specify a path to the updater config file"
|
||||
complete -c keybase -f -n "$ends keybase" -l use-default-log-file -d "Log to the default log file in $XDG_CACHE_HOME, or ~/.cache if unset"
|
||||
complete -c keybase -f -n "$ends keybase" -l use-root-config-file -d "Use the default root config on Linux only"
|
||||
complete -c keybase -f -n "$ends keybase" -l user-cache-size -d "Number of User entries to cache"
|
||||
complete -c keybase -f -n "$ends keybase" -l vdebug -d "Verbose debugging; takes a comma-joined list of levels and tags"
|
||||
complete -c keybase -f -n "$ends keybase" -l version -s v -d "Print the version"
|
||||
|
||||
#commands
|
||||
#L1
|
||||
# commands
|
||||
# 1
|
||||
complete -c keybase -f -n "not $ends keybase"
|
||||
complete -c keybase -f -n "$ends keybase" -a "$keybase"
|
||||
#L2
|
||||
# 2
|
||||
complete -c keybase -f -n "$ends keybase account" -a "$keybase_account"
|
||||
complete -c keybase -f -n "$ends keybase audit" -a "$keybase_audit"
|
||||
complete -c keybase -f -n "$ends keybase base62" -a "$keybase_base62"
|
||||
complete -c keybase -f -n "$ends keybase blocks" -a "$keybase_blocks"
|
||||
complete -c keybase -f -n "$ends keybase bot" -a "$keybase_bot"
|
||||
complete -c keybase -f -n "$ends keybase chat" -a "$keybase_chat"
|
||||
complete -c keybase -f -n "$ends keybase config" -a "$keybase_config"
|
||||
complete -c keybase -f -n "$ends keybase ctl" -a "$keybase_ctl"
|
||||
complete -c keybase -f -n "$ends keybase currency" -a "$keybase_currency"
|
||||
complete -c keybase -f -n "$ends keybase db" -a "$keybase_db"
|
||||
complete -c keybase -f -n "$ends keybase device" -a "$keybase_device"
|
||||
complete -c keybase -f -n "$ends keybase fs" -a "$keybase_fs"
|
||||
complete -c keybase -f -n "$ends keybase git" -a "$keybase_git"
|
||||
@@ -135,19 +144,22 @@ complete -c keybase -f -n "$ends keybase kvstore" -a "$keybase_kvstore"
|
||||
complete -c keybase -f -n "$ends keybase log" -a "$keybase_log"
|
||||
complete -c keybase -f -n "$ends keybase passphrase" -a "$keybase_passphrase"
|
||||
complete -c keybase -f -n "$ends keybase pgp" -a "$keybase_pgp"
|
||||
complete -c keybase -f -n "$ends keybase pprof" -a "$keybase_pprof"
|
||||
complete -c keybase -f -n "$ends keybase rekey" -a "$keybase_rekey"
|
||||
complete -c keybase -f -n "$ends keybase sigs" -a "$keybase_sigs"
|
||||
complete -c keybase -f -n "$ends keybase team" -a "$keybase_team"
|
||||
complete -c keybase -f -n "$ends keybase update" -a "$keybase_update"
|
||||
complete -c keybase -f -n "$ends keybase wallet" -a "$keybase_wallet"
|
||||
#...
|
||||
#L3
|
||||
complete -c keybase -f -n "$ends keybase wot" -a "$keybase_wot"
|
||||
# 3
|
||||
complete -c keybase -f -n "$ends keybase account email" -a "$keybase_account_email"
|
||||
complete -c keybase -f -n "$ends keybase account phonenumber" -a "$keybase_account_phonenumber"
|
||||
complete -c keybase -f -n "$ends keybase bot token" -a "$keybase_bot_token"
|
||||
complete -c keybase -f -n "$ends keybase fs archive" -a "$keybase_fs_archive"
|
||||
complete -c keybase -f -n "$ends keybase fs debug" -a "$keybase_fs_debug"
|
||||
complete -c keybase -f -n "$ends keybase fs sync" -a "$keybase_fs_sync"
|
||||
#...
|
||||
|
||||
#command options
|
||||
# command options
|
||||
complete -c keybase -f -n "$ends keybase account upload-avatar" -l skip-chat-message -s s
|
||||
complete -c keybase -f -n "$ends keybase account upload-avatar" -l team
|
||||
#...
|
||||
# ...
|
||||
|
||||
@@ -133,7 +133,7 @@ complete -c set -n '__fish_set_is_color true false' -a --dim -x
|
||||
complete -c set -n '__fish_set_is_color true false' -a --italics -x
|
||||
complete -c set -n '__fish_set_is_color true true' -a --reverse -x
|
||||
complete -c set -n '__fish_set_is_color true false' -a --underline -x
|
||||
complete -c set -n '__fish_set_is_color true false' -a --underline=curly -x
|
||||
complete -c set -n '__fish_set_is_color true false' -a--underline={double,curly,dotted,dashed} -x
|
||||
|
||||
# Locale completions
|
||||
complete -c set -n '__fish_set_is_locale; and not __fish_seen_argument -s e -l erase' -x -a '(command -sq locale; and locale -a)' -d Locale
|
||||
|
||||
@@ -5,6 +5,6 @@ complete -c set_color -s o -l bold -d 'Make font bold'
|
||||
complete -c set_color -s i -l italics -d Italicise
|
||||
complete -c set_color -s d -l dim -d 'Dim text'
|
||||
complete -c set_color -s r -l reverse -d 'Reverse color text'
|
||||
complete -c set_color -s u -l underline -d 'Underline style' -a 'single curly'
|
||||
complete -c set_color -s u -l underline -d 'Underline style' -a 'single double curly dotted dashed'
|
||||
complete -c set_color -s h -l help -d 'Display help and exit'
|
||||
complete -c set_color -s c -l print-colors -d 'Print a list of all accepted color names'
|
||||
|
||||
@@ -1,61 +1,135 @@
|
||||
# completion for tmutil (macOS)
|
||||
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a addexclusion -d 'Add an exclusion not to back up a file'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from addexclusion' -s v -d 'Volume exclusion'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from addexclusion' -s p -d 'Path exclusion'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a associatedisk -d 'Bind a snapshot volume directory to the specified local disk'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a calculatedrift -d 'Determine the amount of change between snapshots'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a compare -d 'Perform a backup diff'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s a -d 'Compare all supported metadata'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s n -d 'No metadata comparison'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s @ -d 'Compare extended attributes'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s c -d 'Compare creation times'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s d -d 'Compare file data forks'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s e -d 'Compare ACLs'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s f -d 'Compare file flags'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s g -d 'Compare GIDs'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s m -d 'Compare file modes'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s s -d 'Compare sizes'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s t -d 'Compare modification times'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s u -d 'Compare UIDs'
|
||||
complete -r -c tmutil -n '__fish_seen_subcommand_from compare' -s D -d 'Limit traversal depth to depth levels from the beginning of iteration'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s E -d 'Dont take exclusions into account'
|
||||
complete -r -c tmutil -n '__fish_seen_subcommand_from compare' -s I -d 'Ignore path'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s U -d 'Ignore logical volume identity'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a delete -d 'Delete one or more snapshots'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a deletelocalsnapshots -d 'Delete all local Time Machine snapshots for the specified date (formatted YYYY-MM-DD-HHMMSS)'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a destinationinfo -d 'Print information about destinations'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a disable -d 'Turn off automatic backups'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a disablelocal -d 'Turn off local Time Machine snapshots'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a enable -d 'Turn on automatic backups'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a enablelocal -d 'Turn on local Time Machine snapshots'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a inheritbackup -d 'Claim a machine directory or sparsebundle for use by the current machine'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a isexcluded -d 'Determine if a file, directory, or volume are excluded from backups'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a latestbackup -d 'Print the path to the latest snapshot'
|
||||
complete -c tmutil -n __fish_use_subcommand -a listlocalsnapshotdates -d 'List the creation dates of all local Time Machine snapshots'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a listlocalsnapshots -d 'List local Time Machine snapshots of the specified volume'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a listbackups -d 'Print paths for all snapshots'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a localsnapshot -d 'Create new local Time Machine snapshot of APFS volume in TM backup'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a machinedirectory -d 'Print the path to the current machine directory'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a removedestination -d 'Removes a backup destination'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a removeexclusion -d 'Remove an exclusion'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from removeexclusion' -s v -d 'Volume exclusion'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from removeexclusion' -s p -d 'Path exclusion'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a restore -d 'Restore an item'
|
||||
complete -r -c tmutil -n '__fish_seen_subcommand_from restore' -s v
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a setdestination -d 'Set a backup destination'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from setdestination' -s a -d 'Add to the list of destinations'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from setdestination' -s p -d 'Enter the password at a non-echoing interactive prompt'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a snapshot -d 'Create new local Time Machine snapshot'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a startbackup -d 'Begin a backup if one is not already running'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from startbackup' -s a -l auto -d 'Automatic mode'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from startbackup' -s b -l block -d 'Block until finished'
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from startbackup' -s r -l rotation -d 'Automatic rotation'
|
||||
complete -r -c tmutil -n '__fish_seen_subcommand_from startbackup' -s d -l destination -d 'Backup destination'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a stopbackup -d 'Cancel a backup currently in progress'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a thinlocalsnapshots -d 'Thin local Time Machine snapshots for the specified volume'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a uniquesize -d 'Analyze the specified path and determine its unique size'
|
||||
complete -r -c tmutil -n __fish_use_subcommand -a verifychecksums -d 'Verify snapshot'
|
||||
complete -f -c tmutil -n __fish_use_subcommand -a version -d 'Print version'
|
||||
function __fish_tmutil_destination_ids
|
||||
for line in $(tmutil destinationinfo)
|
||||
if string match -q '*===*' -- $line
|
||||
# New section so clear out variables
|
||||
set -f name ''
|
||||
set -f kind ''
|
||||
continue
|
||||
end
|
||||
|
||||
complete -f -c tmutil -n '__fish_seen_subcommand_from destinationinfo isexcluded compare' -s X -d 'Print as XML'
|
||||
if string match -q -r '^Name' -- $line
|
||||
# Got the destination name
|
||||
set -f name "$(string match -r -g '^Name\s+:\s+(.*)$' $line | string trim)"
|
||||
continue
|
||||
end
|
||||
|
||||
if string match -q -r '^Kind' -- $line
|
||||
# Got the destination name
|
||||
set -f kind "$(string match -r -g '^Kind\s+:\s+(\w+)' $line | string trim)"
|
||||
continue
|
||||
end
|
||||
|
||||
if string match -q -r '^ID' -- $line
|
||||
# Got the destination ID
|
||||
set -f ID "$(string match -r -g '^ID\s+:\s+(.*)$' $line | string trim)"
|
||||
echo $ID\t$name $kind
|
||||
continue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
complete -c tmutil -n __fish_use_subcommand -a status -d 'Display Time Machine status'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from status'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a addexclusion -d 'Add an exclusion not to back up a file'
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from addexclusion' -s v -d 'Volume exclusion'
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from addexclusion' -s p -d 'Path exclusion'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a associatedisk -d 'Bind a snapshot volume directory to the specified local disk'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a calculatedrift -d 'Determine the amount of change between snapshots'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a compare -d 'Perform a backup diff'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s a -d 'Compare all supported metadata'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s n -d 'No metadata comparison'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s @ -d 'Compare extended attributes'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s c -d 'Compare creation times'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s d -d 'Compare file data forks'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s e -d 'Compare ACLs'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s f -d 'Compare file flags'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s g -d 'Compare GIDs'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s m -d 'Compare file modes'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s s -d 'Compare sizes'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s t -d 'Compare modification times'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s u -d 'Compare UIDs'
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from compare' -s D -d 'Limit traversal depth to depth levels from the beginning of iteration'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s E -d 'Dont take exclusions into account'
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from compare' -s I -d 'Ignore path'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from compare' -s U -d 'Ignore logical volume identity'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a delete -d 'Delete one or more snapshots'
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from delete' -s d -d 'Backup mount point'
|
||||
complete -c tmutil -r -f -n '__fish_seen_subcommand_from delete' -s t -d Timestamp
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from delete' -s p -d Path
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a deletelocalsnapshots -d 'Delete all local Time Machine snapshots for the specified date (formatted YYYY-MM-DD-HHMMSS)'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a deleteinprogress -d 'Delete all in-progress backups for a machine directory'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a destinationinfo -d 'Print information about destinations'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a disable -d 'Turn off automatic backups'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a disablelocal -d 'Turn off local Time Machine snapshots'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a enable -d 'Turn on automatic backups'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a enablelocal -d 'Turn on local Time Machine snapshots'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a inheritbackup -d 'Claim a machine directory or sparsebundle for use by the current machine'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a isexcluded -d 'Determine if a file, directory, or volume are excluded from backups'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a latestbackup -d 'Print the path to the latest snapshot'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a listlocalsnapshotdates -d 'List the creation dates of all local Time Machine snapshots'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a listlocalsnapshots -d 'List local Time Machine snapshots of the specified volume'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a listbackups -d 'Print paths for all snapshots'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a localsnapshot -d 'Create new local Time Machine snapshot of APFS volume in TM backup'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a machinedirectory -d 'Print the path to the current machine directory'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a removedestination -d 'Removes a backup destination'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from removedestination' -a '$(__fish_tmutil_destination_ids)'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a removeexclusion -d 'Remove an exclusion'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from removeexclusion' -s v -d 'Volume exclusion'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from removeexclusion' -s p -d 'Path exclusion'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a restore -d 'Restore an item'
|
||||
complete -c tmutil -r -n '__fish_seen_subcommand_from restore' -s v
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a setdestination -d 'Set a backup destination'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from setdestination' -s a -d 'Add to the list of destinations'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from setdestination' -s p -d 'Enter the password at a non-echoing interactive prompt'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a snapshot -f -d 'Create new local Time Machine snapshot'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a startbackup -d 'Begin a backup if one is not already running'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from startbackup' -s a -l auto -d 'Automatic mode'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from startbackup' -s b -l block -d 'Block until finished'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from startbackup' -s r -l rotation -d 'Automatic rotation'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from startbackup' -s d -l destination -r -a '$(__fish_tmutil_destination_ids)' -d 'Backup destination'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a stopbackup -d 'Cancel a backup currently in progress'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a thinlocalsnapshots -d 'Thin local Time Machine snapshots for the specified volume'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a uniquesize -d 'Analyze the specified path and determine its unique size'
|
||||
|
||||
complete -c tmutil -r -n __fish_use_subcommand -a verifychecksums -d 'Verify snapshot'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a version -d 'Print version'
|
||||
|
||||
complete -c tmutil -f -n __fish_use_subcommand -a setquota -d 'Set the quota for the destination in gigabytes'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from setquota' -r -a '$(__fish_tmutil_destination_ids)'
|
||||
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from destinationinfo isexcluded compare status' -s X -d 'Print as XML'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from latestbackup listbackups' -s m -d 'Destination volume to list backups from'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from latestbackup listbackups' -s t -d 'Attempt to mount the backups and list their mounted paths'
|
||||
complete -c tmutil -f -n '__fish_seen_subcommand_from latestbackup listbackups' -s d -d 'Backup mount point'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
# Returns 0 if the command has not had a subcommand yet
|
||||
# Does not currently account for -chdir
|
||||
function __fish_terraform_needs_command
|
||||
function __fish_tofu_needs_command
|
||||
set -l cmd (commandline -xpc)
|
||||
|
||||
if test (count $cmd) -eq 1
|
||||
@@ -10,19 +11,19 @@ function __fish_terraform_needs_command
|
||||
return 1
|
||||
end
|
||||
|
||||
function __fish_terraform_workspaces
|
||||
function __fish_tofu_workspaces
|
||||
tofu workspace list | string replace -r "^[\s\*]*" ""
|
||||
end
|
||||
|
||||
# general options
|
||||
complete -f -c tofu -n "not __fish_terraform_needs_command" -o version -d "Print version information"
|
||||
complete -f -c tofu -n "not __fish_tofu_needs_command" -o version -d "Print version information"
|
||||
complete -f -c tofu -o help -d "Show help"
|
||||
|
||||
### apply/destroy
|
||||
set -l apply apply destroy
|
||||
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a apply -d "Build or change infrastructure"
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a destroy -d "Destroy infrastructure"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a apply -d "Build or change infrastructure"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a destroy -d "Destroy infrastructure"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o auto-approve -d "Skip interactive approval"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from $apply" -o backup -d "Path to backup the existing state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o compact-warnings -d "Show only error summaries"
|
||||
@@ -31,17 +32,17 @@ complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o lock-timeout -d "
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o input=true -d "Ask for input for variables if not directly set"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o parallelism -d "Limit the number of concurrent operations"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from $apply" -o state -d "Path to a Terraform state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from $apply" -o state -d "Path to a OpenTofu state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from $apply" -o state-out -d "Path to write state"
|
||||
|
||||
### console
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a console -d "Interactive console for Terraform interpolations"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from console" -o state -d "Path to a Terraform state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from console" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a console -d "Interactive console for OpenTofu interpolations"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from console" -o state -d "Path to a OpenTofu state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from console" -o var -d "Set a variable in the OpenTofu configuration"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from console" -o var-file -d "Set variables from a file"
|
||||
|
||||
### fmt
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a fmt -d "Rewrite config files to canonical format"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a fmt -d "Rewrite config files to canonical format"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from fmt" -o list=false -d "Don't list files whose formatting differs"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from fmt" -o write=false -d "Don't write to source files"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from fmt" -o diff -d "Display diffs of formatting changes"
|
||||
@@ -50,21 +51,22 @@ complete -f -c tofu -n "__fish_seen_subcommand_from fmt" -o no-color -d "If spec
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from fmt" -o recursive -d "Also process files in subdirectories"
|
||||
|
||||
### get
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a get -d "Download and install modules for the configuration"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a get -d "Download and install modules for the configuration"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from get" -o update -d "Check modules for updates"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from get" -o no-color -d "If specified, output won't contain any color"
|
||||
|
||||
### graph
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a graph -d "Create a visual graph of Terraform resources"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a graph -d "Create a visual graph of OpenTofu resources"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o plan -d "Use specified plan file instead of current directory"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o draw-cycles -d "Highlight any cycles in the graph"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o type=plan -d "Output plan graph"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o type=plan-refresh-only -d "Output plan graph assuming refresh only"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o type=plan-destroy -d "Output plan graph assuming destroy"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o type=apply -d "Output apply graph"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o consice -d "Shorten the plan output"
|
||||
|
||||
### import
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a import -d "Import existing infrastructure into Terraform"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a import -d "Import existing infrastructure into OpenTofu"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from import" -o backup -d "Path to backup the existing state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o config -d "Path to a directory of configuration files"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o allow-missing-config -d "Allow import without resource block"
|
||||
@@ -72,13 +74,13 @@ complete -f -c tofu -n "__fish_seen_subcommand_from import" -o input=false -d "D
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o lock=false -d "Don't hold a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o lock-timeout -d "Duration to retry a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from import" -o state -d "Path to a Terraform state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from import" -o state -d "Path to a OpenTofu state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from import" -o state-out -d "Path to write state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from import" -o var -d "Set a variable in the OpenTofu configuration"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from import" -o var-file -d "Set variables from a file"
|
||||
|
||||
### init
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a init -d "Initialize a new or existing Terraform configuration"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a init -d "Initialize a new or existing OpenTofu configuration"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o backend=false -d "Disable backend initialization"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o cloud=false -d "Disable backend initialization"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from init" -o backend-config -d "Backend configuration"
|
||||
@@ -95,24 +97,25 @@ complete -r -c tofu -n "__fish_seen_subcommand_from init" -o migrate-state -d "R
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o upgrade -d "Install latest dependencies, ignoring lockfile"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o lockfile=readonly -d "Set dependency lockfile mode to readonly"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o ignore-remote-version -d "Ignore local and remote backend compatibility check"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o json -d "Print output in JSON"
|
||||
|
||||
### login
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a login -d "Retrieves auth token for the given hostname"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a login -d "Retrieves auth token for the given hostname"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from login" -a "(__fish_print_hostnames)"
|
||||
|
||||
### logout
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a logout -d "Removes auth token for the given hostname"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a logout -d "Removes auth token for the given hostname"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from logout" -a "(__fish_print_hostnames)"
|
||||
|
||||
### output
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a output -d "Read an output from a state file"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a output -d "Read an output from a state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from output" -o state -d "Path to the state file to read"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from output" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from output" -o json -d "Print output in JSON format"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from output" -o raw -d "Print raw strings directly"
|
||||
|
||||
### plan
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a plan -d "Generate and show an execution plan"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a plan -d "Generate and show an execution plan"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o compact-warnings -d "Show only error summaries"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o detailed-exitcode -d "Return detailed exit codes"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o input=true -d "Ask for input for variables if not directly set"
|
||||
@@ -121,7 +124,7 @@ complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o lock-timeout -d "Du
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o out -d "Write a plan file to the given path"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from plan" -o parallelism -d "Limit the number of concurrent operations"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from plan" -o state -d "Path to a Terraform state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from plan" -o state -d "Path to a OpenTofu state file"
|
||||
|
||||
### plan customization options are reusable across apply, destroy, and plan
|
||||
set -l plan apply destroy plan
|
||||
@@ -131,82 +134,86 @@ complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o refresh-only -d "S
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o refresh=false -d "Skip checking for external changes"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o replace -d "Force replacement of resource using its address"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o target -d "Resource to target"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o exclude -d "Resource to ignore during plan"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $plan" -o var -d "Set a variable in the OpenTofu configuration"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from $plan" -o var-file -d "Set variables from a file"
|
||||
|
||||
### providers
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a providers -d "Print tree of modules with their provider requirements"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a providers -d "Print tree of modules with their provider requirements"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from providers" -a "lock mirror schema"
|
||||
|
||||
### refresh
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a refresh -d "Update local state file against real resources"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a refresh -d "Update local state file against real resources"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o compact-warnings -d "Show only error summaries"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from refresh" -o backup -d "Path to backup the existing state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o input=true -d "Ask for input for variables if not directly set"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o lock=false -d "Don't hold a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o lock-timeout -d "Duration to retry a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from refresh" -o state -d "Path to a Terraform state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from refresh" -o state -d "Path to a OpenTofu state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from refresh" -o state-out -d "Path to write state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o target -d "Resource to target"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from refresh" -o var -d "Set a variable in the OpenTofu configuration"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from refresh" -o var-file -d "Set variables from a file"
|
||||
|
||||
### show
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a show -d "Inspect Terraform state or plan"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a show -d "Inspect OpenTofu state or plan"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from show" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from validate" -o json -d "Produce output in JSON format"
|
||||
|
||||
### state
|
||||
complete -r -c tofu -n __fish_terraform_needs_command -a state -d "Advanced state management"
|
||||
complete -r -c tofu -n __fish_tofu_needs_command -a state -d "Advanced state management"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a list -d "List resources in state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a ls -d "List resources in state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a mv -d "Move an item in the state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a move -d "Move an item in the state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a pull -d "Pull current state and output to stdout"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a push -d "Update remote state from local state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a replace-provider -d "Replace provider in the state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a rm -d "Remove instance from the state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a remove -d "Remove instance from the state"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a show -d "Show a resource in the state"
|
||||
|
||||
### taint
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a taint -d "Manually mark a resource for recreation"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a taint -d "Manually mark a resource for recreation"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from taint" -o allow-missing -d "Succeed even if resource is missing"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from taint" -o backup -d "Path to backup the existing state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from taint" -o lock=false -d "Don't hold a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from taint" -o lock-timeout -d "Duration to retry a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from taint" -o ignore-remote-version -d "Ignore local and remote backend compatibility check"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from taint" -o state -d "Path to a Terraform state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from taint" -o state -d "Path to a OpenTofu state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from taint" -o state-out -d "Path to write state"
|
||||
|
||||
### test
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a test -d "Runs automated test of shared modules"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a test -d "Runs automated test of shared modules"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from test" -o compact-warnings -d "Show only error summaries"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from test" -o junit-xml -d "Also write test results to provided JUnit XML file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from test" -o no-color -d "If specified, output won't contain any color"
|
||||
|
||||
### untaint
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a untaint -d "Manually unmark a resource as tainted"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a untaint -d "Manually unmark a resource as tainted"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from untaint" -o allow-missing -d "Succeed even if resource is missing"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from untaint" -o backup -d "Path to backup the existing state file"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from untaint" -o lock=false -d "Don't hold a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from untaint" -o lock-timeout -d "Duration to retry a state lock"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from untaint" -o ignore-remote-version -d "Ignore local and remote backend compatibility check"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from untaint" -o state -d "Path to a Terraform state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from untaint" -o state -d "Path to a OpenTofu state file"
|
||||
complete -r -c tofu -n "__fish_seen_subcommand_from untaint" -o state-out -d "Path to write state"
|
||||
|
||||
### validate
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a validate -d "Validate the Terraform files"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a validate -d "Validate the OpenTofu files"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from validate" -o json -d "Produce output in JSON format"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from validate" -o no-color -d "If specified, output won't contain any color"
|
||||
|
||||
### version
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a version -d "Print the Terraform version"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a version -d "Print the OpenTofu version"
|
||||
|
||||
### workspace
|
||||
set -l workspace_commands list select new delete
|
||||
|
||||
complete -f -c tofu -n __fish_terraform_needs_command -a workspace -d "Workspace management"
|
||||
complete -f -c tofu -n __fish_tofu_needs_command -a workspace -d "Workspace management"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from workspace && not __fish_seen_subcommand_from $workspace_commands" -a list -d "List workspaces"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from workspace && not __fish_seen_subcommand_from $workspace_commands" -a select -d "Select an workspace"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from workspace && not __fish_seen_subcommand_from $workspace_commands" -a new -d "Create a new workspace"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from workspace && not __fish_seen_subcommand_from $workspace_commands" -a delete -d "Delete an existing workspace"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from workspace && __fish_seen_subcommand_from select delete" -a "(__fish_terraform_workspaces)"
|
||||
complete -f -c tofu -n "__fish_seen_subcommand_from workspace && __fish_seen_subcommand_from select delete" -a "(__fish_tofu_workspaces)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function __fish_winetricks__complete_verbs
|
||||
winetricks list-all |
|
||||
winetricks list-all 2>/dev/null |
|
||||
string match --invert --regex '^==' |
|
||||
string match --invert --regex '^(apps|dlls|fonts|games|settings)$' |
|
||||
string replace --regex '(\S+)\s+(.+)' '$1\t$2'
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
set -l commands status get-volume inspect set-default set-volume set-mute set-profile clear-default
|
||||
|
||||
if wpctl settings &>/dev/null
|
||||
set --append commands settings
|
||||
end
|
||||
|
||||
function __wpctl_get_nodes -a section -a type
|
||||
set -l havesection
|
||||
set -l havetype
|
||||
@@ -38,6 +42,19 @@ function __wpctl_command_shape
|
||||
end
|
||||
end
|
||||
|
||||
function __wpctl_get_settings
|
||||
set -l wpctl_settings (wpctl settings 2>/dev/null| string collect)
|
||||
or return
|
||||
|
||||
string match --regex --all --quiet '\- Name: (?<wpctl_settings_name>.*)\n Desc: (?<wpctl_settings_desc>.*)' $wpctl_settings
|
||||
|
||||
for i in (seq (count $wpctl_settings_name))
|
||||
set -l name $wpctl_settings_name[$i]
|
||||
set -l desc (string shorten --max 60 $wpctl_settings_desc[$i])
|
||||
echo $name\t$desc
|
||||
end
|
||||
end
|
||||
|
||||
complete -c wpctl -f
|
||||
|
||||
complete -c wpctl -s h -l help -d "Show help options"
|
||||
@@ -48,6 +65,9 @@ complete -c wpctl -n "__fish_seen_subcommand_from inspect" -s a -l associated -d
|
||||
complete -c wpctl -n "__fish_seen_subcommand_from set-volume" -s p -l pid -d "Selects all nodes associated to the given PID"
|
||||
complete -c wpctl -n "__fish_seen_subcommand_from set-volume" -s l -l limit -d "Limit volume to below this value"
|
||||
complete -c wpctl -n "__fish_seen_subcommand_from set-mute" -s p -l pid -d "Selects all nodes associated to the given PID"
|
||||
complete -c wpctl -n "__fish_seen_subcommand_from settings" -s d -l delete -d "Deletes the saved setting value"
|
||||
complete -c wpctl -n "__fish_seen_subcommand_from settings" -s s -l save -d "Saves the setting value"
|
||||
complete -c wpctl -n "__fish_seen_subcommand_from settings" -s r -l reset -d "Resets the saved setting to its default value"
|
||||
|
||||
complete -c wpctl -n __wpctl_command_shape -a "$commands"
|
||||
complete -c wpctl -n '__wpctl_command_shape "*"' -n "__fish_seen_subcommand_from get-volume inspect set-volume set-mute set-profile" -a "@DEFAULT_AUDIO_SOURCE@" -d "Default Microphone"
|
||||
@@ -56,3 +76,4 @@ complete -c wpctl -n '__wpctl_command_shape "*"' -n "__fish_seen_subcommand_from
|
||||
complete -c wpctl -n '__wpctl_command_shape "*"' -n "__fish_seen_subcommand_from get-volume inspect set-volume set-mute set-profile" -a "(__wpctl_get_nodes Audio Sources) (__wpctl_get_nodes Audio Sinks)"
|
||||
complete -c wpctl -n '__wpctl_command_shape "*"' -n "__fish_seen_subcommand_from inspect set-profile" -a "(__wpctl_get_nodes Audio Sources) (__wpctl_get_nodes Audio Sinks) (__wpctl_get_nodes Video Source)"
|
||||
complete -c wpctl -n '__wpctl_command_shape set-mute "*"' -a "0 1 toggle"
|
||||
complete -c wpctl -n '__wpctl_command_shape settings' -a "(__wpctl_get_settings)"
|
||||
|
||||
@@ -69,8 +69,8 @@ function __fish_complete_man
|
||||
|
||||
# Fish commands are not given by apropos
|
||||
if not set -ql exclude_fish_commands
|
||||
set -l files $__fish_data_dir/man/man1/*.1
|
||||
string replace -r '.*/([^/]+)\.1$' '$1\t1: fish command' -- $files (status list-files man/man1/ 2>/dev/null)
|
||||
set -l files $__fish_data_dir/man/man1/*.1*
|
||||
string replace -r '.*/([^/]+)\.1(\.gz)?$' '$1\t1: fish command' -- $files (status list-files man/man1/ 2>/dev/null)
|
||||
end
|
||||
else
|
||||
return 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function __fish_print_commands --description "Print a list of documented fish commands"
|
||||
if test -d $__fish_data_dir/man/man1/
|
||||
if set -q __fish_data_dir[1] && test -d $__fish_data_dir/man/man1/
|
||||
for file in $__fish_data_dir/man/man1/**.1*
|
||||
string replace -r '.*/' '' -- $file |
|
||||
string replace -r '.1(.gz)?$' '' |
|
||||
|
||||
@@ -48,6 +48,9 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
|
||||
bind --preset $argv shift-right forward-bigword
|
||||
bind --preset $argv shift-left backward-bigword
|
||||
|
||||
bind --preset $argv alt-b prevd-or-backward-word
|
||||
bind --preset $argv alt-f nextd-or-forward-word
|
||||
|
||||
set -l alt_right_aliases alt-right \e\[1\;9C # iTerm2 < 3.5.12
|
||||
set -l alt_left_aliases alt-left \e\[1\;9D # iTerm2 < 3.5.12
|
||||
if test (__fish_uname) = Darwin
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
function __fish_systemctl --description 'Call systemctl with some options from the current commandline'
|
||||
# We don't want to complete with ANSI color codes
|
||||
set -lu SYSTEMD_COLORS
|
||||
|
||||
# These options are all global - before or after subcommand/arguments.
|
||||
# There's a bunch of long-only options in here, so we need to be creative with the mandatory short version.
|
||||
set -l opts t/type= s-state= p/property= a/all
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
function __fish_systemctl_services
|
||||
# We don't want to complete with ANSI color codes
|
||||
set -lu SYSTEMD_COLORS
|
||||
|
||||
if type -q systemctl
|
||||
if __fish_contains_opt user
|
||||
systemctl --user list-unit-files --full --no-legend --no-pager --plain --type=service 2>/dev/null $argv | string split -f 1 ' '
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Like for running machines, I'm assuming machinectl doesn't allow spaces in image names
|
||||
# This does not include the special image ".host" since it isn't valid for most operations
|
||||
function __fish_systemd_machine_images
|
||||
# We don't want to complete with ANSI color codes
|
||||
set -lu SYSTEMD_COLORS
|
||||
|
||||
machinectl --no-legend --no-pager list-images | while read -l a b
|
||||
echo $a
|
||||
end
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# It seems machinectl will eliminate spaces from machine names so we don't need to handle that
|
||||
function __fish_systemd_machines
|
||||
# We don't want to complete with ANSI color codes
|
||||
set -lu SYSTEMD_COLORS
|
||||
|
||||
machinectl --no-legend --no-pager list --all | while read -l a b
|
||||
echo $a
|
||||
end
|
||||
|
||||
@@ -5,18 +5,13 @@ function export --description 'Set env variable. Alias for `set -gx` for bash co
|
||||
end
|
||||
for arg in $argv
|
||||
set -l v (string split -m 1 "=" -- $arg)
|
||||
set -l value
|
||||
switch (count $v)
|
||||
case 1
|
||||
set -gx $v $$v
|
||||
set value $$v[1]
|
||||
case 2
|
||||
if contains -- $v[1] PATH CDPATH MANPATH
|
||||
set -l colonized_path (string replace -- "$$v[1]" (string join ":" -- $$v[1]) $v[2])
|
||||
set -gx $v[1] (string split ":" -- $colonized_path)
|
||||
else
|
||||
# status is 1 from the contains check, and `set` does not change the status on success: reset it.
|
||||
true
|
||||
set -gx $v[1] $v[2]
|
||||
end
|
||||
set value $v[2]
|
||||
end
|
||||
set -gx $v[1] $value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,8 +61,6 @@ function fish_default_key_bindings -d "emacs-like key binds"
|
||||
bind --preset $argv alt-delete kill-token
|
||||
bind --preset $argv ctrl-delete kill-word
|
||||
end
|
||||
bind --preset $argv alt-b prevd-or-backward-word
|
||||
bind --preset $argv alt-f nextd-or-forward-word
|
||||
|
||||
bind --preset $argv alt-\< beginning-of-buffer
|
||||
bind --preset $argv alt-\> end-of-buffer
|
||||
|
||||
@@ -113,13 +113,10 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
bind -s --preset -M insert end end-of-line
|
||||
bind -s --preset -M default end end-of-line
|
||||
|
||||
# Vi moves the cursor back if, after deleting, it is at EOL.
|
||||
# To emulate that, move forward, then backward, which will be a NOP
|
||||
# if there is something to move forward to.
|
||||
bind -s --preset -M default x delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M default x delete-char
|
||||
bind -s --preset -M default X backward-delete-char
|
||||
bind -s --preset -M insert delete delete-char forward-single-char backward-char
|
||||
bind -s --preset -M default delete delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M insert delete delete-char
|
||||
bind -s --preset -M default delete delete-char
|
||||
|
||||
# Backspace deletes a char in insert mode, but not in normal/default mode.
|
||||
bind -s --preset -M insert backspace backward-delete-char
|
||||
@@ -238,7 +235,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
# in vim p means paste *after* current character, so go forward a char before pasting
|
||||
# also in vim, P means paste *at* current position (like at '|' with cursor = line),
|
||||
# \ so there's no need to go back a char, just paste it without moving
|
||||
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_modefish_cursor_end_modeinclusive' yank
|
||||
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' yank
|
||||
bind -s --preset P yank
|
||||
bind -s --preset g,p yank-pop
|
||||
|
||||
@@ -252,10 +249,10 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
# Lowercase r, enters replace_one mode
|
||||
#
|
||||
bind -s --preset -m replace_one r repaint-mode
|
||||
bind -s --preset -M replace_one -m default '' delete-char self-insert backward-char repaint-mode
|
||||
bind -s --preset -M replace_one -m default enter 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default ctrl-j 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default ctrl-m 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default '' 'set -g fish_cursor_end_mode exclusive' delete-char self-insert backward-char repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default enter 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default ctrl-j 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default ctrl-m 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default escape cancel repaint-mode
|
||||
bind -s --preset -M replace_one -m default ctrl-\[ cancel repaint-mode
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ function history --description "display or manipulate interactive command histor
|
||||
set -l searchterm $argv
|
||||
if not set -q argv[1]
|
||||
read -P"Search term: " searchterm
|
||||
or return $status
|
||||
end
|
||||
|
||||
if test "$search_mode" = --exact
|
||||
@@ -119,6 +120,7 @@ function history --description "display or manipulate interactive command histor
|
||||
echo "Enter 'all' to delete all the matching entries."
|
||||
echo
|
||||
read --local --prompt "echo 'Delete which entries? '" choice
|
||||
or return $status
|
||||
echo ''
|
||||
|
||||
if test -z "$choice"
|
||||
@@ -192,6 +194,7 @@ function history --description "display or manipulate interactive command histor
|
||||
|
||||
printf (_ "If you enter 'yes' your entire interactive command history will be erased\n")
|
||||
read --local --prompt "echo 'Are you sure you want to clear history? (yes/no) '" choice
|
||||
or return $status
|
||||
if test "$choice" = yes
|
||||
builtin history clear $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||
and printf (_ "Command history cleared!\n")
|
||||
@@ -205,6 +208,7 @@ function history --description "display or manipulate interactive command histor
|
||||
set -l newitem $argv
|
||||
if not set -q argv[1]
|
||||
read -P "Command: " newitem
|
||||
or return $status
|
||||
end
|
||||
|
||||
builtin history append $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
|
||||
|
||||
@@ -244,7 +244,7 @@ def parse_color(color_str):
|
||||
underline = "single"
|
||||
elif comp.startswith("--underline="):
|
||||
underline = comp.stripprefix("--underline=")
|
||||
elif comp.startswith("-u"): # Multiple short options like "-rbcurly" are not yet supported.
|
||||
elif comp.startswith("-u"): # Multiple short options like "-rucurly" are not yet supported.
|
||||
underline = comp.stripprefix("-u")
|
||||
elif comp == "--italics" or comp == "-i":
|
||||
italics = True
|
||||
|
||||
2641
src/ast.rs
2641
src/ast.rs
File diff suppressed because it is too large
Load Diff
198
src/autoload.rs
198
src/autoload.rs
@@ -9,7 +9,9 @@
|
||||
#[cfg(test)]
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wchar::{wstr, WString, L};
|
||||
use crate::wchar_ext::WExt;
|
||||
use crate::wutil::{file_id_for_path, FileId, INVALID_FILE_ID};
|
||||
use crate::FLOGF;
|
||||
use lru::LruCache;
|
||||
#[cfg(feature = "embed-data")]
|
||||
use rust_embed::RustEmbed;
|
||||
@@ -56,6 +58,12 @@ pub fn has_asset(_cmd: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum AssetDir {
|
||||
Functions,
|
||||
Completions,
|
||||
}
|
||||
|
||||
pub enum AutoloadPath {
|
||||
#[cfg(feature = "embed-data")]
|
||||
Embedded(String),
|
||||
@@ -63,7 +71,7 @@ pub enum AutoloadPath {
|
||||
}
|
||||
|
||||
enum AutoloadResult {
|
||||
Path(WString),
|
||||
Path(AutoloadPath),
|
||||
Loaded,
|
||||
Pending,
|
||||
None,
|
||||
@@ -97,71 +105,33 @@ pub fn new(env_var_name: &'static wstr) -> Self {
|
||||
/// mark_autoload_finished() with the same command. Note this does not actually execute any
|
||||
/// code; it is the caller's responsibility to load the file.
|
||||
pub fn resolve_command(&mut self, cmd: &wstr, env: &dyn Environment) -> Option<AutoloadPath> {
|
||||
use crate::wchar_ext::WExt;
|
||||
|
||||
let mut possible_path = None;
|
||||
if let Some(var) = env.get(self.env_var_name) {
|
||||
match self.resolve_command_impl(cmd, var.as_list()) {
|
||||
AutoloadResult::Path(path) => {
|
||||
crate::FLOGF!(autoload, "Loading from path with var: %ls", path);
|
||||
// HACK: Ignore generated_completions until we tried the embedded assets
|
||||
if path
|
||||
.find("/generated_completions/".chars().collect::<Vec<_>>())
|
||||
.is_some()
|
||||
{
|
||||
possible_path = Some(path);
|
||||
} else {
|
||||
return Some(AutoloadPath::Path(path));
|
||||
match self.resolve_command_impl(
|
||||
cmd,
|
||||
env.get(self.env_var_name)
|
||||
.as_ref()
|
||||
.map(|var| var.as_list())
|
||||
.unwrap_or_default(),
|
||||
) {
|
||||
AutoloadResult::Path(path) => {
|
||||
match &path {
|
||||
#[cfg(feature = "embed-data")]
|
||||
AutoloadPath::Embedded(_) => {
|
||||
FLOGF!(autoload, "Embedded: %ls", cmd);
|
||||
}
|
||||
AutoloadPath::Path(path) => {
|
||||
FLOGF!(
|
||||
autoload,
|
||||
"Loading %ls from var %ls from path %ls",
|
||||
cmd,
|
||||
self.env_var_name,
|
||||
path
|
||||
)
|
||||
}
|
||||
}
|
||||
AutoloadResult::Loaded => return None,
|
||||
AutoloadResult::Pending => return None,
|
||||
AutoloadResult::None => (),
|
||||
};
|
||||
} else {
|
||||
match self.resolve_command_impl(cmd, &[]) {
|
||||
AutoloadResult::Path(path) => {
|
||||
crate::FLOGF!(autoload, "Loading from path with var: %ls", path);
|
||||
return Some(AutoloadPath::Path(path));
|
||||
}
|
||||
AutoloadResult::Loaded => return None,
|
||||
AutoloadResult::Pending => return None,
|
||||
AutoloadResult::None => (),
|
||||
};
|
||||
}
|
||||
|
||||
// HACK: In cargo tests, this used to never load functions
|
||||
// It will hang for reasons unrelated to this.
|
||||
if cfg!(test) {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(feature = "embed-data")]
|
||||
{
|
||||
let narrow = wcs2string(cmd);
|
||||
let cmdstr = std::str::from_utf8(&narrow).ok()?;
|
||||
let p = if self.env_var_name == "fish_function_path" {
|
||||
"functions/".to_owned() + cmdstr + ".fish"
|
||||
} else if self.env_var_name == "fish_complete_path" {
|
||||
"completions/".to_owned() + cmdstr + ".fish"
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if has_asset(&p) {
|
||||
if let Some(loaded_file) = self.autoloaded_files.get(cmd) {
|
||||
if *loaded_file == INVALID_FILE_ID {
|
||||
// The file has been autoloaded and is unchanged.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
self.current_autoloading.insert(cmd.to_owned());
|
||||
self.autoloaded_files
|
||||
.insert(cmd.to_owned(), INVALID_FILE_ID);
|
||||
crate::FLOGF!(autoload, "Embedded: %ls", cmd);
|
||||
return Some(AutoloadPath::Embedded(p));
|
||||
Some(path)
|
||||
}
|
||||
AutoloadResult::Loaded | AutoloadResult::Pending | AutoloadResult::None => None,
|
||||
}
|
||||
possible_path.map(AutoloadPath::Path)
|
||||
}
|
||||
|
||||
/// Helper to actually perform an autoload.
|
||||
@@ -182,7 +152,7 @@ pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) {
|
||||
AutoloadPath::Embedded(name) => {
|
||||
use crate::common::str2wcstring;
|
||||
use std::sync::Arc;
|
||||
crate::FLOGF!(autoload, "Loading embedded: %ls", name);
|
||||
FLOGF!(autoload, "Loading embedded: %ls", name);
|
||||
let emfile = Asset::get(name).expect("Embedded file not found");
|
||||
let src = str2wcstring(&emfile.data);
|
||||
let mut widename = L!("embedded:").to_owned();
|
||||
@@ -209,7 +179,9 @@ pub fn autoload_in_progress(&self, cmd: &wstr) -> bool {
|
||||
/// Return whether a command could potentially be autoloaded.
|
||||
/// This does not actually mark the command as being autoloaded.
|
||||
pub fn can_autoload(&mut self, cmd: &wstr) -> bool {
|
||||
self.cache.check(cmd, true /* allow stale */).is_some()
|
||||
self.cache
|
||||
.check(self.env_var_name, cmd, true /* allow stale */)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Return whether autoloading has been attempted for a command.
|
||||
@@ -259,13 +231,19 @@ fn resolve_command_impl(&mut self, cmd: &wstr, paths: &[WString]) -> AutoloadRes
|
||||
}
|
||||
|
||||
// Do we have an entry to load?
|
||||
let Some(file) = self.cache.check(cmd, false) else {
|
||||
let Some(file) = self.cache.check(self.env_var_name, cmd, false) else {
|
||||
return AutoloadResult::None;
|
||||
};
|
||||
|
||||
let file_id = match &file {
|
||||
AutoloadableFileInfo::FileInfo(file) => &file.file_id,
|
||||
#[cfg(feature = "embed-data")]
|
||||
AutoloadableFileInfo::EmbeddedPath(_) => &INVALID_FILE_ID,
|
||||
};
|
||||
|
||||
// Is this file the same as what we previously autoloaded?
|
||||
if let Some(loaded_file) = self.autoloaded_files.get(cmd) {
|
||||
if *loaded_file == file.file_id {
|
||||
if *loaded_file == *file_id {
|
||||
// The file has been autoloaded and is unchanged.
|
||||
return AutoloadResult::Loaded;
|
||||
}
|
||||
@@ -273,8 +251,13 @@ fn resolve_command_impl(&mut self, cmd: &wstr, paths: &[WString]) -> AutoloadRes
|
||||
|
||||
// We're going to (tell our caller to) autoload this command.
|
||||
self.current_autoloading.insert(cmd.to_owned());
|
||||
self.autoloaded_files.insert(cmd.to_owned(), file.file_id);
|
||||
AutoloadResult::Path(file.path)
|
||||
self.autoloaded_files
|
||||
.insert(cmd.to_owned(), file_id.clone());
|
||||
AutoloadResult::Path(match file {
|
||||
AutoloadableFileInfo::FileInfo(path) => AutoloadPath::Path(path.path),
|
||||
#[cfg(feature = "embed-data")]
|
||||
AutoloadableFileInfo::EmbeddedPath(path) => AutoloadPath::Embedded(path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,19 +266,28 @@ fn resolve_command_impl(&mut self, cmd: &wstr, paths: &[WString]) -> AutoloadRes
|
||||
|
||||
/// Represents a file that we might want to autoload.
|
||||
#[derive(Clone)]
|
||||
struct AutoloadableFile {
|
||||
struct FileInfo {
|
||||
/// The path to the file.
|
||||
path: WString,
|
||||
/// The metadata for the file.
|
||||
file_id: FileId,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum AutoloadableFileInfo {
|
||||
/// An on-disk file.
|
||||
FileInfo(FileInfo),
|
||||
/// An embedded file.
|
||||
#[cfg(feature = "embed-data")]
|
||||
EmbeddedPath(String),
|
||||
}
|
||||
|
||||
// A timestamp is a monotonic point in time.
|
||||
type Timestamp = time::Instant;
|
||||
type MissesLruCache = LruCache<WString, Timestamp>;
|
||||
|
||||
struct KnownFile {
|
||||
file: AutoloadableFile,
|
||||
file: AutoloadableFileInfo,
|
||||
last_checked: Timestamp,
|
||||
}
|
||||
|
||||
@@ -343,10 +335,32 @@ fn dirs(&self) -> &[WString] {
|
||||
/// Check if a command `cmd` can be loaded.
|
||||
/// If `allow_stale` is true, allow stale entries; otherwise discard them.
|
||||
/// This returns an autoloadable file, or none() if there is no such file.
|
||||
fn check(&mut self, cmd: &wstr, allow_stale: bool) -> Option<AutoloadableFile> {
|
||||
fn check(
|
||||
&mut self,
|
||||
env_var_name: &wstr,
|
||||
cmd: &wstr,
|
||||
allow_stale: bool,
|
||||
) -> Option<AutoloadableFileInfo> {
|
||||
let asset_dir = cfg!(feature = "embed-data").then_some(()).and_then(|()| {
|
||||
if env_var_name == "fish_function_path" {
|
||||
Some(AssetDir::Functions)
|
||||
} else if cfg!(feature = "embed-data") && env_var_name == "fish_complete_path" {
|
||||
Some(AssetDir::Completions)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// Check hits.
|
||||
if let Some(value) = self.known_files.get(cmd) {
|
||||
if allow_stale || Self::is_fresh(value.last_checked, Self::current_timestamp()) {
|
||||
#[cfg(feature = "embed-data")]
|
||||
let embedded = matches!(value.file, AutoloadableFileInfo::EmbeddedPath(_));
|
||||
#[cfg(not(feature = "embed-data"))]
|
||||
let embedded = false;
|
||||
if allow_stale
|
||||
|| embedded
|
||||
|| Self::is_fresh(value.last_checked, Self::current_timestamp())
|
||||
{
|
||||
// Re-use this cached hit.
|
||||
return Some(value.file.clone());
|
||||
}
|
||||
@@ -365,7 +379,10 @@ fn check(&mut self, cmd: &wstr, allow_stale: bool) -> Option<AutoloadableFile> {
|
||||
}
|
||||
|
||||
// We couldn't satisfy this request from the cache. Hit the disk.
|
||||
let file = self.locate_file(cmd);
|
||||
let file = self
|
||||
.locate_file(cmd, asset_dir, false)
|
||||
.or_else(|| self.locate_asset(cmd, asset_dir?))
|
||||
.or_else(|| self.locate_file(cmd, asset_dir, true));
|
||||
if let Some(file) = file.as_ref() {
|
||||
let old_value = self.known_files.insert(
|
||||
cmd.to_owned(),
|
||||
@@ -408,7 +425,12 @@ fn is_fresh(then: Timestamp, now: Timestamp) -> bool {
|
||||
|
||||
/// Attempt to find an autoloadable file by searching our path list for a given command.
|
||||
/// Return the file, or none() if none.
|
||||
fn locate_file(&self, cmd: &wstr) -> Option<AutoloadableFile> {
|
||||
fn locate_file(
|
||||
&self,
|
||||
cmd: &wstr,
|
||||
asset_dir: Option<AssetDir>,
|
||||
want_generated_completions: bool,
|
||||
) -> Option<AutoloadableFileInfo> {
|
||||
// If the command is empty or starts with NULL (i.e. is empty as a path)
|
||||
// we'd try to source the *directory*, which exists.
|
||||
// So instead ignore these here.
|
||||
@@ -421,6 +443,12 @@ fn locate_file(&self, cmd: &wstr) -> Option<AutoloadableFile> {
|
||||
// Re-use the storage for path.
|
||||
let mut path;
|
||||
for dir in self.dirs() {
|
||||
if asset_dir == Some(AssetDir::Completions) {
|
||||
// HACK: Ignore generated_completions until we tried the embedded assets
|
||||
if dir.ends_with(L!("/generated_completions")) != want_generated_completions {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Construct the path as dir/cmd.fish
|
||||
path = dir.to_owned();
|
||||
path.push('/');
|
||||
@@ -430,11 +458,31 @@ fn locate_file(&self, cmd: &wstr) -> Option<AutoloadableFile> {
|
||||
let file_id = file_id_for_path(&path);
|
||||
if file_id != INVALID_FILE_ID {
|
||||
// Found it.
|
||||
return Some(AutoloadableFile { path, file_id });
|
||||
return Some(AutoloadableFileInfo::FileInfo(FileInfo { path, file_id }));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "embed-data"))]
|
||||
fn locate_asset(&self, _cmd: &wstr, _asset_dir: AssetDir) -> Option<AutoloadableFileInfo> {
|
||||
None
|
||||
}
|
||||
#[cfg(feature = "embed-data")]
|
||||
fn locate_asset(&self, cmd: &wstr, asset_dir: AssetDir) -> Option<AutoloadableFileInfo> {
|
||||
// HACK: In cargo tests, this used to never load functions
|
||||
// It will hang for reasons unrelated to this.
|
||||
if cfg!(test) {
|
||||
return None;
|
||||
}
|
||||
let narrow = wcs2string(cmd);
|
||||
let cmdstr = std::str::from_utf8(&narrow).ok()?;
|
||||
let p = match asset_dir {
|
||||
AssetDir::Functions => "functions/".to_owned() + cmdstr + ".fish",
|
||||
AssetDir::Completions => "completions/".to_owned() + cmdstr + ".fish",
|
||||
};
|
||||
has_asset(&p).then_some(AutoloadableFileInfo::EmbeddedPath(p))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use fish::{
|
||||
ast::Ast,
|
||||
ast,
|
||||
builtins::{
|
||||
fish_indent, fish_key_reader,
|
||||
shared::{
|
||||
@@ -226,7 +226,7 @@ fn run_command_list(parser: &Parser, cmds: &[OsString]) -> Result<(), libc::c_in
|
||||
let cmd_wcs = str2wcstring(cmd.as_bytes());
|
||||
|
||||
let mut errors = ParseErrorList::new();
|
||||
let ast = Ast::parse(&cmd_wcs, ParseTreeFlags::empty(), Some(&mut errors));
|
||||
let ast = ast::parse(&cmd_wcs, ParseTreeFlags::empty(), Some(&mut errors));
|
||||
let errored = ast.errored() || {
|
||||
parse_util_detect_errors_in_ast(&ast, &cmd_wcs, Some(&mut errors)).is_err()
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::prelude::*;
|
||||
use crate::ast::{Ast, Leaf};
|
||||
use crate::ast::{self, Kind, Leaf};
|
||||
use crate::common::{unescape_string, UnescapeFlags, UnescapeStringStyle};
|
||||
use crate::complete::Completion;
|
||||
use crate::expand::{expand_string, ExpandFlags, ExpandResultCode};
|
||||
@@ -44,7 +44,7 @@ enum AppendMode {
|
||||
Append,
|
||||
}
|
||||
|
||||
enum TokenMode {
|
||||
enum TokenOutputMode {
|
||||
Expanded,
|
||||
Raw,
|
||||
Unescaped,
|
||||
@@ -108,7 +108,7 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr)
|
||||
}
|
||||
insert.find(L!("$ "))?; // Early return.
|
||||
let source = prefix.to_owned() + insert;
|
||||
let ast = Ast::parse(
|
||||
let ast = ast::parse(
|
||||
&source,
|
||||
ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS | ParseTreeFlags::LEAVE_UNTERMINATED,
|
||||
None,
|
||||
@@ -116,7 +116,7 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr)
|
||||
let mut stripped = WString::new();
|
||||
let mut have = prefix.len();
|
||||
for node in ast.walk() {
|
||||
let Some(ds) = node.as_decorated_statement() else {
|
||||
let Kind::DecoratedStatement(ds) = node.kind() else {
|
||||
continue;
|
||||
};
|
||||
let Some(range) = ds.command.range() else {
|
||||
@@ -151,7 +151,7 @@ fn write_part(
|
||||
range: Range<usize>,
|
||||
range_is_single_token: bool,
|
||||
cut_at_cursor: bool,
|
||||
token_mode: Option<TokenMode>,
|
||||
token_mode: Option<TokenOutputMode>,
|
||||
buffer: &wstr,
|
||||
cursor_pos: usize,
|
||||
streams: &mut IoStreams,
|
||||
@@ -171,7 +171,7 @@ fn write_part(
|
||||
let mut args = vec![];
|
||||
let mut add_token = |token_text: &wstr| {
|
||||
match token_mode {
|
||||
TokenMode::Expanded => {
|
||||
TokenOutputMode::Expanded => {
|
||||
const COMMANDLINE_TOKENS_MAX_EXPANSION: usize = 512;
|
||||
|
||||
match expand_string(
|
||||
@@ -199,10 +199,10 @@ fn write_part(
|
||||
ExpandResultCode::ok => (),
|
||||
};
|
||||
}
|
||||
TokenMode::Raw => {
|
||||
TokenOutputMode::Raw => {
|
||||
args.push(Completion::from_completion(token_text.to_owned()));
|
||||
}
|
||||
TokenMode::Unescaped => {
|
||||
TokenOutputMode::Unescaped => {
|
||||
let unescaped = unescape_string(
|
||||
token_text,
|
||||
UnescapeStringStyle::Script(UnescapeFlags::INCOMPLETE),
|
||||
@@ -218,10 +218,16 @@ fn write_part(
|
||||
add_token(buff);
|
||||
} else {
|
||||
let mut tok = Tokenizer::new(buff, TOK_ACCEPT_UNFINISHED);
|
||||
let mut in_redirection = false;
|
||||
while let Some(token) = tok.next() {
|
||||
if cut_at_cursor && token.end() >= pos {
|
||||
break;
|
||||
}
|
||||
let is_redirection_target = in_redirection;
|
||||
in_redirection = token.type_ == TokenType::redirect;
|
||||
if is_redirection_target && token.type_ == TokenType::string {
|
||||
continue;
|
||||
}
|
||||
if token.type_ != TokenType::string {
|
||||
continue;
|
||||
}
|
||||
@@ -319,9 +325,9 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
token_mode = Some(match c {
|
||||
'x' => TokenMode::Expanded,
|
||||
'\x02' => TokenMode::Raw,
|
||||
'o' => TokenMode::Unescaped,
|
||||
'x' => TokenOutputMode::Expanded,
|
||||
'\x02' => TokenOutputMode::Raw,
|
||||
'o' => TokenOutputMode::Unescaped,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
@@ -447,7 +453,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
"--cut-at-cursor and --tokens can not be used when setting the commandline"
|
||||
"--cut-at-cursor and token options can not be used when setting the commandline"
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
|
||||
@@ -177,7 +177,7 @@ fn builtin_complete_remove_cmd(
|
||||
|
||||
if !removed {
|
||||
// This means that all loops were empty.
|
||||
complete_remove_all(cmd.to_owned(), cmd_is_path);
|
||||
complete_remove_all(cmd.to_owned(), cmd_is_path, /*explicit=*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
use libc::LC_ALL;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::ast::{
|
||||
self, Ast, Category, Leaf, List, Node, NodeVisitor, SourceRangeList, Traversal, Type,
|
||||
};
|
||||
use crate::ast::{self, Ast, Kind, Leaf, Node, NodeVisitor, SourceRangeList, Traversal};
|
||||
use crate::common::{
|
||||
str2wcstring, unescape_string, wcs2string, UnescapeFlags, UnescapeStringStyle, PROGRAM_NAME,
|
||||
};
|
||||
@@ -96,7 +94,6 @@ struct AstSizeMetrics {
|
||||
/// Note tokens and keywords are also counted as leaves.
|
||||
branch_count: usize,
|
||||
leaf_count: usize,
|
||||
list_count: usize,
|
||||
token_count: usize,
|
||||
keyword_count: usize,
|
||||
// An estimate of the total allocated size of the ast in bytes.
|
||||
@@ -109,7 +106,6 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, " nodes: {}", self.node_count)?;
|
||||
writeln!(f, " branches: {}", self.branch_count)?;
|
||||
writeln!(f, " leaves: {}", self.leaf_count)?;
|
||||
writeln!(f, " lists: {}", self.list_count)?;
|
||||
writeln!(f, " tokens: {}", self.token_count)?;
|
||||
writeln!(f, " keywords: {}", self.keyword_count)?;
|
||||
|
||||
@@ -127,10 +123,10 @@ impl<'a> NodeVisitor<'a> for AstSizeMetrics {
|
||||
fn visit(&mut self, node: &'a dyn Node) {
|
||||
self.node_count += 1;
|
||||
self.memory_size += node.self_memory_size();
|
||||
match node.category() {
|
||||
Category::branch => self.branch_count += 1,
|
||||
Category::leaf => self.leaf_count += 1,
|
||||
Category::list => self.list_count += 1,
|
||||
if node.as_leaf().is_some() {
|
||||
self.leaf_count += 1;
|
||||
} else {
|
||||
self.branch_count += 1; // treating lists as branches
|
||||
}
|
||||
if node.as_token().is_some() {
|
||||
self.token_count += 1;
|
||||
@@ -138,7 +134,7 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
if node.as_keyword().is_some() {
|
||||
self.keyword_count += 1;
|
||||
}
|
||||
node.accept(self, false);
|
||||
node.accept(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +218,7 @@ fn compute_gaps(&self) -> Vec<SourceRange> {
|
||||
// Collect the token ranges into a list.
|
||||
let mut tok_ranges = vec![];
|
||||
for node in Traversal::new(self.ast.top()) {
|
||||
if node.category() == Category::leaf {
|
||||
if let Some(node) = node.as_leaf() {
|
||||
let r = node.source_range();
|
||||
if r.length() > 0 {
|
||||
tok_ranges.push(r);
|
||||
@@ -268,10 +264,10 @@ fn compute_preferred_semi_locations(&self) -> Vec<usize> {
|
||||
// See if we have a condition and an andor_job_list.
|
||||
let condition;
|
||||
let andors;
|
||||
if let Some(ifc) = node.as_if_clause() {
|
||||
if let Kind::IfClause(ifc) = node.kind() {
|
||||
condition = ifc.condition.semi_nl.as_ref();
|
||||
andors = &ifc.andor_tail;
|
||||
} else if let Some(wc) = node.as_while_header() {
|
||||
} else if let Kind::WhileHeader(wc) = node.kind() {
|
||||
condition = wc.condition.semi_nl.as_ref();
|
||||
andors = &wc.andor_tail;
|
||||
} else {
|
||||
@@ -279,10 +275,10 @@ fn compute_preferred_semi_locations(&self) -> Vec<usize> {
|
||||
}
|
||||
|
||||
// If there is no and-or tail then we always use a newline.
|
||||
if andors.count() > 0 {
|
||||
if !andors.is_empty() {
|
||||
condition.map(&mut mark_semi_from_input);
|
||||
// Mark all but last of the andor list.
|
||||
for andor in andors.iter().take(andors.count() - 1) {
|
||||
for andor in andors.iter().take(andors.len() - 1) {
|
||||
mark_semi_from_input(andor.job.semi_nl.as_ref().unwrap());
|
||||
}
|
||||
}
|
||||
@@ -290,7 +286,7 @@ fn compute_preferred_semi_locations(&self) -> Vec<usize> {
|
||||
|
||||
// `{ x; y; }` gets semis if the input uses semis and it spans only one line.
|
||||
for node in Traversal::new(self.ast.top()) {
|
||||
let Some(brace_statement) = node.as_brace_statement() else {
|
||||
let Kind::BraceStatement(brace_statement) = node.kind() else {
|
||||
continue;
|
||||
};
|
||||
if self
|
||||
@@ -307,7 +303,7 @@ fn compute_preferred_semi_locations(&self) -> Vec<usize> {
|
||||
|
||||
// `x ; and y` gets semis if it has them already, and they are on the same line.
|
||||
for node in Traversal::new(self.ast.top()) {
|
||||
let Some(job_list) = node.as_job_list() else {
|
||||
let Kind::JobList(job_list) = node.kind() else {
|
||||
continue;
|
||||
};
|
||||
let mut prev_job_semi_nl = None;
|
||||
@@ -355,7 +351,7 @@ fn compute_multi_line_brace_statement_locations(&self) -> Vec<usize> {
|
||||
.collect();
|
||||
let mut next_newline = 0;
|
||||
for node in Traversal::new(self.ast.top()) {
|
||||
let Some(brace_statement) = node.as_brace_statement() else {
|
||||
let Kind::BraceStatement(brace_statement) = node.kind() else {
|
||||
continue;
|
||||
};
|
||||
while next_newline != newline_offsets.len()
|
||||
@@ -385,14 +381,14 @@ fn indent(&self, index: usize) -> usize {
|
||||
// Return gap text flags for the gap text that comes *before* a given node type.
|
||||
fn gap_text_flags_before_node(&self, node: &dyn Node) -> GapFlags {
|
||||
let mut result = GapFlags::default();
|
||||
match node.typ() {
|
||||
match node.kind() {
|
||||
// Allow escaped newlines before leaf nodes that can be part of a long command.
|
||||
Type::argument | Type::redirection | Type::variable_assignment => {
|
||||
Kind::Argument(_) | Kind::Redirection(_) | Kind::VariableAssignment(_) => {
|
||||
result.allow_escaped_newlines = true
|
||||
}
|
||||
Type::token_base => {
|
||||
Kind::Token(token) => {
|
||||
// Allow escaped newlines before && and ||, and also pipes.
|
||||
match node.as_token().unwrap().token_type() {
|
||||
match token.token_type() {
|
||||
ParseTokenType::andand | ParseTokenType::oror | ParseTokenType::pipe => {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
@@ -400,21 +396,21 @@ fn gap_text_flags_before_node(&self, node: &dyn Node) -> GapFlags {
|
||||
// Allow escaped newlines before commands that follow a variable assignment
|
||||
// since both can be long (#7955).
|
||||
let p = self.traversal.parent(node);
|
||||
if p.typ() != Type::decorated_statement {
|
||||
if !matches!(p.kind(), Kind::DecoratedStatement(_)) {
|
||||
return result;
|
||||
}
|
||||
let p = self.traversal.parent(p);
|
||||
assert_eq!(p.typ(), Type::statement);
|
||||
assert!(matches!(p.kind(), Kind::Statement(_)));
|
||||
let p = self.traversal.parent(p);
|
||||
if let Some(job) = p.as_job_pipeline() {
|
||||
if let Kind::JobPipeline(job) = p.kind() {
|
||||
if !job.variables.is_empty() {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
} else if let Some(job_cnt) = p.as_job_continuation() {
|
||||
} else if let Kind::JobContinuation(job_cnt) = p.kind() {
|
||||
if !job_cnt.variables.is_empty() {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
} else if let Some(not_stmt) = p.as_not_statement() {
|
||||
} else if let Kind::NotStatement(not_stmt) = p.kind() {
|
||||
if !not_stmt.variables.is_empty() {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
@@ -731,14 +727,12 @@ fn visit_semi_nl(&mut self, node: &dyn ast::Token) {
|
||||
}
|
||||
|
||||
fn is_multi_line_brace(&self, node: &dyn ast::Token) -> bool {
|
||||
self.traversal
|
||||
.parent(node.as_node())
|
||||
.as_brace_statement()
|
||||
.is_some_and(|brace_statement| {
|
||||
self.multi_line_brace_statement_locations
|
||||
.binary_search(&brace_statement.source_range().start())
|
||||
.is_ok()
|
||||
})
|
||||
let Kind::BraceStatement(brace) = self.traversal.parent(node.as_node()).kind() else {
|
||||
return false;
|
||||
};
|
||||
self.multi_line_brace_statement_locations
|
||||
.binary_search(&brace.source_range().start())
|
||||
.is_ok()
|
||||
}
|
||||
fn visit_left_brace(&mut self, node: &dyn ast::Token) {
|
||||
let range = node.source_range();
|
||||
@@ -837,29 +831,30 @@ fn prettify_traversal(&mut self) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
match node.typ() {
|
||||
Type::argument | Type::variable_assignment => {
|
||||
|
||||
match node.kind() {
|
||||
Kind::Argument(_) | Kind::VariableAssignment(_) => {
|
||||
self.emit_node_text(node);
|
||||
self.traversal.skip_children(node);
|
||||
}
|
||||
Type::redirection => {
|
||||
self.visit_redirection(node.as_redirection().unwrap());
|
||||
Kind::Redirection(node) => {
|
||||
self.visit_redirection(node);
|
||||
self.traversal.skip_children(node);
|
||||
}
|
||||
Type::maybe_newlines => {
|
||||
self.visit_maybe_newlines(node.as_maybe_newlines().unwrap());
|
||||
Kind::MaybeNewlines(node) => {
|
||||
self.visit_maybe_newlines(node);
|
||||
self.traversal.skip_children(node);
|
||||
}
|
||||
Type::begin_header => {
|
||||
self.visit_begin_header(node.as_begin_header().unwrap());
|
||||
Kind::BeginHeader(node) => {
|
||||
self.visit_begin_header(node);
|
||||
self.traversal.skip_children(node);
|
||||
}
|
||||
_ => {
|
||||
// For branch and list nodes, default is to visit their children.
|
||||
if [Category::branch, Category::list].contains(&node.category()) {
|
||||
continue;
|
||||
}
|
||||
panic!("unexpected node type");
|
||||
// Default is to visit children. We expect all leaves to have been handled above.
|
||||
assert!(
|
||||
node.as_leaf().is_none(),
|
||||
"Should have handled all leaf nodes"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1246,7 +1241,7 @@ struct TokenRange {
|
||||
// Entry point for prettification.
|
||||
fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString {
|
||||
if DUMP_PARSE_TREE.load() {
|
||||
let ast = Ast::parse(
|
||||
let ast = ast::parse(
|
||||
src,
|
||||
ParseTreeFlags::LEAVE_UNTERMINATED
|
||||
| ParseTreeFlags::INCLUDE_COMMENTS
|
||||
@@ -1261,7 +1256,7 @@ fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString {
|
||||
metrics.visit(ast.top());
|
||||
streams.err.appendln(format!("{}", metrics));
|
||||
}
|
||||
let ast = Ast::parse(src, parse_flags(), None);
|
||||
let ast = ast::parse(src, parse_flags(), None);
|
||||
let mut printer = PrettyPrinter::new(src, &ast, do_indent);
|
||||
printer.prettify()
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
use crate::{
|
||||
builtins::shared::BUILTIN_ERR_UNKNOWN,
|
||||
common::{shell_modes, str2wcstring, PROGRAM_NAME},
|
||||
env::env_init,
|
||||
env::{env_init, EnvStack, Environment},
|
||||
future_feature_flags,
|
||||
input_common::{
|
||||
terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent, InputEventQueue,
|
||||
InputEventQueuer, KeyEvent, QueryResponseEvent, TerminalQuery,
|
||||
match_key_event_to_key, terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent,
|
||||
InputEventQueue, InputEventQueuer, KeyEvent, QueryResponseEvent, TerminalQuery,
|
||||
},
|
||||
key::{char_to_symbol, Key, Modifiers},
|
||||
key::{char_to_symbol, Key},
|
||||
nix::isatty,
|
||||
panic::panic_handler,
|
||||
print_help::print_help,
|
||||
@@ -39,19 +40,24 @@
|
||||
use super::prelude::*;
|
||||
|
||||
/// Return true if the recent sequence of characters indicates the user wants to exit the program.
|
||||
fn should_exit(streams: &mut IoStreams, recent_keys: &mut Vec<KeyEvent>, key: KeyEvent) -> bool {
|
||||
recent_keys.push(key);
|
||||
fn should_exit(
|
||||
streams: &mut IoStreams,
|
||||
recent_keys: &mut Vec<KeyEvent>,
|
||||
key_evt: KeyEvent,
|
||||
) -> bool {
|
||||
recent_keys.push(key_evt);
|
||||
|
||||
for evt in [VINTR, VEOF] {
|
||||
let modes = shell_modes();
|
||||
let cc = Key::from_single_byte(modes.c_cc[evt]);
|
||||
|
||||
if key == cc {
|
||||
if match_key_event_to_key(&key_evt, &cc).is_some() {
|
||||
if recent_keys
|
||||
.iter()
|
||||
.rev()
|
||||
.nth(1)
|
||||
.is_some_and(|&prev| prev == cc)
|
||||
.and_then(|prev| match_key_event_to_key(prev, &cc))
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -103,33 +109,27 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool)
|
||||
}
|
||||
streams.out.append(L!("\n"));
|
||||
}
|
||||
let mut print_bind_example = |key: &Key, recommended: bool| {
|
||||
streams.out.append(sprintf!(
|
||||
"bind %s 'do something'%s\n",
|
||||
key,
|
||||
if recommended {
|
||||
" # recommended notation"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
));
|
||||
};
|
||||
let have_shifted_key = kevt.key.shifted_codepoint != '\0';
|
||||
// If we have shift + some other modifier, the lowercase version is the canonical one.
|
||||
let prefer_explicit_shift = kevt.key.modifiers.shift
|
||||
&& kevt.key.modifiers != Modifiers::SHIFT
|
||||
&& kevt
|
||||
.key
|
||||
.shifted_codepoint
|
||||
.to_lowercase()
|
||||
.eq(Some(kevt.key.codepoint).into_iter());
|
||||
let mut keys = vec![(kevt.key.key, "")];
|
||||
if have_shifted_key {
|
||||
let mut shifted_key = kevt.key.key;
|
||||
shifted_key.modifiers.shift = false;
|
||||
shifted_key.codepoint = kevt.key.shifted_codepoint;
|
||||
print_bind_example(&shifted_key, !prefer_explicit_shift);
|
||||
keys.push((shifted_key, "shifted key"));
|
||||
}
|
||||
if kevt.key.base_layout_codepoint != '\0' {
|
||||
let mut base_layout_key = kevt.key.key;
|
||||
base_layout_key.codepoint = kevt.key.base_layout_codepoint;
|
||||
keys.push((base_layout_key, "physical key"));
|
||||
}
|
||||
for (key, explanation) in keys {
|
||||
streams.out.append(sprintf!(
|
||||
"bind %s 'do something'%s%s\n",
|
||||
key,
|
||||
if explanation.is_empty() { "" } else { " # " },
|
||||
explanation,
|
||||
));
|
||||
}
|
||||
print_bind_example(&kevt.key, have_shifted_key && prefer_explicit_shift);
|
||||
|
||||
if continuous_mode && should_exit(streams, &mut recent_chars, kevt.key) {
|
||||
streams.err.appendln("\nExiting at your request.");
|
||||
@@ -268,6 +268,11 @@ fn throwing_main() -> i32 {
|
||||
threads::init();
|
||||
env_init(None, true, false);
|
||||
reader_init(false);
|
||||
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
||||
for s in features_var.as_list() {
|
||||
future_feature_flags::set_from_string(s.as_utfstr());
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = Fd(FdOutputStream::new(STDOUT_FILENO));
|
||||
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
use crate::wutil::encoding::zero_mbstate;
|
||||
use crate::wutil::perror;
|
||||
use libc::SEEK_CUR;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -244,7 +245,7 @@ fn read_interactive(
|
||||
|
||||
let mline = {
|
||||
let _interactive = parser.push_scope(|s| s.is_interactive = true);
|
||||
reader_readline(parser, nchars)
|
||||
reader_readline(parser, NonZeroUsize::try_from(nchars).ok())
|
||||
};
|
||||
terminal_protocols_disable_ifn();
|
||||
if let Some(line) = mline {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use super::prelude::*;
|
||||
use crate::color::Color;
|
||||
use crate::common::str2wcstring;
|
||||
use crate::screen::is_dumb;
|
||||
use crate::screen::{is_dumb, only_grayscale};
|
||||
use crate::terminal::{use_terminfo, Outputter};
|
||||
use crate::text_face::{
|
||||
self, parse_text_face_and_options, PrintColorsArgs, SpecifiedTextFace, TextFace, TextStyling,
|
||||
@@ -128,7 +128,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
));
|
||||
|
||||
if specified_face.fg.is_some() && outp.contents().is_empty() {
|
||||
assert!(is_dumb() || use_terminfo());
|
||||
assert!(is_dumb() || only_grayscale() || use_terminfo());
|
||||
// We need to do *something* or the lack of any output messes up
|
||||
// when the cartesian product here would make "foo" disappear:
|
||||
// $ echo (set_color foo)bar
|
||||
|
||||
@@ -672,11 +672,10 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
STATUS_CURRENT_CMD => {
|
||||
let command = &parser.libdata().status_vars.command;
|
||||
if !command.is_empty() {
|
||||
streams.out.append(command);
|
||||
streams.out.appendln(command);
|
||||
} else {
|
||||
streams.out.appendln(*PROGRAM_NAME.get().unwrap());
|
||||
}
|
||||
streams.out.append_char('\n');
|
||||
}
|
||||
STATUS_CURRENT_COMMANDLINE => {
|
||||
let commandline = &parser.libdata().status_vars.commandline;
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
use crate::wutil::encoding::{mbrtowc, wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX};
|
||||
use crate::wutil::fish_iswalnum;
|
||||
use bitflags::bitflags;
|
||||
use core::slice;
|
||||
use libc::{EIO, O_WRONLY, SIGTTOU, SIG_IGN, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
@@ -1184,15 +1183,12 @@ pub fn truncate_at_nul(input: &wstr) -> &wstr {
|
||||
}
|
||||
|
||||
pub fn cstr2wcstring(input: &[u8]) -> WString {
|
||||
let strlen = input.iter().position(|c| *c == b'\0').unwrap();
|
||||
str2wcstring(&input[0..strlen])
|
||||
let input = CStr::from_bytes_until_nul(input).unwrap().to_bytes();
|
||||
str2wcstring(input)
|
||||
}
|
||||
|
||||
pub(crate) fn charptr2wcstring(input: *const libc::c_char) -> WString {
|
||||
let input: &[u8] = unsafe {
|
||||
let strlen = libc::strlen(input);
|
||||
slice::from_raw_parts(input.cast(), strlen)
|
||||
};
|
||||
let input: &[u8] = unsafe { CStr::from_ptr(input).to_bytes() };
|
||||
str2wcstring(input)
|
||||
}
|
||||
|
||||
@@ -1340,7 +1336,7 @@ fn can_be_encoded(wc: char) -> bool {
|
||||
/// Return the number of bytes read, or 0 on EOF, or an error.
|
||||
pub fn read_blocked(fd: RawFd, buf: &mut [u8]) -> nix::Result<usize> {
|
||||
loop {
|
||||
let res = nix::unistd::read(fd, buf);
|
||||
let res = nix::unistd::read(unsafe { BorrowedFd::borrow_raw(fd) }, buf);
|
||||
if let Err(nix::Error::EINTR) = res {
|
||||
continue;
|
||||
}
|
||||
@@ -1563,11 +1559,13 @@ pub fn is_windows_subsystem_for_linux(v: WSL) -> bool {
|
||||
}
|
||||
|
||||
let wsl = RESULT.get_or_init(|| {
|
||||
let mut info: libc::utsname = unsafe { mem::zeroed() };
|
||||
let release: &[u8] = unsafe {
|
||||
libc::uname(&mut info);
|
||||
std::mem::transmute(&info.release[..])
|
||||
let release = unsafe {
|
||||
let mut info = mem::MaybeUninit::uninit();
|
||||
libc::uname(info.as_mut_ptr());
|
||||
let info = info.assume_init();
|
||||
info.release
|
||||
};
|
||||
let release: &[u8] = unsafe { std::mem::transmute(&release[..]) };
|
||||
|
||||
// Sample utsname.release under WSLv2, testing for something like `4.19.104-microsoft-standard`
|
||||
// or `5.10.16.3-microsoft-standard-WSL2`
|
||||
@@ -1635,7 +1633,7 @@ pub fn fish_reserved_codepoint(c: char) -> bool {
|
||||
|
||||
pub fn redirect_tty_output(in_signal_handler: bool) {
|
||||
unsafe {
|
||||
let mut t: libc::termios = mem::zeroed();
|
||||
let mut t = mem::MaybeUninit::uninit();
|
||||
let s = CStr::from_bytes_with_nul(b"/dev/null\0").unwrap();
|
||||
let fd = libc::open(s.as_ptr(), O_WRONLY);
|
||||
if in_signal_handler && fd == -1 {
|
||||
@@ -1644,7 +1642,7 @@ pub fn redirect_tty_output(in_signal_handler: bool) {
|
||||
assert!(fd != -1, "Could not open /dev/null!");
|
||||
}
|
||||
for stdfd in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] {
|
||||
if libc::tcgetattr(stdfd, &mut t) == -1 && errno::errno().0 == EIO {
|
||||
if libc::tcgetattr(stdfd, t.as_mut_ptr()) == -1 && errno::errno().0 == EIO {
|
||||
libc::dup2(fd, stdfd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
ast::unescape_keyword,
|
||||
common::charptr2wcstring,
|
||||
reader::{get_quote, is_backslashed},
|
||||
tokenizer::is_brace_statement,
|
||||
util::wcsfilecmp,
|
||||
wutil::sprintf,
|
||||
};
|
||||
@@ -667,20 +666,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
|
||||
|
||||
// Get all the arguments.
|
||||
let mut tokens = Vec::new();
|
||||
{
|
||||
let proc_range =
|
||||
parse_util_process_extent(&cmdline, position_in_statement, Some(&mut tokens));
|
||||
let start = proc_range.start;
|
||||
if start != 0
|
||||
&& cmdline.as_char_slice()[start - 1] == '{'
|
||||
&& (start == cmdline.len()
|
||||
|| !is_brace_statement(cmdline.as_char_slice().get(start).copied()))
|
||||
{
|
||||
// We don't want to suggest commands here, since this command line parses as
|
||||
// brace expansion.
|
||||
return;
|
||||
}
|
||||
}
|
||||
parse_util_process_extent(&cmdline, position_in_statement, Some(&mut tokens));
|
||||
let actual_token_count = tokens.len();
|
||||
|
||||
// Hack: fix autosuggestion by removing prefixing "and"s #6249.
|
||||
@@ -2357,14 +2343,14 @@ pub fn complete_remove(cmd: WString, cmd_is_path: bool, option: &wstr, typ: Comp
|
||||
}
|
||||
|
||||
/// Removes all completions for a given command.
|
||||
pub fn complete_remove_all(cmd: WString, cmd_is_path: bool) {
|
||||
pub fn complete_remove_all(cmd: WString, cmd_is_path: bool, explicit: bool) {
|
||||
let mut completion_map = COMPLETION_MAP.lock().expect("mutex poisoned");
|
||||
let idx = CompletionEntryIndex {
|
||||
name: cmd,
|
||||
is_path: cmd_is_path,
|
||||
};
|
||||
let removed = completion_map.remove(&idx).is_some();
|
||||
if !removed && !idx.is_path {
|
||||
if explicit && !removed && !idx.is_path {
|
||||
COMPLETION_TOMBSTONES.lock().unwrap().insert(idx.name);
|
||||
}
|
||||
}
|
||||
@@ -2537,7 +2523,7 @@ pub fn complete_invalidate_path() {
|
||||
.expect("mutex poisoned")
|
||||
.get_autoloaded_commands();
|
||||
for cmd in cmds {
|
||||
complete_remove_all(cmd, false /* not a path */);
|
||||
complete_remove_all(cmd, /*cmd_is_path=*/ false, /*explicit=*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
src/env/config_paths.rs
vendored
7
src/env/config_paths.rs
vendored
@@ -134,7 +134,7 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||
FLOGF!(
|
||||
config,
|
||||
"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \
|
||||
%ls\npaths.doc: %ls\npaths.bin: %ls",
|
||||
%ls\npaths.doc: %ls\npaths.bin: %ls\npaths.locale: %ls",
|
||||
paths
|
||||
.data
|
||||
.clone()
|
||||
@@ -147,6 +147,11 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||
.clone()
|
||||
.map(|x| x.display().to_string())
|
||||
.unwrap_or("|not found|".to_string()),
|
||||
paths
|
||||
.locale
|
||||
.clone()
|
||||
.map(|x| x.display().to_string())
|
||||
.unwrap_or("|not found|".to_string()),
|
||||
);
|
||||
|
||||
paths
|
||||
|
||||
@@ -801,7 +801,7 @@ fn save(&mut self, directory: &wstr) -> bool {
|
||||
{
|
||||
let mut times: [libc::timespec; 2] = unsafe { std::mem::zeroed() };
|
||||
times[0].tv_nsec = libc::UTIME_OMIT; // don't change ctime
|
||||
if unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut times[1]) } != 0 {
|
||||
if unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut times[1]) } == 0 {
|
||||
unsafe {
|
||||
libc::futimens(private_file.as_raw_fd(), ×[0]);
|
||||
}
|
||||
|
||||
35
src/exec.rs
35
src/exec.rs
@@ -31,9 +31,7 @@
|
||||
};
|
||||
use crate::libc::_PATH_BSHELL;
|
||||
use crate::nix::isatty;
|
||||
use crate::null_terminated_array::{
|
||||
null_terminated_array_length, AsNullTerminatedArray, OwningNullTerminatedArray,
|
||||
};
|
||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
|
||||
#[cfg(FISH_USE_POSIX_SPAWN)]
|
||||
use crate::proc::Pid;
|
||||
@@ -52,13 +50,14 @@
|
||||
use crate::wutil::{wgettext, wgettext_fmt};
|
||||
use errno::{errno, set_errno};
|
||||
use libc::{
|
||||
c_char, EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
|
||||
EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
|
||||
STDIN_FILENO, STDOUT_FILENO,
|
||||
};
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::sys::stat;
|
||||
use std::ffi::CStr;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
use std::slice;
|
||||
@@ -388,8 +387,8 @@ pub fn is_thompson_shell_script(path: &CStr) -> bool {
|
||||
fn safe_launch_process(
|
||||
_p: &Process,
|
||||
actual_cmd: &CStr,
|
||||
argv: &impl AsNullTerminatedArray<CharType = c_char>,
|
||||
envv: &impl AsNullTerminatedArray<CharType = c_char>,
|
||||
argv: &OwningNullTerminatedArray,
|
||||
envv: &OwningNullTerminatedArray,
|
||||
) -> ! {
|
||||
// This function never returns, so we take certain liberties with constness.
|
||||
|
||||
@@ -405,7 +404,7 @@ fn safe_launch_process(
|
||||
// Construct new argv.
|
||||
// We must not allocate memory, so only 128 args are supported.
|
||||
const maxargs: usize = 128;
|
||||
let nargs = null_terminated_array_length(argv.get());
|
||||
let nargs = argv.len();
|
||||
let argv = unsafe { slice::from_raw_parts(argv.get(), nargs) };
|
||||
if nargs <= maxargs {
|
||||
// +1 for /bin/sh, +1 for terminating nullptr
|
||||
@@ -422,7 +421,7 @@ fn safe_launch_process(
|
||||
}
|
||||
|
||||
set_errno(err);
|
||||
safe_report_exec_error(errno().0, actual_cmd, argv.get(), envv.get());
|
||||
safe_report_exec_error(errno().0, actual_cmd, argv, envv);
|
||||
exit_without_destructors(exit_code_from_exec_error(err.0));
|
||||
}
|
||||
|
||||
@@ -442,7 +441,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
restore_term_mode(false);
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &*envp);
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envp);
|
||||
}
|
||||
|
||||
// Returns whether we can use posix spawn for a given process in a given job.
|
||||
@@ -481,8 +480,11 @@ fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut blocked_signals: libc::sigset_t = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigemptyset(&mut blocked_signals) };
|
||||
let mut blocked_signals = MaybeUninit::uninit();
|
||||
let mut blocked_signals = unsafe {
|
||||
libc::sigemptyset(blocked_signals.as_mut_ptr());
|
||||
blocked_signals.assume_init()
|
||||
};
|
||||
let blocked_signals = if blocked_signals_for_job(j, &mut blocked_signals) {
|
||||
Some(&blocked_signals)
|
||||
} else {
|
||||
@@ -685,8 +687,11 @@ fn fork_child_for_process(
|
||||
};
|
||||
|
||||
// Decide if the job wants to set a custom sigmask.
|
||||
let mut blocked_signals: libc::sigset_t = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigemptyset(&mut blocked_signals) };
|
||||
let mut blocked_signals = MaybeUninit::uninit();
|
||||
let mut blocked_signals = unsafe {
|
||||
libc::sigemptyset(blocked_signals.as_mut_ptr());
|
||||
blocked_signals.assume_init()
|
||||
};
|
||||
let blocked_signals = if blocked_signals_for_job(job, &mut blocked_signals) {
|
||||
Some(&blocked_signals)
|
||||
} else {
|
||||
@@ -858,7 +863,7 @@ fn exec_external_command(
|
||||
let pid = match pid {
|
||||
Ok(pid) => pid,
|
||||
Err(err) => {
|
||||
safe_report_exec_error(err.0, &actual_cmd, argv.get(), envv.get());
|
||||
safe_report_exec_error(err.0, &actual_cmd, &argv, &envv);
|
||||
p.status
|
||||
.update(&ProcStatus::from_exit_code(exit_code_from_exec_error(
|
||||
err.0,
|
||||
@@ -897,7 +902,7 @@ fn exec_external_command(
|
||||
}
|
||||
|
||||
fork_child_for_process(j, p, &dup2s, L!("external command"), |p| {
|
||||
safe_launch_process(p, &actual_cmd, &argv, &*envv)
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envv)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
use crate::wildcard::{WildcardResult, ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
|
||||
use crate::wutil::{normalize_path, wcstoi_partial, Options};
|
||||
use bitflags::bitflags;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
bitflags! {
|
||||
/// Set of flags controlling expansions.
|
||||
@@ -1157,19 +1158,20 @@ fn expand_home_directory(input: &mut WString, vars: &dyn Environment) {
|
||||
} else {
|
||||
// Some other user's home directory.
|
||||
let name_cstr = wcs2zstring(username);
|
||||
let mut userinfo: libc::passwd = unsafe { std::mem::zeroed() };
|
||||
let mut userinfo = MaybeUninit::uninit();
|
||||
let mut result: *mut libc::passwd = std::ptr::null_mut();
|
||||
let mut buf = [0 as libc::c_char; 8192];
|
||||
let retval = unsafe {
|
||||
libc::getpwnam_r(
|
||||
name_cstr.as_ptr(),
|
||||
&mut userinfo,
|
||||
userinfo.as_mut_ptr(),
|
||||
&mut buf[0],
|
||||
std::mem::size_of_val(&buf),
|
||||
&mut result,
|
||||
)
|
||||
};
|
||||
if retval == 0 && !result.is_null() {
|
||||
let userinfo = unsafe { userinfo.assume_init() };
|
||||
home = Some(charptr2wcstring(userinfo.pw_dir));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,12 @@ pub struct FdReadableSet {
|
||||
impl FdReadableSet {
|
||||
/// Construct an empty set.
|
||||
pub fn new() -> FdReadableSet {
|
||||
FdReadableSet {
|
||||
fdset_: unsafe { std::mem::zeroed() },
|
||||
nfds_: 0,
|
||||
}
|
||||
let mut fdset_ = std::mem::MaybeUninit::uninit();
|
||||
let fdset_ = unsafe {
|
||||
libc::FD_ZERO(fdset_.as_mut_ptr());
|
||||
fdset_.assume_init()
|
||||
};
|
||||
FdReadableSet { fdset_, nfds_: 0 }
|
||||
}
|
||||
|
||||
/// Reset back to an empty set.
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct AutoCloseFd {
|
||||
|
||||
impl Read for AutoCloseFd {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
nix::unistd::read(self.as_raw_fd(), buf).map_err(std::io::Error::from)
|
||||
nix::unistd::read(self, buf).map_err(std::io::Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ fn heightenize_fd(fd: OwnedFd, input_has_cloexec: bool) -> nix::Result<OwnedFd>
|
||||
}
|
||||
|
||||
// Here we are asking the kernel to give us a cloexec fd.
|
||||
let newfd = match nix::fcntl::fcntl(raw_fd, FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD)) {
|
||||
let newfd = match nix::fcntl::fcntl(&fd, FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD)) {
|
||||
Ok(newfd) => newfd,
|
||||
Err(err) => {
|
||||
perror("fcntl");
|
||||
@@ -238,7 +238,7 @@ pub fn open_cloexec(path: &CStr, flags: OFlag, mode: nix::sys::stat::Mode) -> ni
|
||||
// If it is that's our cancel signal, so we abort.
|
||||
loop {
|
||||
let ret = nix::fcntl::open(path, flags | OFlag::O_CLOEXEC, mode);
|
||||
let ret = ret.map(|raw_fd| unsafe { File::from_raw_fd(raw_fd) });
|
||||
let ret = ret.map(File::from);
|
||||
match ret {
|
||||
Ok(file) => {
|
||||
return Ok(file);
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn all_categories() -> Vec<&'static category_t> {
|
||||
(char_encoding, "char-encoding", "Character encoding issues");
|
||||
|
||||
(history, "history", "Command history events");
|
||||
(history_file, "history-file", "Reading/Writing the history file", true);
|
||||
(history_file, "history-file", "Reading/Writing the history file");
|
||||
|
||||
(profile_history, "profile-history", "History performance measurements");
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
// That means no locking, no allocating, no freeing memory, etc!
|
||||
use super::flog_safe::FLOG_SAFE;
|
||||
use crate::nix::getpid;
|
||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||
use crate::proc::Pid;
|
||||
use crate::redirection::Dup2List;
|
||||
use crate::signal::signal_reset_handlers;
|
||||
use crate::{common::exit_without_destructors, wutil::fstat};
|
||||
use libc::{c_char, pid_t, O_RDONLY};
|
||||
use libc::{pid_t, O_RDONLY};
|
||||
use std::ffi::CStr;
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
@@ -35,20 +36,6 @@ fn clear_cloexec(fd: i32) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe strlen(). Note strlen is async-signal safe as of POSIX.1-2008
|
||||
/// but we just implement our own.
|
||||
fn strlen_safe(s: *const libc::c_char) -> usize {
|
||||
let mut len = 0;
|
||||
let mut cursor: *const libc::c_char = s;
|
||||
unsafe {
|
||||
while *cursor != 0 {
|
||||
len += 1;
|
||||
cursor = cursor.offset(1);
|
||||
}
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
/// Report the error code for a failed setpgid call.
|
||||
pub(crate) fn report_setpgid_error(
|
||||
err: i32,
|
||||
@@ -243,29 +230,13 @@ pub fn execute_fork() -> pid_t {
|
||||
pub(crate) fn safe_report_exec_error(
|
||||
err: i32,
|
||||
actual_cmd: &CStr,
|
||||
argvv: *const *const c_char,
|
||||
envv: *const *const c_char,
|
||||
argvv: &OwningNullTerminatedArray,
|
||||
envv: &OwningNullTerminatedArray,
|
||||
) {
|
||||
match err {
|
||||
libc::E2BIG => {
|
||||
let mut sz = 0;
|
||||
let mut szenv = 0;
|
||||
unsafe {
|
||||
// Compute total size of argv.
|
||||
let mut cursor = argvv;
|
||||
while !(*cursor).is_null() {
|
||||
sz += strlen_safe(*cursor) + 1;
|
||||
cursor = cursor.offset(1);
|
||||
}
|
||||
|
||||
// Compute total size of envp.
|
||||
cursor = envv;
|
||||
while !(*cursor).is_null() {
|
||||
szenv += strlen_safe(*cursor) + 1;
|
||||
cursor = cursor.offset(1);
|
||||
}
|
||||
sz += szenv;
|
||||
}
|
||||
let szenv = envv.iter().map(|s| s.to_bytes().len()).sum::<usize>();
|
||||
let sz = szenv + argvv.iter().map(|s| s.to_bytes().len()).sum::<usize>();
|
||||
|
||||
let arg_max = unsafe { libc::sysconf(libc::_SC_ARG_MAX) };
|
||||
if arg_max > 0 {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use errno::Errno;
|
||||
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
// The posix_spawn family of functions is unusual in that it returns errno codes directly in the return value, not via errno.
|
||||
@@ -25,9 +26,9 @@ fn check_fail(res: i32) -> Result<(), Errno> {
|
||||
impl Attr {
|
||||
fn new() -> Result<Self, Errno> {
|
||||
unsafe {
|
||||
let mut attr: posix_spawnattr_t = std::mem::zeroed();
|
||||
check_fail(libc::posix_spawnattr_init(&mut attr))?;
|
||||
Ok(Self(attr))
|
||||
let mut attr = MaybeUninit::uninit();
|
||||
check_fail(libc::posix_spawnattr_init(attr.as_mut_ptr()))?;
|
||||
Ok(Self(attr.assume_init()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +63,9 @@ fn drop(&mut self) {
|
||||
impl FileActions {
|
||||
fn new() -> Result<Self, Errno> {
|
||||
unsafe {
|
||||
let mut actions: posix_spawn_file_actions_t = std::mem::zeroed();
|
||||
check_fail(libc::posix_spawn_file_actions_init(&mut actions))?;
|
||||
Ok(Self(actions))
|
||||
let mut actions = MaybeUninit::uninit();
|
||||
check_fail(libc::posix_spawn_file_actions_init(actions.as_mut_ptr()))?;
|
||||
Ok(Self(actions.assume_init()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +130,9 @@ pub fn new(j: &Job, dup2s: &Dup2List) -> Result<PosixSpawner, Errno> {
|
||||
attr.set_sigdefault(&signals_to_default)?;
|
||||
|
||||
// Reset the sigmask.
|
||||
let mut sigmask = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigemptyset(&mut sigmask) };
|
||||
let mut sigmask = MaybeUninit::uninit();
|
||||
unsafe { libc::sigemptyset(sigmask.as_mut_ptr()) };
|
||||
let mut sigmask = unsafe { sigmask.assume_init() };
|
||||
blocked_signals_for_job(j, &mut sigmask);
|
||||
attr.set_sigmask(&sigmask)?;
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
//! Functions for syntax highlighting.
|
||||
use crate::abbrs::{self, with_abbrs};
|
||||
use crate::ast::{
|
||||
self, Argument, Ast, BlockStatement, BlockStatementHeaderVariant, BraceStatement,
|
||||
DecoratedStatement, Keyword, List, Node, NodeVisitor, Redirection, Token, Type,
|
||||
VariableAssignment,
|
||||
self, Argument, BlockStatement, BlockStatementHeader, BraceStatement, DecoratedStatement,
|
||||
Keyword, Kind, Node, NodeVisitor, Redirection, Token, VariableAssignment,
|
||||
};
|
||||
use crate::builtins::shared::builtin_exists;
|
||||
use crate::color::Color;
|
||||
@@ -272,15 +271,16 @@ fn autosuggest_parse_command(
|
||||
buff: &wstr,
|
||||
ctx: &OperationContext<'_>,
|
||||
) -> Option<(WString, WString)> {
|
||||
let ast = Ast::parse(
|
||||
let ast = ast::parse(
|
||||
buff,
|
||||
ParseTreeFlags::CONTINUE_AFTER_ERROR | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS,
|
||||
None,
|
||||
);
|
||||
|
||||
// Find the first statement.
|
||||
let jc = ast.top().as_job_list().unwrap().get(0)?;
|
||||
let first_statement = jc.job.statement.contents.as_decorated_statement()?;
|
||||
let job_list: &ast::JobList = ast.top();
|
||||
let jc = job_list.get(0)?;
|
||||
let first_statement = jc.job.statement.as_decorated_statement()?;
|
||||
|
||||
if let Some(expanded_command) = statement_get_expanded_command(buff, first_statement, ctx) {
|
||||
let mut arg = WString::new();
|
||||
@@ -709,7 +709,7 @@ pub fn highlight(&mut self) -> ColorArray {
|
||||
| ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS
|
||||
| ParseTreeFlags::LEAVE_UNTERMINATED
|
||||
| ParseTreeFlags::SHOW_EXTRA_SEMIS;
|
||||
let ast = Ast::parse(self.buff, ast_flags, None);
|
||||
let ast = ast::parse(self.buff, ast_flags, None);
|
||||
|
||||
self.visit_children(ast.top());
|
||||
if self.ctx.check_cancel() {
|
||||
@@ -832,7 +832,7 @@ fn color_range(&mut self, range: SourceRange, color: HighlightSpec) {
|
||||
|
||||
// Visit the children of a node.
|
||||
fn visit_children(&mut self, node: &dyn Node) {
|
||||
node.accept(self, false);
|
||||
node.accept(self);
|
||||
}
|
||||
// AST visitor implementations.
|
||||
fn visit_keyword(&mut self, node: &dyn Keyword) {
|
||||
@@ -1044,15 +1044,14 @@ fn visit_decorated_statement(&mut self, stmt: &DecoratedStatement) {
|
||||
}
|
||||
fn visit_block_statement(&mut self, block: &BlockStatement) {
|
||||
match &block.header {
|
||||
BlockStatementHeaderVariant::None => panic!(),
|
||||
BlockStatementHeaderVariant::ForHeader(node) => self.visit(node),
|
||||
BlockStatementHeaderVariant::WhileHeader(node) => self.visit(node),
|
||||
BlockStatementHeaderVariant::FunctionHeader(node) => self.visit(node),
|
||||
BlockStatementHeaderVariant::BeginHeader(node) => self.visit(node),
|
||||
BlockStatementHeader::For(node) => self.visit(node),
|
||||
BlockStatementHeader::While(node) => self.visit(node),
|
||||
BlockStatementHeader::Function(node) => self.visit(node),
|
||||
BlockStatementHeader::Begin(node) => self.visit(node),
|
||||
}
|
||||
self.visit(&block.args_or_redirs);
|
||||
let pending_variables_count = self.pending_variables.len();
|
||||
if let Some(fh) = block.header.as_for_header() {
|
||||
if let BlockStatementHeader::For(fh) = &block.header {
|
||||
let var_name = fh.var_name.source(self.buff);
|
||||
self.pending_variables.push(var_name);
|
||||
}
|
||||
@@ -1114,17 +1113,13 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
self.visit_token(token);
|
||||
return;
|
||||
}
|
||||
match node.typ() {
|
||||
Type::argument => self.visit_argument(node.as_argument().unwrap(), false, true),
|
||||
Type::redirection => self.visit_redirection(node.as_redirection().unwrap()),
|
||||
Type::variable_assignment => {
|
||||
self.visit_variable_assignment(node.as_variable_assignment().unwrap())
|
||||
}
|
||||
Type::decorated_statement => {
|
||||
self.visit_decorated_statement(node.as_decorated_statement().unwrap())
|
||||
}
|
||||
Type::block_statement => self.visit_block_statement(node.as_block_statement().unwrap()),
|
||||
Type::brace_statement => self.visit_brace_statement(node.as_brace_statement().unwrap()),
|
||||
match node.kind() {
|
||||
Kind::Argument(node) => self.visit_argument(node, false, true),
|
||||
Kind::Redirection(node) => self.visit_redirection(node),
|
||||
Kind::VariableAssignment(node) => self.visit_variable_assignment(node),
|
||||
Kind::DecoratedStatement(node) => self.visit_decorated_statement(node),
|
||||
Kind::BlockStatement(node) => self.visit_block_statement(node),
|
||||
Kind::BraceStatement(node) => self.visit_brace_statement(node),
|
||||
// Default implementation is to just visit children.
|
||||
_ => self.visit_children(node),
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
ffi::CString,
|
||||
fs::File,
|
||||
io::{BufRead, Read, Seek, SeekFrom, Write},
|
||||
mem::MaybeUninit,
|
||||
num::NonZeroUsize,
|
||||
ops::ControlFlow,
|
||||
os::{fd::AsRawFd, unix::fs::MetadataExt},
|
||||
@@ -41,13 +42,13 @@
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use libc::{fchown, flock, LOCK_EX, LOCK_SH, LOCK_UN};
|
||||
use libc::{fchown, flock, EINTR, LOCK_EX, LOCK_SH, LOCK_UN};
|
||||
use lru::LruCache;
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{
|
||||
ast::{Ast, Node},
|
||||
ast::{self, Kind, Node},
|
||||
common::{
|
||||
str2wcstring, unescape_string, valid_var_name, wcs2zstring, CancelChecker,
|
||||
UnescapeStringStyle,
|
||||
@@ -1358,8 +1359,15 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
let start_time = SystemTime::now();
|
||||
let retval = unsafe { flock(file.as_raw_fd(), lock_type) };
|
||||
let (ok, start_time) = loop {
|
||||
let start_time = SystemTime::now();
|
||||
if unsafe { flock(file.as_raw_fd(), lock_type) } != -1 {
|
||||
break (true, start_time);
|
||||
}
|
||||
if errno::errno().0 != EINTR {
|
||||
break (false, start_time);
|
||||
}
|
||||
};
|
||||
if let Ok(duration) = start_time.elapsed() {
|
||||
if duration > Duration::from_millis(250) {
|
||||
FLOG!(
|
||||
@@ -1372,7 +1380,7 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
|
||||
ABANDONED_LOCKING.store(true);
|
||||
}
|
||||
}
|
||||
retval != -1
|
||||
ok
|
||||
}
|
||||
|
||||
/// Unlock a history file.
|
||||
@@ -1443,9 +1451,9 @@ fn format_history_record(
|
||||
// This warns for musl, but the warning is useless to us - there is nothing we can or should do.
|
||||
#[allow(deprecated)]
|
||||
let seconds = seconds as libc::time_t;
|
||||
let mut timestamp: libc::tm = unsafe { std::mem::zeroed() };
|
||||
let mut timestamp = MaybeUninit::uninit();
|
||||
if let Some(show_time_format) = show_time_format.and_then(|s| CString::new(s).ok()) {
|
||||
if !unsafe { libc::localtime_r(&seconds, &mut timestamp).is_null() } {
|
||||
if !unsafe { libc::localtime_r(&seconds, timestamp.as_mut_ptr()).is_null() } {
|
||||
const max_tstamp_length: usize = 100;
|
||||
let mut timestamp_str = [0_u8; max_tstamp_length];
|
||||
use libc::strftime;
|
||||
@@ -1454,7 +1462,7 @@ fn format_history_record(
|
||||
&mut timestamp_str[0] as *mut u8 as *mut libc::c_char,
|
||||
max_tstamp_length,
|
||||
show_time_format.as_ptr(),
|
||||
×tamp,
|
||||
timestamp.as_ptr(),
|
||||
)
|
||||
} != 0
|
||||
{
|
||||
@@ -1496,7 +1504,7 @@ fn should_import_bash_history_line(line: &wstr) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
if Ast::parse(line, ParseTreeFlags::empty(), None).errored() {
|
||||
if ast::parse(line, ParseTreeFlags::empty(), None).errored() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1581,16 +1589,16 @@ pub fn add_pending_with_file_detection(
|
||||
|
||||
// Find all arguments that look like they could be file paths.
|
||||
let mut needs_sync_write = false;
|
||||
let ast = Ast::parse(s, ParseTreeFlags::empty(), None);
|
||||
let ast = ast::parse(s, ParseTreeFlags::empty(), None);
|
||||
|
||||
let mut potential_paths = Vec::new();
|
||||
for node in ast.walk() {
|
||||
if let Some(arg) = node.as_argument() {
|
||||
if let Kind::Argument(arg) = node.kind() {
|
||||
let potential_path = arg.source(s);
|
||||
if string_could_be_path(potential_path) {
|
||||
potential_paths.push(potential_path.to_owned());
|
||||
}
|
||||
} else if let Some(stmt) = node.as_decorated_statement() {
|
||||
} else if let Kind::DecoratedStatement(stmt) = node.kind() {
|
||||
// Hack hack hack - if the command is likely to trigger an exit, then don't do
|
||||
// background file detection, because we won't be able to write it to our history file
|
||||
// before we exit.
|
||||
|
||||
121
src/input.rs
121
src/input.rs
@@ -7,8 +7,8 @@
|
||||
use crate::future::IsSomeAnd;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::{
|
||||
CharEvent, CharInputStyle, ImplicitEvent, InputData, InputEventQueuer, ReadlineCmd,
|
||||
TerminalQuery, R_END_INPUT_FUNCTIONS,
|
||||
match_key_event_to_key, CharEvent, CharInputStyle, ImplicitEvent, InputData, InputEventQueuer,
|
||||
KeyMatchQuality, ReadlineCmd, TerminalQuery, R_END_INPUT_FUNCTIONS,
|
||||
};
|
||||
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
|
||||
use crate::proc::job_reap;
|
||||
@@ -20,6 +20,7 @@
|
||||
use crate::wchar::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::RefMut;
|
||||
use std::mem;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex, MutexGuard,
|
||||
@@ -502,14 +503,19 @@ fn next(&mut self) -> CharEvent {
|
||||
|
||||
/// Check if the next event is the given character. This advances the index on success only.
|
||||
/// If `escaped` is set, then return false if this (or any other) character had a timeout.
|
||||
fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> bool {
|
||||
fn next_is_char(
|
||||
&mut self,
|
||||
style: &KeyNameStyle,
|
||||
key: Key,
|
||||
escaped: bool,
|
||||
) -> Option<KeyMatchQuality> {
|
||||
assert!(
|
||||
self.idx <= self.peeked.len(),
|
||||
"Index must not be larger than dequeued event count"
|
||||
);
|
||||
// See if we had a timeout already.
|
||||
if escaped && self.had_timeout {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
// Grab a new event if we have exhausted what we have already peeked.
|
||||
// Use either readch or readch_timed, per our param.
|
||||
@@ -520,7 +526,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
Some(evt) => evt,
|
||||
None => {
|
||||
self.had_timeout = true;
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -529,7 +535,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
Some(evt) => evt,
|
||||
None => {
|
||||
self.had_timeout = true;
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -539,9 +545,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
// Now we have peeked far enough; check the event.
|
||||
// If it matches the char, then increment the index.
|
||||
let evt = &self.peeked[self.idx];
|
||||
let Some(kevt) = evt.get_key() else {
|
||||
return false;
|
||||
};
|
||||
let kevt = evt.get_key()?;
|
||||
if kevt.seq == L!("\x1b") && key.modifiers == Modifiers::ALT {
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
@@ -549,13 +553,13 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
|
||||
}
|
||||
if *style == KeyNameStyle::Plain {
|
||||
if kevt.key == key {
|
||||
let result = match_key_event_to_key(&kevt.key, &key);
|
||||
if let Some(key_match) = &result {
|
||||
assert!(self.subidx == 0);
|
||||
self.idx += 1;
|
||||
FLOG!(reader, "matched full key", key);
|
||||
return true;
|
||||
FLOG!(reader, "matched full key", key, "kind", key_match);
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
let actual_seq = kevt.seq.as_char_slice();
|
||||
if !actual_seq.is_empty() {
|
||||
@@ -575,7 +579,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
actual_seq.len()
|
||||
)
|
||||
);
|
||||
return true;
|
||||
return Some(KeyMatchQuality::Legacy);
|
||||
}
|
||||
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
|
||||
if self.subidx + 1 == actual_seq.len() {
|
||||
@@ -595,11 +599,11 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
self.subidx = 0;
|
||||
}
|
||||
FLOG!(reader, format!("matched {key} against raw escape sequence"));
|
||||
return true;
|
||||
return Some(KeyMatchQuality::Legacy);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
/// Consume all events up to the current index.
|
||||
@@ -627,7 +631,12 @@ pub fn restart(&mut self) {
|
||||
}
|
||||
|
||||
/// Return true if this `peeker` matches a given sequence of char events given by `str`.
|
||||
fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
fn try_peek_sequence(
|
||||
&mut self,
|
||||
style: &KeyNameStyle,
|
||||
seq: &[Key],
|
||||
quality: &mut Vec<KeyMatchQuality>,
|
||||
) -> bool {
|
||||
assert!(
|
||||
!seq.is_empty(),
|
||||
"Empty sequence passed to try_peek_sequence"
|
||||
@@ -637,9 +646,10 @@ fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
// If we just read an escape, we need to add a timeout for the next char,
|
||||
// to distinguish between the actual escape key and an "alt"-modifier.
|
||||
let escaped = *style != KeyNameStyle::Plain && prev == Key::from_raw(key::Escape);
|
||||
if !self.next_is_char(style, *key, escaped) {
|
||||
let Some(spec) = self.next_is_char(style, *key, escaped) else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
quality.push(spec);
|
||||
prev = *key;
|
||||
}
|
||||
if self.subidx != 0 {
|
||||
@@ -656,16 +666,24 @@ fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
/// user's mapping list, then the preset list.
|
||||
/// Return none if nothing matches, or if we may have matched a longer sequence but it was
|
||||
/// interrupted by a readline event.
|
||||
pub fn find_mapping(
|
||||
pub fn find_mapping<'a>(
|
||||
&mut self,
|
||||
vars: &dyn Environment,
|
||||
ip: &InputMappingSet,
|
||||
ip: &'a InputMappingSet,
|
||||
) -> Option<InputMapping> {
|
||||
let mut generic: Option<&InputMapping> = None;
|
||||
let bind_mode = input_get_bind_mode(vars);
|
||||
let mut escape: Option<&InputMapping> = None;
|
||||
|
||||
struct MatchedMapping<'a> {
|
||||
mapping: &'a InputMapping,
|
||||
quality: Vec<KeyMatchQuality>,
|
||||
idx: usize,
|
||||
subidx: usize,
|
||||
}
|
||||
|
||||
let mut deferred: Option<MatchedMapping<'a>> = None;
|
||||
|
||||
let ml = ip.mapping_list.iter().chain(ip.preset_mapping_list.iter());
|
||||
let mut quality = vec![];
|
||||
for m in ml {
|
||||
if m.mode != bind_mode {
|
||||
continue;
|
||||
@@ -673,24 +691,41 @@ pub fn find_mapping(
|
||||
|
||||
// Defer generic mappings until the end.
|
||||
if m.is_generic() {
|
||||
if generic.is_none() {
|
||||
generic = Some(m);
|
||||
if deferred.is_none() {
|
||||
deferred = Some(MatchedMapping {
|
||||
mapping: m,
|
||||
quality: vec![],
|
||||
idx: self.idx,
|
||||
subidx: self.subidx,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// FLOG!(reader, "trying mapping", format!("{:?}", m));
|
||||
if self.try_peek_sequence(&m.key_name_style, &m.seq) {
|
||||
// A binding for just escape should also be deferred
|
||||
// so escape sequences take precedence.
|
||||
if m.seq == vec![Key::from_raw(key::Escape)] {
|
||||
if escape.is_none() {
|
||||
escape = Some(m);
|
||||
}
|
||||
} else {
|
||||
if self.try_peek_sequence(&m.key_name_style, &m.seq, &mut quality) {
|
||||
// // A binding for just escape should also be deferred
|
||||
// // so escape sequences take precedence.
|
||||
let is_escape = m.seq == vec![Key::from_raw(key::Escape)];
|
||||
let is_perfect_match = quality
|
||||
.iter()
|
||||
.all(|key_match| *key_match == KeyMatchQuality::Exact);
|
||||
if !is_escape && is_perfect_match {
|
||||
return Some(m.clone());
|
||||
}
|
||||
if deferred
|
||||
.as_ref()
|
||||
.is_none_or(|matched| !is_escape && quality >= matched.quality)
|
||||
{
|
||||
deferred = Some(MatchedMapping {
|
||||
mapping: m,
|
||||
quality: mem::take(&mut quality),
|
||||
idx: self.idx,
|
||||
subidx: self.subidx,
|
||||
});
|
||||
}
|
||||
}
|
||||
quality.clear();
|
||||
self.restart();
|
||||
}
|
||||
if self.char_sequence_interrupted() {
|
||||
@@ -699,17 +734,13 @@ pub fn find_mapping(
|
||||
return None;
|
||||
}
|
||||
|
||||
if escape.is_some() {
|
||||
// We need to reconsume the escape.
|
||||
self.next();
|
||||
return escape.cloned();
|
||||
}
|
||||
|
||||
if generic.is_some() {
|
||||
generic.cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
deferred
|
||||
.map(|matched| {
|
||||
self.idx = matched.idx;
|
||||
self.subidx = matched.subidx;
|
||||
matched.mapping
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,7 +778,7 @@ pub fn read_char(&mut self) -> CharEvent {
|
||||
use ImplicitEvent::*;
|
||||
match evt {
|
||||
Key(_) => true,
|
||||
Implicit(Eof | CheckExit) => true,
|
||||
Implicit(Eof) => true,
|
||||
Readline(_) | Command(_) | Implicit(_) | QueryResponse(_) => false,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
use crate::wutil::fish_wcstol;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::fd::RawFd;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::ptr;
|
||||
@@ -153,20 +154,23 @@ pub enum ReadlineCmd {
|
||||
pub struct KeyEvent {
|
||||
pub key: Key,
|
||||
pub shifted_codepoint: char,
|
||||
pub base_layout_codepoint: char,
|
||||
}
|
||||
|
||||
impl KeyEvent {
|
||||
pub(crate) fn new(modifiers: Modifiers, codepoint: char) -> Self {
|
||||
Self::from(Key::new(modifiers, codepoint))
|
||||
}
|
||||
pub(crate) fn with_shifted_codepoint(
|
||||
pub(crate) fn new_with(
|
||||
modifiers: Modifiers,
|
||||
codepoint: char,
|
||||
shifted_codepoint: Option<char>,
|
||||
shifted_key: Option<char>,
|
||||
base_layout_key: Option<char>,
|
||||
) -> Self {
|
||||
Self {
|
||||
key: Key::new(modifiers, codepoint),
|
||||
shifted_codepoint: shifted_codepoint.unwrap_or_default(),
|
||||
shifted_codepoint: shifted_key.unwrap_or_default(),
|
||||
base_layout_codepoint: base_layout_key.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn from_raw(codepoint: char) -> Self {
|
||||
@@ -179,10 +183,7 @@ pub fn from_single_byte(c: u8) -> Self {
|
||||
|
||||
impl From<Key> for KeyEvent {
|
||||
fn from(key: Key) -> Self {
|
||||
Self {
|
||||
key,
|
||||
shifted_codepoint: '\0',
|
||||
}
|
||||
Self::new_with(key.modifiers, key.codepoint, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,24 +219,51 @@ fn apply_shift(mut key: Key, do_ascii: bool, shifted_codepoint: char) -> Option<
|
||||
Some(key)
|
||||
}
|
||||
|
||||
impl PartialEq<Key> for KeyEvent {
|
||||
fn eq(&self, key: &Key) -> bool {
|
||||
if &self.key == key {
|
||||
return true;
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) enum KeyMatchQuality {
|
||||
Legacy,
|
||||
BaseLayoutModuloShift,
|
||||
BaseLayout,
|
||||
ModuloShift,
|
||||
Exact,
|
||||
}
|
||||
|
||||
let Some(shifted_evt) = apply_shift(self.key, false, self.shifted_codepoint) else {
|
||||
return false;
|
||||
};
|
||||
let Some(shifted_key) = apply_shift(*key, true, '\0') else {
|
||||
return false;
|
||||
};
|
||||
shifted_evt == shifted_key
|
||||
impl FloggableDebug for KeyMatchQuality {}
|
||||
|
||||
pub(crate) fn match_key_event_to_key(event: &KeyEvent, key: &Key) -> Option<KeyMatchQuality> {
|
||||
if &event.key == key {
|
||||
return Some(KeyMatchQuality::Exact);
|
||||
}
|
||||
|
||||
let shifted_evt = apply_shift(event.key, false, event.shifted_codepoint);
|
||||
let shifted_key = apply_shift(*key, true, '\0');
|
||||
if shifted_evt.is_some() && shifted_evt == shifted_key {
|
||||
return Some(KeyMatchQuality::ModuloShift);
|
||||
}
|
||||
|
||||
if event.base_layout_codepoint != '\0' {
|
||||
let mut base_layout_key = event.key;
|
||||
base_layout_key.codepoint = event.base_layout_codepoint;
|
||||
if base_layout_key == *key {
|
||||
return Some(KeyMatchQuality::BaseLayout);
|
||||
}
|
||||
let shifted_base_layout_key = apply_shift(base_layout_key, true, '\0');
|
||||
if shifted_base_layout_key.is_some() && shifted_base_layout_key == shifted_key {
|
||||
return Some(KeyMatchQuality::BaseLayoutModuloShift);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_event_eq() {
|
||||
fn test_match_key_event_to_key() {
|
||||
macro_rules! validate {
|
||||
($evt:expr, $key:expr, $expected:expr) => {
|
||||
assert_eq!(match_key_event_to_key(&$evt, &$key), $expected);
|
||||
};
|
||||
}
|
||||
|
||||
let none = Modifiers::default();
|
||||
let shift = Modifiers::SHIFT;
|
||||
let ctrl = Modifiers::CTRL;
|
||||
@@ -245,41 +273,69 @@ fn test_key_event_eq() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(KeyEvent::new(none, 'a'), Key::new(none, 'a'));
|
||||
assert_ne!(KeyEvent::new(none, 'a'), Key::new(none, 'A'));
|
||||
assert_eq!(KeyEvent::new(shift, 'a'), Key::new(shift, 'a'));
|
||||
assert_ne!(KeyEvent::new(shift, 'a'), Key::new(none, 'A'));
|
||||
assert_ne!(KeyEvent::new(shift, 'ä'), Key::new(none, 'Ä'));
|
||||
let exact = KeyMatchQuality::Exact;
|
||||
let modulo_shift = KeyMatchQuality::ModuloShift;
|
||||
let base_layout = KeyMatchQuality::BaseLayout;
|
||||
let base_layout_modulo_shift = KeyMatchQuality::BaseLayoutModuloShift;
|
||||
|
||||
validate!(KeyEvent::new(none, 'a'), Key::new(none, 'a'), Some(exact));
|
||||
validate!(KeyEvent::new(none, 'a'), Key::new(none, 'A'), None);
|
||||
validate!(KeyEvent::new(shift, 'a'), Key::new(shift, 'a'), Some(exact));
|
||||
validate!(KeyEvent::new(shift, 'a'), Key::new(none, 'A'), None);
|
||||
validate!(KeyEvent::new(shift, 'ä'), Key::new(none, 'Ä'), None);
|
||||
// For historical reasons we canonicalize notation for ASCII keys like "shift-a" to "A",
|
||||
// but not "shift-a" events - those should send a shifted key.
|
||||
assert_eq!(KeyEvent::new(none, 'A'), Key::new(shift, 'a'));
|
||||
assert_ne!(KeyEvent::new(none, 'A'), Key::new(shift, 'A'));
|
||||
assert_eq!(KeyEvent::new(none, 'Ä'), Key::new(none, 'Ä'));
|
||||
assert_ne!(KeyEvent::new(none, 'Ä'), Key::new(shift, 'ä'));
|
||||
validate!(
|
||||
KeyEvent::new(none, 'A'),
|
||||
Key::new(shift, 'a'),
|
||||
Some(modulo_shift)
|
||||
);
|
||||
validate!(KeyEvent::new(none, 'A'), Key::new(shift, 'A'), None);
|
||||
validate!(KeyEvent::new(none, 'Ä'), Key::new(none, 'Ä'), Some(exact));
|
||||
validate!(KeyEvent::new(none, 'Ä'), Key::new(shift, 'ä'), None);
|
||||
|
||||
// FYI: for codepoints that are not letters with uppercase/lowercase versions, we use
|
||||
// the shifted key in the canonical notation, because the unshifted one may depend on the
|
||||
// keyboard layout.
|
||||
let ctrl_shift_equals = KeyEvent::with_shifted_codepoint(ctrl_shift, '=', Some('+'));
|
||||
assert_eq!(ctrl_shift_equals, Key::new(ctrl_shift, '='));
|
||||
assert_eq!(ctrl_shift_equals, Key::new(ctrl, '+')); // canonical notation
|
||||
assert_ne!(ctrl_shift_equals, Key::new(ctrl_shift, '+'));
|
||||
assert_ne!(ctrl_shift_equals, Key::new(ctrl, '='));
|
||||
let ctrl_shift_equals = KeyEvent::new_with(ctrl_shift, '=', Some('+'), None);
|
||||
validate!(ctrl_shift_equals, Key::new(ctrl_shift, '='), Some(exact));
|
||||
validate!(ctrl_shift_equals, Key::new(ctrl, '+'), Some(modulo_shift)); // canonical notation
|
||||
validate!(ctrl_shift_equals, Key::new(ctrl_shift, '+'), None);
|
||||
validate!(ctrl_shift_equals, Key::new(ctrl, '='), None);
|
||||
|
||||
// A event like capslock-shift-ä may or may not include a shifted codepoint.
|
||||
//
|
||||
// Without a shifted codepoint, we cannot easily match ctrl-Ä.
|
||||
let caps_ctrl_shift_ä = KeyEvent::new(ctrl_shift, 'ä');
|
||||
assert_eq!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä')); // canonical notation
|
||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'));
|
||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä')); // can't match without shifted key
|
||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'));
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä'), Some(exact)); // canonical notation
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'), None);
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä'), None); // can't match without shifted key
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'), None);
|
||||
// With a shifted codepoint, we can match the alternative notation too.
|
||||
let caps_ctrl_shift_ä = KeyEvent::with_shifted_codepoint(ctrl_shift, 'ä', Some('Ä'));
|
||||
assert_eq!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä')); // canonical notation
|
||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'));
|
||||
assert_eq!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä')); // matched via shifted key
|
||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'));
|
||||
let caps_ctrl_shift_ä = KeyEvent::new_with(ctrl_shift, 'ä', Some('Ä'), None);
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä'), Some(exact)); // canonical notation
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'), None);
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä'), Some(modulo_shift)); // matched via shifted key
|
||||
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'), None);
|
||||
|
||||
let ctrl_ц = KeyEvent::new_with(ctrl, 'ц', None, Some('w'));
|
||||
let ctrl_shift_ц = KeyEvent::new_with(ctrl_shift, 'ц', Some('Ц'), Some('w'));
|
||||
validate!(ctrl_ц, Key::new(ctrl, 'ц'), Some(exact));
|
||||
validate!(ctrl_ц, Key::new(ctrl, 'w'), Some(base_layout));
|
||||
validate!(ctrl_ц, Key::new(ctrl_shift, 'ц'), None);
|
||||
validate!(ctrl_ц, Key::new(ctrl_shift, 'w'), None);
|
||||
validate!(
|
||||
ctrl_shift_ц,
|
||||
Key::new(ctrl, 'W'),
|
||||
Some(base_layout_modulo_shift)
|
||||
);
|
||||
validate!(ctrl_shift_ц, Key::new(ctrl, 'w'), None);
|
||||
|
||||
// Note that "bind ctrl-Ц" will win over "bind ctrl-shift-w".
|
||||
// This is because we consider shift transformation to be less magic than base-key
|
||||
// transformation.
|
||||
validate!(ctrl_shift_ц, Key::new(ctrl, 'Ц'), Some(modulo_shift));
|
||||
validate!(ctrl_shift_ц, Key::new(ctrl_shift, 'w'), Some(base_layout));
|
||||
}
|
||||
|
||||
/// Represents an event on the character input stream.
|
||||
@@ -858,7 +914,7 @@ fn try_readch(&mut self, blocking: bool) -> Option<CharEvent> {
|
||||
}
|
||||
let mut seq = WString::new();
|
||||
let mut key = key_with_escape;
|
||||
if key.is_some_and(|key| key == Key::from_raw(key::Invalid)) {
|
||||
if key.is_some_and(|key| key.key == Key::from_raw(key::Invalid)) {
|
||||
continue;
|
||||
}
|
||||
assert!(key.map_or(true, |key| key.codepoint != key::Invalid));
|
||||
@@ -924,7 +980,11 @@ fn try_readch(&mut self, blocking: bool) -> Option<CharEvent> {
|
||||
}
|
||||
});
|
||||
let vintr = shell_modes().c_cc[libc::VINTR];
|
||||
if vintr != 0 && key.is_some_and(|key| key == Key::from_single_byte(vintr))
|
||||
if vintr != 0
|
||||
&& key.is_some_and(|key| {
|
||||
match_key_event_to_key(&key, &Key::from_single_byte(vintr))
|
||||
.is_some()
|
||||
})
|
||||
{
|
||||
FLOG!(
|
||||
reader,
|
||||
@@ -932,6 +992,7 @@ fn try_readch(&mut self, blocking: bool) -> Option<CharEvent> {
|
||||
);
|
||||
let ok = stop_query(self.blocking_query());
|
||||
assert!(ok);
|
||||
self.get_input_data_mut().queue.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -959,17 +1020,14 @@ fn parse_escape_sequence(
|
||||
assert!(buffer.len() <= 2);
|
||||
let recursive_invocation = buffer.len() == 2;
|
||||
let Some(next) = self.try_readb(buffer) else {
|
||||
if !self.paste_is_buffering() {
|
||||
return Some(KeyEvent::from_raw(key::Escape));
|
||||
}
|
||||
return None;
|
||||
return Some(KeyEvent::from_raw(key::Escape));
|
||||
};
|
||||
let invalid = KeyEvent::from_raw(key::Invalid);
|
||||
if recursive_invocation && next == b'\x1b' {
|
||||
return Some(
|
||||
match self.parse_escape_sequence(buffer, have_escape_prefix) {
|
||||
Some(mut nested_sequence) => {
|
||||
if nested_sequence == invalid.key {
|
||||
if nested_sequence.key == invalid.key {
|
||||
return Some(KeyEvent::from_raw(key::Escape));
|
||||
}
|
||||
nested_sequence.modifiers.alt = true;
|
||||
@@ -1045,7 +1103,7 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let masked_key = |codepoint: char, shifted_codepoint: Option<char>| {
|
||||
let kitty_key = |key: char, shifted_key: Option<char>, base_layout_key: Option<char>| {
|
||||
let mask = params[1][0].saturating_sub(1);
|
||||
let (mut modifiers, caps_lock) = parse_mask(mask);
|
||||
|
||||
@@ -1069,12 +1127,13 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
// match the "shift-ä" event, as suggested in the kitty issue.
|
||||
if caps_lock
|
||||
&& modifiers == Modifiers::SHIFT
|
||||
&& !codepoint.to_uppercase().eq(Some(codepoint).into_iter())
|
||||
&& !key.to_uppercase().eq(Some(key).into_iter())
|
||||
{
|
||||
modifiers.shift = false;
|
||||
}
|
||||
KeyEvent::with_shifted_codepoint(modifiers, codepoint, shifted_codepoint)
|
||||
KeyEvent::new_with(modifiers, key, shifted_key, base_layout_key)
|
||||
};
|
||||
let masked_key = |key: char| kitty_key(key, None, None);
|
||||
|
||||
let key = match c {
|
||||
b'$' => {
|
||||
@@ -1089,13 +1148,13 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
b'A' => masked_key(key::Up, None),
|
||||
b'B' => masked_key(key::Down, None),
|
||||
b'C' => masked_key(key::Right, None),
|
||||
b'D' => masked_key(key::Left, None),
|
||||
b'E' => masked_key('5', None), // Numeric keypad
|
||||
b'F' => masked_key(key::End, None), // PC/xterm style
|
||||
b'H' => masked_key(key::Home, None), // PC/xterm style
|
||||
b'A' => masked_key(key::Up),
|
||||
b'B' => masked_key(key::Down),
|
||||
b'C' => masked_key(key::Right),
|
||||
b'D' => masked_key(key::Left),
|
||||
b'E' => masked_key('5'), // Numeric keypad
|
||||
b'F' => masked_key(key::End), // PC/xterm style
|
||||
b'H' => masked_key(key::Home), // PC/xterm style
|
||||
b'M' | b'm' => {
|
||||
self.disable_mouse_tracking();
|
||||
// Generic X10 or modified VT200 sequence, or extended (SGR/1006) mouse
|
||||
@@ -1151,8 +1210,8 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
b'P' => masked_key(function_key(1), None),
|
||||
b'Q' => masked_key(function_key(2), None),
|
||||
b'P' => masked_key(function_key(1)),
|
||||
b'Q' => masked_key(function_key(2)),
|
||||
b'R' => {
|
||||
let Some(y) = params[0][0]
|
||||
.checked_sub(1)
|
||||
@@ -1173,35 +1232,33 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
));
|
||||
return None;
|
||||
}
|
||||
b'S' => masked_key(function_key(4), None),
|
||||
b'S' => masked_key(function_key(4)),
|
||||
b'~' => match params[0][0] {
|
||||
1 => masked_key(key::Home, None), // VT220/tmux style
|
||||
2 => masked_key(key::Insert, None),
|
||||
3 => masked_key(key::Delete, None),
|
||||
4 => masked_key(key::End, None), // VT220/tmux style
|
||||
5 => masked_key(key::PageUp, None),
|
||||
6 => masked_key(key::PageDown, None),
|
||||
7 => masked_key(key::Home, None), // rxvt style
|
||||
8 => masked_key(key::End, None), // rxvt style
|
||||
1 => masked_key(key::Home), // VT220/tmux style
|
||||
2 => masked_key(key::Insert),
|
||||
3 => masked_key(key::Delete),
|
||||
4 => masked_key(key::End), // VT220/tmux style
|
||||
5 => masked_key(key::PageUp),
|
||||
6 => masked_key(key::PageDown),
|
||||
7 => masked_key(key::Home), // rxvt style
|
||||
8 => masked_key(key::End), // rxvt style
|
||||
11..=15 => masked_key(
|
||||
char::from_u32(u32::from(function_key(1)) + params[0][0] - 11).unwrap(),
|
||||
None,
|
||||
),
|
||||
17..=21 => masked_key(
|
||||
char::from_u32(u32::from(function_key(6)) + params[0][0] - 17).unwrap(),
|
||||
None,
|
||||
),
|
||||
23 | 24 => masked_key(
|
||||
char::from_u32(u32::from(function_key(11)) + params[0][0] - 23).unwrap(),
|
||||
None,
|
||||
),
|
||||
25 | 26 => KeyEvent::from(shift(
|
||||
char::from_u32(u32::from(function_key(3)) + params[0][0] - 25).unwrap(),
|
||||
)), // rxvt style
|
||||
27 => {
|
||||
let key =
|
||||
canonicalize_keyed_control_char(char::from_u32(params[2][0]).unwrap());
|
||||
masked_key(key, None)
|
||||
let Some(key) = char::from_u32(params[2][0]) else {
|
||||
return invalid_sequence(buffer);
|
||||
};
|
||||
masked_key(canonicalize_keyed_control_char(key))
|
||||
}
|
||||
28 | 29 => KeyEvent::from(shift(
|
||||
char::from_u32(u32::from(function_key(5)) + params[0][0] - 28).unwrap(),
|
||||
@@ -1269,13 +1326,23 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
57424 => key::End,
|
||||
57425 => key::Insert,
|
||||
57426 => key::Delete,
|
||||
cp => canonicalize_keyed_control_char(char::from_u32(cp).unwrap()),
|
||||
cp => {
|
||||
let Some(key) = char::from_u32(cp) else {
|
||||
return invalid_sequence(buffer);
|
||||
};
|
||||
canonicalize_keyed_control_char(key)
|
||||
}
|
||||
};
|
||||
masked_key(
|
||||
let Some(shifted_key) = char::from_u32(params[0][1]) else {
|
||||
return invalid_sequence(buffer);
|
||||
};
|
||||
let Some(base_layout_key) = char::from_u32(params[0][2]) else {
|
||||
return invalid_sequence(buffer);
|
||||
};
|
||||
kitty_key(
|
||||
key,
|
||||
Some(canonicalize_keyed_control_char(
|
||||
char::from_u32(params[0][1]).unwrap(),
|
||||
)),
|
||||
Some(canonicalize_keyed_control_char(shifted_key)),
|
||||
Some(base_layout_key),
|
||||
)
|
||||
}
|
||||
b'Z' => KeyEvent::from(shift(key::Tab)),
|
||||
@@ -1463,8 +1530,8 @@ fn readch_timed(&mut self, wait_time_ms: usize) -> Option<CharEvent> {
|
||||
// We are not prepared to handle a signal immediately; we only want to know if we get input on
|
||||
// our fd before the timeout. Use pselect to block all signals; we will handle signals
|
||||
// before the next call to readch().
|
||||
let mut sigs: libc::sigset_t = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigfillset(&mut sigs) };
|
||||
let mut sigs = MaybeUninit::uninit();
|
||||
unsafe { libc::sigfillset(sigs.as_mut_ptr()) };
|
||||
|
||||
// pselect expects timeouts in nanoseconds.
|
||||
const NSEC_PER_MSEC: u64 = 1000 * 1000;
|
||||
@@ -1476,27 +1543,27 @@ fn readch_timed(&mut self, wait_time_ms: usize) -> Option<CharEvent> {
|
||||
};
|
||||
|
||||
// We have one fd of interest.
|
||||
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
|
||||
let mut fdset = MaybeUninit::uninit();
|
||||
let in_fd = self.get_in_fd();
|
||||
unsafe {
|
||||
libc::FD_ZERO(&mut fdset);
|
||||
libc::FD_SET(in_fd, &mut fdset);
|
||||
libc::FD_ZERO(fdset.as_mut_ptr());
|
||||
libc::FD_SET(in_fd, fdset.as_mut_ptr());
|
||||
};
|
||||
let res = unsafe {
|
||||
libc::pselect(
|
||||
in_fd + 1,
|
||||
&mut fdset,
|
||||
fdset.as_mut_ptr(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&timeout,
|
||||
&sigs,
|
||||
sigs.as_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
// Prevent signal starvation on WSL causing the `torn_escapes.py` test to fail
|
||||
if is_windows_subsystem_for_linux(WSL::V1) {
|
||||
// Merely querying the current thread's sigmask is sufficient to deliver a pending signal
|
||||
let _ = unsafe { libc::pthread_sigmask(0, ptr::null(), &mut sigs) };
|
||||
let _ = unsafe { libc::pthread_sigmask(0, ptr::null(), sigs.as_mut_ptr()) };
|
||||
}
|
||||
if res > 0 {
|
||||
return Some(self.readch());
|
||||
@@ -1632,7 +1699,7 @@ fn enqueue_interrupt_key(&mut self) {
|
||||
reader,
|
||||
"Received interrupt, giving up on waiting for terminal response"
|
||||
);
|
||||
self.push_back(interrupt_evt);
|
||||
self.get_input_data_mut().queue.clear();
|
||||
} else {
|
||||
self.push_front(interrupt_evt);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
common::{escape_string, EscapeFlags, EscapeStringStyle},
|
||||
fallback::fish_wcwidth,
|
||||
flog::FloggableDebug,
|
||||
future_feature_flags::{test as feature_test, FeatureFlag},
|
||||
reader::TERMINAL_MODE_ON_STARTUP,
|
||||
wchar::{decode_byte_from_char, prelude::*},
|
||||
wutil::{fish_is_pua, fish_wcstoul},
|
||||
@@ -438,7 +439,9 @@ fn ctrl_to_symbol(buf: &mut WString, c: char) {
|
||||
/// Return true if the character must be escaped when used in the sequence of chars to be bound in
|
||||
/// a `bind` command.
|
||||
fn must_escape(is_first_in_token: bool, c: char) -> bool {
|
||||
"[]()<>{}*\\$;&|'\"".contains(c) || (is_first_in_token && "~#".contains(c))
|
||||
"[]()<>{}*\\$;&|'\"".contains(c)
|
||||
|| (is_first_in_token && "~#".contains(c))
|
||||
|| (c == '?' && !feature_test(FeatureFlag::qmark_noglob))
|
||||
}
|
||||
|
||||
fn ascii_printable_to_symbol(buf: &mut WString, is_first_in_token: bool, c: char) {
|
||||
|
||||
10
src/libc.c
10
src/libc.c
@@ -1,13 +1,13 @@
|
||||
#include <locale.h>
|
||||
#include <paths.h>
|
||||
#include <paths.h> // _PATH_BSHELL
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdlib.h> // MB_CUR_MAX
|
||||
#include <sys/mount.h> // MNT_LOCAL
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
#include <sys/statvfs.h> // ST_LOCAL
|
||||
#include <unistd.h> // _CS_PATH, _PC_CASE_SENSITIVE
|
||||
|
||||
size_t C_MB_CUR_MAX() { return MB_CUR_MAX; }
|
||||
|
||||
|
||||
@@ -4,58 +4,35 @@
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
|
||||
pub trait NulTerminatedString {
|
||||
type CharType: Copy;
|
||||
|
||||
/// Return a pointer to the null-terminated string.
|
||||
fn c_str(&self) -> *const Self::CharType;
|
||||
}
|
||||
|
||||
impl NulTerminatedString for CStr {
|
||||
type CharType = c_char;
|
||||
|
||||
fn c_str(&self) -> *const c_char {
|
||||
self.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsNullTerminatedArray {
|
||||
type CharType;
|
||||
fn get(&self) -> *mut *const Self::CharType;
|
||||
}
|
||||
|
||||
/// This supports the null-terminated array of NUL-terminated strings consumed by exec.
|
||||
/// Given a list of strings, construct a vector of pointers to those strings contents.
|
||||
/// This is used for building null-terminated arrays of null-terminated strings.
|
||||
pub struct NullTerminatedArray<'p, T: NulTerminatedString + ?Sized> {
|
||||
pointers: Box<[*const T::CharType]>,
|
||||
_phantom: PhantomData<&'p T>,
|
||||
struct NullTerminatedArray<'p> {
|
||||
pointers: Box<[*const c_char]>,
|
||||
_phantom: PhantomData<&'p CStr>,
|
||||
}
|
||||
|
||||
impl<'p, Str: NulTerminatedString + ?Sized> AsNullTerminatedArray for NullTerminatedArray<'p, Str> {
|
||||
type CharType = Str::CharType;
|
||||
impl<'p> NullTerminatedArray<'p> {
|
||||
/// Return the list of pointers, appropriate for envp or argv.
|
||||
/// Note this returns a mutable array of const strings. The caller may rearrange the strings but
|
||||
/// not modify their contents.
|
||||
/// We freely give out mutable pointers even though we are not mut; this is because most of the uses
|
||||
/// expect the array to be mutable even though fish does not mutate it, so it's either this or cast
|
||||
/// away the const at the call site.
|
||||
fn get(&self) -> *mut *const Str::CharType {
|
||||
pub fn get(&self) -> *mut *const c_char {
|
||||
assert!(
|
||||
!self.pointers.is_empty() && self.pointers.last().unwrap().is_null(),
|
||||
"Should have null terminator"
|
||||
);
|
||||
self.pointers.as_ptr() as *mut *const Str::CharType
|
||||
self.pointers.as_ptr().cast_mut()
|
||||
}
|
||||
}
|
||||
impl<'p, Str: NulTerminatedString + ?Sized> NullTerminatedArray<'p, Str> {
|
||||
/// Construct from a list of "strings".
|
||||
/// This holds pointers into the strings.
|
||||
pub fn new<S: AsRef<Str>>(strs: &'p [S]) -> Self {
|
||||
pub fn new<S: AsRef<CStr>>(strs: &'p [S]) -> Self {
|
||||
let mut pointers = Vec::new();
|
||||
pointers.reserve_exact(1 + strs.len());
|
||||
for s in strs {
|
||||
pointers.push(s.as_ref().c_str());
|
||||
pointers.push(s.as_ref().as_ptr());
|
||||
}
|
||||
pointers.push(ptr::null());
|
||||
NullTerminatedArray {
|
||||
@@ -66,8 +43,8 @@ pub fn new<S: AsRef<Str>>(strs: &'p [S]) -> Self {
|
||||
}
|
||||
|
||||
/// Safety: NullTerminatedArray is Send and Sync because it's immutable.
|
||||
unsafe impl<T: NulTerminatedString + ?Sized + Send> Send for NullTerminatedArray<'_, T> {}
|
||||
unsafe impl<T: NulTerminatedString + ?Sized + Sync> Sync for NullTerminatedArray<'_, T> {}
|
||||
unsafe impl Send for NullTerminatedArray<'_> {}
|
||||
unsafe impl Sync for NullTerminatedArray<'_> {}
|
||||
|
||||
/// A container which exposes a null-terminated array of pointers to strings that it owns.
|
||||
/// This is useful for persisted null-terminated arrays, e.g. the exported environment variable
|
||||
@@ -75,22 +52,17 @@ unsafe impl<T: NulTerminatedString + ?Sized + Sync> Sync for NullTerminatedArray
|
||||
pub struct OwningNullTerminatedArray {
|
||||
// Note that null_terminated_array holds pointers into our boxed strings.
|
||||
// The 'static is a lie.
|
||||
_strings: Pin<Box<[CString]>>,
|
||||
null_terminated_array: NullTerminatedArray<'static, CStr>,
|
||||
strings: Pin<Box<[CString]>>,
|
||||
null_terminated_array: NullTerminatedArray<'static>,
|
||||
}
|
||||
|
||||
const _: () = assert_send::<OwningNullTerminatedArray>();
|
||||
const _: () = assert_sync::<OwningNullTerminatedArray>();
|
||||
|
||||
impl AsNullTerminatedArray for OwningNullTerminatedArray {
|
||||
type CharType = c_char;
|
||||
/// Cover over null_terminated_array.get().
|
||||
fn get(&self) -> *mut *const c_char {
|
||||
impl OwningNullTerminatedArray {
|
||||
pub fn get(&self) -> *mut *const c_char {
|
||||
self.null_terminated_array.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl OwningNullTerminatedArray {
|
||||
pub fn get_mut(&self) -> *mut *mut c_char {
|
||||
self.get().cast()
|
||||
}
|
||||
@@ -100,31 +72,16 @@ pub fn new(strs: Vec<CString>) -> Self {
|
||||
// Safety: we're pinning the strings, so they won't move.
|
||||
let string_slice: &'static [CString] = unsafe { std::mem::transmute(&*strings) };
|
||||
OwningNullTerminatedArray {
|
||||
_strings: Pin::from(strings),
|
||||
strings: Pin::from(strings),
|
||||
null_terminated_array: NullTerminatedArray::new(string_slice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length of a null-terminated array of pointers to something.
|
||||
pub(crate) fn null_terminated_array_length<T>(mut arr: *const *const T) -> usize {
|
||||
let mut len = 0;
|
||||
// Safety: caller must ensure that arr is null-terminated.
|
||||
unsafe {
|
||||
while !arr.read().is_null() {
|
||||
arr = arr.offset(1);
|
||||
len += 1;
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.strings.len()
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = &CString> {
|
||||
self.strings.iter()
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_terminated_array_length() {
|
||||
let arr = [&1, &2, &3, std::ptr::null()];
|
||||
assert_eq!(null_terminated_array_length(arr.as_ptr()), 3);
|
||||
let arr: &[*const u64] = &[std::ptr::null()];
|
||||
assert_eq!(null_terminated_array_length(arr.as_ptr()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -150,4 +107,9 @@ fn test_owning_null_terminated_array() {
|
||||
assert_eq!(CStr::from_ptr(*ptr.offset(1)).to_str().unwrap(), "bar");
|
||||
assert_eq!(*ptr.offset(2), ptr::null());
|
||||
}
|
||||
assert_eq!(arr.len(), 2);
|
||||
let mut iter = arr.iter();
|
||||
assert_eq!(iter.next().map(|s| s.to_str().unwrap()), Some("foo"));
|
||||
assert_eq!(iter.next().map(|s| s.to_str().unwrap()), Some("bar"));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
@@ -38,24 +38,15 @@ pub struct ParserTestErrorBits: u8 {
|
||||
}
|
||||
|
||||
/// A range of source code.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
|
||||
pub struct SourceRange {
|
||||
pub start: u32,
|
||||
pub length: u32,
|
||||
}
|
||||
|
||||
impl Default for SourceRange {
|
||||
fn default() -> Self {
|
||||
SourceRange {
|
||||
start: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceRange {
|
||||
pub fn as_usize(&self) -> std::ops::Range<usize> {
|
||||
(*self).into()
|
||||
pub fn as_usize(self) -> std::ops::Range<usize> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,20 +147,20 @@ pub fn new(start: usize, length: usize) -> Self {
|
||||
length: length.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
pub fn start(&self) -> usize {
|
||||
pub fn start(self) -> usize {
|
||||
self.start.try_into().unwrap()
|
||||
}
|
||||
pub fn length(&self) -> usize {
|
||||
pub fn length(self) -> usize {
|
||||
self.length.try_into().unwrap()
|
||||
}
|
||||
pub fn end(&self) -> usize {
|
||||
pub fn end(self) -> usize {
|
||||
self.start
|
||||
.checked_add(self.length)
|
||||
.expect("Overflow")
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
pub fn combine(&self, other: Self) -> Self {
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
let start = std::cmp::min(self.start, other.start);
|
||||
SourceRange {
|
||||
start,
|
||||
@@ -182,7 +173,7 @@ pub fn combine(&self, other: Self) -> Self {
|
||||
}
|
||||
|
||||
// Return true if a location is in this range, including one-past-the-end.
|
||||
pub fn contains_inclusive(&self, loc: usize) -> bool {
|
||||
pub fn contains_inclusive(self, loc: usize) -> bool {
|
||||
self.start() <= loc && loc - self.start() <= self.length()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Provides the "linkage" between an ast and actual execution structures (job_t, etc.).
|
||||
|
||||
use crate::ast::{
|
||||
self, unescape_keyword, BlockStatementHeaderVariant, Keyword, Leaf, List, Node,
|
||||
StatementVariant, Token,
|
||||
self, unescape_keyword, BlockStatementHeader, Keyword, Leaf, Node, Statement, Token,
|
||||
};
|
||||
use crate::builtins;
|
||||
use crate::builtins::shared::{
|
||||
@@ -140,13 +139,9 @@ pub fn eval_node(
|
||||
node: &'a dyn Node,
|
||||
associated_block: Option<BlockId>,
|
||||
) -> EndExecutionReason {
|
||||
match node.typ() {
|
||||
ast::Type::statement => {
|
||||
self.eval_statement(ctx, node.as_statement().unwrap(), associated_block)
|
||||
}
|
||||
ast::Type::job_list => {
|
||||
self.eval_job_list(ctx, node.as_job_list().unwrap(), associated_block.unwrap())
|
||||
}
|
||||
match node.kind() {
|
||||
ast::Kind::Statement(node) => self.eval_statement(ctx, node, associated_block),
|
||||
ast::Kind::JobList(node) => self.eval_job_list(ctx, node, associated_block.unwrap()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -160,22 +155,14 @@ fn eval_statement(
|
||||
associated_block: Option<BlockId>,
|
||||
) -> EndExecutionReason {
|
||||
// Note we only expect block-style statements here. No not statements.
|
||||
match &statement.contents {
|
||||
StatementVariant::BlockStatement(block) => {
|
||||
self.run_block_statement(ctx, block, associated_block)
|
||||
}
|
||||
StatementVariant::BraceStatement(brace_statement) => {
|
||||
match &statement {
|
||||
Statement::Block(block) => self.run_block_statement(ctx, block, associated_block),
|
||||
Statement::Brace(brace_statement) => {
|
||||
self.run_begin_statement(ctx, &brace_statement.jobs)
|
||||
}
|
||||
StatementVariant::IfStatement(ifstat) => {
|
||||
self.run_if_statement(ctx, ifstat, associated_block)
|
||||
}
|
||||
StatementVariant::SwitchStatement(switchstat) => {
|
||||
self.run_switch_statement(ctx, switchstat)
|
||||
}
|
||||
StatementVariant::DecoratedStatement(_)
|
||||
| StatementVariant::NotStatement(_)
|
||||
| StatementVariant::None => panic!(),
|
||||
Statement::If(ifstat) => self.run_if_statement(ctx, ifstat, associated_block),
|
||||
Statement::Switch(switchstat) => self.run_switch_statement(ctx, switchstat),
|
||||
Statement::Decorated(_) | Statement::Not(_) => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +405,7 @@ fn infinite_recursive_statement_in_job_list<'b>(
|
||||
// Helper to return if a statement is infinitely recursive in this function.
|
||||
let statement_recurses = |stat: &'b ast::Statement| -> Option<&'b ast::DecoratedStatement> {
|
||||
// Ignore non-decorated statements like `if`, etc.
|
||||
let StatementVariant::DecoratedStatement(dc) = &stat.contents else {
|
||||
let Statement::Decorated(dc) = &stat else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -560,18 +547,16 @@ fn job_is_simple_block(&self, job: &ast::JobPipeline) -> bool {
|
||||
let no_redirs =
|
||||
|list: &ast::ArgumentOrRedirectionList| !list.iter().any(|val| val.is_redirection());
|
||||
|
||||
// Check if we're a block statement with redirections. We do it this obnoxious way to preserve
|
||||
// type safety (in case we add more specific statement types).
|
||||
match &job.statement.contents {
|
||||
StatementVariant::BlockStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
StatementVariant::BraceStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
StatementVariant::SwitchStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
StatementVariant::IfStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
StatementVariant::NotStatement(_) | StatementVariant::DecoratedStatement(_) => {
|
||||
// Check if we're a block statement with redirections.
|
||||
match &job.statement {
|
||||
Statement::Block(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
Statement::Brace(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
Statement::Switch(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
Statement::If(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
Statement::Not(_) | Statement::Decorated(_) => {
|
||||
// not block statements
|
||||
false
|
||||
}
|
||||
StatementVariant::None => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,9 +649,6 @@ fn populate_job_process(
|
||||
statement: &ast::Statement,
|
||||
variable_assignments: &ast::VariableAssignmentList,
|
||||
) -> EndExecutionReason {
|
||||
// Get the "specific statement" which is boolean / block / if / switch / decorated.
|
||||
let specific_statement = &statement.contents;
|
||||
|
||||
let mut block = None;
|
||||
let result =
|
||||
self.apply_variable_assignments(ctx, Some(proc), variable_assignments, &mut block);
|
||||
@@ -679,20 +661,16 @@ fn populate_job_process(
|
||||
return result;
|
||||
}
|
||||
|
||||
match &specific_statement {
|
||||
StatementVariant::NotStatement(not_statement) => {
|
||||
match &statement {
|
||||
Statement::Not(not_statement) => {
|
||||
self.populate_not_process(ctx, job, proc, not_statement)
|
||||
}
|
||||
StatementVariant::BlockStatement(_)
|
||||
| StatementVariant::BraceStatement(_)
|
||||
| StatementVariant::IfStatement(_)
|
||||
| StatementVariant::SwitchStatement(_) => {
|
||||
self.populate_block_process(ctx, proc, statement, specific_statement)
|
||||
Statement::Block(_) | Statement::Brace(_) | Statement::If(_) | Statement::Switch(_) => {
|
||||
self.populate_block_process(ctx, proc, statement)
|
||||
}
|
||||
StatementVariant::DecoratedStatement(decorated_statement) => {
|
||||
Statement::Decorated(decorated_statement) => {
|
||||
self.populate_plain_process(ctx, proc, decorated_statement)
|
||||
}
|
||||
StatementVariant::None => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,17 +819,16 @@ fn populate_block_process(
|
||||
ctx: &OperationContext<'_>,
|
||||
proc: &mut Process,
|
||||
statement: &ast::Statement,
|
||||
specific_statement: &ast::StatementVariant,
|
||||
) -> EndExecutionReason {
|
||||
// We handle block statements by creating process_type_t::block_node, that will bounce back to
|
||||
// We handle block statements by creating ProcessType::block_node, that will bounce back to
|
||||
// us when it's time to execute them.
|
||||
// Get the argument or redirections list.
|
||||
// TODO: args_or_redirs should be available without resolving the statement type.
|
||||
let args_or_redirs = match specific_statement {
|
||||
StatementVariant::BlockStatement(block_statement) => &block_statement.args_or_redirs,
|
||||
StatementVariant::BraceStatement(brace_statement) => &brace_statement.args_or_redirs,
|
||||
StatementVariant::IfStatement(if_statement) => &if_statement.args_or_redirs,
|
||||
StatementVariant::SwitchStatement(switch_statement) => &switch_statement.args_or_redirs,
|
||||
let args_or_redirs = match statement {
|
||||
Statement::Block(block_statement) => &block_statement.args_or_redirs,
|
||||
Statement::Brace(brace_statement) => &brace_statement.args_or_redirs,
|
||||
Statement::If(if_statement) => &if_statement.args_or_redirs,
|
||||
Statement::Switch(switch_statement) => &switch_statement.args_or_redirs,
|
||||
_ => panic!("Unexpected block node type"),
|
||||
};
|
||||
|
||||
@@ -876,17 +853,12 @@ fn run_block_statement(
|
||||
let bh = &statement.header;
|
||||
let contents = &statement.jobs;
|
||||
match bh {
|
||||
BlockStatementHeaderVariant::ForHeader(fh) => self.run_for_statement(ctx, fh, contents),
|
||||
BlockStatementHeaderVariant::WhileHeader(wh) => {
|
||||
BlockStatementHeader::For(fh) => self.run_for_statement(ctx, fh, contents),
|
||||
BlockStatementHeader::While(wh) => {
|
||||
self.run_while_statement(ctx, wh, contents, associated_block)
|
||||
}
|
||||
BlockStatementHeaderVariant::FunctionHeader(fh) => {
|
||||
self.run_function_statement(ctx, statement, fh)
|
||||
}
|
||||
BlockStatementHeaderVariant::BeginHeader(_bh) => {
|
||||
self.run_begin_statement(ctx, contents)
|
||||
}
|
||||
BlockStatementHeaderVariant::None => panic!(),
|
||||
BlockStatementHeader::Function(fh) => self.run_function_statement(ctx, statement, fh),
|
||||
BlockStatementHeader::Begin(_bh) => self.run_begin_statement(ctx, contents),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1594,29 +1566,23 @@ fn run_1_job(
|
||||
}
|
||||
});
|
||||
|
||||
let specific_statement = &job_node.statement.contents;
|
||||
assert!(specific_statement_type_is_redirectable_block(
|
||||
specific_statement
|
||||
));
|
||||
let statement = &job_node.statement;
|
||||
assert!(statement_is_redirectable_block(statement));
|
||||
if result == EndExecutionReason::ok {
|
||||
result = match &specific_statement {
|
||||
StatementVariant::BlockStatement(block_statement) => {
|
||||
result = match statement {
|
||||
Statement::Block(block_statement) => {
|
||||
self.run_block_statement(ctx, block_statement, associated_block)
|
||||
}
|
||||
StatementVariant::BraceStatement(brace_statement) => {
|
||||
Statement::Brace(brace_statement) => {
|
||||
self.run_begin_statement(ctx, &brace_statement.jobs)
|
||||
}
|
||||
StatementVariant::IfStatement(ifstmt) => {
|
||||
self.run_if_statement(ctx, ifstmt, associated_block)
|
||||
}
|
||||
StatementVariant::SwitchStatement(switchstmt) => {
|
||||
self.run_switch_statement(ctx, switchstmt)
|
||||
}
|
||||
Statement::If(ifstmt) => self.run_if_statement(ctx, ifstmt, associated_block),
|
||||
Statement::Switch(switchstmt) => self.run_switch_statement(ctx, switchstmt),
|
||||
// Other types should be impossible due to the
|
||||
// specific_statement_type_is_redirectable_block check.
|
||||
StatementVariant::NotStatement(_)
|
||||
| StatementVariant::DecoratedStatement(_)
|
||||
| StatementVariant::None => panic!(),
|
||||
// statement_is_redirectable_block check.
|
||||
Statement::Not(_) | Statement::Decorated(_) => {
|
||||
panic!()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1627,7 +1593,7 @@ fn run_1_job(
|
||||
profile_item.duration = ProfileItem::now() - start_time;
|
||||
profile_item.level = ctx.parser().scope().eval_level;
|
||||
profile_item.cmd =
|
||||
profiling_cmd_name_for_redirectable_block(specific_statement, self.pstree());
|
||||
profiling_cmd_name_for_redirectable_block(statement, self.pstree());
|
||||
profile_item.skipped = false;
|
||||
}
|
||||
|
||||
@@ -1931,56 +1897,35 @@ enum Globspec {
|
||||
}
|
||||
type AstArgsList<'a> = Vec<&'a ast::Argument>;
|
||||
|
||||
/// These are the specific statement types that support redirections.
|
||||
fn type_is_redirectable_block(typ: ast::Type) -> bool {
|
||||
[
|
||||
ast::Type::block_statement,
|
||||
ast::Type::brace_statement,
|
||||
ast::Type::if_statement,
|
||||
ast::Type::switch_statement,
|
||||
]
|
||||
.contains(&typ)
|
||||
}
|
||||
|
||||
fn specific_statement_type_is_redirectable_block(node: &ast::StatementVariant) -> bool {
|
||||
type_is_redirectable_block(node.typ())
|
||||
fn statement_is_redirectable_block(node: &ast::Statement) -> bool {
|
||||
match node {
|
||||
Statement::Decorated(_) | Statement::Not(_) => false,
|
||||
Statement::Block(_) | Statement::Brace(_) | Statement::If(_) | Statement::Switch(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of a redirectable block, for profiling purposes.
|
||||
fn profiling_cmd_name_for_redirectable_block(
|
||||
node: &ast::StatementVariant,
|
||||
node: &ast::Statement,
|
||||
pstree: &ParsedSourceRef,
|
||||
) -> WString {
|
||||
assert!(specific_statement_type_is_redirectable_block(node));
|
||||
assert!(statement_is_redirectable_block(node));
|
||||
|
||||
let source_range = node.try_source_range().expect("No source range for block");
|
||||
|
||||
let src_end = match node {
|
||||
StatementVariant::BlockStatement(block_statement) => {
|
||||
Statement::Block(block_statement) => {
|
||||
let block_header = &block_statement.header;
|
||||
match block_header {
|
||||
BlockStatementHeaderVariant::ForHeader(for_header) => {
|
||||
for_header.semi_nl.source_range().start()
|
||||
}
|
||||
BlockStatementHeaderVariant::WhileHeader(while_header) => {
|
||||
while_header.condition.source_range().start()
|
||||
}
|
||||
BlockStatementHeaderVariant::FunctionHeader(function_header) => {
|
||||
function_header.semi_nl.source_range().start()
|
||||
}
|
||||
BlockStatementHeaderVariant::BeginHeader(begin_header) => {
|
||||
begin_header.kw_begin.source_range().start()
|
||||
}
|
||||
BlockStatementHeaderVariant::None => panic!("Unexpected block header type"),
|
||||
BlockStatementHeader::For(node) => node.semi_nl.source_range().start(),
|
||||
BlockStatementHeader::While(node) => node.condition.source_range().start(),
|
||||
BlockStatementHeader::Function(node) => node.semi_nl.source_range().start(),
|
||||
BlockStatementHeader::Begin(node) => node.kw_begin.source_range().start(),
|
||||
}
|
||||
}
|
||||
StatementVariant::BraceStatement(brace_statement) => {
|
||||
brace_statement.left_brace.source_range().start()
|
||||
}
|
||||
StatementVariant::IfStatement(ifstmt) => {
|
||||
ifstmt.if_clause.condition.job.source_range().end()
|
||||
}
|
||||
StatementVariant::SwitchStatement(switchstmt) => switchstmt.semi_nl.source_range().start(),
|
||||
Statement::Brace(brace_statement) => brace_statement.left_brace.source_range().start(),
|
||||
Statement::If(ifstmt) => ifstmt.if_clause.condition.job.source_range().end(),
|
||||
Statement::Switch(switchstmt) => switchstmt.semi_nl.source_range().start(),
|
||||
_ => {
|
||||
panic!("Not a redirectable block_type");
|
||||
}
|
||||
@@ -2011,17 +1956,19 @@ fn job_node_wants_timing(job_node: &ast::JobPipeline) -> bool {
|
||||
}
|
||||
|
||||
// Helper to return true if a node is 'not time ...' or 'not not time...' or...
|
||||
let is_timed_not_statement = |mut stat: &ast::Statement| loop {
|
||||
match &stat.contents {
|
||||
StatementVariant::NotStatement(ns) => {
|
||||
if ns.time.is_some() {
|
||||
return true;
|
||||
fn is_timed_not_statement(mut stat: &ast::Statement) -> bool {
|
||||
loop {
|
||||
match &stat {
|
||||
Statement::Not(ns) => {
|
||||
if ns.time.is_some() {
|
||||
return true;
|
||||
}
|
||||
stat = &ns.contents;
|
||||
}
|
||||
stat = &ns.contents;
|
||||
_ => return false,
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Do we have a 'not time ...' anywhere in our pipeline?
|
||||
if is_timed_not_statement(&job_node.statement) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ast::{Ast, Node};
|
||||
use crate::ast::{self, Ast, Node};
|
||||
use crate::common::{assert_send, assert_sync};
|
||||
use crate::parse_constants::{
|
||||
token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseKeyword,
|
||||
@@ -187,7 +187,7 @@ pub fn parse_source(
|
||||
flags: ParseTreeFlags,
|
||||
errors: Option<&mut ParseErrorList>,
|
||||
) -> Option<ParsedSourceRef> {
|
||||
let ast = Ast::parse(&src, flags, errors);
|
||||
let ast = ast::parse(&src, flags, errors);
|
||||
if ast.errored() && !flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) {
|
||||
None
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Various mostly unrelated utility functions related to parsing, loading and evaluating fish code.
|
||||
use crate::ast::{
|
||||
self, is_same_node, Ast, Keyword, Leaf, List, Node, NodeVisitor, Token, Traversal,
|
||||
self, is_same_node, Ast, Keyword, Kind, Leaf, Node, NodeVisitor, Token, Traversal,
|
||||
};
|
||||
use crate::builtins::shared::builtin_exists;
|
||||
use crate::common::{
|
||||
@@ -220,17 +220,30 @@ fn parse_util_locate_cmdsub(
|
||||
let mut last_dollar = None;
|
||||
let mut paran_begin = None;
|
||||
let mut paran_end = None;
|
||||
enum Quote {
|
||||
Real(char),
|
||||
VirtualDouble,
|
||||
}
|
||||
fn process_opening_quote(
|
||||
input: &[char],
|
||||
inout_is_quoted: &mut Option<&mut bool>,
|
||||
paran_count: i32,
|
||||
quoted_cmdsubs: &mut Vec<i32>,
|
||||
pos: usize,
|
||||
mut pos: usize,
|
||||
last_dollar: &mut Option<usize>,
|
||||
quote: char,
|
||||
quote: Quote,
|
||||
) -> Option<usize> {
|
||||
let quote = match quote {
|
||||
Quote::Real(q) => q,
|
||||
Quote::VirtualDouble => {
|
||||
pos = pos.saturating_sub(1);
|
||||
'"'
|
||||
}
|
||||
};
|
||||
let q_end = quote_end(input.into(), pos, quote)?;
|
||||
// Found a valid closing quote.
|
||||
if input[q_end] == '$' {
|
||||
// The closing quote is another quoted command substitution.
|
||||
*last_dollar = Some(q_end);
|
||||
quoted_cmdsubs.push(paran_count);
|
||||
}
|
||||
@@ -256,9 +269,9 @@ fn process_opening_quote(
|
||||
&mut quoted_cmdsubs,
|
||||
pos,
|
||||
&mut last_dollar,
|
||||
'"',
|
||||
Quote::VirtualDouble,
|
||||
)
|
||||
.unwrap_or(input.len());
|
||||
.map_or(input.len(), |pos| pos + 1);
|
||||
}
|
||||
|
||||
while pos < input.len() {
|
||||
@@ -272,7 +285,7 @@ fn process_opening_quote(
|
||||
&mut quoted_cmdsubs,
|
||||
pos,
|
||||
&mut last_dollar,
|
||||
c,
|
||||
Quote::Real(c),
|
||||
) {
|
||||
Some(q_end) => pos = q_end,
|
||||
None => break,
|
||||
@@ -295,7 +308,8 @@ fn process_opening_quote(
|
||||
} else if c == ')' {
|
||||
paran_count -= 1;
|
||||
|
||||
if paran_count == 0 && paran_end.is_none() {
|
||||
if paran_count == 0 {
|
||||
assert!(paran_end.is_none());
|
||||
paran_end = Some(pos);
|
||||
break;
|
||||
}
|
||||
@@ -309,21 +323,19 @@ fn process_opening_quote(
|
||||
if quoted_cmdsubs.last() == Some(¶n_count) {
|
||||
quoted_cmdsubs.pop();
|
||||
// Quoted command substitutions temporarily close double quotes.
|
||||
// In "foo$(bar)baz$(qux)"
|
||||
// We are here ^
|
||||
// After the ) in a quoted command substitution, we need to act as if
|
||||
// there was an invisible double quote.
|
||||
match quote_end(input.into(), pos, '"') {
|
||||
Some(q_end) => {
|
||||
// Found a valid closing quote.
|
||||
// Stop at $(qux), which is another quoted command substitution.
|
||||
if input[q_end] == '$' {
|
||||
quoted_cmdsubs.push(paran_count);
|
||||
}
|
||||
pos = q_end;
|
||||
}
|
||||
// In "foo$(bar)baz$(qux)", after the ), we need to act as if there was a double quote.
|
||||
match process_opening_quote(
|
||||
input,
|
||||
&mut inout_is_quoted,
|
||||
paran_count,
|
||||
&mut quoted_cmdsubs,
|
||||
pos,
|
||||
&mut last_dollar,
|
||||
Quote::VirtualDouble,
|
||||
) {
|
||||
Some(q_end) => pos = q_end,
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
is_token_begin = is_token_delimiter(c, input.get(pos + 1).copied());
|
||||
@@ -755,7 +767,7 @@ fn compute_indents(src: &wstr, initial_indent: i32) -> Vec<i32> {
|
||||
// the last node we visited becomes the input indent of the next. I.e. in the case of 'switch
|
||||
// foo ; cas', we get an invalid parse tree (since 'cas' is not valid) but we indent it as if it
|
||||
// were a case item list.
|
||||
let ast = Ast::parse(
|
||||
let ast = ast::parse(
|
||||
src,
|
||||
ParseTreeFlags::CONTINUE_AFTER_ERROR
|
||||
| ParseTreeFlags::INCLUDE_COMMENTS
|
||||
@@ -983,22 +995,18 @@ fn indent_string_part(&mut self, range: Range<usize>, is_double_quoted: bool) {
|
||||
impl<'a> NodeVisitor<'a> for IndentVisitor<'a> {
|
||||
// Default implementation is to just visit children.
|
||||
fn visit(&mut self, node: &'a dyn Node) {
|
||||
let mut inc = 0;
|
||||
let mut dec = 0;
|
||||
use ast::{Category, Type};
|
||||
match node.typ() {
|
||||
Type::job_list | Type::andor_job_list => {
|
||||
let mut inc_dec = (0, 0);
|
||||
match node.kind() {
|
||||
Kind::JobList(_) | Kind::AndorJobList(_) => {
|
||||
// Job lists are never unwound.
|
||||
inc = 1;
|
||||
dec = 1;
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
|
||||
// Increment indents for conditions in headers (#1665).
|
||||
Type::job_conjunction => {
|
||||
let typ = self.parent.unwrap().typ();
|
||||
if matches!(typ, Type::if_clause | Type::while_header) {
|
||||
inc = 1;
|
||||
dec = 1;
|
||||
Kind::JobConjunction(_node) => {
|
||||
let parent_kind = self.parent.unwrap().kind();
|
||||
if matches!(parent_kind, Kind::IfClause(_) | Kind::WhileHeader(_)) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1011,22 +1019,20 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
// ....cmd3
|
||||
// end
|
||||
// See #7252.
|
||||
Type::job_continuation => {
|
||||
if self.has_newline(&node.as_job_continuation().unwrap().newlines) {
|
||||
inc = 1;
|
||||
dec = 1;
|
||||
Kind::JobContinuation(node) => {
|
||||
if self.has_newline(&node.newlines) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Likewise for && and ||.
|
||||
Type::job_conjunction_continuation => {
|
||||
if self.has_newline(&node.as_job_conjunction_continuation().unwrap().newlines) {
|
||||
inc = 1;
|
||||
dec = 1;
|
||||
Kind::JobConjunctionContinuation(node) => {
|
||||
if self.has_newline(&node.newlines) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Type::case_item_list => {
|
||||
Kind::CaseItemList(_) => {
|
||||
// Here's a hack. Consider:
|
||||
// switch abc
|
||||
// cas
|
||||
@@ -1044,37 +1050,35 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
// And so we will think that the 'cas' job is at the same level as the switch.
|
||||
// To address this, if we see that the switch statement was not closed, do not
|
||||
// decrement the indent afterwards.
|
||||
inc = 1;
|
||||
let switchs = self.parent.unwrap().as_switch_statement().unwrap();
|
||||
dec = if switchs.end.has_source() { 1 } else { 0 };
|
||||
let Kind::SwitchStatement(switchs) = self.parent.unwrap().kind() else {
|
||||
panic!("Expected switch statement");
|
||||
};
|
||||
let dec = if switchs.end.has_source() { 1 } else { 0 };
|
||||
inc_dec = (1, dec);
|
||||
}
|
||||
Type::token_base => {
|
||||
let token_type = node.as_token().unwrap().token_type();
|
||||
let parent_type = self.parent.unwrap().typ();
|
||||
if parent_type == Type::begin_header && token_type == ParseTokenType::end {
|
||||
|
||||
Kind::Token(node) => {
|
||||
let token_type = node.token_type();
|
||||
let parent_kind = self.parent.unwrap().kind();
|
||||
if matches!(parent_kind, Kind::BeginHeader(_)) && token_type == ParseTokenType::end
|
||||
{
|
||||
// The newline after "begin" is optional, so it is part of the header.
|
||||
// The header is not in the indented block, so indent the newline here.
|
||||
if node.source(self.src) == "\n" {
|
||||
inc = 1;
|
||||
dec = 1;
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
}
|
||||
// if token_type == ParseTokenType::right_brace && parent_type == Type::brace_statement
|
||||
// {
|
||||
// inc = 1;
|
||||
// dec = 1;
|
||||
// }
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
let range = node.source_range();
|
||||
if range.length() > 0 && node.category() == Category::leaf {
|
||||
if range.length() > 0 && node.as_leaf().is_some() {
|
||||
self.record_line_continuations_until(range.start());
|
||||
self.indents[self.last_leaf_end..range.start()].fill(self.last_indent);
|
||||
}
|
||||
|
||||
self.indent += inc;
|
||||
self.indent += inc_dec.0;
|
||||
|
||||
// If we increased the indentation, apply it to the remainder of the string, even if the
|
||||
// list is empty. For example (where _ represents the cursor):
|
||||
@@ -1083,12 +1087,12 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
// _
|
||||
//
|
||||
// we want to indent the newline.
|
||||
if inc != 0 {
|
||||
if inc_dec.0 != 0 {
|
||||
self.last_indent = self.indent;
|
||||
}
|
||||
|
||||
// If this is a leaf node, apply the current indentation.
|
||||
if node.category() == Category::leaf && range.length() != 0 {
|
||||
if node.as_leaf().is_some() && range.length() != 0 {
|
||||
let leading_spaces = self.src[..range.start()]
|
||||
.chars()
|
||||
.rev()
|
||||
@@ -1101,9 +1105,9 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
}
|
||||
|
||||
let saved = self.parent.replace(node);
|
||||
node.accept(self, false);
|
||||
node.accept(self);
|
||||
self.parent = saved;
|
||||
self.indent -= dec;
|
||||
self.indent -= inc_dec.1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1128,7 +1132,7 @@ pub fn parse_util_detect_errors(
|
||||
|
||||
// Parse the input string into an ast. Some errors are detected here.
|
||||
let mut parse_errors = ParseErrorList::new();
|
||||
let ast = Ast::parse(buff_src, parse_flags, Some(&mut parse_errors));
|
||||
let ast = ast::parse(buff_src, parse_flags, Some(&mut parse_errors));
|
||||
if allow_incomplete {
|
||||
// Issue #1238: If the only error was unterminated quote, then consider this to have parsed
|
||||
// successfully.
|
||||
@@ -1198,76 +1202,95 @@ pub fn parse_util_detect_errors_in_ast(
|
||||
|
||||
let mut traversal = ast::Traversal::new(ast.top());
|
||||
while let Some(node) = traversal.next() {
|
||||
if let Some(jc) = node.as_job_continuation() {
|
||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||
// See if our pipe has source but our statement does not.
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() {
|
||||
has_unclosed_pipe = true;
|
||||
match node.kind() {
|
||||
Kind::JobContinuation(jc) => {
|
||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||
// See if our pipe has source but our statement does not.
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() {
|
||||
has_unclosed_pipe = true;
|
||||
}
|
||||
}
|
||||
} else if let Some(job_conjunction) = node.as_job_conjunction() {
|
||||
errored |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors);
|
||||
} else if let Some(jcc) = node.as_job_conjunction_continuation() {
|
||||
// Somewhat clumsy way of checking for a job without source in a conjunction.
|
||||
// See if our conjunction operator (&& or ||) has source but our job does not.
|
||||
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() {
|
||||
has_unclosed_conjunction = true;
|
||||
Kind::JobConjunction(job_conjunction) => {
|
||||
errored |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors);
|
||||
}
|
||||
} else if let Some(arg) = node.as_argument() {
|
||||
let arg_src = arg.source(buff_src);
|
||||
res |= parse_util_detect_errors_in_argument(arg, arg_src, &mut out_errors)
|
||||
.err()
|
||||
.unwrap_or_default();
|
||||
} else if let Some(job) = node.as_job_pipeline() {
|
||||
// Disallow background in the following cases:
|
||||
//
|
||||
// foo & ; and bar
|
||||
// foo & ; or bar
|
||||
// if foo & ; end
|
||||
// while foo & ; end
|
||||
// If it's not a background job, nothing to do.
|
||||
if job.bg.is_some() {
|
||||
errored |= detect_errors_in_backgrounded_job(&traversal, job, &mut out_errors);
|
||||
Kind::JobConjunctionContinuation(jcc) => {
|
||||
// Somewhat clumsy way of checking for a job without source in a conjunction.
|
||||
// See if our conjunction operator (&& or ||) has source but our job does not.
|
||||
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() {
|
||||
has_unclosed_conjunction = true;
|
||||
}
|
||||
}
|
||||
} else if let Some(stmt) = node.as_decorated_statement() {
|
||||
errored |=
|
||||
detect_errors_in_decorated_statement(buff_src, &traversal, stmt, &mut out_errors);
|
||||
} else if let Some(block) = node.as_block_statement() {
|
||||
// If our 'end' had no source, we are unsourced.
|
||||
if !block.end.has_source() {
|
||||
has_unclosed_block = true;
|
||||
Kind::Argument(arg) => {
|
||||
let arg_src = arg.source(buff_src);
|
||||
res |= parse_util_detect_errors_in_argument(arg, arg_src, &mut out_errors)
|
||||
.err()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&block.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
} else if let Some(brace_statement) = node.as_brace_statement() {
|
||||
// If our closing brace had no source, we are unsourced.
|
||||
if !brace_statement.right_brace.has_source() {
|
||||
has_unclosed_block = true;
|
||||
Kind::JobPipeline(job) => {
|
||||
// Disallow background in the following cases:
|
||||
//
|
||||
// foo & ; and bar
|
||||
// foo & ; or bar
|
||||
// if foo & ; end
|
||||
// while foo & ; end
|
||||
// If it's not a background job, nothing to do.
|
||||
if job.bg.is_some() {
|
||||
errored |= detect_errors_in_backgrounded_job(&traversal, job, &mut out_errors);
|
||||
}
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&brace_statement.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
} else if let Some(ifs) = node.as_if_statement() {
|
||||
// If our 'end' had no source, we are unsourced.
|
||||
if !ifs.end.has_source() {
|
||||
has_unclosed_block = true;
|
||||
Kind::DecoratedStatement(stmt) => {
|
||||
errored |= detect_errors_in_decorated_statement(
|
||||
buff_src,
|
||||
&traversal,
|
||||
stmt,
|
||||
&mut out_errors,
|
||||
);
|
||||
}
|
||||
errored |=
|
||||
detect_errors_in_block_redirection_list(node, &ifs.args_or_redirs, &mut out_errors);
|
||||
} else if let Some(switchs) = node.as_switch_statement() {
|
||||
// If our 'end' had no source, we are unsourced.
|
||||
if !switchs.end.has_source() {
|
||||
has_unclosed_block = true;
|
||||
Kind::BlockStatement(block) => {
|
||||
// If our 'end' had no source, we are unsourced.
|
||||
if !block.end.has_source() {
|
||||
has_unclosed_block = true;
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&block.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&switchs.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
Kind::BraceStatement(brace_statement) => {
|
||||
// If our closing brace had no source, we are unsourced.
|
||||
if !brace_statement.right_brace.has_source() {
|
||||
has_unclosed_block = true;
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&brace_statement.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
}
|
||||
Kind::IfStatement(ifs) => {
|
||||
// If our 'end' had no source, we are unsourced.
|
||||
if !ifs.end.has_source() {
|
||||
has_unclosed_block = true;
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&ifs.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
}
|
||||
Kind::SwitchStatement(switchs) => {
|
||||
// If our 'end' had no source, we are unsourced.
|
||||
if !switchs.end.has_source() {
|
||||
has_unclosed_block = true;
|
||||
}
|
||||
errored |= detect_errors_in_block_redirection_list(
|
||||
node,
|
||||
&switchs.args_or_redirs,
|
||||
&mut out_errors,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,14 +1327,15 @@ pub fn parse_util_detect_errors_in_argument_list(
|
||||
|
||||
// Parse the string as a freestanding argument list.
|
||||
let mut errors = ParseErrorList::new();
|
||||
let ast = Ast::parse_argument_list(arg_list_src, ParseTreeFlags::empty(), Some(&mut errors));
|
||||
let ast = ast::parse_argument_list(arg_list_src, ParseTreeFlags::empty(), Some(&mut errors));
|
||||
if !errors.is_empty() {
|
||||
return get_error_text(&errors);
|
||||
}
|
||||
|
||||
// Get the root argument list and extract arguments from it.
|
||||
// Test each of these.
|
||||
let args = &ast.top().as_freestanding_argument_list().unwrap().arguments;
|
||||
let arg_list: &ast::FreestandingArgumentList = ast.top();
|
||||
let args = &arg_list.arguments;
|
||||
for arg in args.iter() {
|
||||
let arg_src = arg.source(arg_list_src);
|
||||
if parse_util_detect_errors_in_argument(arg, arg_src, &mut Some(&mut errors)).is_err() {
|
||||
@@ -1541,19 +1565,22 @@ fn detect_errors_in_backgrounded_job(
|
||||
// foo & ; or bar
|
||||
// if foo & ; end
|
||||
// while foo & ; end
|
||||
let Some(job_conj) = traversal.parent(job).as_job_conjunction() else {
|
||||
let Kind::JobConjunction(job_conj) = traversal.parent(job).kind() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let job_conj_parent = traversal.parent(job_conj);
|
||||
if job_conj_parent.as_if_clause().is_some() || job_conj_parent.as_while_header().is_some() {
|
||||
if matches!(
|
||||
job_conj_parent.kind(),
|
||||
Kind::IfClause(_) | Kind::WhileHeader(_)
|
||||
) {
|
||||
errored = append_syntax_error!(
|
||||
parse_errors,
|
||||
source_range.start(),
|
||||
source_range.length(),
|
||||
BACKGROUND_IN_CONDITIONAL_ERROR_MSG
|
||||
);
|
||||
} else if let Some(jlist) = job_conj_parent.as_job_list() {
|
||||
} else if let Kind::JobList(jlist) = job_conj_parent.kind() {
|
||||
// This isn't very complete, e.g. we don't catch 'foo & ; not and bar'.
|
||||
// Find the index of ourselves in the job list.
|
||||
let index = jlist
|
||||
@@ -1607,13 +1634,18 @@ fn detect_errors_in_decorated_statement(
|
||||
}
|
||||
|
||||
// Get the statement we are part of.
|
||||
let st = traversal.parent(dst).as_statement().unwrap();
|
||||
let Kind::Statement(st) = traversal.parent(dst).kind() else {
|
||||
panic!();
|
||||
};
|
||||
|
||||
// Walk up to the job.
|
||||
let job = traversal
|
||||
.parent_nodes()
|
||||
.find_map(|n| n.as_job_pipeline())
|
||||
.expect("Should have found the job");
|
||||
.find_map(|n| match n.kind() {
|
||||
Kind::JobPipeline(job) => Some(job),
|
||||
_ => None,
|
||||
})
|
||||
.expect("should have found the job");
|
||||
|
||||
// Check our pipeline position.
|
||||
let pipe_pos = if job.continuation.is_empty() {
|
||||
@@ -1724,17 +1756,17 @@ fn detect_errors_in_decorated_statement(
|
||||
// loop from the ancestor alone; we need the header. That is, we hit a
|
||||
// block_statement, and have to check its header.
|
||||
let mut found_loop = false;
|
||||
for block in traversal
|
||||
.parent_nodes()
|
||||
.filter_map(|anc| anc.as_block_statement())
|
||||
{
|
||||
match block.header.typ() {
|
||||
ast::Type::for_header | ast::Type::while_header => {
|
||||
for block in traversal.parent_nodes().filter_map(|anc| match anc.kind() {
|
||||
Kind::BlockStatement(block) => Some(block),
|
||||
_ => None,
|
||||
}) {
|
||||
match block.header {
|
||||
ast::BlockStatementHeader::For(_) | ast::BlockStatementHeader::While(_) => {
|
||||
// This is a loop header, so we can break or continue.
|
||||
found_loop = true;
|
||||
break;
|
||||
}
|
||||
ast::Type::function_header => {
|
||||
ast::BlockStatementHeader::Function(_) => {
|
||||
// This is a function header, so we cannot break or
|
||||
// continue. We stop our search here.
|
||||
found_loop = false;
|
||||
@@ -1808,7 +1840,7 @@ fn detect_errors_in_block_redirection_list(
|
||||
return false;
|
||||
};
|
||||
let r = first_arg.source_range();
|
||||
if parent.as_brace_statement().is_some() {
|
||||
if let Kind::BraceStatement(_) = parent.kind() {
|
||||
append_syntax_error!(out_errors, r.start(), r.length(), RIGHT_BRACE_ARG_ERR_MSG);
|
||||
} else {
|
||||
append_syntax_error!(out_errors, r.start(), r.length(), END_ARG_ERR_MSG);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// The fish parser. Contains functions for parsing and evaluating code.
|
||||
|
||||
use crate::ast::{self, Ast, List, Node};
|
||||
use crate::ast::{self, Node};
|
||||
use crate::builtins::shared::STATUS_ILLEGAL_CMD;
|
||||
use crate::common::{
|
||||
escape_string, wcs2string, CancelChecker, EscapeFlags, EscapeStringStyle, FilenameRef,
|
||||
@@ -30,6 +30,7 @@
|
||||
use crate::util::get_time;
|
||||
use crate::wait_handle::WaitHandleStore;
|
||||
use crate::wchar::{wstr, WString, L};
|
||||
use crate::wchar_ext::WExt;
|
||||
use crate::wutil::{perror, wgettext, wgettext_fmt};
|
||||
use crate::{function, FLOG};
|
||||
use libc::c_int;
|
||||
@@ -556,7 +557,7 @@ pub fn eval_parsed_source(
|
||||
block_type: BlockType,
|
||||
) -> EvalRes {
|
||||
assert!([BlockType::top, BlockType::subst].contains(&block_type));
|
||||
let job_list = ps.ast.top().as_job_list().unwrap();
|
||||
let job_list = ps.ast.top();
|
||||
if !job_list.is_empty() {
|
||||
// Execute the top job list.
|
||||
self.eval_node(ps, job_list, io, job_group, block_type)
|
||||
@@ -581,7 +582,7 @@ pub fn eval_wstr(
|
||||
use crate::parse_tree::ParsedSource;
|
||||
use crate::parse_util::parse_util_detect_errors_in_ast;
|
||||
let mut errors = vec![];
|
||||
let ast = Ast::parse(&src, ParseTreeFlags::empty(), Some(&mut errors));
|
||||
let ast = ast::parse(&src, ParseTreeFlags::empty(), Some(&mut errors));
|
||||
let mut errored = ast.errored();
|
||||
if !errored {
|
||||
errored = parse_util_detect_errors_in_ast(&ast, &src, Some(&mut errors)).is_err();
|
||||
@@ -726,7 +727,7 @@ pub fn expand_argument_list(
|
||||
ctx: &OperationContext<'_>,
|
||||
) -> CompletionList {
|
||||
// Parse the string as an argument list.
|
||||
let ast = Ast::parse_argument_list(arg_list_src, ParseTreeFlags::default(), None);
|
||||
let ast = ast::parse_argument_list(arg_list_src, ParseTreeFlags::default(), None);
|
||||
if ast.errored() {
|
||||
// Failed to parse. Here we expect to have reported any errors in test_args.
|
||||
return vec![];
|
||||
@@ -734,8 +735,7 @@ pub fn expand_argument_list(
|
||||
|
||||
// Get the root argument list and extract arguments from it.
|
||||
let mut result = vec![];
|
||||
let list = ast.top().as_freestanding_argument_list().unwrap();
|
||||
for arg in &list.arguments {
|
||||
for arg in &ast.top().arguments {
|
||||
let arg_src = arg.source(arg_list_src);
|
||||
if matches!(
|
||||
expand_string(arg_src.to_owned(), &mut result, flags, ctx, None).result,
|
||||
@@ -1068,7 +1068,7 @@ pub fn job_get_with_index_from_pid(&self, pid: Pid) -> Option<(usize, JobRef)> {
|
||||
|
||||
/// Returns a new profile item if profiling is active. The caller should fill it in.
|
||||
/// The Parser will deallocate it.
|
||||
/// If profiling is not active, this returns nullptr.
|
||||
/// If profiling is not active, this returns None.
|
||||
pub fn create_profile_item(&self) -> Option<usize> {
|
||||
if PROFILING_ACTIVE.load() {
|
||||
let mut profile_items = self.profile_items.borrow_mut();
|
||||
@@ -1272,7 +1272,12 @@ fn print_profile(items: &[ProfileItem], out: &mut File) {
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
let _ = out.write_all(&wcs2string(&item.cmd));
|
||||
let indentation_level = col_width + 1 + col_width + 1 + level + 1;
|
||||
let indented_cmd = item.cmd.replace(
|
||||
L!("\n"),
|
||||
&(WString::from("\n") + &wstr::repeat(L!(" "), indentation_level)[..]),
|
||||
);
|
||||
let _ = out.write_all(&wcs2string(&indented_cmd));
|
||||
let _ = out.write_all(b"\n");
|
||||
}
|
||||
}
|
||||
|
||||
60
src/path.rs
60
src/path.rs
@@ -2,7 +2,7 @@
|
||||
//! for testing if a command with a given name can be found in the PATH, and various other
|
||||
//! path-related issues.
|
||||
|
||||
use crate::common::{is_windows_subsystem_for_linux as is_wsl, wcs2osstring, wcs2zstring, WSL};
|
||||
use crate::common::{wcs2osstring, wcs2zstring};
|
||||
use crate::env::{EnvMode, EnvStack, Environment};
|
||||
use crate::expand::{expand_tilde, HOME_DIRECTORY};
|
||||
use crate::flog::{FLOG, FLOGF};
|
||||
@@ -15,6 +15,7 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::ErrorKind;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
/// Returns the user configuration directory for fish. If the directory or one of its parents
|
||||
@@ -308,29 +309,6 @@ fn path_get_path_core<S: AsRef<wstr>>(cmd: &wstr, pathsv: &[S]) -> GetPathResult
|
||||
return GetPathResult::new(test_path(cmd).err(), cmd.to_owned());
|
||||
}
|
||||
|
||||
// WSLv1/WSLv2 tack on the entire Windows PATH to the end of the PATH environment variable, and
|
||||
// accessing these paths from WSL binaries is pathalogically slow. We also don't expect to find
|
||||
// any "normal" nix binaries under these paths, so we can skip them unless we are executing bins
|
||||
// with Windows-ish names. We try to keep paths manually added to $fish_user_paths by only
|
||||
// chopping off entries after the last "normal" PATH entry.
|
||||
let pathsv = if is_wsl(WSL::Any) && !cmd.contains('.') {
|
||||
let win_path_count = pathsv
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|p| {
|
||||
let p = p.as_ref();
|
||||
p.starts_with("/mnt/")
|
||||
&& p.chars()
|
||||
.nth("/mnt/x".len())
|
||||
.map(|c| c == '/')
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.count();
|
||||
&pathsv[..pathsv.len() - win_path_count]
|
||||
} else {
|
||||
pathsv
|
||||
};
|
||||
|
||||
let mut best = noent_res;
|
||||
for next_path in pathsv {
|
||||
let next_path: &wstr = next_path.as_ref();
|
||||
@@ -693,10 +671,11 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
|
||||
let narrow = wcs2zstring(path);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statfs(narrow.as_ptr(), &mut buf) } < 0 {
|
||||
let mut buf = MaybeUninit::uninit();
|
||||
if unsafe { libc::statfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 0 {
|
||||
return DirRemoteness::unknown;
|
||||
}
|
||||
let buf = unsafe { buf.assume_init() };
|
||||
// Linux has constants for these like NFS_SUPER_MAGIC, SMB_SUPER_MAGIC, CIFS_MAGIC_NUMBER but
|
||||
// these are in varying headers. Simply hard code them.
|
||||
// Note that we treat FUSE filesystems as remote, which means we lock less on such filesystems.
|
||||
@@ -726,32 +705,19 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let st_local = ST_LOCAL();
|
||||
if st_local != 0 {
|
||||
// ST_LOCAL is a flag to statvfs, which is itself standardized.
|
||||
// In practice the only system to use this path is NetBSD.
|
||||
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
|
||||
return DirRemoteness::unknown;
|
||||
}
|
||||
// statvfs::f_flag is `unsigned long`, which is 4-bytes on most 32-bit targets.
|
||||
#[cfg_attr(target_pointer_width = "64", allow(clippy::useless_conversion))]
|
||||
return if u64::from(buf.f_flag) & st_local != 0 {
|
||||
DirRemoteness::local
|
||||
} else {
|
||||
DirRemoteness::remote
|
||||
};
|
||||
}
|
||||
let mnt_local = MNT_LOCAL();
|
||||
if mnt_local != 0 {
|
||||
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
|
||||
// ST_LOCAL is a flag to statvfs, which is itself standardized.
|
||||
// In practice the only system to define it is NetBSD.
|
||||
let local_flag = ST_LOCAL() | MNT_LOCAL();
|
||||
if local_flag != 0 {
|
||||
let mut buf = MaybeUninit::uninit();
|
||||
if unsafe { libc::statvfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 0 {
|
||||
return DirRemoteness::unknown;
|
||||
}
|
||||
let buf = unsafe { buf.assume_init() };
|
||||
// statfs::f_flag is hard-coded as 64-bits on 32/64-bit FreeBSD but it's a (4-byte)
|
||||
// long on 32-bit NetBSD.. and always 4-bytes on macOS (even on 64-bit builds).
|
||||
#[allow(clippy::useless_conversion)]
|
||||
return if u64::from(buf.f_flag) & mnt_local != 0 {
|
||||
return if u64::from(buf.f_flag) & local_flag != 0 {
|
||||
DirRemoteness::local
|
||||
} else {
|
||||
DirRemoteness::remote
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::fd::RawFd;
|
||||
use std::rc::Rc;
|
||||
@@ -343,9 +344,9 @@ pub fn reclaim(&mut self) {
|
||||
/// Save the current tty modes into the owning job group, if we are transferred.
|
||||
pub fn save_tty_modes(&mut self) {
|
||||
if let Some(ref mut owner) = self.owner {
|
||||
let mut tmodes: libc::termios = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::tcgetattr(STDIN_FILENO, &mut tmodes) } == 0 {
|
||||
owner.tmodes.replace(Some(tmodes));
|
||||
let mut tmodes = MaybeUninit::uninit();
|
||||
if unsafe { libc::tcgetattr(STDIN_FILENO, tmodes.as_mut_ptr()) } == 0 {
|
||||
owner.tmodes.replace(Some(unsafe { tmodes.assume_init() }));
|
||||
} else if errno::errno().0 != ENOTTY {
|
||||
perror("tcgetattr");
|
||||
}
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::cmp;
|
||||
use std::io::BufReader;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::ControlFlow;
|
||||
use std::ops::Range;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::fd::RawFd;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
@@ -45,7 +47,7 @@
|
||||
use errno::{errno, Errno};
|
||||
|
||||
use crate::abbrs::abbrs_match;
|
||||
use crate::ast::{is_same_node, Ast, Category};
|
||||
use crate::ast::{self, is_same_node, Kind};
|
||||
use crate::builtins::shared::ErrorCode;
|
||||
use crate::builtins::shared::STATUS_CMD_ERROR;
|
||||
use crate::builtins::shared::STATUS_CMD_OK;
|
||||
@@ -241,7 +243,11 @@ pub(crate) fn initial_query(
|
||||
vars: Option<&dyn Environment>,
|
||||
) {
|
||||
blocking_query.get_or_init(|| {
|
||||
let query = if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() {
|
||||
let query = if is_dumb()
|
||||
|| IN_MIDNIGHT_COMMANDER.load()
|
||||
|| IN_DVTM.load()
|
||||
|| !isatty(STDOUT_FILENO)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
// Query for kitty keyboard protocol support.
|
||||
@@ -655,10 +661,11 @@ pub fn reader_read(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), Error
|
||||
// See also, commit 396bf12. Without the need for this workaround we would just write:
|
||||
// int inter = ((fd == STDIN_FILENO) && isatty(STDIN_FILENO));
|
||||
if fd == STDIN_FILENO {
|
||||
let mut t: libc::termios = unsafe { std::mem::zeroed() };
|
||||
let mut t = MaybeUninit::uninit();
|
||||
if isatty(STDIN_FILENO) {
|
||||
interactive = true;
|
||||
} else if unsafe { libc::tcgetattr(STDIN_FILENO, &mut t) } == -1 && errno().0 == EIO {
|
||||
} else if unsafe { libc::tcgetattr(STDIN_FILENO, t.as_mut_ptr()) } == -1 && errno().0 == EIO
|
||||
{
|
||||
redirect_tty_output(false);
|
||||
interactive = true;
|
||||
}
|
||||
@@ -797,7 +804,7 @@ fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> {
|
||||
loop {
|
||||
let mut buff = [0_u8; 4096];
|
||||
|
||||
match nix::unistd::read(fd, &mut buff) {
|
||||
match nix::unistd::read(unsafe { BorrowedFd::borrow_raw(fd) }, &mut buff) {
|
||||
Ok(0) => {
|
||||
// EOF.
|
||||
break;
|
||||
@@ -1057,12 +1064,11 @@ pub fn reader_reading_interrupted(data: &mut ReaderData) -> i32 {
|
||||
}
|
||||
|
||||
/// Read one line of input. Before calling this function, reader_push() must have been called in
|
||||
/// order to set up a valid reader environment. If nchars > 0, return after reading that many
|
||||
/// order to set up a valid reader environment. If nchars is given, return after reading that many
|
||||
/// characters even if a full line has not yet been read. Note: the returned value may be longer
|
||||
/// than nchars if a single keypress resulted in multiple characters being inserted into the
|
||||
/// commandline.
|
||||
pub fn reader_readline(parser: &Parser, nchars: usize) -> Option<WString> {
|
||||
let nchars = NonZeroUsize::try_from(nchars).ok();
|
||||
pub fn reader_readline(parser: &Parser, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
let data = current_data().unwrap();
|
||||
let mut reader = Reader { parser, data };
|
||||
reader.readline(nchars)
|
||||
@@ -1523,6 +1529,9 @@ pub(crate) fn blocking_query(&self) -> RefMut<'_, Option<TerminalQuery>> {
|
||||
}
|
||||
|
||||
pub fn request_cursor_position(&mut self, out: &mut Outputter, q: CursorPositionQuery) {
|
||||
if !isatty(STDOUT_FILENO) {
|
||||
return;
|
||||
}
|
||||
let mut query = self.blocking_query();
|
||||
assert!(query.is_none());
|
||||
*query = Some(TerminalQuery::CursorPositionReport(q));
|
||||
@@ -2179,8 +2188,10 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
unsafe { libc::tcsetpgrp(self.conf.inputfd, libc::getpgrp()) };
|
||||
|
||||
// Get the current terminal modes. These will be restored when the function returns.
|
||||
let mut old_modes: libc::termios = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::tcgetattr(self.conf.inputfd, &mut old_modes) } == -1 && errno().0 == EIO {
|
||||
let mut old_modes = MaybeUninit::uninit();
|
||||
if unsafe { libc::tcgetattr(self.conf.inputfd, old_modes.as_mut_ptr()) } == -1
|
||||
&& errno().0 == EIO
|
||||
{
|
||||
redirect_tty_output(false);
|
||||
}
|
||||
|
||||
@@ -2274,7 +2285,7 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as _ {
|
||||
// The order of the two conditions below is important. Try to restore the mode
|
||||
// in all cases, but only complain if interactive.
|
||||
if unsafe { libc::tcsetattr(self.conf.inputfd, TCSANOW, &old_modes) } == -1
|
||||
if unsafe { libc::tcsetattr(self.conf.inputfd, TCSANOW, old_modes.as_ptr()) } == -1
|
||||
&& is_interactive_session()
|
||||
{
|
||||
if errno().0 == EIO {
|
||||
@@ -2430,8 +2441,7 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
});
|
||||
|
||||
// If we ran `exit` anywhere, exit.
|
||||
self.exit_loop_requested =
|
||||
self.exit_loop_requested || self.parser.libdata().exit_current_script;
|
||||
self.exit_loop_requested |= self.parser.libdata().exit_current_script;
|
||||
self.parser.libdata_mut().exit_current_script = false;
|
||||
if self.exit_loop_requested {
|
||||
return ControlFlow::Continue(());
|
||||
@@ -3940,9 +3950,9 @@ fn handle_execute(&mut self) -> bool {
|
||||
// using a backslash, insert a newline.
|
||||
// If the user hits return while navigating the pager, it only clears the pager.
|
||||
if self.is_navigating_pager_contents() {
|
||||
let search_field = &self.data.pager.search_field_line;
|
||||
if self.history_pager.is_some() && self.pager.selected_completion_idx.is_none() {
|
||||
let range = 0..self.command_line.len();
|
||||
let search_field = &self.data.pager.search_field_line;
|
||||
let offset_from_end = search_field.len() - search_field.position();
|
||||
let mut cursor = self.command_line.position();
|
||||
let updated = replace_line_at_cursor(
|
||||
@@ -3952,6 +3962,13 @@ fn handle_execute(&mut self) -> bool {
|
||||
);
|
||||
self.replace_substring(EditableLineTag::Commandline, range, updated);
|
||||
self.command_line.set_position(cursor - offset_from_end);
|
||||
} else if self
|
||||
.pager
|
||||
.selected_completion(&self.data.current_page_rendering)
|
||||
.is_none()
|
||||
{
|
||||
let failed_search = search_field.text().to_owned();
|
||||
self.insert_string(EditableLineTag::Commandline, &failed_search);
|
||||
}
|
||||
self.clear_pager();
|
||||
return true;
|
||||
@@ -4258,10 +4275,10 @@ fn term_donate(quiet: bool /* = false */) {
|
||||
|
||||
/// Copy the (potentially changed) terminal modes and use them from now on.
|
||||
pub fn term_copy_modes() {
|
||||
let mut modes: libc::termios = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::tcgetattr(STDIN_FILENO, &mut modes) };
|
||||
let mut modes = MaybeUninit::uninit();
|
||||
unsafe { libc::tcgetattr(STDIN_FILENO, modes.as_mut_ptr()) };
|
||||
let mut tty_modes_for_external_cmds = TTY_MODES_FOR_EXTERNAL_CMDS.lock().unwrap();
|
||||
*tty_modes_for_external_cmds = modes;
|
||||
*tty_modes_for_external_cmds = unsafe { modes.assume_init() };
|
||||
// We still want to fix most egregious breakage.
|
||||
// E.g. OPOST is *not* something that should be set globally,
|
||||
// and 99% triggered by a crashed program.
|
||||
@@ -4876,7 +4893,13 @@ fn update_autosuggestion(&mut self) {
|
||||
}
|
||||
|
||||
let el = &self.data.command_line;
|
||||
let autosuggestion = &self.autosuggestion;
|
||||
if self.is_at_line_with_autosuggestion() {
|
||||
assert!(string_prefixes_string_maybe_case_insensitive(
|
||||
autosuggestion.icase,
|
||||
&el.text()[autosuggestion.search_string_range.clone()],
|
||||
&autosuggestion.text
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5339,15 +5362,15 @@ fn extract_tokens(s: &wstr) -> Vec<PositionedToken> {
|
||||
let ast_flags = ParseTreeFlags::CONTINUE_AFTER_ERROR
|
||||
| ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS
|
||||
| ParseTreeFlags::LEAVE_UNTERMINATED;
|
||||
let ast = Ast::parse(s, ast_flags, None);
|
||||
let ast = ast::parse(s, ast_flags, None);
|
||||
|
||||
let mut result = vec![];
|
||||
let mut traversal = ast.walk();
|
||||
while let Some(node) = traversal.next() {
|
||||
// We are only interested in leaf nodes with source.
|
||||
if node.category() != Category::leaf {
|
||||
if node.as_leaf().is_none() {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let range = node.source_range();
|
||||
if range.length() == 0 {
|
||||
continue;
|
||||
@@ -5381,10 +5404,10 @@ fn extract_tokens(s: &wstr) -> Vec<PositionedToken> {
|
||||
if !has_cmd_subs {
|
||||
// Common case of no command substitutions in this leaf node.
|
||||
// Check if a node is the command portion of a decorated statement.
|
||||
let is_cmd = traversal
|
||||
.parent(node)
|
||||
.as_decorated_statement()
|
||||
.is_some_and(|stmt| is_same_node(node, &stmt.command));
|
||||
let mut is_cmd = false;
|
||||
if let Kind::DecoratedStatement(stmt) = traversal.parent(node).kind() {
|
||||
is_cmd = is_same_node(node, &stmt.command);
|
||||
}
|
||||
result.push(PositionedToken { range, is_cmd })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,11 +756,11 @@ pub fn save_status(&mut self) {
|
||||
/// Return whether we believe the cursor is wrapped onto the last line, and that line is
|
||||
/// otherwise empty. This includes both soft and hard wrapping.
|
||||
pub fn cursor_is_wrapped_to_own_line(&self) -> bool {
|
||||
// Note == comparison against the line count is correct: we do not create a line just for the
|
||||
// cursor. If there is a line containing the cursor, then it means that line has contents and we
|
||||
// should return false.
|
||||
// Don't consider dumb terminals to have wrapping for the purposes of this function.
|
||||
self.actual.cursor.x == 0 && self.actual.cursor.y == self.actual.line_count() && !is_dumb()
|
||||
self.actual.cursor.x == 0
|
||||
&& self.actual.cursor.y != 0
|
||||
&& self.actual.cursor.y + 1 == self.actual.line_count()
|
||||
&& !is_dumb()
|
||||
}
|
||||
|
||||
/// Appends a character to the end of the line that the output cursor is on. This function
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroI32;
|
||||
|
||||
use crate::common::exit_without_destructors;
|
||||
@@ -131,8 +132,9 @@ pub fn signal_reset_handlers() {
|
||||
|
||||
for data in SIGNAL_TABLE.iter() {
|
||||
if data.signal == libc::SIGHUP {
|
||||
let mut oact: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigaction(libc::SIGHUP, std::ptr::null(), &mut oact) };
|
||||
let mut oact = MaybeUninit::uninit();
|
||||
unsafe { libc::sigaction(libc::SIGHUP, std::ptr::null(), oact.as_mut_ptr()) };
|
||||
let oact = unsafe { oact.assume_init() };
|
||||
if oact.sa_sigaction == libc::SIG_IGN {
|
||||
continue;
|
||||
}
|
||||
@@ -277,30 +279,31 @@ pub fn signal_handle(sig: Signal) {
|
||||
}
|
||||
|
||||
pub static signals_to_default: Lazy<libc::sigset_t> = Lazy::new(|| {
|
||||
let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigemptyset(&mut set) };
|
||||
let mut set = MaybeUninit::uninit();
|
||||
unsafe { libc::sigemptyset(set.as_mut_ptr()) };
|
||||
for data in SIGNAL_TABLE.iter() {
|
||||
// If SIGHUP is being ignored (e.g., because were were run via `nohup`) don't reset it.
|
||||
// We don't special case other signals because if they're being ignored that shouldn't
|
||||
// affect processes we spawn. They should get the default behavior for those signals.
|
||||
if data.signal == libc::SIGHUP {
|
||||
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::sigaction(data.signal.code(), std::ptr::null(), &mut act) };
|
||||
let mut act = MaybeUninit::uninit();
|
||||
unsafe { libc::sigaction(data.signal.code(), std::ptr::null(), act.as_mut_ptr()) };
|
||||
let act = unsafe { act.assume_init() };
|
||||
if act.sa_sigaction == libc::SIG_IGN {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
unsafe { libc::sigaddset(&mut set, data.signal.code()) };
|
||||
unsafe { libc::sigaddset(set.as_mut_ptr(), data.signal.code()) };
|
||||
}
|
||||
return set;
|
||||
unsafe { set.assume_init() }
|
||||
});
|
||||
|
||||
/// Ensure we did not inherit any blocked signals. See issue #3964.
|
||||
pub fn signal_unblock_all() {
|
||||
unsafe {
|
||||
let mut iset: libc::sigset_t = std::mem::zeroed();
|
||||
libc::sigemptyset(&mut iset);
|
||||
libc::sigprocmask(libc::SIG_SETMASK, &iset, std::ptr::null_mut());
|
||||
let mut iset = MaybeUninit::uninit();
|
||||
libc::sigemptyset(iset.as_mut_ptr());
|
||||
libc::sigprocmask(libc::SIG_SETMASK, iset.as_ptr(), std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,12 +55,11 @@ pub(crate) enum TerminalCommand<'a> {
|
||||
EnterBoldMode,
|
||||
EnterDimMode,
|
||||
EnterItalicsMode,
|
||||
EnterUnderlineMode,
|
||||
EnterUnderlineMode(UnderlineStyle),
|
||||
EnterReverseMode,
|
||||
EnterStandoutMode,
|
||||
ExitItalicsMode,
|
||||
ExitUnderlineMode,
|
||||
EnterCurlyUnderlineMode,
|
||||
|
||||
// Screen clearing
|
||||
ClearScreen,
|
||||
@@ -149,12 +148,11 @@ fn write(out: &mut impl Output, sequence: &'static [u8]) -> bool {
|
||||
EnterBoldMode => ti(self, b"\x1b[1m", |t| &t.enter_bold_mode),
|
||||
EnterDimMode => ti(self, b"\x1b[2m", |t| &t.enter_dim_mode),
|
||||
EnterItalicsMode => ti(self, b"\x1b[3m", |t| &t.enter_italics_mode),
|
||||
EnterUnderlineMode => ti(self, b"\x1b[4m", |t| &t.enter_underline_mode),
|
||||
EnterUnderlineMode(style) => underline_mode(self, style),
|
||||
EnterReverseMode => ti(self, b"\x1b[7m", |t| &t.enter_reverse_mode),
|
||||
EnterStandoutMode => ti(self, b"\x1b[7m", |t| &t.enter_standout_mode),
|
||||
ExitItalicsMode => ti(self, b"\x1b[23m", |t| &t.exit_italics_mode),
|
||||
ExitUnderlineMode => ti(self, b"\x1b[24m", |t| &t.exit_underline_mode),
|
||||
EnterCurlyUnderlineMode => write(self, b"\x1b[4:3m"),
|
||||
ClearScreen => ti(self, b"\x1b[H\x1b[2J", |term| &term.clear_screen),
|
||||
ClearToEndOfLine => ti(self, b"\x1b[K", |term| &term.clr_eol),
|
||||
ClearToEndOfScreen => ti(self, b"\x1b[J", |term| &term.clr_eos),
|
||||
@@ -234,6 +232,19 @@ pub(crate) fn use_terminfo() -> bool {
|
||||
!future_feature_flags::test(FeatureFlag::ignore_terminfo) && TERM.lock().unwrap().is_some()
|
||||
}
|
||||
|
||||
fn underline_mode(out: &mut impl Output, style: UnderlineStyle) -> bool {
|
||||
use UnderlineStyle as UL;
|
||||
let style = match style {
|
||||
UL::Single => return maybe_terminfo(out, b"\x1b[4m", |t| &t.enter_underline_mode),
|
||||
UL::Double => 2,
|
||||
UL::Curly => 3,
|
||||
UL::Dotted => 4,
|
||||
UL::Dashed => 5,
|
||||
};
|
||||
write_to_output!(out, "\x1b[4:{}m", style);
|
||||
true
|
||||
}
|
||||
|
||||
fn palette_color(out: &mut impl Output, paintable: Paintable, mut idx: u8) -> bool {
|
||||
if only_grayscale() && !(Color::Named { idx }).is_grayscale() {
|
||||
return false;
|
||||
@@ -515,9 +526,9 @@ fn set_text_face_internal(&mut self, face: TextFace, salvage_unreadable: bool) {
|
||||
let style = face.style;
|
||||
|
||||
use TerminalCommand::{
|
||||
DefaultBackgroundColor, DefaultUnderlineColor, EnterBoldMode, EnterCurlyUnderlineMode,
|
||||
EnterDimMode, EnterItalicsMode, EnterReverseMode, EnterStandoutMode,
|
||||
EnterUnderlineMode, ExitAttributeMode, ExitItalicsMode, ExitUnderlineMode,
|
||||
DefaultBackgroundColor, DefaultUnderlineColor, EnterBoldMode, EnterDimMode,
|
||||
EnterItalicsMode, EnterReverseMode, EnterStandoutMode, EnterUnderlineMode,
|
||||
ExitAttributeMode, ExitItalicsMode, ExitUnderlineMode,
|
||||
};
|
||||
|
||||
// Removes all styles that are individually resettable.
|
||||
@@ -587,12 +598,9 @@ fn set_text_face_internal(&mut self, face: TextFace, salvage_unreadable: bool) {
|
||||
self.last.style.underline_style = None;
|
||||
}
|
||||
}
|
||||
Some(underline) => {
|
||||
if self.write_command(match underline {
|
||||
UnderlineStyle::Single => EnterUnderlineMode,
|
||||
UnderlineStyle::Curly => EnterCurlyUnderlineMode,
|
||||
}) {
|
||||
self.last.style.underline_style = Some(underline);
|
||||
Some(underline_style) => {
|
||||
if self.write_command(EnterUnderlineMode(underline_style)) {
|
||||
self.last.style.underline_style = Some(underline_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user