mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-28 17:51: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
|
- uses: dtolnay/rust-toolchain@1.70
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
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.
|
# Generate a locale that uses a comma as decimal separator.
|
||||||
sudo locale-gen fr_FR.UTF-8
|
sudo locale-gen fr_FR.UTF-8
|
||||||
- name: cmake
|
- name: cmake
|
||||||
@@ -32,6 +32,17 @@ jobs:
|
|||||||
- name: make fish_run_tests
|
- name: make fish_run_tests
|
||||||
run: |
|
run: |
|
||||||
make -C build VERBOSE=1 fish_run_tests
|
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:
|
ubuntu-32bit-static-pcre2:
|
||||||
|
|
||||||
@@ -82,13 +93,14 @@ jobs:
|
|||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
sudo apt install gettext libpcre2-dev python3-pexpect tmux
|
sudo apt install gettext libpcre2-dev python3-pexpect tmux
|
||||||
|
sudo apt install llvm # for llvm-symbolizer
|
||||||
- name: cmake
|
- name: cmake
|
||||||
env:
|
env:
|
||||||
CC: clang
|
CC: clang
|
||||||
run: |
|
run: |
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
# Rust's ASAN requires the build system to explicitly pass a --target triple. We read that
|
# 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
|
cmake .. -DASAN=1 -DRust_CARGO_TARGET=x86_64-unknown-linux-gnu -DCMAKE_BUILD_TYPE=Debug
|
||||||
- name: make
|
- name: make
|
||||||
run: |
|
run: |
|
||||||
@@ -104,8 +116,8 @@ jobs:
|
|||||||
# UPDATE: this can cause spurious leak reports for __cxa_thread_atexit_impl() under glibc.
|
# 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
|
LSAN_OPTIONS: verbosity=0:log_threads=0:use_tls=1:print_suppressions=0
|
||||||
run: |
|
run: |
|
||||||
llvm_version=$(clang --version | awk 'NR==1 { split($NF, version, "."); print version[1] }')
|
set -x
|
||||||
export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-$llvm_version
|
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"
|
export LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$PWD/build_tools/lsan_suppressions.txt"
|
||||||
make -C build VERBOSE=1 fish_run_tests
|
make -C build VERBOSE=1 fish_run_tests
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -38,7 +38,8 @@ Desktop.ini
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
|
|
||||||
messages.pot
|
po/template.po
|
||||||
|
*.mo
|
||||||
.directory
|
.directory
|
||||||
.fuse_hidden*
|
.fuse_hidden*
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
fish 4.1.0 (released ???)
|
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
|
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.
|
- 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
|
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,
|
- 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`).
|
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`).
|
- 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
|
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
|
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`).
|
- 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.
|
- 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
|
Fish uses the GNU gettext library to translate messages from English to
|
||||||
other languages.
|
other languages.
|
||||||
|
|
||||||
Creating and updating translations requires the Gettext tools, including
|
Translation sources are
|
||||||
``xgettext``, ``msgfmt`` and ``msgmerge``. Translation sources are
|
|
||||||
stored in the ``po`` directory, named ``LANG.po``, where ``LANG`` is the
|
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
|
two letter ISO 639-1 language code of the target language (e.g. ``de`` for
|
||||||
German).
|
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
|
Creating new translations requires the Gettext tools.
|
||||||
the source tree
|
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
|
||||||
* copy ``messages.pot`` to ``po/LANG.po``
|
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
|
to install ``cargo-expand`` (Note that other versions might not work correctly with our scripts).
|
||||||
``build_tools/fish_xgettext.fish`` from the source tree
|
To create a new translation, run::
|
||||||
|
|
||||||
* update the existing translation by running
|
build_tools/update_translations.fish po/LANG.po
|
||||||
``msgmerge --update --no-fuzzy-matching po/LANG.po messages.pot``
|
|
||||||
|
|
||||||
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
|
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::
|
Open up the po file, for example ``po/sv.po``, and you'll see something like::
|
||||||
|
|
||||||
msgid "%ls: No suitable job\n"
|
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::
|
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.
|
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
|
Be cautious about blindly updating an existing translation file.
|
||||||
changes to an existing message (eg changing the punctuation) will cause
|
``msgid`` strings should never be updated manually, only by running the appropriate script.
|
||||||
existing translations to be removed, since the tools do literal string
|
|
||||||
matching. Therefore, in general, you need to carefully review any
|
Modifications to strings in source files
|
||||||
recommended deletions.
|
----------------------------------------
|
||||||
|
|
||||||
|
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
|
Setting Code Up For Translations
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|||||||
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -89,9 +89,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.10"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
@@ -126,6 +126,8 @@ name = "fish-printf"
|
|||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
"widestring",
|
"widestring",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -173,9 +175,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -216,9 +218,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.29.0"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -546,6 +548,18 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
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]]
|
[[package]]
|
||||||
name = "unix_path"
|
name = "unix_path"
|
||||||
version = "1.0.1"
|
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
|
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
||||||
# files as of 22 April 2024.
|
# files as of 22 April 2024.
|
||||||
lru = "0.13.0"
|
lru = "0.13.0"
|
||||||
nix = { version = "0.29.0", default-features = false, features = [
|
nix = { version = "0.30.1", default-features = false, features = [
|
||||||
"event",
|
"event",
|
||||||
"inotify",
|
"inotify",
|
||||||
"resource",
|
"resource",
|
||||||
@@ -99,7 +99,7 @@ embed-data = ["dep:rust-embed"]
|
|||||||
asan = []
|
asan = []
|
||||||
tsan = []
|
tsan = []
|
||||||
|
|
||||||
[lints]
|
[workspace.lints]
|
||||||
rust.non_camel_case_types = "allow"
|
rust.non_camel_case_types = "allow"
|
||||||
rust.non_upper_case_globals = "allow"
|
rust.non_upper_case_globals = "allow"
|
||||||
rust.unknown_lints = "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.
|
# In the future, they might change to flag other methods of printing.
|
||||||
clippy.print_stdout = "deny"
|
clippy.print_stdout = "deny"
|
||||||
clippy.print_stderr = "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")))
|
Ok(cfg!(any(target_os = "ios", target_os = "macos")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unexpected_cfgs)]
|
|
||||||
fn detect_cygwin(_: &Target) -> Result<bool, Box<dyn Error>> {
|
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
|
/// 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
|
# 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 profile1 (cat $argv[1])
|
||||||
set -l profile2 (cat $argv[2])
|
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 line1 $profile1[$line_no]
|
||||||
set -l line2 $profile2[$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
|
echo $line1
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
set -l results1 (string match -r '^(\d+)\t(\d+)\s+(.*)' $line1)
|
set -l results1 (string match -r '^\s*(\d+)\s+(\d+)\s+(.*)' $line1)
|
||||||
set -l results2 (string match -r '^(\d+)\t(\d+)\s+(.*)' $line2)
|
set -l results2 (string match -r '^\s*(\d+)\s+(\d+)\s+(.*)' $line2)
|
||||||
|
|
||||||
# times from both files
|
# times from both files
|
||||||
set -l time1 $results1[2..3]
|
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[1] (math $time1[1] - $time2[1])
|
||||||
set diff[2] (math $time1[2] - $time2[2])
|
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
|
end
|
||||||
|
|||||||
@@ -1,68 +1,101 @@
|
|||||||
#!/usr/bin/env fish
|
#!/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
|
begin
|
||||||
# works around that - based on share/functions/funced.fish.
|
# Write header. This is required by msguniq.
|
||||||
set -q TMPDIR
|
# Note that this results in the file being overwritten.
|
||||||
or set -l TMPDIR /tmp
|
# This is desired behavior, to get rid of the results of prior invocations
|
||||||
set -l tmpdir (mktemp -d $TMPDIR/fish.XXXXXX)
|
# of this script.
|
||||||
or exit 1
|
begin
|
||||||
|
echo 'msgid ""'
|
||||||
|
echo 'msgstr ""'
|
||||||
|
echo '"Content-Type: text/plain; charset=UTF-8\n"'
|
||||||
|
echo ""
|
||||||
|
end
|
||||||
|
|
||||||
# This is a gigantic crime.
|
set -l cargo_expanded_file (mktemp)
|
||||||
# xgettext still does not support rust *at all*, so we use cargo-expand to get all our wgettext invocations.
|
# This is a gigantic crime.
|
||||||
set -l expanded (cargo expand --lib; for f in fish{,_indent,_key_reader}; cargo expand --bin $f; end)
|
# 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 rust_string_file (mktemp)
|
||||||
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)
|
|
||||||
|
|
||||||
# Extract any constants
|
# Extract any gettext call
|
||||||
set -a strs (string match -rv 'BUILD_VERSION:|PACKAGE_NAME' -- $expanded |
|
grep -A1 wgettext_static_str <$cargo_expanded_file |
|
||||||
string match -rg 'const [A-Z_]*: &str = "(.*)"' | string replace -a "\'" "'")
|
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.
|
# Extract any constants
|
||||||
# The escaping so far works out okay.
|
grep -Ev 'BUILD_VERSION:|PACKAGE_NAME' <$cargo_expanded_file |
|
||||||
for str in $strs
|
grep -E 'const [A-Z_]*: &str = "(.*)"' |
|
||||||
# grep -P needed for string escape to be compatible (PCRE-style),
|
sed -E -e 's/^.*const [A-Z_]*: &str = "(.*)".*$/\1/' -e "s_\\\'_'_g" >>$rust_string_file
|
||||||
# -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
|
|
||||||
|
|
||||||
# This regex handles descriptions for `complete` and `function` statements. These messages are not
|
rm $cargo_expanded_file
|
||||||
# particularly important to translate. Hence the "implicit" label.
|
|
||||||
set -l implicit_regex '(?:^| +)(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
|
|
||||||
|
|
||||||
# This regex handles explicit requests to translate a message. These are more important to translate
|
# Sort the extracted strings and remove duplicates.
|
||||||
# than messages which should be implicitly translated.
|
# Then, transform them into the po format.
|
||||||
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
|
# 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
|
rm $rust_string_file
|
||||||
mkdir -p $tmpdir/explicit/share/completions $tmpdir/explicit/share/functions
|
|
||||||
|
|
||||||
for f in share/config.fish share/completions/*.fish share/functions/*.fish
|
function extract_fish_script_messages --argument-names regex
|
||||||
# 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
|
|
||||||
|
|
||||||
# Handle `complete` / `function` description messages. The `| fish` is subtle. It basically
|
# Using xgettext causes more trouble than it helps.
|
||||||
# avoids the need to use `source` with a command substitution that could affect the current
|
# This is due to handling of escaping in fish differing from formats xgettext understands
|
||||||
# shell.
|
# (e.g. POSIX shell strings).
|
||||||
string replace --filter --regex $implicit_regex '$1' <$f | string unescape \
|
# We work around this issue by manually writing the file content.
|
||||||
| string replace --all '"' '\\"' | string replace -r '(.*)' 'N_ "$1"' >$tmpdir/implicit/$f
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
# This regex handles explicit requests to translate a message. These are more important to translate
|
||||||
sed -i 's_^#: /.*/share/_#: share/_' messages.pot
|
# 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,
|
gettext-base,
|
||||||
# for nroff and preconv
|
# for nroff and preconv
|
||||||
groff-base,
|
groff-base,
|
||||||
man-db,
|
|
||||||
# for terminal definitions
|
# for terminal definitions
|
||||||
ncurses-base,
|
ncurses-base,
|
||||||
# for kill
|
# 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
|
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:
|
.. _cmd-bind-termlimits:
|
||||||
|
|
||||||
Terminal Limitations
|
Terminal Limitations
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ The following options change what part of the commandline is printed or updated:
|
|||||||
**--search-field**
|
**--search-field**
|
||||||
Use the pager search field instead of the command line. Returns false if the search field is not shown.
|
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:
|
The following options change the way ``commandline`` prints the current commandline buffer:
|
||||||
|
|
||||||
**-c** or **--cut-at-cursor**
|
**-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.
|
Perform argument expansion on the selection and print one argument per line.
|
||||||
Command substitutions are not expanded but forwarded as-is.
|
Command substitutions are not expanded but forwarded as-is.
|
||||||
|
|
||||||
**--tokens-raw**
|
**-o**, **tokenize**, **--tokens-raw**
|
||||||
Print arguments in the selection as they appear on the command line, one per line.
|
|
||||||
|
|
||||||
**-o** or **tokenize**
|
|
||||||
Deprecated; do not use.
|
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.
|
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 ``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::
|
You can also define an empty ``fish_mode_prompt`` function to remove the vi mode indicators::
|
||||||
|
|
||||||
@@ -51,6 +51,9 @@ Example
|
|||||||
case replace_one
|
case replace_one
|
||||||
set_color --bold green
|
set_color --bold green
|
||||||
echo 'R'
|
echo 'R'
|
||||||
|
case replace
|
||||||
|
set_color --bold bryellow
|
||||||
|
echo 'R'
|
||||||
case visual
|
case visual
|
||||||
set_color --bold brmagenta
|
set_color --bold brmagenta
|
||||||
echo 'V'
|
echo 'V'
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ The following options are available:
|
|||||||
Sets reverse mode.
|
Sets reverse mode.
|
||||||
|
|
||||||
**-u** or **--underline**, or **-uSTYLE** or **--underline=STYLE**
|
**-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**
|
**-h** or **--help**
|
||||||
Displays help about using this command.
|
Displays help about using this command.
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ Synopsis
|
|||||||
|
|
||||||
.. synopsis::
|
.. synopsis::
|
||||||
|
|
||||||
string join [-q | --quiet] SEP [STRING ...]
|
string join [-q | --quiet] [-n | --no-empty] [--] SEP [STRING ...]
|
||||||
string join0 [-q | --quiet] [STRING ...]
|
string join0 [-q | --quiet] [-n | --no-empty] [--] [STRING ...]
|
||||||
|
|
||||||
.. END SYNOPSIS
|
.. END SYNOPSIS
|
||||||
|
|
||||||
@@ -18,11 +18,28 @@ Description
|
|||||||
|
|
||||||
.. BEGIN 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
|
.. END DESCRIPTION
|
||||||
|
|
||||||
@@ -43,4 +60,14 @@ Examples
|
|||||||
>_ string join '' a b c
|
>_ string join '' a b c
|
||||||
abc
|
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
|
.. END EXAMPLES
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Synopsis
|
|||||||
string collect [-a | --allow-empty] [-N | --no-trim-newlines] [STRING ...]
|
string collect [-a | --allow-empty] [-N | --no-trim-newlines] [STRING ...]
|
||||||
string escape [-n | --no-quoted] [--style=] [STRING ...]
|
string escape [-n | --no-quoted] [--style=] [STRING ...]
|
||||||
string join [-q | --quiet] [-n | --no-empty] SEP [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 length [-q | --quiet] [STRING ...]
|
||||||
string lower [-q | --quiet] [STRING ...]
|
string lower [-q | --quiet] [STRING ...]
|
||||||
string match [-a | --all] [-e | --entire] [-i | --ignore-case]
|
string match [-a | --all] [-e | --entire] [-i | --ignore-case]
|
||||||
@@ -27,7 +27,7 @@ Synopsis
|
|||||||
[-r | --regex] [-q | --quiet] PATTERN REPLACE [STRING ...]
|
[-r | --regex] [-q | --quiet] PATTERN REPLACE [STRING ...]
|
||||||
string shorten [(-c | --char) CHARS] [(-m | --max) INTEGER]
|
string shorten [(-c | --char) CHARS] [(-m | --max) INTEGER]
|
||||||
[-N | --no-newline] [-l | --left] [-q | --quiet] [STRING ...]
|
[-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 ...]
|
[-q | --quiet] [-r | --right] SEP [STRING ...]
|
||||||
string split0 [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
|
string split0 [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
|
||||||
[-q | --quiet] [-r | --right] [STRING ...]
|
[-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-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.
|
- :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.
|
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:
|
.. _cartesian-product:
|
||||||
|
|
||||||
Combining lists
|
Combining lists
|
||||||
|
|||||||
@@ -120,10 +120,22 @@ Optional Commands
|
|||||||
- smul
|
- smul
|
||||||
- Enter underline mode.
|
- Enter underline mode.
|
||||||
-
|
-
|
||||||
|
* - ``\e[4:2m``
|
||||||
|
- Su
|
||||||
|
- Enter double underline mode.
|
||||||
|
- kitty
|
||||||
* - ``\e[4:3m``
|
* - ``\e[4:3m``
|
||||||
- Su
|
- Su
|
||||||
- Enter curly underline mode.
|
- Enter curly underline mode.
|
||||||
- kitty
|
- kitty
|
||||||
|
* - ``\e[4:4m``
|
||||||
|
- Su
|
||||||
|
- Enter dotted underline mode.
|
||||||
|
- kitty
|
||||||
|
* - ``\e[4:5m``
|
||||||
|
- Su
|
||||||
|
- Enter dashed underline mode.
|
||||||
|
- kitty
|
||||||
* - ``\e[7m``
|
* - ``\e[7m``
|
||||||
- rev
|
- rev
|
||||||
- Enter reverse video mode (swap foreground and background colors).
|
- 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::
|
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::
|
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.)
|
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
|
function fish_prompt
|
||||||
set_color purple
|
set_color purple
|
||||||
date "+%m/%d/%y"
|
date "+%m/%d/%y"
|
||||||
set_color F00
|
set_color FF0000
|
||||||
echo (pwd) '>' (set_color normal)
|
echo (pwd) '>' (set_color normal)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,12 @@ BuildRequires: glibc-langpack-en
|
|||||||
BuildRequires: python3 procps
|
BuildRequires: python3 procps
|
||||||
|
|
||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
Requires: terminfo-base
|
Requires: terminfo-base groff
|
||||||
%else
|
%else
|
||||||
Requires: ncurses-base
|
Requires: ncurses-base groff-base
|
||||||
%endif
|
%endif
|
||||||
Requires: file
|
Requires: file
|
||||||
Requires: python3
|
Requires: python3
|
||||||
Requires: man
|
|
||||||
Requires: procps
|
Requires: procps
|
||||||
|
|
||||||
# Although the build scripts mangle the version number to be RPM compatible
|
# 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]
|
[package]
|
||||||
name = "fish-printf"
|
name = "fish-printf"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
repository = "https://github.com/fish-shell/fish-shell"
|
repository = "https://github.com/fish-shell/fish-shell"
|
||||||
description = "printf implementation, based on musl"
|
description = "printf implementation, based on musl"
|
||||||
@@ -9,3 +10,8 @@ license = "MIT"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.155"
|
libc = "0.2.155"
|
||||||
widestring = { version = "1.0.2", optional = true }
|
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.
|
/// - `args`: Iterator over the arguments to format.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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
|
/// # Example
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
#[cfg(feature = "widestring")]
|
#[cfg(feature = "widestring")]
|
||||||
use widestring::Utf32Str as wstr;
|
use widestring::Utf32Str as wstr;
|
||||||
@@ -382,7 +384,7 @@ pub fn sprintf_locale(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read field width. We do not support $.
|
// 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()?;
|
let arg_width = args.next().ok_or(Error::MissingArg)?.as_sint()?;
|
||||||
s.advance_by(1);
|
s.advance_by(1);
|
||||||
if arg_width < 0 {
|
if arg_width < 0 {
|
||||||
@@ -397,7 +399,7 @@ pub fn sprintf_locale(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Optionally read precision. We do not support $.
|
// 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."
|
// "A negative precision is treated as though it were missing."
|
||||||
// Here we assume the precision is always signed.
|
// Here we assume the precision is always signed.
|
||||||
s.advance_by(2);
|
s.advance_by(2);
|
||||||
@@ -410,7 +412,7 @@ pub fn sprintf_locale(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
// Disallow precisions larger than i32::MAX, in keeping with C.
|
// 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);
|
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),
|
// "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.
|
// 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);
|
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;
|
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 => {
|
CS::e | CS::f | CS::g | CS::a | CS::E | CS::F | CS::G | CS::A => {
|
||||||
// Floating point types handle output on their own.
|
// Floating point types handle output on their own.
|
||||||
let float = arg.as_float()?;
|
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)?;
|
out_len = out_len.checked_add(len).ok_or(Error::Overflow)?;
|
||||||
continue 'main;
|
continue 'main;
|
||||||
}
|
}
|
||||||
CS::p => {
|
CS::p => {
|
||||||
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
|
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()?;
|
let uint = arg.as_uint()?;
|
||||||
if uint != 0 {
|
if uint != 0 {
|
||||||
prefix = "0x";
|
prefix = "0x";
|
||||||
@@ -479,8 +490,8 @@ pub fn sprintf_locale(
|
|||||||
if uint != 0 {
|
if uint != 0 {
|
||||||
write!(buf, "{:o}", uint)?;
|
write!(buf, "{:o}", uint)?;
|
||||||
}
|
}
|
||||||
if flags.alt_form && prec.unwrap_or(0) <= buf.len() + 1 {
|
if flags.alt_form && desired_precision.unwrap_or(0) <= buf.len() + 1 {
|
||||||
prec = Some(buf.len() + 1);
|
desired_precision = Some(buf.len() + 1);
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
@@ -514,10 +525,38 @@ pub fn sprintf_locale(
|
|||||||
CS::s => {
|
CS::s => {
|
||||||
// also 'S'
|
// also 'S'
|
||||||
let s = arg.as_str(buf)?;
|
let s = arg.as_str(buf)?;
|
||||||
let p = prec.unwrap_or(s.len()).min(s.len());
|
|
||||||
prec = Some(p);
|
|
||||||
flags.zero_pad = false;
|
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.
|
// 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.
|
// 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.
|
// 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 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()),
|
true => body.len() + locale.separator_count(body.len()),
|
||||||
false => body.len(),
|
false => body.width(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the precision.
|
// Resolve the precision.
|
||||||
// In the case of a non-numeric conversion, update the precision to at least the
|
// In the case of a non-numeric conversion, update the precision to at least the
|
||||||
// length of the string.
|
// length of the string.
|
||||||
let prec = if !spec_is_numeric {
|
let desired_precision = if !spec_is_numeric {
|
||||||
prec.unwrap_or(body_len)
|
desired_precision.unwrap_or(body_width)
|
||||||
} else {
|
} else {
|
||||||
prec.unwrap_or(1).max(body_len)
|
desired_precision.unwrap_or(1).max(body_width)
|
||||||
};
|
};
|
||||||
|
|
||||||
let prefix_len = prefix.len();
|
let prefix_width = prefix.width();
|
||||||
let unpadded_width = prefix_len.checked_add(prec).ok_or(Error::Overflow)?;
|
let unpadded_width = prefix_width
|
||||||
let width = width.max(unpadded_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?
|
// Pad on the left with spaces to the desired width?
|
||||||
if !flags.left_adj && !flags.zero_pad {
|
if !flags.left_adj && !flags.zero_pad {
|
||||||
@@ -560,7 +602,8 @@ pub fn sprintf_locale(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pad on the left to the given precision?
|
// 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.
|
// Output the actual value, perhaps with grouping.
|
||||||
if wants_grouping {
|
if wants_grouping {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ macro_rules! sprintf_check {
|
|||||||
$(,)? // optional trailing comma
|
$(,)? // optional trailing comma
|
||||||
) => {
|
) => {
|
||||||
{
|
{
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
let mut target = String::new();
|
let mut target = String::new();
|
||||||
let mut args = [$($arg.to_arg()),*];
|
let mut args = [$($arg.to_arg()),*];
|
||||||
let len = $crate::printf_c_locale(
|
let len = $crate::printf_c_locale(
|
||||||
@@ -20,7 +21,7 @@ macro_rules! sprintf_check {
|
|||||||
$fmt.as_ref() as &str,
|
$fmt.as_ref() as &str,
|
||||||
&mut args,
|
&mut args,
|
||||||
).expect("printf failed");
|
).expect("printf failed");
|
||||||
assert!(len == target.len(), "Wrong length returned: {} vs {}", len, target.len());
|
assert_eq!(len, target.width(), "Wrong length returned");
|
||||||
target
|
target
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -735,6 +736,18 @@ fn test_huge_precision_g() {
|
|||||||
sprintf_err!("%.2147483648g", f => Error::Overflow);
|
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]
|
#[test]
|
||||||
fn test_errors() {
|
fn test_errors() {
|
||||||
use Error::*;
|
use Error::*;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
function __fish_apt_no_subcommand -d 'Test if apt has yet to be given the subcommand'
|
function __fish_apt_no_subcommand -d 'Test if apt has yet to be given the subcommand'
|
||||||
for i in (commandline -xpc)
|
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
|
return 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -11,7 +11,7 @@ end
|
|||||||
|
|
||||||
function __fish_apt_use_package -d 'Test if apt command should have packages as potential completion'
|
function __fish_apt_use_package -d 'Test if apt command should have packages as potential completion'
|
||||||
for i in (commandline -xpc)
|
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
|
return 0
|
||||||
end
|
end
|
||||||
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 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 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 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-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 -l no-install-suggests -d 'Do not install suggested packages'
|
||||||
complete -c apt-get -s d -l download-only -d 'Download Only'
|
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
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
set -l all_subcmds update upgrade full-upgrade search list install show remove edit-sources purge changelog autoremove depends rdepends
|
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
|
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 installed_pkg_subcmds remove
|
||||||
set -l handle_file_pkg_subcmds install
|
set -l handle_file_pkg_subcmds install
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ __fish_apt_subcommand changelog -r -d 'Download and display package changelog'
|
|||||||
# Autoremove
|
# Autoremove
|
||||||
__fish_apt_subcommand autoremove -d 'Remove packages no longer needed as dependencies'
|
__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'
|
__fish_apt_subcommand autopurge -d 'Remove packages no longer needed as dependencies and delete their config files'
|
||||||
|
|
||||||
# Clean
|
# Clean
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ end
|
|||||||
|
|
||||||
function __fish_apt_use_package -d 'Test if aptitude command should have packages as potential completion'
|
function __fish_apt_use_package -d 'Test if aptitude command should have packages as potential completion'
|
||||||
for i in (commandline -xpc)
|
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
|
return 0
|
||||||
end
|
end
|
||||||
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 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 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 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 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 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 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 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 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 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 d -l download-only -d 'Download only'
|
||||||
complete -c aptitude -s f -l fix-broken -d 'Correct broken dependencies'
|
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 -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 P -l prompt -d 'Always display a prompt'
|
||||||
complete -c aptitude -s R -l without-recommends -d 'Do not treat recommendations as dependencies'
|
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 -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 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 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 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 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'
|
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).
|
# Tab completion for cargo (https://github.com/rust-lang/cargo).
|
||||||
|
|
||||||
## --- WRITTEN MANUALLY ---
|
## --- 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 -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"
|
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
|
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 -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
|
end
|
||||||
|
|
||||||
for x in bench b build c check r run rustc t test
|
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 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 "(cargo run --example 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
|
end
|
||||||
|
|
||||||
# If using rustup, get the list of installed targets from there. Otherwise print all targets.
|
# If using rustup, get the list of installed targets from there. Otherwise print all targets.
|
||||||
@@ -38,18 +43,18 @@ end
|
|||||||
|
|
||||||
function __fish_cargo_features
|
function __fish_cargo_features
|
||||||
if command -q jq
|
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)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_cargo_packages
|
function __fish_cargo_packages
|
||||||
if command -q jq
|
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)
|
else if set -l python (__fish_anypython)
|
||||||
cargo metadata --no-deps --format-version 1 |
|
__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')"
|
command $python -Sc "import sys, json"\n"print(*[x['name'] for x in json.load(sys.stdin)['packages']], sep='\n')"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
complete -c cargo -n '__fish_seen_subcommand_from run test build debug check clippy' -s p -l package \
|
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 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 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 -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 -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 -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)'
|
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 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 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 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" -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 bin -d 'Only uninstall the binary NAME'
|
||||||
complete -c cargo -n "__fish_seen_subcommand_from uninstall" -l root -d 'Directory to uninstall packages from'
|
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
|
# 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.
|
# for --foo will always be fast.
|
||||||
if command -q timeout
|
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
|
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
|
||||||
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 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 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 -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 I -l input -d "Specify command to operate on"
|
||||||
complete -c commandline -s C -l cursor -d "Set/get cursor position, not buffer contents"
|
complete -c commandline -s C -l cursor -d "Set/get cursor position, not buffer contents"
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ function __fish_git
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
# Using 'command git' to avoid interactions for aliases from git to (e.g.) hub
|
# 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
|
end
|
||||||
|
|
||||||
# Print an optspec for argparse to handle git's options that are independent of any subcommand.
|
# 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
|
function __fish_git_branches
|
||||||
# This is much faster than using `git branch` and avoids dealing with localized "detached HEAD" messages.
|
# 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.
|
# 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_local_branches
|
||||||
__fish_git for-each-ref --format='%(refname:strip=2)%09Remote Branch' refs/remotes/ 2>/dev/null
|
__fish_git_remote_branches
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_git_submodules
|
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
|
__fish_git for-each-ref --format='%(refname:strip=2)%09Local Branch' --sort=-committerdate refs/heads/ 2>/dev/null
|
||||||
end
|
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
|
function __fish_git_unique_remote_branches
|
||||||
# `git checkout` accepts remote branches without the remote part
|
# `git checkout` accepts remote branches without the remote part
|
||||||
# if they are unambiguous.
|
# if they are unambiguous.
|
||||||
# E.g. if only alice has a "frobulate" branch
|
# E.g. if only alice has a "frobulate" branch
|
||||||
# `git checkout frobulate` is equivalent to `git checkout -b frobulate --track alice/frobulate`.
|
# `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" \
|
--sort="refname:strip=3" \
|
||||||
"refs/remotes/*/$match*" "refs/remotes/*/*/**" 2>/dev/null | uniq -u
|
refs/remotes/ 2>/dev/null | uniq -u
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_git_tags
|
function __fish_git_tags
|
||||||
@@ -82,7 +87,8 @@ end
|
|||||||
function __fish_git_heads
|
function __fish_git_heads
|
||||||
set -l gitdir (__fish_git rev-parse --git-dir 2>/dev/null)
|
set -l gitdir (__fish_git rev-parse --git-dir 2>/dev/null)
|
||||||
or return # No git dir, no need to even test.
|
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
|
if test -f $gitdir/$head
|
||||||
echo $head
|
echo $head
|
||||||
end
|
end
|
||||||
@@ -96,7 +102,35 @@ function __fish_git_refs
|
|||||||
end
|
end
|
||||||
|
|
||||||
function __fish_git_remotes
|
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
|
end
|
||||||
|
|
||||||
function __fish_git_files
|
function __fish_git_files
|
||||||
@@ -647,6 +681,7 @@ function __fish_git_aliased_command
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
set -g __fish_git_aliases
|
||||||
git config -z --get-regexp 'alias\..*' | while read -lz alias cmdline
|
git config -z --get-regexp 'alias\..*' | while read -lz alias cmdline
|
||||||
set -l command (__fish_git_aliased_command $cmdline)
|
set -l command (__fish_git_aliased_command $cmdline)
|
||||||
string match -q --regex '\w+' -- $command; or continue
|
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
|
# Suggest branches for the specified remote - returns 1 if no known remote is specified
|
||||||
function __fish_git_branch_for_remote
|
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 remote
|
||||||
set -l cmd (commandline -xpc)
|
set -l cmd (commandline -xpc)
|
||||||
for r in $remotes
|
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 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 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 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 negotiate-only -d "Don't fetch, only show commits in common with the server"
|
||||||
complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
|
complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server'
|
||||||
|
|
||||||
@@ -1087,10 +1122,8 @@ complete -f -c git -n "__fish_git_using_command remote" -n "__fish_seen_subcomma
|
|||||||
|
|
||||||
### show
|
### 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_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)'
|
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_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' -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)'
|
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
|
### checkout
|
||||||
complete -F -c git -n '__fish_git_using_command checkout' -n 'contains -- -- (commandline -xpc)'
|
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'
|
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).
|
# 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)'
|
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
|
### describe
|
||||||
complete -c git -n __fish_git_needs_command -a describe -d 'Give an object a human readable name'
|
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
|
__fish_git_add_revision_completion -n '__fish_git_using_command describe'
|
||||||
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
|
|
||||||
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 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 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'
|
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_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_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_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 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 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'
|
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
|
### merge
|
||||||
complete -f -c git -n __fish_git_needs_command -a merge -d 'Join multiple development histories'
|
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 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' -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'
|
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
|
### 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_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' -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 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'
|
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
|
### push
|
||||||
complete -f -c git -n __fish_git_needs_command -a push -d 'Push changes elsewhere'
|
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 '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
|
begin
|
||||||
complete -f -c git -n '__fish_git_using_command push' -n __fish_git_branch_for_remote -ka '(__fish_git_branches)'
|
# TODO
|
||||||
complete -f -c git -n '__fish_git_using_command push' -n __fish_git_branch_for_remote -ka '(__fish_git_heads)'
|
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
|
# 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'
|
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
|
# 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
|
### rebase
|
||||||
complete -f -c git -n __fish_git_needs_command -a rebase -d 'Reapply commit sequence on a new base'
|
complete -f -c git -n __fish_git_needs_command -a rebase -d 'Reapply commit sequence on a new base'
|
||||||
complete -f -c git -n '__fish_git_using_command rebase' -a '(__fish_git_remotes)' -d 'Remote alias'
|
__fish_git_add_revision_completion -n '__fish_git_using_command rebase'
|
||||||
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
|
|
||||||
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 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 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'
|
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 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-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 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 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'
|
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.
|
# 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 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 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 -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,
|
# reset can either undo changes to versioned modified files,
|
||||||
# or remove files from the staging area.
|
# or remove files from the staging area.
|
||||||
# Deleted files seem to need a "--" separator.
|
# 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' -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 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 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' -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 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'
|
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
|
### 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_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)'
|
__fish_git_add_revision_completion -n '__fish_git_using_command rev-parse'
|
||||||
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
|
|
||||||
complete -c git -n '__fish_git_using_command rev-parse' -l abbrev-ref -d 'Output non-ambiguous short object names'
|
complete -c git -n '__fish_git_using_command rev-parse' -l abbrev-ref -d 'Output non-ambiguous short object names'
|
||||||
|
|
||||||
### revert
|
### revert
|
||||||
@@ -2115,7 +2147,7 @@ complete -f -c git -n '__fish_git_using_command stripspace' -s c -l comment-line
|
|||||||
|
|
||||||
### tag
|
### 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_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 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 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'
|
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 -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'
|
||||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_branches)'
|
begin
|
||||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_heads)' -d Head
|
set -lx __fish_git_unqualified_unique_remote_branches true
|
||||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_tags)' -d Tag
|
__fish_git_add_revision_completion -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_unique_remote_branches)' -d 'Unique Remote Branch'
|
end
|
||||||
complete -c git -n '__fish_git_using_command worktree' -n '__fish_seen_subcommand_from add' -ka '(__fish_git_local_branches)'
|
|
||||||
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'
|
||||||
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 -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'
|
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 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_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 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 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 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' -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'
|
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
|
### format-patch
|
||||||
complete -f -c git -n __fish_git_needs_command -a format-patch -d 'Generate patch series to send upstream'
|
complete -f -c git -n __fish_git_needs_command -a format-patch -d 'Generate patch series to send upstream'
|
||||||
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 -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 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"
|
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)"
|
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
|
set -a __fish_git_custom_commands_completion $file
|
||||||
end
|
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
|
case address
|
||||||
# We're still _on_ the second word, which is the subcommand
|
# We're still _on_ the second word, which is the subcommand
|
||||||
if not set -q cmd[3]
|
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" \
|
delete "Delete protocol address" \
|
||||||
show "Look at protocol addresses" \
|
show "Look at protocol addresses" \
|
||||||
flush "Flush protocol addresses"
|
flush "Flush protocol addresses" \
|
||||||
|
help "Display help"
|
||||||
else
|
else
|
||||||
switch $cmd[2]
|
switch $cmd[2]
|
||||||
# Change and replace are undocumented (apart from mentions in the BNF)
|
# Change and replace are undocumented (apart from mentions in the BNF)
|
||||||
@@ -339,7 +341,8 @@ function __fish_complete_ip
|
|||||||
end
|
end
|
||||||
case link
|
case link
|
||||||
if not set -q cmd[3]
|
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" \
|
delete "Delete virtual link" \
|
||||||
set "Change device attributes" \
|
set "Change device attributes" \
|
||||||
show "Display device attributes" \
|
show "Display device attributes" \
|
||||||
@@ -430,18 +433,20 @@ function __fish_complete_ip
|
|||||||
end
|
end
|
||||||
case show
|
case show
|
||||||
case help
|
case help
|
||||||
|
__fish_ip_types
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
case neighbour
|
case neighbour
|
||||||
if not set -q cmd[3]
|
if not set -q cmd[3]
|
||||||
printf '%s\t%s\n' help "Show help" \
|
printf '%s\t%s\n' \
|
||||||
add "Add new neighbour entry" \
|
add "Add new neighbour entry" \
|
||||||
delete "Delete neighbour entry" \
|
delete "Delete neighbour entry" \
|
||||||
change "Change neighbour entry" \
|
change "Change neighbour entry" \
|
||||||
replace "Add or change neighbour entry" \
|
replace "Add or change neighbour entry" \
|
||||||
show "List neighbour entries" \
|
show "List neighbour entries" \
|
||||||
flush "Flush neighbour entries" \
|
flush "Flush neighbour entries" \
|
||||||
get "Lookup neighbour entry"
|
get "Lookup neighbour entry" \
|
||||||
|
help "Display help"
|
||||||
else
|
else
|
||||||
switch $cmd[2]
|
switch $cmd[2]
|
||||||
case add del delete change replace
|
case add del delete change replace
|
||||||
@@ -505,7 +510,8 @@ function __fish_complete_ip
|
|||||||
get "Get a single route" \
|
get "Get a single route" \
|
||||||
save "Save routing table to stdout" \
|
save "Save routing table to stdout" \
|
||||||
showdump "Show saved routing table from stdin" \
|
showdump "Show saved routing table from stdin" \
|
||||||
restore "Restore routing table from stdin"
|
restore "Restore routing table from stdin" \
|
||||||
|
help "Display help"
|
||||||
else
|
else
|
||||||
# TODO: switch on $cmd[2] and complete subcommand specific arguments
|
# TODO: switch on $cmd[2] and complete subcommand specific arguments
|
||||||
# for now just complete most useful arguments for the last token
|
# for now just complete most useful arguments for the last token
|
||||||
@@ -524,7 +530,8 @@ function __fish_complete_ip
|
|||||||
flush "Flush rules" \
|
flush "Flush rules" \
|
||||||
show "List rules" \
|
show "List rules" \
|
||||||
save "Save rules to stdout" \
|
save "Save rules to stdout" \
|
||||||
restore "Restore rules from stdin"
|
restore "Restore rules from stdin" \
|
||||||
|
help "Display help"
|
||||||
else
|
else
|
||||||
# TODO: switch on $cmd[2] and complete subcommand specific arguments
|
# TODO: switch on $cmd[2] and complete subcommand specific arguments
|
||||||
# for now just complete most useful arguments for the last token
|
# for now just complete most useful arguments for the last token
|
||||||
@@ -579,6 +586,27 @@ function __fish_complete_ip
|
|||||||
end
|
end
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#Keybase 5.6.1
|
# Keybase 6.4.0
|
||||||
|
|
||||||
function __fish_keybase_line_ends_with
|
function __fish_keybase_line_ends_with
|
||||||
set -l line (commandline -pxc | string match -v -r '^-')
|
set -l line (commandline -pxc | string match -v -r '^-')
|
||||||
@@ -9,123 +9,132 @@ function __fish_keybase_line_ends_with
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#variables
|
# variables
|
||||||
set -l ends __fish_keybase_line_ends_with
|
set -l ends __fish_keybase_line_ends_with
|
||||||
set -l seen __fish_seen_subcommand_from
|
# 1
|
||||||
#L1
|
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
|
||||||
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
|
# 2
|
||||||
#L2
|
set -l keybase_account contact-settings delete email h help lockdown phonenumber recover-username reset-cancel upload-avatar
|
||||||
set -l keybase_account contact-settings delete email h help lockdown recover-username upload-avatar
|
set -l keybase_audit box h help
|
||||||
set -l keybase_blocks h help list-users list-teams
|
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_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_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_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_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_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 create delete gc h help lfs-config list settings
|
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_help advanced gpg keyring tor
|
||||||
set -l keybase_kvstore api h help
|
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_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_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_rekey h help paper status
|
||||||
set -l keybase_sigs h help list revoke
|
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_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
|
set -l keybase_wallet accounts balances detail details export h help list
|
||||||
#L3
|
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_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_debug deobfuscate dump h help obfuscate
|
||||||
set -l keybase_fs_sync disable enable h help show
|
set -l keybase_fs_sync disable enable h help show
|
||||||
#...
|
|
||||||
|
|
||||||
#global options
|
# global options
|
||||||
complete -c keybase -f -n "$ends keybase" -l api-dump-unsafe
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
complete -c keybase -f -n "$ends keybase" -l generate-bash-completion -d ""
|
||||||
complete -c keybase -f -n "$ends keybase" -l gpg
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
complete -c keybase -f -n "$ends keybase" -l remember-passphrase -d "Remember keybase passphrase"
|
||||||
complete -c keybase -f -n "$ends keybase" -l run-mode
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
complete -c keybase -f -n "$ends keybase" -l version -s v -d "Print the version"
|
||||||
|
|
||||||
#commands
|
# commands
|
||||||
#L1
|
# 1
|
||||||
complete -c keybase -f -n "not $ends keybase"
|
complete -c keybase -f -n "not $ends keybase"
|
||||||
complete -c keybase -f -n "$ends keybase" -a "$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 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 blocks" -a "$keybase_blocks"
|
||||||
complete -c keybase -f -n "$ends keybase bot" -a "$keybase_bot"
|
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 chat" -a "$keybase_chat"
|
||||||
complete -c keybase -f -n "$ends keybase config" -a "$keybase_config"
|
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 ctl" -a "$keybase_ctl"
|
||||||
complete -c keybase -f -n "$ends keybase currency" -a "$keybase_currency"
|
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 device" -a "$keybase_device"
|
||||||
complete -c keybase -f -n "$ends keybase fs" -a "$keybase_fs"
|
complete -c keybase -f -n "$ends keybase fs" -a "$keybase_fs"
|
||||||
complete -c keybase -f -n "$ends keybase git" -a "$keybase_git"
|
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 log" -a "$keybase_log"
|
||||||
complete -c keybase -f -n "$ends keybase passphrase" -a "$keybase_passphrase"
|
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 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 rekey" -a "$keybase_rekey"
|
||||||
complete -c keybase -f -n "$ends keybase sigs" -a "$keybase_sigs"
|
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 team" -a "$keybase_team"
|
||||||
complete -c keybase -f -n "$ends keybase update" -a "$keybase_update"
|
complete -c keybase -f -n "$ends keybase update" -a "$keybase_update"
|
||||||
complete -c keybase -f -n "$ends keybase wallet" -a "$keybase_wallet"
|
complete -c keybase -f -n "$ends keybase wallet" -a "$keybase_wallet"
|
||||||
#...
|
complete -c keybase -f -n "$ends keybase wot" -a "$keybase_wot"
|
||||||
#L3
|
# 3
|
||||||
complete -c keybase -f -n "$ends keybase account email" -a "$keybase_account_email"
|
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 debug" -a "$keybase_fs_debug"
|
||||||
complete -c keybase -f -n "$ends keybase fs sync" -a "$keybase_fs_sync"
|
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 skip-chat-message -s s
|
||||||
complete -c keybase -f -n "$ends keybase account upload-avatar" -l team
|
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 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 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 -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
|
# 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
|
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 i -l italics -d Italicise
|
||||||
complete -c set_color -s d -l dim -d 'Dim text'
|
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 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 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'
|
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)
|
# completion for tmutil (macOS)
|
||||||
|
|
||||||
complete -f -c tmutil -n __fish_use_subcommand -a addexclusion -d 'Add an exclusion not to back up a file'
|
function __fish_tmutil_destination_ids
|
||||||
complete -f -c tmutil -n '__fish_seen_subcommand_from addexclusion' -s v -d 'Volume exclusion'
|
for line in $(tmutil destinationinfo)
|
||||||
complete -f -c tmutil -n '__fish_seen_subcommand_from addexclusion' -s p -d 'Path exclusion'
|
if string match -q '*===*' -- $line
|
||||||
complete -r -c tmutil -n __fish_use_subcommand -a associatedisk -d 'Bind a snapshot volume directory to the specified local disk'
|
# New section so clear out variables
|
||||||
complete -r -c tmutil -n __fish_use_subcommand -a calculatedrift -d 'Determine the amount of change between snapshots'
|
set -f name ''
|
||||||
complete -r -c tmutil -n __fish_use_subcommand -a compare -d 'Perform a backup diff'
|
set -f kind ''
|
||||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s a -d 'Compare all supported metadata'
|
continue
|
||||||
complete -f -c tmutil -n '__fish_seen_subcommand_from compare' -s n -d 'No metadata comparison'
|
end
|
||||||
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'
|
|
||||||
|
|
||||||
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
|
# Returns 0 if the command has not had a subcommand yet
|
||||||
# Does not currently account for -chdir
|
# Does not currently account for -chdir
|
||||||
function __fish_terraform_needs_command
|
function __fish_tofu_needs_command
|
||||||
set -l cmd (commandline -xpc)
|
set -l cmd (commandline -xpc)
|
||||||
|
|
||||||
if test (count $cmd) -eq 1
|
if test (count $cmd) -eq 1
|
||||||
@@ -10,19 +11,19 @@ function __fish_terraform_needs_command
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_terraform_workspaces
|
function __fish_tofu_workspaces
|
||||||
tofu workspace list | string replace -r "^[\s\*]*" ""
|
tofu workspace list | string replace -r "^[\s\*]*" ""
|
||||||
end
|
end
|
||||||
|
|
||||||
# general options
|
# 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"
|
complete -f -c tofu -o help -d "Show help"
|
||||||
|
|
||||||
### apply/destroy
|
### apply/destroy
|
||||||
set -l apply 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_tofu_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 destroy -d "Destroy infrastructure"
|
||||||
complete -f -c tofu -n "__fish_seen_subcommand_from $apply" -o auto-approve -d "Skip interactive approval"
|
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 -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"
|
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 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 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 -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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from $apply" -o state-out -d "Path to write state"
|
||||||
|
|
||||||
### console
|
### console
|
||||||
complete -f -c tofu -n __fish_terraform_needs_command -a console -d "Interactive console for Terraform interpolations"
|
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 Terraform state file"
|
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 Terraform configuration"
|
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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from console" -o var-file -d "Set variables from a file"
|
||||||
|
|
||||||
### fmt
|
### 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 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 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"
|
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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from fmt" -o recursive -d "Also process files in subdirectories"
|
||||||
|
|
||||||
### get
|
### 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 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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from get" -o no-color -d "If specified, output won't contain any color"
|
||||||
|
|
||||||
### graph
|
### 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 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 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 -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-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=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 type=apply -d "Output apply graph"
|
||||||
|
complete -f -c tofu -n "__fish_seen_subcommand_from graph" -o consice -d "Shorten the plan output"
|
||||||
|
|
||||||
### import
|
### 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 -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 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"
|
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=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 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 -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 -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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from import" -o var-file -d "Set variables from a file"
|
||||||
|
|
||||||
### init
|
### 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 backend=false -d "Disable backend initialization"
|
||||||
complete -f -c tofu -n "__fish_seen_subcommand_from init" -o cloud=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"
|
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 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 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 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
|
### 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)"
|
complete -f -c tofu -n "__fish_seen_subcommand_from login" -a "(__fish_print_hostnames)"
|
||||||
|
|
||||||
### logout
|
### 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)"
|
complete -f -c tofu -n "__fish_seen_subcommand_from logout" -a "(__fish_print_hostnames)"
|
||||||
|
|
||||||
### output
|
### 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 -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 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 json -d "Print output in JSON format"
|
||||||
complete -f -c tofu -n "__fish_seen_subcommand_from output" -o raw -d "Print raw strings directly"
|
complete -f -c tofu -n "__fish_seen_subcommand_from output" -o raw -d "Print raw strings directly"
|
||||||
|
|
||||||
### plan
|
### 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 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 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"
|
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 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 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 -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
|
### plan customization options are reusable across apply, destroy, and plan
|
||||||
set -l plan apply destroy 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 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 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 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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from $plan" -o var-file -d "Set variables from a file"
|
||||||
|
|
||||||
### providers
|
### 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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from providers" -a "lock mirror schema"
|
||||||
|
|
||||||
### refresh
|
### 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 -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 -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 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=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 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 -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 -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 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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from refresh" -o var-file -d "Set variables from a file"
|
||||||
|
|
||||||
### show
|
### 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 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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from validate" -o json -d "Produce output in JSON format"
|
||||||
|
|
||||||
### state
|
### 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 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 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 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 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 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 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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from state" -a show -d "Show a resource in the state"
|
||||||
|
|
||||||
### taint
|
### 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 -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 -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=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 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 -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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from taint" -o state-out -d "Path to write state"
|
||||||
|
|
||||||
### test
|
### 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 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 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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from test" -o no-color -d "If specified, output won't contain any color"
|
||||||
|
|
||||||
### untaint
|
### 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 -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 -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=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 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 -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"
|
complete -r -c tofu -n "__fish_seen_subcommand_from untaint" -o state-out -d "Path to write state"
|
||||||
|
|
||||||
### validate
|
### 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 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"
|
complete -f -c tofu -n "__fish_seen_subcommand_from validate" -o no-color -d "If specified, output won't contain any color"
|
||||||
|
|
||||||
### version
|
### 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
|
### workspace
|
||||||
set -l workspace_commands list select new delete
|
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 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 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 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 && 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
|
function __fish_winetricks__complete_verbs
|
||||||
winetricks list-all |
|
winetricks list-all 2>/dev/null |
|
||||||
string match --invert --regex '^==' |
|
string match --invert --regex '^==' |
|
||||||
string match --invert --regex '^(apps|dlls|fonts|games|settings)$' |
|
string match --invert --regex '^(apps|dlls|fonts|games|settings)$' |
|
||||||
string replace --regex '(\S+)\s+(.+)' '$1\t$2'
|
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
|
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
|
function __wpctl_get_nodes -a section -a type
|
||||||
set -l havesection
|
set -l havesection
|
||||||
set -l havetype
|
set -l havetype
|
||||||
@@ -38,6 +42,19 @@ function __wpctl_command_shape
|
|||||||
end
|
end
|
||||||
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 -f
|
||||||
|
|
||||||
complete -c wpctl -s h -l help -d "Show help options"
|
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 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-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 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 -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"
|
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 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 "*"' -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 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
|
# Fish commands are not given by apropos
|
||||||
if not set -ql exclude_fish_commands
|
if not set -ql exclude_fish_commands
|
||||||
set -l files $__fish_data_dir/man/man1/*.1
|
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)
|
string replace -r '.*/([^/]+)\.1(\.gz)?$' '$1\t1: fish command' -- $files (status list-files man/man1/ 2>/dev/null)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
function __fish_print_commands --description "Print a list of documented fish commands"
|
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*
|
for file in $__fish_data_dir/man/man1/**.1*
|
||||||
string replace -r '.*/' '' -- $file |
|
string replace -r '.*/' '' -- $file |
|
||||||
string replace -r '.1(.gz)?$' '' |
|
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-right forward-bigword
|
||||||
bind --preset $argv shift-left backward-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_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
|
set -l alt_left_aliases alt-left \e\[1\;9D # iTerm2 < 3.5.12
|
||||||
if test (__fish_uname) = Darwin
|
if test (__fish_uname) = Darwin
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
function __fish_systemctl --description 'Call systemctl with some options from the current commandline'
|
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.
|
# 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.
|
# 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
|
set -l opts t/type= s-state= p/property= a/all
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
function __fish_systemctl_services
|
function __fish_systemctl_services
|
||||||
|
# We don't want to complete with ANSI color codes
|
||||||
|
set -lu SYSTEMD_COLORS
|
||||||
|
|
||||||
if type -q systemctl
|
if type -q systemctl
|
||||||
if __fish_contains_opt user
|
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 ' '
|
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
|
# 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
|
# This does not include the special image ".host" since it isn't valid for most operations
|
||||||
function __fish_systemd_machine_images
|
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
|
machinectl --no-legend --no-pager list-images | while read -l a b
|
||||||
echo $a
|
echo $a
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# It seems machinectl will eliminate spaces from machine names so we don't need to handle that
|
# It seems machinectl will eliminate spaces from machine names so we don't need to handle that
|
||||||
function __fish_systemd_machines
|
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
|
machinectl --no-legend --no-pager list --all | while read -l a b
|
||||||
echo $a
|
echo $a
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,18 +5,13 @@ function export --description 'Set env variable. Alias for `set -gx` for bash co
|
|||||||
end
|
end
|
||||||
for arg in $argv
|
for arg in $argv
|
||||||
set -l v (string split -m 1 "=" -- $arg)
|
set -l v (string split -m 1 "=" -- $arg)
|
||||||
|
set -l value
|
||||||
switch (count $v)
|
switch (count $v)
|
||||||
case 1
|
case 1
|
||||||
set -gx $v $$v
|
set value $$v[1]
|
||||||
case 2
|
case 2
|
||||||
if contains -- $v[1] PATH CDPATH MANPATH
|
set value $v[2]
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
set -gx $v[1] $value
|
||||||
end
|
end
|
||||||
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 alt-delete kill-token
|
||||||
bind --preset $argv ctrl-delete kill-word
|
bind --preset $argv ctrl-delete kill-word
|
||||||
end
|
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-\< beginning-of-buffer
|
||||||
bind --preset $argv alt-\> end-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 insert end end-of-line
|
||||||
bind -s --preset -M default 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.
|
bind -s --preset -M default x delete-char
|
||||||
# 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 backward-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 insert delete delete-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 default delete delete-char
|
||||||
|
|
||||||
# Backspace deletes a char in insert mode, but not in normal/default mode.
|
# Backspace deletes a char in insert mode, but not in normal/default mode.
|
||||||
bind -s --preset -M insert backspace backward-delete-char
|
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
|
# 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),
|
# 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
|
# \ 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 P yank
|
||||||
bind -s --preset g,p yank-pop
|
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
|
# Lowercase r, enters replace_one mode
|
||||||
#
|
#
|
||||||
bind -s --preset -m replace_one r repaint-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 '' '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 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
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 '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 '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 '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 '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 escape cancel repaint-mode
|
||||||
bind -s --preset -M replace_one -m default ctrl-\[ 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
|
set -l searchterm $argv
|
||||||
if not set -q argv[1]
|
if not set -q argv[1]
|
||||||
read -P"Search term: " searchterm
|
read -P"Search term: " searchterm
|
||||||
|
or return $status
|
||||||
end
|
end
|
||||||
|
|
||||||
if test "$search_mode" = --exact
|
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 "Enter 'all' to delete all the matching entries."
|
||||||
echo
|
echo
|
||||||
read --local --prompt "echo 'Delete which entries? '" choice
|
read --local --prompt "echo 'Delete which entries? '" choice
|
||||||
|
or return $status
|
||||||
echo ''
|
echo ''
|
||||||
|
|
||||||
if test -z "$choice"
|
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")
|
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
|
read --local --prompt "echo 'Are you sure you want to clear history? (yes/no) '" choice
|
||||||
|
or return $status
|
||||||
if test "$choice" = yes
|
if test "$choice" = yes
|
||||||
builtin history clear $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
builtin history clear $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $argv
|
||||||
and printf (_ "Command history cleared!\n")
|
and printf (_ "Command history cleared!\n")
|
||||||
@@ -205,6 +208,7 @@ function history --description "display or manipulate interactive command histor
|
|||||||
set -l newitem $argv
|
set -l newitem $argv
|
||||||
if not set -q argv[1]
|
if not set -q argv[1]
|
||||||
read -P "Command: " newitem
|
read -P "Command: " newitem
|
||||||
|
or return $status
|
||||||
end
|
end
|
||||||
|
|
||||||
builtin history append $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
|
builtin history append $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"
|
underline = "single"
|
||||||
elif comp.startswith("--underline="):
|
elif comp.startswith("--underline="):
|
||||||
underline = comp.stripprefix("--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")
|
underline = comp.stripprefix("-u")
|
||||||
elif comp == "--italics" or comp == "-i":
|
elif comp == "--italics" or comp == "-i":
|
||||||
italics = True
|
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)]
|
#[cfg(test)]
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
use crate::wchar::{wstr, WString, L};
|
use crate::wchar::{wstr, WString, L};
|
||||||
|
use crate::wchar_ext::WExt;
|
||||||
use crate::wutil::{file_id_for_path, FileId, INVALID_FILE_ID};
|
use crate::wutil::{file_id_for_path, FileId, INVALID_FILE_ID};
|
||||||
|
use crate::FLOGF;
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
#[cfg(feature = "embed-data")]
|
#[cfg(feature = "embed-data")]
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
@@ -56,6 +58,12 @@ pub fn has_asset(_cmd: &str) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
enum AssetDir {
|
||||||
|
Functions,
|
||||||
|
Completions,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum AutoloadPath {
|
pub enum AutoloadPath {
|
||||||
#[cfg(feature = "embed-data")]
|
#[cfg(feature = "embed-data")]
|
||||||
Embedded(String),
|
Embedded(String),
|
||||||
@@ -63,7 +71,7 @@ pub enum AutoloadPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum AutoloadResult {
|
enum AutoloadResult {
|
||||||
Path(WString),
|
Path(AutoloadPath),
|
||||||
Loaded,
|
Loaded,
|
||||||
Pending,
|
Pending,
|
||||||
None,
|
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
|
/// 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.
|
/// code; it is the caller's responsibility to load the file.
|
||||||
pub fn resolve_command(&mut self, cmd: &wstr, env: &dyn Environment) -> Option<AutoloadPath> {
|
pub fn resolve_command(&mut self, cmd: &wstr, env: &dyn Environment) -> Option<AutoloadPath> {
|
||||||
use crate::wchar_ext::WExt;
|
match self.resolve_command_impl(
|
||||||
|
cmd,
|
||||||
let mut possible_path = None;
|
env.get(self.env_var_name)
|
||||||
if let Some(var) = env.get(self.env_var_name) {
|
.as_ref()
|
||||||
match self.resolve_command_impl(cmd, var.as_list()) {
|
.map(|var| var.as_list())
|
||||||
AutoloadResult::Path(path) => {
|
.unwrap_or_default(),
|
||||||
crate::FLOGF!(autoload, "Loading from path with var: %ls", path);
|
) {
|
||||||
// HACK: Ignore generated_completions until we tried the embedded assets
|
AutoloadResult::Path(path) => {
|
||||||
if path
|
match &path {
|
||||||
.find("/generated_completions/".chars().collect::<Vec<_>>())
|
#[cfg(feature = "embed-data")]
|
||||||
.is_some()
|
AutoloadPath::Embedded(_) => {
|
||||||
{
|
FLOGF!(autoload, "Embedded: %ls", cmd);
|
||||||
possible_path = Some(path);
|
}
|
||||||
} else {
|
AutoloadPath::Path(path) => {
|
||||||
return Some(AutoloadPath::Path(path));
|
FLOGF!(
|
||||||
|
autoload,
|
||||||
|
"Loading %ls from var %ls from path %ls",
|
||||||
|
cmd,
|
||||||
|
self.env_var_name,
|
||||||
|
path
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AutoloadResult::Loaded => return None,
|
Some(path)
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
AutoloadResult::Loaded | AutoloadResult::Pending | AutoloadResult::None => None,
|
||||||
}
|
}
|
||||||
possible_path.map(AutoloadPath::Path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to actually perform an autoload.
|
/// Helper to actually perform an autoload.
|
||||||
@@ -182,7 +152,7 @@ pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) {
|
|||||||
AutoloadPath::Embedded(name) => {
|
AutoloadPath::Embedded(name) => {
|
||||||
use crate::common::str2wcstring;
|
use crate::common::str2wcstring;
|
||||||
use std::sync::Arc;
|
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 emfile = Asset::get(name).expect("Embedded file not found");
|
||||||
let src = str2wcstring(&emfile.data);
|
let src = str2wcstring(&emfile.data);
|
||||||
let mut widename = L!("embedded:").to_owned();
|
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.
|
/// Return whether a command could potentially be autoloaded.
|
||||||
/// This does not actually mark the command as being autoloaded.
|
/// This does not actually mark the command as being autoloaded.
|
||||||
pub fn can_autoload(&mut self, cmd: &wstr) -> bool {
|
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.
|
/// 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?
|
// 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;
|
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?
|
// Is this file the same as what we previously autoloaded?
|
||||||
if let Some(loaded_file) = self.autoloaded_files.get(cmd) {
|
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.
|
// The file has been autoloaded and is unchanged.
|
||||||
return AutoloadResult::Loaded;
|
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.
|
// We're going to (tell our caller to) autoload this command.
|
||||||
self.current_autoloading.insert(cmd.to_owned());
|
self.current_autoloading.insert(cmd.to_owned());
|
||||||
self.autoloaded_files.insert(cmd.to_owned(), file.file_id);
|
self.autoloaded_files
|
||||||
AutoloadResult::Path(file.path)
|
.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.
|
/// Represents a file that we might want to autoload.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AutoloadableFile {
|
struct FileInfo {
|
||||||
/// The path to the file.
|
/// The path to the file.
|
||||||
path: WString,
|
path: WString,
|
||||||
/// The metadata for the file.
|
/// The metadata for the file.
|
||||||
file_id: FileId,
|
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.
|
// A timestamp is a monotonic point in time.
|
||||||
type Timestamp = time::Instant;
|
type Timestamp = time::Instant;
|
||||||
type MissesLruCache = LruCache<WString, Timestamp>;
|
type MissesLruCache = LruCache<WString, Timestamp>;
|
||||||
|
|
||||||
struct KnownFile {
|
struct KnownFile {
|
||||||
file: AutoloadableFile,
|
file: AutoloadableFileInfo,
|
||||||
last_checked: Timestamp,
|
last_checked: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,10 +335,32 @@ fn dirs(&self) -> &[WString] {
|
|||||||
/// Check if a command `cmd` can be loaded.
|
/// Check if a command `cmd` can be loaded.
|
||||||
/// If `allow_stale` is true, allow stale entries; otherwise discard them.
|
/// If `allow_stale` is true, allow stale entries; otherwise discard them.
|
||||||
/// This returns an autoloadable file, or none() if there is no such file.
|
/// 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.
|
// Check hits.
|
||||||
if let Some(value) = self.known_files.get(cmd) {
|
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.
|
// Re-use this cached hit.
|
||||||
return Some(value.file.clone());
|
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.
|
// 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() {
|
if let Some(file) = file.as_ref() {
|
||||||
let old_value = self.known_files.insert(
|
let old_value = self.known_files.insert(
|
||||||
cmd.to_owned(),
|
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.
|
/// Attempt to find an autoloadable file by searching our path list for a given command.
|
||||||
/// Return the file, or none() if none.
|
/// 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)
|
// 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.
|
// we'd try to source the *directory*, which exists.
|
||||||
// So instead ignore these here.
|
// So instead ignore these here.
|
||||||
@@ -421,6 +443,12 @@ fn locate_file(&self, cmd: &wstr) -> Option<AutoloadableFile> {
|
|||||||
// Re-use the storage for path.
|
// Re-use the storage for path.
|
||||||
let mut path;
|
let mut path;
|
||||||
for dir in self.dirs() {
|
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
|
// Construct the path as dir/cmd.fish
|
||||||
path = dir.to_owned();
|
path = dir.to_owned();
|
||||||
path.push('/');
|
path.push('/');
|
||||||
@@ -430,11 +458,31 @@ fn locate_file(&self, cmd: &wstr) -> Option<AutoloadableFile> {
|
|||||||
let file_id = file_id_for_path(&path);
|
let file_id = file_id_for_path(&path);
|
||||||
if file_id != INVALID_FILE_ID {
|
if file_id != INVALID_FILE_ID {
|
||||||
// Found it.
|
// Found it.
|
||||||
return Some(AutoloadableFile { path, file_id });
|
return Some(AutoloadableFileInfo::FileInfo(FileInfo { path, file_id }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#![allow(clippy::uninlined_format_args)]
|
#![allow(clippy::uninlined_format_args)]
|
||||||
|
|
||||||
use fish::{
|
use fish::{
|
||||||
ast::Ast,
|
ast,
|
||||||
builtins::{
|
builtins::{
|
||||||
fish_indent, fish_key_reader,
|
fish_indent, fish_key_reader,
|
||||||
shared::{
|
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 cmd_wcs = str2wcstring(cmd.as_bytes());
|
||||||
|
|
||||||
let mut errors = ParseErrorList::new();
|
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() || {
|
let errored = ast.errored() || {
|
||||||
parse_util_detect_errors_in_ast(&ast, &cmd_wcs, Some(&mut errors)).is_err()
|
parse_util_detect_errors_in_ast(&ast, &cmd_wcs, Some(&mut errors)).is_err()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::ast::{Ast, Leaf};
|
use crate::ast::{self, Kind, Leaf};
|
||||||
use crate::common::{unescape_string, UnescapeFlags, UnescapeStringStyle};
|
use crate::common::{unescape_string, UnescapeFlags, UnescapeStringStyle};
|
||||||
use crate::complete::Completion;
|
use crate::complete::Completion;
|
||||||
use crate::expand::{expand_string, ExpandFlags, ExpandResultCode};
|
use crate::expand::{expand_string, ExpandFlags, ExpandResultCode};
|
||||||
@@ -44,7 +44,7 @@ enum AppendMode {
|
|||||||
Append,
|
Append,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TokenMode {
|
enum TokenOutputMode {
|
||||||
Expanded,
|
Expanded,
|
||||||
Raw,
|
Raw,
|
||||||
Unescaped,
|
Unescaped,
|
||||||
@@ -108,7 +108,7 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr)
|
|||||||
}
|
}
|
||||||
insert.find(L!("$ "))?; // Early return.
|
insert.find(L!("$ "))?; // Early return.
|
||||||
let source = prefix.to_owned() + insert;
|
let source = prefix.to_owned() + insert;
|
||||||
let ast = Ast::parse(
|
let ast = ast::parse(
|
||||||
&source,
|
&source,
|
||||||
ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS | ParseTreeFlags::LEAVE_UNTERMINATED,
|
ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS | ParseTreeFlags::LEAVE_UNTERMINATED,
|
||||||
None,
|
None,
|
||||||
@@ -116,7 +116,7 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr)
|
|||||||
let mut stripped = WString::new();
|
let mut stripped = WString::new();
|
||||||
let mut have = prefix.len();
|
let mut have = prefix.len();
|
||||||
for node in ast.walk() {
|
for node in ast.walk() {
|
||||||
let Some(ds) = node.as_decorated_statement() else {
|
let Kind::DecoratedStatement(ds) = node.kind() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(range) = ds.command.range() else {
|
let Some(range) = ds.command.range() else {
|
||||||
@@ -151,7 +151,7 @@ fn write_part(
|
|||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
range_is_single_token: bool,
|
range_is_single_token: bool,
|
||||||
cut_at_cursor: bool,
|
cut_at_cursor: bool,
|
||||||
token_mode: Option<TokenMode>,
|
token_mode: Option<TokenOutputMode>,
|
||||||
buffer: &wstr,
|
buffer: &wstr,
|
||||||
cursor_pos: usize,
|
cursor_pos: usize,
|
||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
@@ -171,7 +171,7 @@ fn write_part(
|
|||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
let mut add_token = |token_text: &wstr| {
|
let mut add_token = |token_text: &wstr| {
|
||||||
match token_mode {
|
match token_mode {
|
||||||
TokenMode::Expanded => {
|
TokenOutputMode::Expanded => {
|
||||||
const COMMANDLINE_TOKENS_MAX_EXPANSION: usize = 512;
|
const COMMANDLINE_TOKENS_MAX_EXPANSION: usize = 512;
|
||||||
|
|
||||||
match expand_string(
|
match expand_string(
|
||||||
@@ -199,10 +199,10 @@ fn write_part(
|
|||||||
ExpandResultCode::ok => (),
|
ExpandResultCode::ok => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
TokenMode::Raw => {
|
TokenOutputMode::Raw => {
|
||||||
args.push(Completion::from_completion(token_text.to_owned()));
|
args.push(Completion::from_completion(token_text.to_owned()));
|
||||||
}
|
}
|
||||||
TokenMode::Unescaped => {
|
TokenOutputMode::Unescaped => {
|
||||||
let unescaped = unescape_string(
|
let unescaped = unescape_string(
|
||||||
token_text,
|
token_text,
|
||||||
UnescapeStringStyle::Script(UnescapeFlags::INCOMPLETE),
|
UnescapeStringStyle::Script(UnescapeFlags::INCOMPLETE),
|
||||||
@@ -218,10 +218,16 @@ fn write_part(
|
|||||||
add_token(buff);
|
add_token(buff);
|
||||||
} else {
|
} else {
|
||||||
let mut tok = Tokenizer::new(buff, TOK_ACCEPT_UNFINISHED);
|
let mut tok = Tokenizer::new(buff, TOK_ACCEPT_UNFINISHED);
|
||||||
|
let mut in_redirection = false;
|
||||||
while let Some(token) = tok.next() {
|
while let Some(token) = tok.next() {
|
||||||
if cut_at_cursor && token.end() >= pos {
|
if cut_at_cursor && token.end() >= pos {
|
||||||
break;
|
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 {
|
if token.type_ != TokenType::string {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -319,9 +325,9 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
|||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
}
|
}
|
||||||
token_mode = Some(match c {
|
token_mode = Some(match c {
|
||||||
'x' => TokenMode::Expanded,
|
'x' => TokenOutputMode::Expanded,
|
||||||
'\x02' => TokenMode::Raw,
|
'\x02' => TokenOutputMode::Raw,
|
||||||
'o' => TokenMode::Unescaped,
|
'o' => TokenOutputMode::Unescaped,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -447,7 +453,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
|||||||
streams.err.append(wgettext_fmt!(
|
streams.err.append(wgettext_fmt!(
|
||||||
BUILTIN_ERR_COMBO2,
|
BUILTIN_ERR_COMBO2,
|
||||||
cmd,
|
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);
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ fn builtin_complete_remove_cmd(
|
|||||||
|
|
||||||
if !removed {
|
if !removed {
|
||||||
// This means that all loops were empty.
|
// 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 libc::LC_ALL;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::ast::{
|
use crate::ast::{self, Ast, Kind, Leaf, Node, NodeVisitor, SourceRangeList, Traversal};
|
||||||
self, Ast, Category, Leaf, List, Node, NodeVisitor, SourceRangeList, Traversal, Type,
|
|
||||||
};
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
str2wcstring, unescape_string, wcs2string, UnescapeFlags, UnescapeStringStyle, PROGRAM_NAME,
|
str2wcstring, unescape_string, wcs2string, UnescapeFlags, UnescapeStringStyle, PROGRAM_NAME,
|
||||||
};
|
};
|
||||||
@@ -96,7 +94,6 @@ struct AstSizeMetrics {
|
|||||||
/// Note tokens and keywords are also counted as leaves.
|
/// Note tokens and keywords are also counted as leaves.
|
||||||
branch_count: usize,
|
branch_count: usize,
|
||||||
leaf_count: usize,
|
leaf_count: usize,
|
||||||
list_count: usize,
|
|
||||||
token_count: usize,
|
token_count: usize,
|
||||||
keyword_count: usize,
|
keyword_count: usize,
|
||||||
// An estimate of the total allocated size of the ast in bytes.
|
// 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, " nodes: {}", self.node_count)?;
|
||||||
writeln!(f, " branches: {}", self.branch_count)?;
|
writeln!(f, " branches: {}", self.branch_count)?;
|
||||||
writeln!(f, " leaves: {}", self.leaf_count)?;
|
writeln!(f, " leaves: {}", self.leaf_count)?;
|
||||||
writeln!(f, " lists: {}", self.list_count)?;
|
|
||||||
writeln!(f, " tokens: {}", self.token_count)?;
|
writeln!(f, " tokens: {}", self.token_count)?;
|
||||||
writeln!(f, " keywords: {}", self.keyword_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) {
|
fn visit(&mut self, node: &'a dyn Node) {
|
||||||
self.node_count += 1;
|
self.node_count += 1;
|
||||||
self.memory_size += node.self_memory_size();
|
self.memory_size += node.self_memory_size();
|
||||||
match node.category() {
|
if node.as_leaf().is_some() {
|
||||||
Category::branch => self.branch_count += 1,
|
self.leaf_count += 1;
|
||||||
Category::leaf => self.leaf_count += 1,
|
} else {
|
||||||
Category::list => self.list_count += 1,
|
self.branch_count += 1; // treating lists as branches
|
||||||
}
|
}
|
||||||
if node.as_token().is_some() {
|
if node.as_token().is_some() {
|
||||||
self.token_count += 1;
|
self.token_count += 1;
|
||||||
@@ -138,7 +134,7 @@ fn visit(&mut self, node: &'a dyn Node) {
|
|||||||
if node.as_keyword().is_some() {
|
if node.as_keyword().is_some() {
|
||||||
self.keyword_count += 1;
|
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.
|
// Collect the token ranges into a list.
|
||||||
let mut tok_ranges = vec![];
|
let mut tok_ranges = vec![];
|
||||||
for node in Traversal::new(self.ast.top()) {
|
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();
|
let r = node.source_range();
|
||||||
if r.length() > 0 {
|
if r.length() > 0 {
|
||||||
tok_ranges.push(r);
|
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.
|
// See if we have a condition and an andor_job_list.
|
||||||
let condition;
|
let condition;
|
||||||
let andors;
|
let andors;
|
||||||
if let Some(ifc) = node.as_if_clause() {
|
if let Kind::IfClause(ifc) = node.kind() {
|
||||||
condition = ifc.condition.semi_nl.as_ref();
|
condition = ifc.condition.semi_nl.as_ref();
|
||||||
andors = &ifc.andor_tail;
|
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();
|
condition = wc.condition.semi_nl.as_ref();
|
||||||
andors = &wc.andor_tail;
|
andors = &wc.andor_tail;
|
||||||
} else {
|
} 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 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);
|
condition.map(&mut mark_semi_from_input);
|
||||||
// Mark all but last of the andor list.
|
// 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());
|
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.
|
// `{ x; y; }` gets semis if the input uses semis and it spans only one line.
|
||||||
for node in Traversal::new(self.ast.top()) {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
if self
|
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.
|
// `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()) {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
let mut prev_job_semi_nl = None;
|
let mut prev_job_semi_nl = None;
|
||||||
@@ -355,7 +351,7 @@ fn compute_multi_line_brace_statement_locations(&self) -> Vec<usize> {
|
|||||||
.collect();
|
.collect();
|
||||||
let mut next_newline = 0;
|
let mut next_newline = 0;
|
||||||
for node in Traversal::new(self.ast.top()) {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
while next_newline != newline_offsets.len()
|
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.
|
// 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 {
|
fn gap_text_flags_before_node(&self, node: &dyn Node) -> GapFlags {
|
||||||
let mut result = GapFlags::default();
|
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.
|
// 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
|
result.allow_escaped_newlines = true
|
||||||
}
|
}
|
||||||
Type::token_base => {
|
Kind::Token(token) => {
|
||||||
// Allow escaped newlines before && and ||, and also pipes.
|
// 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 => {
|
ParseTokenType::andand | ParseTokenType::oror | ParseTokenType::pipe => {
|
||||||
result.allow_escaped_newlines = true;
|
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
|
// Allow escaped newlines before commands that follow a variable assignment
|
||||||
// since both can be long (#7955).
|
// since both can be long (#7955).
|
||||||
let p = self.traversal.parent(node);
|
let p = self.traversal.parent(node);
|
||||||
if p.typ() != Type::decorated_statement {
|
if !matches!(p.kind(), Kind::DecoratedStatement(_)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
let p = self.traversal.parent(p);
|
let p = self.traversal.parent(p);
|
||||||
assert_eq!(p.typ(), Type::statement);
|
assert!(matches!(p.kind(), Kind::Statement(_)));
|
||||||
let p = self.traversal.parent(p);
|
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() {
|
if !job.variables.is_empty() {
|
||||||
result.allow_escaped_newlines = true;
|
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() {
|
if !job_cnt.variables.is_empty() {
|
||||||
result.allow_escaped_newlines = true;
|
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() {
|
if !not_stmt.variables.is_empty() {
|
||||||
result.allow_escaped_newlines = true;
|
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 {
|
fn is_multi_line_brace(&self, node: &dyn ast::Token) -> bool {
|
||||||
self.traversal
|
let Kind::BraceStatement(brace) = self.traversal.parent(node.as_node()).kind() else {
|
||||||
.parent(node.as_node())
|
return false;
|
||||||
.as_brace_statement()
|
};
|
||||||
.is_some_and(|brace_statement| {
|
self.multi_line_brace_statement_locations
|
||||||
self.multi_line_brace_statement_locations
|
.binary_search(&brace.source_range().start())
|
||||||
.binary_search(&brace_statement.source_range().start())
|
.is_ok()
|
||||||
.is_ok()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn visit_left_brace(&mut self, node: &dyn ast::Token) {
|
fn visit_left_brace(&mut self, node: &dyn ast::Token) {
|
||||||
let range = node.source_range();
|
let range = node.source_range();
|
||||||
@@ -837,29 +831,30 @@ fn prettify_traversal(&mut self) {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match node.typ() {
|
|
||||||
Type::argument | Type::variable_assignment => {
|
match node.kind() {
|
||||||
|
Kind::Argument(_) | Kind::VariableAssignment(_) => {
|
||||||
self.emit_node_text(node);
|
self.emit_node_text(node);
|
||||||
self.traversal.skip_children(node);
|
self.traversal.skip_children(node);
|
||||||
}
|
}
|
||||||
Type::redirection => {
|
Kind::Redirection(node) => {
|
||||||
self.visit_redirection(node.as_redirection().unwrap());
|
self.visit_redirection(node);
|
||||||
self.traversal.skip_children(node);
|
self.traversal.skip_children(node);
|
||||||
}
|
}
|
||||||
Type::maybe_newlines => {
|
Kind::MaybeNewlines(node) => {
|
||||||
self.visit_maybe_newlines(node.as_maybe_newlines().unwrap());
|
self.visit_maybe_newlines(node);
|
||||||
self.traversal.skip_children(node);
|
self.traversal.skip_children(node);
|
||||||
}
|
}
|
||||||
Type::begin_header => {
|
Kind::BeginHeader(node) => {
|
||||||
self.visit_begin_header(node.as_begin_header().unwrap());
|
self.visit_begin_header(node);
|
||||||
self.traversal.skip_children(node);
|
self.traversal.skip_children(node);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// For branch and list nodes, default is to visit their children.
|
// Default is to visit children. We expect all leaves to have been handled above.
|
||||||
if [Category::branch, Category::list].contains(&node.category()) {
|
assert!(
|
||||||
continue;
|
node.as_leaf().is_none(),
|
||||||
}
|
"Should have handled all leaf nodes"
|
||||||
panic!("unexpected node type");
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1246,7 +1241,7 @@ struct TokenRange {
|
|||||||
// Entry point for prettification.
|
// Entry point for prettification.
|
||||||
fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString {
|
fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString {
|
||||||
if DUMP_PARSE_TREE.load() {
|
if DUMP_PARSE_TREE.load() {
|
||||||
let ast = Ast::parse(
|
let ast = ast::parse(
|
||||||
src,
|
src,
|
||||||
ParseTreeFlags::LEAVE_UNTERMINATED
|
ParseTreeFlags::LEAVE_UNTERMINATED
|
||||||
| ParseTreeFlags::INCLUDE_COMMENTS
|
| ParseTreeFlags::INCLUDE_COMMENTS
|
||||||
@@ -1261,7 +1256,7 @@ fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString {
|
|||||||
metrics.visit(ast.top());
|
metrics.visit(ast.top());
|
||||||
streams.err.appendln(format!("{}", metrics));
|
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);
|
let mut printer = PrettyPrinter::new(src, &ast, do_indent);
|
||||||
printer.prettify()
|
printer.prettify()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
builtins::shared::BUILTIN_ERR_UNKNOWN,
|
builtins::shared::BUILTIN_ERR_UNKNOWN,
|
||||||
common::{shell_modes, str2wcstring, PROGRAM_NAME},
|
common::{shell_modes, str2wcstring, PROGRAM_NAME},
|
||||||
env::env_init,
|
env::{env_init, EnvStack, Environment},
|
||||||
|
future_feature_flags,
|
||||||
input_common::{
|
input_common::{
|
||||||
terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent, InputEventQueue,
|
match_key_event_to_key, terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent,
|
||||||
InputEventQueuer, KeyEvent, QueryResponseEvent, TerminalQuery,
|
InputEventQueue, InputEventQueuer, KeyEvent, QueryResponseEvent, TerminalQuery,
|
||||||
},
|
},
|
||||||
key::{char_to_symbol, Key, Modifiers},
|
key::{char_to_symbol, Key},
|
||||||
nix::isatty,
|
nix::isatty,
|
||||||
panic::panic_handler,
|
panic::panic_handler,
|
||||||
print_help::print_help,
|
print_help::print_help,
|
||||||
@@ -39,19 +40,24 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// Return true if the recent sequence of characters indicates the user wants to exit the program.
|
/// 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 {
|
fn should_exit(
|
||||||
recent_keys.push(key);
|
streams: &mut IoStreams,
|
||||||
|
recent_keys: &mut Vec<KeyEvent>,
|
||||||
|
key_evt: KeyEvent,
|
||||||
|
) -> bool {
|
||||||
|
recent_keys.push(key_evt);
|
||||||
|
|
||||||
for evt in [VINTR, VEOF] {
|
for evt in [VINTR, VEOF] {
|
||||||
let modes = shell_modes();
|
let modes = shell_modes();
|
||||||
let cc = Key::from_single_byte(modes.c_cc[evt]);
|
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
|
if recent_keys
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.is_some_and(|&prev| prev == cc)
|
.and_then(|prev| match_key_event_to_key(prev, &cc))
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -103,33 +109,27 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool)
|
|||||||
}
|
}
|
||||||
streams.out.append(L!("\n"));
|
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';
|
let have_shifted_key = kevt.key.shifted_codepoint != '\0';
|
||||||
// If we have shift + some other modifier, the lowercase version is the canonical one.
|
let mut keys = vec![(kevt.key.key, "")];
|
||||||
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());
|
|
||||||
if have_shifted_key {
|
if have_shifted_key {
|
||||||
let mut shifted_key = kevt.key.key;
|
let mut shifted_key = kevt.key.key;
|
||||||
shifted_key.modifiers.shift = false;
|
shifted_key.modifiers.shift = false;
|
||||||
shifted_key.codepoint = kevt.key.shifted_codepoint;
|
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) {
|
if continuous_mode && should_exit(streams, &mut recent_chars, kevt.key) {
|
||||||
streams.err.appendln("\nExiting at your request.");
|
streams.err.appendln("\nExiting at your request.");
|
||||||
@@ -268,6 +268,11 @@ fn throwing_main() -> i32 {
|
|||||||
threads::init();
|
threads::init();
|
||||||
env_init(None, true, false);
|
env_init(None, true, false);
|
||||||
reader_init(false);
|
reader_init(false);
|
||||||
|
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
||||||
|
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 out = Fd(FdOutputStream::new(STDOUT_FILENO));
|
||||||
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
use crate::wutil::encoding::zero_mbstate;
|
use crate::wutil::encoding::zero_mbstate;
|
||||||
use crate::wutil::perror;
|
use crate::wutil::perror;
|
||||||
use libc::SEEK_CUR;
|
use libc::SEEK_CUR;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ fn read_interactive(
|
|||||||
|
|
||||||
let mline = {
|
let mline = {
|
||||||
let _interactive = parser.push_scope(|s| s.is_interactive = true);
|
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();
|
terminal_protocols_disable_ifn();
|
||||||
if let Some(line) = mline {
|
if let Some(line) = mline {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::common::str2wcstring;
|
use crate::common::str2wcstring;
|
||||||
use crate::screen::is_dumb;
|
use crate::screen::{is_dumb, only_grayscale};
|
||||||
use crate::terminal::{use_terminfo, Outputter};
|
use crate::terminal::{use_terminfo, Outputter};
|
||||||
use crate::text_face::{
|
use crate::text_face::{
|
||||||
self, parse_text_face_and_options, PrintColorsArgs, SpecifiedTextFace, TextFace, TextStyling,
|
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() {
|
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
|
// We need to do *something* or the lack of any output messes up
|
||||||
// when the cartesian product here would make "foo" disappear:
|
// when the cartesian product here would make "foo" disappear:
|
||||||
// $ echo (set_color foo)bar
|
// $ echo (set_color foo)bar
|
||||||
|
|||||||
@@ -672,11 +672,10 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
|||||||
STATUS_CURRENT_CMD => {
|
STATUS_CURRENT_CMD => {
|
||||||
let command = &parser.libdata().status_vars.command;
|
let command = &parser.libdata().status_vars.command;
|
||||||
if !command.is_empty() {
|
if !command.is_empty() {
|
||||||
streams.out.append(command);
|
streams.out.appendln(command);
|
||||||
} else {
|
} else {
|
||||||
streams.out.appendln(*PROGRAM_NAME.get().unwrap());
|
streams.out.appendln(*PROGRAM_NAME.get().unwrap());
|
||||||
}
|
}
|
||||||
streams.out.append_char('\n');
|
|
||||||
}
|
}
|
||||||
STATUS_CURRENT_COMMANDLINE => {
|
STATUS_CURRENT_COMMANDLINE => {
|
||||||
let commandline = &parser.libdata().status_vars.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::encoding::{mbrtowc, wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX};
|
||||||
use crate::wutil::fish_iswalnum;
|
use crate::wutil::fish_iswalnum;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use core::slice;
|
|
||||||
use libc::{EIO, O_WRONLY, SIGTTOU, SIG_IGN, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
use libc::{EIO, O_WRONLY, SIGTTOU, SIG_IGN, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
@@ -1184,15 +1183,12 @@ pub fn truncate_at_nul(input: &wstr) -> &wstr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn cstr2wcstring(input: &[u8]) -> WString {
|
pub fn cstr2wcstring(input: &[u8]) -> WString {
|
||||||
let strlen = input.iter().position(|c| *c == b'\0').unwrap();
|
let input = CStr::from_bytes_until_nul(input).unwrap().to_bytes();
|
||||||
str2wcstring(&input[0..strlen])
|
str2wcstring(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn charptr2wcstring(input: *const libc::c_char) -> WString {
|
pub(crate) fn charptr2wcstring(input: *const libc::c_char) -> WString {
|
||||||
let input: &[u8] = unsafe {
|
let input: &[u8] = unsafe { CStr::from_ptr(input).to_bytes() };
|
||||||
let strlen = libc::strlen(input);
|
|
||||||
slice::from_raw_parts(input.cast(), strlen)
|
|
||||||
};
|
|
||||||
str2wcstring(input)
|
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.
|
/// 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> {
|
pub fn read_blocked(fd: RawFd, buf: &mut [u8]) -> nix::Result<usize> {
|
||||||
loop {
|
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 {
|
if let Err(nix::Error::EINTR) = res {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1563,11 +1559,13 @@ pub fn is_windows_subsystem_for_linux(v: WSL) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let wsl = RESULT.get_or_init(|| {
|
let wsl = RESULT.get_or_init(|| {
|
||||||
let mut info: libc::utsname = unsafe { mem::zeroed() };
|
let release = unsafe {
|
||||||
let release: &[u8] = unsafe {
|
let mut info = mem::MaybeUninit::uninit();
|
||||||
libc::uname(&mut info);
|
libc::uname(info.as_mut_ptr());
|
||||||
std::mem::transmute(&info.release[..])
|
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`
|
// Sample utsname.release under WSLv2, testing for something like `4.19.104-microsoft-standard`
|
||||||
// or `5.10.16.3-microsoft-standard-WSL2`
|
// 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) {
|
pub fn redirect_tty_output(in_signal_handler: bool) {
|
||||||
unsafe {
|
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 s = CStr::from_bytes_with_nul(b"/dev/null\0").unwrap();
|
||||||
let fd = libc::open(s.as_ptr(), O_WRONLY);
|
let fd = libc::open(s.as_ptr(), O_WRONLY);
|
||||||
if in_signal_handler && fd == -1 {
|
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!");
|
assert!(fd != -1, "Could not open /dev/null!");
|
||||||
}
|
}
|
||||||
for stdfd in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] {
|
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);
|
libc::dup2(fd, stdfd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
ast::unescape_keyword,
|
ast::unescape_keyword,
|
||||||
common::charptr2wcstring,
|
common::charptr2wcstring,
|
||||||
reader::{get_quote, is_backslashed},
|
reader::{get_quote, is_backslashed},
|
||||||
tokenizer::is_brace_statement,
|
|
||||||
util::wcsfilecmp,
|
util::wcsfilecmp,
|
||||||
wutil::sprintf,
|
wutil::sprintf,
|
||||||
};
|
};
|
||||||
@@ -667,20 +666,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
|
|||||||
|
|
||||||
// Get all the arguments.
|
// Get all the arguments.
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
{
|
parse_util_process_extent(&cmdline, position_in_statement, Some(&mut tokens));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let actual_token_count = tokens.len();
|
let actual_token_count = tokens.len();
|
||||||
|
|
||||||
// Hack: fix autosuggestion by removing prefixing "and"s #6249.
|
// 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.
|
/// 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 mut completion_map = COMPLETION_MAP.lock().expect("mutex poisoned");
|
||||||
let idx = CompletionEntryIndex {
|
let idx = CompletionEntryIndex {
|
||||||
name: cmd,
|
name: cmd,
|
||||||
is_path: cmd_is_path,
|
is_path: cmd_is_path,
|
||||||
};
|
};
|
||||||
let removed = completion_map.remove(&idx).is_some();
|
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);
|
COMPLETION_TOMBSTONES.lock().unwrap().insert(idx.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2537,7 +2523,7 @@ pub fn complete_invalidate_path() {
|
|||||||
.expect("mutex poisoned")
|
.expect("mutex poisoned")
|
||||||
.get_autoloaded_commands();
|
.get_autoloaded_commands();
|
||||||
for cmd in cmds {
|
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!(
|
FLOGF!(
|
||||||
config,
|
config,
|
||||||
"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \
|
"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
|
paths
|
||||||
.data
|
.data
|
||||||
.clone()
|
.clone()
|
||||||
@@ -147,6 +147,11 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
|||||||
.clone()
|
.clone()
|
||||||
.map(|x| x.display().to_string())
|
.map(|x| x.display().to_string())
|
||||||
.unwrap_or("|not found|".to_string()),
|
.unwrap_or("|not found|".to_string()),
|
||||||
|
paths
|
||||||
|
.locale
|
||||||
|
.clone()
|
||||||
|
.map(|x| x.display().to_string())
|
||||||
|
.unwrap_or("|not found|".to_string()),
|
||||||
);
|
);
|
||||||
|
|
||||||
paths
|
paths
|
||||||
|
|||||||
@@ -801,7 +801,7 @@ fn save(&mut self, directory: &wstr) -> bool {
|
|||||||
{
|
{
|
||||||
let mut times: [libc::timespec; 2] = unsafe { std::mem::zeroed() };
|
let mut times: [libc::timespec; 2] = unsafe { std::mem::zeroed() };
|
||||||
times[0].tv_nsec = libc::UTIME_OMIT; // don't change ctime
|
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 {
|
unsafe {
|
||||||
libc::futimens(private_file.as_raw_fd(), ×[0]);
|
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::libc::_PATH_BSHELL;
|
||||||
use crate::nix::isatty;
|
use crate::nix::isatty;
|
||||||
use crate::null_terminated_array::{
|
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||||
null_terminated_array_length, AsNullTerminatedArray, OwningNullTerminatedArray,
|
|
||||||
};
|
|
||||||
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
|
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
|
||||||
#[cfg(FISH_USE_POSIX_SPAWN)]
|
#[cfg(FISH_USE_POSIX_SPAWN)]
|
||||||
use crate::proc::Pid;
|
use crate::proc::Pid;
|
||||||
@@ -52,13 +50,14 @@
|
|||||||
use crate::wutil::{wgettext, wgettext_fmt};
|
use crate::wutil::{wgettext, wgettext_fmt};
|
||||||
use errno::{errno, set_errno};
|
use errno::{errno, set_errno};
|
||||||
use libc::{
|
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,
|
STDIN_FILENO, STDOUT_FILENO,
|
||||||
};
|
};
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::stat;
|
use nix::sys::stat;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||||
use std::slice;
|
use std::slice;
|
||||||
@@ -388,8 +387,8 @@ pub fn is_thompson_shell_script(path: &CStr) -> bool {
|
|||||||
fn safe_launch_process(
|
fn safe_launch_process(
|
||||||
_p: &Process,
|
_p: &Process,
|
||||||
actual_cmd: &CStr,
|
actual_cmd: &CStr,
|
||||||
argv: &impl AsNullTerminatedArray<CharType = c_char>,
|
argv: &OwningNullTerminatedArray,
|
||||||
envv: &impl AsNullTerminatedArray<CharType = c_char>,
|
envv: &OwningNullTerminatedArray,
|
||||||
) -> ! {
|
) -> ! {
|
||||||
// This function never returns, so we take certain liberties with constness.
|
// This function never returns, so we take certain liberties with constness.
|
||||||
|
|
||||||
@@ -405,7 +404,7 @@ fn safe_launch_process(
|
|||||||
// Construct new argv.
|
// Construct new argv.
|
||||||
// We must not allocate memory, so only 128 args are supported.
|
// We must not allocate memory, so only 128 args are supported.
|
||||||
const maxargs: usize = 128;
|
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) };
|
let argv = unsafe { slice::from_raw_parts(argv.get(), nargs) };
|
||||||
if nargs <= maxargs {
|
if nargs <= maxargs {
|
||||||
// +1 for /bin/sh, +1 for terminating nullptr
|
// +1 for /bin/sh, +1 for terminating nullptr
|
||||||
@@ -422,7 +421,7 @@ fn safe_launch_process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_errno(err);
|
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));
|
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.
|
// Ensure the terminal modes are what they were before we changed them.
|
||||||
restore_term_mode(false);
|
restore_term_mode(false);
|
||||||
// Bounce to launch_process. This never returns.
|
// 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.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut blocked_signals: libc::sigset_t = unsafe { std::mem::zeroed() };
|
let mut blocked_signals = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigemptyset(&mut blocked_signals) };
|
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) {
|
let blocked_signals = if blocked_signals_for_job(j, &mut blocked_signals) {
|
||||||
Some(&blocked_signals)
|
Some(&blocked_signals)
|
||||||
} else {
|
} else {
|
||||||
@@ -685,8 +687,11 @@ fn fork_child_for_process(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Decide if the job wants to set a custom sigmask.
|
// Decide if the job wants to set a custom sigmask.
|
||||||
let mut blocked_signals: libc::sigset_t = unsafe { std::mem::zeroed() };
|
let mut blocked_signals = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigemptyset(&mut blocked_signals) };
|
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) {
|
let blocked_signals = if blocked_signals_for_job(job, &mut blocked_signals) {
|
||||||
Some(&blocked_signals)
|
Some(&blocked_signals)
|
||||||
} else {
|
} else {
|
||||||
@@ -858,7 +863,7 @@ fn exec_external_command(
|
|||||||
let pid = match pid {
|
let pid = match pid {
|
||||||
Ok(pid) => pid,
|
Ok(pid) => pid,
|
||||||
Err(err) => {
|
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
|
p.status
|
||||||
.update(&ProcStatus::from_exit_code(exit_code_from_exec_error(
|
.update(&ProcStatus::from_exit_code(exit_code_from_exec_error(
|
||||||
err.0,
|
err.0,
|
||||||
@@ -897,7 +902,7 @@ fn exec_external_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fork_child_for_process(j, p, &dup2s, L!("external command"), |p| {
|
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::wildcard::{WildcardResult, ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
|
||||||
use crate::wutil::{normalize_path, wcstoi_partial, Options};
|
use crate::wutil::{normalize_path, wcstoi_partial, Options};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// Set of flags controlling expansions.
|
/// Set of flags controlling expansions.
|
||||||
@@ -1157,19 +1158,20 @@ fn expand_home_directory(input: &mut WString, vars: &dyn Environment) {
|
|||||||
} else {
|
} else {
|
||||||
// Some other user's home directory.
|
// Some other user's home directory.
|
||||||
let name_cstr = wcs2zstring(username);
|
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 result: *mut libc::passwd = std::ptr::null_mut();
|
||||||
let mut buf = [0 as libc::c_char; 8192];
|
let mut buf = [0 as libc::c_char; 8192];
|
||||||
let retval = unsafe {
|
let retval = unsafe {
|
||||||
libc::getpwnam_r(
|
libc::getpwnam_r(
|
||||||
name_cstr.as_ptr(),
|
name_cstr.as_ptr(),
|
||||||
&mut userinfo,
|
userinfo.as_mut_ptr(),
|
||||||
&mut buf[0],
|
&mut buf[0],
|
||||||
std::mem::size_of_val(&buf),
|
std::mem::size_of_val(&buf),
|
||||||
&mut result,
|
&mut result,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
if retval == 0 && !result.is_null() {
|
if retval == 0 && !result.is_null() {
|
||||||
|
let userinfo = unsafe { userinfo.assume_init() };
|
||||||
home = Some(charptr2wcstring(userinfo.pw_dir));
|
home = Some(charptr2wcstring(userinfo.pw_dir));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ pub struct FdReadableSet {
|
|||||||
impl FdReadableSet {
|
impl FdReadableSet {
|
||||||
/// Construct an empty set.
|
/// Construct an empty set.
|
||||||
pub fn new() -> FdReadableSet {
|
pub fn new() -> FdReadableSet {
|
||||||
FdReadableSet {
|
let mut fdset_ = std::mem::MaybeUninit::uninit();
|
||||||
fdset_: unsafe { std::mem::zeroed() },
|
let fdset_ = unsafe {
|
||||||
nfds_: 0,
|
libc::FD_ZERO(fdset_.as_mut_ptr());
|
||||||
}
|
fdset_.assume_init()
|
||||||
|
};
|
||||||
|
FdReadableSet { fdset_, nfds_: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset back to an empty set.
|
/// Reset back to an empty set.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ pub struct AutoCloseFd {
|
|||||||
|
|
||||||
impl Read for AutoCloseFd {
|
impl Read for AutoCloseFd {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
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.
|
// 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,
|
Ok(newfd) => newfd,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
perror("fcntl");
|
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.
|
// If it is that's our cancel signal, so we abort.
|
||||||
loop {
|
loop {
|
||||||
let ret = nix::fcntl::open(path, flags | OFlag::O_CLOEXEC, mode);
|
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 {
|
match ret {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
return Ok(file);
|
return Ok(file);
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ pub fn all_categories() -> Vec<&'static category_t> {
|
|||||||
(char_encoding, "char-encoding", "Character encoding issues");
|
(char_encoding, "char-encoding", "Character encoding issues");
|
||||||
|
|
||||||
(history, "history", "Command history events");
|
(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");
|
(profile_history, "profile-history", "History performance measurements");
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
// That means no locking, no allocating, no freeing memory, etc!
|
// That means no locking, no allocating, no freeing memory, etc!
|
||||||
use super::flog_safe::FLOG_SAFE;
|
use super::flog_safe::FLOG_SAFE;
|
||||||
use crate::nix::getpid;
|
use crate::nix::getpid;
|
||||||
|
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||||
use crate::proc::Pid;
|
use crate::proc::Pid;
|
||||||
use crate::redirection::Dup2List;
|
use crate::redirection::Dup2List;
|
||||||
use crate::signal::signal_reset_handlers;
|
use crate::signal::signal_reset_handlers;
|
||||||
use crate::{common::exit_without_destructors, wutil::fstat};
|
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::ffi::CStr;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::os::unix::fs::MetadataExt;
|
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.
|
/// Report the error code for a failed setpgid call.
|
||||||
pub(crate) fn report_setpgid_error(
|
pub(crate) fn report_setpgid_error(
|
||||||
err: i32,
|
err: i32,
|
||||||
@@ -243,29 +230,13 @@ pub fn execute_fork() -> pid_t {
|
|||||||
pub(crate) fn safe_report_exec_error(
|
pub(crate) fn safe_report_exec_error(
|
||||||
err: i32,
|
err: i32,
|
||||||
actual_cmd: &CStr,
|
actual_cmd: &CStr,
|
||||||
argvv: *const *const c_char,
|
argvv: &OwningNullTerminatedArray,
|
||||||
envv: *const *const c_char,
|
envv: &OwningNullTerminatedArray,
|
||||||
) {
|
) {
|
||||||
match err {
|
match err {
|
||||||
libc::E2BIG => {
|
libc::E2BIG => {
|
||||||
let mut sz = 0;
|
let szenv = envv.iter().map(|s| s.to_bytes().len()).sum::<usize>();
|
||||||
let mut szenv = 0;
|
let sz = szenv + argvv.iter().map(|s| s.to_bytes().len()).sum::<usize>();
|
||||||
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 arg_max = unsafe { libc::sysconf(libc::_SC_ARG_MAX) };
|
let arg_max = unsafe { libc::sysconf(libc::_SC_ARG_MAX) };
|
||||||
if arg_max > 0 {
|
if arg_max > 0 {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
use errno::Errno;
|
use errno::Errno;
|
||||||
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
|
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::sync::atomic::Ordering;
|
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.
|
// 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 {
|
impl Attr {
|
||||||
fn new() -> Result<Self, Errno> {
|
fn new() -> Result<Self, Errno> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut attr: posix_spawnattr_t = std::mem::zeroed();
|
let mut attr = MaybeUninit::uninit();
|
||||||
check_fail(libc::posix_spawnattr_init(&mut attr))?;
|
check_fail(libc::posix_spawnattr_init(attr.as_mut_ptr()))?;
|
||||||
Ok(Self(attr))
|
Ok(Self(attr.assume_init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +63,9 @@ fn drop(&mut self) {
|
|||||||
impl FileActions {
|
impl FileActions {
|
||||||
fn new() -> Result<Self, Errno> {
|
fn new() -> Result<Self, Errno> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut actions: posix_spawn_file_actions_t = std::mem::zeroed();
|
let mut actions = MaybeUninit::uninit();
|
||||||
check_fail(libc::posix_spawn_file_actions_init(&mut actions))?;
|
check_fail(libc::posix_spawn_file_actions_init(actions.as_mut_ptr()))?;
|
||||||
Ok(Self(actions))
|
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)?;
|
attr.set_sigdefault(&signals_to_default)?;
|
||||||
|
|
||||||
// Reset the sigmask.
|
// Reset the sigmask.
|
||||||
let mut sigmask = unsafe { std::mem::zeroed() };
|
let mut sigmask = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigemptyset(&mut sigmask) };
|
unsafe { libc::sigemptyset(sigmask.as_mut_ptr()) };
|
||||||
|
let mut sigmask = unsafe { sigmask.assume_init() };
|
||||||
blocked_signals_for_job(j, &mut sigmask);
|
blocked_signals_for_job(j, &mut sigmask);
|
||||||
attr.set_sigmask(&sigmask)?;
|
attr.set_sigmask(&sigmask)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
//! Functions for syntax highlighting.
|
//! Functions for syntax highlighting.
|
||||||
use crate::abbrs::{self, with_abbrs};
|
use crate::abbrs::{self, with_abbrs};
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
self, Argument, Ast, BlockStatement, BlockStatementHeaderVariant, BraceStatement,
|
self, Argument, BlockStatement, BlockStatementHeader, BraceStatement, DecoratedStatement,
|
||||||
DecoratedStatement, Keyword, List, Node, NodeVisitor, Redirection, Token, Type,
|
Keyword, Kind, Node, NodeVisitor, Redirection, Token, VariableAssignment,
|
||||||
VariableAssignment,
|
|
||||||
};
|
};
|
||||||
use crate::builtins::shared::builtin_exists;
|
use crate::builtins::shared::builtin_exists;
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
@@ -272,15 +271,16 @@ fn autosuggest_parse_command(
|
|||||||
buff: &wstr,
|
buff: &wstr,
|
||||||
ctx: &OperationContext<'_>,
|
ctx: &OperationContext<'_>,
|
||||||
) -> Option<(WString, WString)> {
|
) -> Option<(WString, WString)> {
|
||||||
let ast = Ast::parse(
|
let ast = ast::parse(
|
||||||
buff,
|
buff,
|
||||||
ParseTreeFlags::CONTINUE_AFTER_ERROR | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS,
|
ParseTreeFlags::CONTINUE_AFTER_ERROR | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Find the first statement.
|
// Find the first statement.
|
||||||
let jc = ast.top().as_job_list().unwrap().get(0)?;
|
let job_list: &ast::JobList = ast.top();
|
||||||
let first_statement = jc.job.statement.contents.as_decorated_statement()?;
|
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) {
|
if let Some(expanded_command) = statement_get_expanded_command(buff, first_statement, ctx) {
|
||||||
let mut arg = WString::new();
|
let mut arg = WString::new();
|
||||||
@@ -709,7 +709,7 @@ pub fn highlight(&mut self) -> ColorArray {
|
|||||||
| ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS
|
| ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS
|
||||||
| ParseTreeFlags::LEAVE_UNTERMINATED
|
| ParseTreeFlags::LEAVE_UNTERMINATED
|
||||||
| ParseTreeFlags::SHOW_EXTRA_SEMIS;
|
| 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());
|
self.visit_children(ast.top());
|
||||||
if self.ctx.check_cancel() {
|
if self.ctx.check_cancel() {
|
||||||
@@ -832,7 +832,7 @@ fn color_range(&mut self, range: SourceRange, color: HighlightSpec) {
|
|||||||
|
|
||||||
// Visit the children of a node.
|
// Visit the children of a node.
|
||||||
fn visit_children(&mut self, node: &dyn Node) {
|
fn visit_children(&mut self, node: &dyn Node) {
|
||||||
node.accept(self, false);
|
node.accept(self);
|
||||||
}
|
}
|
||||||
// AST visitor implementations.
|
// AST visitor implementations.
|
||||||
fn visit_keyword(&mut self, node: &dyn Keyword) {
|
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) {
|
fn visit_block_statement(&mut self, block: &BlockStatement) {
|
||||||
match &block.header {
|
match &block.header {
|
||||||
BlockStatementHeaderVariant::None => panic!(),
|
BlockStatementHeader::For(node) => self.visit(node),
|
||||||
BlockStatementHeaderVariant::ForHeader(node) => self.visit(node),
|
BlockStatementHeader::While(node) => self.visit(node),
|
||||||
BlockStatementHeaderVariant::WhileHeader(node) => self.visit(node),
|
BlockStatementHeader::Function(node) => self.visit(node),
|
||||||
BlockStatementHeaderVariant::FunctionHeader(node) => self.visit(node),
|
BlockStatementHeader::Begin(node) => self.visit(node),
|
||||||
BlockStatementHeaderVariant::BeginHeader(node) => self.visit(node),
|
|
||||||
}
|
}
|
||||||
self.visit(&block.args_or_redirs);
|
self.visit(&block.args_or_redirs);
|
||||||
let pending_variables_count = self.pending_variables.len();
|
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);
|
let var_name = fh.var_name.source(self.buff);
|
||||||
self.pending_variables.push(var_name);
|
self.pending_variables.push(var_name);
|
||||||
}
|
}
|
||||||
@@ -1114,17 +1113,13 @@ fn visit(&mut self, node: &'a dyn Node) {
|
|||||||
self.visit_token(token);
|
self.visit_token(token);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match node.typ() {
|
match node.kind() {
|
||||||
Type::argument => self.visit_argument(node.as_argument().unwrap(), false, true),
|
Kind::Argument(node) => self.visit_argument(node, false, true),
|
||||||
Type::redirection => self.visit_redirection(node.as_redirection().unwrap()),
|
Kind::Redirection(node) => self.visit_redirection(node),
|
||||||
Type::variable_assignment => {
|
Kind::VariableAssignment(node) => self.visit_variable_assignment(node),
|
||||||
self.visit_variable_assignment(node.as_variable_assignment().unwrap())
|
Kind::DecoratedStatement(node) => self.visit_decorated_statement(node),
|
||||||
}
|
Kind::BlockStatement(node) => self.visit_block_statement(node),
|
||||||
Type::decorated_statement => {
|
Kind::BraceStatement(node) => self.visit_brace_statement(node),
|
||||||
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()),
|
|
||||||
// Default implementation is to just visit children.
|
// Default implementation is to just visit children.
|
||||||
_ => self.visit_children(node),
|
_ => self.visit_children(node),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
ffi::CString,
|
ffi::CString,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, Read, Seek, SeekFrom, Write},
|
io::{BufRead, Read, Seek, SeekFrom, Write},
|
||||||
|
mem::MaybeUninit,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
os::{fd::AsRawFd, unix::fs::MetadataExt},
|
os::{fd::AsRawFd, unix::fs::MetadataExt},
|
||||||
@@ -41,13 +42,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bitflags::bitflags;
|
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 lru::LruCache;
|
||||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Ast, Node},
|
ast::{self, Kind, Node},
|
||||||
common::{
|
common::{
|
||||||
str2wcstring, unescape_string, valid_var_name, wcs2zstring, CancelChecker,
|
str2wcstring, unescape_string, valid_var_name, wcs2zstring, CancelChecker,
|
||||||
UnescapeStringStyle,
|
UnescapeStringStyle,
|
||||||
@@ -1358,8 +1359,15 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_time = SystemTime::now();
|
let (ok, start_time) = loop {
|
||||||
let retval = unsafe { flock(file.as_raw_fd(), lock_type) };
|
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 let Ok(duration) = start_time.elapsed() {
|
||||||
if duration > Duration::from_millis(250) {
|
if duration > Duration::from_millis(250) {
|
||||||
FLOG!(
|
FLOG!(
|
||||||
@@ -1372,7 +1380,7 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
|
|||||||
ABANDONED_LOCKING.store(true);
|
ABANDONED_LOCKING.store(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retval != -1
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unlock a history file.
|
/// 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.
|
// This warns for musl, but the warning is useless to us - there is nothing we can or should do.
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let seconds = seconds as libc::time_t;
|
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 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;
|
const max_tstamp_length: usize = 100;
|
||||||
let mut timestamp_str = [0_u8; max_tstamp_length];
|
let mut timestamp_str = [0_u8; max_tstamp_length];
|
||||||
use libc::strftime;
|
use libc::strftime;
|
||||||
@@ -1454,7 +1462,7 @@ fn format_history_record(
|
|||||||
&mut timestamp_str[0] as *mut u8 as *mut libc::c_char,
|
&mut timestamp_str[0] as *mut u8 as *mut libc::c_char,
|
||||||
max_tstamp_length,
|
max_tstamp_length,
|
||||||
show_time_format.as_ptr(),
|
show_time_format.as_ptr(),
|
||||||
×tamp,
|
timestamp.as_ptr(),
|
||||||
)
|
)
|
||||||
} != 0
|
} != 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1581,16 +1589,16 @@ pub fn add_pending_with_file_detection(
|
|||||||
|
|
||||||
// Find all arguments that look like they could be file paths.
|
// Find all arguments that look like they could be file paths.
|
||||||
let mut needs_sync_write = false;
|
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();
|
let mut potential_paths = Vec::new();
|
||||||
for node in ast.walk() {
|
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);
|
let potential_path = arg.source(s);
|
||||||
if string_could_be_path(potential_path) {
|
if string_could_be_path(potential_path) {
|
||||||
potential_paths.push(potential_path.to_owned());
|
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
|
// 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
|
// background file detection, because we won't be able to write it to our history file
|
||||||
// before we exit.
|
// before we exit.
|
||||||
|
|||||||
121
src/input.rs
121
src/input.rs
@@ -7,8 +7,8 @@
|
|||||||
use crate::future::IsSomeAnd;
|
use crate::future::IsSomeAnd;
|
||||||
use crate::global_safety::RelaxedAtomicBool;
|
use crate::global_safety::RelaxedAtomicBool;
|
||||||
use crate::input_common::{
|
use crate::input_common::{
|
||||||
CharEvent, CharInputStyle, ImplicitEvent, InputData, InputEventQueuer, ReadlineCmd,
|
match_key_event_to_key, CharEvent, CharInputStyle, ImplicitEvent, InputData, InputEventQueuer,
|
||||||
TerminalQuery, R_END_INPUT_FUNCTIONS,
|
KeyMatchQuality, ReadlineCmd, TerminalQuery, R_END_INPUT_FUNCTIONS,
|
||||||
};
|
};
|
||||||
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
|
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
|
||||||
use crate::proc::job_reap;
|
use crate::proc::job_reap;
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
use crate::wchar::prelude::*;
|
use crate::wchar::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::cell::RefMut;
|
use std::cell::RefMut;
|
||||||
|
use std::mem;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
Mutex, MutexGuard,
|
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.
|
/// 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.
|
/// 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!(
|
assert!(
|
||||||
self.idx <= self.peeked.len(),
|
self.idx <= self.peeked.len(),
|
||||||
"Index must not be larger than dequeued event count"
|
"Index must not be larger than dequeued event count"
|
||||||
);
|
);
|
||||||
// See if we had a timeout already.
|
// See if we had a timeout already.
|
||||||
if escaped && self.had_timeout {
|
if escaped && self.had_timeout {
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
// Grab a new event if we have exhausted what we have already peeked.
|
// Grab a new event if we have exhausted what we have already peeked.
|
||||||
// Use either readch or readch_timed, per our param.
|
// 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,
|
Some(evt) => evt,
|
||||||
None => {
|
None => {
|
||||||
self.had_timeout = true;
|
self.had_timeout = true;
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -529,7 +535,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
|||||||
Some(evt) => evt,
|
Some(evt) => evt,
|
||||||
None => {
|
None => {
|
||||||
self.had_timeout = true;
|
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.
|
// Now we have peeked far enough; check the event.
|
||||||
// If it matches the char, then increment the index.
|
// If it matches the char, then increment the index.
|
||||||
let evt = &self.peeked[self.idx];
|
let evt = &self.peeked[self.idx];
|
||||||
let Some(kevt) = evt.get_key() else {
|
let kevt = evt.get_key()?;
|
||||||
return false;
|
|
||||||
};
|
|
||||||
if kevt.seq == L!("\x1b") && key.modifiers == Modifiers::ALT {
|
if kevt.seq == L!("\x1b") && key.modifiers == Modifiers::ALT {
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
self.subidx = 0;
|
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);
|
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
|
||||||
}
|
}
|
||||||
if *style == KeyNameStyle::Plain {
|
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);
|
assert!(self.subidx == 0);
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
FLOG!(reader, "matched full key", key);
|
FLOG!(reader, "matched full key", key, "kind", key_match);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
let actual_seq = kevt.seq.as_char_slice();
|
let actual_seq = kevt.seq.as_char_slice();
|
||||||
if !actual_seq.is_empty() {
|
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()
|
actual_seq.len()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return true;
|
return Some(KeyMatchQuality::Legacy);
|
||||||
}
|
}
|
||||||
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
|
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
|
||||||
if self.subidx + 1 == actual_seq.len() {
|
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;
|
self.subidx = 0;
|
||||||
}
|
}
|
||||||
FLOG!(reader, format!("matched {key} against raw escape sequence"));
|
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.
|
/// 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`.
|
/// 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!(
|
assert!(
|
||||||
!seq.is_empty(),
|
!seq.is_empty(),
|
||||||
"Empty sequence passed to try_peek_sequence"
|
"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,
|
// 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.
|
// to distinguish between the actual escape key and an "alt"-modifier.
|
||||||
let escaped = *style != KeyNameStyle::Plain && prev == Key::from_raw(key::Escape);
|
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;
|
return false;
|
||||||
}
|
};
|
||||||
|
quality.push(spec);
|
||||||
prev = *key;
|
prev = *key;
|
||||||
}
|
}
|
||||||
if self.subidx != 0 {
|
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.
|
/// 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
|
/// Return none if nothing matches, or if we may have matched a longer sequence but it was
|
||||||
/// interrupted by a readline event.
|
/// interrupted by a readline event.
|
||||||
pub fn find_mapping(
|
pub fn find_mapping<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
vars: &dyn Environment,
|
vars: &dyn Environment,
|
||||||
ip: &InputMappingSet,
|
ip: &'a InputMappingSet,
|
||||||
) -> Option<InputMapping> {
|
) -> Option<InputMapping> {
|
||||||
let mut generic: Option<&InputMapping> = None;
|
|
||||||
let bind_mode = input_get_bind_mode(vars);
|
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 ml = ip.mapping_list.iter().chain(ip.preset_mapping_list.iter());
|
||||||
|
let mut quality = vec![];
|
||||||
for m in ml {
|
for m in ml {
|
||||||
if m.mode != bind_mode {
|
if m.mode != bind_mode {
|
||||||
continue;
|
continue;
|
||||||
@@ -673,24 +691,41 @@ pub fn find_mapping(
|
|||||||
|
|
||||||
// Defer generic mappings until the end.
|
// Defer generic mappings until the end.
|
||||||
if m.is_generic() {
|
if m.is_generic() {
|
||||||
if generic.is_none() {
|
if deferred.is_none() {
|
||||||
generic = Some(m);
|
deferred = Some(MatchedMapping {
|
||||||
|
mapping: m,
|
||||||
|
quality: vec![],
|
||||||
|
idx: self.idx,
|
||||||
|
subidx: self.subidx,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FLOG!(reader, "trying mapping", format!("{:?}", m));
|
// FLOG!(reader, "trying mapping", format!("{:?}", m));
|
||||||
if self.try_peek_sequence(&m.key_name_style, &m.seq) {
|
if self.try_peek_sequence(&m.key_name_style, &m.seq, &mut quality) {
|
||||||
// A binding for just escape should also be deferred
|
// // A binding for just escape should also be deferred
|
||||||
// so escape sequences take precedence.
|
// // so escape sequences take precedence.
|
||||||
if m.seq == vec![Key::from_raw(key::Escape)] {
|
let is_escape = m.seq == vec![Key::from_raw(key::Escape)];
|
||||||
if escape.is_none() {
|
let is_perfect_match = quality
|
||||||
escape = Some(m);
|
.iter()
|
||||||
}
|
.all(|key_match| *key_match == KeyMatchQuality::Exact);
|
||||||
} else {
|
if !is_escape && is_perfect_match {
|
||||||
return Some(m.clone());
|
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();
|
self.restart();
|
||||||
}
|
}
|
||||||
if self.char_sequence_interrupted() {
|
if self.char_sequence_interrupted() {
|
||||||
@@ -699,17 +734,13 @@ pub fn find_mapping(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if escape.is_some() {
|
deferred
|
||||||
// We need to reconsume the escape.
|
.map(|matched| {
|
||||||
self.next();
|
self.idx = matched.idx;
|
||||||
return escape.cloned();
|
self.subidx = matched.subidx;
|
||||||
}
|
matched.mapping
|
||||||
|
})
|
||||||
if generic.is_some() {
|
.cloned()
|
||||||
generic.cloned()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,7 +778,7 @@ pub fn read_char(&mut self) -> CharEvent {
|
|||||||
use ImplicitEvent::*;
|
use ImplicitEvent::*;
|
||||||
match evt {
|
match evt {
|
||||||
Key(_) => true,
|
Key(_) => true,
|
||||||
Implicit(Eof | CheckExit) => true,
|
Implicit(Eof) => true,
|
||||||
Readline(_) | Command(_) | Implicit(_) | QueryResponse(_) => false,
|
Readline(_) | Command(_) | Implicit(_) | QueryResponse(_) => false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
use crate::wutil::fish_wcstol;
|
use crate::wutil::fish_wcstol;
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
@@ -153,20 +154,23 @@ pub enum ReadlineCmd {
|
|||||||
pub struct KeyEvent {
|
pub struct KeyEvent {
|
||||||
pub key: Key,
|
pub key: Key,
|
||||||
pub shifted_codepoint: char,
|
pub shifted_codepoint: char,
|
||||||
|
pub base_layout_codepoint: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyEvent {
|
impl KeyEvent {
|
||||||
pub(crate) fn new(modifiers: Modifiers, codepoint: char) -> Self {
|
pub(crate) fn new(modifiers: Modifiers, codepoint: char) -> Self {
|
||||||
Self::from(Key::new(modifiers, codepoint))
|
Self::from(Key::new(modifiers, codepoint))
|
||||||
}
|
}
|
||||||
pub(crate) fn with_shifted_codepoint(
|
pub(crate) fn new_with(
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
codepoint: char,
|
codepoint: char,
|
||||||
shifted_codepoint: Option<char>,
|
shifted_key: Option<char>,
|
||||||
|
base_layout_key: Option<char>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key: Key::new(modifiers, codepoint),
|
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 {
|
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 {
|
impl From<Key> for KeyEvent {
|
||||||
fn from(key: Key) -> Self {
|
fn from(key: Key) -> Self {
|
||||||
Self {
|
Self::new_with(key.modifiers, key.codepoint, None, None)
|
||||||
key,
|
|
||||||
shifted_codepoint: '\0',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,24 +219,51 @@ fn apply_shift(mut key: Key, do_ascii: bool, shifted_codepoint: char) -> Option<
|
|||||||
Some(key)
|
Some(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<Key> for KeyEvent {
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
fn eq(&self, key: &Key) -> bool {
|
pub(crate) enum KeyMatchQuality {
|
||||||
if &self.key == key {
|
Legacy,
|
||||||
return true;
|
BaseLayoutModuloShift,
|
||||||
}
|
BaseLayout,
|
||||||
|
ModuloShift,
|
||||||
|
Exact,
|
||||||
|
}
|
||||||
|
|
||||||
let Some(shifted_evt) = apply_shift(self.key, false, self.shifted_codepoint) else {
|
impl FloggableDebug for KeyMatchQuality {}
|
||||||
return false;
|
|
||||||
};
|
pub(crate) fn match_key_event_to_key(event: &KeyEvent, key: &Key) -> Option<KeyMatchQuality> {
|
||||||
let Some(shifted_key) = apply_shift(*key, true, '\0') else {
|
if &event.key == key {
|
||||||
return false;
|
return Some(KeyMatchQuality::Exact);
|
||||||
};
|
|
||||||
shifted_evt == shifted_key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
#[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 none = Modifiers::default();
|
||||||
let shift = Modifiers::SHIFT;
|
let shift = Modifiers::SHIFT;
|
||||||
let ctrl = Modifiers::CTRL;
|
let ctrl = Modifiers::CTRL;
|
||||||
@@ -245,41 +273,69 @@ fn test_key_event_eq() {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(KeyEvent::new(none, 'a'), Key::new(none, 'a'));
|
let exact = KeyMatchQuality::Exact;
|
||||||
assert_ne!(KeyEvent::new(none, 'a'), Key::new(none, 'A'));
|
let modulo_shift = KeyMatchQuality::ModuloShift;
|
||||||
assert_eq!(KeyEvent::new(shift, 'a'), Key::new(shift, 'a'));
|
let base_layout = KeyMatchQuality::BaseLayout;
|
||||||
assert_ne!(KeyEvent::new(shift, 'a'), Key::new(none, 'A'));
|
let base_layout_modulo_shift = KeyMatchQuality::BaseLayoutModuloShift;
|
||||||
assert_ne!(KeyEvent::new(shift, 'ä'), Key::new(none, 'Ä'));
|
|
||||||
|
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",
|
// 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.
|
// but not "shift-a" events - those should send a shifted key.
|
||||||
assert_eq!(KeyEvent::new(none, 'A'), Key::new(shift, 'a'));
|
validate!(
|
||||||
assert_ne!(KeyEvent::new(none, 'A'), Key::new(shift, 'A'));
|
KeyEvent::new(none, 'A'),
|
||||||
assert_eq!(KeyEvent::new(none, 'Ä'), Key::new(none, 'Ä'));
|
Key::new(shift, 'a'),
|
||||||
assert_ne!(KeyEvent::new(none, 'Ä'), Key::new(shift, 'ä'));
|
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
|
// 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
|
// the shifted key in the canonical notation, because the unshifted one may depend on the
|
||||||
// keyboard layout.
|
// keyboard layout.
|
||||||
let ctrl_shift_equals = KeyEvent::with_shifted_codepoint(ctrl_shift, '=', Some('+'));
|
let ctrl_shift_equals = KeyEvent::new_with(ctrl_shift, '=', Some('+'), None);
|
||||||
assert_eq!(ctrl_shift_equals, Key::new(ctrl_shift, '='));
|
validate!(ctrl_shift_equals, Key::new(ctrl_shift, '='), Some(exact));
|
||||||
assert_eq!(ctrl_shift_equals, Key::new(ctrl, '+')); // canonical notation
|
validate!(ctrl_shift_equals, Key::new(ctrl, '+'), Some(modulo_shift)); // canonical notation
|
||||||
assert_ne!(ctrl_shift_equals, Key::new(ctrl_shift, '+'));
|
validate!(ctrl_shift_equals, Key::new(ctrl_shift, '+'), None);
|
||||||
assert_ne!(ctrl_shift_equals, Key::new(ctrl, '='));
|
validate!(ctrl_shift_equals, Key::new(ctrl, '='), None);
|
||||||
|
|
||||||
// A event like capslock-shift-ä may or may not include a shifted codepoint.
|
// A event like capslock-shift-ä may or may not include a shifted codepoint.
|
||||||
//
|
//
|
||||||
// Without a shifted codepoint, we cannot easily match ctrl-Ä.
|
// Without a shifted codepoint, we cannot easily match ctrl-Ä.
|
||||||
let caps_ctrl_shift_ä = KeyEvent::new(ctrl_shift, 'ä');
|
let caps_ctrl_shift_ä = KeyEvent::new(ctrl_shift, 'ä');
|
||||||
assert_eq!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä')); // canonical notation
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä'), Some(exact)); // canonical notation
|
||||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'));
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'), None);
|
||||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä')); // can't match without shifted key
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä'), None); // can't match without shifted key
|
||||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'));
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'), None);
|
||||||
// With a shifted codepoint, we can match the alternative notation too.
|
// With a shifted codepoint, we can match the alternative notation too.
|
||||||
let caps_ctrl_shift_ä = KeyEvent::with_shifted_codepoint(ctrl_shift, 'ä', Some('Ä'));
|
let caps_ctrl_shift_ä = KeyEvent::new_with(ctrl_shift, 'ä', Some('Ä'), None);
|
||||||
assert_eq!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä')); // canonical notation
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'ä'), Some(exact)); // canonical notation
|
||||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'));
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'ä'), None);
|
||||||
assert_eq!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä')); // matched via shifted key
|
validate!(caps_ctrl_shift_ä, Key::new(ctrl, 'Ä'), Some(modulo_shift)); // matched via shifted key
|
||||||
assert_ne!(caps_ctrl_shift_ä, Key::new(ctrl_shift, 'Ä'));
|
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.
|
/// 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 seq = WString::new();
|
||||||
let mut key = key_with_escape;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
assert!(key.map_or(true, |key| key.codepoint != key::Invalid));
|
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];
|
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!(
|
FLOG!(
|
||||||
reader,
|
reader,
|
||||||
@@ -932,6 +992,7 @@ fn try_readch(&mut self, blocking: bool) -> Option<CharEvent> {
|
|||||||
);
|
);
|
||||||
let ok = stop_query(self.blocking_query());
|
let ok = stop_query(self.blocking_query());
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
|
self.get_input_data_mut().queue.clear();
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -959,17 +1020,14 @@ fn parse_escape_sequence(
|
|||||||
assert!(buffer.len() <= 2);
|
assert!(buffer.len() <= 2);
|
||||||
let recursive_invocation = buffer.len() == 2;
|
let recursive_invocation = buffer.len() == 2;
|
||||||
let Some(next) = self.try_readb(buffer) else {
|
let Some(next) = self.try_readb(buffer) else {
|
||||||
if !self.paste_is_buffering() {
|
return Some(KeyEvent::from_raw(key::Escape));
|
||||||
return Some(KeyEvent::from_raw(key::Escape));
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
};
|
};
|
||||||
let invalid = KeyEvent::from_raw(key::Invalid);
|
let invalid = KeyEvent::from_raw(key::Invalid);
|
||||||
if recursive_invocation && next == b'\x1b' {
|
if recursive_invocation && next == b'\x1b' {
|
||||||
return Some(
|
return Some(
|
||||||
match self.parse_escape_sequence(buffer, have_escape_prefix) {
|
match self.parse_escape_sequence(buffer, have_escape_prefix) {
|
||||||
Some(mut nested_sequence) => {
|
Some(mut nested_sequence) => {
|
||||||
if nested_sequence == invalid.key {
|
if nested_sequence.key == invalid.key {
|
||||||
return Some(KeyEvent::from_raw(key::Escape));
|
return Some(KeyEvent::from_raw(key::Escape));
|
||||||
}
|
}
|
||||||
nested_sequence.modifiers.alt = true;
|
nested_sequence.modifiers.alt = true;
|
||||||
@@ -1045,7 +1103,7 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
|||||||
return None;
|
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 mask = params[1][0].saturating_sub(1);
|
||||||
let (mut modifiers, caps_lock) = parse_mask(mask);
|
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.
|
// match the "shift-ä" event, as suggested in the kitty issue.
|
||||||
if caps_lock
|
if caps_lock
|
||||||
&& modifiers == Modifiers::SHIFT
|
&& modifiers == Modifiers::SHIFT
|
||||||
&& !codepoint.to_uppercase().eq(Some(codepoint).into_iter())
|
&& !key.to_uppercase().eq(Some(key).into_iter())
|
||||||
{
|
{
|
||||||
modifiers.shift = false;
|
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 {
|
let key = match c {
|
||||||
b'$' => {
|
b'$' => {
|
||||||
@@ -1089,13 +1148,13 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b'A' => masked_key(key::Up, None),
|
b'A' => masked_key(key::Up),
|
||||||
b'B' => masked_key(key::Down, None),
|
b'B' => masked_key(key::Down),
|
||||||
b'C' => masked_key(key::Right, None),
|
b'C' => masked_key(key::Right),
|
||||||
b'D' => masked_key(key::Left, None),
|
b'D' => masked_key(key::Left),
|
||||||
b'E' => masked_key('5', None), // Numeric keypad
|
b'E' => masked_key('5'), // Numeric keypad
|
||||||
b'F' => masked_key(key::End, None), // PC/xterm style
|
b'F' => masked_key(key::End), // PC/xterm style
|
||||||
b'H' => masked_key(key::Home, None), // PC/xterm style
|
b'H' => masked_key(key::Home), // PC/xterm style
|
||||||
b'M' | b'm' => {
|
b'M' | b'm' => {
|
||||||
self.disable_mouse_tracking();
|
self.disable_mouse_tracking();
|
||||||
// Generic X10 or modified VT200 sequence, or extended (SGR/1006) mouse
|
// 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;
|
return None;
|
||||||
}
|
}
|
||||||
b'P' => masked_key(function_key(1), None),
|
b'P' => masked_key(function_key(1)),
|
||||||
b'Q' => masked_key(function_key(2), None),
|
b'Q' => masked_key(function_key(2)),
|
||||||
b'R' => {
|
b'R' => {
|
||||||
let Some(y) = params[0][0]
|
let Some(y) = params[0][0]
|
||||||
.checked_sub(1)
|
.checked_sub(1)
|
||||||
@@ -1173,35 +1232,33 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
|||||||
));
|
));
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
b'S' => masked_key(function_key(4), None),
|
b'S' => masked_key(function_key(4)),
|
||||||
b'~' => match params[0][0] {
|
b'~' => match params[0][0] {
|
||||||
1 => masked_key(key::Home, None), // VT220/tmux style
|
1 => masked_key(key::Home), // VT220/tmux style
|
||||||
2 => masked_key(key::Insert, None),
|
2 => masked_key(key::Insert),
|
||||||
3 => masked_key(key::Delete, None),
|
3 => masked_key(key::Delete),
|
||||||
4 => masked_key(key::End, None), // VT220/tmux style
|
4 => masked_key(key::End), // VT220/tmux style
|
||||||
5 => masked_key(key::PageUp, None),
|
5 => masked_key(key::PageUp),
|
||||||
6 => masked_key(key::PageDown, None),
|
6 => masked_key(key::PageDown),
|
||||||
7 => masked_key(key::Home, None), // rxvt style
|
7 => masked_key(key::Home), // rxvt style
|
||||||
8 => masked_key(key::End, None), // rxvt style
|
8 => masked_key(key::End), // rxvt style
|
||||||
11..=15 => masked_key(
|
11..=15 => masked_key(
|
||||||
char::from_u32(u32::from(function_key(1)) + params[0][0] - 11).unwrap(),
|
char::from_u32(u32::from(function_key(1)) + params[0][0] - 11).unwrap(),
|
||||||
None,
|
|
||||||
),
|
),
|
||||||
17..=21 => masked_key(
|
17..=21 => masked_key(
|
||||||
char::from_u32(u32::from(function_key(6)) + params[0][0] - 17).unwrap(),
|
char::from_u32(u32::from(function_key(6)) + params[0][0] - 17).unwrap(),
|
||||||
None,
|
|
||||||
),
|
),
|
||||||
23 | 24 => masked_key(
|
23 | 24 => masked_key(
|
||||||
char::from_u32(u32::from(function_key(11)) + params[0][0] - 23).unwrap(),
|
char::from_u32(u32::from(function_key(11)) + params[0][0] - 23).unwrap(),
|
||||||
None,
|
|
||||||
),
|
),
|
||||||
25 | 26 => KeyEvent::from(shift(
|
25 | 26 => KeyEvent::from(shift(
|
||||||
char::from_u32(u32::from(function_key(3)) + params[0][0] - 25).unwrap(),
|
char::from_u32(u32::from(function_key(3)) + params[0][0] - 25).unwrap(),
|
||||||
)), // rxvt style
|
)), // rxvt style
|
||||||
27 => {
|
27 => {
|
||||||
let key =
|
let Some(key) = char::from_u32(params[2][0]) else {
|
||||||
canonicalize_keyed_control_char(char::from_u32(params[2][0]).unwrap());
|
return invalid_sequence(buffer);
|
||||||
masked_key(key, None)
|
};
|
||||||
|
masked_key(canonicalize_keyed_control_char(key))
|
||||||
}
|
}
|
||||||
28 | 29 => KeyEvent::from(shift(
|
28 | 29 => KeyEvent::from(shift(
|
||||||
char::from_u32(u32::from(function_key(5)) + params[0][0] - 28).unwrap(),
|
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,
|
57424 => key::End,
|
||||||
57425 => key::Insert,
|
57425 => key::Insert,
|
||||||
57426 => key::Delete,
|
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,
|
key,
|
||||||
Some(canonicalize_keyed_control_char(
|
Some(canonicalize_keyed_control_char(shifted_key)),
|
||||||
char::from_u32(params[0][1]).unwrap(),
|
Some(base_layout_key),
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
b'Z' => KeyEvent::from(shift(key::Tab)),
|
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
|
// 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
|
// our fd before the timeout. Use pselect to block all signals; we will handle signals
|
||||||
// before the next call to readch().
|
// before the next call to readch().
|
||||||
let mut sigs: libc::sigset_t = unsafe { std::mem::zeroed() };
|
let mut sigs = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigfillset(&mut sigs) };
|
unsafe { libc::sigfillset(sigs.as_mut_ptr()) };
|
||||||
|
|
||||||
// pselect expects timeouts in nanoseconds.
|
// pselect expects timeouts in nanoseconds.
|
||||||
const NSEC_PER_MSEC: u64 = 1000 * 1000;
|
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.
|
// 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();
|
let in_fd = self.get_in_fd();
|
||||||
unsafe {
|
unsafe {
|
||||||
libc::FD_ZERO(&mut fdset);
|
libc::FD_ZERO(fdset.as_mut_ptr());
|
||||||
libc::FD_SET(in_fd, &mut fdset);
|
libc::FD_SET(in_fd, fdset.as_mut_ptr());
|
||||||
};
|
};
|
||||||
let res = unsafe {
|
let res = unsafe {
|
||||||
libc::pselect(
|
libc::pselect(
|
||||||
in_fd + 1,
|
in_fd + 1,
|
||||||
&mut fdset,
|
fdset.as_mut_ptr(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
&timeout,
|
&timeout,
|
||||||
&sigs,
|
sigs.as_ptr(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prevent signal starvation on WSL causing the `torn_escapes.py` test to fail
|
// Prevent signal starvation on WSL causing the `torn_escapes.py` test to fail
|
||||||
if is_windows_subsystem_for_linux(WSL::V1) {
|
if is_windows_subsystem_for_linux(WSL::V1) {
|
||||||
// Merely querying the current thread's sigmask is sufficient to deliver a pending signal
|
// 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 {
|
if res > 0 {
|
||||||
return Some(self.readch());
|
return Some(self.readch());
|
||||||
@@ -1632,7 +1699,7 @@ fn enqueue_interrupt_key(&mut self) {
|
|||||||
reader,
|
reader,
|
||||||
"Received interrupt, giving up on waiting for terminal response"
|
"Received interrupt, giving up on waiting for terminal response"
|
||||||
);
|
);
|
||||||
self.push_back(interrupt_evt);
|
self.get_input_data_mut().queue.clear();
|
||||||
} else {
|
} else {
|
||||||
self.push_front(interrupt_evt);
|
self.push_front(interrupt_evt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
common::{escape_string, EscapeFlags, EscapeStringStyle},
|
common::{escape_string, EscapeFlags, EscapeStringStyle},
|
||||||
fallback::fish_wcwidth,
|
fallback::fish_wcwidth,
|
||||||
flog::FloggableDebug,
|
flog::FloggableDebug,
|
||||||
|
future_feature_flags::{test as feature_test, FeatureFlag},
|
||||||
reader::TERMINAL_MODE_ON_STARTUP,
|
reader::TERMINAL_MODE_ON_STARTUP,
|
||||||
wchar::{decode_byte_from_char, prelude::*},
|
wchar::{decode_byte_from_char, prelude::*},
|
||||||
wutil::{fish_is_pua, fish_wcstoul},
|
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
|
/// Return true if the character must be escaped when used in the sequence of chars to be bound in
|
||||||
/// a `bind` command.
|
/// a `bind` command.
|
||||||
fn must_escape(is_first_in_token: bool, c: char) -> bool {
|
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) {
|
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 <locale.h>
|
||||||
#include <paths.h>
|
#include <paths.h> // _PATH_BSHELL
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h> // MB_CUR_MAX
|
||||||
|
#include <sys/mount.h> // MNT_LOCAL
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <unistd.h>
|
#include <sys/statvfs.h> // ST_LOCAL
|
||||||
|
#include <unistd.h> // _CS_PATH, _PC_CASE_SENSITIVE
|
||||||
#define UNUSED(x) (void)(x)
|
|
||||||
|
|
||||||
size_t C_MB_CUR_MAX() { return MB_CUR_MAX; }
|
size_t C_MB_CUR_MAX() { return MB_CUR_MAX; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,58 +4,35 @@
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::ptr;
|
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.
|
/// 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.
|
/// 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.
|
/// This is used for building null-terminated arrays of null-terminated strings.
|
||||||
pub struct NullTerminatedArray<'p, T: NulTerminatedString + ?Sized> {
|
struct NullTerminatedArray<'p> {
|
||||||
pointers: Box<[*const T::CharType]>,
|
pointers: Box<[*const c_char]>,
|
||||||
_phantom: PhantomData<&'p T>,
|
_phantom: PhantomData<&'p CStr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p, Str: NulTerminatedString + ?Sized> AsNullTerminatedArray for NullTerminatedArray<'p, Str> {
|
impl<'p> NullTerminatedArray<'p> {
|
||||||
type CharType = Str::CharType;
|
|
||||||
/// Return the list of pointers, appropriate for envp or argv.
|
/// 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
|
/// Note this returns a mutable array of const strings. The caller may rearrange the strings but
|
||||||
/// not modify their contents.
|
/// not modify their contents.
|
||||||
/// We freely give out mutable pointers even though we are not mut; this is because most of the uses
|
/// 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
|
/// 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.
|
/// away the const at the call site.
|
||||||
fn get(&self) -> *mut *const Str::CharType {
|
pub fn get(&self) -> *mut *const c_char {
|
||||||
assert!(
|
assert!(
|
||||||
!self.pointers.is_empty() && self.pointers.last().unwrap().is_null(),
|
!self.pointers.is_empty() && self.pointers.last().unwrap().is_null(),
|
||||||
"Should have null terminator"
|
"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".
|
/// Construct from a list of "strings".
|
||||||
/// This holds pointers into the 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();
|
let mut pointers = Vec::new();
|
||||||
pointers.reserve_exact(1 + strs.len());
|
pointers.reserve_exact(1 + strs.len());
|
||||||
for s in strs {
|
for s in strs {
|
||||||
pointers.push(s.as_ref().c_str());
|
pointers.push(s.as_ref().as_ptr());
|
||||||
}
|
}
|
||||||
pointers.push(ptr::null());
|
pointers.push(ptr::null());
|
||||||
NullTerminatedArray {
|
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.
|
/// Safety: NullTerminatedArray is Send and Sync because it's immutable.
|
||||||
unsafe impl<T: NulTerminatedString + ?Sized + Send> Send for NullTerminatedArray<'_, T> {}
|
unsafe impl Send for NullTerminatedArray<'_> {}
|
||||||
unsafe impl<T: NulTerminatedString + ?Sized + Sync> Sync for NullTerminatedArray<'_, T> {}
|
unsafe impl Sync for NullTerminatedArray<'_> {}
|
||||||
|
|
||||||
/// A container which exposes a null-terminated array of pointers to strings that it owns.
|
/// 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
|
/// 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 {
|
pub struct OwningNullTerminatedArray {
|
||||||
// Note that null_terminated_array holds pointers into our boxed strings.
|
// Note that null_terminated_array holds pointers into our boxed strings.
|
||||||
// The 'static is a lie.
|
// The 'static is a lie.
|
||||||
_strings: Pin<Box<[CString]>>,
|
strings: Pin<Box<[CString]>>,
|
||||||
null_terminated_array: NullTerminatedArray<'static, CStr>,
|
null_terminated_array: NullTerminatedArray<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert_send::<OwningNullTerminatedArray>();
|
const _: () = assert_send::<OwningNullTerminatedArray>();
|
||||||
const _: () = assert_sync::<OwningNullTerminatedArray>();
|
const _: () = assert_sync::<OwningNullTerminatedArray>();
|
||||||
|
|
||||||
impl AsNullTerminatedArray for OwningNullTerminatedArray {
|
impl OwningNullTerminatedArray {
|
||||||
type CharType = c_char;
|
pub fn get(&self) -> *mut *const c_char {
|
||||||
/// Cover over null_terminated_array.get().
|
|
||||||
fn get(&self) -> *mut *const c_char {
|
|
||||||
self.null_terminated_array.get()
|
self.null_terminated_array.get()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl OwningNullTerminatedArray {
|
|
||||||
pub fn get_mut(&self) -> *mut *mut c_char {
|
pub fn get_mut(&self) -> *mut *mut c_char {
|
||||||
self.get().cast()
|
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.
|
// Safety: we're pinning the strings, so they won't move.
|
||||||
let string_slice: &'static [CString] = unsafe { std::mem::transmute(&*strings) };
|
let string_slice: &'static [CString] = unsafe { std::mem::transmute(&*strings) };
|
||||||
OwningNullTerminatedArray {
|
OwningNullTerminatedArray {
|
||||||
_strings: Pin::from(strings),
|
strings: Pin::from(strings),
|
||||||
null_terminated_array: NullTerminatedArray::new(string_slice),
|
null_terminated_array: NullTerminatedArray::new(string_slice),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn len(&self) -> usize {
|
||||||
|
self.strings.len()
|
||||||
/// 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 {
|
pub fn iter(&self) -> impl Iterator<Item = &CString> {
|
||||||
let mut len = 0;
|
self.strings.iter()
|
||||||
// Safety: caller must ensure that arr is null-terminated.
|
|
||||||
unsafe {
|
|
||||||
while !arr.read().is_null() {
|
|
||||||
arr = arr.offset(1);
|
|
||||||
len += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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]
|
#[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!(CStr::from_ptr(*ptr.offset(1)).to_str().unwrap(), "bar");
|
||||||
assert_eq!(*ptr.offset(2), ptr::null());
|
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.
|
/// A range of source code.
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
|
||||||
pub struct SourceRange {
|
pub struct SourceRange {
|
||||||
pub start: u32,
|
pub start: u32,
|
||||||
pub length: u32,
|
pub length: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SourceRange {
|
|
||||||
fn default() -> Self {
|
|
||||||
SourceRange {
|
|
||||||
start: 0,
|
|
||||||
length: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceRange {
|
impl SourceRange {
|
||||||
pub fn as_usize(&self) -> std::ops::Range<usize> {
|
pub fn as_usize(self) -> std::ops::Range<usize> {
|
||||||
(*self).into()
|
self.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,20 +147,20 @@ pub fn new(start: usize, length: usize) -> Self {
|
|||||||
length: length.try_into().unwrap(),
|
length: length.try_into().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start(&self) -> usize {
|
pub fn start(self) -> usize {
|
||||||
self.start.try_into().unwrap()
|
self.start.try_into().unwrap()
|
||||||
}
|
}
|
||||||
pub fn length(&self) -> usize {
|
pub fn length(self) -> usize {
|
||||||
self.length.try_into().unwrap()
|
self.length.try_into().unwrap()
|
||||||
}
|
}
|
||||||
pub fn end(&self) -> usize {
|
pub fn end(self) -> usize {
|
||||||
self.start
|
self.start
|
||||||
.checked_add(self.length)
|
.checked_add(self.length)
|
||||||
.expect("Overflow")
|
.expect("Overflow")
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
pub fn combine(&self, other: Self) -> Self {
|
pub fn combine(self, other: Self) -> Self {
|
||||||
let start = std::cmp::min(self.start, other.start);
|
let start = std::cmp::min(self.start, other.start);
|
||||||
SourceRange {
|
SourceRange {
|
||||||
start,
|
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.
|
// 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()
|
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.).
|
//! Provides the "linkage" between an ast and actual execution structures (job_t, etc.).
|
||||||
|
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
self, unescape_keyword, BlockStatementHeaderVariant, Keyword, Leaf, List, Node,
|
self, unescape_keyword, BlockStatementHeader, Keyword, Leaf, Node, Statement, Token,
|
||||||
StatementVariant, Token,
|
|
||||||
};
|
};
|
||||||
use crate::builtins;
|
use crate::builtins;
|
||||||
use crate::builtins::shared::{
|
use crate::builtins::shared::{
|
||||||
@@ -140,13 +139,9 @@ pub fn eval_node(
|
|||||||
node: &'a dyn Node,
|
node: &'a dyn Node,
|
||||||
associated_block: Option<BlockId>,
|
associated_block: Option<BlockId>,
|
||||||
) -> EndExecutionReason {
|
) -> EndExecutionReason {
|
||||||
match node.typ() {
|
match node.kind() {
|
||||||
ast::Type::statement => {
|
ast::Kind::Statement(node) => self.eval_statement(ctx, node, associated_block),
|
||||||
self.eval_statement(ctx, node.as_statement().unwrap(), associated_block)
|
ast::Kind::JobList(node) => self.eval_job_list(ctx, node, associated_block.unwrap()),
|
||||||
}
|
|
||||||
ast::Type::job_list => {
|
|
||||||
self.eval_job_list(ctx, node.as_job_list().unwrap(), associated_block.unwrap())
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,22 +155,14 @@ fn eval_statement(
|
|||||||
associated_block: Option<BlockId>,
|
associated_block: Option<BlockId>,
|
||||||
) -> EndExecutionReason {
|
) -> EndExecutionReason {
|
||||||
// Note we only expect block-style statements here. No not statements.
|
// Note we only expect block-style statements here. No not statements.
|
||||||
match &statement.contents {
|
match &statement {
|
||||||
StatementVariant::BlockStatement(block) => {
|
Statement::Block(block) => self.run_block_statement(ctx, block, associated_block),
|
||||||
self.run_block_statement(ctx, block, associated_block)
|
Statement::Brace(brace_statement) => {
|
||||||
}
|
|
||||||
StatementVariant::BraceStatement(brace_statement) => {
|
|
||||||
self.run_begin_statement(ctx, &brace_statement.jobs)
|
self.run_begin_statement(ctx, &brace_statement.jobs)
|
||||||
}
|
}
|
||||||
StatementVariant::IfStatement(ifstat) => {
|
Statement::If(ifstat) => self.run_if_statement(ctx, ifstat, associated_block),
|
||||||
self.run_if_statement(ctx, ifstat, associated_block)
|
Statement::Switch(switchstat) => self.run_switch_statement(ctx, switchstat),
|
||||||
}
|
Statement::Decorated(_) | Statement::Not(_) => panic!(),
|
||||||
StatementVariant::SwitchStatement(switchstat) => {
|
|
||||||
self.run_switch_statement(ctx, switchstat)
|
|
||||||
}
|
|
||||||
StatementVariant::DecoratedStatement(_)
|
|
||||||
| StatementVariant::NotStatement(_)
|
|
||||||
| StatementVariant::None => 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.
|
// Helper to return if a statement is infinitely recursive in this function.
|
||||||
let statement_recurses = |stat: &'b ast::Statement| -> Option<&'b ast::DecoratedStatement> {
|
let statement_recurses = |stat: &'b ast::Statement| -> Option<&'b ast::DecoratedStatement> {
|
||||||
// Ignore non-decorated statements like `if`, etc.
|
// Ignore non-decorated statements like `if`, etc.
|
||||||
let StatementVariant::DecoratedStatement(dc) = &stat.contents else {
|
let Statement::Decorated(dc) = &stat else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -560,18 +547,16 @@ fn job_is_simple_block(&self, job: &ast::JobPipeline) -> bool {
|
|||||||
let no_redirs =
|
let no_redirs =
|
||||||
|list: &ast::ArgumentOrRedirectionList| !list.iter().any(|val| val.is_redirection());
|
|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
|
// Check if we're a block statement with redirections.
|
||||||
// type safety (in case we add more specific statement types).
|
match &job.statement {
|
||||||
match &job.statement.contents {
|
Statement::Block(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||||
StatementVariant::BlockStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
Statement::Brace(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||||
StatementVariant::BraceStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
Statement::Switch(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||||
StatementVariant::SwitchStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
Statement::If(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||||
StatementVariant::IfStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
Statement::Not(_) | Statement::Decorated(_) => {
|
||||||
StatementVariant::NotStatement(_) | StatementVariant::DecoratedStatement(_) => {
|
|
||||||
// not block statements
|
// not block statements
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
StatementVariant::None => panic!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,9 +649,6 @@ fn populate_job_process(
|
|||||||
statement: &ast::Statement,
|
statement: &ast::Statement,
|
||||||
variable_assignments: &ast::VariableAssignmentList,
|
variable_assignments: &ast::VariableAssignmentList,
|
||||||
) -> EndExecutionReason {
|
) -> EndExecutionReason {
|
||||||
// Get the "specific statement" which is boolean / block / if / switch / decorated.
|
|
||||||
let specific_statement = &statement.contents;
|
|
||||||
|
|
||||||
let mut block = None;
|
let mut block = None;
|
||||||
let result =
|
let result =
|
||||||
self.apply_variable_assignments(ctx, Some(proc), variable_assignments, &mut block);
|
self.apply_variable_assignments(ctx, Some(proc), variable_assignments, &mut block);
|
||||||
@@ -679,20 +661,16 @@ fn populate_job_process(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &specific_statement {
|
match &statement {
|
||||||
StatementVariant::NotStatement(not_statement) => {
|
Statement::Not(not_statement) => {
|
||||||
self.populate_not_process(ctx, job, proc, not_statement)
|
self.populate_not_process(ctx, job, proc, not_statement)
|
||||||
}
|
}
|
||||||
StatementVariant::BlockStatement(_)
|
Statement::Block(_) | Statement::Brace(_) | Statement::If(_) | Statement::Switch(_) => {
|
||||||
| StatementVariant::BraceStatement(_)
|
self.populate_block_process(ctx, proc, statement)
|
||||||
| StatementVariant::IfStatement(_)
|
|
||||||
| StatementVariant::SwitchStatement(_) => {
|
|
||||||
self.populate_block_process(ctx, proc, statement, specific_statement)
|
|
||||||
}
|
}
|
||||||
StatementVariant::DecoratedStatement(decorated_statement) => {
|
Statement::Decorated(decorated_statement) => {
|
||||||
self.populate_plain_process(ctx, proc, decorated_statement)
|
self.populate_plain_process(ctx, proc, decorated_statement)
|
||||||
}
|
}
|
||||||
StatementVariant::None => panic!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,17 +819,16 @@ fn populate_block_process(
|
|||||||
ctx: &OperationContext<'_>,
|
ctx: &OperationContext<'_>,
|
||||||
proc: &mut Process,
|
proc: &mut Process,
|
||||||
statement: &ast::Statement,
|
statement: &ast::Statement,
|
||||||
specific_statement: &ast::StatementVariant,
|
|
||||||
) -> EndExecutionReason {
|
) -> 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.
|
// us when it's time to execute them.
|
||||||
// Get the argument or redirections list.
|
// Get the argument or redirections list.
|
||||||
// TODO: args_or_redirs should be available without resolving the statement type.
|
// TODO: args_or_redirs should be available without resolving the statement type.
|
||||||
let args_or_redirs = match specific_statement {
|
let args_or_redirs = match statement {
|
||||||
StatementVariant::BlockStatement(block_statement) => &block_statement.args_or_redirs,
|
Statement::Block(block_statement) => &block_statement.args_or_redirs,
|
||||||
StatementVariant::BraceStatement(brace_statement) => &brace_statement.args_or_redirs,
|
Statement::Brace(brace_statement) => &brace_statement.args_or_redirs,
|
||||||
StatementVariant::IfStatement(if_statement) => &if_statement.args_or_redirs,
|
Statement::If(if_statement) => &if_statement.args_or_redirs,
|
||||||
StatementVariant::SwitchStatement(switch_statement) => &switch_statement.args_or_redirs,
|
Statement::Switch(switch_statement) => &switch_statement.args_or_redirs,
|
||||||
_ => panic!("Unexpected block node type"),
|
_ => panic!("Unexpected block node type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -876,17 +853,12 @@ fn run_block_statement(
|
|||||||
let bh = &statement.header;
|
let bh = &statement.header;
|
||||||
let contents = &statement.jobs;
|
let contents = &statement.jobs;
|
||||||
match bh {
|
match bh {
|
||||||
BlockStatementHeaderVariant::ForHeader(fh) => self.run_for_statement(ctx, fh, contents),
|
BlockStatementHeader::For(fh) => self.run_for_statement(ctx, fh, contents),
|
||||||
BlockStatementHeaderVariant::WhileHeader(wh) => {
|
BlockStatementHeader::While(wh) => {
|
||||||
self.run_while_statement(ctx, wh, contents, associated_block)
|
self.run_while_statement(ctx, wh, contents, associated_block)
|
||||||
}
|
}
|
||||||
BlockStatementHeaderVariant::FunctionHeader(fh) => {
|
BlockStatementHeader::Function(fh) => self.run_function_statement(ctx, statement, fh),
|
||||||
self.run_function_statement(ctx, statement, fh)
|
BlockStatementHeader::Begin(_bh) => self.run_begin_statement(ctx, contents),
|
||||||
}
|
|
||||||
BlockStatementHeaderVariant::BeginHeader(_bh) => {
|
|
||||||
self.run_begin_statement(ctx, contents)
|
|
||||||
}
|
|
||||||
BlockStatementHeaderVariant::None => panic!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1594,29 +1566,23 @@ fn run_1_job(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let specific_statement = &job_node.statement.contents;
|
let statement = &job_node.statement;
|
||||||
assert!(specific_statement_type_is_redirectable_block(
|
assert!(statement_is_redirectable_block(statement));
|
||||||
specific_statement
|
|
||||||
));
|
|
||||||
if result == EndExecutionReason::ok {
|
if result == EndExecutionReason::ok {
|
||||||
result = match &specific_statement {
|
result = match statement {
|
||||||
StatementVariant::BlockStatement(block_statement) => {
|
Statement::Block(block_statement) => {
|
||||||
self.run_block_statement(ctx, block_statement, associated_block)
|
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)
|
self.run_begin_statement(ctx, &brace_statement.jobs)
|
||||||
}
|
}
|
||||||
StatementVariant::IfStatement(ifstmt) => {
|
Statement::If(ifstmt) => self.run_if_statement(ctx, ifstmt, associated_block),
|
||||||
self.run_if_statement(ctx, ifstmt, associated_block)
|
Statement::Switch(switchstmt) => self.run_switch_statement(ctx, switchstmt),
|
||||||
}
|
|
||||||
StatementVariant::SwitchStatement(switchstmt) => {
|
|
||||||
self.run_switch_statement(ctx, switchstmt)
|
|
||||||
}
|
|
||||||
// Other types should be impossible due to the
|
// Other types should be impossible due to the
|
||||||
// specific_statement_type_is_redirectable_block check.
|
// statement_is_redirectable_block check.
|
||||||
StatementVariant::NotStatement(_)
|
Statement::Not(_) | Statement::Decorated(_) => {
|
||||||
| StatementVariant::DecoratedStatement(_)
|
panic!()
|
||||||
| StatementVariant::None => panic!(),
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1627,7 +1593,7 @@ fn run_1_job(
|
|||||||
profile_item.duration = ProfileItem::now() - start_time;
|
profile_item.duration = ProfileItem::now() - start_time;
|
||||||
profile_item.level = ctx.parser().scope().eval_level;
|
profile_item.level = ctx.parser().scope().eval_level;
|
||||||
profile_item.cmd =
|
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;
|
profile_item.skipped = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1931,56 +1897,35 @@ enum Globspec {
|
|||||||
}
|
}
|
||||||
type AstArgsList<'a> = Vec<&'a ast::Argument>;
|
type AstArgsList<'a> = Vec<&'a ast::Argument>;
|
||||||
|
|
||||||
/// These are the specific statement types that support redirections.
|
fn statement_is_redirectable_block(node: &ast::Statement) -> bool {
|
||||||
fn type_is_redirectable_block(typ: ast::Type) -> bool {
|
match node {
|
||||||
[
|
Statement::Decorated(_) | Statement::Not(_) => false,
|
||||||
ast::Type::block_statement,
|
Statement::Block(_) | Statement::Brace(_) | Statement::If(_) | Statement::Switch(_) => true,
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the name of a redirectable block, for profiling purposes.
|
/// Get the name of a redirectable block, for profiling purposes.
|
||||||
fn profiling_cmd_name_for_redirectable_block(
|
fn profiling_cmd_name_for_redirectable_block(
|
||||||
node: &ast::StatementVariant,
|
node: &ast::Statement,
|
||||||
pstree: &ParsedSourceRef,
|
pstree: &ParsedSourceRef,
|
||||||
) -> WString {
|
) -> 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 source_range = node.try_source_range().expect("No source range for block");
|
||||||
|
|
||||||
let src_end = match node {
|
let src_end = match node {
|
||||||
StatementVariant::BlockStatement(block_statement) => {
|
Statement::Block(block_statement) => {
|
||||||
let block_header = &block_statement.header;
|
let block_header = &block_statement.header;
|
||||||
match block_header {
|
match block_header {
|
||||||
BlockStatementHeaderVariant::ForHeader(for_header) => {
|
BlockStatementHeader::For(node) => node.semi_nl.source_range().start(),
|
||||||
for_header.semi_nl.source_range().start()
|
BlockStatementHeader::While(node) => node.condition.source_range().start(),
|
||||||
}
|
BlockStatementHeader::Function(node) => node.semi_nl.source_range().start(),
|
||||||
BlockStatementHeaderVariant::WhileHeader(while_header) => {
|
BlockStatementHeader::Begin(node) => node.kw_begin.source_range().start(),
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StatementVariant::BraceStatement(brace_statement) => {
|
Statement::Brace(brace_statement) => brace_statement.left_brace.source_range().start(),
|
||||||
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(),
|
||||||
StatementVariant::IfStatement(ifstmt) => {
|
|
||||||
ifstmt.if_clause.condition.job.source_range().end()
|
|
||||||
}
|
|
||||||
StatementVariant::SwitchStatement(switchstmt) => switchstmt.semi_nl.source_range().start(),
|
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Not a redirectable block_type");
|
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...
|
// 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 {
|
fn is_timed_not_statement(mut stat: &ast::Statement) -> bool {
|
||||||
match &stat.contents {
|
loop {
|
||||||
StatementVariant::NotStatement(ns) => {
|
match &stat {
|
||||||
if ns.time.is_some() {
|
Statement::Not(ns) => {
|
||||||
return true;
|
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?
|
// Do we have a 'not time ...' anywhere in our pipeline?
|
||||||
if is_timed_not_statement(&job_node.statement) {
|
if is_timed_not_statement(&job_node.statement) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::ast::{Ast, Node};
|
use crate::ast::{self, Ast, Node};
|
||||||
use crate::common::{assert_send, assert_sync};
|
use crate::common::{assert_send, assert_sync};
|
||||||
use crate::parse_constants::{
|
use crate::parse_constants::{
|
||||||
token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseKeyword,
|
token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseKeyword,
|
||||||
@@ -187,7 +187,7 @@ pub fn parse_source(
|
|||||||
flags: ParseTreeFlags,
|
flags: ParseTreeFlags,
|
||||||
errors: Option<&mut ParseErrorList>,
|
errors: Option<&mut ParseErrorList>,
|
||||||
) -> Option<ParsedSourceRef> {
|
) -> 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) {
|
if ast.errored() && !flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Various mostly unrelated utility functions related to parsing, loading and evaluating fish code.
|
//! Various mostly unrelated utility functions related to parsing, loading and evaluating fish code.
|
||||||
use crate::ast::{
|
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::builtins::shared::builtin_exists;
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
@@ -220,17 +220,30 @@ fn parse_util_locate_cmdsub(
|
|||||||
let mut last_dollar = None;
|
let mut last_dollar = None;
|
||||||
let mut paran_begin = None;
|
let mut paran_begin = None;
|
||||||
let mut paran_end = None;
|
let mut paran_end = None;
|
||||||
|
enum Quote {
|
||||||
|
Real(char),
|
||||||
|
VirtualDouble,
|
||||||
|
}
|
||||||
fn process_opening_quote(
|
fn process_opening_quote(
|
||||||
input: &[char],
|
input: &[char],
|
||||||
inout_is_quoted: &mut Option<&mut bool>,
|
inout_is_quoted: &mut Option<&mut bool>,
|
||||||
paran_count: i32,
|
paran_count: i32,
|
||||||
quoted_cmdsubs: &mut Vec<i32>,
|
quoted_cmdsubs: &mut Vec<i32>,
|
||||||
pos: usize,
|
mut pos: usize,
|
||||||
last_dollar: &mut Option<usize>,
|
last_dollar: &mut Option<usize>,
|
||||||
quote: char,
|
quote: Quote,
|
||||||
) -> Option<usize> {
|
) -> 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)?;
|
let q_end = quote_end(input.into(), pos, quote)?;
|
||||||
|
// Found a valid closing quote.
|
||||||
if input[q_end] == '$' {
|
if input[q_end] == '$' {
|
||||||
|
// The closing quote is another quoted command substitution.
|
||||||
*last_dollar = Some(q_end);
|
*last_dollar = Some(q_end);
|
||||||
quoted_cmdsubs.push(paran_count);
|
quoted_cmdsubs.push(paran_count);
|
||||||
}
|
}
|
||||||
@@ -256,9 +269,9 @@ fn process_opening_quote(
|
|||||||
&mut quoted_cmdsubs,
|
&mut quoted_cmdsubs,
|
||||||
pos,
|
pos,
|
||||||
&mut last_dollar,
|
&mut last_dollar,
|
||||||
'"',
|
Quote::VirtualDouble,
|
||||||
)
|
)
|
||||||
.unwrap_or(input.len());
|
.map_or(input.len(), |pos| pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
while pos < input.len() {
|
while pos < input.len() {
|
||||||
@@ -272,7 +285,7 @@ fn process_opening_quote(
|
|||||||
&mut quoted_cmdsubs,
|
&mut quoted_cmdsubs,
|
||||||
pos,
|
pos,
|
||||||
&mut last_dollar,
|
&mut last_dollar,
|
||||||
c,
|
Quote::Real(c),
|
||||||
) {
|
) {
|
||||||
Some(q_end) => pos = q_end,
|
Some(q_end) => pos = q_end,
|
||||||
None => break,
|
None => break,
|
||||||
@@ -295,7 +308,8 @@ fn process_opening_quote(
|
|||||||
} else if c == ')' {
|
} else if c == ')' {
|
||||||
paran_count -= 1;
|
paran_count -= 1;
|
||||||
|
|
||||||
if paran_count == 0 && paran_end.is_none() {
|
if paran_count == 0 {
|
||||||
|
assert!(paran_end.is_none());
|
||||||
paran_end = Some(pos);
|
paran_end = Some(pos);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -309,21 +323,19 @@ fn process_opening_quote(
|
|||||||
if quoted_cmdsubs.last() == Some(¶n_count) {
|
if quoted_cmdsubs.last() == Some(¶n_count) {
|
||||||
quoted_cmdsubs.pop();
|
quoted_cmdsubs.pop();
|
||||||
// Quoted command substitutions temporarily close double quotes.
|
// Quoted command substitutions temporarily close double quotes.
|
||||||
// In "foo$(bar)baz$(qux)"
|
// In "foo$(bar)baz$(qux)", after the ), we need to act as if there was a double quote.
|
||||||
// We are here ^
|
match process_opening_quote(
|
||||||
// After the ) in a quoted command substitution, we need to act as if
|
input,
|
||||||
// there was an invisible double quote.
|
&mut inout_is_quoted,
|
||||||
match quote_end(input.into(), pos, '"') {
|
paran_count,
|
||||||
Some(q_end) => {
|
&mut quoted_cmdsubs,
|
||||||
// Found a valid closing quote.
|
pos,
|
||||||
// Stop at $(qux), which is another quoted command substitution.
|
&mut last_dollar,
|
||||||
if input[q_end] == '$' {
|
Quote::VirtualDouble,
|
||||||
quoted_cmdsubs.push(paran_count);
|
) {
|
||||||
}
|
Some(q_end) => pos = q_end,
|
||||||
pos = q_end;
|
|
||||||
}
|
|
||||||
None => break,
|
None => break,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is_token_begin = is_token_delimiter(c, input.get(pos + 1).copied());
|
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
|
// 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
|
// 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.
|
// were a case item list.
|
||||||
let ast = Ast::parse(
|
let ast = ast::parse(
|
||||||
src,
|
src,
|
||||||
ParseTreeFlags::CONTINUE_AFTER_ERROR
|
ParseTreeFlags::CONTINUE_AFTER_ERROR
|
||||||
| ParseTreeFlags::INCLUDE_COMMENTS
|
| 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> {
|
impl<'a> NodeVisitor<'a> for IndentVisitor<'a> {
|
||||||
// Default implementation is to just visit children.
|
// Default implementation is to just visit children.
|
||||||
fn visit(&mut self, node: &'a dyn Node) {
|
fn visit(&mut self, node: &'a dyn Node) {
|
||||||
let mut inc = 0;
|
let mut inc_dec = (0, 0);
|
||||||
let mut dec = 0;
|
match node.kind() {
|
||||||
use ast::{Category, Type};
|
Kind::JobList(_) | Kind::AndorJobList(_) => {
|
||||||
match node.typ() {
|
|
||||||
Type::job_list | Type::andor_job_list => {
|
|
||||||
// Job lists are never unwound.
|
// Job lists are never unwound.
|
||||||
inc = 1;
|
inc_dec = (1, 1);
|
||||||
dec = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment indents for conditions in headers (#1665).
|
// Increment indents for conditions in headers (#1665).
|
||||||
Type::job_conjunction => {
|
Kind::JobConjunction(_node) => {
|
||||||
let typ = self.parent.unwrap().typ();
|
let parent_kind = self.parent.unwrap().kind();
|
||||||
if matches!(typ, Type::if_clause | Type::while_header) {
|
if matches!(parent_kind, Kind::IfClause(_) | Kind::WhileHeader(_)) {
|
||||||
inc = 1;
|
inc_dec = (1, 1);
|
||||||
dec = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,22 +1019,20 @@ fn visit(&mut self, node: &'a dyn Node) {
|
|||||||
// ....cmd3
|
// ....cmd3
|
||||||
// end
|
// end
|
||||||
// See #7252.
|
// See #7252.
|
||||||
Type::job_continuation => {
|
Kind::JobContinuation(node) => {
|
||||||
if self.has_newline(&node.as_job_continuation().unwrap().newlines) {
|
if self.has_newline(&node.newlines) {
|
||||||
inc = 1;
|
inc_dec = (1, 1);
|
||||||
dec = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Likewise for && and ||.
|
// Likewise for && and ||.
|
||||||
Type::job_conjunction_continuation => {
|
Kind::JobConjunctionContinuation(node) => {
|
||||||
if self.has_newline(&node.as_job_conjunction_continuation().unwrap().newlines) {
|
if self.has_newline(&node.newlines) {
|
||||||
inc = 1;
|
inc_dec = (1, 1);
|
||||||
dec = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::case_item_list => {
|
Kind::CaseItemList(_) => {
|
||||||
// Here's a hack. Consider:
|
// Here's a hack. Consider:
|
||||||
// switch abc
|
// switch abc
|
||||||
// cas
|
// 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.
|
// 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
|
// To address this, if we see that the switch statement was not closed, do not
|
||||||
// decrement the indent afterwards.
|
// decrement the indent afterwards.
|
||||||
inc = 1;
|
let Kind::SwitchStatement(switchs) = self.parent.unwrap().kind() else {
|
||||||
let switchs = self.parent.unwrap().as_switch_statement().unwrap();
|
panic!("Expected switch statement");
|
||||||
dec = if switchs.end.has_source() { 1 } else { 0 };
|
};
|
||||||
|
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();
|
Kind::Token(node) => {
|
||||||
let parent_type = self.parent.unwrap().typ();
|
let token_type = node.token_type();
|
||||||
if parent_type == Type::begin_header && token_type == ParseTokenType::end {
|
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 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.
|
// The header is not in the indented block, so indent the newline here.
|
||||||
if node.source(self.src) == "\n" {
|
if node.source(self.src) == "\n" {
|
||||||
inc = 1;
|
inc_dec = (1, 1);
|
||||||
dec = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if token_type == ParseTokenType::right_brace && parent_type == Type::brace_statement
|
|
||||||
// {
|
|
||||||
// inc = 1;
|
|
||||||
// dec = 1;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
let range = node.source_range();
|
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.record_line_continuations_until(range.start());
|
||||||
self.indents[self.last_leaf_end..range.start()].fill(self.last_indent);
|
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
|
// 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):
|
// 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.
|
// we want to indent the newline.
|
||||||
if inc != 0 {
|
if inc_dec.0 != 0 {
|
||||||
self.last_indent = self.indent;
|
self.last_indent = self.indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a leaf node, apply the current indentation.
|
// 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()]
|
let leading_spaces = self.src[..range.start()]
|
||||||
.chars()
|
.chars()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -1101,9 +1105,9 @@ fn visit(&mut self, node: &'a dyn Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let saved = self.parent.replace(node);
|
let saved = self.parent.replace(node);
|
||||||
node.accept(self, false);
|
node.accept(self);
|
||||||
self.parent = saved;
|
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.
|
// Parse the input string into an ast. Some errors are detected here.
|
||||||
let mut parse_errors = ParseErrorList::new();
|
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 {
|
if allow_incomplete {
|
||||||
// Issue #1238: If the only error was unterminated quote, then consider this to have parsed
|
// Issue #1238: If the only error was unterminated quote, then consider this to have parsed
|
||||||
// successfully.
|
// successfully.
|
||||||
@@ -1198,76 +1202,95 @@ pub fn parse_util_detect_errors_in_ast(
|
|||||||
|
|
||||||
let mut traversal = ast::Traversal::new(ast.top());
|
let mut traversal = ast::Traversal::new(ast.top());
|
||||||
while let Some(node) = traversal.next() {
|
while let Some(node) = traversal.next() {
|
||||||
if let Some(jc) = node.as_job_continuation() {
|
match node.kind() {
|
||||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
Kind::JobContinuation(jc) => {
|
||||||
// See if our pipe has source but our statement does not.
|
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() {
|
// See if our pipe has source but our statement does not.
|
||||||
has_unclosed_pipe = true;
|
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() {
|
Kind::JobConjunction(job_conjunction) => {
|
||||||
errored |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors);
|
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;
|
|
||||||
}
|
}
|
||||||
} else if let Some(arg) = node.as_argument() {
|
Kind::JobConjunctionContinuation(jcc) => {
|
||||||
let arg_src = arg.source(buff_src);
|
// Somewhat clumsy way of checking for a job without source in a conjunction.
|
||||||
res |= parse_util_detect_errors_in_argument(arg, arg_src, &mut out_errors)
|
// See if our conjunction operator (&& or ||) has source but our job does not.
|
||||||
.err()
|
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() {
|
||||||
.unwrap_or_default();
|
has_unclosed_conjunction = true;
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
} else if let Some(stmt) = node.as_decorated_statement() {
|
Kind::Argument(arg) => {
|
||||||
errored |=
|
let arg_src = arg.source(buff_src);
|
||||||
detect_errors_in_decorated_statement(buff_src, &traversal, stmt, &mut out_errors);
|
res |= parse_util_detect_errors_in_argument(arg, arg_src, &mut out_errors)
|
||||||
} else if let Some(block) = node.as_block_statement() {
|
.err()
|
||||||
// If our 'end' had no source, we are unsourced.
|
.unwrap_or_default();
|
||||||
if !block.end.has_source() {
|
|
||||||
has_unclosed_block = true;
|
|
||||||
}
|
}
|
||||||
errored |= detect_errors_in_block_redirection_list(
|
Kind::JobPipeline(job) => {
|
||||||
node,
|
// Disallow background in the following cases:
|
||||||
&block.args_or_redirs,
|
//
|
||||||
&mut out_errors,
|
// foo & ; and bar
|
||||||
);
|
// foo & ; or bar
|
||||||
} else if let Some(brace_statement) = node.as_brace_statement() {
|
// if foo & ; end
|
||||||
// If our closing brace had no source, we are unsourced.
|
// while foo & ; end
|
||||||
if !brace_statement.right_brace.has_source() {
|
// If it's not a background job, nothing to do.
|
||||||
has_unclosed_block = true;
|
if job.bg.is_some() {
|
||||||
|
errored |= detect_errors_in_backgrounded_job(&traversal, job, &mut out_errors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
errored |= detect_errors_in_block_redirection_list(
|
Kind::DecoratedStatement(stmt) => {
|
||||||
node,
|
errored |= detect_errors_in_decorated_statement(
|
||||||
&brace_statement.args_or_redirs,
|
buff_src,
|
||||||
&mut out_errors,
|
&traversal,
|
||||||
);
|
stmt,
|
||||||
} else if let Some(ifs) = node.as_if_statement() {
|
&mut out_errors,
|
||||||
// If our 'end' had no source, we are unsourced.
|
);
|
||||||
if !ifs.end.has_source() {
|
|
||||||
has_unclosed_block = true;
|
|
||||||
}
|
}
|
||||||
errored |=
|
Kind::BlockStatement(block) => {
|
||||||
detect_errors_in_block_redirection_list(node, &ifs.args_or_redirs, &mut out_errors);
|
// If our 'end' had no source, we are unsourced.
|
||||||
} else if let Some(switchs) = node.as_switch_statement() {
|
if !block.end.has_source() {
|
||||||
// If our 'end' had no source, we are unsourced.
|
has_unclosed_block = true;
|
||||||
if !switchs.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(
|
Kind::BraceStatement(brace_statement) => {
|
||||||
node,
|
// If our closing brace had no source, we are unsourced.
|
||||||
&switchs.args_or_redirs,
|
if !brace_statement.right_brace.has_source() {
|
||||||
&mut out_errors,
|
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.
|
// Parse the string as a freestanding argument list.
|
||||||
let mut errors = ParseErrorList::new();
|
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() {
|
if !errors.is_empty() {
|
||||||
return get_error_text(&errors);
|
return get_error_text(&errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the root argument list and extract arguments from it.
|
// Get the root argument list and extract arguments from it.
|
||||||
// Test each of these.
|
// 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() {
|
for arg in args.iter() {
|
||||||
let arg_src = arg.source(arg_list_src);
|
let arg_src = arg.source(arg_list_src);
|
||||||
if parse_util_detect_errors_in_argument(arg, arg_src, &mut Some(&mut errors)).is_err() {
|
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
|
// foo & ; or bar
|
||||||
// if foo & ; end
|
// if foo & ; end
|
||||||
// while 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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let job_conj_parent = traversal.parent(job_conj);
|
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!(
|
errored = append_syntax_error!(
|
||||||
parse_errors,
|
parse_errors,
|
||||||
source_range.start(),
|
source_range.start(),
|
||||||
source_range.length(),
|
source_range.length(),
|
||||||
BACKGROUND_IN_CONDITIONAL_ERROR_MSG
|
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'.
|
// This isn't very complete, e.g. we don't catch 'foo & ; not and bar'.
|
||||||
// Find the index of ourselves in the job list.
|
// Find the index of ourselves in the job list.
|
||||||
let index = jlist
|
let index = jlist
|
||||||
@@ -1607,13 +1634,18 @@ fn detect_errors_in_decorated_statement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the statement we are part of.
|
// 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.
|
// Walk up to the job.
|
||||||
let job = traversal
|
let job = traversal
|
||||||
.parent_nodes()
|
.parent_nodes()
|
||||||
.find_map(|n| n.as_job_pipeline())
|
.find_map(|n| match n.kind() {
|
||||||
.expect("Should have found the job");
|
Kind::JobPipeline(job) => Some(job),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("should have found the job");
|
||||||
|
|
||||||
// Check our pipeline position.
|
// Check our pipeline position.
|
||||||
let pipe_pos = if job.continuation.is_empty() {
|
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
|
// loop from the ancestor alone; we need the header. That is, we hit a
|
||||||
// block_statement, and have to check its header.
|
// block_statement, and have to check its header.
|
||||||
let mut found_loop = false;
|
let mut found_loop = false;
|
||||||
for block in traversal
|
for block in traversal.parent_nodes().filter_map(|anc| match anc.kind() {
|
||||||
.parent_nodes()
|
Kind::BlockStatement(block) => Some(block),
|
||||||
.filter_map(|anc| anc.as_block_statement())
|
_ => None,
|
||||||
{
|
}) {
|
||||||
match block.header.typ() {
|
match block.header {
|
||||||
ast::Type::for_header | ast::Type::while_header => {
|
ast::BlockStatementHeader::For(_) | ast::BlockStatementHeader::While(_) => {
|
||||||
// This is a loop header, so we can break or continue.
|
// This is a loop header, so we can break or continue.
|
||||||
found_loop = true;
|
found_loop = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ast::Type::function_header => {
|
ast::BlockStatementHeader::Function(_) => {
|
||||||
// This is a function header, so we cannot break or
|
// This is a function header, so we cannot break or
|
||||||
// continue. We stop our search here.
|
// continue. We stop our search here.
|
||||||
found_loop = false;
|
found_loop = false;
|
||||||
@@ -1808,7 +1840,7 @@ fn detect_errors_in_block_redirection_list(
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let r = first_arg.source_range();
|
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);
|
append_syntax_error!(out_errors, r.start(), r.length(), RIGHT_BRACE_ARG_ERR_MSG);
|
||||||
} else {
|
} else {
|
||||||
append_syntax_error!(out_errors, r.start(), r.length(), END_ARG_ERR_MSG);
|
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.
|
// 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::builtins::shared::STATUS_ILLEGAL_CMD;
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
escape_string, wcs2string, CancelChecker, EscapeFlags, EscapeStringStyle, FilenameRef,
|
escape_string, wcs2string, CancelChecker, EscapeFlags, EscapeStringStyle, FilenameRef,
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
use crate::util::get_time;
|
use crate::util::get_time;
|
||||||
use crate::wait_handle::WaitHandleStore;
|
use crate::wait_handle::WaitHandleStore;
|
||||||
use crate::wchar::{wstr, WString, L};
|
use crate::wchar::{wstr, WString, L};
|
||||||
|
use crate::wchar_ext::WExt;
|
||||||
use crate::wutil::{perror, wgettext, wgettext_fmt};
|
use crate::wutil::{perror, wgettext, wgettext_fmt};
|
||||||
use crate::{function, FLOG};
|
use crate::{function, FLOG};
|
||||||
use libc::c_int;
|
use libc::c_int;
|
||||||
@@ -556,7 +557,7 @@ pub fn eval_parsed_source(
|
|||||||
block_type: BlockType,
|
block_type: BlockType,
|
||||||
) -> EvalRes {
|
) -> EvalRes {
|
||||||
assert!([BlockType::top, BlockType::subst].contains(&block_type));
|
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() {
|
if !job_list.is_empty() {
|
||||||
// Execute the top job list.
|
// Execute the top job list.
|
||||||
self.eval_node(ps, job_list, io, job_group, block_type)
|
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_tree::ParsedSource;
|
||||||
use crate::parse_util::parse_util_detect_errors_in_ast;
|
use crate::parse_util::parse_util_detect_errors_in_ast;
|
||||||
let mut errors = vec![];
|
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();
|
let mut errored = ast.errored();
|
||||||
if !errored {
|
if !errored {
|
||||||
errored = parse_util_detect_errors_in_ast(&ast, &src, Some(&mut errors)).is_err();
|
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<'_>,
|
ctx: &OperationContext<'_>,
|
||||||
) -> CompletionList {
|
) -> CompletionList {
|
||||||
// Parse the string as an argument list.
|
// 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() {
|
if ast.errored() {
|
||||||
// Failed to parse. Here we expect to have reported any errors in test_args.
|
// Failed to parse. Here we expect to have reported any errors in test_args.
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -734,8 +735,7 @@ pub fn expand_argument_list(
|
|||||||
|
|
||||||
// Get the root argument list and extract arguments from it.
|
// Get the root argument list and extract arguments from it.
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let list = ast.top().as_freestanding_argument_list().unwrap();
|
for arg in &ast.top().arguments {
|
||||||
for arg in &list.arguments {
|
|
||||||
let arg_src = arg.source(arg_list_src);
|
let arg_src = arg.source(arg_list_src);
|
||||||
if matches!(
|
if matches!(
|
||||||
expand_string(arg_src.to_owned(), &mut result, flags, ctx, None).result,
|
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.
|
/// Returns a new profile item if profiling is active. The caller should fill it in.
|
||||||
/// The Parser will deallocate it.
|
/// 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> {
|
pub fn create_profile_item(&self) -> Option<usize> {
|
||||||
if PROFILING_ACTIVE.load() {
|
if PROFILING_ACTIVE.load() {
|
||||||
let mut profile_items = self.profile_items.borrow_mut();
|
let mut profile_items = self.profile_items.borrow_mut();
|
||||||
@@ -1272,7 +1272,12 @@ fn print_profile(items: &[ProfileItem], out: &mut File) {
|
|||||||
)
|
)
|
||||||
.as_bytes(),
|
.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");
|
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
|
//! for testing if a command with a given name can be found in the PATH, and various other
|
||||||
//! path-related issues.
|
//! 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::env::{EnvMode, EnvStack, Environment};
|
||||||
use crate::expand::{expand_tilde, HOME_DIRECTORY};
|
use crate::expand::{expand_tilde, HOME_DIRECTORY};
|
||||||
use crate::flog::{FLOG, FLOGF};
|
use crate::flog::{FLOG, FLOGF};
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::os::unix::prelude::*;
|
use std::os::unix::prelude::*;
|
||||||
|
|
||||||
/// Returns the user configuration directory for fish. If the directory or one of its parents
|
/// Returns the user configuration directory for fish. If the directory or one of its parents
|
||||||
@@ -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());
|
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;
|
let mut best = noent_res;
|
||||||
for next_path in pathsv {
|
for next_path in pathsv {
|
||||||
let next_path: &wstr = next_path.as_ref();
|
let next_path: &wstr = next_path.as_ref();
|
||||||
@@ -693,10 +671,11 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
|
|||||||
let narrow = wcs2zstring(path);
|
let narrow = wcs2zstring(path);
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
|
let mut buf = MaybeUninit::uninit();
|
||||||
if unsafe { libc::statfs(narrow.as_ptr(), &mut buf) } < 0 {
|
if unsafe { libc::statfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 0 {
|
||||||
return DirRemoteness::unknown;
|
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
|
// 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.
|
// 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.
|
// 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"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
{
|
{
|
||||||
let st_local = ST_LOCAL();
|
// ST_LOCAL is a flag to statvfs, which is itself standardized.
|
||||||
if st_local != 0 {
|
// In practice the only system to define it is NetBSD.
|
||||||
// ST_LOCAL is a flag to statvfs, which is itself standardized.
|
let local_flag = ST_LOCAL() | MNT_LOCAL();
|
||||||
// In practice the only system to use this path is NetBSD.
|
if local_flag != 0 {
|
||||||
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
|
let mut buf = MaybeUninit::uninit();
|
||||||
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
|
if unsafe { libc::statvfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 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 {
|
|
||||||
return DirRemoteness::unknown;
|
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)
|
// 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).
|
// long on 32-bit NetBSD.. and always 4-bytes on macOS (even on 64-bit builds).
|
||||||
#[allow(clippy::useless_conversion)]
|
#[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
|
DirRemoteness::local
|
||||||
} else {
|
} else {
|
||||||
DirRemoteness::remote
|
DirRemoteness::remote
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::rc::Rc;
|
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.
|
/// Save the current tty modes into the owning job group, if we are transferred.
|
||||||
pub fn save_tty_modes(&mut self) {
|
pub fn save_tty_modes(&mut self) {
|
||||||
if let Some(ref mut owner) = self.owner {
|
if let Some(ref mut owner) = self.owner {
|
||||||
let mut tmodes: libc::termios = unsafe { std::mem::zeroed() };
|
let mut tmodes = MaybeUninit::uninit();
|
||||||
if unsafe { libc::tcgetattr(STDIN_FILENO, &mut tmodes) } == 0 {
|
if unsafe { libc::tcgetattr(STDIN_FILENO, tmodes.as_mut_ptr()) } == 0 {
|
||||||
owner.tmodes.replace(Some(tmodes));
|
owner.tmodes.replace(Some(unsafe { tmodes.assume_init() }));
|
||||||
} else if errno::errno().0 != ENOTTY {
|
} else if errno::errno().0 != ENOTTY {
|
||||||
perror("tcgetattr");
|
perror("tcgetattr");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,11 @@
|
|||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use std::os::fd::BorrowedFd;
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
use errno::{errno, Errno};
|
use errno::{errno, Errno};
|
||||||
|
|
||||||
use crate::abbrs::abbrs_match;
|
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::ErrorCode;
|
||||||
use crate::builtins::shared::STATUS_CMD_ERROR;
|
use crate::builtins::shared::STATUS_CMD_ERROR;
|
||||||
use crate::builtins::shared::STATUS_CMD_OK;
|
use crate::builtins::shared::STATUS_CMD_OK;
|
||||||
@@ -241,7 +243,11 @@ pub(crate) fn initial_query(
|
|||||||
vars: Option<&dyn Environment>,
|
vars: Option<&dyn Environment>,
|
||||||
) {
|
) {
|
||||||
blocking_query.get_or_init(|| {
|
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
|
None
|
||||||
} else {
|
} else {
|
||||||
// Query for kitty keyboard protocol support.
|
// 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:
|
// See also, commit 396bf12. Without the need for this workaround we would just write:
|
||||||
// int inter = ((fd == STDIN_FILENO) && isatty(STDIN_FILENO));
|
// int inter = ((fd == STDIN_FILENO) && isatty(STDIN_FILENO));
|
||||||
if fd == STDIN_FILENO {
|
if fd == STDIN_FILENO {
|
||||||
let mut t: libc::termios = unsafe { std::mem::zeroed() };
|
let mut t = MaybeUninit::uninit();
|
||||||
if isatty(STDIN_FILENO) {
|
if isatty(STDIN_FILENO) {
|
||||||
interactive = true;
|
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);
|
redirect_tty_output(false);
|
||||||
interactive = true;
|
interactive = true;
|
||||||
}
|
}
|
||||||
@@ -797,7 +804,7 @@ fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> {
|
|||||||
loop {
|
loop {
|
||||||
let mut buff = [0_u8; 4096];
|
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) => {
|
Ok(0) => {
|
||||||
// EOF.
|
// EOF.
|
||||||
break;
|
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
|
/// 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
|
/// 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
|
/// than nchars if a single keypress resulted in multiple characters being inserted into the
|
||||||
/// commandline.
|
/// commandline.
|
||||||
pub fn reader_readline(parser: &Parser, nchars: usize) -> Option<WString> {
|
pub fn reader_readline(parser: &Parser, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||||
let nchars = NonZeroUsize::try_from(nchars).ok();
|
|
||||||
let data = current_data().unwrap();
|
let data = current_data().unwrap();
|
||||||
let mut reader = Reader { parser, data };
|
let mut reader = Reader { parser, data };
|
||||||
reader.readline(nchars)
|
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) {
|
pub fn request_cursor_position(&mut self, out: &mut Outputter, q: CursorPositionQuery) {
|
||||||
|
if !isatty(STDOUT_FILENO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut query = self.blocking_query();
|
let mut query = self.blocking_query();
|
||||||
assert!(query.is_none());
|
assert!(query.is_none());
|
||||||
*query = Some(TerminalQuery::CursorPositionReport(q));
|
*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()) };
|
unsafe { libc::tcsetpgrp(self.conf.inputfd, libc::getpgrp()) };
|
||||||
|
|
||||||
// Get the current terminal modes. These will be restored when the function returns.
|
// Get the current terminal modes. These will be restored when the function returns.
|
||||||
let mut old_modes: libc::termios = unsafe { std::mem::zeroed() };
|
let mut old_modes = MaybeUninit::uninit();
|
||||||
if unsafe { libc::tcgetattr(self.conf.inputfd, &mut old_modes) } == -1 && errno().0 == EIO {
|
if unsafe { libc::tcgetattr(self.conf.inputfd, old_modes.as_mut_ptr()) } == -1
|
||||||
|
&& errno().0 == EIO
|
||||||
|
{
|
||||||
redirect_tty_output(false);
|
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 _ {
|
if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as _ {
|
||||||
// The order of the two conditions below is important. Try to restore the mode
|
// The order of the two conditions below is important. Try to restore the mode
|
||||||
// in all cases, but only complain if interactive.
|
// 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()
|
&& is_interactive_session()
|
||||||
{
|
{
|
||||||
if errno().0 == EIO {
|
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.
|
// 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;
|
self.parser.libdata_mut().exit_current_script = false;
|
||||||
if self.exit_loop_requested {
|
if self.exit_loop_requested {
|
||||||
return ControlFlow::Continue(());
|
return ControlFlow::Continue(());
|
||||||
@@ -3940,9 +3950,9 @@ fn handle_execute(&mut self) -> bool {
|
|||||||
// using a backslash, insert a newline.
|
// using a backslash, insert a newline.
|
||||||
// If the user hits return while navigating the pager, it only clears the pager.
|
// If the user hits return while navigating the pager, it only clears the pager.
|
||||||
if self.is_navigating_pager_contents() {
|
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() {
|
if self.history_pager.is_some() && self.pager.selected_completion_idx.is_none() {
|
||||||
let range = 0..self.command_line.len();
|
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 offset_from_end = search_field.len() - search_field.position();
|
||||||
let mut cursor = self.command_line.position();
|
let mut cursor = self.command_line.position();
|
||||||
let updated = replace_line_at_cursor(
|
let updated = replace_line_at_cursor(
|
||||||
@@ -3952,6 +3962,13 @@ fn handle_execute(&mut self) -> bool {
|
|||||||
);
|
);
|
||||||
self.replace_substring(EditableLineTag::Commandline, range, updated);
|
self.replace_substring(EditableLineTag::Commandline, range, updated);
|
||||||
self.command_line.set_position(cursor - offset_from_end);
|
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();
|
self.clear_pager();
|
||||||
return true;
|
return true;
|
||||||
@@ -4258,10 +4275,10 @@ fn term_donate(quiet: bool /* = false */) {
|
|||||||
|
|
||||||
/// Copy the (potentially changed) terminal modes and use them from now on.
|
/// Copy the (potentially changed) terminal modes and use them from now on.
|
||||||
pub fn term_copy_modes() {
|
pub fn term_copy_modes() {
|
||||||
let mut modes: libc::termios = unsafe { std::mem::zeroed() };
|
let mut modes = MaybeUninit::uninit();
|
||||||
unsafe { libc::tcgetattr(STDIN_FILENO, &mut modes) };
|
unsafe { libc::tcgetattr(STDIN_FILENO, modes.as_mut_ptr()) };
|
||||||
let mut tty_modes_for_external_cmds = TTY_MODES_FOR_EXTERNAL_CMDS.lock().unwrap();
|
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.
|
// We still want to fix most egregious breakage.
|
||||||
// E.g. OPOST is *not* something that should be set globally,
|
// E.g. OPOST is *not* something that should be set globally,
|
||||||
// and 99% triggered by a crashed program.
|
// and 99% triggered by a crashed program.
|
||||||
@@ -4876,7 +4893,13 @@ fn update_autosuggestion(&mut self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let el = &self.data.command_line;
|
let el = &self.data.command_line;
|
||||||
|
let autosuggestion = &self.autosuggestion;
|
||||||
if self.is_at_line_with_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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5339,15 +5362,15 @@ fn extract_tokens(s: &wstr) -> Vec<PositionedToken> {
|
|||||||
let ast_flags = ParseTreeFlags::CONTINUE_AFTER_ERROR
|
let ast_flags = ParseTreeFlags::CONTINUE_AFTER_ERROR
|
||||||
| ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS
|
| ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS
|
||||||
| ParseTreeFlags::LEAVE_UNTERMINATED;
|
| 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 result = vec![];
|
||||||
let mut traversal = ast.walk();
|
let mut traversal = ast.walk();
|
||||||
while let Some(node) = traversal.next() {
|
while let Some(node) = traversal.next() {
|
||||||
// We are only interested in leaf nodes with source.
|
// We are only interested in leaf nodes with source.
|
||||||
if node.category() != Category::leaf {
|
if node.as_leaf().is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
let range = node.source_range();
|
let range = node.source_range();
|
||||||
if range.length() == 0 {
|
if range.length() == 0 {
|
||||||
continue;
|
continue;
|
||||||
@@ -5381,10 +5404,10 @@ fn extract_tokens(s: &wstr) -> Vec<PositionedToken> {
|
|||||||
if !has_cmd_subs {
|
if !has_cmd_subs {
|
||||||
// Common case of no command substitutions in this leaf node.
|
// Common case of no command substitutions in this leaf node.
|
||||||
// Check if a node is the command portion of a decorated statement.
|
// Check if a node is the command portion of a decorated statement.
|
||||||
let is_cmd = traversal
|
let mut is_cmd = false;
|
||||||
.parent(node)
|
if let Kind::DecoratedStatement(stmt) = traversal.parent(node).kind() {
|
||||||
.as_decorated_statement()
|
is_cmd = is_same_node(node, &stmt.command);
|
||||||
.is_some_and(|stmt| is_same_node(node, &stmt.command));
|
}
|
||||||
result.push(PositionedToken { range, is_cmd })
|
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
|
/// 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.
|
/// otherwise empty. This includes both soft and hard wrapping.
|
||||||
pub fn cursor_is_wrapped_to_own_line(&self) -> bool {
|
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.
|
// 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
|
/// 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 std::num::NonZeroI32;
|
||||||
|
|
||||||
use crate::common::exit_without_destructors;
|
use crate::common::exit_without_destructors;
|
||||||
@@ -131,8 +132,9 @@ pub fn signal_reset_handlers() {
|
|||||||
|
|
||||||
for data in SIGNAL_TABLE.iter() {
|
for data in SIGNAL_TABLE.iter() {
|
||||||
if data.signal == libc::SIGHUP {
|
if data.signal == libc::SIGHUP {
|
||||||
let mut oact: libc::sigaction = unsafe { std::mem::zeroed() };
|
let mut oact = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigaction(libc::SIGHUP, std::ptr::null(), &mut oact) };
|
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 {
|
if oact.sa_sigaction == libc::SIG_IGN {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -277,30 +279,31 @@ pub fn signal_handle(sig: Signal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub static signals_to_default: Lazy<libc::sigset_t> = Lazy::new(|| {
|
pub static signals_to_default: Lazy<libc::sigset_t> = Lazy::new(|| {
|
||||||
let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
|
let mut set = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigemptyset(&mut set) };
|
unsafe { libc::sigemptyset(set.as_mut_ptr()) };
|
||||||
for data in SIGNAL_TABLE.iter() {
|
for data in SIGNAL_TABLE.iter() {
|
||||||
// If SIGHUP is being ignored (e.g., because were were run via `nohup`) don't reset it.
|
// 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
|
// 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.
|
// affect processes we spawn. They should get the default behavior for those signals.
|
||||||
if data.signal == libc::SIGHUP {
|
if data.signal == libc::SIGHUP {
|
||||||
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
|
let mut act = MaybeUninit::uninit();
|
||||||
unsafe { libc::sigaction(data.signal.code(), std::ptr::null(), &mut act) };
|
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 {
|
if act.sa_sigaction == libc::SIG_IGN {
|
||||||
continue;
|
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.
|
/// Ensure we did not inherit any blocked signals. See issue #3964.
|
||||||
pub fn signal_unblock_all() {
|
pub fn signal_unblock_all() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut iset: libc::sigset_t = std::mem::zeroed();
|
let mut iset = MaybeUninit::uninit();
|
||||||
libc::sigemptyset(&mut iset);
|
libc::sigemptyset(iset.as_mut_ptr());
|
||||||
libc::sigprocmask(libc::SIG_SETMASK, &iset, std::ptr::null_mut());
|
libc::sigprocmask(libc::SIG_SETMASK, iset.as_ptr(), std::ptr::null_mut());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,11 @@ pub(crate) enum TerminalCommand<'a> {
|
|||||||
EnterBoldMode,
|
EnterBoldMode,
|
||||||
EnterDimMode,
|
EnterDimMode,
|
||||||
EnterItalicsMode,
|
EnterItalicsMode,
|
||||||
EnterUnderlineMode,
|
EnterUnderlineMode(UnderlineStyle),
|
||||||
EnterReverseMode,
|
EnterReverseMode,
|
||||||
EnterStandoutMode,
|
EnterStandoutMode,
|
||||||
ExitItalicsMode,
|
ExitItalicsMode,
|
||||||
ExitUnderlineMode,
|
ExitUnderlineMode,
|
||||||
EnterCurlyUnderlineMode,
|
|
||||||
|
|
||||||
// Screen clearing
|
// Screen clearing
|
||||||
ClearScreen,
|
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),
|
EnterBoldMode => ti(self, b"\x1b[1m", |t| &t.enter_bold_mode),
|
||||||
EnterDimMode => ti(self, b"\x1b[2m", |t| &t.enter_dim_mode),
|
EnterDimMode => ti(self, b"\x1b[2m", |t| &t.enter_dim_mode),
|
||||||
EnterItalicsMode => ti(self, b"\x1b[3m", |t| &t.enter_italics_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),
|
EnterReverseMode => ti(self, b"\x1b[7m", |t| &t.enter_reverse_mode),
|
||||||
EnterStandoutMode => ti(self, b"\x1b[7m", |t| &t.enter_standout_mode),
|
EnterStandoutMode => ti(self, b"\x1b[7m", |t| &t.enter_standout_mode),
|
||||||
ExitItalicsMode => ti(self, b"\x1b[23m", |t| &t.exit_italics_mode),
|
ExitItalicsMode => ti(self, b"\x1b[23m", |t| &t.exit_italics_mode),
|
||||||
ExitUnderlineMode => ti(self, b"\x1b[24m", |t| &t.exit_underline_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),
|
ClearScreen => ti(self, b"\x1b[H\x1b[2J", |term| &term.clear_screen),
|
||||||
ClearToEndOfLine => ti(self, b"\x1b[K", |term| &term.clr_eol),
|
ClearToEndOfLine => ti(self, b"\x1b[K", |term| &term.clr_eol),
|
||||||
ClearToEndOfScreen => ti(self, b"\x1b[J", |term| &term.clr_eos),
|
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()
|
!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 {
|
fn palette_color(out: &mut impl Output, paintable: Paintable, mut idx: u8) -> bool {
|
||||||
if only_grayscale() && !(Color::Named { idx }).is_grayscale() {
|
if only_grayscale() && !(Color::Named { idx }).is_grayscale() {
|
||||||
return false;
|
return false;
|
||||||
@@ -515,9 +526,9 @@ fn set_text_face_internal(&mut self, face: TextFace, salvage_unreadable: bool) {
|
|||||||
let style = face.style;
|
let style = face.style;
|
||||||
|
|
||||||
use TerminalCommand::{
|
use TerminalCommand::{
|
||||||
DefaultBackgroundColor, DefaultUnderlineColor, EnterBoldMode, EnterCurlyUnderlineMode,
|
DefaultBackgroundColor, DefaultUnderlineColor, EnterBoldMode, EnterDimMode,
|
||||||
EnterDimMode, EnterItalicsMode, EnterReverseMode, EnterStandoutMode,
|
EnterItalicsMode, EnterReverseMode, EnterStandoutMode, EnterUnderlineMode,
|
||||||
EnterUnderlineMode, ExitAttributeMode, ExitItalicsMode, ExitUnderlineMode,
|
ExitAttributeMode, ExitItalicsMode, ExitUnderlineMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Removes all styles that are individually resettable.
|
// 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;
|
self.last.style.underline_style = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(underline) => {
|
Some(underline_style) => {
|
||||||
if self.write_command(match underline {
|
if self.write_command(EnterUnderlineMode(underline_style)) {
|
||||||
UnderlineStyle::Single => EnterUnderlineMode,
|
self.last.style.underline_style = Some(underline_style);
|
||||||
UnderlineStyle::Curly => EnterCurlyUnderlineMode,
|
|
||||||
}) {
|
|
||||||
self.last.style.underline_style = Some(underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user