mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-04-25 04:11:14 -03:00
Compare commits
50 Commits
47a3757f73
...
single-thr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb95431990 | ||
|
|
1e0b686730 | ||
|
|
f7c336021b | ||
|
|
523e25df17 | ||
|
|
c8b28d4d24 | ||
|
|
ba35214e1e | ||
|
|
d05d8557a7 | ||
|
|
a3dc57873c | ||
|
|
0c078c179d | ||
|
|
ca443e2e54 | ||
|
|
63c3306e6c | ||
|
|
923d0b7974 | ||
|
|
52998635f9 | ||
|
|
1ccf4ad480 | ||
|
|
23b5b01242 | ||
|
|
ca2b5dc40b | ||
|
|
0dfe06f4c9 | ||
|
|
4e47f47d85 | ||
|
|
f3e43e932f | ||
|
|
1dfc75bb9c | ||
|
|
fa33f6f0e0 | ||
|
|
31363120aa | ||
|
|
2304077e0d | ||
|
|
86c052b6ba | ||
|
|
68472da48a | ||
|
|
4b172fc735 | ||
|
|
944ab91fab | ||
|
|
34535fcb61 | ||
|
|
9e4eb37696 | ||
|
|
dda76d7f18 | ||
|
|
fdb1d95521 | ||
|
|
937f3bc6cb | ||
|
|
ebc32adc09 | ||
|
|
a4b6348315 | ||
|
|
b21a4a7197 | ||
|
|
0cd227533f | ||
|
|
5eb7687a64 | ||
|
|
8d6426295e | ||
|
|
85e76ba356 | ||
|
|
fee4288122 | ||
|
|
413246a93d | ||
|
|
3cb939c9a8 | ||
|
|
4790a444d8 | ||
|
|
da924927a0 | ||
|
|
29ff2fdd43 | ||
|
|
732c04420b | ||
|
|
947abd7464 | ||
|
|
12cfe59578 | ||
|
|
4b60d18b44 | ||
|
|
dd8e59db03 |
2
.github/actions/rust-toolchain/action.yml
vendored
2
.github/actions/rust-toolchain/action.yml
vendored
@@ -25,7 +25,7 @@ runs:
|
||||
set -x
|
||||
toolchain=$(
|
||||
case "$toolchain_channel" in
|
||||
(stable) echo 1.93 ;; # updatecli.d/rust.yml
|
||||
(stable) echo 1.95 ;; # updatecli.d/rust.yml
|
||||
(msrv) echo 1.85 ;; # updatecli.d/rust.yml
|
||||
(*)
|
||||
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
|
||||
|
||||
2
.github/workflows/lint-dependencies.yml
vendored
2
.github/workflows/lint-dependencies.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
||||
with:
|
||||
command: check licenses
|
||||
arguments: --all-features --locked --exclude-dev
|
||||
rust-version: 1.93 # updatecli.d/rust.yml
|
||||
rust-version: 1.95 # updatecli.d/rust.yml
|
||||
|
||||
13
.github/workflows/lint.yml
vendored
13
.github/workflows/lint.yml
vendored
@@ -22,6 +22,17 @@ jobs:
|
||||
- name: check rustfmt
|
||||
run: find build.rs crates src -type f -name '*.rs' | xargs rustfmt --check
|
||||
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install shellcheck
|
||||
run: sudo apt install shellcheck
|
||||
- name: shellcheck
|
||||
run: cargo xtask shellcheck
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -32,6 +43,8 @@ jobs:
|
||||
features: ""
|
||||
- rust_version: "stable"
|
||||
features: "--no-default-features"
|
||||
- rust_version: "stable"
|
||||
features: "--all-features"
|
||||
- rust_version: "msrv"
|
||||
features: ""
|
||||
steps:
|
||||
|
||||
53
.gitignore
vendored
53
.gitignore
vendored
@@ -20,7 +20,6 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
!tests/*.out
|
||||
*.out
|
||||
*.pch
|
||||
*.slo
|
||||
@@ -36,46 +35,31 @@
|
||||
Desktop.ini
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
__pycache__/
|
||||
|
||||
.directory
|
||||
.fuse_hidden*
|
||||
|
||||
|
||||
# Directories that only contain transitory files from building and testing.
|
||||
/doc/
|
||||
/share/man/
|
||||
/share/doc/
|
||||
/test/
|
||||
/user_doc/
|
||||
|
||||
# File names that can appear in the project root that represent artifacts from
|
||||
# building and testing.
|
||||
/FISH-BUILD-VERSION-FILE
|
||||
/command_list.txt
|
||||
/command_list_toc.txt
|
||||
/compile_commands.json
|
||||
/doc.h
|
||||
# Artifacts from in-tree builds ("cmake .").
|
||||
/build.ninja
|
||||
/cargo/
|
||||
/CMakeCache.txt
|
||||
/CMakeFiles/
|
||||
/cmake_install.cmake
|
||||
/fish
|
||||
/fish.pc
|
||||
/fish_indent
|
||||
/fish_key_reader
|
||||
/fish_tests
|
||||
/lexicon.txt
|
||||
/lexicon_filter
|
||||
/toc.txt
|
||||
/version
|
||||
fish-build-version-witness.txt
|
||||
__pycache__
|
||||
/fish-localization-map-cache/
|
||||
/fish.pc
|
||||
/fish.pc.noversion
|
||||
/.ninja_log
|
||||
|
||||
# File names that can appear below the project root that represent artifacts
|
||||
# from building and testing.
|
||||
/doc_src/commands.hdr
|
||||
/doc_src/index.hdr
|
||||
/po/*.gmo
|
||||
/share/__fish_build_paths.fish
|
||||
/share/pkgconfig
|
||||
/tests/*.tmp.*
|
||||
/tests/.last-check-all-files
|
||||
/.venv/
|
||||
|
||||
# xcode
|
||||
## Build generated
|
||||
@@ -83,24 +67,19 @@ __pycache__
|
||||
*.xccheckout
|
||||
*.xcscmblueprin
|
||||
.vscode
|
||||
/DerivedData/
|
||||
/build/
|
||||
/DerivedData/
|
||||
/tags
|
||||
xcuserdata/
|
||||
/xcuserdata/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
/target/
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by clangd
|
||||
/.cache
|
||||
/.cache/
|
||||
|
||||
# JetBrains editors.
|
||||
.idea/
|
||||
|
||||
@@ -19,6 +19,8 @@ Other improvements
|
||||
------------------
|
||||
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
|
||||
- :doc:`fish_update_completions <cmds/fish_update_completions>` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
|
||||
- Improve user experience when removing history entries via the :doc:`web-based config <cmds/fish_config>`.
|
||||
- :doc:`funced <cmds/funced>` will no longer lose work if there are parse errors multiple times without new changes to the file
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
|
||||
79
Cargo.lock
generated
79
Cargo.lock
generated
@@ -183,6 +183,31 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -278,6 +303,7 @@ dependencies = [
|
||||
"fish-gettext-mo-file-parser",
|
||||
"fish-printf",
|
||||
"fish-tempfile",
|
||||
"fish-thread",
|
||||
"fish-util",
|
||||
"fish-wcstringutil",
|
||||
"fish-wgetopt",
|
||||
@@ -296,7 +322,9 @@ dependencies = [
|
||||
"rand",
|
||||
"rsconf",
|
||||
"rust-embed",
|
||||
"rustc_version",
|
||||
"serial_test",
|
||||
"strum_macros",
|
||||
"unix_path",
|
||||
"xterm-color",
|
||||
]
|
||||
@@ -331,6 +359,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"fish-build-helper",
|
||||
"fish-feature-flags",
|
||||
"fish-thread",
|
||||
"fish-widestring",
|
||||
"libc",
|
||||
"nix",
|
||||
@@ -408,6 +437,10 @@ dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-thread"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "fish-util"
|
||||
version = "0.0.0"
|
||||
@@ -520,6 +553,22 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
@@ -889,6 +938,15 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -919,6 +977,12 @@ version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -1015,6 +1079,18 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
@@ -1166,6 +1242,9 @@ dependencies = [
|
||||
"clap",
|
||||
"fish-build-helper",
|
||||
"fish-tempfile",
|
||||
"fish-thread",
|
||||
"ignore",
|
||||
"pcre2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
||||
@@ -30,11 +30,13 @@ fish-gettext-maps = { path = "crates/gettext-maps" }
|
||||
fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" }
|
||||
fish-printf = { path = "crates/printf", features = ["widestring"] }
|
||||
fish-tempfile = { path = "crates/tempfile" }
|
||||
fish-thread = { path = "crates/thread" }
|
||||
fish-util = { path = "crates/util" }
|
||||
fish-wcstringutil = { path = "crates/wcstringutil" }
|
||||
fish-widecharwidth = { path = "crates/widecharwidth" }
|
||||
fish-widestring = { path = "crates/widestring" }
|
||||
fish-wgetopt = { path = "crates/wgetopt" }
|
||||
ignore = "0.4.25"
|
||||
itertools = "0.14.0"
|
||||
libc = "0.2.177"
|
||||
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
|
||||
@@ -73,7 +75,9 @@ rust-embed = { version = "8.11.0", features = [
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
rustc_version = "0.4.1"
|
||||
serial_test = { version = "3", default-features = false }
|
||||
strum_macros = "0.28.0"
|
||||
widestring = "1.2.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
@@ -114,6 +118,7 @@ fish-gettext = { workspace = true, optional = true }
|
||||
fish-gettext-extraction = { workspace = true, optional = true }
|
||||
fish-printf.workspace = true
|
||||
fish-tempfile.workspace = true
|
||||
fish-thread.workspace = true
|
||||
fish-util.workspace = true
|
||||
fish-wcstringutil.workspace = true
|
||||
fish-wgetopt.workspace = true
|
||||
@@ -128,6 +133,7 @@ num-traits.workspace = true
|
||||
once_cell.workspace = true
|
||||
pcre2.workspace = true
|
||||
rand.workspace = true
|
||||
strum_macros.workspace = true
|
||||
xterm-color.workspace = true
|
||||
|
||||
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
|
||||
@@ -156,6 +162,7 @@ fish-build-helper.workspace = true
|
||||
fish-gettext-mo-file-parser.workspace = true
|
||||
phf_codegen = { workspace = true, optional = true }
|
||||
rsconf.workspace = true
|
||||
rustc_version.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
unix_path.workspace = true
|
||||
@@ -194,7 +201,6 @@ tsan = []
|
||||
|
||||
[workspace.lints]
|
||||
rust.non_camel_case_types = "allow"
|
||||
rust.non_upper_case_globals = "allow"
|
||||
rust.unknown_lints = { level = "allow", priority = -1 }
|
||||
rust.unstable_name_collisions = "allow"
|
||||
rustdoc.private_intra_doc_links = "allow"
|
||||
|
||||
4
build.rs
4
build.rs
@@ -6,6 +6,10 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
let is_nightly =
|
||||
rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly;
|
||||
rsconf::declare_cfg("nightly", is_nightly);
|
||||
|
||||
setup_paths();
|
||||
|
||||
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
|
||||
|
||||
@@ -60,6 +60,7 @@ cargo() {
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck disable=2317,2329
|
||||
cleanup () {
|
||||
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
|
||||
rm -r "$gettext_template_dir"
|
||||
@@ -89,6 +90,7 @@ fi
|
||||
|
||||
gettext_template_dir=$(mktemp -d)
|
||||
(
|
||||
# shellcheck disable=2030
|
||||
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
|
||||
cargo build --workspace --all-targets --features=gettext-extract
|
||||
)
|
||||
@@ -96,8 +98,11 @@ if $lint; then
|
||||
if command -v cargo-deny >/dev/null; then
|
||||
cargo deny --all-features --locked --exclude-dev check licenses
|
||||
fi
|
||||
|
||||
cargo xtask shellcheck
|
||||
|
||||
PATH="$build_dir:$PATH" cargo xtask format --all --check
|
||||
for features in "" --no-default-features; do
|
||||
for features in "" --no-default-features --all-features; do
|
||||
cargo clippy --workspace --all-targets $features
|
||||
done
|
||||
fi
|
||||
@@ -112,7 +117,8 @@ fi
|
||||
# - https://github.com/msys2/MSYS2-packages/issues/5784
|
||||
(
|
||||
if $is_cygwin; then
|
||||
export PATH="$PATH:$(rustc --print target-libdir)"
|
||||
PATH="$PATH:$(rustc --print target-libdir)"
|
||||
export PATH
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
)
|
||||
@@ -124,20 +130,24 @@ fi
|
||||
|
||||
# Using "()" not "{}" because we do want a subshell (for the export)
|
||||
system_tests() (
|
||||
[ -n "$@" ] && export "$@"
|
||||
# shellcheck disable=2163
|
||||
[ -n "$*" ] && export "$@"
|
||||
# shellcheck disable=2031
|
||||
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
|
||||
"$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
)
|
||||
|
||||
test_cmd='FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir" "$workspace_root/tests/test_driver.py" "$build_dir"'
|
||||
if $is_cygwin; then
|
||||
echo -e "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}"
|
||||
system_tests $cygwin_var=winsymlinks
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=winsymlinks
|
||||
|
||||
echo -e "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}"
|
||||
system_tests $cygwin_var=winsymlinks
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=
|
||||
else
|
||||
echo -e "=== Running ${green}integration tests${reset}"
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests${reset}\n"
|
||||
system_tests
|
||||
fi
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ manifest=$tmpdir/Cargo.toml
|
||||
lockfile=$tmpdir/Cargo.lock
|
||||
|
||||
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml >"$manifest"
|
||||
awk -v version=$VERSION '
|
||||
awk -v version="$VERSION" '
|
||||
/^name = "fish"$/ { ok=1 }
|
||||
ok == 1 && /^version = ".*"$/ {
|
||||
ok = 2;
|
||||
|
||||
@@ -86,10 +86,13 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
|
||||
echo
|
||||
echo 'Download links:'
|
||||
echo 'To download the source code for fish, we suggest the file named ``fish-'"$version"'.tar.xz``.'
|
||||
# shellcheck disable=2016
|
||||
echo 'The file downloaded from ``Source code (tar.gz)`` will not build correctly.'
|
||||
# shellcheck disable=2016
|
||||
echo 'A GPG signature using `this key <'"${FISH_GPG_PUBLIC_KEY_URL:-???}"'>`__ is available as ``fish-'"$version"'.tar.xz.asc``.'
|
||||
echo
|
||||
echo 'The files called ``fish-'"$version"'-linux-*.tar.xz`` contain'
|
||||
# shellcheck disable=2016
|
||||
echo '`standalone fish binaries <https://github.com/fish-shell/fish-shell/?tab=readme-ov-file#building-fish-with-cargo>`__'
|
||||
echo 'for any Linux with the given CPU architecture.'
|
||||
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst
|
||||
|
||||
@@ -72,7 +72,7 @@ integration_branch=$(
|
||||
--format='%(refname:strip=2)'
|
||||
)
|
||||
[ -n "$integration_branch" ] ||
|
||||
git merge-base --is-ancestor $remote/master HEAD
|
||||
git merge-base --is-ancestor "$remote"/master HEAD
|
||||
|
||||
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
|
||||
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
|
||||
@@ -113,9 +113,9 @@ CreateCommit "Release $version"
|
||||
# Tags must be full objects, not lightweight tags, for
|
||||
# git_version-gen.sh to work.
|
||||
git -c "user.signingKey=$committer" \
|
||||
tag --sign --message="Release $version" $version
|
||||
tag --sign --message="Release $version" "$version"
|
||||
|
||||
git push $remote $version
|
||||
git push "$remote" "$version"
|
||||
|
||||
TIMEOUT=
|
||||
gh() {
|
||||
@@ -173,6 +173,7 @@ actual_tag_oid=$(git ls-remote "$remote" |
|
||||
(
|
||||
cd "$tmpdir/local-tarball/fish-$version"
|
||||
uv --no-managed-python venv
|
||||
# shellcheck disable=1091
|
||||
. .venv/bin/activate
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .
|
||||
ninja doc
|
||||
@@ -180,14 +181,17 @@ actual_tag_oid=$(git ls-remote "$remote" |
|
||||
CopyDocs() {
|
||||
rm -rf "$fish_site/site/docs/$1"
|
||||
cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
|
||||
git -C $fish_site add "site/docs/$1"
|
||||
git -C "$fish_site" add "site/docs/$1"
|
||||
}
|
||||
minor_version=${version%.*}
|
||||
CopyDocs "$minor_version"
|
||||
latest_release=$(
|
||||
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
|
||||
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
|
||||
's/-/~/g' | LC_ALL=C sort --version-sort)
|
||||
sed '
|
||||
# De-prioritize release candidates (1.2.3-rc0)
|
||||
s/-/~/g
|
||||
' | LC_ALL=C sort --version-sort
|
||||
)
|
||||
printf %s\\n "$releases" | tail -1
|
||||
)
|
||||
if [ "$version" = "$latest_release" ]; then
|
||||
@@ -272,7 +276,7 @@ done
|
||||
)
|
||||
|
||||
if [ -n "$integration_branch" ]; then {
|
||||
git push $remote "$version^{commit}":refs/heads/$integration_branch
|
||||
git push "$remote" "$version^{commit}:refs/heads/$integration_branch"
|
||||
} else {
|
||||
changelog=$(cat - CHANGELOG.rst <<EOF
|
||||
fish ?.?.? (released ???)
|
||||
@@ -283,7 +287,7 @@ EOF
|
||||
printf %s\\n "$changelog" >CHANGELOG.rst
|
||||
git add CHANGELOG.rst
|
||||
CreateCommit "start new cycle"
|
||||
git push $remote HEAD:master
|
||||
git push "$remote" HEAD:master
|
||||
} fi
|
||||
|
||||
milestone_version="$(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -11,6 +11,6 @@ codename=$(
|
||||
curl -fsS https://sources.debian.org/api/src/"${package}"/ |
|
||||
jq -r --arg codename "${codename}" '
|
||||
.versions[] | select(.suites[] == $codename) | .version' |
|
||||
sed 's/^\([0-9]\+\.[0-9]\+\).*/\1/' |
|
||||
sed -E 's/^([0-9]+\.[0-9]+).*/\1/' |
|
||||
sort --version-sort |
|
||||
tail -1
|
||||
|
||||
@@ -10,6 +10,7 @@ license.workspace = true
|
||||
bitflags.workspace = true
|
||||
fish-feature-flags.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
fish-thread.workspace = true
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use bitflags::bitflags;
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
use fish_widestring::{
|
||||
ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE, ASCII_MAX, BRACE_BEGIN, BRACE_END, BRACE_SEP,
|
||||
BRACE_SPACE, BYTE_MAX, HOME_DIRECTORY, INTERNAL_SEPARATOR, L, PROCESS_EXPAND_SELF,
|
||||
@@ -18,7 +19,7 @@
|
||||
unix::ffi::OsStrExt as _,
|
||||
},
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
Arc,
|
||||
atomic::{AtomicI32, AtomicU32, Ordering},
|
||||
},
|
||||
time,
|
||||
@@ -398,7 +399,7 @@ fn escape_string_url(input: &wstr) -> WString {
|
||||
for byte in narrow {
|
||||
if (byte & 0x80) == 0 {
|
||||
let c = char::from_u32(u32::from(byte)).unwrap();
|
||||
if c.is_alphanumeric() || [b'/', b'.', b'~', b'-', b'_'].contains(&byte) {
|
||||
if c.is_alphanumeric() || b"/.~-_".contains(&byte) {
|
||||
// The above characters don't need to be encoded.
|
||||
out.push(c);
|
||||
continue;
|
||||
@@ -534,8 +535,8 @@ enum Mode {
|
||||
let mut to_append_or_none = Some(c);
|
||||
if mode == Mode::Unquoted {
|
||||
match c {
|
||||
'\\' => {
|
||||
if !ignore_backslashes {
|
||||
'\\'
|
||||
if !ignore_backslashes => {
|
||||
// Backslashes (escapes) are complicated and may result in errors, or
|
||||
// appending INTERNAL_SEPARATORs, so we have to handle them specially.
|
||||
if let Some(escape_chars) = read_unquoted_escape(
|
||||
@@ -555,28 +556,25 @@ enum Mode {
|
||||
// We've already appended, don't append anything else.
|
||||
to_append_or_none = None;
|
||||
}
|
||||
}
|
||||
'~' => {
|
||||
'~'
|
||||
if unescape_special
|
||||
&& (input_position == 0 || Some(input_position) == potential_word_start)
|
||||
{
|
||||
=> {
|
||||
to_append_or_none = Some(HOME_DIRECTORY);
|
||||
}
|
||||
}
|
||||
'%' => {
|
||||
'%'
|
||||
// Note that this only recognizes %self if the string is literally %self.
|
||||
// %self/foo will NOT match this.
|
||||
if allow_percent_self
|
||||
&& unescape_special
|
||||
&& input_position == 0
|
||||
&& input == PROCESS_EXPAND_SELF_STR
|
||||
{
|
||||
=> {
|
||||
to_append_or_none = Some(PROCESS_EXPAND_SELF);
|
||||
input_position += PROCESS_EXPAND_SELF_STR.len() - 1; // skip over 'self's
|
||||
}
|
||||
}
|
||||
'*' => {
|
||||
if unescape_special {
|
||||
'*'
|
||||
if unescape_special => {
|
||||
// In general, this is ANY_STRING. But as a hack, if the last appended char
|
||||
// is ANY_STRING, delete the last char and store ANY_STRING_RECURSIVE to
|
||||
// reflect the fact that ** is the recursive wildcard.
|
||||
@@ -588,14 +586,12 @@ enum Mode {
|
||||
to_append_or_none = Some(ANY_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
'?' => {
|
||||
if unescape_special && !feature_test(FeatureFlag::QuestionMarkNoGlob) {
|
||||
'?'
|
||||
if unescape_special && !feature_test(FeatureFlag::QuestionMarkNoGlob) => {
|
||||
to_append_or_none = Some(ANY_CHAR);
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
if unescape_special {
|
||||
'$'
|
||||
if unescape_special => {
|
||||
let is_cmdsub = input_position + 1 < input.len()
|
||||
&& input.char_at(input_position + 1) == '(';
|
||||
if !is_cmdsub {
|
||||
@@ -603,18 +599,16 @@ enum Mode {
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' => {
|
||||
if unescape_special {
|
||||
'{'
|
||||
if unescape_special => {
|
||||
brace_count += 1;
|
||||
to_append_or_none = Some(BRACE_BEGIN);
|
||||
// We need to store where the brace *ends up* in the output.
|
||||
braces.push(result.len());
|
||||
potential_word_start = Some(input_position + 1);
|
||||
}
|
||||
}
|
||||
'}' => {
|
||||
if unescape_special {
|
||||
'}'
|
||||
if unescape_special => {
|
||||
// HACK: The completion machinery sometimes hands us partial tokens.
|
||||
// We can't parse them properly, but it shouldn't hurt,
|
||||
// so we don't assert here.
|
||||
@@ -646,19 +640,16 @@ enum Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
',' => {
|
||||
if unescape_special && brace_count > 0 {
|
||||
','
|
||||
if unescape_special && brace_count > 0 => {
|
||||
to_append_or_none = Some(BRACE_SEP);
|
||||
vars_or_seps.push(input_position);
|
||||
potential_word_start = Some(input_position + 1);
|
||||
}
|
||||
}
|
||||
' ' => {
|
||||
if unescape_special && brace_count > 0 {
|
||||
' '
|
||||
if unescape_special && brace_count > 0 => {
|
||||
to_append_or_none = Some(BRACE_SPACE);
|
||||
}
|
||||
}
|
||||
'\'' => {
|
||||
mode = Mode::SingleQuotes;
|
||||
to_append_or_none = if unescape_special {
|
||||
@@ -743,11 +734,9 @@ enum Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
if unescape_special {
|
||||
to_append_or_none = Some(VARIABLE_EXPAND_SINGLE);
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
'$' if unescape_special => {
|
||||
to_append_or_none = Some(VARIABLE_EXPAND_SINGLE);
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -1030,9 +1019,8 @@ pub fn read_unquoted_escape(
|
||||
/// session. We err on the side of assuming it's not a console session. This approach isn't
|
||||
/// bullet-proof and that's OK.
|
||||
pub fn is_console_session() -> bool {
|
||||
static IS_CONSOLE_SESSION: OnceLock<bool> = OnceLock::new();
|
||||
// TODO(terminal-workaround)
|
||||
*IS_CONSOLE_SESSION.get_or_init(|| {
|
||||
static IS_CONSOLE_SESSION: SingleThreadedLazyCell<bool> = SingleThreadedLazyCell::new(||
|
||||
// No console session on Apple, and ttyname may hang (#12506).
|
||||
!cfg!(apple)
|
||||
&& nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) })
|
||||
@@ -1049,8 +1037,8 @@ pub fn is_console_session() -> bool {
|
||||
// and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something` or `sun-color`.
|
||||
is_console_tty
|
||||
&& env::var_os("TERM").is_none_or(|t| !t.as_bytes().contains(&b'-'))
|
||||
})
|
||||
})
|
||||
}));
|
||||
*IS_CONSOLE_SESSION
|
||||
}
|
||||
|
||||
/// Exits without invoking destructors (via _exit), useful for code after fork.
|
||||
|
||||
14
crates/thread/Cargo.toml
Normal file
14
crates/thread/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "fish-thread"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
98
crates/thread/src/lib.rs
Normal file
98
crates/thread/src/lib.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use core::{cell::OnceCell, marker::PhantomData};
|
||||
use std::{
|
||||
cell::LazyCell,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ThreadId(usize);
|
||||
|
||||
/// Get the calling thread's fish-specific thread id.
|
||||
///
|
||||
/// This thread id is internal to the `threads` module for low-level purposes and should not be
|
||||
/// leaked to other modules; general purpose code that needs a thread id should use rust's native
|
||||
/// thread id functionality.
|
||||
///
|
||||
/// We use our own implementation because Rust's own `Thread::id()` allocates via `Arc`, is fairly
|
||||
/// slow, and uses a `Mutex` on 32-bit platforms (or anywhere without an atomic 64-bit CAS).
|
||||
#[inline(always)]
|
||||
pub fn thread_id() -> ThreadId {
|
||||
static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
// It would be faster and much nicer to use #[thread_local] here, but that's nightly only.
|
||||
// This is still faster than going through Thread::thread_id(); it's something like 15ns
|
||||
// for each `Thread::thread_id()` call vs 1-2 ns with `#[thread_local]` and 2-4ns with
|
||||
// `thread_local!`.
|
||||
thread_local! {
|
||||
static THREAD_ID: ThreadId = ThreadId(THREAD_COUNTER.fetch_add(1, Ordering::Relaxed));
|
||||
}
|
||||
let id = THREAD_ID.with(|id| *id);
|
||||
// This assertion is only here to reduce hair loss in case someone runs into a known linker bug;
|
||||
// as it's not here to catch logic errors in our own code, it can be elided in release mode.
|
||||
debug_assert_ne!(id, ThreadId(0), "TLS storage not initialized!");
|
||||
id
|
||||
}
|
||||
|
||||
/// A `Sync` and `Send` wrapper for non-`Sync`/`Send` types.
|
||||
/// Only allows access from one thread.
|
||||
pub struct SingleThreaded<T> {
|
||||
thread_id: OnceCell<ThreadId>,
|
||||
data: T,
|
||||
// Make type !Send and !Sync by default
|
||||
_marker: PhantomData<*const ()>,
|
||||
}
|
||||
|
||||
// Can be shared across threads as long as T is 'static.
|
||||
unsafe impl<T: 'static> Send for SingleThreaded<T> {}
|
||||
unsafe impl<T: 'static> Sync for SingleThreaded<T> {}
|
||||
|
||||
impl<T> SingleThreaded<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
thread_id: OnceCell::new(),
|
||||
data: value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
assert!(thread_id() == *self.thread_id.get_or_init(thread_id));
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for SingleThreaded<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SingleThreadedLazyCell<T, F = fn() -> T>(SingleThreaded<LazyCell<T, F>>);
|
||||
|
||||
impl<T, F: FnOnce() -> T> SingleThreadedLazyCell<T, F> {
|
||||
pub const fn new(f: F) -> Self {
|
||||
Self(SingleThreaded::new(LazyCell::new(f)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce() -> T> std::ops::Deref for SingleThreadedLazyCell<T, F> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SingleThreadedOnceCell<T>(SingleThreaded<OnceCell<T>>);
|
||||
|
||||
impl<T> SingleThreadedOnceCell<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self(SingleThreaded::new(OnceCell::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for SingleThreadedOnceCell<T> {
|
||||
type Target = OnceCell<T>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,7 @@ anstyle.workspace = true
|
||||
clap.workspace = true
|
||||
fish-build-helper.workspace = true
|
||||
fish-tempfile.workspace = true
|
||||
fish-thread.workspace = true
|
||||
ignore.workspace = true
|
||||
pcre2.workspace = true
|
||||
walkdir.workspace = true
|
||||
|
||||
@@ -14,6 +14,7 @@ macro_rules! fail {
|
||||
}
|
||||
|
||||
pub mod format;
|
||||
pub mod shellcheck;
|
||||
|
||||
pub trait CommandExt {
|
||||
fn run_or_fail(&mut self);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use fish_build_helper::as_os_strs;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use xtask::{CommandExt as _, cargo, format::FormatArgs};
|
||||
use xtask::{CommandExt as _, cargo, format::FormatArgs, shellcheck::shellcheck};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -28,6 +28,9 @@ enum Task {
|
||||
},
|
||||
/// Build man pages
|
||||
ManPages,
|
||||
/// run ShellCheck on non-fish shell scripts
|
||||
#[command(name = "shellcheck")]
|
||||
ShellCheck,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -37,6 +40,7 @@ fn main() {
|
||||
Task::Format(format_args) => xtask::format::format(format_args),
|
||||
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
|
||||
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
|
||||
Task::ShellCheck => shellcheck(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
49
crates/xtask/src/shellcheck.rs
Normal file
49
crates/xtask/src/shellcheck.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use fish_build_helper::workspace_root;
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
use ignore::Walk;
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
pub fn shellcheck() {
|
||||
let file_paths = files_to_check();
|
||||
match Command::new("shellcheck")
|
||||
.args(file_paths)
|
||||
.current_dir(workspace_root())
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to run shellcheck: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_shell_script<P: AsRef<Path>>(path: P) -> bool {
|
||||
let file = File::open(&path).unwrap();
|
||||
let mut first_line = String::new();
|
||||
let Ok(_) = BufReader::new(file).read_line(&mut first_line) else {
|
||||
return false;
|
||||
};
|
||||
static SHEBANG_REGEX: SingleThreadedLazyCell<Regex> =
|
||||
SingleThreadedLazyCell::new(|| Regex::new("^#!.*[^i]sh").unwrap());
|
||||
SHEBANG_REGEX
|
||||
.is_match(first_line.trim().as_bytes())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn files_to_check() -> Vec<PathBuf> {
|
||||
Walk::new(workspace_root())
|
||||
.map(|path| path.unwrap_or_else(|e| fail!("Error traversing workspace: {e}")))
|
||||
.filter(|path| path.file_type().unwrap().is_file())
|
||||
.map(|path| path.into_path())
|
||||
.filter(|path| is_shell_script(path))
|
||||
.collect()
|
||||
}
|
||||
@@ -11,8 +11,10 @@ Synopsis
|
||||
Description
|
||||
-----------
|
||||
|
||||
``set_color`` is used to control the color and styling of text in the terminal.
|
||||
*VALUE* describes that styling.
|
||||
``set_color`` controls the color and styling of text in the terminal.
|
||||
It writes non-printing color and text style escape sequences to standard output.
|
||||
|
||||
*VALUE* describes the styling.
|
||||
*VALUE* can be a reserved color name like **red** or an RGB color value given as 3 or 6 hexadecimal digits ("F27" or "FF2277").
|
||||
A special keyword **normal** resets text formatting to terminal defaults, however it is not recommended and the **--reset** option is preferred as it is less confusing and more future-proof.
|
||||
|
||||
@@ -93,7 +95,9 @@ Notes
|
||||
3. Because of the risk of confusion, ``set_color --reset`` is recommended over ``set_color normal``.
|
||||
4. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
|
||||
5. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
|
||||
6. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
|
||||
6. If you use ``set_color`` in a command substitution or a pipe, these characters will also be captured.
|
||||
This may or may not be desirable.
|
||||
Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
@@ -11,7 +11,7 @@ complete -c wget -s a -l append-output -d "Append all messages to logfile"
|
||||
complete -c wget -s d -l debug -d "Turn on debug output"
|
||||
complete -c wget -s q -l quiet -d "Quiet mode"
|
||||
complete -c wget -s v -l verbose -d "Verbose mode"
|
||||
complete -c wget -l non-verbose -d "Turn off verbose without being completely quiet"
|
||||
complete -c wget -l no-verbose -d "Turn off verbose without being completely quiet"
|
||||
complete -c wget -o nv -d "Turn off verbose without being completely quiet"
|
||||
complete -c wget -s i -l input-file -d "Read URLs from file" -r
|
||||
complete -c wget -s F -l force-html -d "Force input to be treated as HTML"
|
||||
|
||||
@@ -36,8 +36,8 @@ 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.
|
||||
set -l xdg_data_dirs
|
||||
if set -q XDG_DATA_DIRS
|
||||
set -l xdg_data_dirs /usr/local/share/fish /usr/share/fish
|
||||
if test -n "$XDG_DATA_DIRS"
|
||||
set --path xdg_data_dirs $XDG_DATA_DIRS
|
||||
set xdg_data_dirs (string replace -r '([^/])/$' '$1' -- $xdg_data_dirs)/fish
|
||||
end
|
||||
|
||||
@@ -91,9 +91,8 @@ function funced --description 'Edit function definition'
|
||||
# Repeatedly edit until it either parses successfully, or the user cancels
|
||||
# If the editor command itself fails, we assume the user cancelled or the file
|
||||
# could not be edited, and we do not try again
|
||||
set -l checksum (__fish_md5 "$tmpname")
|
||||
while true
|
||||
set -l checksum (__fish_md5 "$tmpname")
|
||||
|
||||
if not $editor $tmpname
|
||||
echo (_ "Editing failed or was cancelled")
|
||||
else
|
||||
|
||||
@@ -56,6 +56,7 @@ tt {
|
||||
.tab:hover,
|
||||
#tab_contents .master_element:hover,
|
||||
.color_scheme_choice_container:hover,
|
||||
.data_table > tr:hover,
|
||||
.prompt_choices_list > .ng-scope:hover {
|
||||
background-color: #DDE;
|
||||
}
|
||||
@@ -284,6 +285,7 @@ tt {
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.data_table_row {}
|
||||
@@ -310,7 +312,8 @@ tt {
|
||||
}
|
||||
|
||||
.history_delete {
|
||||
width: 20px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.data_table_cell,
|
||||
|
||||
@@ -293,7 +293,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_abbreviations() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
{
|
||||
let mut abbrs = abbrs_get_set();
|
||||
@@ -424,7 +424,7 @@ macro_rules! validate {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn rename_abbrs() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
|
||||
with_abbrs_mut(|abbrs_g| {
|
||||
let mut add = |name: &wstr, repl: &wstr, position: Position| {
|
||||
|
||||
@@ -2832,7 +2832,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_ast_parse() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let src = L!("echo");
|
||||
let ast = ast::parse(src, ParseTreeFlags::default(), None);
|
||||
assert!(!ast.any_error);
|
||||
@@ -2889,7 +2889,7 @@ fn test_is_same_node() {
|
||||
}
|
||||
|
||||
// Run with cargo +nightly bench --features=benchmark
|
||||
#[cfg(feature = "benchmark")]
|
||||
#[cfg(all(nightly, feature = "benchmark"))]
|
||||
#[cfg(test)]
|
||||
mod bench {
|
||||
extern crate test;
|
||||
|
||||
@@ -463,7 +463,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_autoload() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
use crate::fds::wopen_cloexec;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use nix::fcntl::OFlag;
|
||||
|
||||
@@ -1206,39 +1206,6 @@ fn read_file(mut f: impl Read) -> Result<WString, ()> {
|
||||
Ok(bytes2wcstring(&buf))
|
||||
}
|
||||
|
||||
fn highlight_role_to_string(role: HighlightRole) -> &'static wstr {
|
||||
match role {
|
||||
HighlightRole::normal => L!("normal"),
|
||||
HighlightRole::error => L!("error"),
|
||||
HighlightRole::command => L!("command"),
|
||||
HighlightRole::keyword => L!("keyword"),
|
||||
HighlightRole::statement_terminator => L!("statement_terminator"),
|
||||
HighlightRole::param => L!("param"),
|
||||
HighlightRole::option => L!("option"),
|
||||
HighlightRole::comment => L!("comment"),
|
||||
HighlightRole::search_match => L!("search_match"),
|
||||
HighlightRole::operat => L!("operat"),
|
||||
HighlightRole::escape => L!("escape"),
|
||||
HighlightRole::quote => L!("quote"),
|
||||
HighlightRole::redirection => L!("redirection"),
|
||||
HighlightRole::autosuggestion => L!("autosuggestion"),
|
||||
HighlightRole::selection => L!("selection"),
|
||||
HighlightRole::pager_progress => L!("pager_progress"),
|
||||
HighlightRole::pager_background => L!("pager_background"),
|
||||
HighlightRole::pager_prefix => L!("pager_prefix"),
|
||||
HighlightRole::pager_completion => L!("pager_completion"),
|
||||
HighlightRole::pager_description => L!("pager_description"),
|
||||
HighlightRole::pager_secondary_background => L!("pager_secondary_background"),
|
||||
HighlightRole::pager_secondary_prefix => L!("pager_secondary_prefix"),
|
||||
HighlightRole::pager_secondary_completion => L!("pager_secondary_completion"),
|
||||
HighlightRole::pager_secondary_description => L!("pager_secondary_description"),
|
||||
HighlightRole::pager_selected_background => L!("pager_selected_background"),
|
||||
HighlightRole::pager_selected_prefix => L!("pager_selected_prefix"),
|
||||
HighlightRole::pager_selected_completion => L!("pager_selected_completion"),
|
||||
HighlightRole::pager_selected_description => L!("pager_selected_description"),
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point for Pygments CSV output.
|
||||
// Our output is a newline-separated string.
|
||||
// Each line is of the form `start,end,role`
|
||||
@@ -1281,14 +1248,7 @@ struct TokenRange {
|
||||
// Now render these to a string.
|
||||
let mut result = String::with_capacity(token_ranges.len() * 32);
|
||||
for range in token_ranges {
|
||||
writeln!(
|
||||
result,
|
||||
"{},{},{}",
|
||||
range.start,
|
||||
range.end,
|
||||
highlight_role_to_string(range.role)
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(result, "{},{},{}", range.start, range.end, range.role).unwrap();
|
||||
}
|
||||
result.into_bytes()
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
print_help::print_help,
|
||||
proc::set_interactive_session,
|
||||
reader::{
|
||||
check_exit_loop_maybe_warning, reader_init, safe_reader_set_exit_signal, set_shell_modes,
|
||||
terminal_init,
|
||||
check_exit_loop_maybe_warning, reader_init, set_shell_modes,
|
||||
signal_safe_reader_set_exit_signal, terminal_init,
|
||||
},
|
||||
threads,
|
||||
topic_monitor::topic_monitor_init,
|
||||
@@ -98,7 +98,7 @@ fn process_input(
|
||||
use QueryResultEvent::*;
|
||||
let kevt = match input_queue.readch() {
|
||||
CharEvent::Implicit(ImplicitEvent::Eof) => {
|
||||
safe_reader_set_exit_signal(libc::SIGHUP);
|
||||
signal_safe_reader_set_exit_signal(libc::SIGHUP);
|
||||
continue;
|
||||
}
|
||||
CharEvent::Key(kevt) => kevt,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
use crate::{builtins::error::Error, env::Environment as _, err_fmt, wutil::wrealpath};
|
||||
|
||||
// The pwd builtin. Respect -P to resolve symbolic links. Respect -L to not do that (the default).
|
||||
const short_options: &wstr = L!("LPh");
|
||||
const long_options: &[WOption] = &[
|
||||
const SHORT_OPTIONS: &wstr = L!("LPh");
|
||||
const LONG_OPTIONS: &[WOption] = &[
|
||||
wopt(L!("help"), NoArgument, 'h'),
|
||||
wopt(L!("logical"), NoArgument, 'L'),
|
||||
wopt(L!("physical"), NoArgument, 'P'),
|
||||
@@ -16,7 +16,7 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
|
||||
let cmd = argv[0];
|
||||
let argc = argv.len();
|
||||
let mut resolve_symlinks = false;
|
||||
let mut w = WGetopter::new(short_options, long_options, argv);
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
|
||||
while let Some(opt) = w.next_opt() {
|
||||
match opt {
|
||||
'L' => resolve_symlinks = false,
|
||||
|
||||
@@ -16,8 +16,8 @@ struct Options {
|
||||
no_symlinks: bool,
|
||||
}
|
||||
|
||||
const short_options: &wstr = L!("+hs");
|
||||
const long_options: &[WOption] = &[
|
||||
const SHORT_OPTIONS: &wstr = L!("+hs");
|
||||
const LONG_OPTIONS: &[WOption] = &[
|
||||
wopt(L!("no-symlinks"), NoArgument, 's'),
|
||||
wopt(L!("help"), NoArgument, 'h'),
|
||||
];
|
||||
@@ -31,7 +31,7 @@ fn parse_options(
|
||||
|
||||
let mut opts = Options::default();
|
||||
|
||||
let mut w = WGetopter::new(short_options, long_options, args);
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, args);
|
||||
|
||||
while let Some(c) = w.next_opt() {
|
||||
match c {
|
||||
|
||||
@@ -74,7 +74,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "escape"], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "escape", ""], STATUS_CMD_OK, "''\n");
|
||||
validate!(["string", "escape", "-n", ""], STATUS_CMD_OK, "\n");
|
||||
|
||||
@@ -113,7 +113,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "join"], STATUS_INVALID_ARGS, "");
|
||||
validate!(["string", "join", ""], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "join", "", "", "", ""], STATUS_CMD_OK, "\n");
|
||||
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "length"], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "length", ""], STATUS_CMD_ERROR, "0\n");
|
||||
validate!(["string", "length", "", "", ""], STATUS_CMD_ERROR, "0\n0\n0\n");
|
||||
|
||||
@@ -427,7 +427,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "match"], STATUS_INVALID_ARGS, "");
|
||||
validate!(["string", "match", ""], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "match", "", ""], STATUS_CMD_OK, "\n");
|
||||
|
||||
@@ -160,12 +160,8 @@ fn interpret_escape(arg: &'args wstr) -> Option<WString> {
|
||||
let mut cursor = arg;
|
||||
while !cursor.is_empty() {
|
||||
if cursor.char_at(0) == '\\' {
|
||||
if let Some(escape_len) = read_unquoted_escape(cursor, &mut result, true, false) {
|
||||
cursor = cursor.slice_from(escape_len);
|
||||
} else {
|
||||
// invalid escape
|
||||
return None;
|
||||
}
|
||||
let escape_len = read_unquoted_escape(cursor, &mut result, true, false)?;
|
||||
cursor = cursor.slice_from(escape_len);
|
||||
} else {
|
||||
result.push(cursor.char_at(0));
|
||||
cursor = cursor.slice_from(1);
|
||||
@@ -285,7 +281,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "replace", ""], STATUS_INVALID_ARGS, "");
|
||||
validate!(["string", "replace", "", ""], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "replace", "", "", ""], STATUS_CMD_ERROR, "\n");
|
||||
|
||||
@@ -299,7 +299,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "split"], STATUS_INVALID_ARGS, "");
|
||||
validate!(["string", "split", ":"], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "split", ".", "www.ch.ic.ac.uk"], STATUS_CMD_OK, "www\nch\nic\nac\nuk\n");
|
||||
|
||||
@@ -130,7 +130,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "sub"], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "sub", "abcde"], STATUS_CMD_OK, "abcde\n");
|
||||
validate!(["string", "sub", "-l", "x", "abcde"], STATUS_INVALID_ARGS, "");
|
||||
|
||||
@@ -105,7 +105,7 @@ mod tests {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn plain() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
validate!(["string", "trim"], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "trim", ""], STATUS_CMD_ERROR, "\n");
|
||||
validate!(["string", "trim", " "], STATUS_CMD_OK, "\n");
|
||||
|
||||
@@ -1261,7 +1261,7 @@ fn test_test() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_test_builtin() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
test_test_brackets();
|
||||
test_test();
|
||||
}
|
||||
|
||||
@@ -213,8 +213,8 @@ pub fn is_windows_subsystem_for_linux(_: WSL) -> bool {
|
||||
/// See <https://github.com/Microsoft/WSL/issues/423> and [Microsoft/WSL#2997](https://github.com/Microsoft/WSL/issues/2997)
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_windows_subsystem_for_linux(v: WSL) -> bool {
|
||||
use std::sync::OnceLock;
|
||||
static RESULT: OnceLock<Option<WSL>> = OnceLock::new();
|
||||
use fish_thread::SingleThreadedOnceCell;
|
||||
static RESULT: SingleThreadedOnceCell<Option<WSL>> = SingleThreadedOnceCell::new();
|
||||
|
||||
// This is called post-fork from [`report_setpgid_error()`], so the fast path must not involve
|
||||
// any allocations or mutexes. We can't rely on all the std functions to be alloc-free in both
|
||||
@@ -286,7 +286,7 @@ pub fn is_windows_subsystem_for_linux(v: WSL) -> bool {
|
||||
Some(WSL::V1)
|
||||
});
|
||||
|
||||
wsl.map(|wsl| v == WSL::Any || wsl == v).unwrap_or(false)
|
||||
wsl.is_some_and(|wsl| v == WSL::Any || wsl == v)
|
||||
}
|
||||
|
||||
/// Test if the given char is valid in a variable name.
|
||||
@@ -467,7 +467,7 @@ fn test_escape_no_printables() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_escape_quotes() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
macro_rules! validate {
|
||||
($cmd:expr, $quote:expr, $no_tilde:expr, $expected:expr) => {
|
||||
assert_eq!(
|
||||
@@ -707,11 +707,11 @@ macro_rules! check_decode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "benchmark")]
|
||||
#[cfg(all(nightly, feature = "benchmark"))]
|
||||
#[cfg(test)]
|
||||
mod bench {
|
||||
extern crate test;
|
||||
use crate::common::bytes2wcstring;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
use fish_util::wcsfilecmp;
|
||||
use fish_wcstringutil::{
|
||||
StringFuzzyMatch, string_fuzzy_match_string, string_prefixes_string,
|
||||
string_prefixes_string_case_insensitive, string_suffixes_string_case_insensitive,
|
||||
strip_executable_suffix,
|
||||
string_suffixes_string_case_insensitive, strip_executable_suffix,
|
||||
};
|
||||
use fish_widestring::{WExt as _, charptr2wcstring};
|
||||
use std::{
|
||||
@@ -159,7 +158,10 @@ pub fn new(
|
||||
r#match: StringFuzzyMatch, /* = exact_match */
|
||||
flags: CompleteFlags,
|
||||
) -> Self {
|
||||
let flags = resolve_auto_space(&completion, flags);
|
||||
let mut flags = resolve_auto_space(&completion, flags);
|
||||
if r#match.requires_full_replacement() {
|
||||
flags |= CompleteFlags::REPLACES_TOKEN;
|
||||
}
|
||||
Self {
|
||||
completion,
|
||||
description,
|
||||
@@ -1483,14 +1485,12 @@ fn complete_param_for_command(
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
let mut flags = CompleteFlags::empty();
|
||||
|
||||
if r#match.requires_full_replacement() {
|
||||
flags = CompleteFlags::REPLACES_TOKEN;
|
||||
let offset = if r#match.requires_full_replacement() {
|
||||
0
|
||||
} else {
|
||||
offset = s.len();
|
||||
}
|
||||
s.len()
|
||||
};
|
||||
let completion = whole_opt.slice_from(offset);
|
||||
|
||||
// does this switch have any known arguments
|
||||
let has_arg = !o.comp.is_empty();
|
||||
@@ -1502,14 +1502,14 @@ fn complete_param_for_command(
|
||||
// a completion. By default we avoid using '=' and instead rely on '--switch
|
||||
// switch-arg', since it is more commonly supported by homebrew getopt-like
|
||||
// functions.
|
||||
let completion = sprintf!("%s=", whole_opt.slice_from(offset));
|
||||
let completion = sprintf!("%s=", completion);
|
||||
|
||||
// Append a long-style option with a mandatory trailing equal sign
|
||||
if !self.completions.add(Completion::new(
|
||||
completion,
|
||||
o.desc.localize().to_owned(),
|
||||
StringFuzzyMatch::exact_match(),
|
||||
flags | CompleteFlags::NO_SPACE,
|
||||
r#match,
|
||||
CompleteFlags::NO_SPACE,
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1517,10 +1517,10 @@ fn complete_param_for_command(
|
||||
|
||||
// Append a long-style option
|
||||
if !self.completions.add(Completion::new(
|
||||
whole_opt.slice_from(offset).to_owned(),
|
||||
completion.to_owned(),
|
||||
o.desc.localize().to_owned(),
|
||||
StringFuzzyMatch::exact_match(),
|
||||
flags,
|
||||
r#match,
|
||||
CompleteFlags::empty(),
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1666,7 +1666,7 @@ fn complete_variable(&mut self, s: &wstr, start_offset: usize) -> bool {
|
||||
// Take only the suffix.
|
||||
env_name.slice_from(varlen).to_owned()
|
||||
} else {
|
||||
flags |= CompleteFlags::REPLACES_TOKEN | CompleteFlags::DONT_ESCAPE;
|
||||
flags |= CompleteFlags::DONT_ESCAPE;
|
||||
whole_var.slice_to(start_offset).to_owned() + env_name.as_utfstr()
|
||||
};
|
||||
|
||||
@@ -1735,10 +1735,8 @@ enum Mode {
|
||||
|
||||
match c {
|
||||
'\\' => skip_next = true,
|
||||
'$' => {
|
||||
if mode == Unquoted || mode == DoubleQuoted {
|
||||
variable_start = Some(in_pos);
|
||||
}
|
||||
'$' if (mode == Unquoted || mode == DoubleQuoted) => {
|
||||
variable_start = Some(in_pos);
|
||||
}
|
||||
'\'' => {
|
||||
if mode == SingleQuoted {
|
||||
@@ -1754,9 +1752,7 @@ enum Mode {
|
||||
mode = DoubleQuoted;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// all other chars ignored here
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1823,30 +1819,23 @@ fn getpwent_name() -> Option<WString> {
|
||||
break;
|
||||
}
|
||||
|
||||
if string_prefixes_string(user_name, &pw_name) {
|
||||
if let Some(r#match) = StringFuzzyMatch::try_create(user_name, &pw_name, true) {
|
||||
let desc = wgettext_fmt!(COMPLETE_USER_DESC, &pw_name);
|
||||
// Append a user name.
|
||||
// TODO: propagate overflow?
|
||||
let mut flags = CompleteFlags::NO_SPACE;
|
||||
if r#match.requires_full_replacement() {
|
||||
flags |= CompleteFlags::DONT_ESCAPE;
|
||||
}
|
||||
let _ = self.completions.add(Completion::new(
|
||||
pw_name.slice_from(name_len).to_owned(),
|
||||
if r#match.requires_full_replacement() {
|
||||
sprintf!("~%s", &pw_name)
|
||||
} else {
|
||||
pw_name.slice_from(name_len).to_owned()
|
||||
},
|
||||
desc,
|
||||
StringFuzzyMatch::exact_match(),
|
||||
CompleteFlags::NO_SPACE,
|
||||
));
|
||||
result = true;
|
||||
} else if string_prefixes_string_case_insensitive(user_name, &pw_name) {
|
||||
let name = sprintf!("~%s", &pw_name);
|
||||
let desc = wgettext_fmt!(COMPLETE_USER_DESC, &pw_name);
|
||||
|
||||
// Append a user name
|
||||
// TODO: propagate overflow?
|
||||
let _ = self.completions.add(Completion::new(
|
||||
name,
|
||||
desc,
|
||||
StringFuzzyMatch::exact_match(),
|
||||
CompleteFlags::REPLACES_TOKEN
|
||||
| CompleteFlags::DONT_ESCAPE
|
||||
| CompleteFlags::NO_SPACE,
|
||||
r#match,
|
||||
flags,
|
||||
));
|
||||
result = true;
|
||||
}
|
||||
@@ -2657,7 +2646,7 @@ fn comma_join(lst: Vec<WString>) -> WString {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_complete() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let vars = PwdEnvironment {
|
||||
parent: TestEnvironment {
|
||||
vars: HashMap::from([
|
||||
@@ -3175,7 +3164,7 @@ macro_rules! unique_completion_applies_as {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_autosuggest_suggest_special() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
macro_rules! perform_one_autosuggestion_cd_test {
|
||||
($command:literal, $expected:literal, $vars:expr) => {
|
||||
@@ -3356,7 +3345,7 @@ macro_rules! perform_one_completion_cd_test {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_autosuggestion_ignores() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Testing scenarios that should produce no autosuggestions
|
||||
macro_rules! perform_one_autosuggestion_should_ignore_test {
|
||||
($command:literal) => {
|
||||
|
||||
8
src/env/config_paths.rs
vendored
8
src/env/config_paths.rs
vendored
@@ -1,10 +1,10 @@
|
||||
use crate::common::{BUILD_DIR, get_program_name};
|
||||
use crate::{flog, flogf};
|
||||
use fish_build_helper::workspace_root;
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// A struct of configuration directories, determined in main() that fish will optionally pass to
|
||||
/// env_init.
|
||||
@@ -22,7 +22,6 @@ pub struct ConfigPaths {
|
||||
|
||||
impl ConfigPaths {
|
||||
pub fn new() -> Self {
|
||||
FISH_PATH.get_or_init(compute_fish_path);
|
||||
let exec_path = get_fish_path();
|
||||
flog!(
|
||||
config,
|
||||
@@ -167,11 +166,12 @@ pub enum FishPath {
|
||||
LookUpInPath,
|
||||
}
|
||||
|
||||
static FISH_PATH: OnceLock<FishPath> = OnceLock::new();
|
||||
static FISH_PATH: SingleThreadedLazyCell<FishPath, fn() -> FishPath> =
|
||||
SingleThreadedLazyCell::new(compute_fish_path);
|
||||
|
||||
/// Get the absolute path to the fish executable itself
|
||||
pub fn get_fish_path() -> &'static FishPath {
|
||||
FISH_PATH.get().unwrap()
|
||||
&FISH_PATH
|
||||
}
|
||||
|
||||
fn compute_fish_path() -> FishPath {
|
||||
|
||||
24
src/env/environment.rs
vendored
24
src/env/environment.rs
vendored
@@ -30,6 +30,7 @@
|
||||
wutil::{fish_wcstol, wgetcwd},
|
||||
};
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
use fish_thread::{SingleThreadedLazyCell, SingleThreadedOnceCell};
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::{cstr2wcstring, osstr2wcstring, str2wcstring};
|
||||
use libc::c_int;
|
||||
@@ -41,7 +42,7 @@
|
||||
collections::HashMap,
|
||||
ffi::CStr,
|
||||
path::PathBuf,
|
||||
sync::{Arc, LazyLock, OnceLock},
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
/// Set when a universal variable has been modified but not yet been written to disk via sync().
|
||||
@@ -405,14 +406,14 @@ pub fn universal_sync(&self, always: bool, is_repainting: bool) -> Vec<Event> {
|
||||
/// A variable stack that only represents globals.
|
||||
/// Do not push or pop from this.
|
||||
pub fn globals() -> &'static EnvStack {
|
||||
use std::sync::OnceLock;
|
||||
static GLOBALS: OnceLock<EnvStack> = OnceLock::new();
|
||||
GLOBALS.get_or_init(|| EnvStack {
|
||||
inner: EnvStackImpl::new(),
|
||||
can_push_pop: false,
|
||||
// Do not dispatch variable changes - this is used at startup when we are importing env vars.
|
||||
dispatches_var_changes: false,
|
||||
})
|
||||
static GLOBALS: SingleThreadedLazyCell<EnvStack> =
|
||||
SingleThreadedLazyCell::new(|| EnvStack {
|
||||
inner: EnvStackImpl::new(),
|
||||
can_push_pop: false,
|
||||
// Do not dispatch variable changes - this is used at startup when we are importing env vars.
|
||||
dispatches_var_changes: false,
|
||||
});
|
||||
&GLOBALS
|
||||
}
|
||||
|
||||
pub fn set_argv(&self, argv: Vec<WString>, is_repainting: bool) {
|
||||
@@ -562,7 +563,8 @@ fn setup_path(global_exported_mode: EnvSetMode) {
|
||||
|
||||
/// The originally inherited variables and their values.
|
||||
/// This is a simple key->value map and not e.g. cut into paths.
|
||||
pub static INHERITED_VARS: OnceLock<HashMap<WString, WString>> = OnceLock::new();
|
||||
pub static INHERITED_VARS: SingleThreadedOnceCell<HashMap<WString, WString>> =
|
||||
SingleThreadedOnceCell::new();
|
||||
|
||||
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
|
||||
let vars = EnvStack::globals();
|
||||
@@ -812,7 +814,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_env_snapshot() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
std::fs::create_dir_all("test/fish_env_snapshot_test/").unwrap();
|
||||
let parser = TestParser::new();
|
||||
let vars = parser.vars();
|
||||
|
||||
10
src/env/environment_impl.rs
vendored
10
src/env/environment_impl.rs
vendored
@@ -13,6 +13,7 @@
|
||||
use crate::reader::{commandline_get_state, reader_status_count};
|
||||
use crate::threads::{is_forked_child, is_main_thread};
|
||||
use crate::wutil::fish_wcstol_radix;
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use nix::sys::stat::{Mode, umask};
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
@@ -27,13 +28,10 @@
|
||||
/// Getter for universal variables.
|
||||
/// This is typically initialized in env_init(), and is considered empty before then.
|
||||
pub fn uvars() -> MutexGuard<'static, EnvUniversal> {
|
||||
use std::sync::OnceLock;
|
||||
/// Universal variables instance.
|
||||
static UVARS: OnceLock<Mutex<EnvUniversal>> = OnceLock::new();
|
||||
UVARS
|
||||
.get_or_init(|| Mutex::new(EnvUniversal::new()))
|
||||
.lock()
|
||||
.unwrap()
|
||||
static UVARS: SingleThreadedLazyCell<Mutex<EnvUniversal>> =
|
||||
SingleThreadedLazyCell::new(|| Mutex::new(EnvUniversal::new()));
|
||||
UVARS.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Whether we were launched with no_config; in this case setting a uvar instead sets a global.
|
||||
|
||||
10
src/env/var.rs
vendored
10
src/env/var.rs
vendored
@@ -1,6 +1,7 @@
|
||||
use crate::signal::Signal;
|
||||
use bitflags::bitflags;
|
||||
use fish_common::assert_sorted_by_name;
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::{L, WString, wstr};
|
||||
use libc::c_int;
|
||||
@@ -130,13 +131,12 @@ pub struct EnvVar {
|
||||
|
||||
impl Default for EnvVar {
|
||||
fn default() -> Self {
|
||||
use std::sync::OnceLock;
|
||||
/// A shared read-only empty list.
|
||||
static EMPTY_LIST: OnceLock<Arc<[WString]>> = OnceLock::new();
|
||||
let empty_list = EMPTY_LIST.get_or_init(|| Arc::new([]));
|
||||
static EMPTY_LIST: SingleThreadedLazyCell<Arc<[WString]>> =
|
||||
SingleThreadedLazyCell::new(|| Arc::new([]));
|
||||
|
||||
EnvVar {
|
||||
values: Arc::clone(empty_list),
|
||||
values: Arc::clone(&*EMPTY_LIST),
|
||||
flags: EnvVarFlags::empty(),
|
||||
}
|
||||
}
|
||||
@@ -369,7 +369,7 @@ fn test_timezone_env_vars() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_env_vars() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
test_timezone_env_vars();
|
||||
// TODO: Add tests for the locale vars.
|
||||
|
||||
|
||||
@@ -830,7 +830,7 @@ fn make_test_uvar_path() -> std::io::Result<(TempDir, WString)> {
|
||||
}
|
||||
|
||||
fn test_universal_helper(x: usize, path: &wstr) {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let mut uvars = EnvUniversal::new();
|
||||
uvars.initialize_at_path(path.to_owned());
|
||||
|
||||
@@ -853,7 +853,7 @@ fn test_universal_helper(x: usize, path: &wstr) {
|
||||
|
||||
#[test]
|
||||
fn test_universal() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let (_test_dir, test_path) = make_test_uvar_path().unwrap();
|
||||
|
||||
let threads = 1;
|
||||
@@ -893,7 +893,7 @@ fn test_universal() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_universal_output() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let flag_export = EnvVarFlags::EXPORT;
|
||||
let flag_pathvar = EnvVarFlags::PATHVAR;
|
||||
|
||||
@@ -950,7 +950,7 @@ fn test_universal_output() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_universal_parsing() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let input = concat!(
|
||||
"# This file contains fish universal variable definitions.\n",
|
||||
"# VERSION: 3.0\n",
|
||||
@@ -1002,7 +1002,7 @@ fn test_universal_parsing() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_universal_parsing_legacy() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let input = concat!(
|
||||
"# This file contains fish universal variable definitions.\n",
|
||||
"SET varA:ValA1\\x1eValA2\n",
|
||||
@@ -1030,7 +1030,7 @@ fn test_universal_parsing_legacy() {
|
||||
|
||||
#[test]
|
||||
fn test_universal_callbacks() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let (_test_dir, test_path) = make_test_uvar_path().unwrap();
|
||||
let mut uvars1 = EnvUniversal::new();
|
||||
let mut uvars2 = EnvUniversal::new();
|
||||
@@ -1100,7 +1100,7 @@ macro_rules! sync {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_universal_formats() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
macro_rules! validate {
|
||||
( $version_line:literal, $expected_format:expr ) => {
|
||||
assert_eq!(
|
||||
@@ -1121,7 +1121,7 @@ macro_rules! validate {
|
||||
|
||||
#[test]
|
||||
fn test_universal_ok_to_save() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Ensure we don't try to save after reading from a newer fish.
|
||||
let (_test_dir, test_path) = make_test_uvar_path().unwrap();
|
||||
let contents = b"# VERSION: 99999.99\n";
|
||||
|
||||
20
src/exec.rs
20
src/exec.rs
@@ -20,7 +20,7 @@
|
||||
PATH_BSHELL, blocked_signals_for_job,
|
||||
postfork::{
|
||||
child_setup_process, execute_fork, execute_setpgid, report_setpgid_error,
|
||||
safe_report_exec_error,
|
||||
signal_safe_report_exec_error,
|
||||
},
|
||||
};
|
||||
use crate::function::{self, FunctionProperties};
|
||||
@@ -44,6 +44,7 @@
|
||||
use crate::wutil::{fish_wcstol, perror_io};
|
||||
use errno::{errno, set_errno};
|
||||
use fish_common::{ScopeGuard, exit_without_destructors, truncate_at_nul, write_loop};
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
use fish_widestring::{ToWString as _, bytes2wcstring, wcs2bytes, wcs2zstring};
|
||||
use libc::{
|
||||
EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
|
||||
@@ -62,7 +63,7 @@
|
||||
os::fd::{AsRawFd as _, FromRawFd as _, OwnedFd, RawFd},
|
||||
slice,
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
},
|
||||
};
|
||||
@@ -72,9 +73,10 @@
|
||||
/// to their target fds.
|
||||
/// TODO: this IO could be multiplexed using FdMonitor.
|
||||
fn exec_thread_pool() -> &'static Arc<ThreadPool> {
|
||||
static EXEC_THREAD_POOL: OnceLock<Arc<ThreadPool>> = OnceLock::new();
|
||||
static EXEC_THREAD_POOL: SingleThreadedLazyCell<Arc<ThreadPool>> =
|
||||
SingleThreadedLazyCell::new(|| ThreadPool::new(1, usize::MAX));
|
||||
// Use an unbounded queue because otherwise we risk deadlock.
|
||||
EXEC_THREAD_POOL.get_or_init(|| ThreadPool::new(1, usize::MAX))
|
||||
&EXEC_THREAD_POOL
|
||||
}
|
||||
|
||||
/// Execute the processes specified by `j` in the parser \p.
|
||||
@@ -393,7 +395,7 @@ pub fn is_thompson_shell_script(path: &CStr) -> bool {
|
||||
/// This function is executed by the child process created by a call to fork(). It should be called
|
||||
/// after \c child_setup_process. It calls execve to replace the fish process image with the command
|
||||
/// specified in \c p. It never returns. Called in a forked child! Do not allocate memory, etc.
|
||||
fn safe_launch_process(
|
||||
fn signal_safe_launch_process(
|
||||
_p: &Process,
|
||||
actual_cmd: &CStr,
|
||||
argv: &OwningNullTerminatedArray,
|
||||
@@ -431,7 +433,7 @@ fn safe_launch_process(
|
||||
}
|
||||
|
||||
set_errno(err);
|
||||
safe_report_exec_error(errno().0, actual_cmd, argv, envv);
|
||||
signal_safe_report_exec_error(errno().0, actual_cmd, argv, envv);
|
||||
exit_without_destructors(exit_code_from_exec_error(err.0));
|
||||
}
|
||||
|
||||
@@ -451,7 +453,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
restore_term_mode();
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envp);
|
||||
signal_safe_launch_process(p, &actual_cmd, &argv, &envp);
|
||||
}
|
||||
|
||||
// Returns whether we can use posix spawn for a given process in a given job.
|
||||
@@ -908,7 +910,7 @@ fn exec_external_command(
|
||||
let pid = match pid {
|
||||
Ok(pid) => pid,
|
||||
Err(err) => {
|
||||
safe_report_exec_error(err.0, &actual_cmd, &argv, &envv);
|
||||
signal_safe_report_exec_error(err.0, &actual_cmd, &argv, &envv);
|
||||
p.status
|
||||
.set(ProcStatus::from_exit_code(exit_code_from_exec_error(err.0)));
|
||||
return Err(());
|
||||
@@ -940,7 +942,7 @@ fn exec_external_command(
|
||||
}
|
||||
|
||||
fork_child_for_process(j, p, &dup2s, pgroup_policy, |p| {
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envv)
|
||||
signal_safe_launch_process(p, &actual_cmd, &argv, &envv)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -800,10 +800,8 @@ fn expand_braces(
|
||||
brace_end = Some(pos);
|
||||
}
|
||||
}
|
||||
BRACE_SEP => {
|
||||
if brace_count == 1 {
|
||||
last_sep = Some(pos);
|
||||
}
|
||||
BRACE_SEP if brace_count == 1 => {
|
||||
last_sep = Some(pos);
|
||||
}
|
||||
_ => {
|
||||
// we ignore all other characters here
|
||||
@@ -1608,7 +1606,7 @@ fn expand_test_impl(
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_expand() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
/// Perform parameter expansion and test if the output equals the zero-terminated parameter list /// supplied.
|
||||
///
|
||||
@@ -1902,7 +1900,7 @@ macro_rules! expand_test {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_expand_overflow() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Testing overflowing expansions
|
||||
// Ensure that we have sane limits on number of expansions - see #7497.
|
||||
|
||||
@@ -1939,7 +1937,7 @@ fn test_expand_overflow() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_abbreviations() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Testing abbreviations
|
||||
|
||||
with_abbrs_mut(|abbrset| {
|
||||
|
||||
@@ -551,7 +551,7 @@ fn write42(&self) {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn fd_monitor_items() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let monitor = FdMonitor::new();
|
||||
|
||||
// Item which will never receive data or be called.
|
||||
|
||||
@@ -323,7 +323,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_pipes() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Here we just test that each pipe has CLOEXEC set and is in the high range.
|
||||
// Note pipe creation may fail due to fd exhaustion; don't fail in that case.
|
||||
let mut pipes = vec![];
|
||||
|
||||
@@ -225,7 +225,7 @@ pub fn execute_fork() -> pid_t {
|
||||
exit_without_destructors(1)
|
||||
}
|
||||
|
||||
pub(crate) fn safe_report_exec_error(
|
||||
pub(crate) fn signal_safe_report_exec_error(
|
||||
err: i32,
|
||||
actual_cmd: &CStr,
|
||||
argvv: &OwningNullTerminatedArray,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use crate::exec::{PgroupPolicy, is_thompson_shell_script};
|
||||
use crate::proc::Job;
|
||||
use crate::redirection::Dup2List;
|
||||
use crate::signal::signals_to_default;
|
||||
use crate::signal::SIGNALS_TO_DEFAULT;
|
||||
use errno::Errno;
|
||||
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
|
||||
use std::ffi::{CStr, CString};
|
||||
@@ -129,7 +129,7 @@ pub fn new(
|
||||
}
|
||||
|
||||
// Everybody gets default handlers.
|
||||
attr.set_sigdefault(&signals_to_default)?;
|
||||
attr.set_sigdefault(&SIGNALS_TO_DEFAULT)?;
|
||||
|
||||
// Reset the sigmask.
|
||||
let mut sigmask = MaybeUninit::uninit();
|
||||
|
||||
@@ -733,7 +733,7 @@ fn test_redirections() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_is_potential_path() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Directories
|
||||
std::fs::create_dir_all("test/is_potential_path_test/alpha/").unwrap();
|
||||
std::fs::create_dir_all("test/is_potential_path_test/beta/").unwrap();
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
WString, wstr,
|
||||
};
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
use strum_macros::Display;
|
||||
|
||||
use super::file_tester::IsFile;
|
||||
|
||||
@@ -586,10 +587,8 @@ enum Mode {
|
||||
} else {
|
||||
// Not a backslash.
|
||||
match c {
|
||||
'~' => {
|
||||
if in_pos == 0 {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'~' if in_pos == 0 => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'$' => {
|
||||
assert!(in_pos < buff_len);
|
||||
@@ -597,10 +596,8 @@ enum Mode {
|
||||
// Subtract one to account for the upcoming loop increment.
|
||||
in_pos -= 1;
|
||||
}
|
||||
'?' => {
|
||||
if !feature_test(FeatureFlag::QuestionMarkNoGlob) {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'?' if !feature_test(FeatureFlag::QuestionMarkNoGlob) => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'*' | '(' | ')' => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
@@ -613,10 +610,8 @@ enum Mode {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
bracket_count -= 1;
|
||||
}
|
||||
',' => {
|
||||
if bracket_count > 0 {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
',' if bracket_count > 0 => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'\'' => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::quote);
|
||||
@@ -660,9 +655,9 @@ enum Mode {
|
||||
'"' => {
|
||||
mode = Mode::unquoted;
|
||||
}
|
||||
'\\' => {
|
||||
'\\'
|
||||
// Backslash
|
||||
if in_pos + 1 < buff_len {
|
||||
if in_pos + 1 < buff_len => {
|
||||
let escaped_char = buffstr.as_char_slice()[in_pos + 1];
|
||||
if matches!(escaped_char, '\\' | '"' | '\n' | '$') {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::escape); // backslash
|
||||
@@ -670,7 +665,6 @@ enum Mode {
|
||||
in_pos += 1; // skip over backslash
|
||||
}
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
in_pos += color_variable(&buffstr[in_pos..], &mut colors[in_pos..]);
|
||||
// Subtract one to account for the upcoming increment in the loop.
|
||||
@@ -1264,7 +1258,7 @@ fn get_fallback(role: HighlightRole) -> HighlightRole {
|
||||
}
|
||||
|
||||
/// Describes the role of a span of text.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Display)]
|
||||
#[repr(u8)]
|
||||
pub enum HighlightRole {
|
||||
#[default]
|
||||
@@ -1336,7 +1330,7 @@ fn get_overlong_path() -> String {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_highlighting() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
// Testing syntax highlighting
|
||||
parser.pushd("test/fish_highlight_test/");
|
||||
@@ -1821,7 +1815,7 @@ macro_rules! validate {
|
||||
#[serial]
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
fn test_trailing_spaces_after_command() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
let vars = parser.vars();
|
||||
|
||||
@@ -1875,7 +1869,7 @@ fn test_trailing_spaces_after_command() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_resolve_role() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
let vars = parser.vars();
|
||||
|
||||
|
||||
@@ -2288,7 +2288,7 @@ fn test_history_merge() {
|
||||
|
||||
#[test]
|
||||
fn test_history_path_detection() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Regression test for #7582.
|
||||
// Temporary directory for the history files.
|
||||
let hist_tmpdir = fish_tempfile::new_dir().unwrap();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(feature = "benchmark", feature(test))]
|
||||
#![cfg_attr(all(nightly, feature = "benchmark"), feature(test))]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
pub const BUILD_VERSION: &str = env!("FISH_BUILD_VERSION");
|
||||
|
||||
@@ -204,7 +204,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_unlocalized() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let abc_str = LocalizableString::from_external_source(WString::from("abc"));
|
||||
let s: &'static wstr = wgettext!(abc_str);
|
||||
assert_eq!(s, "abc");
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
|
||||
use crate::common::CancelChecker;
|
||||
use crate::env::EnvDyn;
|
||||
use crate::env::{EnvStack, Environment};
|
||||
@@ -57,10 +59,9 @@ pub fn vars(&self) -> &dyn Environment {
|
||||
|
||||
// Return an "empty" context which contains no variables, no parser, and never cancels.
|
||||
pub fn empty() -> OperationContext<'static> {
|
||||
use std::sync::OnceLock;
|
||||
static NULL_ENV: OnceLock<EnvStack> = OnceLock::new();
|
||||
let null_env = NULL_ENV.get_or_init(EnvStack::new);
|
||||
OperationContext::background(null_env, EXPANSION_LIMIT_DEFAULT)
|
||||
static NULL_ENV: SingleThreadedLazyCell<EnvStack, fn() -> EnvStack> =
|
||||
SingleThreadedLazyCell::new(EnvStack::new);
|
||||
OperationContext::background(&*NULL_ENV, EXPANSION_LIMIT_DEFAULT)
|
||||
}
|
||||
|
||||
// Return an operation context that contains only global variables, no parser, and never
|
||||
|
||||
@@ -1322,7 +1322,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_pager_navigation() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Generate 19 strings of width 10. There's 2 spaces between completions, and our term size is
|
||||
// 80; these can therefore fit into 6 columns (6 * 12 - 2 = 70) or 5 columns (58) but not 7
|
||||
// columns (7 * 12 - 2 = 82).
|
||||
@@ -1412,7 +1412,7 @@ macro_rules! validate {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_pager_layout() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// These tests are woefully incomplete
|
||||
|
||||
let rendered_lines = |pager: &mut Pager, width: u16| {
|
||||
|
||||
26
src/panic.rs
26
src/panic.rs
@@ -1,22 +1,28 @@
|
||||
use crate::{common::get_program_name, nix::isatty, threads::is_main_thread};
|
||||
use fish_common::read_blocked;
|
||||
use fish_thread::SingleThreadedOnceCell;
|
||||
use libc::STDIN_FILENO;
|
||||
use std::{
|
||||
panic::{UnwindSafe, set_hook, take_hook},
|
||||
sync::{
|
||||
OnceLock,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub static AT_EXIT: OnceLock<Box<dyn Fn() + Send + Sync>> = OnceLock::new();
|
||||
pub static AT_EXIT: SingleThreadedOnceCell<Box<dyn Fn() + Send + Sync>> =
|
||||
SingleThreadedOnceCell::new();
|
||||
|
||||
pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
||||
// The isatty() check will stop us from hanging in most fish tests, but not those
|
||||
// running in a simulated terminal emulator environment (such as the tmux or pexpect
|
||||
// tests). The FISH_FAST_FAIL environment variable is set in the test driver to
|
||||
// prevent the test suite from hanging on panic.
|
||||
let cleanup = || {
|
||||
if is_main_thread() {
|
||||
if let Some(at_exit) = AT_EXIT.get() {
|
||||
(at_exit)();
|
||||
}
|
||||
}
|
||||
};
|
||||
if isatty(STDIN_FILENO) && std::env::var_os("FISH_FAST_FAIL").is_none() {
|
||||
let standard_hook = take_hook();
|
||||
set_hook(Box::new(move |panic_info| {
|
||||
@@ -28,11 +34,7 @@ pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
||||
{
|
||||
return;
|
||||
}
|
||||
if is_main_thread() {
|
||||
if let Some(at_exit) = AT_EXIT.get() {
|
||||
(at_exit)();
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
eprintf!("%s crashed, please report a bug.", get_program_name());
|
||||
if !is_main_thread() {
|
||||
eprintf!("\n");
|
||||
@@ -53,8 +55,6 @@ pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
||||
}));
|
||||
}
|
||||
let exit_status = main();
|
||||
if let Some(at_exit) = AT_EXIT.get() {
|
||||
(at_exit)();
|
||||
}
|
||||
cleanup();
|
||||
std::process::exit(exit_status)
|
||||
}
|
||||
|
||||
@@ -968,17 +968,13 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
// ....cmd3
|
||||
// end
|
||||
// See #7252.
|
||||
Kind::JobContinuation(node) => {
|
||||
if self.has_newline(&node.newlines) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
Kind::JobContinuation(node) if self.has_newline(&node.newlines) => {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
|
||||
// Likewise for && and ||.
|
||||
Kind::JobConjunctionContinuation(node) => {
|
||||
if self.has_newline(&node.newlines) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
Kind::JobConjunctionContinuation(node) if self.has_newline(&node.newlines) => {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
|
||||
Kind::CaseItemList(_) => {
|
||||
@@ -1150,23 +1146,21 @@ pub fn detect_parse_errors_in_ast(
|
||||
let mut traversal = ast::Traversal::new(ast.top());
|
||||
while let Some(node) = traversal.next() {
|
||||
match node.kind() {
|
||||
Kind::JobContinuation(jc) => {
|
||||
Kind::JobContinuation(jc)
|
||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||
// See if our pipe has source but our statement does not.
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() {
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() => {
|
||||
has_unclosed_pipe = true;
|
||||
}
|
||||
}
|
||||
Kind::JobConjunction(job_conjunction) => {
|
||||
issue.error |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors);
|
||||
}
|
||||
Kind::JobConjunctionContinuation(jcc) => {
|
||||
Kind::JobConjunctionContinuation(jcc)
|
||||
// Somewhat clumsy way of checking for a job without source in a conjunction.
|
||||
// See if our conjunction operator (&& or ||) has source but our job does not.
|
||||
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() {
|
||||
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() => {
|
||||
has_unclosed_conjunction = true;
|
||||
}
|
||||
}
|
||||
Kind::Argument(arg) => {
|
||||
let arg_src = arg.source(buff_src);
|
||||
if let Err(e) = detect_errors_in_argument(arg, arg_src, &mut out_errors) {
|
||||
@@ -1174,7 +1168,7 @@ pub fn detect_parse_errors_in_ast(
|
||||
issue.incomplete |= e.incomplete;
|
||||
}
|
||||
}
|
||||
Kind::JobPipeline(job) => {
|
||||
Kind::JobPipeline(job)
|
||||
// Disallow background in the following cases:
|
||||
//
|
||||
// foo & ; and bar
|
||||
@@ -1182,11 +1176,10 @@ pub fn detect_parse_errors_in_ast(
|
||||
// if foo & ; end
|
||||
// while foo & ; end
|
||||
// If it's not a background job, nothing to do.
|
||||
if job.bg.is_some() {
|
||||
if job.bg.is_some() => {
|
||||
issue.error |=
|
||||
detect_errors_in_backgrounded_job(&traversal, job, &mut out_errors);
|
||||
}
|
||||
}
|
||||
Kind::DecoratedStatement(stmt) => {
|
||||
issue.error |= detect_errors_in_decorated_statement(
|
||||
buff_src,
|
||||
@@ -1922,7 +1915,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_error_messages() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Given a format string, returns a list of non-empty strings separated by format specifiers. The
|
||||
// format specifiers themselves are omitted.
|
||||
fn separate_by_format_specifiers(format: &wstr) -> Vec<&wstr> {
|
||||
@@ -2014,7 +2007,7 @@ macro_rules! validate {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_get_cmdsubst_extent() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let a = L!("echo (echo (echo hi");
|
||||
assert_eq!(get_cmdsubst_extent(a, 0), 0..a.len());
|
||||
assert_eq!(get_cmdsubst_extent(a, 1), 0..a.len());
|
||||
@@ -2030,7 +2023,7 @@ fn test_get_cmdsubst_extent() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_slice_length() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
assert_eq!(slice_length(L!("[2]")), Some(3));
|
||||
assert_eq!(slice_length(L!("[12]")), Some(4));
|
||||
assert_eq!(slice_length(L!("[\"foo\"]")), Some(7));
|
||||
@@ -2040,7 +2033,7 @@ fn test_slice_length() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_indents() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// A struct which is either text or a new indent.
|
||||
struct Segment {
|
||||
// The indent to set
|
||||
|
||||
@@ -1522,7 +1522,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_parser() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
macro_rules! detect_errors {
|
||||
($src:literal) => {
|
||||
detect_parse_errors(L!($src), None, true /* accept incomplete */)
|
||||
@@ -1825,7 +1825,7 @@ fn detect_argument_errors(src: &str) -> Result<(), ParseIssue> {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_new_parser_correctness() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
macro_rules! validate {
|
||||
($src:expr, $ok:expr) => {
|
||||
let ast = ast::parse(L!($src), ParseTreeFlags::default(), None);
|
||||
@@ -1855,7 +1855,7 @@ macro_rules! validate {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_new_parser_correctness_by_fuzzing() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let fuzzes = [
|
||||
L!("if"),
|
||||
L!("else"),
|
||||
@@ -1921,7 +1921,7 @@ fn string_for_permutation(
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_new_parser_ll2() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns
|
||||
// true if successful.
|
||||
fn test_1_parse_ll2(src: &wstr) -> Option<(WString, WString, StatementDecoration)> {
|
||||
@@ -2042,7 +2042,7 @@ macro_rules! check_function_help {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_new_parser_ad_hoc() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Very ad-hoc tests for issues encountered.
|
||||
|
||||
// Ensure that 'case' terminates a job list.
|
||||
@@ -2102,7 +2102,7 @@ fn test_new_parser_ad_hoc() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_new_parser_errors() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
macro_rules! validate {
|
||||
($src:expr, $expected_code:expr) => {
|
||||
let mut errors = vec![];
|
||||
@@ -2138,7 +2138,7 @@ macro_rules! validate {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_eval_recursion_detection() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
// Ensure that we don't crash on infinite self recursion and mutual recursion.
|
||||
let parser = TestParser::new();
|
||||
parser.eval(
|
||||
@@ -2158,7 +2158,7 @@ fn test_eval_recursion_detection() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_eval_illegal_exit_code() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
macro_rules! validate {
|
||||
($cmd:expr, $result:expr) => {
|
||||
@@ -2184,7 +2184,7 @@ macro_rules! validate {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_eval_empty_function_name() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
parser.eval(
|
||||
L!("function '' ; echo fail; exit 42 ; end ; ''"),
|
||||
@@ -2195,7 +2195,7 @@ fn test_eval_empty_function_name() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_expand_argument_list() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = TestParser::new();
|
||||
let comps: Vec<WString> = Parser::expand_argument_list(
|
||||
L!("alpha 'beta gamma' delta"),
|
||||
@@ -2237,7 +2237,7 @@ fn test_1_cancellation(parser: &Parser, src: &wstr) {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_cancellation() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let parser = Parser::new(EnvStack::new(), CancelBehavior::Clear);
|
||||
let _pop = fake_scoped_reader(&parser);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
QueryXtversion,
|
||||
},
|
||||
},
|
||||
termsize::{safe_termsize_invalidate_tty, termsize_last, termsize_update},
|
||||
termsize::{signal_safe_termsize_invalidate_tty, termsize_last, termsize_update},
|
||||
text_face::{TextFace, parse_text_face},
|
||||
threads::{assert_is_background_thread, assert_is_main_thread},
|
||||
tokenizer::{
|
||||
@@ -117,6 +117,7 @@
|
||||
};
|
||||
use fish_fallback::{fish_wcwidth, lowercase};
|
||||
use fish_feature_flags::FeatureFlag;
|
||||
use fish_thread::SingleThreadedOnceCell;
|
||||
use fish_util::{perror, write_to_fd};
|
||||
use fish_wcstringutil::{
|
||||
CaseSensitivity, IsPrefix, StringFuzzyMatch, count_preceding_backslashes, is_prefix,
|
||||
@@ -147,7 +148,7 @@
|
||||
os::fd::{AsRawFd as _, BorrowedFd, FromRawFd as _, OwnedFd, RawFd},
|
||||
pin::Pin,
|
||||
sync::{
|
||||
Arc, LazyLock, Mutex, MutexGuard, OnceLock,
|
||||
Arc, LazyLock, Mutex, MutexGuard,
|
||||
atomic::{AtomicI32, AtomicU8, AtomicU32, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
@@ -174,7 +175,8 @@ fn zeroed_termios() -> Termios {
|
||||
pub static SHELL_MODES: LazyLock<Mutex<Termios>> = LazyLock::new(|| Mutex::new(zeroed_termios()));
|
||||
|
||||
/// The valid terminal modes on startup.
|
||||
static TERMINAL_MODE_ON_STARTUP: OnceLock<libc::termios> = OnceLock::new();
|
||||
static TERMINAL_MODE_ON_STARTUP: SingleThreadedOnceCell<libc::termios> =
|
||||
SingleThreadedOnceCell::new();
|
||||
|
||||
/// Mode we use to execute programs.
|
||||
static TTY_MODES_FOR_EXTERNAL_CMDS: LazyLock<Mutex<Termios>> =
|
||||
@@ -284,7 +286,7 @@ pub fn terminal_init(vars: &dyn Environment, inputfd: RawFd) -> TerminalInitResu
|
||||
use ImplicitEvent::{CheckExit, Eof};
|
||||
use QueryResultEvent::*;
|
||||
match input_queue.readch() {
|
||||
Implicit(Eof) => safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Implicit(Eof) => signal_safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Implicit(CheckExit) => {}
|
||||
CharEvent::QueryResult(Response(QueryResponse::PrimaryDeviceAttribute)) => {
|
||||
break;
|
||||
@@ -989,6 +991,7 @@ fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> {
|
||||
|
||||
/// Initialize the reader.
|
||||
pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
assert_is_main_thread();
|
||||
let terminal_mode_on_startup = match tcgetattr(unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) })
|
||||
{
|
||||
Ok(modes) => {
|
||||
@@ -1000,10 +1003,12 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
Err(_) => zeroed_termios(),
|
||||
};
|
||||
|
||||
if !cfg!(test) {
|
||||
assert!(AT_EXIT.get().is_none());
|
||||
}
|
||||
AT_EXIT.get_or_init(|| Box::new(move || reader_deinit(will_restore_foreground_pgroup)));
|
||||
AT_EXIT
|
||||
.set(Box::new(move || {
|
||||
reader_deinit(will_restore_foreground_pgroup);
|
||||
}))
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
|
||||
// Set the mode used for program execution, initialized to the current mode.
|
||||
let mut external_modes = terminal_mode_on_startup;
|
||||
@@ -1319,7 +1324,7 @@ pub fn read_generation_count() -> u32 {
|
||||
|
||||
/// The readers interrupt signal handler. Cancels all currently running blocks.
|
||||
/// This is called from a signal handler!
|
||||
pub fn safe_reader_handle_sigint() {
|
||||
pub fn signal_safe_reader_handle_sigint() {
|
||||
INTERRUPTED.store(SIGINT, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -1341,7 +1346,7 @@ pub fn reader_test_and_clear_interrupted() -> i32 {
|
||||
}
|
||||
|
||||
/// Mark that we received an exit signal (SIGHUP or SIGTERM). Invoked from a signal handler.
|
||||
pub fn safe_reader_set_exit_signal(sig: i32) {
|
||||
pub fn signal_safe_reader_set_exit_signal(sig: i32) {
|
||||
// Beware, we may be in a signal handler.
|
||||
EXIT_SIGNAL.store(sig, Ordering::Relaxed);
|
||||
}
|
||||
@@ -2690,7 +2695,7 @@ fn run_input_command_scripts(&mut self, cmd: &wstr) {
|
||||
// ECHO mode, causing a race between new input and restoring the mode (#7770). So we leave the
|
||||
// tty alone, run the commands in shell mode, and then restore shell modes.
|
||||
set_shell_modes(STDIN_FILENO, "bind scripts");
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
/// Read normal characters, inserting them into the command line.
|
||||
@@ -2873,7 +2878,7 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
CharEvent::Implicit(implicit_event) => {
|
||||
use ImplicitEvent::*;
|
||||
match implicit_event {
|
||||
Eof => safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Eof => signal_safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
CheckExit => (),
|
||||
FocusIn => {
|
||||
event::fire_generic(self.parser, L!("fish_focus_in").to_owned(), vec![]);
|
||||
@@ -4869,7 +4874,7 @@ fn term_steal(copy_modes: bool) {
|
||||
term_copy_modes();
|
||||
}
|
||||
set_shell_modes(STDIN_FILENO, "shell");
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
// Ensure that fish owns the terminal, possibly waiting. If we cannot acquire the terminal, then
|
||||
@@ -5006,7 +5011,7 @@ fn reader_interactive_init() {
|
||||
set_shell_modes(STDIN_FILENO, "startup");
|
||||
}
|
||||
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
/// Return whether fish is currently unwinding the stack in preparation to exit.
|
||||
@@ -7026,9 +7031,11 @@ fn handle_completions(&mut self, token_range: Range<usize>, mut comp: Vec<Comple
|
||||
|
||||
comp.retain(|c| !c.replaces_token() || reader_can_replace(&tok, c.flags));
|
||||
|
||||
for c in &mut comp {
|
||||
if !will_replace_token && c.replaces_token() {
|
||||
c.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
|
||||
if !will_replace_token {
|
||||
for c in &mut comp {
|
||||
if c.replaces_token() {
|
||||
c.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2086,7 +2086,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_escape_code_length() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let mut lc = LayoutCache::new();
|
||||
assert_eq!(lc.escape_code_length(L!("")), 0);
|
||||
assert_eq!(lc.escape_code_length(L!("abcd")), 0);
|
||||
@@ -2121,7 +2121,7 @@ fn test_escape_code_length() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_layout_cache() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let mut seqs = LayoutCache::new();
|
||||
|
||||
// Verify escape code cache.
|
||||
@@ -2193,7 +2193,7 @@ fn test_layout_cache() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_prompt_truncation() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let mut cache = LayoutCache::new();
|
||||
let mut trunc = WString::new();
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::event::{enqueue_signal, is_signal_observed};
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{safe_reader_handle_sigint, safe_reader_set_exit_signal};
|
||||
use crate::termsize::safe_termsize_invalidate_tty;
|
||||
use crate::reader::{signal_safe_reader_handle_sigint, signal_safe_reader_set_exit_signal};
|
||||
use crate::termsize::signal_safe_termsize_invalidate_tty;
|
||||
use crate::topic_monitor::{Generation, GenerationsList, Topic, topic_monitor_principal};
|
||||
use crate::tty_handoff::safe_mark_tty_invalid;
|
||||
use crate::tty_handoff::signal_safe_mark_tty_invalid;
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use errno::{errno, set_errno};
|
||||
use fish_common::exit_without_destructors;
|
||||
@@ -86,14 +86,14 @@ extern "C" fn fish_signal_handler(
|
||||
match sig {
|
||||
libc::SIGWINCH => {
|
||||
// Respond to a winch signal by telling the termsize container.
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
libc::SIGHUP | libc::SIGTERM => {
|
||||
// Exit unless the signal was trapped.
|
||||
if !observed {
|
||||
safe_reader_set_exit_signal(sig);
|
||||
signal_safe_reader_set_exit_signal(sig);
|
||||
if sig == libc::SIGHUP {
|
||||
safe_mark_tty_invalid();
|
||||
signal_safe_mark_tty_invalid();
|
||||
}
|
||||
}
|
||||
topic_monitor_principal().post(Topic::SigHupIntTerm);
|
||||
@@ -103,7 +103,7 @@ extern "C" fn fish_signal_handler(
|
||||
if !observed {
|
||||
CANCELLATION_SIGNAL.store(libc::SIGINT, Ordering::Relaxed);
|
||||
}
|
||||
safe_reader_handle_sigint();
|
||||
signal_safe_reader_handle_sigint();
|
||||
topic_monitor_principal().post(Topic::SigHupIntTerm);
|
||||
}
|
||||
libc::SIGCHLD => {
|
||||
@@ -273,7 +273,7 @@ pub fn signal_handle(sig: Signal) {
|
||||
sigaction(sig, &act, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
pub static signals_to_default: LazyLock<libc::sigset_t> = LazyLock::new(|| {
|
||||
pub static SIGNALS_TO_DEFAULT: LazyLock<libc::sigset_t> = LazyLock::new(|| {
|
||||
let mut set = MaybeUninit::uninit();
|
||||
unsafe { libc::sigemptyset(set.as_mut_ptr()) };
|
||||
for data in SIGNAL_TABLE.iter() {
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
// Generic output functions.
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
screen::{is_dumb, only_grayscale},
|
||||
text_face::{ResettableStyle, TextFace, TextStyling, UnderlineStyle},
|
||||
threads::MainThread,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use fish_color::{Color, Color24};
|
||||
use fish_common::{EscapeStringStyle, escape_string, write_loop};
|
||||
use fish_feature_flags::FeatureFlag;
|
||||
use fish_thread::SingleThreaded;
|
||||
use fish_widestring::{wcs2bytes, wcs2bytes_appending};
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
ops::{Deref, DerefMut},
|
||||
os::{fd::RawFd, unix::ffi::OsStrExt as _},
|
||||
sync::{
|
||||
OnceLock,
|
||||
atomic::{AtomicU8, Ordering},
|
||||
},
|
||||
sync::atomic::{AtomicU8, Ordering},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
@@ -189,8 +187,8 @@ fn osc_133_prompt_start(out: &mut Outputter) -> bool {
|
||||
if !fish_feature_flags::feature_test(FeatureFlag::MarkPrompt) {
|
||||
return false;
|
||||
}
|
||||
static TEST_BALLOON: OnceLock<()> = OnceLock::new();
|
||||
if TEST_BALLOON.set(()).is_ok() {
|
||||
static TEST_BALLOON: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
if !TEST_BALLOON.swap(true) {
|
||||
write_to_output!(out, "\x1b]133;A;click_events=1\x1b\\");
|
||||
} else {
|
||||
write_to_output!(out, "\x1b]133;A;click_events=1\x07");
|
||||
@@ -577,8 +575,8 @@ pub fn write_bytes(&mut self, buf: &[u8]) {
|
||||
/// Access the outputter for stdout.
|
||||
/// This should only be used from the main thread.
|
||||
pub fn stdoutput() -> &'static RefCell<Outputter> {
|
||||
static STDOUTPUT: MainThread<RefCell<Outputter>> =
|
||||
MainThread::new(RefCell::new(Outputter::new_from_fd(libc::STDOUT_FILENO)));
|
||||
static STDOUTPUT: SingleThreaded<RefCell<Outputter>> =
|
||||
SingleThreaded::new(RefCell::new(Outputter::new_from_fd(libc::STDOUT_FILENO)));
|
||||
STDOUTPUT.get()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ pub fn termsize_update(parser: &Parser) -> Termsize {
|
||||
}
|
||||
|
||||
/// May be called form a signal handler (WINCH).
|
||||
pub fn safe_termsize_invalidate_tty() {
|
||||
pub fn signal_safe_termsize_invalidate_tty() {
|
||||
TTY_TERMSIZE_GEN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_termsize() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let env_global = EnvSetMode::new(EnvMode::GLOBAL, false);
|
||||
let parser = TestParser::new();
|
||||
let vars = parser.vars();
|
||||
@@ -298,7 +298,7 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
assert_eq!(ts.last(), Termsize::defaults());
|
||||
|
||||
// Ok let's tell it. But it still doesn't update right away.
|
||||
let handle_winch = safe_termsize_invalidate_tty;
|
||||
let handle_winch = signal_safe_termsize_invalidate_tty;
|
||||
handle_winch();
|
||||
assert_eq!(ts.last(), Termsize::defaults());
|
||||
|
||||
|
||||
@@ -4,23 +4,20 @@
|
||||
use crate::locale::set_libc_locales;
|
||||
use crate::parser::{CancelBehavior, Parser};
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{reader_deinit, reader_init};
|
||||
use crate::signal::signal_reset_handlers;
|
||||
use crate::topic_monitor::topic_monitor_init;
|
||||
use crate::wutil::wgetcwd;
|
||||
use crate::{env::EnvStack, proc::proc_init};
|
||||
use fish_common::{ScopeGuard, ScopeGuarding};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::env::set_current_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub use serial_test::serial;
|
||||
|
||||
pub fn test_init() -> impl ScopeGuarding<Target = ()> {
|
||||
static DONE: OnceLock<()> = OnceLock::new();
|
||||
DONE.get_or_init(|| {
|
||||
pub fn test_init() {
|
||||
static DONE: std::sync::Once = std::sync::Once::new();
|
||||
DONE.call_once(|| {
|
||||
// If we are building with `cargo build` and have build w/ `cmake`, this might not
|
||||
// yet exist.
|
||||
let mut test_dir = PathBuf::from(BUILD_DIR);
|
||||
@@ -42,10 +39,6 @@ pub fn test_init() -> impl ScopeGuarding<Target = ()> {
|
||||
// Set PWD from getcwd - fixes #5599
|
||||
EnvStack::globals().set_pwd_from_getcwd();
|
||||
});
|
||||
reader_init(false);
|
||||
ScopeGuard::new((), |()| {
|
||||
reader_deinit(false);
|
||||
})
|
||||
}
|
||||
|
||||
/// An environment built around an std::map.
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
//! Support for thread pools and thread management.
|
||||
use crate::flog::{FloggableDebug, flog};
|
||||
use fish_thread::{ThreadId, thread_id};
|
||||
use nix::sys::signal::{SigSet, SigmaskHow, Signal};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::time::Duration;
|
||||
|
||||
impl FloggableDebug for ThreadId {}
|
||||
impl FloggableDebug for std::thread::ThreadId {}
|
||||
|
||||
/// The thread id of the main thread, as set by [`init()`] at startup.
|
||||
static MAIN_THREAD_ID: OnceLock<usize> = OnceLock::new();
|
||||
static MAIN_THREAD_ID: OnceLock<ThreadId> = OnceLock::new();
|
||||
/// Used to bypass thread assertions when testing.
|
||||
const THREAD_ASSERTS_CFG_FOR_TESTING: bool = cfg!(test);
|
||||
/// This allows us to notice when we've forked.
|
||||
@@ -26,6 +27,7 @@ impl FloggableDebug for std::thread::ThreadId {}
|
||||
pub fn init() {
|
||||
MAIN_THREAD_ID
|
||||
.set(thread_id())
|
||||
.map_err(|_| ())
|
||||
.expect("threads::init() must only be called once (at startup)!");
|
||||
|
||||
extern "C" fn child_post_fork() {
|
||||
@@ -38,7 +40,7 @@ extern "C" fn child_post_fork() {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn main_thread_id() -> usize {
|
||||
fn main_thread_id() -> ThreadId {
|
||||
#[cold]
|
||||
fn init_not_called() -> ! {
|
||||
panic!("threads::init() was not called at startup!");
|
||||
@@ -50,31 +52,6 @@ fn init_not_called() -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the calling thread's fish-specific thread id.
|
||||
///
|
||||
/// This thread id is internal to the `threads` module for low-level purposes and should not be
|
||||
/// leaked to other modules; general purpose code that needs a thread id should use rust's native
|
||||
/// thread id functionality.
|
||||
///
|
||||
/// We use our own implementation because Rust's own `Thread::id()` allocates via `Arc`, is fairly
|
||||
/// slow, and uses a `Mutex` on 32-bit platforms (or anywhere without an atomic 64-bit CAS).
|
||||
#[inline(always)]
|
||||
fn thread_id() -> usize {
|
||||
static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
// It would be faster and much nicer to use #[thread_local] here, but that's nightly only.
|
||||
// This is still faster than going through Thread::thread_id(); it's something like 15ns
|
||||
// for each `Thread::thread_id()` call vs 1-2 ns with `#[thread_local]` and 2-4ns with
|
||||
// `thread_local!`.
|
||||
thread_local! {
|
||||
static THREAD_ID: usize = THREAD_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
let id = THREAD_ID.with(|id| *id);
|
||||
// This assertion is only here to reduce hair loss in case someone runs into a known linker bug;
|
||||
// as it's not here to catch logic errors in our own code, it can be elided in release mode.
|
||||
debug_assert_ne!(id, 0, "TLS storage not initialized!");
|
||||
id
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_main_thread() -> bool {
|
||||
thread_id() == main_thread_id()
|
||||
@@ -290,33 +267,6 @@ fn spawn_thread(self: &Arc<Self>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Sync` and `Send` wrapper for non-`Sync`/`Send` types.
|
||||
/// Only allows access from the main thread.
|
||||
pub struct MainThread<T> {
|
||||
data: T,
|
||||
// Make type !Send and !Sync by default
|
||||
_marker: PhantomData<*const ()>,
|
||||
}
|
||||
|
||||
// Manually implement Send and Sync for MainThread<T> to ensure it can be shared across threads
|
||||
// as long as T is 'static.
|
||||
unsafe impl<T: 'static> Send for MainThread<T> {}
|
||||
unsafe impl<T: 'static> Sync for MainThread<T> {}
|
||||
|
||||
impl<T> MainThread<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
data: value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
assert_is_main_thread();
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadPool {
|
||||
/// The worker loop entry point for this thread.
|
||||
/// This is run in a background thread.
|
||||
|
||||
@@ -618,7 +618,7 @@ mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_topic_monitor() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let monitor = TopicMonitor::default();
|
||||
let gens = GenerationsList::new();
|
||||
let t = Topic::SigChld;
|
||||
@@ -644,7 +644,7 @@ fn test_topic_monitor() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_topic_monitor_torture() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let monitor = Arc::new(TopicMonitor::default());
|
||||
const THREAD_COUNT: usize = 64;
|
||||
let t1 = Topic::SigChld;
|
||||
|
||||
@@ -18,25 +18,24 @@
|
||||
use crate::threads::assert_is_main_thread;
|
||||
use crate::wutil::{perror_nix, wcstoi};
|
||||
use fish_common::write_loop;
|
||||
use fish_thread::SingleThreadedOnceCell;
|
||||
use fish_util::perror;
|
||||
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
|
||||
use nix::sys::termios::tcgetattr;
|
||||
use nix::unistd::getpgrp;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::sync::{
|
||||
OnceLock,
|
||||
atomic::{AtomicPtr, Ordering},
|
||||
};
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
/// Whether kitty keyboard protocol support is present in the TTY.
|
||||
static KITTY_KEYBOARD_SUPPORTED: OnceLock<bool> = OnceLock::new();
|
||||
static KITTY_KEYBOARD_SUPPORTED: SingleThreadedOnceCell<bool> = SingleThreadedOnceCell::new();
|
||||
|
||||
/// Set that the TTY supports the kitty keyboard protocol.
|
||||
pub fn maybe_set_kitty_keyboard_capability() {
|
||||
KITTY_KEYBOARD_SUPPORTED.get_or_init(|| true);
|
||||
}
|
||||
|
||||
pub(crate) static SCROLL_CONTENT_UP_SUPPORTED: OnceLock<bool> = OnceLock::new();
|
||||
pub(crate) static SCROLL_CONTENT_UP_SUPPORTED: SingleThreadedOnceCell<bool> =
|
||||
SingleThreadedOnceCell::new();
|
||||
pub(crate) const SCROLL_CONTENT_UP_TERMINFO_CODE: &str = "indn";
|
||||
|
||||
// Get the support capability for kitty keyboard protocol.
|
||||
@@ -51,10 +50,11 @@ pub fn maybe_set_scroll_content_up_capability() {
|
||||
});
|
||||
}
|
||||
|
||||
pub static TERMINAL_OS_NAME: OnceLock<Option<WString>> = OnceLock::new();
|
||||
pub static TERMINAL_OS_NAME: SingleThreadedOnceCell<Option<WString>> =
|
||||
SingleThreadedOnceCell::new();
|
||||
pub(crate) const XTGETTCAP_QUERY_OS_NAME: &str = "query-os-name";
|
||||
|
||||
pub static XTVERSION: OnceLock<WString> = OnceLock::new();
|
||||
pub static XTVERSION: SingleThreadedOnceCell<WString> = SingleThreadedOnceCell::new();
|
||||
|
||||
pub fn xtversion() -> Option<&'static wstr> {
|
||||
XTVERSION.get().as_ref().map(|s| s.as_utfstr())
|
||||
@@ -335,7 +335,7 @@ pub fn deactivate_tty_protocols() {
|
||||
|
||||
// Called from a signal handler to mark the tty as invalid (e.g. SIGHUP).
|
||||
// This suppresses any further attempts to write protocols to the tty,
|
||||
pub fn safe_mark_tty_invalid() {
|
||||
pub fn signal_safe_mark_tty_invalid() {
|
||||
TTY_INVALID.store(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::{os::fd::RawFd, sync::OnceLock};
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
use fish_thread::SingleThreadedLazyCell;
|
||||
|
||||
#[cfg(apple)]
|
||||
mod notifyd;
|
||||
@@ -67,9 +69,9 @@ pub fn create_notifier() -> Box<dyn UniversalNotifier> {
|
||||
Box::new(NullNotifier)
|
||||
}
|
||||
|
||||
// Default instance. Other instances are possible for testing.
|
||||
static DEFAULT_NOTIFIER: OnceLock<Box<dyn UniversalNotifier>> = OnceLock::new();
|
||||
|
||||
pub fn default_notifier() -> &'static dyn UniversalNotifier {
|
||||
DEFAULT_NOTIFIER.get_or_init(create_notifier).as_ref()
|
||||
// Default instance. Other instances are possible for testing.
|
||||
static DEFAULT_NOTIFIER: SingleThreadedLazyCell<Box<dyn UniversalNotifier>> =
|
||||
SingleThreadedLazyCell::new(create_notifier);
|
||||
&**DEFAULT_NOTIFIER
|
||||
}
|
||||
|
||||
@@ -149,16 +149,11 @@ fn wildcard_complete_internal(
|
||||
|
||||
// Note: out_completion may be empty if the completion really is empty, e.g. tab-completing
|
||||
// 'foo' when a file 'foo' exists.
|
||||
let local_flags = if full_replacement {
|
||||
flags | CompleteFlags::REPLACES_TOKEN
|
||||
} else {
|
||||
flags
|
||||
};
|
||||
if !out.add(Completion::new(
|
||||
out_completion.to_owned(),
|
||||
out_desc,
|
||||
m,
|
||||
local_flags,
|
||||
flags,
|
||||
)) {
|
||||
return WildcardResult::Overflow;
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ fn test_wdirname_wbasename() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_wwrite_to_fd() {
|
||||
let _cleanup = test_init();
|
||||
test_init();
|
||||
let temp_file = fish_tempfile::new_file().unwrap();
|
||||
let mut rng = rand::rng();
|
||||
let sizes = [1, 2, 3, 5, 13, 23, 64, 128, 255, 4096, 4096 * 2];
|
||||
|
||||
@@ -346,7 +346,9 @@ complete -C'cd .'
|
||||
# Note that there is no kern.osproductversion under older OS X releases!
|
||||
#
|
||||
# NetBSD 10 does not support it.
|
||||
if test (uname) = NetBSD || { test (uname) = Darwin && test (sysctl kern.osproductversion 2>/dev/null | string match -r \\d+; or echo 10) -lt 12 }
|
||||
# Cygwin/MSYS does not support it when using ACL. And without ACL, a directory
|
||||
# cannot be made unreadable, making the test pointless. So either way, skip it
|
||||
if test (uname) = NetBSD || __fish_is_cygwin || { test (uname) = Darwin && test (sysctl kern.osproductversion 2>/dev/null | string match -r \\d+; or echo 10) -lt 12 }
|
||||
# Not supported. Satisfy the CHECKs below.
|
||||
echo fake/a
|
||||
echo fake/a/b
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
mkdir dir
|
||||
echo "#!/bin/sh" >dir/foo.exe
|
||||
echo "#!/bin/sh" >dir/foo.bar
|
||||
set PATH (pwd)/dir $PATH
|
||||
if ! cygwin_noacl ./
|
||||
chmod u+x dir/foo*
|
||||
end
|
||||
set -p PATH (pwd)/dir
|
||||
|
||||
# === Check that `complete` prefers to non-exe name, unless the user started
|
||||
# to type the extension
|
||||
@@ -36,6 +39,9 @@ complete -C"./dir/foo."
|
||||
|
||||
# === Check that if we have a non-exe and an exe file, they both show
|
||||
echo "#!/bin/sh" >dir/foo.bar.exe
|
||||
if ! cygwin_noacl ./
|
||||
chmod u+x dir/foo.bar.exe
|
||||
end
|
||||
complete -C"./dir/foo.ba"
|
||||
# CHECK: ./dir/foo.bar{{\t}}command
|
||||
# CHECK: ./dir/foo.bar.exe{{\t}}command
|
||||
|
||||
@@ -727,3 +727,9 @@ complete -c foo -a "foo\\"
|
||||
|
||||
complete -C
|
||||
# CHECKERR: complete: Can not get commandline in non-interactive mode
|
||||
|
||||
if string match -rq -- '^[a-z]+$' $USER
|
||||
set -l first_letter_wrong_case (string sub -l 1 -- $USER | string upper)
|
||||
string match -rq -- "$USER\t.*" (complete -C "echo ~$first_letter_wrong_case")
|
||||
or echo "`complete -C'echo ~$first_letter_wrong_case'` did not yield $USER"
|
||||
end
|
||||
|
||||
@@ -14,6 +14,9 @@ status job-control full
|
||||
sleep 1 &
|
||||
set -l pid (jobs -lp)
|
||||
kill -SIGSTOP $pid
|
||||
while jobs -l | grep -q running
|
||||
sleep .01
|
||||
end
|
||||
disown
|
||||
# CHECKERR: disown: job 1 ('sleep 1 &') was stopped and has been signalled to continue.
|
||||
echo $status
|
||||
|
||||
@@ -231,7 +231,14 @@ path filter -w stuff/*
|
||||
echo "=== test --perm exec"
|
||||
# CHECK: === test --perm exec
|
||||
|
||||
path filter --perm exec stuff/*
|
||||
begin
|
||||
path filter --perm exec stuff/*
|
||||
# Cygwin with ACL doesn't return non-readable files, so add them manually to pass the test
|
||||
if __fish_is_cygwin && ! set -q noacl
|
||||
echo stuff/exec
|
||||
echo stuff/writeexec
|
||||
end
|
||||
end | sort
|
||||
# CHECK: stuff/all
|
||||
# CHECK: stuff/exec
|
||||
# CHECK: stuff/readexec
|
||||
@@ -240,7 +247,13 @@ path filter --perm exec stuff/*
|
||||
echo "=== test -x"
|
||||
# CHECK: === test -x
|
||||
|
||||
path filter -x stuff/*
|
||||
begin
|
||||
path filter -x exec stuff/*
|
||||
if __fish_is_cygwin && ! set -q noacl
|
||||
echo stuff/exec
|
||||
echo stuff/writeexec
|
||||
end
|
||||
end | sort
|
||||
# CHECK: stuff/all
|
||||
# CHECK: stuff/exec
|
||||
# CHECK: stuff/readexec
|
||||
@@ -271,7 +284,12 @@ end
|
||||
echo "=== test --perm write,exec"
|
||||
# CHECK: === test --perm write,exec
|
||||
|
||||
path filter --perm write,exec stuff/*
|
||||
begin
|
||||
path filter --perm write,exec stuff/*
|
||||
if __fish_is_cygwin && ! set -q noacl
|
||||
echo stuff/writeexec
|
||||
end
|
||||
end | sort
|
||||
# CHECK: stuff/all
|
||||
# CHECK: stuff/writeexec
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#RUN: fish_indent=%fish_indent %fish %s
|
||||
#REQUIRES: command -v sphinx-build
|
||||
# Cygwin has some old version of python/sphinx/... triggering deprecation errors
|
||||
# (and MSYS doesn't have sphinx-build at all)
|
||||
#REQUIRES: %fish -c "not __fish_is_cygwin"
|
||||
|
||||
set -l workspace_root (status dirname)/../..
|
||||
set -l build_script $workspace_root/tests/test_functions/sphinx-shared.sh
|
||||
|
||||
30
tests/checks/tmux-complete4.fish
Normal file
30
tests/checks/tmux-complete4.fish
Normal file
@@ -0,0 +1,30 @@
|
||||
#RUN: %fish %s
|
||||
#REQUIRES: command -v tmux
|
||||
#REQUIRES: uname -r | grep -qv Microsoft
|
||||
|
||||
isolated-tmux-start -C '
|
||||
complete : -s c -l clip
|
||||
complete : -s q -l qrcode
|
||||
set -g fish_autosuggestion_enabled 0
|
||||
'
|
||||
touch somefile1
|
||||
touch somefile2
|
||||
|
||||
isolated-tmux send-keys C-l ': -c'
|
||||
|
||||
function tab
|
||||
isolated-tmux send-keys Tab
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p | awk '/./ { print "[" $0 "]" }'
|
||||
end
|
||||
|
||||
tab
|
||||
# CHECK: [prompt 0> : -cq]
|
||||
|
||||
tab
|
||||
# CHECK: [prompt 0> : -cq somefile]
|
||||
# CHECK: [somefile1 somefile2]
|
||||
|
||||
tab
|
||||
# CHECK: [prompt 0> : -cq somefile1]
|
||||
# CHECK: [somefile1 somefile2]
|
||||
39
tests/checks/xdg-data-dirs-default.fish
Normal file
39
tests/checks/xdg-data-dirs-default.fish
Normal file
@@ -0,0 +1,39 @@
|
||||
#RUN: fish=%fish %fish %s
|
||||
|
||||
set -e FISH_UNIT_TESTS_RUNNING
|
||||
function expect-datadirs
|
||||
$fish -c '
|
||||
for datadir in $argv
|
||||
contains -- $datadir/fish/vendor_functions.d $fish_function_path
|
||||
and echo function_path has $datadir
|
||||
contains -- $datadir/fish/vendor_completions.d $fish_complete_path
|
||||
and echo complete_path has $datadir
|
||||
contains -- $datadir/fish/vendor_conf.d $__fish_vendor_confdirs
|
||||
and echo vendor_conf has $datadir
|
||||
end
|
||||
' $argv
|
||||
end
|
||||
|
||||
expect-datadirs /usr/local/share /usr/share
|
||||
# CHECK: function_path has /usr/local/share
|
||||
# CHECK: complete_path has /usr/local/share
|
||||
# CHECK: vendor_conf has /usr/local/share
|
||||
# CHECK: function_path has /usr/share
|
||||
# CHECK: complete_path has /usr/share
|
||||
# CHECK: vendor_conf has /usr/share
|
||||
|
||||
XDG_DATA_DIRS= expect-datadirs /usr/local/share /usr/share
|
||||
# CHECK: function_path has /usr/local/share
|
||||
# CHECK: complete_path has /usr/local/share
|
||||
# CHECK: vendor_conf has /usr/local/share
|
||||
# CHECK: function_path has /usr/share
|
||||
# CHECK: complete_path has /usr/share
|
||||
# CHECK: vendor_conf has /usr/share
|
||||
|
||||
XDG_DATA_DIRS=/custom/path1:/custom/path2 expect-datadirs /custom/path1 /custom/path2
|
||||
# CHECK: function_path has /custom/path1
|
||||
# CHECK: complete_path has /custom/path1
|
||||
# CHECK: vendor_conf has /custom/path1
|
||||
# CHECK: function_path has /custom/path2
|
||||
# CHECK: complete_path has /custom/path2
|
||||
# CHECK: vendor_conf has /custom/path2
|
||||
@@ -2,10 +2,19 @@ function cygwin_nosymlinks --description \
|
||||
"Return 0 if Cygwin fakes symlinks, return 1 otherwise"
|
||||
|
||||
switch (__fish_uname)
|
||||
case "MSYS*"
|
||||
not string match -q "*winsymlinks*" -- "$MSYS"
|
||||
case "CYGWIN*"
|
||||
not string match -q "*winsymlinks*" -- "$CYGWIN"
|
||||
# Cygwin has various ways of creating symlinks but they should
|
||||
# all be recognized as such (with a few exceptions, which we do
|
||||
# not support in testing, see
|
||||
# https://cygwin.com/cygwin-ug-net/using.html#pathnames-symlinks)
|
||||
return 1
|
||||
case "MSYS*"
|
||||
# In addition to the standard Cygwin symlinks, MSYS2 also has
|
||||
# `deepcopy` mode, which is the default, and does not create
|
||||
# recognizable symlinks
|
||||
# (https://www.msys2.org/docs/symlinks/)
|
||||
not string match -q "*winsymlinks*" -- "$MSYS"
|
||||
or string match -q "*winsymlinks:deepcopy*" -- "$MSYS"
|
||||
case "*"
|
||||
return 1
|
||||
end
|
||||
|
||||
@@ -6,9 +6,9 @@ sources:
|
||||
spec:
|
||||
shell: bash
|
||||
command: |
|
||||
set -eo pipefail
|
||||
set -e
|
||||
# Check that we have latest stable.
|
||||
if rustup check | grep ^stable- | grep 'Update available'; then
|
||||
if rustup check | grep '^stable-.*[uU]pdate available'; then
|
||||
echo >&2 "Rust toolchain 'stable' is stale, please update it"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,6 @@ set -e
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
|
||||
Reference in New Issue
Block a user