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