mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-05 00:01:15 -03:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5bc7bd5f9 | ||
|
|
2b3bd29588 | ||
|
|
0be3f9e57e | ||
|
|
2524ece2cc | ||
|
|
b975472828 | ||
|
|
20427ff1f6 | ||
|
|
5b3b825ab2 | ||
|
|
ccd3348eed | ||
|
|
845b9be1f5 | ||
|
|
400f2b130a | ||
|
|
362f7cedf6 | ||
|
|
2c959469f0 | ||
|
|
6c34bcf8f6 | ||
|
|
f510b62b7f | ||
|
|
b31387416d | ||
|
|
941a6cb434 | ||
|
|
931072f5d1 | ||
|
|
f4f9db73da | ||
|
|
9ef3f30c56 | ||
|
|
d19c927760 | ||
|
|
22e5b21f10 | ||
|
|
f0d2444769 | ||
|
|
7975060e4a | ||
|
|
354dc3d272 | ||
|
|
7640e95bd7 | ||
|
|
767115a93d | ||
|
|
f0c8788a52 | ||
|
|
a3cbb01b27 | ||
|
|
d630b4ae8a | ||
|
|
a2c5b2a567 | ||
|
|
18295f4402 | ||
|
|
443fd604cc | ||
|
|
9e022ff7cf | ||
|
|
aba927054f |
@@ -1,9 +1,29 @@
|
|||||||
|
fish 4.3.2 (released December 30, 2025)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
This release fixes the following problems identified in 4.3.0:
|
||||||
|
|
||||||
|
- Pre-built macOS packages failed to start due to a ``Malformed Mach-O file`` error (:issue:`12224`).
|
||||||
|
- ``extra_functionsdir`` (usually ``vendor_functions.d``) and friends were not used (:issue:`12226`).
|
||||||
|
- Sample config file ``~/.config/fish/config.fish/`` and config directories ``~/.config/fish/conf.d/``, ``~/.config/fish/completions/`` and ``~/.config/fish/functions/`` were recreated on every startup instead of only the first time fish runs on a system (:issue:`12230`).
|
||||||
|
- Spurious echo of ``^[[I`` in some scenarios (:issue:`12232`).
|
||||||
|
- Infinite prompt redraw loop on some prompts (:issue:`12233`).
|
||||||
|
- The removal of pre-built HTML docs from tarballs revealed that cross compilation is broken because we use ``${CMAKE_BINARY_DIR}/fish_indent`` for building HTML docs.
|
||||||
|
As a workaround, the new CMake build option ``FISH_INDENT_FOR_BUILDING_DOCS`` can be set to the path of a runnable ``fish_indent`` binary.
|
||||||
|
|
||||||
|
fish 4.3.1 (released December 28, 2025)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
This release fixes the following problem identified in 4.3.0:
|
||||||
|
|
||||||
|
- Possible crash after expanding an abbreviation (:issue:`12223`).
|
||||||
|
|
||||||
fish 4.3.0 (released December 28, 2025)
|
fish 4.3.0 (released December 28, 2025)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
Deprecations and removed features
|
Deprecations and removed features
|
||||||
---------------------------------
|
---------------------------------
|
||||||
- fish no longer sets :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
|
- fish no longer sets user-facing :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
|
||||||
Specifically, the ``fish_color_*``, ``fish_pager_color_*`` and ``fish_key_bindings`` variables are now set in the global scope by default.
|
Specifically, the ``fish_color_*``, ``fish_pager_color_*`` and ``fish_key_bindings`` variables are now set in the global scope by default.
|
||||||
After upgrading to 4.3.0, fish will (once and never again) migrate these universals to globals set at startup in the
|
After upgrading to 4.3.0, fish will (once and never again) migrate these universals to globals set at startup in the
|
||||||
``~/.config/fish/conf.d/fish_frozen_theme.fish`` and
|
``~/.config/fish/conf.d/fish_frozen_theme.fish`` and
|
||||||
@@ -30,7 +50,7 @@ Interactive improvements
|
|||||||
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
|
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
|
||||||
- Prefix-matching completions are now shown even if they don't match the case typed by the user (:issue:`7944`).
|
- Prefix-matching completions are now shown even if they don't match the case typed by the user (:issue:`7944`).
|
||||||
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
|
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
|
||||||
- When using the exe name (``foo.exe``), fish will use to the description and completions for ``foo`` if there are none for ``foo.exe``.
|
- When using the exe name (``foo.exe``), fish will use the description and completions for ``foo`` if there are none for ``foo.exe``.
|
||||||
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
|
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
|
||||||
|
|
||||||
New or improved bindings
|
New or improved bindings
|
||||||
@@ -45,8 +65,6 @@ Improved terminal support
|
|||||||
- The working directory is now reported on every fresh prompt (via OSC 7), fixing scenarios where a child process (like ``ssh``) left behind a stale working directory (:issue:`12191`).
|
- The working directory is now reported on every fresh prompt (via OSC 7), fixing scenarios where a child process (like ``ssh``) left behind a stale working directory (:issue:`12191`).
|
||||||
- OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`).
|
- OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`).
|
||||||
- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS <status-terminal-os>`.
|
- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS <status-terminal-os>`.
|
||||||
- Focus reporting is enabled unconditionally, not just inside tmux.
|
|
||||||
To use it, define functions that handle the ``fish_focus_in`` or ``fish_focus_out`` :ref:`events <event>`.
|
|
||||||
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around some incompatible terminals.
|
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around some incompatible terminals.
|
||||||
|
|
||||||
For distributors and developers
|
For distributors and developers
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -152,7 +152,7 @@ checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fish"
|
name = "fish"
|
||||||
version = "4.3.0"
|
version = "4.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cc",
|
"cc",
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ debug = true
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "fish"
|
name = "fish"
|
||||||
version = "4.3.0"
|
version = "4.3.2"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
default-run = "fish"
|
default-run = "fish"
|
||||||
|
|||||||
@@ -158,6 +158,11 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
|
|||||||
- Rust_CARGO=path - the path to cargo. If not set, cmake will check $PATH and ~/.cargo/bin
|
- Rust_CARGO=path - the path to cargo. If not set, cmake will check $PATH and ~/.cargo/bin
|
||||||
- Rust_CARGO_TARGET=target - the target to pass to cargo. Set this for cross-compilation.
|
- Rust_CARGO_TARGET=target - the target to pass to cargo. Set this for cross-compilation.
|
||||||
- WITH_DOCS=ON|OFF - whether to build the documentation. By default, this is ON when Sphinx is installed.
|
- WITH_DOCS=ON|OFF - whether to build the documentation. By default, this is ON when Sphinx is installed.
|
||||||
|
- FISH_INDENT_FOR_BUILDING_DOCS - useful for cross-compilation.
|
||||||
|
Set this to the path to the ``fish_indent`` executable to use for building HTML docs.
|
||||||
|
By default, ``${CMAKE_BINARY_DIR}/fish_indent`` will be used.
|
||||||
|
If that's not runnable on the compile host,
|
||||||
|
you can build a native one with ``cargo build --bin fish_indent`` and set this to ``$PWD/target/debug/fish_indent``.
|
||||||
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
|
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
|
||||||
- WITH_GETTEXT=ON|OFF - whether to include translations.
|
- WITH_GETTEXT=ON|OFF - whether to include translations.
|
||||||
- extra_functionsdir, extra_completionsdir and extra_confdir - to compile in an additional directory to be searched for functions, completions and configuration snippets
|
- extra_functionsdir, extra_completionsdir and extra_confdir - to compile in an additional directory to be searched for functions, completions and configuration snippets
|
||||||
|
|||||||
3
build.rs
3
build.rs
@@ -40,9 +40,6 @@ fn main() {
|
|||||||
// the source directory is the current working directory of the build script
|
// the source directory is the current working directory of the build script
|
||||||
rsconf::set_env_value("FISH_BUILD_VERSION", version);
|
rsconf::set_env_value("FISH_BUILD_VERSION", version);
|
||||||
|
|
||||||
// safety: single-threaded code.
|
|
||||||
unsafe { std::env::set_var("FISH_BUILD_VERSION", version) };
|
|
||||||
|
|
||||||
fish_build_helper::rebuild_if_embedded_path_changed("share");
|
fish_build_helper::rebuild_if_embedded_path_changed("share");
|
||||||
|
|
||||||
let build = cc::Build::new();
|
let build = cc::Build::new();
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ git_permission_failed=0
|
|||||||
|
|
||||||
# First see if there is a version file (included in release tarballs),
|
# First see if there is a version file (included in release tarballs),
|
||||||
# then try git-describe, then default.
|
# then try git-describe, then default.
|
||||||
if test -f version
|
if test -f "$FISH_BASE_DIR"/version
|
||||||
then
|
then
|
||||||
VN=$(cat version) || VN="$DEF_VER"
|
VN=$(cat "$FISH_BASE_DIR"/version) || VN="$DEF_VER"
|
||||||
else
|
else
|
||||||
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
|
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
|
||||||
:
|
:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ NOTARIZE=
|
|||||||
|
|
||||||
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
|
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
|
||||||
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.12'
|
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.12'
|
||||||
cmake_args=
|
cmake_args=()
|
||||||
|
|
||||||
while getopts "c:sf:i:p:e:nj:" opt; do
|
while getopts "c:sf:i:p:e:nj:" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
@@ -82,17 +82,16 @@ do_cmake() {
|
|||||||
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
|
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
|
# Build for x86-64 but do not install; instead we will make a fat binary inside the root.
|
||||||
{ cd "$PKGDIR/build_x86_64" \
|
{ cd "$PKGDIR/build_x86_64" \
|
||||||
&& do_cmake -DRust_CARGO_TARGET=x86_64-apple-darwin \
|
&& do_cmake -DRust_CARGO_TARGET=x86_64-apple-darwin \
|
||||||
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
||||||
|
|
||||||
# Fatten them up.
|
# Fatten it up.
|
||||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
FILE=$PKGDIR/root/usr/local/bin/fish
|
||||||
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
|
X86_FILE=$PKGDIR/build_x86_64/$(basename "$FILE")
|
||||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||||
chmod 755 "$FILE"
|
chmod 755 "$FILE"
|
||||||
done
|
|
||||||
|
|
||||||
if test -n "$SIGN"; then
|
if test -n "$SIGN"; then
|
||||||
echo "Signing executables"
|
echo "Signing executables"
|
||||||
@@ -105,9 +104,7 @@ if test -n "$SIGN"; then
|
|||||||
if [ -n "$ENTITLEMENTS_FILE" ]; then
|
if [ -n "$ENTITLEMENTS_FILE" ]; then
|
||||||
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
|
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
|
||||||
fi
|
fi
|
||||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
(set +x; rcodesign sign "${ARGS[@]}" "$PKGDIR"/root/usr/local/bin/fish)
|
||||||
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
|
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
|
||||||
@@ -128,15 +125,13 @@ fi
|
|||||||
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
|
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||||
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
|
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||||
|
|
||||||
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
# Make the app's /usr/local/bin/fish binary universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||||
cd "$PKGDIR/build_arm64"
|
cd "$PKGDIR/build_arm64"
|
||||||
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
|
FILE=fish.app/Contents/Resources/base/usr/local/bin/fish
|
||||||
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
|
X86_FILE=$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")
|
||||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||||
|
# macho-universal-create screws up the permissions.
|
||||||
# macho-universal-create screws up the permissions.
|
chmod 755 "$FILE"
|
||||||
chmod 755 "$FILE"
|
|
||||||
done
|
|
||||||
|
|
||||||
if test -n "$SIGN"; then
|
if test -n "$SIGN"; then
|
||||||
echo "Signing app"
|
echo "Signing app"
|
||||||
|
|||||||
@@ -86,9 +86,10 @@ sed -i \
|
|||||||
CommitVersion() {
|
CommitVersion() {
|
||||||
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
|
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
|
||||||
cargo fetch --offline
|
cargo fetch --offline
|
||||||
# debchange is a Debian script to manage the Debian changelog, but
|
if [ "$1" = "$version" ]; then
|
||||||
# it's too annoying to install everywhere. Just do it by hand.
|
# debchange is a Debian script to manage the Debian changelog, but
|
||||||
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
|
# it's too annoying to install everywhere. Just do it by hand.
|
||||||
|
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
|
||||||
fish (${version}-1) stable; urgency=medium
|
fish (${version}-1) stable; urgency=medium
|
||||||
|
|
||||||
* Release of new version $version.
|
* Release of new version $version.
|
||||||
@@ -98,8 +99,10 @@ fish (${version}-1) stable; urgency=medium
|
|||||||
-- $committer $(date -R)
|
-- $committer $(date -R)
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
mv contrib/debian/changelog.new contrib/debian/changelog
|
mv contrib/debian/changelog.new contrib/debian/changelog
|
||||||
git add CHANGELOG.rst Cargo.toml Cargo.lock contrib/debian/changelog
|
git add contrib/debian/changelog
|
||||||
|
fi
|
||||||
|
git add CHANGELOG.rst Cargo.toml Cargo.lock
|
||||||
git commit -m "$2
|
git commit -m "$2
|
||||||
|
|
||||||
Created by ./build_tools/release.sh $version"
|
Created by ./build_tools/release.sh $version"
|
||||||
@@ -296,6 +299,8 @@ milestone_number() {
|
|||||||
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
|
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
|
||||||
--method PATCH --raw-field state=closed
|
--method PATCH --raw-field state=closed
|
||||||
|
|
||||||
|
next_minor_version=$(echo "$minor_version" |
|
||||||
|
awk -F. '{ printf "%s.%s", $1, $2+1 }')
|
||||||
if [ -z "$(milestone_number "$next_minor_version")" ]; then
|
if [ -z "$(milestone_number "$next_minor_version")" ]; then
|
||||||
gh_api_repo milestones --method POST \
|
gh_api_repo milestones --method POST \
|
||||||
--raw-field title="fish $next_minor_version"
|
--raw-field title="fish $next_minor_version"
|
||||||
|
|||||||
@@ -12,11 +12,21 @@ set(SPHINX_BUILD_DIR "${SPHINX_ROOT_DIR}/build")
|
|||||||
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
|
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
|
||||||
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
|
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
|
||||||
|
|
||||||
# sphinx-docs uses fish_indent for highlighting.
|
set(FISH_INDENT_FOR_BUILDING_DOCS "" CACHE FILEPATH "Path to fish_indent executable for building HTML docs")
|
||||||
# Prepend the output dir of fish_indent to PATH.
|
|
||||||
|
if(FISH_INDENT_FOR_BUILDING_DOCS)
|
||||||
|
get_filename_component(FISH_INDENT_DIR "${FISH_INDENT_FOR_BUILDING_DOCS}" DIRECTORY)
|
||||||
|
set(SPHINX_HTML_FISH_INDENT_PATH ${FISH_INDENT_DIR})
|
||||||
|
set(SPHINX_HTML_FISH_INDENT_DEP)
|
||||||
|
else()
|
||||||
|
set(SPHINX_HTML_FISH_INDENT_PATH ${CMAKE_BINARY_DIR})
|
||||||
|
set(SPHINX_HTML_FISH_INDENT_DEP fish_indent)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_target(sphinx-docs
|
add_custom_target(sphinx-docs
|
||||||
mkdir -p ${SPHINX_HTML_DIR}/_static/
|
mkdir -p ${SPHINX_HTML_DIR}/_static/
|
||||||
COMMAND env PATH="${CMAKE_BINARY_DIR}:$$PATH"
|
COMMAND env FISH_BUILD_VERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/${FBVF}
|
||||||
|
PATH="${SPHINX_HTML_FISH_INDENT_PATH}:$$PATH"
|
||||||
${SPHINX_EXECUTABLE}
|
${SPHINX_EXECUTABLE}
|
||||||
-j auto
|
-j auto
|
||||||
-q -b html
|
-q -b html
|
||||||
@@ -24,7 +34,10 @@ add_custom_target(sphinx-docs
|
|||||||
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
|
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
|
||||||
"${SPHINX_SRC_DIR}"
|
"${SPHINX_SRC_DIR}"
|
||||||
"${SPHINX_HTML_DIR}"
|
"${SPHINX_HTML_DIR}"
|
||||||
DEPENDS ${SPHINX_SRC_DIR}/fish_indent_lexer.py fish_indent
|
DEPENDS
|
||||||
|
CHECK-FISH-BUILD-VERSION-FILE
|
||||||
|
${SPHINX_SRC_DIR}/fish_indent_lexer.py
|
||||||
|
${SPHINX_HTML_FISH_INDENT_DEP}
|
||||||
COMMENT "Building HTML documentation with Sphinx")
|
COMMENT "Building HTML documentation with Sphinx")
|
||||||
|
|
||||||
add_custom_target(sphinx-manpages
|
add_custom_target(sphinx-manpages
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
fish (4.3.2-1) stable; urgency=medium
|
||||||
|
|
||||||
|
* Release of new version 4.3.2.
|
||||||
|
|
||||||
|
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.2 for details.
|
||||||
|
|
||||||
|
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 30 Dec 2025 17:21:04 +0100
|
||||||
|
|
||||||
|
fish (4.3.1-1) stable; urgency=medium
|
||||||
|
|
||||||
|
* Release of new version 4.3.1.
|
||||||
|
|
||||||
|
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.1 for details.
|
||||||
|
|
||||||
|
-- Johannes Altmanninger <aclopte@gmail.com> Sun, 28 Dec 2025 16:54:44 +0100
|
||||||
|
|
||||||
fish (4.3.0-1) stable; urgency=medium
|
fish (4.3.0-1) stable; urgency=medium
|
||||||
|
|
||||||
* Release of new version 4.3.0.
|
* Release of new version 4.3.0.
|
||||||
|
|||||||
@@ -118,13 +118,12 @@ author = "fish-shell developers"
|
|||||||
issue_url = "https://github.com/fish-shell/fish-shell/issues"
|
issue_url = "https://github.com/fish-shell/fish-shell/issues"
|
||||||
|
|
||||||
# Parsing FISH-BUILD-VERSION-FILE is possible but hard to ensure that it is in the right place
|
# Parsing FISH-BUILD-VERSION-FILE is possible but hard to ensure that it is in the right place
|
||||||
# fish_indent is guaranteed to be on PATH for the Pygments highlighter anyway
|
|
||||||
if "FISH_BUILD_VERSION_FILE" in os.environ:
|
if "FISH_BUILD_VERSION_FILE" in os.environ:
|
||||||
|
# From Cmake
|
||||||
f = open(os.environ["FISH_BUILD_VERSION_FILE"], "r")
|
f = open(os.environ["FISH_BUILD_VERSION_FILE"], "r")
|
||||||
ret = f.readline().strip()
|
ret = f.readline().strip()
|
||||||
elif "FISH_BUILD_VERSION" in os.environ:
|
|
||||||
ret = os.environ["FISH_BUILD_VERSION"]
|
|
||||||
else:
|
else:
|
||||||
|
# From Cargo, or no build system.
|
||||||
ret = subprocess.check_output(
|
ret = subprocess.check_output(
|
||||||
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
|
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
|
||||||
).decode("utf-8")
|
).decode("utf-8")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
complete -c help -n __fish_is_first_arg -x -a '(
|
complete -c help -n __fish_is_first_arg -x -a '(
|
||||||
{
|
{
|
||||||
__fish_data_with_file help_sections (command -v cat) |
|
status get-file help_sections |
|
||||||
string replace -r "^index(#|\$)" introduction\$1
|
string replace -r "^index(#|\$)" introduction\$1
|
||||||
printf cmds/%s\n ! . : \[ \{
|
printf cmds/%s\n ! . : \[ \{
|
||||||
} |
|
} |
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ end
|
|||||||
set -l __extra_completionsdir
|
set -l __extra_completionsdir
|
||||||
set -l __extra_functionsdir
|
set -l __extra_functionsdir
|
||||||
set -l __extra_confdir
|
set -l __extra_confdir
|
||||||
__fish_data_with_file __fish_build_paths.fish source
|
status get-file __fish_build_paths.fish | source
|
||||||
|
|
||||||
# Compute the directories for vendor configuration. We want to include
|
# Compute the directories for vendor configuration. We want to include
|
||||||
# all of XDG_DATA_DIRS, as well as the __extra_* dirs defined above.
|
# all of XDG_DATA_DIRS, as well as the __extra_* dirs defined above.
|
||||||
@@ -205,7 +205,7 @@ if command -q kill
|
|||||||
end
|
end
|
||||||
|
|
||||||
if status is-interactive
|
if status is-interactive
|
||||||
__fish_theme_migrate
|
__fish_migrate
|
||||||
end
|
end
|
||||||
fish_config theme choose default --no-override
|
fish_config theme choose default --no-override
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,6 @@
|
|||||||
#
|
#
|
||||||
function __fish_config_interactive -d "Initializations that should be performed when entering interactive mode"
|
function __fish_config_interactive -d "Initializations that should be performed when entering interactive mode"
|
||||||
functions -e __fish_config_interactive
|
functions -e __fish_config_interactive
|
||||||
# Create empty configuration directores if they do not already exist
|
|
||||||
test -e $__fish_config_dir/completions/ -a -e $__fish_config_dir/conf.d/ -a -e $__fish_config_dir/functions/ ||
|
|
||||||
mkdir -p $__fish_config_dir/{completions, conf.d, functions}
|
|
||||||
|
|
||||||
# Create config.fish with some boilerplate if it does not exist
|
|
||||||
test -e $__fish_config_dir/config.fish || echo "\
|
|
||||||
if status is-interactive
|
|
||||||
# Commands to run in interactive sessions can go here
|
|
||||||
end" >$__fish_config_dir/config.fish
|
|
||||||
|
|
||||||
set -g __fish_active_key_bindings
|
set -g __fish_active_key_bindings
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# localization: skip(private)
|
# localization: skip(private)
|
||||||
function __fish_data_with_file
|
function __fish_config_with_file
|
||||||
set -l path $argv[1]
|
set -l path $argv[1]
|
||||||
set -l cmd $argv[2..]
|
set -l cmd $argv[2..]
|
||||||
if string match -rq -- ^/ $path
|
if string match -rq -- ^/ $path
|
||||||
@@ -1,9 +1,27 @@
|
|||||||
# localization: skip(private)
|
# localization: skip(private)
|
||||||
function __fish_theme_migrate
|
function __fish_migrate
|
||||||
functions -e __fish_theme_migrate
|
functions -e __fish_migrate
|
||||||
|
|
||||||
|
set -l migration_version 4300
|
||||||
|
|
||||||
# Maybe migrate.
|
# Maybe migrate.
|
||||||
if not set -q __fish_initialized || test $__fish_initialized -ge 4300
|
if set -q __fish_initialized && test $__fish_initialized -ge $migration_version
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create empty configuration directores if they do not already exist
|
||||||
|
test -e $__fish_config_dir/completions/ -a -e $__fish_config_dir/conf.d/ -a -e $__fish_config_dir/functions/ ||
|
||||||
|
mkdir -p $__fish_config_dir/{completions, conf.d, functions}
|
||||||
|
|
||||||
|
# Create config.fish with some boilerplate if it does not exist
|
||||||
|
test -e $__fish_config_dir/config.fish || echo "\
|
||||||
|
if status is-interactive
|
||||||
|
# Commands to run in interactive sessions can go here
|
||||||
|
end" >$__fish_config_dir/config.fish
|
||||||
|
|
||||||
|
set -l mark_migration_done set -U __fish_initialized $migration_version
|
||||||
|
if not set -q __fish_initialized
|
||||||
|
$mark_migration_done
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -22,7 +40,7 @@ function __fish_theme_migrate
|
|||||||
for varname in $theme_uvars
|
for varname in $theme_uvars
|
||||||
set -a theme_data "$(string escape -- $varname $$varname | string join " ")"
|
set -a theme_data "$(string escape -- $varname $$varname | string join " ")"
|
||||||
end
|
end
|
||||||
__fish_theme_freeze __fish_theme_migrate $theme_data
|
__fish_theme_freeze __fish_migrate $theme_data
|
||||||
set msg_suffix " by default."\n" Migrated them to global variables set in $(set_color --underline)$(
|
set msg_suffix " by default."\n" Migrated them to global variables set in $(set_color --underline)$(
|
||||||
__fish_unexpand_tilde $__fish_config_dir/conf.d/fish_frozen_theme.fish
|
__fish_unexpand_tilde $__fish_config_dir/conf.d/fish_frozen_theme.fish
|
||||||
)$(set_color normal)"
|
)$(set_color normal)"
|
||||||
@@ -35,6 +53,7 @@ function __fish_theme_migrate
|
|||||||
set -l relative_filename conf.d/fish_frozen_key_bindings.fish
|
set -l relative_filename conf.d/fish_frozen_key_bindings.fish
|
||||||
set -l filename $__fish_config_dir/$relative_filename
|
set -l filename $__fish_config_dir/$relative_filename
|
||||||
__fish_backup_config_files $relative_filename
|
__fish_backup_config_files $relative_filename
|
||||||
|
mkdir -p -- (path dirname -- $filename)
|
||||||
echo >$filename "\
|
echo >$filename "\
|
||||||
# This file was created by fish when upgrading to version 4.3, to migrate
|
# This file was created by fish when upgrading to version 4.3, to migrate
|
||||||
# the 'fish_key_bindings' variable from its old default scope (universal)
|
# the 'fish_key_bindings' variable from its old default scope (universal)
|
||||||
@@ -62,7 +81,7 @@ set --erase --universal fish_key_bindings"
|
|||||||
(set_color normal))
|
(set_color normal))
|
||||||
source $__fish_config_dir/$relative_filename
|
source $__fish_config_dir/$relative_filename
|
||||||
end
|
end
|
||||||
set -U __fish_initialized 4300
|
$mark_migration_done
|
||||||
if $removing_uvars
|
if $removing_uvars
|
||||||
echo -s (set_color --bold) 'fish:' (set_color normal) " upgraded to version 4.3:"
|
echo -s (set_color --bold) 'fish:' (set_color normal) " upgraded to version 4.3:"
|
||||||
string join \n -- $msg
|
string join \n -- $msg
|
||||||
@@ -6,15 +6,16 @@ function __fish_theme_cat -a theme_name
|
|||||||
echo >&2 Searched (__fish_theme_dir) "and `status list-files themes`"
|
echo >&2 Searched (__fish_theme_dir) "and `status list-files themes`"
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
set -l theme_data (__fish_data_with_file $theme_path cat)
|
set -l theme_data (if string match -q '/*' -- $theme_path; cat $theme_path; else status get-file $theme_path; end)
|
||||||
set -l allowed_lines \
|
set -l allowed_lines \
|
||||||
'\s*' \
|
'\s*' \
|
||||||
'\s*#.*' \
|
'\s*#.*' \
|
||||||
'\[(dark|light|unknown)\]' \
|
'\[(dark|light|unknown)\]' \
|
||||||
(__fish_theme_variable_filter)
|
(__fish_theme_variable_filter)
|
||||||
set allowed_lines "^($(string join -- '|' $allowed_lines))\$"
|
set allowed_lines "^($(string join -- '|' $allowed_lines))\$"
|
||||||
for line in $theme_data
|
printf '%s\n' $theme_data | string match -rvq -- $allowed_lines
|
||||||
string match -rq -- $allowed_lines $line
|
and for line in $theme_data
|
||||||
|
string match -rq -- $allowed_lines $theme_data
|
||||||
or printf >&2 "error: unsupported line in theme '%s': %s\n" $theme_name $line
|
or printf >&2 "error: unsupported line in theme '%s': %s\n" $theme_name $line
|
||||||
end
|
end
|
||||||
string join \n $theme_data
|
string join \n $theme_data
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ function __fish_theme_freeze
|
|||||||
__fish_backup_config_files $relative_path
|
__fish_backup_config_files $relative_path
|
||||||
|
|
||||||
set -l help_section interactive#syntax-highlighting
|
set -l help_section interactive#syntax-highlighting
|
||||||
__fish_data_with_file help_sections $(command -v grep) -Fxq $help_section
|
status get-file help_sections | string match -q $help_section
|
||||||
or echo "fish: internal error: missing help section '$help_section'"
|
or echo "fish: internal error: missing help section '$help_section'"
|
||||||
|
|
||||||
|
mkdir -p -- (path dirname -- $__fish_config_dir/conf.d)
|
||||||
printf >$__fish_config_dir/$relative_path %s\n \
|
printf >$__fish_config_dir/$relative_path %s\n \
|
||||||
$(test $data_source = __fish_theme_migrate &&
|
$(test $data_source = __fish_migrate &&
|
||||||
echo "\
|
echo "\
|
||||||
# This file was created by fish when upgrading to version 4.3, to migrate
|
# This file was created by fish when upgrading to version 4.3, to migrate
|
||||||
# theme variables from universal to global scope.") \
|
# theme variables from universal to global scope.") \
|
||||||
@@ -21,7 +22,7 @@ function __fish_theme_freeze
|
|||||||
# or
|
# or
|
||||||
# man fish-interactive | less +/^SYNTAX.HIGHLIGHTING
|
# man fish-interactive | less +/^SYNTAX.HIGHLIGHTING
|
||||||
# for appropriate commands to add to ~/.config/fish/config.fish instead." \
|
# for appropriate commands to add to ~/.config/fish/config.fish instead." \
|
||||||
$(test $data_source = __fish_theme_migrate &&
|
$(test $data_source = __fish_migrate &&
|
||||||
echo '# See also the release notes for fish 4.3.0 (run `help relnotes`).') \
|
echo '# See also the release notes for fish 4.3.0 (run `help relnotes`).') \
|
||||||
"" \
|
"" \
|
||||||
'set --global '$theme_data
|
'set --global '$theme_data
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ function fish_config --description "Launch fish's web based configuration"
|
|||||||
echo -s (set_color --underline) $promptname (set_color normal)
|
echo -s (set_color --underline) $promptname (set_color normal)
|
||||||
$fish -c '
|
$fish -c '
|
||||||
functions -e fish_right_prompt
|
functions -e fish_right_prompt
|
||||||
__fish_data_with_file $argv[1] source
|
__fish_config_with_file $argv[1] source
|
||||||
false
|
false
|
||||||
fish_prompt
|
fish_prompt
|
||||||
echo (set_color normal)
|
echo (set_color normal)
|
||||||
@@ -120,9 +120,9 @@ function fish_config --description "Launch fish's web based configuration"
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
__fish_config_prompt_reset
|
__fish_config_prompt_reset
|
||||||
__fish_data_with_file $prompt_path source
|
__fish_config_with_file $prompt_path source
|
||||||
if not functions -q fish_mode_prompt
|
if not functions -q fish_mode_prompt
|
||||||
__fish_data_with_file functions/fish_mode_prompt.fish source
|
status get-file functions/fish_mode_prompt.fish | source
|
||||||
end
|
end
|
||||||
case save
|
case save
|
||||||
if begin
|
if begin
|
||||||
@@ -142,7 +142,7 @@ function fish_config --description "Launch fish's web based configuration"
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
__fish_config_prompt_reset
|
__fish_config_prompt_reset
|
||||||
__fish_data_with_file $prompt_path source
|
__fish_config_with_file $prompt_path source
|
||||||
end
|
end
|
||||||
|
|
||||||
funcsave fish_prompt
|
funcsave fish_prompt
|
||||||
@@ -156,7 +156,7 @@ function fish_config --description "Launch fish's web based configuration"
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not functions -q fish_mode_prompt
|
if not functions -q fish_mode_prompt
|
||||||
__fish_data_with_file functions/fish_mode_prompt.fish source
|
status get-file functions/fish_mode_prompt.fish | source
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -287,7 +287,7 @@ function __fish_config_theme_choose
|
|||||||
|
|
||||||
set -l color_theme
|
set -l color_theme
|
||||||
__fish_config_theme_canonicalize
|
__fish_config_theme_canonicalize
|
||||||
set -l theme_data (type -q cat && __fish_theme_cat $theme_name)
|
set -l theme_data (__fish_theme_cat $theme_name)
|
||||||
or return
|
or return
|
||||||
set -l color_themes dark light unknown
|
set -l color_themes dark light unknown
|
||||||
set -l theme_is_color_theme_aware false
|
set -l theme_is_color_theme_aware false
|
||||||
@@ -340,7 +340,7 @@ function __fish_config_theme_choose
|
|||||||
end
|
end
|
||||||
|
|
||||||
set -l color_theme
|
set -l color_theme
|
||||||
string join \n -- $theme_data |
|
string match -re -- (__fish_theme_variable_filter)'|^\[.*\]$' $theme_data |
|
||||||
while read -lat toks
|
while read -lat toks
|
||||||
if $theme_is_color_theme_aware
|
if $theme_is_color_theme_aware
|
||||||
for ct in $color_themes
|
for ct in $color_themes
|
||||||
@@ -354,8 +354,8 @@ function __fish_config_theme_choose
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
set -l varname $toks[1]
|
set -l varname $toks[1]
|
||||||
string match -rq -- (__fish_theme_variable_filter) "$varname"
|
string match -q '[*' -- $varname
|
||||||
or continue
|
and continue
|
||||||
# If we're supposed to set universally, remove any shadowing globals
|
# If we're supposed to set universally, remove any shadowing globals
|
||||||
# so the change takes effect immediately (and there's no warning).
|
# so the change takes effect immediately (and there's no warning).
|
||||||
if test $scope = -U; and set -qg $varname
|
if test $scope = -U; and set -qg $varname
|
||||||
|
|||||||
@@ -131,21 +131,15 @@ function fish_delta
|
|||||||
printf (_ "%sUnmodified%s: %s\n") $colors[4] $colors[1] $file
|
printf (_ "%sUnmodified%s: %s\n") $colors[4] $colors[1] $file
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function __fish_delta_diff_maybe_file -a maybe_default_file
|
if $default_exists
|
||||||
# TODO Use "set -l foo (cat)" instead of the temp file.
|
|
||||||
# https://github.com/fish-shell/fish-shell/issues/206
|
|
||||||
set -l tmpfile (__fish_mktemp_relative fish-delta)
|
set -l tmpfile (__fish_mktemp_relative fish-delta)
|
||||||
cat $maybe_default_file >$tmpfile
|
status get-file $dir/$bn >$tmpfile
|
||||||
__fish_delta_diff $tmpfile
|
__fish_delta_diff $tmpfile
|
||||||
command rm $tmpfile
|
command rm $tmpfile
|
||||||
end
|
|
||||||
if $default_exists
|
|
||||||
__fish_data_with_file $dir/$bn __fish_delta_diff_maybe_file
|
|
||||||
else
|
else
|
||||||
__fish_delta_diff /dev/null
|
__fish_delta_diff /dev/null
|
||||||
end
|
end
|
||||||
functions --erase __fish_delta_diff
|
functions --erase __fish_delta_diff
|
||||||
functions --erase __fish_delta_diff_maybe_file
|
|
||||||
else
|
else
|
||||||
# Without diff, we can't really tell if the contents are the same.
|
# Without diff, we can't really tell if the contents are the same.
|
||||||
printf (_ "%sPossibly changed%s: %s\n") $colors[3] $colors[1] $file
|
printf (_ "%sPossibly changed%s: %s\n") $colors[3] $colors[1] $file
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function fish_update_completions --description "Update man-page based completion
|
|||||||
--cleanup-in $__fish_user_data_dir/generated_completions \
|
--cleanup-in $__fish_user_data_dir/generated_completions \
|
||||||
--cleanup-in $__fish_cache_dir/generated_completions
|
--cleanup-in $__fish_cache_dir/generated_completions
|
||||||
|
|
||||||
__fish_data_with_file tools/create_manpage_completions.py cat |
|
status get-file tools/create_manpage_completions.py |
|
||||||
if $detach
|
if $detach
|
||||||
# Run python directly in the background and swallow all output
|
# Run python directly in the background and swallow all output
|
||||||
# Orphan the job so that it continues to run in case of an early exit (#6269)
|
# Orphan the job so that it continues to run in case of an early exit (#6269)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ chromium-browser
|
|||||||
switch "$fish_help_item"
|
switch "$fish_help_item"
|
||||||
case ''
|
case ''
|
||||||
set fish_help_page index.html
|
set fish_help_page index.html
|
||||||
case (__fish_data_with_file help_sections (command -v cat) | string replace -r "^index(#|\$)" introduction\$1)
|
case (status get-file help_sections | string replace -r "^index(#|\$)" introduction\$1)
|
||||||
set fish_help_page (
|
set fish_help_page (
|
||||||
printf %s $fish_help_item |
|
printf %s $fish_help_item |
|
||||||
string replace -r '^introduction(#|$)' 'index$1' |
|
string replace -r '^introduction(#|$)' 'index$1' |
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
parse_constants::{ParseErrorList, ParseTreeFlags},
|
parse_constants::{ParseErrorList, ParseTreeFlags},
|
||||||
parse_tree::ParsedSource,
|
parse_tree::ParsedSource,
|
||||||
parse_util::parse_util_detect_errors_in_ast,
|
parse_util::parse_util_detect_errors_in_ast,
|
||||||
parser::{BlockType, CancelBehavior, Parser},
|
parser::{BlockType, CancelBehavior, Parser, ParserEnvSetMode},
|
||||||
path::path_get_config,
|
path::path_get_config,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
printf,
|
printf,
|
||||||
@@ -498,9 +498,9 @@ fn throwing_main() -> i32 {
|
|||||||
|
|
||||||
if is_interactive_session() && opts.no_config && !opts.no_exec {
|
if is_interactive_session() && opts.no_config && !opts.no_exec {
|
||||||
// If we have no config, we default to the default key bindings.
|
// If we have no config, we default to the default key bindings.
|
||||||
parser.vars().set_one(
|
parser.set_one(
|
||||||
L!("fish_key_bindings"),
|
L!("fish_key_bindings"),
|
||||||
EnvMode::UNEXPORT,
|
ParserEnvSetMode::new(EnvMode::UNEXPORT),
|
||||||
L!("fish_default_key_bindings").to_owned(),
|
L!("fish_default_key_bindings").to_owned(),
|
||||||
);
|
);
|
||||||
if function::exists(L!("fish_default_key_bindings"), parser) {
|
if function::exists(L!("fish_default_key_bindings"), parser) {
|
||||||
@@ -545,9 +545,9 @@ fn throwing_main() -> i32 {
|
|||||||
// Pass additional args as $argv.
|
// Pass additional args as $argv.
|
||||||
// Note that we *don't* support setting argv[0]/$0, unlike e.g. bash.
|
// Note that we *don't* support setting argv[0]/$0, unlike e.g. bash.
|
||||||
let list = &args[my_optind..];
|
let list = &args[my_optind..];
|
||||||
parser.vars().set(
|
parser.set_var(
|
||||||
L!("argv"),
|
L!("argv"),
|
||||||
EnvMode::default(),
|
ParserEnvSetMode::default(),
|
||||||
list.iter().map(|s| s.to_owned()).collect(),
|
list.iter().map(|s| s.to_owned()).collect(),
|
||||||
);
|
);
|
||||||
res = run_command_list(parser, &opts.batch_cmds);
|
res = run_command_list(parser, &opts.batch_cmds);
|
||||||
@@ -580,9 +580,9 @@ fn throwing_main() -> i32 {
|
|||||||
}
|
}
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
let list = &args[my_optind..];
|
let list = &args[my_optind..];
|
||||||
parser.vars().set(
|
parser.set_var(
|
||||||
L!("argv"),
|
L!("argv"),
|
||||||
EnvMode::default(),
|
ParserEnvSetMode::default(),
|
||||||
list.iter().map(|s| s.to_owned()).collect(),
|
list.iter().map(|s| s.to_owned()).collect(),
|
||||||
);
|
);
|
||||||
let rel_filename = &args[my_optind - 1];
|
let rel_filename = &args[my_optind - 1];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
use crate::abbrs::{self, Abbreviation, Position};
|
use crate::abbrs::{self, Abbreviation, Position};
|
||||||
use crate::common::{EscapeStringStyle, escape, escape_string, valid_func_name};
|
use crate::common::{EscapeStringStyle, escape, escape_string, valid_func_name};
|
||||||
use crate::env::{EnvMode, EnvStackSetResult};
|
use crate::env::{EnvMode, EnvStackSetResult};
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::re::{regex_make_anchored, to_boxed_chars};
|
use crate::re::{regex_make_anchored, to_boxed_chars};
|
||||||
use pcre2::utf32::{Regex, RegexBuilder};
|
use pcre2::utf32::{Regex, RegexBuilder};
|
||||||
|
|
||||||
@@ -460,7 +461,8 @@ fn abbr_erase(opts: &Options, parser: &Parser) -> BuiltinResult {
|
|||||||
let esc_src = escape(arg);
|
let esc_src = escape(arg);
|
||||||
if !esc_src.is_empty() {
|
if !esc_src.is_empty() {
|
||||||
let var_name = WString::from_str("_fish_abbr_") + esc_src.as_utfstr();
|
let var_name = WString::from_str("_fish_abbr_") + esc_src.as_utfstr();
|
||||||
let ret = parser.vars().remove(&var_name, EnvMode::UNIVERSAL);
|
let ret =
|
||||||
|
parser.remove_var(&var_name, ParserEnvSetMode::new(EnvMode::UNIVERSAL));
|
||||||
|
|
||||||
if ret == EnvStackSetResult::Ok {
|
if ret == EnvStackSetResult::Ok {
|
||||||
result = Ok(SUCCESS)
|
result = Ok(SUCCESS)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
use crate::env::{EnvMode, EnvStack};
|
use crate::env::{EnvMode, EnvSetMode, EnvStack};
|
||||||
use crate::exec::exec_subshell;
|
use crate::exec::exec_subshell;
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::wutil::fish_iswalnum;
|
use crate::wutil::fish_iswalnum;
|
||||||
|
|
||||||
const VAR_NAME_PREFIX: &wstr = L!("_flag_");
|
const VAR_NAME_PREFIX: &wstr = L!("_flag_");
|
||||||
@@ -699,24 +700,31 @@ fn validate_arg<'opts>(
|
|||||||
return Ok(SUCCESS);
|
return Ok(SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vars = parser.vars();
|
parser.vars().push(true /* new_scope */);
|
||||||
vars.push(true /* new_scope */);
|
|
||||||
|
|
||||||
let env_mode = EnvMode::LOCAL | EnvMode::EXPORT;
|
let local_exported_mode = ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT);
|
||||||
vars.set_one(L!("_argparse_cmd"), env_mode, opts_name.to_owned());
|
parser.set_one(
|
||||||
|
L!("_argparse_cmd"),
|
||||||
|
local_exported_mode,
|
||||||
|
opts_name.to_owned(),
|
||||||
|
);
|
||||||
let flag_name = WString::from(VAR_NAME_PREFIX) + "name";
|
let flag_name = WString::from(VAR_NAME_PREFIX) + "name";
|
||||||
if is_long_flag {
|
if is_long_flag {
|
||||||
vars.set_one(&flag_name, env_mode, opt_spec.long_flag.to_owned());
|
parser.set_one(
|
||||||
} else {
|
|
||||||
vars.set_one(
|
|
||||||
&flag_name,
|
&flag_name,
|
||||||
env_mode,
|
local_exported_mode,
|
||||||
|
opt_spec.long_flag.to_owned(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
parser.set_one(
|
||||||
|
&flag_name,
|
||||||
|
local_exported_mode,
|
||||||
WString::from_chars(vec![opt_spec.short_flag]),
|
WString::from_chars(vec![opt_spec.short_flag]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
vars.set_one(
|
parser.set_one(
|
||||||
&(WString::from(VAR_NAME_PREFIX) + "value"),
|
&(WString::from(VAR_NAME_PREFIX) + "value"),
|
||||||
env_mode,
|
local_exported_mode,
|
||||||
woptarg.to_owned(),
|
woptarg.to_owned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -733,7 +741,7 @@ fn validate_arg<'opts>(
|
|||||||
streams.err.append(&output);
|
streams.err.append(&output);
|
||||||
streams.err.append_char('\n');
|
streams.err.append_char('\n');
|
||||||
}
|
}
|
||||||
vars.pop();
|
parser.vars().pop(parser.is_repainting());
|
||||||
retval.map(|()| SUCCESS)
|
retval.map(|()| SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,7 +1146,7 @@ fn check_min_max_args_constraints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Put the result of parsing the supplied args into the caller environment as local vars.
|
/// Put the result of parsing the supplied args into the caller environment as local vars.
|
||||||
fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
|
fn set_argparse_result_vars(vars: &EnvStack, local_mode: EnvSetMode, opts: ArgParseCmdOpts) {
|
||||||
for opt_spec in opts.options.values() {
|
for opt_spec in opts.options.values() {
|
||||||
if opt_spec.num_seen == 0 {
|
if opt_spec.num_seen == 0 {
|
||||||
continue;
|
continue;
|
||||||
@@ -1147,7 +1155,7 @@ fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
|
|||||||
if opt_spec.short_flag_valid {
|
if opt_spec.short_flag_valid {
|
||||||
let mut var_name = WString::from(VAR_NAME_PREFIX);
|
let mut var_name = WString::from(VAR_NAME_PREFIX);
|
||||||
var_name.push(opt_spec.short_flag);
|
var_name.push(opt_spec.short_flag);
|
||||||
vars.set(&var_name, EnvMode::LOCAL, opt_spec.vals.clone());
|
vars.set(&var_name, local_mode, opt_spec.vals.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opt_spec.long_flag.is_empty() {
|
if !opt_spec.long_flag.is_empty() {
|
||||||
@@ -1158,14 +1166,14 @@ fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
|
|||||||
.chars()
|
.chars()
|
||||||
.map(|c| if fish_iswalnum(c) { c } else { '_' });
|
.map(|c| if fish_iswalnum(c) { c } else { '_' });
|
||||||
let var_name_long: WString = VAR_NAME_PREFIX.chars().chain(long_flag).collect();
|
let var_name_long: WString = VAR_NAME_PREFIX.chars().chain(long_flag).collect();
|
||||||
vars.set(&var_name_long, EnvMode::LOCAL, opt_spec.vals.clone());
|
vars.set(&var_name_long, local_mode, opt_spec.vals.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = opts.args.into_iter().map(|s| s.into_owned()).collect();
|
let args = opts.args.into_iter().map(|s| s.into_owned()).collect();
|
||||||
vars.set(L!("argv"), EnvMode::LOCAL, args);
|
vars.set(L!("argv"), local_mode, args);
|
||||||
let args_opts = opts.args_opts.into_iter().map(|s| s.into_owned()).collect();
|
let args_opts = opts.args_opts.into_iter().map(|s| s.into_owned()).collect();
|
||||||
vars.set(L!("argv_opts"), EnvMode::LOCAL, args_opts);
|
vars.set(L!("argv_opts"), local_mode, args_opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
|
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
|
||||||
@@ -1213,7 +1221,11 @@ pub fn argparse(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
|||||||
|
|
||||||
check_min_max_args_constraints(&opts, streams)?;
|
check_min_max_args_constraints(&opts, streams)?;
|
||||||
|
|
||||||
set_argparse_result_vars(parser.vars(), opts);
|
set_argparse_result_vars(
|
||||||
|
parser.vars(),
|
||||||
|
parser.convert_env_set_mode(ParserEnvSetMode::new(EnvMode::LOCAL)),
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(SUCCESS)
|
Ok(SUCCESS)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
env::{EnvMode, Environment},
|
env::{EnvMode, Environment},
|
||||||
fds::{BEST_O_SEARCH, wopen_dir},
|
fds::{BEST_O_SEARCH, wopen_dir},
|
||||||
|
parser::ParserEnvSetMode,
|
||||||
path::path_apply_cdpath,
|
path::path_apply_cdpath,
|
||||||
wutil::{normalize_path, wperror, wreadlink},
|
wutil::{normalize_path, wperror, wreadlink},
|
||||||
};
|
};
|
||||||
@@ -127,7 +128,11 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
|||||||
// Stash the fd for the cwd in the parser.
|
// Stash the fd for the cwd in the parser.
|
||||||
parser.libdata_mut().cwd_fd = Some(dir_fd);
|
parser.libdata_mut().cwd_fd = Some(dir_fd);
|
||||||
|
|
||||||
parser.set_var_and_fire(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, vec![norm_dir]);
|
parser.set_var_and_fire(
|
||||||
|
L!("PWD"),
|
||||||
|
ParserEnvSetMode::new(EnvMode::EXPORT | EnvMode::GLOBAL),
|
||||||
|
vec![norm_dir],
|
||||||
|
);
|
||||||
return Ok(SUCCESS);
|
return Ok(SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
|||||||
|
|
||||||
let mut override_buffer = None;
|
let mut override_buffer = None;
|
||||||
|
|
||||||
const short_options: &wstr = L!("abijpctfxorhI:CBELSsP");
|
let short_options = L!("abijpctfxorhI:CBELSsP");
|
||||||
let long_options: &[WOption] = &[
|
let long_options: &[WOption] = &[
|
||||||
wopt(L!("append"), ArgType::NoArgument, 'a'),
|
wopt(L!("append"), ArgType::NoArgument, 'a'),
|
||||||
wopt(L!("insert"), ArgType::NoArgument, 'i'),
|
wopt(L!("insert"), ArgType::NoArgument, 'i'),
|
||||||
@@ -399,7 +399,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
|||||||
// Don't enqueue a repaint if we're currently in the middle of one,
|
// Don't enqueue a repaint if we're currently in the middle of one,
|
||||||
// because that's an infinite loop.
|
// because that's an infinite loop.
|
||||||
if matches!(cmd, RL::RepaintMode | RL::ForceRepaint | RL::Repaint)
|
if matches!(cmd, RL::RepaintMode | RL::ForceRepaint | RL::Repaint)
|
||||||
&& parser.libdata().is_repaint
|
&& parser.is_repainting()
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Implementation of the fg builtin.
|
//! Implementation of the fg builtin.
|
||||||
|
|
||||||
use crate::fds::make_fd_blocking;
|
use crate::fds::make_fd_blocking;
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::reader::{reader_save_screen_state, reader_write_title};
|
use crate::reader::{reader_save_screen_state, reader_write_title};
|
||||||
use crate::tokenizer::tok_command;
|
use crate::tokenizer::tok_command;
|
||||||
use crate::wutil::perror;
|
use crate::wutil::perror;
|
||||||
@@ -123,7 +124,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
|||||||
// Provide value for `status current-command`
|
// Provide value for `status current-command`
|
||||||
parser.libdata_mut().status_vars.command = ft.clone();
|
parser.libdata_mut().status_vars.command = ft.clone();
|
||||||
// Also provide a value for the deprecated fish 2.0 $_ variable
|
// Also provide a value for the deprecated fish 2.0 $_ variable
|
||||||
parser.set_var_and_fire(L!("_"), EnvMode::EXPORT, vec![ft]);
|
parser.set_var_and_fire(L!("_"), ParserEnvSetMode::new(EnvMode::EXPORT), vec![ft]);
|
||||||
// Provide value for `status current-commandline`
|
// Provide value for `status current-commandline`
|
||||||
parser.libdata_mut().status_vars.commandline = job.command().to_owned();
|
parser.libdata_mut().status_vars.commandline = job.command().to_owned();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ fn job_id_for_pid(pid: Pid, parser: &Parser) -> Option<u64> {
|
|||||||
/// Returns an exit status.
|
/// Returns an exit status.
|
||||||
fn parse_cmd_opts(
|
fn parse_cmd_opts(
|
||||||
opts: &mut FunctionCmdOpts,
|
opts: &mut FunctionCmdOpts,
|
||||||
optind: &mut usize,
|
|
||||||
argv: &mut [&wstr],
|
argv: &mut [&wstr],
|
||||||
parser: &Parser,
|
parser: &Parser,
|
||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
@@ -83,6 +82,34 @@ fn parse_cmd_opts(
|
|||||||
let print_hints = false;
|
let print_hints = false;
|
||||||
let mut handling_named_arguments = false;
|
let mut handling_named_arguments = false;
|
||||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
|
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
|
||||||
|
|
||||||
|
let mut validate_variable_name =
|
||||||
|
|streams: &mut IoStreams, varname: &wstr, read_only_ok: bool| {
|
||||||
|
if !valid_var_name(varname) {
|
||||||
|
streams.err.append(&varname_error(cmd, varname));
|
||||||
|
return Err(STATUS_INVALID_ARGS);
|
||||||
|
}
|
||||||
|
if !read_only_ok && is_read_only(varname) {
|
||||||
|
streams.err.append(&wgettext_fmt!(
|
||||||
|
"%s: variable '%s' is read-only\n",
|
||||||
|
cmd,
|
||||||
|
varname
|
||||||
|
));
|
||||||
|
return Err(STATUS_INVALID_ARGS);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
fn add_named_argument(
|
||||||
|
validate_variable_name: &mut impl FnMut(&mut IoStreams, &wstr, bool) -> Result<(), i32>,
|
||||||
|
streams: &mut IoStreams,
|
||||||
|
opts: &mut FunctionCmdOpts,
|
||||||
|
varname: &wstr,
|
||||||
|
) -> Result<(), i32> {
|
||||||
|
validate_variable_name(streams, varname, /*read_only_ok=*/ false)?;
|
||||||
|
opts.named_arguments.push(varname.to_owned());
|
||||||
|
Ok::<(), ErrorCode>(())
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(opt) = w.next_opt() {
|
while let Some(opt) = w.next_opt() {
|
||||||
// NON_OPTION_CHAR is returned when we reach a non-permuted non-option.
|
// NON_OPTION_CHAR is returned when we reach a non-permuted non-option.
|
||||||
if opt != 'a' && opt != NON_OPTION_CHAR {
|
if opt != 'a' && opt != NON_OPTION_CHAR {
|
||||||
@@ -91,17 +118,9 @@ fn parse_cmd_opts(
|
|||||||
match opt {
|
match opt {
|
||||||
NON_OPTION_CHAR => {
|
NON_OPTION_CHAR => {
|
||||||
// A positional argument we got because we use RETURN_IN_ORDER.
|
// A positional argument we got because we use RETURN_IN_ORDER.
|
||||||
let woptarg = w.woptarg.unwrap().to_owned();
|
let woptarg = w.woptarg.unwrap();
|
||||||
if handling_named_arguments {
|
if handling_named_arguments {
|
||||||
if is_read_only(&woptarg) {
|
add_named_argument(&mut validate_variable_name, streams, opts, woptarg)?;
|
||||||
streams.err.append(&wgettext_fmt!(
|
|
||||||
"%s: variable '%s' is read-only\n",
|
|
||||||
cmd,
|
|
||||||
woptarg
|
|
||||||
));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
opts.named_arguments.push(woptarg);
|
|
||||||
} else {
|
} else {
|
||||||
streams.err.append(&wgettext_fmt!(
|
streams.err.append(&wgettext_fmt!(
|
||||||
"%s: %s: unexpected positional argument",
|
"%s: %s: unexpected positional argument",
|
||||||
@@ -127,10 +146,7 @@ fn parse_cmd_opts(
|
|||||||
}
|
}
|
||||||
'v' => {
|
'v' => {
|
||||||
let name = w.woptarg.unwrap().to_owned();
|
let name = w.woptarg.unwrap().to_owned();
|
||||||
if !valid_var_name(&name) {
|
validate_variable_name(streams, &name, /*read_only_ok=*/ true)?;
|
||||||
streams.err.append(&varname_error(cmd, &name));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
opts.events.push(EventDescription::Variable { name });
|
opts.events.push(EventDescription::Variable { name });
|
||||||
}
|
}
|
||||||
'e' => {
|
'e' => {
|
||||||
@@ -175,17 +191,13 @@ fn parse_cmd_opts(
|
|||||||
opts.events.push(e);
|
opts.events.push(e);
|
||||||
}
|
}
|
||||||
'a' => {
|
'a' => {
|
||||||
let name = w.woptarg.unwrap().to_owned();
|
|
||||||
if is_read_only(&name) {
|
|
||||||
streams.err.append(&wgettext_fmt!(
|
|
||||||
"%s: variable '%s' is read-only\n",
|
|
||||||
cmd,
|
|
||||||
name
|
|
||||||
));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
handling_named_arguments = true;
|
handling_named_arguments = true;
|
||||||
opts.named_arguments.push(name);
|
add_named_argument(
|
||||||
|
&mut validate_variable_name,
|
||||||
|
streams,
|
||||||
|
opts,
|
||||||
|
w.woptarg.unwrap(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
'S' => {
|
'S' => {
|
||||||
opts.shadow_scope = false;
|
opts.shadow_scope = false;
|
||||||
@@ -195,10 +207,7 @@ fn parse_cmd_opts(
|
|||||||
}
|
}
|
||||||
'V' => {
|
'V' => {
|
||||||
let woptarg = w.woptarg.unwrap();
|
let woptarg = w.woptarg.unwrap();
|
||||||
if !valid_var_name(woptarg) {
|
validate_variable_name(streams, woptarg, /*read_only_ok=*/ false)?;
|
||||||
streams.err.append(&varname_error(cmd, woptarg));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
opts.inherit_vars.push(woptarg.to_owned());
|
opts.inherit_vars.push(woptarg.to_owned());
|
||||||
}
|
}
|
||||||
'h' => {
|
'h' => {
|
||||||
@@ -228,7 +237,23 @@ fn parse_cmd_opts(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*optind = w.wopt_index;
|
let optind = w.wopt_index;
|
||||||
|
if argv.len() != optind {
|
||||||
|
if !opts.named_arguments.is_empty() {
|
||||||
|
// Remaining arguments are named arguments.
|
||||||
|
for &arg in argv[optind..].iter() {
|
||||||
|
add_named_argument(&mut validate_variable_name, streams, opts, arg)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
streams.err.append(&wgettext_fmt!(
|
||||||
|
"%s: %s: unexpected positional argument",
|
||||||
|
cmd,
|
||||||
|
argv[optind],
|
||||||
|
));
|
||||||
|
return Err(STATUS_INVALID_ARGS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(SUCCESS)
|
Ok(SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,34 +312,13 @@ pub fn function(
|
|||||||
let argv = &mut argv[1..];
|
let argv = &mut argv[1..];
|
||||||
|
|
||||||
let mut opts = FunctionCmdOpts::default();
|
let mut opts = FunctionCmdOpts::default();
|
||||||
let mut optind = 0;
|
parse_cmd_opts(&mut opts, argv, parser, streams)?;
|
||||||
parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams)?;
|
|
||||||
|
|
||||||
if opts.print_help {
|
if opts.print_help {
|
||||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||||
return Ok(SUCCESS);
|
return Ok(SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if argv.len() != optind {
|
|
||||||
if !opts.named_arguments.is_empty() {
|
|
||||||
// Remaining arguments are named arguments.
|
|
||||||
for &arg in argv[optind..].iter() {
|
|
||||||
if !valid_var_name(arg) {
|
|
||||||
streams.err.append(&varname_error(cmd, arg));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
opts.named_arguments.push(arg.to_owned());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streams.err.append(&wgettext_fmt!(
|
|
||||||
"%s: %s: unexpected positional argument",
|
|
||||||
cmd,
|
|
||||||
argv[optind],
|
|
||||||
));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the current filename.
|
// Extract the current filename.
|
||||||
let definition_file = parser.libdata().current_filename.clone();
|
let definition_file = parser.libdata().current_filename.clone();
|
||||||
|
|
||||||
@@ -331,13 +335,6 @@ pub fn function(
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for named in &opts.named_arguments {
|
|
||||||
if !valid_var_name(named) {
|
|
||||||
streams.err.append(&varname_error(cmd, named));
|
|
||||||
return Err(STATUS_INVALID_ARGS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have what we need to actually define the function.
|
// We have what we need to actually define the function.
|
||||||
let props = function::FunctionProperties {
|
let props = function::FunctionProperties {
|
||||||
func_node,
|
func_node,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
use crate::input_common::decode_one_codepoint_utf8;
|
use crate::input_common::decode_one_codepoint_utf8;
|
||||||
use crate::nix::isatty;
|
use crate::nix::isatty;
|
||||||
use crate::parse_execution::varname_error;
|
use crate::parse_execution::varname_error;
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::reader::ReaderConfig;
|
use crate::reader::ReaderConfig;
|
||||||
use crate::reader::commandline_set_buffer;
|
use crate::reader::commandline_set_buffer;
|
||||||
use crate::reader::{reader_pop, reader_push, reader_readline, set_shell_modes_temporarily};
|
use crate::reader::{reader_pop, reader_push, reader_readline, set_shell_modes_temporarily};
|
||||||
@@ -42,7 +43,7 @@ pub(crate) enum TokenOutputMode {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Options {
|
struct Options {
|
||||||
print_help: bool,
|
print_help: bool,
|
||||||
place: EnvMode,
|
place: ParserEnvSetMode,
|
||||||
prompt: Option<WString>,
|
prompt: Option<WString>,
|
||||||
prompt_str: Option<WString>,
|
prompt_str: Option<WString>,
|
||||||
right_prompt: WString,
|
right_prompt: WString,
|
||||||
@@ -63,7 +64,7 @@ struct Options {
|
|||||||
impl Options {
|
impl Options {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Options {
|
Options {
|
||||||
place: EnvMode::USER,
|
place: ParserEnvSetMode::user(EnvMode::empty()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,10 +130,10 @@ fn parse_cmd_opts(
|
|||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
}
|
}
|
||||||
'f' => {
|
'f' => {
|
||||||
opts.place |= EnvMode::FUNCTION;
|
opts.place.mode |= EnvMode::FUNCTION;
|
||||||
}
|
}
|
||||||
'g' => {
|
'g' => {
|
||||||
opts.place |= EnvMode::GLOBAL;
|
opts.place.mode |= EnvMode::GLOBAL;
|
||||||
}
|
}
|
||||||
'h' => {
|
'h' => {
|
||||||
opts.print_help = true;
|
opts.print_help = true;
|
||||||
@@ -141,7 +142,7 @@ fn parse_cmd_opts(
|
|||||||
opts.one_line = true;
|
opts.one_line = true;
|
||||||
}
|
}
|
||||||
'l' => {
|
'l' => {
|
||||||
opts.place |= EnvMode::LOCAL;
|
opts.place.mode |= EnvMode::LOCAL;
|
||||||
}
|
}
|
||||||
'n' => {
|
'n' => {
|
||||||
opts.nchars = match fish_wcstoi(w.woptarg.unwrap()) {
|
opts.nchars = match fish_wcstoi(w.woptarg.unwrap()) {
|
||||||
@@ -205,13 +206,13 @@ fn parse_cmd_opts(
|
|||||||
opts.token_mode = Some(new_mode);
|
opts.token_mode = Some(new_mode);
|
||||||
}
|
}
|
||||||
'U' => {
|
'U' => {
|
||||||
opts.place |= EnvMode::UNIVERSAL;
|
opts.place.mode |= EnvMode::UNIVERSAL;
|
||||||
}
|
}
|
||||||
'u' => {
|
'u' => {
|
||||||
opts.place |= EnvMode::UNEXPORT;
|
opts.place.mode |= EnvMode::UNEXPORT;
|
||||||
}
|
}
|
||||||
'x' => {
|
'x' => {
|
||||||
opts.place |= EnvMode::EXPORT;
|
opts.place.mode |= EnvMode::EXPORT;
|
||||||
}
|
}
|
||||||
'z' => {
|
'z' => {
|
||||||
opts.split_null = true;
|
opts.split_null = true;
|
||||||
@@ -483,7 +484,7 @@ fn validate_read_args(
|
|||||||
opts.prompt = Some(DEFAULT_READ_PROMPT.to_owned());
|
opts.prompt = Some(DEFAULT_READ_PROMPT.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.place.contains(EnvMode::UNEXPORT) && opts.place.contains(EnvMode::EXPORT) {
|
if opts.place.mode.contains(EnvMode::UNEXPORT) && opts.place.mode.contains(EnvMode::EXPORT) {
|
||||||
streams
|
streams
|
||||||
.err
|
.err
|
||||||
.append(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
|
.append(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
|
||||||
@@ -493,7 +494,8 @@ fn validate_read_args(
|
|||||||
|
|
||||||
if opts
|
if opts
|
||||||
.place
|
.place
|
||||||
.intersection(EnvMode::LOCAL | EnvMode::FUNCTION | EnvMode::GLOBAL | EnvMode::UNIVERSAL)
|
.mode
|
||||||
|
.intersection(EnvMode::ANY_SCOPE)
|
||||||
.iter()
|
.iter()
|
||||||
.count()
|
.count()
|
||||||
> 1
|
> 1
|
||||||
@@ -620,7 +622,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
|||||||
let vars_left = |var_ptr: usize| argc - var_ptr;
|
let vars_left = |var_ptr: usize| argc - var_ptr;
|
||||||
let clear_remaining_vars = |var_ptr: &mut usize| {
|
let clear_remaining_vars = |var_ptr: &mut usize| {
|
||||||
while vars_left(*var_ptr) != 0 {
|
while vars_left(*var_ptr) != 0 {
|
||||||
parser.vars().set_empty(argv[*var_ptr], opts.place);
|
parser.set_empty(argv[*var_ptr], opts.place);
|
||||||
*var_ptr += 1;
|
*var_ptr += 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
use crate::history::history_session_id;
|
use crate::history::history_session_id;
|
||||||
use crate::parse_execution::varname_error;
|
use crate::parse_execution::varname_error;
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::{
|
use crate::{
|
||||||
env::{EnvMode, EnvVar, Environment},
|
env::{EnvMode, EnvVar, Environment},
|
||||||
wutil::wcstoi::wcstoi_partial,
|
wutil::wcstoi::wcstoi_partial,
|
||||||
@@ -77,8 +78,8 @@ fn default() -> Self {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
fn scope(&self) -> EnvMode {
|
fn env_mode(&self) -> EnvMode {
|
||||||
let mut scope = EnvMode::USER;
|
let mut scope = EnvMode::empty();
|
||||||
for (is_mode, mode) in [
|
for (is_mode, mode) in [
|
||||||
(self.local, EnvMode::LOCAL),
|
(self.local, EnvMode::LOCAL),
|
||||||
(self.function, EnvMode::FUNCTION),
|
(self.function, EnvMode::FUNCTION),
|
||||||
@@ -367,15 +368,16 @@ fn env_set_reporting_errors(
|
|||||||
cmd: &wstr,
|
cmd: &wstr,
|
||||||
opts: &Options,
|
opts: &Options,
|
||||||
key: &wstr,
|
key: &wstr,
|
||||||
scope: EnvMode,
|
mode: EnvMode,
|
||||||
list: Vec<WString>,
|
list: Vec<WString>,
|
||||||
streams: &mut IoStreams,
|
streams: &mut IoStreams,
|
||||||
parser: &Parser,
|
parser: &Parser,
|
||||||
) -> EnvStackSetResult {
|
) -> EnvStackSetResult {
|
||||||
|
let mode = ParserEnvSetMode::user(mode);
|
||||||
let retval = if opts.no_event {
|
let retval = if opts.no_event {
|
||||||
parser.set_var(key, scope | EnvMode::USER, list)
|
parser.set_var(key, mode, list)
|
||||||
} else {
|
} else {
|
||||||
parser.set_var_and_fire(key, scope | EnvMode::USER, list)
|
parser.set_var_and_fire(key, mode, list)
|
||||||
};
|
};
|
||||||
// If this returned OK, the parser already fired the event.
|
// If this returned OK, the parser already fired the event.
|
||||||
handle_env_return(retval, cmd, key, streams);
|
handle_env_return(retval, cmd, key, streams);
|
||||||
@@ -554,7 +556,7 @@ fn erased_at_indexes(mut input: Vec<WString>, mut indexes: Vec<isize>) -> Vec<WS
|
|||||||
/// `set --names` flag was used.
|
/// `set --names` flag was used.
|
||||||
fn list(opts: &Options, parser: &Parser, streams: &mut IoStreams) -> BuiltinResult {
|
fn list(opts: &Options, parser: &Parser, streams: &mut IoStreams) -> BuiltinResult {
|
||||||
let names_only = opts.list;
|
let names_only = opts.list;
|
||||||
let mut names = parser.vars().get_names(opts.scope());
|
let mut names = parser.vars().get_names(opts.env_mode());
|
||||||
names.sort();
|
names.sort();
|
||||||
|
|
||||||
for key in names {
|
for key in names {
|
||||||
@@ -573,7 +575,7 @@ fn list(opts: &Options, parser: &Parser, streams: &mut IoStreams) -> BuiltinResu
|
|||||||
}
|
}
|
||||||
val += &expand_escape_string(history.item_at_index(i).unwrap().str())[..]
|
val += &expand_escape_string(history.item_at_index(i).unwrap().str())[..]
|
||||||
}
|
}
|
||||||
} else if let Some(var) = parser.vars().getf_unless_empty(&key, opts.scope()) {
|
} else if let Some(var) = parser.vars().getf_unless_empty(&key, opts.env_mode()) {
|
||||||
val = expand_escape_variable(&var);
|
val = expand_escape_variable(&var);
|
||||||
}
|
}
|
||||||
if !val.is_empty() {
|
if !val.is_empty() {
|
||||||
@@ -606,7 +608,7 @@ fn query(
|
|||||||
args: &[&wstr],
|
args: &[&wstr],
|
||||||
) -> BuiltinResult {
|
) -> BuiltinResult {
|
||||||
let mut retval = 0;
|
let mut retval = 0;
|
||||||
let scope = opts.scope();
|
let mode = opts.env_mode();
|
||||||
|
|
||||||
// No variables given, this is an error.
|
// No variables given, this is an error.
|
||||||
// 255 is the maximum return code we allow.
|
// 255 is the maximum return code we allow.
|
||||||
@@ -615,7 +617,7 @@ fn query(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let Some(split) = split_var_and_indexes(arg, scope, parser.vars(), streams) else {
|
let Some(split) = split_var_and_indexes(arg, mode, parser.vars(), streams) else {
|
||||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||||
return Err(STATUS_CMD_ERROR);
|
return Err(STATUS_CMD_ERROR);
|
||||||
};
|
};
|
||||||
@@ -708,7 +710,7 @@ fn show(cmd: &wstr, parser: &Parser, streams: &mut IoStreams, args: &[&wstr]) ->
|
|||||||
let vars = parser.vars();
|
let vars = parser.vars();
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
// show all vars
|
// show all vars
|
||||||
let mut names = vars.get_names(EnvMode::USER);
|
let mut names = vars.get_names(EnvMode::empty());
|
||||||
names.sort();
|
names.sort();
|
||||||
for name in names {
|
for name in names {
|
||||||
if name == "history" {
|
if name == "history" {
|
||||||
@@ -776,15 +778,9 @@ fn erase(
|
|||||||
args: &[&wstr],
|
args: &[&wstr],
|
||||||
) -> BuiltinResult {
|
) -> BuiltinResult {
|
||||||
let mut ret = Ok(SUCCESS);
|
let mut ret = Ok(SUCCESS);
|
||||||
let scopes = opts.scope();
|
let mut erase_with_mode = |mode| {
|
||||||
// `set -e` is allowed to be called with multiple scopes.
|
|
||||||
for bit in (0..).take_while(|bit| 1 << bit <= EnvMode::USER.bits()) {
|
|
||||||
let scope = scopes.intersection(EnvMode::from_bits(1 << bit).unwrap());
|
|
||||||
if scope.bits() == 0 || (scope == EnvMode::USER && scopes != EnvMode::USER) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let Some(split) = split_var_and_indexes(arg, scope, parser.vars(), streams) else {
|
let Some(split) = split_var_and_indexes(arg, mode, parser.vars(), streams) else {
|
||||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||||
return Err(STATUS_CMD_ERROR);
|
return Err(STATUS_CMD_ERROR);
|
||||||
};
|
};
|
||||||
@@ -797,7 +793,7 @@ fn erase(
|
|||||||
let retval;
|
let retval;
|
||||||
if split.indexes.is_empty() {
|
if split.indexes.is_empty() {
|
||||||
// unset the var
|
// unset the var
|
||||||
retval = parser.vars().remove(split.varname, scope);
|
retval = parser.remove_var(split.varname, ParserEnvSetMode::new(mode));
|
||||||
// When a non-existent-variable is unset, return NotFound as $status
|
// When a non-existent-variable is unset, return NotFound as $status
|
||||||
// but do not emit any errors at the console as a compromise between user
|
// but do not emit any errors at the console as a compromise between user
|
||||||
// friendliness and correctness.
|
// friendliness and correctness.
|
||||||
@@ -817,7 +813,7 @@ fn erase(
|
|||||||
cmd,
|
cmd,
|
||||||
opts,
|
opts,
|
||||||
split.varname,
|
split.varname,
|
||||||
scope,
|
mode,
|
||||||
result,
|
result,
|
||||||
streams,
|
streams,
|
||||||
parser,
|
parser,
|
||||||
@@ -830,10 +826,49 @@ fn erase(
|
|||||||
ret = retval.into();
|
ret = retval.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
// `set -e` is allowed to be called with multiple scopes.
|
||||||
|
let mode = opts.env_mode();
|
||||||
|
let any_scope = EnvMode::ANY_SCOPE;
|
||||||
|
let scopes = mode.intersection(any_scope);
|
||||||
|
if scopes.is_empty() {
|
||||||
|
erase_with_mode(mode)?;
|
||||||
|
} else {
|
||||||
|
// Historical behavior is to go from inner to outer, which may be relevant for scopes that
|
||||||
|
// collide with the function scope (i.e. local and global).
|
||||||
|
assert!(is_subsequence(
|
||||||
|
scopes.iter(),
|
||||||
|
[
|
||||||
|
EnvMode::LOCAL,
|
||||||
|
EnvMode::FUNCTION,
|
||||||
|
EnvMode::GLOBAL,
|
||||||
|
EnvMode::UNIVERSAL
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
));
|
||||||
|
for scope in scopes.iter() {
|
||||||
|
let other_scopes = any_scope - scope;
|
||||||
|
erase_with_mode(mode - other_scopes)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_subsequence<T: Eq>(
|
||||||
|
mut lhs: impl Iterator<Item = T>,
|
||||||
|
mut rhs: impl Iterator<Item = T>,
|
||||||
|
) -> bool {
|
||||||
|
lhs.all(|l| {
|
||||||
|
for r in rhs.by_ref() {
|
||||||
|
if r == l {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a list of new values for the variable `varname`, respecting the `opts`.
|
/// Return a list of new values for the variable `varname`, respecting the `opts`.
|
||||||
/// This handles the simple case where there are no indexes.
|
/// This handles the simple case where there are no indexes.
|
||||||
fn new_var_values(
|
fn new_var_values(
|
||||||
@@ -916,11 +951,11 @@ fn set_internal(
|
|||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = opts.scope();
|
let mode = opts.env_mode();
|
||||||
let var_expr = argv[0];
|
let var_expr = argv[0];
|
||||||
let argv = &argv[1..];
|
let argv = &argv[1..];
|
||||||
|
|
||||||
let Some(split) = split_var_and_indexes(var_expr, scope, parser.vars(), streams) else {
|
let Some(split) = split_var_and_indexes(var_expr, mode, parser.vars(), streams) else {
|
||||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||||
return Err(STATUS_INVALID_ARGS);
|
return Err(STATUS_INVALID_ARGS);
|
||||||
};
|
};
|
||||||
@@ -984,7 +1019,7 @@ fn set_internal(
|
|||||||
|
|
||||||
// Set the value back in the variable stack and fire any events.
|
// Set the value back in the variable stack and fire any events.
|
||||||
let retval =
|
let retval =
|
||||||
env_set_reporting_errors(cmd, opts, split.varname, scope, new_values, streams, parser);
|
env_set_reporting_errors(cmd, opts, split.varname, mode, new_values, streams, parser);
|
||||||
|
|
||||||
if retval == EnvStackSetResult::Ok {
|
if retval == EnvStackSetResult::Ok {
|
||||||
warn_if_uvar_shadows_global(cmd, opts, split.varname, streams, parser);
|
warn_if_uvar_shadows_global(cmd, opts, split.varname, streams, parser);
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
|||||||
// points to the end of argv. Otherwise we want to skip the file name to get to the args if any.
|
// points to the end of argv. Otherwise we want to skip the file name to get to the args if any.
|
||||||
let remaining_args = &args[optind + if argc == optind { 0 } else { 1 }..];
|
let remaining_args = &args[optind + if argc == optind { 0 } else { 1 }..];
|
||||||
let argv_list = remaining_args.iter().map(|&arg| arg.to_owned()).collect();
|
let argv_list = remaining_args.iter().map(|&arg| arg.to_owned()).collect();
|
||||||
parser.vars().set_argv(argv_list);
|
parser.vars().set_argv(argv_list, parser.is_repainting());
|
||||||
|
|
||||||
let retval = reader_read(parser, fd, streams.io_chain);
|
let retval = reader_read(parser, fd, streams.io_chain);
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::env::{EnvMode, EnvVar, EnvVarFlags};
|
use crate::env::{EnvVar, EnvVarFlags};
|
||||||
use crate::flog::flog;
|
use crate::flog::flog;
|
||||||
use crate::parse_util::parse_util_unescape_wildcards;
|
use crate::parse_util::parse_util_unescape_wildcards;
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::wildcard::{ANY_STRING, wildcard_match};
|
use crate::wildcard::{ANY_STRING, wildcard_match};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -145,9 +146,8 @@ fn handle(
|
|||||||
..
|
..
|
||||||
}) = matcher
|
}) = matcher
|
||||||
{
|
{
|
||||||
let vars = parser.vars();
|
|
||||||
for (name, vals) in first_match_captures.into_iter() {
|
for (name, vals) in first_match_captures.into_iter() {
|
||||||
vars.set(&WString::from(name), EnvMode::default(), vals);
|
parser.set_var(&WString::from(name), ParserEnvSetMode::default(), vals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1951,7 +1951,7 @@ macro_rules! env_stack_set_from_env {
|
|||||||
if let Some(var) = std::env::var_os($var_name) {
|
if let Some(var) = std::env::var_os($var_name) {
|
||||||
$vars.set_one(
|
$vars.set_one(
|
||||||
L!($var_name),
|
L!($var_name),
|
||||||
$crate::env::EnvMode::GLOBAL,
|
$crate::env::EnvSetMode::new_at_early_startup($crate::env::EnvMode::GLOBAL),
|
||||||
$crate::common::bytes2wcstring(var.as_bytes()),
|
$crate::common::bytes2wcstring(var.as_bytes()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
parse_util::{
|
parse_util::{
|
||||||
parse_util_cmdsubst_extent, parse_util_process_extent, parse_util_unescape_wildcards,
|
parse_util_cmdsubst_extent, parse_util_process_extent, parse_util_unescape_wildcards,
|
||||||
},
|
},
|
||||||
parser::{Block, Parser},
|
parser::{Block, Parser, ParserEnvSetMode},
|
||||||
parser_keywords::parser_keywords_is_subcommand,
|
parser_keywords::parser_keywords_is_subcommand,
|
||||||
path::{path_get_path, path_try_get_path},
|
path::{path_get_path, path_try_get_path},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -401,7 +401,7 @@ pub fn expected_dash_count(&self) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Last value used in the order field of [`CompletionEntry`].
|
/// Last value used in the order field of [`CompletionEntry`].
|
||||||
static complete_order: AtomicUsize = AtomicUsize::new(0);
|
static COMPLETE_ORDER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
struct CompletionEntry {
|
struct CompletionEntry {
|
||||||
/// List of all options.
|
/// List of all options.
|
||||||
@@ -415,7 +415,7 @@ impl CompletionEntry {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
options: vec![],
|
options: vec![],
|
||||||
order: complete_order.fetch_add(1, atomic::Ordering::Relaxed),
|
order: COMPLETE_ORDER.fetch_add(1, atomic::Ordering::Relaxed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +606,7 @@ struct Completer<'ctx> {
|
|||||||
condition_cache: HashMap<WString, bool>,
|
condition_cache: HashMap<WString, bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static completion_autoloader: Lazy<Mutex<Autoload>> =
|
static COMPLETION_AUTOLOADER: Lazy<Mutex<Autoload>> =
|
||||||
Lazy::new(|| Mutex::new(Autoload::new(L!("fish_complete_path"))));
|
Lazy::new(|| Mutex::new(Autoload::new(L!("fish_complete_path"))));
|
||||||
|
|
||||||
impl<'ctx> Completer<'ctx> {
|
impl<'ctx> Completer<'ctx> {
|
||||||
@@ -1257,7 +1257,7 @@ fn complete_param_for_command(
|
|||||||
flog!(complete, "Skipping completions for non-existent command");
|
flog!(complete, "Skipping completions for non-existent command");
|
||||||
} else if let Some(parser) = self.ctx.maybe_parser() {
|
} else if let Some(parser) = self.ctx.maybe_parser() {
|
||||||
complete_load(&cmd, parser);
|
complete_load(&cmd, parser);
|
||||||
} else if !completion_autoloader
|
} else if !COMPLETION_AUTOLOADER
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.has_attempted_autoload(&cmd)
|
.has_attempted_autoload(&cmd)
|
||||||
@@ -1793,7 +1793,7 @@ fn try_complete_user(&mut self, s: &wstr) -> bool {
|
|||||||
}
|
}
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
{
|
{
|
||||||
static s_setpwent_lock: Mutex<()> = Mutex::new(());
|
static SETPWENT_LOCK: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
if s.char_at(0) != '~' || s.contains('/') {
|
if s.char_at(0) != '~' || s.contains('/') {
|
||||||
return false;
|
return false;
|
||||||
@@ -1817,7 +1817,7 @@ fn getpwent_name() -> Option<WString> {
|
|||||||
Some(charptr2wcstring(pw.pw_name))
|
Some(charptr2wcstring(pw.pw_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
let _guard = s_setpwent_lock.lock().unwrap();
|
let _guard = SETPWENT_LOCK.lock().unwrap();
|
||||||
|
|
||||||
unsafe { libc::setpwent() };
|
unsafe { libc::setpwent() };
|
||||||
while let Some(pw_name) = getpwent_name() {
|
while let Some(pw_name) = getpwent_name() {
|
||||||
@@ -1914,9 +1914,11 @@ fn apply_var_assignments<T: AsRef<wstr>>(
|
|||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
parser
|
parser.set_var(
|
||||||
.vars()
|
variable_name,
|
||||||
.set(variable_name, EnvMode::LOCAL | EnvMode::EXPORT, vals);
|
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||||
|
vals,
|
||||||
|
);
|
||||||
if self.ctx.check_cancel() {
|
if self.ctx.check_cancel() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2479,14 +2481,14 @@ pub fn complete_load(cmd: &wstr, parser: &Parser) -> bool {
|
|||||||
// We need to take the lock to decide what to load, drop it to perform the load, then reacquire
|
// We need to take the lock to decide what to load, drop it to perform the load, then reacquire
|
||||||
// it.
|
// it.
|
||||||
// Note we only look at the global fish_function_path and fish_complete_path.
|
// Note we only look at the global fish_function_path and fish_complete_path.
|
||||||
let path_to_load = completion_autoloader
|
let path_to_load = COMPLETION_AUTOLOADER
|
||||||
.lock()
|
.lock()
|
||||||
.expect("mutex poisoned")
|
.expect("mutex poisoned")
|
||||||
.resolve_command(cmd, EnvStack::globals());
|
.resolve_command(cmd, EnvStack::globals());
|
||||||
match path_to_load {
|
match path_to_load {
|
||||||
AutoloadResult::Path(path_to_load) => {
|
AutoloadResult::Path(path_to_load) => {
|
||||||
Autoload::perform_autoload(&path_to_load, parser);
|
Autoload::perform_autoload(&path_to_load, parser);
|
||||||
completion_autoloader
|
COMPLETION_AUTOLOADER
|
||||||
.lock()
|
.lock()
|
||||||
.expect("mutex poisoned")
|
.expect("mutex poisoned")
|
||||||
.mark_autoload_finished(cmd);
|
.mark_autoload_finished(cmd);
|
||||||
@@ -2547,7 +2549,7 @@ pub fn complete_invalidate_path() {
|
|||||||
// unload any completions that the user may specified on the command line. We should in
|
// unload any completions that the user may specified on the command line. We should in
|
||||||
// principle track those completions loaded by the autoloader alone.
|
// principle track those completions loaded by the autoloader alone.
|
||||||
|
|
||||||
let cmds = completion_autoloader
|
let cmds = COMPLETION_AUTOLOADER
|
||||||
.lock()
|
.lock()
|
||||||
.expect("mutex poisoned")
|
.expect("mutex poisoned")
|
||||||
.get_autoloaded_commands();
|
.get_autoloaded_commands();
|
||||||
@@ -2630,11 +2632,12 @@ mod tests {
|
|||||||
sort_and_prioritize,
|
sort_and_prioritize,
|
||||||
};
|
};
|
||||||
use crate::abbrs::{self, Abbreviation, with_abbrs_mut};
|
use crate::abbrs::{self, Abbreviation, with_abbrs_mut};
|
||||||
use crate::env::{EnvMode, Environment};
|
use crate::env::{EnvMode, EnvSetMode, Environment};
|
||||||
use crate::io::IoChain;
|
use crate::io::IoChain;
|
||||||
use crate::operation_context::{
|
use crate::operation_context::{
|
||||||
EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT, OperationContext, no_cancel,
|
EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT, OperationContext, no_cancel,
|
||||||
};
|
};
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::reader::completion_apply_to_command_line;
|
use crate::reader::completion_apply_to_command_line;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
@@ -3221,9 +3224,9 @@ macro_rules! perform_one_completion_cd_test {
|
|||||||
// This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia`
|
// This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia`
|
||||||
// test below.
|
// test below.
|
||||||
// Fake out the home directory
|
// Fake out the home directory
|
||||||
parser.vars().set_one(
|
parser.set_one(
|
||||||
L!("HOME"),
|
L!("HOME"),
|
||||||
EnvMode::LOCAL | EnvMode::EXPORT,
|
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||||
L!("test/test-home").to_owned(),
|
L!("test/test-home").to_owned(),
|
||||||
);
|
);
|
||||||
std::fs::create_dir_all("test/test-home/test_autosuggest_suggest_special/").unwrap();
|
std::fs::create_dir_all("test/test-home/test_autosuggest_suggest_special/").unwrap();
|
||||||
@@ -3333,9 +3336,10 @@ macro_rules! perform_one_completion_cd_test {
|
|||||||
perform_one_completion_cd_test!("cd ~absolutelynosuchus", "er/");
|
perform_one_completion_cd_test!("cd ~absolutelynosuchus", "er/");
|
||||||
perform_one_completion_cd_test!("cd ~absolutelynosuchuser/", "path1/");
|
perform_one_completion_cd_test!("cd ~absolutelynosuchuser/", "path1/");
|
||||||
|
|
||||||
parser
|
parser.vars().remove(
|
||||||
.vars()
|
L!("HOME"),
|
||||||
.remove(L!("HOME"), EnvMode::LOCAL | EnvMode::EXPORT);
|
EnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT, false),
|
||||||
|
);
|
||||||
parser.popd();
|
parser.popd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
255
src/env/environment.rs
vendored
255
src/env/environment.rs
vendored
@@ -7,8 +7,8 @@
|
|||||||
use crate::builtins::shared::{BuiltinResult, SUCCESS};
|
use crate::builtins::shared::{BuiltinResult, SUCCESS};
|
||||||
use crate::common::{UnescapeStringStyle, bytes2wcstring, unescape_string, wcs2zstring};
|
use crate::common::{UnescapeStringStyle, bytes2wcstring, unescape_string, wcs2zstring};
|
||||||
use crate::env::config_paths::ConfigPaths;
|
use crate::env::config_paths::ConfigPaths;
|
||||||
use crate::env::{EnvMode, EnvVar, Statuses};
|
use crate::env::{EnvMode, EnvSetMode, EnvVar, Statuses};
|
||||||
use crate::env_dispatch::{env_dispatch_init, env_dispatch_var_change};
|
use crate::env_dispatch::{VarChangeMilieu, env_dispatch_init, env_dispatch_var_change};
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use crate::flog::flog;
|
use crate::flog::flog;
|
||||||
use crate::global_safety::RelaxedAtomicBool;
|
use crate::global_safety::RelaxedAtomicBool;
|
||||||
@@ -212,7 +212,7 @@ pub fn set_last_statuses(&self, statuses: Statuses) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the variable with the specified name to the given values.
|
/// Sets the variable with the specified name to the given values.
|
||||||
pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStackSetResult {
|
pub fn set(&self, key: &wstr, mode: EnvSetMode, mut vals: Vec<WString>) -> EnvStackSetResult {
|
||||||
// Historical behavior.
|
// Historical behavior.
|
||||||
if vals.len() == 1 && (key == "PWD" || key == "HOME") {
|
if vals.len() == 1 && (key == "PWD" || key == "HOME") {
|
||||||
path_make_canonical(vals.first_mut().unwrap());
|
path_make_canonical(vals.first_mut().unwrap());
|
||||||
@@ -238,7 +238,14 @@ pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStack
|
|||||||
// Dispatch changes if we modified the global state or have 'dispatches_var_changes' set.
|
// Dispatch changes if we modified the global state or have 'dispatches_var_changes' set.
|
||||||
// Important to not hold the lock here.
|
// Important to not hold the lock here.
|
||||||
if ret.global_modified || self.dispatches_var_changes {
|
if ret.global_modified || self.dispatches_var_changes {
|
||||||
env_dispatch_var_change(key, self);
|
env_dispatch_var_change(
|
||||||
|
VarChangeMilieu {
|
||||||
|
is_repainting: mode.is_repainting,
|
||||||
|
global_or_universal: ret.global_modified || ret.uvar_modified,
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
self,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mark if we modified a uvar.
|
// Mark if we modified a uvar.
|
||||||
@@ -249,12 +256,12 @@ pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStack
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the variable with the specified name to a single value.
|
/// Sets the variable with the specified name to a single value.
|
||||||
pub fn set_one(&self, key: &wstr, mode: EnvMode, val: WString) -> EnvStackSetResult {
|
pub fn set_one(&self, key: &wstr, mode: EnvSetMode, val: WString) -> EnvStackSetResult {
|
||||||
self.set(key, mode, vec![val])
|
self.set(key, mode, vec![val])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the variable with the specified name to no values.
|
/// Sets the variable with the specified name to no values.
|
||||||
pub fn set_empty(&self, key: &wstr, mode: EnvMode) -> EnvStackSetResult {
|
pub fn set_empty(&self, key: &wstr, mode: EnvSetMode) -> EnvStackSetResult {
|
||||||
self.set(key, mode, Vec::new())
|
self.set(key, mode, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,24 +276,32 @@ pub fn set_pwd_from_getcwd(&self) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.set_one(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, cwd);
|
let global_exported_mode =
|
||||||
|
EnvSetMode::new_at_early_startup(EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||||
|
self.set_one(L!("PWD"), global_exported_mode, cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove environment variable.
|
/// Remove environment variable.
|
||||||
///
|
///
|
||||||
/// \param key The name of the variable to remove
|
/// \param key The name of the variable to remove
|
||||||
/// \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If
|
/// \param mode If this is a user request, read-only variables can not be removed. The mode
|
||||||
/// this is a user request, read-only variables can not be removed. The mode may also specify
|
/// may also specify the scope of the variable that should be erased.
|
||||||
/// the scope of the variable that should be erased.
|
|
||||||
///
|
///
|
||||||
/// Return the set result.
|
/// Return the set result.
|
||||||
pub fn remove(&self, key: &wstr, mode: EnvMode) -> EnvStackSetResult {
|
pub fn remove(&self, key: &wstr, mode: EnvSetMode) -> EnvStackSetResult {
|
||||||
let ret = self.lock().remove(key, mode);
|
let ret = self.lock().remove(key, mode);
|
||||||
#[allow(clippy::collapsible_if)]
|
#[allow(clippy::collapsible_if)]
|
||||||
if ret.status == EnvStackSetResult::Ok {
|
if ret.status == EnvStackSetResult::Ok {
|
||||||
if ret.global_modified || self.dispatches_var_changes {
|
if ret.global_modified || self.dispatches_var_changes {
|
||||||
// Important to not hold the lock here.
|
// Important to not hold the lock here.
|
||||||
env_dispatch_var_change(key, self);
|
env_dispatch_var_change(
|
||||||
|
VarChangeMilieu {
|
||||||
|
is_repainting: mode.is_repainting,
|
||||||
|
global_or_universal: ret.global_modified || ret.uvar_modified,
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
self,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ret.uvar_modified {
|
if ret.uvar_modified {
|
||||||
@@ -307,14 +322,21 @@ pub fn push(&self, new_scope: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pop the variable stack. Used for implementing local variables for functions and for-loops.
|
/// Pop the variable stack. Used for implementing local variables for functions and for-loops.
|
||||||
pub fn pop(&self) {
|
pub fn pop(&self, is_repainting: bool) {
|
||||||
assert!(self.can_push_pop, "push/pop not allowed on global stack");
|
assert!(self.can_push_pop, "push/pop not allowed on global stack");
|
||||||
let popped = self.lock().pop();
|
let popped = self.lock().pop();
|
||||||
if self.dispatches_var_changes {
|
if self.dispatches_var_changes {
|
||||||
// TODO: we would like to coalesce locale changes, so that we only re-initialize
|
// TODO: we would like to coalesce locale changes, so that we only re-initialize
|
||||||
// once.
|
// once.
|
||||||
for key in popped {
|
for key in popped {
|
||||||
env_dispatch_var_change(&key, self);
|
env_dispatch_var_change(
|
||||||
|
VarChangeMilieu {
|
||||||
|
is_repainting,
|
||||||
|
global_or_universal: false,
|
||||||
|
},
|
||||||
|
&key,
|
||||||
|
self,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,7 +357,7 @@ pub fn snapshot(&self) -> EnvDyn {
|
|||||||
/// If `always` is set, perform synchronization even if there's no pending changes from this
|
/// If `always` is set, perform synchronization even if there's no pending changes from this
|
||||||
/// instance (that is, look for changes from other fish instances).
|
/// instance (that is, look for changes from other fish instances).
|
||||||
/// Return a list of events for changed variables.
|
/// Return a list of events for changed variables.
|
||||||
pub fn universal_sync(&self, always: bool) -> Vec<Event> {
|
pub fn universal_sync(&self, always: bool, is_repainting: bool) -> Vec<Event> {
|
||||||
if UVAR_SCOPE_IS_GLOBAL.load() {
|
if UVAR_SCOPE_IS_GLOBAL.load() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
@@ -353,7 +375,14 @@ pub fn universal_sync(&self, always: bool) -> Vec<Event> {
|
|||||||
if let Some(callbacks) = callbacks {
|
if let Some(callbacks) = callbacks {
|
||||||
for callback in callbacks {
|
for callback in callbacks {
|
||||||
let name = callback.key;
|
let name = callback.key;
|
||||||
env_dispatch_var_change(&name, self);
|
env_dispatch_var_change(
|
||||||
|
VarChangeMilieu {
|
||||||
|
is_repainting,
|
||||||
|
global_or_universal: true,
|
||||||
|
},
|
||||||
|
&name,
|
||||||
|
self,
|
||||||
|
);
|
||||||
let evt = if callback.val.is_none() {
|
let evt = if callback.val.is_none() {
|
||||||
Event::variable_erase(name)
|
Event::variable_erase(name)
|
||||||
} else {
|
} else {
|
||||||
@@ -378,8 +407,12 @@ pub fn globals() -> &'static EnvStack {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_argv(&self, argv: Vec<WString>) {
|
pub fn set_argv(&self, argv: Vec<WString>, is_repainting: bool) {
|
||||||
self.set(L!("argv"), EnvMode::LOCAL, argv);
|
self.set(
|
||||||
|
L!("argv"),
|
||||||
|
EnvSetMode::new(EnvMode::LOCAL, is_repainting),
|
||||||
|
argv,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +511,7 @@ pub fn get_home() -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set up the USER and HOME variable.
|
/// Set up the USER and HOME variable.
|
||||||
fn setup_user(vars: &EnvStack) {
|
fn setup_user(global_exported_mode: EnvSetMode, vars: &EnvStack) {
|
||||||
let uid: uid_t = geteuid();
|
let uid: uid_t = geteuid();
|
||||||
let user_var = vars.get_unless_empty(L!("USER"));
|
let user_var = vars.get_unless_empty(L!("USER"));
|
||||||
|
|
||||||
@@ -508,11 +541,11 @@ fn setup_user(vars: &EnvStack) {
|
|||||||
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
|
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("HOME"),
|
L!("HOME"),
|
||||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
global_exported_mode,
|
||||||
bytes2wcstring(s.to_bytes()),
|
bytes2wcstring(s.to_bytes()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
vars.set_empty(L!("HOME"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
vars.set_empty(L!("HOME"), global_exported_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -535,7 +568,7 @@ fn setup_user(vars: &EnvStack) {
|
|||||||
let userinfo = unsafe { userinfo.assume_init() };
|
let userinfo = unsafe { userinfo.assume_init() };
|
||||||
let s = unsafe { CStr::from_ptr(userinfo.pw_name) };
|
let s = unsafe { CStr::from_ptr(userinfo.pw_name) };
|
||||||
let uname = bytes2wcstring(s.to_bytes());
|
let uname = bytes2wcstring(s.to_bytes());
|
||||||
vars.set_one(L!("USER"), EnvMode::GLOBAL | EnvMode::EXPORT, uname);
|
vars.set_one(L!("USER"), global_exported_mode, uname);
|
||||||
// Only change $HOME if it's empty, so we allow e.g. `HOME=(mktemp -d)`.
|
// Only change $HOME if it's empty, so we allow e.g. `HOME=(mktemp -d)`.
|
||||||
// This is okay with common `su` and `sudo` because they set $HOME.
|
// This is okay with common `su` and `sudo` because they set $HOME.
|
||||||
if vars.get_unless_empty(L!("HOME")).is_none() {
|
if vars.get_unless_empty(L!("HOME")).is_none() {
|
||||||
@@ -543,18 +576,18 @@ fn setup_user(vars: &EnvStack) {
|
|||||||
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
|
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("HOME"),
|
L!("HOME"),
|
||||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
global_exported_mode,
|
||||||
bytes2wcstring(s.to_bytes()),
|
bytes2wcstring(s.to_bytes()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// We cannot get $HOME. This triggers warnings for history and config.fish already,
|
// We cannot get $HOME. This triggers warnings for history and config.fish already,
|
||||||
// so it isn't necessary to warn here as well.
|
// so it isn't necessary to warn here as well.
|
||||||
vars.set_empty(L!("HOME"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
vars.set_empty(L!("HOME"), global_exported_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if vars.get_unless_empty(L!("HOME")).is_none() {
|
} else if vars.get_unless_empty(L!("HOME")).is_none() {
|
||||||
// If $USER is empty as well (which we tried to set above), we can't get $HOME.
|
// If $USER is empty as well (which we tried to set above), we can't get $HOME.
|
||||||
vars.set_empty(L!("HOME"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
vars.set_empty(L!("HOME"), global_exported_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,15 +614,11 @@ fn setup_user(vars: &EnvStack) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// Make sure the PATH variable contains something.
|
/// Make sure the PATH variable contains something.
|
||||||
fn setup_path() {
|
fn setup_path(global_exported_mode: EnvSetMode) {
|
||||||
let vars = EnvStack::globals();
|
let vars = EnvStack::globals();
|
||||||
let path = vars.get_unless_empty(L!("PATH"));
|
let path = vars.get_unless_empty(L!("PATH"));
|
||||||
if path.is_none() {
|
if path.is_none() {
|
||||||
vars.set(
|
vars.set(L!("PATH"), global_exported_mode, FALLBACK_PATH.to_vec());
|
||||||
L!("PATH"),
|
|
||||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
|
||||||
FALLBACK_PATH.to_vec(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,6 +629,9 @@ fn setup_path() {
|
|||||||
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
|
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
|
||||||
let vars = EnvStack::globals();
|
let vars = EnvStack::globals();
|
||||||
|
|
||||||
|
let global_mode = EnvSetMode::new_at_early_startup(EnvMode::GLOBAL);
|
||||||
|
let global_exported_mode = EnvSetMode::new_at_early_startup(EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||||
|
|
||||||
let env_iter: Vec<_> = std::env::vars_os()
|
let env_iter: Vec<_> = std::env::vars_os()
|
||||||
.map(|(k, v)| (bytes2wcstring(k.as_bytes()), bytes2wcstring(v.as_bytes())))
|
.map(|(k, v)| (bytes2wcstring(k.as_bytes()), bytes2wcstring(v.as_bytes())))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -619,7 +651,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
// a value we previously (due to user error) exported will cause impossibly
|
// a value we previously (due to user error) exported will cause impossibly
|
||||||
// difficult to debug PATH problems.
|
// difficult to debug PATH problems.
|
||||||
if key != "fish_user_paths" {
|
if key != "fish_user_paths" {
|
||||||
vars.set(&key, EnvMode::EXPORT | EnvMode::GLOBAL, vec![val.clone()]);
|
vars.set(&key, global_exported_mode, vec![val.clone()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inherited_vars.insert(key, val);
|
inherited_vars.insert(key, val);
|
||||||
@@ -631,14 +663,14 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
|
|
||||||
// Set $USER, $HOME and $EUID
|
// Set $USER, $HOME and $EUID
|
||||||
// This involves going to passwd and stuff.
|
// This involves going to passwd and stuff.
|
||||||
vars.set_one(L!("EUID"), EnvMode::GLOBAL, geteuid().to_wstring());
|
vars.set_one(L!("EUID"), global_mode, geteuid().to_wstring());
|
||||||
setup_user(vars);
|
setup_user(global_exported_mode, vars);
|
||||||
|
|
||||||
if let Some(paths) = paths {
|
if let Some(paths) = paths {
|
||||||
let set_path = |key: &wstr, maybe_path: Option<&PathBuf>| {
|
let set_path = |key: &wstr, maybe_path: Option<&PathBuf>| {
|
||||||
vars.set(
|
vars.set(
|
||||||
key,
|
key,
|
||||||
EnvMode::GLOBAL,
|
global_mode,
|
||||||
maybe_path
|
maybe_path
|
||||||
.map(|path| vec![bytes2wcstring(path.as_os_str().as_bytes())])
|
.map(|path| vec![bytes2wcstring(path.as_os_str().as_bytes())])
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -656,45 +688,49 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
let user_config_dir = path_get_config();
|
let user_config_dir = path_get_config();
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
FISH_CONFIG_DIR,
|
FISH_CONFIG_DIR,
|
||||||
EnvMode::GLOBAL,
|
global_mode,
|
||||||
user_config_dir.unwrap_or_default(),
|
user_config_dir.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let user_data_dir = path_get_data();
|
let user_data_dir = path_get_data();
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
FISH_USER_DATA_DIR,
|
FISH_USER_DATA_DIR,
|
||||||
EnvMode::GLOBAL,
|
global_mode,
|
||||||
user_data_dir.unwrap_or_default(),
|
user_data_dir.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let user_cache_dir = path_get_cache();
|
let user_cache_dir = path_get_cache();
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
FISH_CACHE_DIR,
|
FISH_CACHE_DIR,
|
||||||
EnvMode::GLOBAL,
|
global_mode,
|
||||||
user_cache_dir.unwrap_or_default(),
|
user_cache_dir.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
// Set up a default PATH
|
// Set up a default PATH
|
||||||
setup_path();
|
setup_path(global_exported_mode);
|
||||||
|
|
||||||
// Set up $IFS - this used to be in share/config.fish, but really breaks if it isn't done.
|
// Set up $IFS - this used to be in share/config.fish, but really breaks if it isn't done.
|
||||||
vars.set_one(L!("IFS"), EnvMode::GLOBAL, "\n \t".into());
|
vars.set_one(L!("IFS"), global_mode, "\n \t".into());
|
||||||
|
|
||||||
// Ensure this var is present even before an interactive command is run so that if it is used
|
// Ensure this var is present even before an interactive command is run so that if it is used
|
||||||
// in a function like `fish_prompt` or `fish_right_prompt` it is defined at the time the first
|
// in a function like `fish_prompt` or `fish_right_prompt` it is defined at the time the first
|
||||||
// prompt is written.
|
// prompt is written.
|
||||||
vars.set_one(L!("CMD_DURATION"), EnvMode::UNEXPORT, "0".into());
|
vars.set_one(
|
||||||
|
L!("CMD_DURATION"),
|
||||||
|
EnvSetMode::new_at_early_startup(EnvMode::UNEXPORT),
|
||||||
|
"0".into(),
|
||||||
|
);
|
||||||
|
|
||||||
// Set up the version variable.
|
// Set up the version variable.
|
||||||
let version = bytes2wcstring(crate::BUILD_VERSION.as_bytes());
|
let version = bytes2wcstring(crate::BUILD_VERSION.as_bytes());
|
||||||
vars.set_one(L!("version"), EnvMode::GLOBAL, version.clone());
|
vars.set_one(L!("version"), global_mode, version.clone());
|
||||||
vars.set_one(L!("FISH_VERSION"), EnvMode::GLOBAL, version);
|
vars.set_one(L!("FISH_VERSION"), global_mode, version);
|
||||||
|
|
||||||
// Set the $fish_pid variable.
|
// Set the $fish_pid variable.
|
||||||
vars.set_one(L!("fish_pid"), EnvMode::GLOBAL, getpid().to_wstring());
|
vars.set_one(L!("fish_pid"), global_mode, getpid().to_wstring());
|
||||||
|
|
||||||
// Set the $hostname variable
|
// Set the $hostname variable
|
||||||
let hostname: WString = get_hostname_identifier().unwrap_or("fish".into());
|
let hostname: WString = get_hostname_identifier().unwrap_or("fish".into());
|
||||||
vars.set_one(L!("hostname"), EnvMode::GLOBAL, hostname);
|
vars.set_one(L!("hostname"), global_mode, hostname);
|
||||||
|
|
||||||
// Set up SHLVL variable. Not we can't use vars.get() because SHLVL is read-only, and therefore
|
// Set up SHLVL variable. Not we can't use vars.get() because SHLVL is read-only, and therefore
|
||||||
// was not inherited from the environment.
|
// was not inherited from the environment.
|
||||||
@@ -709,13 +745,13 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
} else {
|
} else {
|
||||||
L!("1").to_owned()
|
L!("1").to_owned()
|
||||||
};
|
};
|
||||||
vars.set_one(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT, nshlvl_str);
|
vars.set_one(L!("SHLVL"), global_exported_mode, nshlvl_str);
|
||||||
} else {
|
} else {
|
||||||
// If we're not interactive, simply pass the value along.
|
// If we're not interactive, simply pass the value along.
|
||||||
if let Some(shlvl_var) = std::env::var_os("SHLVL") {
|
if let Some(shlvl_var) = std::env::var_os("SHLVL") {
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("SHLVL"),
|
L!("SHLVL"),
|
||||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
global_exported_mode,
|
||||||
bytes2wcstring(shlvl_var.as_os_str().as_bytes()),
|
bytes2wcstring(shlvl_var.as_os_str().as_bytes()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -736,7 +772,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
&& incoming_pwd.char_at(0) == '/'
|
&& incoming_pwd.char_at(0) == '/'
|
||||||
&& paths_are_same_file(&incoming_pwd, L!("."))
|
&& paths_are_same_file(&incoming_pwd, L!("."))
|
||||||
{
|
{
|
||||||
vars.set_one(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, incoming_pwd);
|
vars.set_one(L!("PWD"), global_exported_mode, incoming_pwd);
|
||||||
} else {
|
} else {
|
||||||
vars.set_pwd_from_getcwd();
|
vars.set_pwd_from_getcwd();
|
||||||
}
|
}
|
||||||
@@ -744,18 +780,14 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
// Initialize termsize variables.
|
// Initialize termsize variables.
|
||||||
let termsize = termsize::SHARED_CONTAINER.initialize(vars as &dyn Environment);
|
let termsize = termsize::SHARED_CONTAINER.initialize(vars as &dyn Environment);
|
||||||
if vars.get_unless_empty(L!("COLUMNS")).is_none() {
|
if vars.get_unless_empty(L!("COLUMNS")).is_none() {
|
||||||
vars.set_one(
|
vars.set_one(L!("COLUMNS"), global_mode, termsize.width().to_wstring());
|
||||||
L!("COLUMNS"),
|
|
||||||
EnvMode::GLOBAL,
|
|
||||||
termsize.width().to_wstring(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if vars.get_unless_empty(L!("LINES")).is_none() {
|
if vars.get_unless_empty(L!("LINES")).is_none() {
|
||||||
vars.set_one(L!("LINES"), EnvMode::GLOBAL, termsize.height().to_wstring());
|
vars.set_one(L!("LINES"), global_mode, termsize.height().to_wstring());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set fish_bind_mode to "default".
|
// Set fish_bind_mode to "default".
|
||||||
vars.set_one(FISH_BIND_MODE_VAR, EnvMode::GLOBAL, "default".into());
|
vars.set_one(FISH_BIND_MODE_VAR, global_mode, "default".into());
|
||||||
|
|
||||||
// Allow changes to variables to produce events.
|
// Allow changes to variables to produce events.
|
||||||
env_dispatch_init(vars);
|
env_dispatch_init(vars);
|
||||||
@@ -770,65 +802,74 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
|||||||
|
|
||||||
if !do_uvars {
|
if !do_uvars {
|
||||||
UVAR_SCOPE_IS_GLOBAL.store(true);
|
UVAR_SCOPE_IS_GLOBAL.store(true);
|
||||||
} else {
|
return;
|
||||||
// Set up universal variables using the default path.
|
}
|
||||||
let callbacks = uvars().initialize().unwrap_or_default();
|
|
||||||
for callback in callbacks {
|
|
||||||
env_dispatch_var_change(&callback.key, vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not import variables that have the same name and value as
|
// Set up universal variables using the default path.
|
||||||
// an exported universal variable. See issues #5258 and #5348.
|
let callbacks = uvars().initialize().unwrap_or_default();
|
||||||
let globals_to_skip = {
|
for callback in callbacks {
|
||||||
let mut to_skip = vec![];
|
env_dispatch_var_change(
|
||||||
let uvars_locked = uvars();
|
VarChangeMilieu {
|
||||||
for (name, uvar) in uvars_locked.get_table() {
|
is_repainting: false,
|
||||||
if !uvar.exports() {
|
global_or_universal: true,
|
||||||
continue;
|
},
|
||||||
}
|
&callback.key,
|
||||||
|
vars,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Look for a global exported variable with the same name.
|
// Do not import variables that have the same name and value as
|
||||||
let global = EnvStack::globals().getf(name, EnvMode::GLOBAL | EnvMode::EXPORT);
|
// an exported universal variable. See issues #5258 and #5348.
|
||||||
if global.is_some_and(|x| x.as_string() == uvar.as_string()) {
|
let globals_to_skip = {
|
||||||
to_skip.push(name.to_owned());
|
let mut to_skip = vec![];
|
||||||
}
|
|
||||||
}
|
|
||||||
to_skip
|
|
||||||
};
|
|
||||||
for name in &globals_to_skip {
|
|
||||||
EnvStack::globals().remove(name, EnvMode::GLOBAL | EnvMode::EXPORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import any abbreviations from uvars.
|
|
||||||
// Note we do not dynamically react to changes.
|
|
||||||
let prefix = L!("_fish_abbr_");
|
|
||||||
let prefix_len = prefix.char_count();
|
|
||||||
let from_universal = true;
|
|
||||||
let mut abbrs = abbrs_get_set();
|
|
||||||
let uvars_locked = uvars();
|
let uvars_locked = uvars();
|
||||||
for (name, uvar) in uvars_locked.get_table() {
|
for (name, uvar) in uvars_locked.get_table() {
|
||||||
if !name.starts_with(prefix) {
|
if !uvar.exports() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let escaped_name = name.slice_from(prefix_len);
|
|
||||||
if let Some(name) = unescape_string(escaped_name, UnescapeStringStyle::Var) {
|
// Look for a global exported variable with the same name.
|
||||||
let key = name.clone();
|
let global = EnvStack::globals().getf(name, EnvMode::GLOBAL | EnvMode::EXPORT);
|
||||||
let replacement: WString = join_strings(uvar.as_list(), ' ');
|
if global.is_some_and(|x| x.as_string() == uvar.as_string()) {
|
||||||
abbrs.add(Abbreviation::new(
|
to_skip.push(name.to_owned());
|
||||||
name,
|
|
||||||
key,
|
|
||||||
replacement,
|
|
||||||
Position::Command,
|
|
||||||
from_universal,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
to_skip
|
||||||
|
};
|
||||||
|
for name in &globals_to_skip {
|
||||||
|
EnvStack::globals().remove(name, global_exported_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import any abbreviations from uvars.
|
||||||
|
// Note we do not dynamically react to changes.
|
||||||
|
let prefix = L!("_fish_abbr_");
|
||||||
|
let prefix_len = prefix.char_count();
|
||||||
|
let from_universal = true;
|
||||||
|
let mut abbrs = abbrs_get_set();
|
||||||
|
let uvars_locked = uvars();
|
||||||
|
for (name, uvar) in uvars_locked.get_table() {
|
||||||
|
if !name.starts_with(prefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let escaped_name = name.slice_from(prefix_len);
|
||||||
|
if let Some(name) = unescape_string(escaped_name, UnescapeStringStyle::Var) {
|
||||||
|
let key = name.clone();
|
||||||
|
let replacement: WString = join_strings(uvar.as_list(), ' ');
|
||||||
|
abbrs.add(Abbreviation::new(
|
||||||
|
name,
|
||||||
|
key,
|
||||||
|
replacement,
|
||||||
|
Position::Command,
|
||||||
|
from_universal,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{EnvMode, EnvStack, Environment};
|
use super::{EnvMode, EnvStack, Environment};
|
||||||
|
use crate::env::EnvSetMode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
|
|
||||||
@@ -844,19 +885,19 @@ fn test_env_snapshot() {
|
|||||||
let before_pwd = vars.get(L!("PWD")).unwrap().as_string();
|
let before_pwd = vars.get(L!("PWD")).unwrap().as_string();
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("test_env_snapshot_var"),
|
L!("test_env_snapshot_var"),
|
||||||
EnvMode::default(),
|
EnvSetMode::default(),
|
||||||
L!("before").to_owned(),
|
L!("before").to_owned(),
|
||||||
);
|
);
|
||||||
let snapshot = vars.snapshot();
|
let snapshot = vars.snapshot();
|
||||||
vars.set_one(L!("PWD"), EnvMode::default(), L!("/newdir").to_owned());
|
vars.set_one(L!("PWD"), EnvSetMode::default(), L!("/newdir").to_owned());
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("test_env_snapshot_var"),
|
L!("test_env_snapshot_var"),
|
||||||
EnvMode::default(),
|
EnvSetMode::default(),
|
||||||
L!("after").to_owned(),
|
L!("after").to_owned(),
|
||||||
);
|
);
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("test_env_snapshot_var_2"),
|
L!("test_env_snapshot_var_2"),
|
||||||
EnvMode::default(),
|
EnvSetMode::default(),
|
||||||
L!("after").to_owned(),
|
L!("after").to_owned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -885,7 +926,7 @@ fn test_env_snapshot() {
|
|||||||
// snapshots see global var changes except for perproc like PWD
|
// snapshots see global var changes except for perproc like PWD
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("test_env_snapshot_var_3"),
|
L!("test_env_snapshot_var_3"),
|
||||||
EnvMode::GLOBAL,
|
EnvSetMode::new(EnvMode::GLOBAL, false),
|
||||||
L!("reallyglobal").to_owned(),
|
L!("reallyglobal").to_owned(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -900,7 +941,7 @@ fn test_env_snapshot() {
|
|||||||
L!("reallyglobal")
|
L!("reallyglobal")
|
||||||
);
|
);
|
||||||
|
|
||||||
vars.pop();
|
vars.pop(false);
|
||||||
parser.popd();
|
parser.popd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,6 +955,6 @@ fn test_no_global_push() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_no_global_pop() {
|
fn test_no_global_pop() {
|
||||||
EnvStack::globals().pop();
|
EnvStack::globals().pop(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/env/environment_impl.rs
vendored
32
src/env/environment_impl.rs
vendored
@@ -1,6 +1,6 @@
|
|||||||
use crate::common::wcs2zstring;
|
use crate::common::wcs2zstring;
|
||||||
use crate::env::{
|
use crate::env::{
|
||||||
ELECTRIC_VARIABLES, ElectricVar, EnvMode, EnvStackSetResult, EnvVar, EnvVarFlags,
|
ELECTRIC_VARIABLES, ElectricVar, EnvMode, EnvSetMode, EnvStackSetResult, EnvVar, EnvVarFlags,
|
||||||
PATH_ARRAY_SEP, Statuses, VarTable, is_read_only,
|
PATH_ARRAY_SEP, Statuses, VarTable, is_read_only,
|
||||||
};
|
};
|
||||||
use crate::env_universal_common::EnvUniversal;
|
use crate::env_universal_common::EnvUniversal;
|
||||||
@@ -116,11 +116,20 @@ struct Query {
|
|||||||
pub user: bool,
|
pub user: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<EnvMode> for Query {
|
||||||
|
fn from(mode: EnvMode) -> Self {
|
||||||
|
Self::new(mode, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<EnvSetMode> for Query {
|
||||||
|
fn from(mode: EnvSetMode) -> Self {
|
||||||
|
Self::new(mode.mode, mode.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Query {
|
impl Query {
|
||||||
/// Creates a `Query` from env mode flags.
|
/// Creates a `Query` from env mode flags.
|
||||||
fn new(mode: EnvMode) -> Self {
|
fn new(mode: EnvMode, user: bool) -> Self {
|
||||||
let has_scope = mode
|
let has_scope = mode.intersects(EnvMode::ANY_SCOPE);
|
||||||
.intersects(EnvMode::LOCAL | EnvMode::FUNCTION | EnvMode::GLOBAL | EnvMode::UNIVERSAL);
|
|
||||||
let has_export_unexport = mode.intersects(EnvMode::EXPORT | EnvMode::UNEXPORT);
|
let has_export_unexport = mode.intersects(EnvMode::EXPORT | EnvMode::UNEXPORT);
|
||||||
Query {
|
Query {
|
||||||
has_scope,
|
has_scope,
|
||||||
@@ -138,7 +147,7 @@ fn new(mode: EnvMode) -> Self {
|
|||||||
pathvar: mode.contains(EnvMode::PATHVAR),
|
pathvar: mode.contains(EnvMode::PATHVAR),
|
||||||
unpathvar: mode.contains(EnvMode::UNPATHVAR),
|
unpathvar: mode.contains(EnvMode::UNPATHVAR),
|
||||||
|
|
||||||
user: mode.contains(EnvMode::USER),
|
user,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +468,7 @@ fn try_get_universal(&self, key: &wstr) -> Option<EnvVar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
|
pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
|
||||||
let query = Query::new(mode);
|
let query = Query::from(mode);
|
||||||
let mut result: Option<EnvVar> = None;
|
let mut result: Option<EnvVar> = None;
|
||||||
// Computed variables are effectively global and can't be shadowed.
|
// Computed variables are effectively global and can't be shadowed.
|
||||||
if query.global {
|
if query.global {
|
||||||
@@ -489,7 +498,7 @@ pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_names(&self, flags: EnvMode) -> Vec<WString> {
|
pub fn get_names(&self, flags: EnvMode) -> Vec<WString> {
|
||||||
let query = Query::new(flags);
|
let query = Query::from(flags);
|
||||||
let mut names: HashSet<WString> = HashSet::new();
|
let mut names: HashSet<WString> = HashSet::new();
|
||||||
|
|
||||||
// Helper to add the names of variables from `envs` to names, respecting show_exported and
|
// Helper to add the names of variables from `envs` to names, respecting show_exported and
|
||||||
@@ -721,8 +730,8 @@ pub fn new() -> EnvMutex<EnvStackImpl> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set a variable under the name `key`, using the given `mode`, setting its value to `val`.
|
/// Set a variable under the name `key`, using the given `mode`, setting its value to `val`.
|
||||||
pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModResult {
|
pub fn set(&mut self, key: &wstr, mode: EnvSetMode, mut val: Vec<WString>) -> ModResult {
|
||||||
let query = Query::new(mode);
|
let query = Query::from(mode);
|
||||||
// Handle electric and read-only variables.
|
// Handle electric and read-only variables.
|
||||||
if let Some(ret) = self.try_set_electric(key, &query, &mut val) {
|
if let Some(ret) = self.try_set_electric(key, &query, &mut val) {
|
||||||
return ModResult::new(ret);
|
return ModResult::new(ret);
|
||||||
@@ -754,6 +763,7 @@ pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModRe
|
|||||||
result.uvar_modified = true;
|
result.uvar_modified = true;
|
||||||
} else if query.global || (query.universal && UVAR_SCOPE_IS_GLOBAL.load()) {
|
} else if query.global || (query.universal && UVAR_SCOPE_IS_GLOBAL.load()) {
|
||||||
Self::set_in_node(&mut self.base.globals, key, val, flags);
|
Self::set_in_node(&mut self.base.globals, key, val, flags);
|
||||||
|
result.global_modified = true;
|
||||||
} else if query.local {
|
} else if query.local {
|
||||||
assert!(
|
assert!(
|
||||||
!self.base.locals.ptr_eq(&self.base.globals),
|
!self.base.locals.ptr_eq(&self.base.globals),
|
||||||
@@ -802,8 +812,8 @@ pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a variable under the name `key`.
|
/// Remove a variable under the name `key`.
|
||||||
pub fn remove(&mut self, key: &wstr, mode: EnvMode) -> ModResult {
|
pub fn remove(&mut self, key: &wstr, mode: EnvSetMode) -> ModResult {
|
||||||
let query = Query::new(mode);
|
let query = Query::from(mode);
|
||||||
// Users can't remove read-only keys.
|
// Users can't remove read-only keys.
|
||||||
if query.user && is_read_only(key) {
|
if query.user && is_read_only(key) {
|
||||||
return ModResult::new(EnvStackSetResult::Scope);
|
return ModResult::new(EnvStackSetResult::Scope);
|
||||||
|
|||||||
48
src/env/var.rs
vendored
48
src/env/var.rs
vendored
@@ -31,20 +31,51 @@ pub struct EnvMode: u16 {
|
|||||||
const PATHVAR = 1 << 6;
|
const PATHVAR = 1 << 6;
|
||||||
/// Flag to unmark a variable as a path variable.
|
/// Flag to unmark a variable as a path variable.
|
||||||
const UNPATHVAR = 1 << 7;
|
const UNPATHVAR = 1 << 7;
|
||||||
/// Flag for variable update request from the user. All variable changes that are made directly
|
|
||||||
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
|
|
||||||
/// serves one purpose: to indicate that an error should be returned if the user is attempting
|
|
||||||
/// to modify a var that should not be modified by direct user action; e.g., a read-only var.
|
|
||||||
const USER = 1 << 8;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EnvMode {
|
||||||
|
pub const ANY_SCOPE: EnvMode = EnvMode::LOCAL
|
||||||
|
.union(EnvMode::FUNCTION)
|
||||||
|
.union(EnvMode::GLOBAL)
|
||||||
|
.union(EnvMode::UNIVERSAL);
|
||||||
|
}
|
||||||
|
|
||||||
impl From<EnvMode> for u16 {
|
impl From<EnvMode> for u16 {
|
||||||
fn from(val: EnvMode) -> Self {
|
fn from(val: EnvMode) -> Self {
|
||||||
val.bits()
|
val.bits()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default)]
|
||||||
|
pub struct EnvSetMode {
|
||||||
|
pub mode: EnvMode,
|
||||||
|
|
||||||
|
/// Flag for variable update request from the user. All variable changes that are made directly
|
||||||
|
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
|
||||||
|
/// serves to indicate that an error should be returned if the user is attempting to modify
|
||||||
|
/// a var that should not be modified by direct user action; e.g., a read-only var.
|
||||||
|
pub user: bool,
|
||||||
|
|
||||||
|
pub is_repainting: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnvSetMode {
|
||||||
|
pub fn new(mode: EnvMode, is_repainting: bool) -> Self {
|
||||||
|
Self::new_with(mode, false, is_repainting)
|
||||||
|
}
|
||||||
|
pub fn new_with(mode: EnvMode, user: bool, is_repainting: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
mode,
|
||||||
|
user,
|
||||||
|
is_repainting,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new_at_early_startup(mode: EnvMode) -> Self {
|
||||||
|
Self::new_with(mode, false, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A collection of status and pipestatus.
|
/// A collection of status and pipestatus.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Statuses {
|
pub struct Statuses {
|
||||||
@@ -287,6 +318,7 @@ pub fn is_read_only(name: &wstr) -> bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{EnvMode, EnvVar, EnvVarFlags};
|
use super::{EnvMode, EnvVar, EnvVarFlags};
|
||||||
|
use crate::env::EnvSetMode;
|
||||||
use crate::env::environment::{EnvStack, Environment};
|
use crate::env::environment::{EnvStack, Environment};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
@@ -299,7 +331,11 @@ mod tests {
|
|||||||
fn return_timezone_hour(tstamp: SystemTime, timezone: &wstr) -> libc::c_int {
|
fn return_timezone_hour(tstamp: SystemTime, timezone: &wstr) -> libc::c_int {
|
||||||
let vars = EnvStack::globals().create_child(true /* dispatches_var_changes */);
|
let vars = EnvStack::globals().create_child(true /* dispatches_var_changes */);
|
||||||
|
|
||||||
vars.set_one(L!("TZ"), EnvMode::EXPORT, timezone.to_owned());
|
vars.set_one(
|
||||||
|
L!("TZ"),
|
||||||
|
EnvSetMode::new(EnvMode::EXPORT, false),
|
||||||
|
timezone.to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
let _var = vars.get(L!("TZ"));
|
let _var = vars.get(L!("TZ"));
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::reader::{
|
use crate::reader::{
|
||||||
reader_change_cursor_end_mode, reader_change_cursor_selection_mode, reader_change_history,
|
reader_change_cursor_end_mode, reader_change_cursor_selection_mode, reader_change_history,
|
||||||
reader_schedule_prompt_repaint, reader_set_autosuggestion_enabled, reader_set_transient_prompt,
|
reader_current_data, reader_schedule_prompt_repaint, reader_set_autosuggestion_enabled,
|
||||||
|
reader_set_transient_prompt,
|
||||||
};
|
};
|
||||||
use crate::screen::{
|
use crate::screen::{
|
||||||
IS_DUMB, LAYOUT_CACHE_SHARED, ONLY_GRAYSCALE, screen_set_midnight_commander_hack,
|
IS_DUMB, LAYOUT_CACHE_SHARED, ONLY_GRAYSCALE, screen_set_midnight_commander_hack,
|
||||||
@@ -47,8 +48,14 @@
|
|||||||
once_cell::sync::Lazy::new(|| {
|
once_cell::sync::Lazy::new(|| {
|
||||||
let mut table = VarDispatchTable::default();
|
let mut table = VarDispatchTable::default();
|
||||||
|
|
||||||
|
macro_rules! vars {
|
||||||
|
( $f:ident ) => {
|
||||||
|
|vars: &EnvStack, _suppress_repaint: bool| $f(vars)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for name in LOCALE_VARIABLES {
|
for name in LOCALE_VARIABLES {
|
||||||
table.add_anon(name, handle_locale_change);
|
table.add_anon(name, vars!(handle_locale_change));
|
||||||
}
|
}
|
||||||
|
|
||||||
for name in CURSES_VARIABLES {
|
for name in CURSES_VARIABLES {
|
||||||
@@ -59,43 +66,49 @@
|
|||||||
table.add_anon(L!("COLORTERM"), handle_fish_term_change);
|
table.add_anon(L!("COLORTERM"), handle_fish_term_change);
|
||||||
table.add_anon(L!("fish_term256"), handle_fish_term_change);
|
table.add_anon(L!("fish_term256"), handle_fish_term_change);
|
||||||
table.add_anon(L!("fish_term24bit"), handle_fish_term_change);
|
table.add_anon(L!("fish_term24bit"), handle_fish_term_change);
|
||||||
table.add_anon(L!("fish_escape_delay_ms"), update_wait_on_escape_ms);
|
table.add_anon(L!("fish_escape_delay_ms"), vars!(update_wait_on_escape_ms));
|
||||||
table.add_anon(
|
table.add_anon(
|
||||||
L!("fish_sequence_key_delay_ms"),
|
L!("fish_sequence_key_delay_ms"),
|
||||||
update_wait_on_sequence_key_ms,
|
vars!(update_wait_on_sequence_key_ms),
|
||||||
);
|
);
|
||||||
table.add_anon(L!("fish_emoji_width"), guess_emoji_width);
|
table.add_anon(L!("fish_emoji_width"), vars!(guess_emoji_width));
|
||||||
table.add_anon(L!("fish_ambiguous_width"), handle_change_ambiguous_width);
|
table.add_anon(
|
||||||
table.add_anon(L!("LINES"), handle_term_size_change);
|
L!("fish_ambiguous_width"),
|
||||||
table.add_anon(L!("COLUMNS"), handle_term_size_change);
|
vars!(handle_change_ambiguous_width),
|
||||||
table.add_anon(L!("fish_complete_path"), handle_complete_path_change);
|
);
|
||||||
table.add_anon(L!("fish_function_path"), handle_function_path_change);
|
table.add_anon(L!("LINES"), vars!(handle_term_size_change));
|
||||||
table.add_anon(L!("fish_read_limit"), handle_read_limit_change);
|
table.add_anon(L!("COLUMNS"), vars!(handle_term_size_change));
|
||||||
table.add_anon(L!("fish_history"), handle_fish_history_change);
|
table.add_anon(L!("fish_complete_path"), vars!(handle_complete_path_change));
|
||||||
|
table.add_anon(L!("fish_function_path"), vars!(handle_function_path_change));
|
||||||
|
table.add_anon(L!("fish_read_limit"), vars!(handle_read_limit_change));
|
||||||
|
table.add_anon(L!("fish_history"), vars!(handle_fish_history_change));
|
||||||
table.add_anon(
|
table.add_anon(
|
||||||
L!("fish_autosuggestion_enabled"),
|
L!("fish_autosuggestion_enabled"),
|
||||||
handle_autosuggestion_change,
|
vars!(handle_autosuggestion_change),
|
||||||
|
);
|
||||||
|
table.add_anon(
|
||||||
|
L!("fish_transient_prompt"),
|
||||||
|
vars!(handle_transient_prompt_change),
|
||||||
);
|
);
|
||||||
table.add_anon(L!("fish_transient_prompt"), handle_transient_prompt_change);
|
|
||||||
table.add_anon(
|
table.add_anon(
|
||||||
L!("fish_use_posix_spawn"),
|
L!("fish_use_posix_spawn"),
|
||||||
handle_fish_use_posix_spawn_change,
|
vars!(handle_fish_use_posix_spawn_change),
|
||||||
);
|
);
|
||||||
table.add_anon(L!("fish_trace"), handle_fish_trace);
|
table.add_anon(L!("fish_trace"), vars!(handle_fish_trace));
|
||||||
table.add_anon(
|
table.add_anon(
|
||||||
L!("fish_cursor_selection_mode"),
|
L!("fish_cursor_selection_mode"),
|
||||||
handle_fish_cursor_selection_mode_change,
|
vars!(handle_fish_cursor_selection_mode_change),
|
||||||
);
|
);
|
||||||
table.add_anon(
|
table.add_anon(
|
||||||
L!("fish_cursor_end_mode"),
|
L!("fish_cursor_end_mode"),
|
||||||
handle_fish_cursor_end_mode_change,
|
vars!(handle_fish_cursor_end_mode_change),
|
||||||
);
|
);
|
||||||
|
|
||||||
table
|
table
|
||||||
});
|
});
|
||||||
|
|
||||||
type NamedEnvCallback = fn(name: &wstr, env: &EnvStack);
|
type NamedEnvCallback = fn(name: &wstr, env: &EnvStack);
|
||||||
type AnonEnvCallback = fn(env: &EnvStack);
|
type AnonEnvCallback = fn(env: &EnvStack, suppress_repaint: bool);
|
||||||
|
|
||||||
enum EnvCallback {
|
enum EnvCallback {
|
||||||
Named(NamedEnvCallback),
|
Named(NamedEnvCallback),
|
||||||
@@ -120,10 +133,10 @@ pub fn add_anon(&mut self, name: &'static wstr, callback: AnonEnvCallback) {
|
|||||||
assert!(prev.is_none(), "Already observing {}", name);
|
assert!(prev.is_none(), "Already observing {}", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch(&self, key: &wstr, vars: &EnvStack) {
|
pub fn dispatch(&self, key: &wstr, vars: &EnvStack, suppress_repaint: bool) {
|
||||||
match self.table.get(key) {
|
match self.table.get(key) {
|
||||||
Some(EnvCallback::Named(named)) => (named)(key, vars),
|
Some(EnvCallback::Named(named)) => (named)(key, vars),
|
||||||
Some(EnvCallback::Anon(anon)) => (anon)(vars),
|
Some(EnvCallback::Anon(anon)) => (anon)(vars, suppress_repaint),
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,26 +219,40 @@ pub fn guess_emoji_width(vars: &EnvStack) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct VarChangeMilieu {
|
||||||
|
pub is_repainting: bool,
|
||||||
|
pub global_or_universal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// React to modifying the given variable.
|
/// React to modifying the given variable.
|
||||||
pub fn env_dispatch_var_change(key: &wstr, vars: &EnvStack) {
|
pub fn env_dispatch_var_change(milieu: VarChangeMilieu, key: &wstr, vars: &EnvStack) {
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
let suppress_repaint = milieu.is_repainting || !milieu.global_or_universal;
|
||||||
|
|
||||||
// We want to ignore variable changes until the dispatch table is explicitly initialized.
|
// We want to ignore variable changes until the dispatch table is explicitly initialized.
|
||||||
if let Some(dispatch_table) = Lazy::get(&VAR_DISPATCH_TABLE) {
|
if let Some(dispatch_table) = Lazy::get(&VAR_DISPATCH_TABLE) {
|
||||||
dispatch_table.dispatch(key, vars);
|
dispatch_table.dispatch(key, vars, suppress_repaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if string_prefixes_string(L!("fish_color_"), key)
|
// TODO(MSRV>=1.88): if-let
|
||||||
// TODO Don't re-exec prompt when only pager color changed.
|
if !suppress_repaint {
|
||||||
|| string_prefixes_string(L!("fish_pager_color_"), key)
|
if let Some(data) = reader_current_data() {
|
||||||
{
|
if string_prefixes_string(L!("fish_color_"), key) || {
|
||||||
reader_schedule_prompt_repaint();
|
// TODO Don't re-exec prompt when only pager color changed.
|
||||||
|
string_prefixes_string(L!("fish_pager_color_"), key)
|
||||||
|
} {
|
||||||
|
data.schedule_prompt_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_fish_term_change(vars: &EnvStack) {
|
fn handle_fish_term_change(vars: &EnvStack, suppress_repaint: bool) {
|
||||||
update_fish_color_support(vars);
|
update_fish_color_support(vars);
|
||||||
reader_schedule_prompt_repaint();
|
if !suppress_repaint {
|
||||||
|
reader_schedule_prompt_repaint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_change_ambiguous_width(vars: &EnvStack) {
|
fn handle_change_ambiguous_width(vars: &EnvStack) {
|
||||||
@@ -307,11 +334,13 @@ fn handle_locale_change(vars: &EnvStack) {
|
|||||||
init_locale(vars);
|
init_locale(vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_term_change(vars: &EnvStack) {
|
fn handle_term_change(vars: &EnvStack, suppress_repaint: bool) {
|
||||||
guess_emoji_width(vars);
|
guess_emoji_width(vars);
|
||||||
init_terminal(vars);
|
init_terminal(vars);
|
||||||
read_terminfo_database(vars);
|
read_terminfo_database(vars);
|
||||||
reader_schedule_prompt_repaint();
|
if !suppress_repaint {
|
||||||
|
reader_schedule_prompt_repaint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_fish_use_posix_spawn_change(vars: &EnvStack) {
|
fn handle_fish_use_posix_spawn_change(vars: &EnvStack) {
|
||||||
|
|||||||
49
src/exec.rs
49
src/exec.rs
@@ -11,7 +11,7 @@
|
|||||||
ScopeGuard, bytes2wcstring, exit_without_destructors, truncate_at_nul, wcs2bytes, wcs2zstring,
|
ScopeGuard, bytes2wcstring, exit_without_destructors, truncate_at_nul, wcs2bytes, wcs2zstring,
|
||||||
write_loop,
|
write_loop,
|
||||||
};
|
};
|
||||||
use crate::env::{EnvMode, EnvStack, Environment, READ_BYTE_LIMIT, Statuses};
|
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment, READ_BYTE_LIMIT, Statuses};
|
||||||
#[cfg(have_posix_spawn)]
|
#[cfg(have_posix_spawn)]
|
||||||
use crate::env_dispatch::use_posix_spawn;
|
use crate::env_dispatch::use_posix_spawn;
|
||||||
use crate::fds::make_fd_blocking;
|
use crate::fds::make_fd_blocking;
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
};
|
};
|
||||||
use crate::nix::{getpid, isatty};
|
use crate::nix::{getpid, isatty};
|
||||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||||
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
|
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser, ParserEnvSetMode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::proc::Pid;
|
use crate::proc::Pid;
|
||||||
use crate::proc::{
|
use crate::proc::{
|
||||||
@@ -96,14 +96,14 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
|||||||
|
|
||||||
// Apply foo=bar variable assignments
|
// Apply foo=bar variable assignments
|
||||||
for assignment in &job.processes()[0].variable_assignments {
|
for assignment in &job.processes()[0].variable_assignments {
|
||||||
parser.vars().set(
|
parser.set_var(
|
||||||
&assignment.variable_name,
|
&assignment.variable_name,
|
||||||
EnvMode::LOCAL | EnvMode::EXPORT,
|
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||||
assignment.values.clone(),
|
assignment.values.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal_exec(parser.vars(), job, block_io);
|
internal_exec(parser.vars(), parser.is_repainting(), job, block_io);
|
||||||
// internal_exec only returns if it failed to set up redirections.
|
// internal_exec only returns if it failed to set up redirections.
|
||||||
// In case of an successful exec, this code is not reached.
|
// In case of an successful exec, this code is not reached.
|
||||||
let status = if job.flags().negate { 0 } else { 1 };
|
let status = if job.flags().negate { 0 } else { 1 };
|
||||||
@@ -234,11 +234,13 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
|
|||||||
// a pgroup, so error out before setting last_pid.
|
// a pgroup, so error out before setting last_pid.
|
||||||
if !job.is_foreground() {
|
if !job.is_foreground() {
|
||||||
if let Some(last_pid) = job.get_last_pid() {
|
if let Some(last_pid) = job.get_last_pid() {
|
||||||
parser
|
parser.set_one(
|
||||||
.vars()
|
L!("last_pid"),
|
||||||
.set_one(L!("last_pid"), EnvMode::GLOBAL, last_pid.to_wstring());
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
|
last_pid.to_wstring(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
parser.vars().set_empty(L!("last_pid"), EnvMode::GLOBAL);
|
parser.set_empty(L!("last_pid"), ParserEnvSetMode::new(EnvMode::GLOBAL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,8 +327,8 @@ fn exit_code_from_exec_error(err: libc::c_int) -> libc::c_int {
|
|||||||
STATUS_NOT_EXECUTABLE
|
STATUS_NOT_EXECUTABLE
|
||||||
}
|
}
|
||||||
#[cfg(apple)]
|
#[cfg(apple)]
|
||||||
libc::EBADARCH => {
|
libc::EBADARCH | libc::EBADMACHO => {
|
||||||
// This is for e.g. running ARM app on Intel Mac.
|
// This is for e.g. running ARM app on Intel Mac or a bad Mach-O executable
|
||||||
STATUS_NOT_EXECUTABLE
|
STATUS_NOT_EXECUTABLE
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -479,7 +481,7 @@ fn can_use_posix_spawn_for_job(job: &Job, dup2s: &Dup2List) -> bool {
|
|||||||
!wants_terminal
|
!wants_terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
fn internal_exec(vars: &EnvStack, is_repainting: bool, j: &Job, block_io: IoChain) {
|
||||||
// Do a regular launch - but without forking first...
|
// Do a regular launch - but without forking first...
|
||||||
let mut all_ios = block_io;
|
let mut all_ios = block_io;
|
||||||
if !all_ios.append_from_specs(j.processes()[0].redirection_specs(), &vars.get_pwd_slash()) {
|
if !all_ios.append_from_specs(j.processes()[0].redirection_specs(), &vars.get_pwd_slash()) {
|
||||||
@@ -508,7 +510,8 @@ fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
|||||||
{
|
{
|
||||||
// Decrement SHLVL as we're removing ourselves from the shell "stack".
|
// Decrement SHLVL as we're removing ourselves from the shell "stack".
|
||||||
if is_interactive_session() {
|
if is_interactive_session() {
|
||||||
let shlvl_var = vars.getf(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT);
|
let global_exported_mode = EnvMode::GLOBAL | EnvMode::EXPORT;
|
||||||
|
let shlvl_var = vars.getf(L!("SHLVL"), global_exported_mode);
|
||||||
let mut shlvl_str = L!("0").to_owned();
|
let mut shlvl_str = L!("0").to_owned();
|
||||||
if let Some(shlvl_var) = shlvl_var {
|
if let Some(shlvl_var) = shlvl_var {
|
||||||
if let Ok(shlvl) = fish_wcstol(&shlvl_var.as_string()) {
|
if let Ok(shlvl) = fish_wcstol(&shlvl_var.as_string()) {
|
||||||
@@ -517,7 +520,11 @@ fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vars.set_one(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT, shlvl_str);
|
vars.set_one(
|
||||||
|
L!("SHLVL"),
|
||||||
|
EnvSetMode::new(global_exported_mode, is_repainting),
|
||||||
|
shlvl_str,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// launch_process _never_ returns.
|
// launch_process _never_ returns.
|
||||||
@@ -957,15 +964,17 @@ fn function_prepare_environment(
|
|||||||
// 2. inherited variables
|
// 2. inherited variables
|
||||||
// 3. argv
|
// 3. argv
|
||||||
|
|
||||||
|
let mode = parser.convert_env_set_mode(ParserEnvSetMode::user(EnvMode::LOCAL));
|
||||||
|
|
||||||
let mut overwrite_argv = false;
|
let mut overwrite_argv = false;
|
||||||
for (idx, named_arg) in props.named_arguments.iter().enumerate() {
|
for (idx, named_arg) in props.named_arguments.iter().enumerate() {
|
||||||
if named_arg == L!("argv") {
|
if named_arg == L!("argv") {
|
||||||
overwrite_argv = true
|
overwrite_argv = true
|
||||||
};
|
};
|
||||||
if idx < argv.len() {
|
if idx < argv.len() {
|
||||||
vars.set_one(named_arg, EnvMode::LOCAL | EnvMode::USER, argv[idx].clone());
|
vars.set_one(named_arg, mode, argv[idx].clone());
|
||||||
} else {
|
} else {
|
||||||
vars.set_empty(named_arg, EnvMode::LOCAL | EnvMode::USER);
|
vars.set_empty(named_arg, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,11 +982,11 @@ fn function_prepare_environment(
|
|||||||
if key == L!("argv") {
|
if key == L!("argv") {
|
||||||
overwrite_argv = true
|
overwrite_argv = true
|
||||||
};
|
};
|
||||||
vars.set(key, EnvMode::LOCAL | EnvMode::USER, value.clone());
|
vars.set(key, mode, value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !overwrite_argv {
|
if !overwrite_argv {
|
||||||
vars.set_argv(argv);
|
vars.set_argv(argv, mode.is_repainting);
|
||||||
}
|
}
|
||||||
fb
|
fb
|
||||||
}
|
}
|
||||||
@@ -1308,9 +1317,9 @@ fn exec_process_in_job(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
for assignment in &p.variable_assignments {
|
for assignment in &p.variable_assignments {
|
||||||
parser.vars().set(
|
parser.set_var(
|
||||||
&assignment.variable_name,
|
&assignment.variable_name,
|
||||||
EnvMode::LOCAL | EnvMode::EXPORT,
|
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||||
assignment.values.clone(),
|
assignment.values.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1597,6 +1597,7 @@ mod tests {
|
|||||||
use crate::expand::{ExpandResultCode, expand_to_receiver};
|
use crate::expand::{ExpandResultCode, expand_to_receiver};
|
||||||
use crate::operation_context::{EXPANSION_LIMIT_DEFAULT, no_cancel};
|
use crate::operation_context::{EXPANSION_LIMIT_DEFAULT, no_cancel};
|
||||||
use crate::parse_constants::ParseErrorList;
|
use crate::parse_constants::ParseErrorList;
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
use crate::wildcard::ANY_STRING;
|
use crate::wildcard::ANY_STRING;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -1957,7 +1958,7 @@ fn test_expand_overflow() {
|
|||||||
|
|
||||||
let parser = TestParser::new();
|
let parser = TestParser::new();
|
||||||
parser.vars().push(true);
|
parser.vars().push(true);
|
||||||
let set = parser.vars().set(L!("bigvar"), EnvMode::LOCAL, vals);
|
let set = parser.set_var(L!("bigvar"), ParserEnvSetMode::new(EnvMode::LOCAL), vals);
|
||||||
assert_eq!(set, EnvStackSetResult::Ok);
|
assert_eq!(set, EnvStackSetResult::Ok);
|
||||||
|
|
||||||
let mut errors = ParseErrorList::new();
|
let mut errors = ParseErrorList::new();
|
||||||
@@ -1977,7 +1978,7 @@ fn test_expand_overflow() {
|
|||||||
assert_ne!(errors, vec![]);
|
assert_ne!(errors, vec![]);
|
||||||
assert_eq!(res, ExpandResultCode::error);
|
assert_eq!(res, ExpandResultCode::error);
|
||||||
|
|
||||||
parser.vars().pop();
|
parser.vars().pop(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -472,6 +472,16 @@ fn err_or_no_exec_handling(interpreter: &CStr, actual_cmd: &CStr) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(apple)]
|
||||||
|
libc::EBADMACHO => {
|
||||||
|
flog_safe!(
|
||||||
|
exec,
|
||||||
|
"Failed to execute process '",
|
||||||
|
actual_cmd,
|
||||||
|
"': Malformed Mach-O file."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
err => {
|
err => {
|
||||||
flog_safe!(
|
flog_safe!(
|
||||||
exec,
|
exec,
|
||||||
|
|||||||
@@ -1274,7 +1274,7 @@ pub struct HighlightSpec {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{HighlightColorResolver, HighlightRole, HighlightSpec, highlight_shell};
|
use super::{HighlightColorResolver, HighlightRole, HighlightSpec, highlight_shell};
|
||||||
use crate::common::ScopeGuard;
|
use crate::common::ScopeGuard;
|
||||||
use crate::env::{EnvMode, Environment};
|
use crate::env::{EnvMode, EnvSetMode, Environment};
|
||||||
use crate::future_feature_flags::{self, FeatureFlag};
|
use crate::future_feature_flags::{self, FeatureFlag};
|
||||||
use crate::highlight::parse_text_face_for_highlight;
|
use crate::highlight::parse_text_face_for_highlight;
|
||||||
use crate::operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext};
|
use crate::operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext};
|
||||||
@@ -1386,26 +1386,15 @@ macro_rules! validate {
|
|||||||
|
|
||||||
// Verify variables and wildcards in commands using /bin/cat.
|
// Verify variables and wildcards in commands using /bin/cat.
|
||||||
let vars = parser.vars();
|
let vars = parser.vars();
|
||||||
vars.set_one(
|
let local_mode = EnvSetMode::new_at_early_startup(EnvMode::LOCAL);
|
||||||
L!("CDPATH"),
|
vars.set_one(L!("CDPATH"), local_mode, L!("./cdpath-entry").to_owned());
|
||||||
EnvMode::LOCAL,
|
|
||||||
L!("./cdpath-entry").to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
vars.set_one(
|
vars.set_one(L!("VARIABLE_IN_COMMAND"), local_mode, L!("a").to_owned());
|
||||||
L!("VARIABLE_IN_COMMAND"),
|
vars.set_one(L!("VARIABLE_IN_COMMAND2"), local_mode, L!("at").to_owned());
|
||||||
EnvMode::LOCAL,
|
|
||||||
L!("a").to_owned(),
|
|
||||||
);
|
|
||||||
vars.set_one(
|
|
||||||
L!("VARIABLE_IN_COMMAND2"),
|
|
||||||
EnvMode::LOCAL,
|
|
||||||
L!("at").to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let _cleanup = ScopeGuard::new((), |_| {
|
let _cleanup = ScopeGuard::new((), |_| {
|
||||||
vars.remove(L!("VARIABLE_IN_COMMAND"), EnvMode::default());
|
vars.remove(L!("VARIABLE_IN_COMMAND"), EnvSetMode::default());
|
||||||
vars.remove(L!("VARIABLE_IN_COMMAND2"), EnvMode::default());
|
vars.remove(L!("VARIABLE_IN_COMMAND2"), EnvSetMode::default());
|
||||||
});
|
});
|
||||||
|
|
||||||
validate!(
|
validate!(
|
||||||
@@ -1798,7 +1787,7 @@ fn test_trailing_spaces_after_command() {
|
|||||||
// First, set up fish_color_command to include underline
|
// First, set up fish_color_command to include underline
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
L!("fish_color_command"),
|
L!("fish_color_command"),
|
||||||
EnvMode::LOCAL,
|
EnvSetMode::new_at_early_startup(EnvMode::LOCAL),
|
||||||
L!("--underline").to_owned(),
|
L!("--underline").to_owned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1850,7 +1839,7 @@ fn test_resolve_role() {
|
|||||||
let vars = parser.vars();
|
let vars = parser.vars();
|
||||||
|
|
||||||
let set = |var: &wstr, value: Vec<WString>| {
|
let set = |var: &wstr, value: Vec<WString>| {
|
||||||
vars.set(var, EnvMode::LOCAL, value);
|
vars.set(var, EnvSetMode::new(EnvMode::LOCAL, false), value);
|
||||||
};
|
};
|
||||||
set(L!("fish_color_normal"), vec![L!("normal").into()]);
|
set(L!("fish_color_normal"), vec![L!("normal").into()]);
|
||||||
set(
|
set(
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::cstr2wcstring,
|
common::cstr2wcstring,
|
||||||
env::EnvVar,
|
env::{EnvSetMode, EnvVar},
|
||||||
fs::{
|
fs::{
|
||||||
LOCKED_FILE_MODE, LockedFile, LockingMode, PotentialUpdate, WriteMethod, lock_and_load,
|
LOCKED_FILE_MODE, LockedFile, LockingMode, PotentialUpdate, WriteMethod, lock_and_load,
|
||||||
rewrite_via_temporary_file,
|
rewrite_via_temporary_file,
|
||||||
@@ -1760,8 +1760,9 @@ pub fn all_paths_are_valid<P: IntoIterator<Item = WString>>(
|
|||||||
|
|
||||||
/// Sets private mode on. Once in private mode, it cannot be turned off.
|
/// Sets private mode on. Once in private mode, it cannot be turned off.
|
||||||
pub fn start_private_mode(vars: &EnvStack) {
|
pub fn start_private_mode(vars: &EnvStack) {
|
||||||
vars.set_one(L!("fish_history"), EnvMode::GLOBAL, L!("").to_owned());
|
let global_mode = EnvSetMode::new_at_early_startup(EnvMode::GLOBAL);
|
||||||
vars.set_one(L!("fish_private_mode"), EnvMode::GLOBAL, L!("1").to_owned());
|
vars.set_one(L!("fish_history"), global_mode, L!("").to_owned());
|
||||||
|
vars.set_one(L!("fish_private_mode"), global_mode, L!("1").to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries private mode status.
|
/// Queries private mode status.
|
||||||
@@ -1777,7 +1778,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use crate::common::ESCAPE_TEST_CHAR;
|
use crate::common::ESCAPE_TEST_CHAR;
|
||||||
use crate::common::{ScopeGuard, bytes2wcstring, wcs2bytes, wcs2osstring};
|
use crate::common::{ScopeGuard, bytes2wcstring, wcs2bytes, wcs2osstring};
|
||||||
use crate::env::{EnvMode, EnvStack};
|
use crate::env::{EnvMode, EnvSetMode, EnvStack};
|
||||||
use crate::fs::{LockedFile, WriteMethod};
|
use crate::fs::{LockedFile, WriteMethod};
|
||||||
use crate::path::path_get_data;
|
use crate::path::path_get_data;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -2270,8 +2271,9 @@ fn test_history_path_detection() {
|
|||||||
let wdir_path = WString::from(tmpdir.path().to_str().unwrap());
|
let wdir_path = WString::from(tmpdir.path().to_str().unwrap());
|
||||||
|
|
||||||
let test_vars = EnvStack::new();
|
let test_vars = EnvStack::new();
|
||||||
test_vars.set_one(L!("PWD"), EnvMode::GLOBAL, wdir_path.clone());
|
let global_mode = EnvSetMode::new(EnvMode::GLOBAL, false);
|
||||||
test_vars.set_one(L!("HOME"), EnvMode::GLOBAL, wdir_path.clone());
|
test_vars.set_one(L!("PWD"), global_mode, wdir_path.clone());
|
||||||
|
test_vars.set_one(L!("HOME"), global_mode, wdir_path.clone());
|
||||||
|
|
||||||
let history = History::with_name(L!("path_detection"));
|
let history = History::with_name(L!("path_detection"));
|
||||||
history.clear();
|
history.clear();
|
||||||
|
|||||||
@@ -35,7 +35,9 @@
|
|||||||
MaybeParentheses::CommandSubstitution, parse_util_locate_cmdsubst_range,
|
MaybeParentheses::CommandSubstitution, parse_util_locate_cmdsubst_range,
|
||||||
parse_util_unescape_wildcards,
|
parse_util_unescape_wildcards,
|
||||||
};
|
};
|
||||||
use crate::parser::{Block, BlockData, BlockId, BlockType, LoopStatus, Parser, ProfileItem};
|
use crate::parser::{
|
||||||
|
Block, BlockData, BlockId, BlockType, LoopStatus, Parser, ParserEnvSetMode, ProfileItem,
|
||||||
|
};
|
||||||
use crate::parser_keywords::parser_keywords_is_subcommand;
|
use crate::parser_keywords::parser_keywords_is_subcommand;
|
||||||
use crate::path::{path_as_implicit_cd, path_try_get_path};
|
use crate::path::{path_as_implicit_cd, path_try_get_path};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -648,8 +650,11 @@ fn apply_variable_assignments(
|
|||||||
vals.clone(),
|
vals.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ctx.parser()
|
ctx.parser().set_var_and_fire(
|
||||||
.set_var_and_fire(variable_name, EnvMode::LOCAL | EnvMode::EXPORT, vals);
|
variable_name,
|
||||||
|
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
|
||||||
|
vals,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
EndExecutionReason::Ok
|
EndExecutionReason::Ok
|
||||||
}
|
}
|
||||||
@@ -926,9 +931,9 @@ fn run_for_statement(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let retval = ctx.parser().vars().set(
|
let retval = ctx.parser().set_var(
|
||||||
&for_var_name,
|
&for_var_name,
|
||||||
EnvMode::LOCAL | EnvMode::USER,
|
ParserEnvSetMode::user(EnvMode::LOCAL),
|
||||||
var.map_or(vec![], |var| var.as_list().to_owned()),
|
var.map_or(vec![], |var| var.as_list().to_owned()),
|
||||||
);
|
);
|
||||||
assert!(retval == EnvStackSetResult::Ok);
|
assert!(retval == EnvStackSetResult::Ok);
|
||||||
@@ -946,10 +951,11 @@ fn run_for_statement(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let retval = ctx
|
let retval = ctx.parser().set_var(
|
||||||
.parser()
|
&for_var_name,
|
||||||
.vars()
|
ParserEnvSetMode::user(EnvMode::empty()),
|
||||||
.set(&for_var_name, EnvMode::USER, vec![val]);
|
vec![val],
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
retval == EnvStackSetResult::Ok,
|
retval == EnvStackSetResult::Ok,
|
||||||
"for loop variable should have been successfully set"
|
"for loop variable should have been successfully set"
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
};
|
};
|
||||||
use crate::complete::CompletionList;
|
use crate::complete::CompletionList;
|
||||||
use crate::env::{
|
use crate::env::{
|
||||||
EnvMode, EnvStack, EnvStackSetResult, Environment, FISH_TERMINAL_COLOR_THEME_VAR, Statuses,
|
EnvMode, EnvSetMode, EnvStack, EnvStackSetResult, Environment, FISH_TERMINAL_COLOR_THEME_VAR,
|
||||||
|
Statuses,
|
||||||
};
|
};
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::expand::{
|
use crate::expand::{
|
||||||
@@ -437,6 +438,21 @@ pub struct Parser {
|
|||||||
pub blocking_query_timeout: RefCell<Option<Duration>>,
|
pub blocking_query_timeout: RefCell<Option<Duration>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default)]
|
||||||
|
pub struct ParserEnvSetMode {
|
||||||
|
pub mode: EnvMode,
|
||||||
|
pub user: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParserEnvSetMode {
|
||||||
|
pub fn new(mode: EnvMode) -> Self {
|
||||||
|
Self { mode, user: false }
|
||||||
|
}
|
||||||
|
pub fn user(mode: EnvMode) -> Self {
|
||||||
|
Self { mode, user: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
/// Create a parser.
|
/// Create a parser.
|
||||||
pub fn new(variables: EnvStack, cancel_behavior: CancelBehavior) -> Parser {
|
pub fn new(variables: EnvStack, cancel_behavior: CancelBehavior) -> Parser {
|
||||||
@@ -945,27 +961,59 @@ pub fn set_last_statuses(&self, s: Statuses) {
|
|||||||
pub fn set_var_and_fire(
|
pub fn set_var_and_fire(
|
||||||
&self,
|
&self,
|
||||||
key: &wstr,
|
key: &wstr,
|
||||||
mode: EnvMode,
|
mode: ParserEnvSetMode,
|
||||||
vals: Vec<WString>,
|
vals: Vec<WString>,
|
||||||
) -> EnvStackSetResult {
|
) -> EnvStackSetResult {
|
||||||
let res = self.vars().set(key, mode, vals);
|
let res = self.set_var(key, mode, vals);
|
||||||
if res == EnvStackSetResult::Ok {
|
if res == EnvStackSetResult::Ok {
|
||||||
event::fire(self, Event::variable_set(key.to_owned()));
|
event::fire(self, Event::variable_set(key.to_owned()));
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_repainting(&self) -> bool {
|
||||||
|
self.libdata().is_repaint
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_env_set_mode(&self, mode: ParserEnvSetMode) -> EnvSetMode {
|
||||||
|
EnvSetMode::new_with(mode.mode, mode.user, self.is_repainting())
|
||||||
|
}
|
||||||
|
|
||||||
/// Cover of vars().set(), without firing events
|
/// Cover of vars().set(), without firing events
|
||||||
pub fn set_var(&self, key: &wstr, mode: EnvMode, vals: Vec<WString>) -> EnvStackSetResult {
|
pub fn set_var(
|
||||||
|
&self,
|
||||||
|
key: &wstr,
|
||||||
|
mode: ParserEnvSetMode,
|
||||||
|
vals: Vec<WString>,
|
||||||
|
) -> EnvStackSetResult {
|
||||||
|
let mode = self.convert_env_set_mode(mode);
|
||||||
self.vars().set(key, mode, vals)
|
self.vars().set(key, mode, vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cover of vars().set_one(), without firing events
|
||||||
|
pub fn set_one(&self, key: &wstr, mode: ParserEnvSetMode, val: WString) -> EnvStackSetResult {
|
||||||
|
let mode = self.convert_env_set_mode(mode);
|
||||||
|
self.vars().set_one(key, mode, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cover of vars().set_empty(), without firing events
|
||||||
|
pub fn set_empty(&self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResult {
|
||||||
|
let mode = self.convert_env_set_mode(mode);
|
||||||
|
self.vars().set_empty(key, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cover of vars().remove(), without firing events
|
||||||
|
pub fn remove_var(&self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResult {
|
||||||
|
let mode = self.convert_env_set_mode(mode);
|
||||||
|
self.vars().remove(key, mode)
|
||||||
|
}
|
||||||
|
|
||||||
/// Update any universal variables and send event handlers.
|
/// Update any universal variables and send event handlers.
|
||||||
/// If `always` is set, then do it even if we have no pending changes (that is, look for
|
/// If `always` is set, then do it even if we have no pending changes (that is, look for
|
||||||
/// changes from other fish instances); otherwise only sync if this instance has changed uvars.
|
/// changes from other fish instances); otherwise only sync if this instance has changed uvars.
|
||||||
pub fn sync_uvars_and_fire(&self, always: bool) {
|
pub fn sync_uvars_and_fire(&self, always: bool) {
|
||||||
if self.syncs_uvars.load() {
|
if self.syncs_uvars.load() {
|
||||||
let evts = self.vars().universal_sync(always);
|
let evts = self.vars().universal_sync(always, self.is_repainting());
|
||||||
for evt in evts {
|
for evt in evts {
|
||||||
event::fire(self, evt);
|
event::fire(self, evt);
|
||||||
}
|
}
|
||||||
@@ -994,7 +1042,7 @@ pub fn pop_block(&self, expected: BlockId) {
|
|||||||
block_list.pop().unwrap()
|
block_list.pop().unwrap()
|
||||||
};
|
};
|
||||||
if block.wants_pop_env() {
|
if block.wants_pop_env() {
|
||||||
self.vars().pop();
|
self.vars().pop(self.is_repainting());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,7 +1295,7 @@ pub fn set_color_theme(&self, background_color: Option<&xterm_color::Color>) {
|
|||||||
);
|
);
|
||||||
self.set_var_and_fire(
|
self.set_var_and_fire(
|
||||||
FISH_TERMINAL_COLOR_THEME_VAR,
|
FISH_TERMINAL_COLOR_THEME_VAR,
|
||||||
EnvMode::GLOBAL,
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
vec![color_theme.to_owned()],
|
vec![color_theme.to_owned()],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/path.rs
10
src/path.rs
@@ -3,7 +3,7 @@
|
|||||||
//! path-related issues.
|
//! path-related issues.
|
||||||
|
|
||||||
use crate::common::{wcs2osstring, wcs2zstring};
|
use crate::common::{wcs2osstring, wcs2zstring};
|
||||||
use crate::env::{EnvMode, EnvStack, Environment, FALLBACK_PATH};
|
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment, FALLBACK_PATH};
|
||||||
use crate::expand::{HOME_DIRECTORY, expand_tilde};
|
use crate::expand::{HOME_DIRECTORY, expand_tilde};
|
||||||
use crate::flog::{flog, flogf};
|
use crate::flog::{flog, flogf};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -134,15 +134,13 @@ fn maybe_issue_path_warning(
|
|||||||
vars: &EnvStack,
|
vars: &EnvStack,
|
||||||
) {
|
) {
|
||||||
let warning_var_name = L!("_FISH_WARNED_").to_owned() + which_dir;
|
let warning_var_name = L!("_FISH_WARNED_").to_owned() + which_dir;
|
||||||
if vars
|
let global_exported_mode = EnvMode::GLOBAL | EnvMode::EXPORT;
|
||||||
.getf(&warning_var_name, EnvMode::GLOBAL | EnvMode::EXPORT)
|
if vars.getf(&warning_var_name, global_exported_mode).is_some() {
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vars.set_one(
|
vars.set_one(
|
||||||
&warning_var_name,
|
&warning_var_name,
|
||||||
EnvMode::GLOBAL | EnvMode::EXPORT,
|
EnvSetMode::new_at_early_startup(global_exported_mode),
|
||||||
L!("1").to_owned(),
|
L!("1").to_owned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
parse_util_get_line_from_offset, parse_util_get_offset, parse_util_get_offset_from_line,
|
parse_util_get_line_from_offset, parse_util_get_offset, parse_util_get_offset_from_line,
|
||||||
parse_util_lineno, parse_util_locate_cmdsubst_range, parse_util_token_extent,
|
parse_util_lineno, parse_util_locate_cmdsubst_range, parse_util_token_extent,
|
||||||
};
|
};
|
||||||
|
use crate::parser::ParserEnvSetMode;
|
||||||
use crate::parser::{BlockType, EvalRes, Parser};
|
use crate::parser::{BlockType, EvalRes, Parser};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::proc::{
|
use crate::proc::{
|
||||||
@@ -392,9 +393,11 @@ pub fn reader_push<'a>(parser: &'a Parser, history_name: &wstr, conf: ReaderConf
|
|||||||
// Provide value for `status current-command`
|
// Provide value for `status current-command`
|
||||||
parser.libdata_mut().status_vars.command = L!("fish").to_owned();
|
parser.libdata_mut().status_vars.command = L!("fish").to_owned();
|
||||||
// Also provide a value for the deprecated fish 2.0 $_ variable
|
// Also provide a value for the deprecated fish 2.0 $_ variable
|
||||||
parser
|
parser.set_one(
|
||||||
.vars()
|
L!("_"),
|
||||||
.set_one(L!("_"), EnvMode::GLOBAL, L!("fish").to_owned());
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
|
L!("fish").to_owned(),
|
||||||
|
);
|
||||||
let old = parser
|
let old = parser
|
||||||
.blocking_query_timeout
|
.blocking_query_timeout
|
||||||
.replace(input_data.blocking_query_timeout);
|
.replace(input_data.blocking_query_timeout);
|
||||||
@@ -1099,9 +1102,7 @@ pub fn reader_set_autosuggestion_enabled(vars: &dyn Environment) {
|
|||||||
let enable = check_bool_var(vars, L!("fish_autosuggestion_enabled"), true);
|
let enable = check_bool_var(vars, L!("fish_autosuggestion_enabled"), true);
|
||||||
if data.conf.autosuggest_ok != enable {
|
if data.conf.autosuggest_ok != enable {
|
||||||
data.conf.autosuggest_ok = enable;
|
data.conf.autosuggest_ok = enable;
|
||||||
data.force_exec_prompt_and_repaint = true;
|
data.schedule_prompt_repaint();
|
||||||
data.input_data
|
|
||||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1121,11 +1122,7 @@ pub fn reader_schedule_prompt_repaint() {
|
|||||||
let Some(data) = current_data() else {
|
let Some(data) = current_data() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !data.force_exec_prompt_and_repaint {
|
data.schedule_prompt_repaint();
|
||||||
data.force_exec_prompt_and_repaint = true;
|
|
||||||
data.input_data
|
|
||||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reader_update_termsize(parser: &Parser) {
|
pub fn reader_update_termsize(parser: &Parser) {
|
||||||
@@ -1622,6 +1619,15 @@ pub fn mouse_left_click(&mut self, click_position: ViewportPosition) {
|
|||||||
CharOffset::Pager(_) | CharOffset::None => {}
|
CharOffset::Pager(_) | CharOffset::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn schedule_prompt_repaint(&mut self) {
|
||||||
|
if self.force_exec_prompt_and_repaint {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.force_exec_prompt_and_repaint = true;
|
||||||
|
self.input_data
|
||||||
|
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reader_save_screen_state() {
|
pub fn reader_save_screen_state() {
|
||||||
@@ -3503,9 +3509,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
|||||||
| rl::PrevdOrBackwardWord => {
|
| rl::PrevdOrBackwardWord => {
|
||||||
if c == rl::PrevdOrBackwardWord && self.command_line.is_empty() {
|
if c == rl::PrevdOrBackwardWord && self.command_line.is_empty() {
|
||||||
self.eval_bind_cmd(L!("prevd"));
|
self.eval_bind_cmd(L!("prevd"));
|
||||||
self.force_exec_prompt_and_repaint = true;
|
self.schedule_prompt_repaint();
|
||||||
self.input_data
|
|
||||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3529,9 +3533,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
|||||||
| rl::NextdOrForwardWord => {
|
| rl::NextdOrForwardWord => {
|
||||||
if c == rl::NextdOrForwardWord && self.command_line.is_empty() {
|
if c == rl::NextdOrForwardWord && self.command_line.is_empty() {
|
||||||
self.eval_bind_cmd(L!("nextd"));
|
self.eval_bind_cmd(L!("nextd"));
|
||||||
self.force_exec_prompt_and_repaint = true;
|
self.schedule_prompt_repaint();
|
||||||
self.input_data
|
|
||||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5989,9 +5991,11 @@ fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
|
|||||||
parser.libdata_mut().status_vars.command = ft.to_owned();
|
parser.libdata_mut().status_vars.command = ft.to_owned();
|
||||||
parser.libdata_mut().status_vars.commandline = cmd.to_owned();
|
parser.libdata_mut().status_vars.commandline = cmd.to_owned();
|
||||||
// Also provide a value for the deprecated fish 2.0 $_ variable
|
// Also provide a value for the deprecated fish 2.0 $_ variable
|
||||||
parser
|
parser.set_one(
|
||||||
.vars()
|
L!("_"),
|
||||||
.set_one(L!("_"), EnvMode::GLOBAL, ft.to_owned());
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
|
ft.to_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_write_title(cmd, parser, true);
|
reader_write_title(cmd, parser, true);
|
||||||
@@ -6009,9 +6013,9 @@ fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
|
|||||||
if !ft.is_empty() {
|
if !ft.is_empty() {
|
||||||
let time_after = Instant::now();
|
let time_after = Instant::now();
|
||||||
let duration = time_after.duration_since(time_before);
|
let duration = time_after.duration_since(time_before);
|
||||||
parser.vars().set_one(
|
parser.set_one(
|
||||||
ENV_CMD_DURATION,
|
ENV_CMD_DURATION,
|
||||||
EnvMode::UNEXPORT,
|
ParserEnvSetMode::new(EnvMode::UNEXPORT),
|
||||||
duration.as_millis().to_wstring(),
|
duration.as_millis().to_wstring(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6021,9 +6025,11 @@ fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
|
|||||||
// Provide value for `status current-command`
|
// Provide value for `status current-command`
|
||||||
parser.libdata_mut().status_vars.command = get_program_name().to_owned();
|
parser.libdata_mut().status_vars.command = get_program_name().to_owned();
|
||||||
// Also provide a value for the deprecated fish 2.0 $_ variable
|
// Also provide a value for the deprecated fish 2.0 $_ variable
|
||||||
parser
|
parser.set_one(
|
||||||
.vars()
|
L!("_"),
|
||||||
.set_one(L!("_"), EnvMode::GLOBAL, get_program_name().to_owned());
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
|
get_program_name().to_owned(),
|
||||||
|
);
|
||||||
// Provide value for `status current-commandline`
|
// Provide value for `status current-commandline`
|
||||||
parser.libdata_mut().status_vars.commandline = L!("").to_owned();
|
parser.libdata_mut().status_vars.commandline = L!("").to_owned();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
use crate::common::assert_sync;
|
use crate::common::assert_sync;
|
||||||
use crate::env::{EnvMode, EnvVar, Environment};
|
use crate::env::{EnvMode, EnvVar, Environment};
|
||||||
use crate::flog::flog;
|
use crate::flog::flog;
|
||||||
use crate::parser::Parser;
|
use crate::parser::{Parser, ParserEnvSetMode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::wutil::fish_wcstoi;
|
use crate::wutil::fish_wcstoi;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
@@ -200,12 +200,12 @@ fn set_columns_lines_vars(&self, val: Termsize, parser: &Parser) {
|
|||||||
let saved = self.setting_env_vars.swap(true, Ordering::Relaxed);
|
let saved = self.setting_env_vars.swap(true, Ordering::Relaxed);
|
||||||
parser.set_var_and_fire(
|
parser.set_var_and_fire(
|
||||||
L!("COLUMNS"),
|
L!("COLUMNS"),
|
||||||
EnvMode::GLOBAL,
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
vec![val.width().to_wstring()],
|
vec![val.width().to_wstring()],
|
||||||
);
|
);
|
||||||
parser.set_var_and_fire(
|
parser.set_var_and_fire(
|
||||||
L!("LINES"),
|
L!("LINES"),
|
||||||
EnvMode::GLOBAL,
|
ParserEnvSetMode::new(EnvMode::GLOBAL),
|
||||||
vec![val.height().to_wstring()],
|
vec![val.height().to_wstring()],
|
||||||
);
|
);
|
||||||
self.setting_env_vars.store(saved, Ordering::Relaxed);
|
self.setting_env_vars.store(saved, Ordering::Relaxed);
|
||||||
@@ -262,7 +262,7 @@ pub fn safe_termsize_invalidate_tty() {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::env::{EnvMode, Environment};
|
use crate::env::{EnvMode, EnvSetMode, Environment};
|
||||||
use crate::termsize::*;
|
use crate::termsize::*;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@@ -272,7 +272,7 @@ mod tests {
|
|||||||
#[serial]
|
#[serial]
|
||||||
fn test_termsize() {
|
fn test_termsize() {
|
||||||
let _cleanup = test_init();
|
let _cleanup = test_init();
|
||||||
let env_global = EnvMode::GLOBAL;
|
let env_global = EnvSetMode::new(EnvMode::GLOBAL, false);
|
||||||
let parser = TestParser::new();
|
let parser = TestParser::new();
|
||||||
let vars = parser.vars();
|
let vars = parser.vars();
|
||||||
|
|
||||||
|
|||||||
@@ -343,18 +343,18 @@ unsafe impl Sync for TopicMonitor {}
|
|||||||
|
|
||||||
/// The principal topic monitor.
|
/// The principal topic monitor.
|
||||||
/// Do not attempt to move this into a lazy_static, it must be accessed from a signal handler.
|
/// Do not attempt to move this into a lazy_static, it must be accessed from a signal handler.
|
||||||
static mut s_principal: *const TopicMonitor = std::ptr::null();
|
static mut PRINCIPAL: *const TopicMonitor = std::ptr::null();
|
||||||
|
|
||||||
impl TopicMonitor {
|
impl TopicMonitor {
|
||||||
/// Initialize the principal monitor, and return it.
|
/// Initialize the principal monitor, and return it.
|
||||||
/// This should be called only on the main thread.
|
/// This should be called only on the main thread.
|
||||||
pub fn initialize() -> &'static Self {
|
pub fn initialize() -> &'static Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
if s_principal.is_null() {
|
if PRINCIPAL.is_null() {
|
||||||
// We simply leak.
|
// We simply leak.
|
||||||
s_principal = Box::into_raw(Box::default());
|
PRINCIPAL = Box::into_raw(Box::default());
|
||||||
}
|
}
|
||||||
&*s_principal
|
&*PRINCIPAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,10 +595,10 @@ pub fn topic_monitor_init() {
|
|||||||
pub fn topic_monitor_principal() -> &'static TopicMonitor {
|
pub fn topic_monitor_principal() -> &'static TopicMonitor {
|
||||||
unsafe {
|
unsafe {
|
||||||
assert!(
|
assert!(
|
||||||
!s_principal.is_null(),
|
!PRINCIPAL.is_null(),
|
||||||
"Principal topic monitor not initialized"
|
"Principal topic monitor not initialized"
|
||||||
);
|
);
|
||||||
&*s_principal
|
&*PRINCIPAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ pub enum TtyQuirks {
|
|||||||
PreCsiMidnightCommander,
|
PreCsiMidnightCommander,
|
||||||
// Running in iTerm2 before 3.5.12, which causes issues when using the kitty keyboard protocol.
|
// Running in iTerm2 before 3.5.12, which causes issues when using the kitty keyboard protocol.
|
||||||
PreKittyIterm2,
|
PreKittyIterm2,
|
||||||
|
// Whether we are running under tmux.
|
||||||
|
Tmux,
|
||||||
// Whether we are running under WezTerm.
|
// Whether we are running under WezTerm.
|
||||||
Wezterm,
|
Wezterm,
|
||||||
}
|
}
|
||||||
@@ -78,6 +80,8 @@ fn detect(vars: &dyn Environment, xtversion: &wstr) -> Self {
|
|||||||
PreCsiMidnightCommander
|
PreCsiMidnightCommander
|
||||||
} else if get_iterm2_version(xtversion).is_some_and(|v| v < (3, 5, 12)) {
|
} else if get_iterm2_version(xtversion).is_some_and(|v| v < (3, 5, 12)) {
|
||||||
PreKittyIterm2
|
PreKittyIterm2
|
||||||
|
} else if xtversion.starts_with(L!("tmux ")) {
|
||||||
|
Tmux
|
||||||
} else if xtversion.starts_with(L!("WezTerm ")) {
|
} else if xtversion.starts_with(L!("WezTerm ")) {
|
||||||
Wezterm
|
Wezterm
|
||||||
} else {
|
} else {
|
||||||
@@ -172,16 +176,16 @@ fn safe_get_supported_protocol(&self) -> ProtocolKind {
|
|||||||
|
|
||||||
// Return the protocols set to enable or disable TTY protocols.
|
// Return the protocols set to enable or disable TTY protocols.
|
||||||
fn get_protocols(self) -> TtyProtocolsSet {
|
fn get_protocols(self) -> TtyProtocolsSet {
|
||||||
let on_chain = vec![
|
let mut on_chain = vec![];
|
||||||
DecsetFocusReporting,
|
let mut off_chain = vec![];
|
||||||
DecsetBracketedPaste,
|
|
||||||
DecsetColorThemeReporting,
|
// Enable focus reporting under tmux
|
||||||
];
|
if self == TtyQuirks::Tmux {
|
||||||
let off_chain = vec![
|
on_chain.push(DecsetFocusReporting);
|
||||||
DecrstFocusReporting,
|
off_chain.push(DecrstFocusReporting);
|
||||||
DecrstBracketedPaste,
|
}
|
||||||
DecrstColorThemeReporting,
|
on_chain.extend_from_slice(&[DecsetBracketedPaste, DecsetColorThemeReporting]);
|
||||||
];
|
off_chain.extend_from_slice(&[DecrstBracketedPaste, DecrstColorThemeReporting]);
|
||||||
|
|
||||||
let on_chain = || on_chain.clone().into_iter();
|
let on_chain = || on_chain.clone().into_iter();
|
||||||
let off_chain = || off_chain.clone().into_iter();
|
let off_chain = || off_chain.clone().into_iter();
|
||||||
|
|||||||
@@ -19,11 +19,25 @@ pub fn count_newlines(s: &wstr) -> usize {
|
|||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_prefix(mut lhs: impl Iterator<Item = char>, mut rhs: impl Iterator<Item = char>) -> bool {
|
||||||
|
loop {
|
||||||
|
match (lhs.next(), rhs.next()) {
|
||||||
|
(None, _) => return true,
|
||||||
|
(Some(_), None) => return false,
|
||||||
|
(Some(lhs), Some(rhs)) => {
|
||||||
|
if lhs != rhs {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Test if a string prefixes another without regard to case. Returns true if a is a prefix of b.
|
/// Test if a string prefixes another without regard to case. Returns true if a is a prefix of b.
|
||||||
pub fn string_prefixes_string_case_insensitive(proposed_prefix: &wstr, value: &wstr) -> bool {
|
pub fn string_prefixes_string_case_insensitive(proposed_prefix: &wstr, value: &wstr) -> bool {
|
||||||
let mut proposed_prefix = lowercase(proposed_prefix.chars());
|
let proposed_prefix = lowercase(proposed_prefix.chars());
|
||||||
let value = lowercase(value.chars());
|
let value = lowercase(value.chars());
|
||||||
proposed_prefix.by_ref().zip(value).all(|(a, b)| a == b) && proposed_prefix.next().is_none()
|
is_prefix(proposed_prefix, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_prefixes_string_maybe_case_insensitive(
|
pub fn string_prefixes_string_maybe_case_insensitive(
|
||||||
@@ -48,9 +62,9 @@ pub fn strip_executable_suffix(path: &wstr) -> Option<&wstr> {
|
|||||||
|
|
||||||
/// Test if a string is a suffix of another.
|
/// Test if a string is a suffix of another.
|
||||||
pub fn string_suffixes_string_case_insensitive(proposed_suffix: &wstr, value: &wstr) -> bool {
|
pub fn string_suffixes_string_case_insensitive(proposed_suffix: &wstr, value: &wstr) -> bool {
|
||||||
let mut proposed_suffix = lowercase_rev(proposed_suffix.chars());
|
let proposed_suffix = lowercase_rev(proposed_suffix.chars());
|
||||||
let value = lowercase_rev(value.chars());
|
let value = lowercase_rev(value.chars());
|
||||||
proposed_suffix.by_ref().zip(value).all(|(a, b)| a == b) && proposed_suffix.next().is_none()
|
is_prefix(proposed_suffix, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a string prefixes another. Returns true if a is a prefix of b.
|
/// Test if a string prefixes another. Returns true if a is a prefix of b.
|
||||||
@@ -573,6 +587,8 @@ macro_rules! validate {
|
|||||||
validate!("İ", "i\u{307}_", true);
|
validate!("İ", "i\u{307}_", true);
|
||||||
validate!("i\u{307}", "İ", true); // prefix is longer
|
validate!("i\u{307}", "İ", true); // prefix is longer
|
||||||
validate!("i", "İ", true);
|
validate!("i", "İ", true);
|
||||||
|
validate!("gs", "gs_", true);
|
||||||
|
validate!("gs_", "gs", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -590,6 +606,8 @@ macro_rules! validate {
|
|||||||
validate!("İ", "i\u{307}", true); // suffix is longer
|
validate!("İ", "i\u{307}", true); // suffix is longer
|
||||||
validate!("İ", "_İ", true);
|
validate!("İ", "_İ", true);
|
||||||
validate!("i", "_İ", false);
|
validate!("i", "_İ", false);
|
||||||
|
validate!("gs", "_gs", true);
|
||||||
|
validate!("_gs ", "gs", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -566,7 +566,7 @@ fn wgetopt_inner(&mut self, longopt_index: &mut usize) -> Option<char> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ArgType, WGetopter, WOption, wopt};
|
use super::{ArgType, WGetopter, wopt};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::wcstringutil::join_strings;
|
use crate::wcstringutil::join_strings;
|
||||||
|
|
||||||
@@ -611,8 +611,8 @@ fn test_exchange() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_wgetopt() {
|
fn test_wgetopt() {
|
||||||
// Regression test for a crash.
|
// Regression test for a crash.
|
||||||
const short_options: &wstr = L!("-a");
|
let short_options = L!("-a");
|
||||||
const long_options: &[WOption] = &[wopt(L!("add"), ArgType::NoArgument, 'a')];
|
let long_options = &[wopt(L!("add"), ArgType::NoArgument, 'a')];
|
||||||
let mut argv = [
|
let mut argv = [
|
||||||
L!("abbr"),
|
L!("abbr"),
|
||||||
L!("--add"),
|
L!("--add"),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ echo no default universal variables
|
|||||||
# CHECK: ok
|
# CHECK: ok
|
||||||
|
|
||||||
provoke-migration
|
provoke-migration
|
||||||
$fish -c __fish_theme_migrate
|
$fish -c __fish_migrate
|
||||||
# CHECK: {{\x1b\[1m}}fish:{{\x1b\[m}} {{upgraded.*}}
|
# CHECK: {{\x1b\[1m}}fish:{{\x1b\[m}} {{upgraded.*}}
|
||||||
# CHECK: {{.*Color.*no.longer.*universal.*}}
|
# CHECK: {{.*Color.*no.longer.*universal.*}}
|
||||||
# CHECK: Migrated {{.*}} {{\S*}}/xdg_config_home/fish/conf.d/fish_frozen_theme.fish{{\x1b\[m}}
|
# CHECK: Migrated {{.*}} {{\S*}}/xdg_config_home/fish/conf.d/fish_frozen_theme.fish{{\x1b\[m}}
|
||||||
@@ -49,7 +49,7 @@ echo no default universal variables
|
|||||||
# But the migration is only done once, in case the user really wants these as universals.
|
# But the migration is only done once, in case the user really wants these as universals.
|
||||||
set -U fish_color_autosuggestion 8e8e8e
|
set -U fish_color_autosuggestion 8e8e8e
|
||||||
$fish -c '
|
$fish -c '
|
||||||
__fish_theme_migrate
|
__fish_migrate
|
||||||
set -eg fish_color_autosuggestion
|
set -eg fish_color_autosuggestion
|
||||||
echo $fish_color_autosuggestion
|
echo $fish_color_autosuggestion
|
||||||
# CHECK: 8e8e8e
|
# CHECK: 8e8e8e
|
||||||
@@ -63,7 +63,7 @@ echo no default universal variables
|
|||||||
echo yes | fish_config theme save default
|
echo yes | fish_config theme save default
|
||||||
fake-old-uvars
|
fake-old-uvars
|
||||||
provoke-migration
|
provoke-migration
|
||||||
$fish -c __fish_theme_migrate
|
$fish -c __fish_migrate
|
||||||
# CHECK: {{\x1b\[1m}}fish:{{\x1b\[m}} {{upgraded.*}}
|
# CHECK: {{\x1b\[1m}}fish:{{\x1b\[m}} {{upgraded.*}}
|
||||||
# CHECK: {{.*Color.*no.longer.*universal.*}}
|
# CHECK: {{.*Color.*no.longer.*universal.*}}
|
||||||
# CHECK: {{.*restart.*}}
|
# CHECK: {{.*restart.*}}
|
||||||
@@ -80,7 +80,7 @@ echo no default universal variables
|
|||||||
$fish -c '
|
$fish -c '
|
||||||
set -g fish_color_autosuggestion red
|
set -g fish_color_autosuggestion red
|
||||||
set -g fish_color_command green --theme=default
|
set -g fish_color_command green --theme=default
|
||||||
__fish_theme_migrate
|
__fish_migrate
|
||||||
for cmd in "" "__fish_color_theme=unknown __fish_apply_theme"
|
for cmd in "" "__fish_color_theme=unknown __fish_apply_theme"
|
||||||
eval $cmd
|
eval $cmd
|
||||||
echo fish_color_autosuggestion $fish_color_autosuggestion
|
echo fish_color_autosuggestion $fish_color_autosuggestion
|
||||||
@@ -98,7 +98,7 @@ echo no default universal variables
|
|||||||
{
|
{
|
||||||
set -U fish_key_bindings fish_vi_key_bindings
|
set -U fish_key_bindings fish_vi_key_bindings
|
||||||
provoke-migration
|
provoke-migration
|
||||||
$fish -c __fish_theme_migrate
|
$fish -c __fish_migrate
|
||||||
# CHECK: {{\x1b\[1m}}fish:{{\x1b\[m}} {{upgraded.*}}
|
# CHECK: {{\x1b\[1m}}fish:{{\x1b\[m}} {{upgraded.*}}
|
||||||
# CHECK: {{.*fish_key_bindings.*no.longer.*universal.*}}
|
# CHECK: {{.*fish_key_bindings.*no.longer.*universal.*}}
|
||||||
# CHECK: Migrated {{.*}} {{\S*}}/xdg_config_home/fish/conf.d/fish_frozen_key_bindings.fish{{\x1b\[m}}
|
# CHECK: Migrated {{.*}} {{\S*}}/xdg_config_home/fish/conf.d/fish_frozen_key_bindings.fish{{\x1b\[m}}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
# No output is good output
|
# No output is good output
|
||||||
for f in (status list-files completions | string match 'completions/*.fish')
|
for f in (status list-files completions | string match 'completions/*.fish')
|
||||||
if type -q (string replace -r '.*/([^/]+).fish' '$1' $f)
|
if type -q (string replace -r '.*/([^/]+).fish' '$1' $f)
|
||||||
set -l out (__fish_data_with_file $f source 2>&1 | string collect)
|
set -l out (status get-file $f | source 2>&1 | string collect)
|
||||||
test -n "$out"
|
test -n "$out"
|
||||||
and echo -- OUTPUT from $f: $out
|
and echo -- OUTPUT from $f: $out
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#RUN: fish=%fish %fish %s
|
#RUN: fish=%fish %fish %s
|
||||||
|
__fish_migrate # make sure the interactive fish doesn't need mkdir in PATH
|
||||||
set -g PATH
|
set -g PATH
|
||||||
$fish -c "nonexistent-command-1234 banana rama"
|
$fish -c "nonexistent-command-1234 banana rama"
|
||||||
#CHECKERR: fish: Unknown command: nonexistent-command-1234
|
#CHECKERR: fish: Unknown command: nonexistent-command-1234
|
||||||
|
|||||||
@@ -164,8 +164,7 @@ fish_config theme show | grep -E 'default-rgb|base16-default|custom-from-usercon
|
|||||||
# CHECK: {{.*}}base16-default (dark color theme){{\x1b\[m}}
|
# CHECK: {{.*}}base16-default (dark color theme){{\x1b\[m}}
|
||||||
|
|
||||||
# Override the default theme with different colors.
|
# Override the default theme with different colors.
|
||||||
__fish_data_with_file themes/none.theme \
|
status get-file themes/none.theme >$__fish_config_dir/themes/default.theme
|
||||||
cat >$__fish_config_dir/themes/default.theme
|
|
||||||
fish_config theme show default ayu | grep -E 'default|ayu.*dark' -A1
|
fish_config theme show default ayu | grep -E 'default|ayu.*dark' -A1
|
||||||
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}default (unknown color theme){{\x1b\[m}}
|
# CHECK: {{\x1b\[m}}{{\x1b\[4m}}default (unknown color theme){{\x1b\[m}}
|
||||||
# CHECK: /bright/vixens{{.*}}
|
# CHECK: /bright/vixens{{.*}}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
touch $__fish_config_dir/functions/delta-test-custom-function.fish
|
touch $__fish_config_dir/functions/delta-test-custom-function.fish
|
||||||
for path in fish_greeting fish_job_summary
|
for path in fish_greeting fish_job_summary
|
||||||
__fish_data_with_file functions/$path.fish \
|
status get-file functions/$path.fish >$__fish_config_dir/functions/$path.fish
|
||||||
cat >$__fish_config_dir/functions/$path.fish
|
|
||||||
end
|
end
|
||||||
set -l tmp (sed 's/$/ # Modified/' $__fish_config_dir/functions/fish_greeting.fish)
|
set -l tmp (sed 's/$/ # Modified/' $__fish_config_dir/functions/fish_greeting.fish)
|
||||||
string join -- \n $tmp >$__fish_config_dir/functions/fish_greeting.fish
|
string join -- \n $tmp >$__fish_config_dir/functions/fish_greeting.fish
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ function foo --argument-names status; end
|
|||||||
# CHECKERR: function foo --argument-names status; end
|
# CHECKERR: function foo --argument-names status; end
|
||||||
# CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
|
# CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
|
||||||
|
|
||||||
|
function foo --inherit-variable status; end
|
||||||
|
# CHECKERR: {{.*}}function.fish (line {{\d+}}): function: variable 'status' is read-only
|
||||||
|
# CHECKERR: function foo --inherit-variable status; end
|
||||||
|
# CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
|
||||||
|
|
||||||
echo status $status
|
echo status $status
|
||||||
# CHECK: status 2
|
# CHECK: status 2
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
# REQUIRES: command -v sphinx-build
|
# REQUIRES: command -v sphinx-build
|
||||||
# REQUIRES: command -v diff
|
# REQUIRES: command -v diff
|
||||||
|
|
||||||
__fish_data_with_file help_sections (command -v cat) | grep -v ^cmds/ >expected
|
status get-file help_sections | grep -v ^cmds/ >expected
|
||||||
|
|
||||||
__fish_data_with_file completions/help.fish cat |
|
status get-file completions/help.fish |
|
||||||
awk '
|
awk '
|
||||||
/ case / && $2 != "'\''cmds/*'\''" {
|
/ case / && $2 != "'\''cmds/*'\''" {
|
||||||
sub(/^introduction/, "index", $2);
|
sub(/^introduction/, "index", $2);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# REQUIRES: test "$FISH_BUILD_DOCS" != "0"
|
# REQUIRES: test "$FISH_BUILD_DOCS" != "0"
|
||||||
|
|
||||||
# Override the test-override again.
|
# Override the test-override again.
|
||||||
__fish_data_with_file functions/__fish_print_help.fish source
|
status get-file functions/__fish_print_help.fish | source
|
||||||
|
|
||||||
set -l deroff col -b -p -x
|
set -l deroff col -b -p -x
|
||||||
|
|
||||||
|
|||||||
49
tests/checks/tmux-repaint.fish
Normal file
49
tests/checks/tmux-repaint.fish
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#RUN: %fish %s
|
||||||
|
#REQUIRES: command -v tmux
|
||||||
|
|
||||||
|
isolated-tmux-start -C '
|
||||||
|
function fish_prompt
|
||||||
|
set -g counter (math $counter + 1)
|
||||||
|
set -g fish_color_status red
|
||||||
|
set -g fish_pager_color_background --background=white
|
||||||
|
echo "$counter> "
|
||||||
|
set -ga TERM .
|
||||||
|
set -ga TERMINFO .
|
||||||
|
set -ga TERMINFO_DIRS .
|
||||||
|
set -ga COLORTERM .
|
||||||
|
set -ga fish_term256 .
|
||||||
|
set -ga fish_term24bit .
|
||||||
|
end
|
||||||
|
set -eg fish_color_param
|
||||||
|
bind ctrl-g,A "{ set -l fish_color_command 111 }"
|
||||||
|
bind ctrl-g,B "set -l fish_color_command 222"
|
||||||
|
bind ctrl-g,C "set -e fish_color_command"
|
||||||
|
bind ctrl-g,D "set -eg fish_color_param"
|
||||||
|
bind ctrl-g,E "set -g fish_color_command 333"
|
||||||
|
bind ctrl-g,F "set -U fish_color_param 444"
|
||||||
|
bind ctrl-g,G "set -eg fish_color_command"
|
||||||
|
'
|
||||||
|
|
||||||
|
isolated-tmux capture-pane -p
|
||||||
|
# The weird global assignments in fish prompt cause an initial repaint.
|
||||||
|
# CHECK: 2>
|
||||||
|
|
||||||
|
isolated-tmux send-keys C-g A C-g B C-g C C-g D
|
||||||
|
tmux-sleep
|
||||||
|
isolated-tmux capture-pane -p
|
||||||
|
# CHECK: 2>
|
||||||
|
|
||||||
|
isolated-tmux send-keys C-g E
|
||||||
|
tmux-sleep
|
||||||
|
isolated-tmux capture-pane -p
|
||||||
|
# CHECK: 3>
|
||||||
|
|
||||||
|
isolated-tmux send-keys C-g F
|
||||||
|
tmux-sleep
|
||||||
|
isolated-tmux capture-pane -p
|
||||||
|
# CHECK: 4>
|
||||||
|
|
||||||
|
isolated-tmux send-keys C-g G
|
||||||
|
tmux-sleep
|
||||||
|
isolated-tmux capture-pane -p
|
||||||
|
# CHECK: 5>
|
||||||
Reference in New Issue
Block a user