mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-08 08:01:15 -03:00
Compare commits
79 Commits
4.6.0
...
8dbbe71bc6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dbbe71bc6 | ||
|
|
d9d9eced98 | ||
|
|
64a829f0df | ||
|
|
440e7fcbc1 | ||
|
|
52495c8124 | ||
|
|
467b03d715 | ||
|
|
b5c40478f6 | ||
|
|
1286745e78 | ||
|
|
b99ae291d6 | ||
|
|
8ae71c80f4 | ||
|
|
cf6170200c | ||
|
|
c13038b968 | ||
|
|
816077281d | ||
|
|
78ea24a262 | ||
|
|
6a5b9bcde1 | ||
|
|
b7b786aabf | ||
|
|
dc63b7bb20 | ||
|
|
9c819c020e | ||
|
|
dc9b1141c8 | ||
|
|
3d364478ee | ||
|
|
faf331fdad | ||
|
|
47b6c0aec2 | ||
|
|
eb478bfc3e | ||
|
|
63cf79f5f6 | ||
|
|
ff284d642e | ||
|
|
cc64da62a9 | ||
|
|
a974fe990f | ||
|
|
39239724ec | ||
|
|
524a7bac6e | ||
|
|
d649c2aab4 | ||
|
|
30e6aa85e2 | ||
|
|
abd7442521 | ||
|
|
f5c48038b5 | ||
|
|
895a6e7034 | ||
|
|
2193e88423 | ||
|
|
86c3778c2a | ||
|
|
d2653b7cac | ||
|
|
cf16949ce7 | ||
|
|
85311546de | ||
|
|
c44aa32a15 | ||
|
|
65bc9b9e3e | ||
|
|
8125f78a84 | ||
|
|
f0f48b4859 | ||
|
|
a009f87630 | ||
|
|
edb66d4d4e | ||
|
|
f3f675b4cc | ||
|
|
434610494f | ||
|
|
3cce1f3f4c | ||
|
|
a5bde7954e | ||
|
|
99d63c21f1 | ||
|
|
c3e3658157 | ||
|
|
8d92016e72 | ||
|
|
f6a72b4e19 | ||
|
|
2f9c2df10d | ||
|
|
0367aaea7d | ||
|
|
e25b4b6f05 | ||
|
|
90cbfd288e | ||
|
|
68453843d4 | ||
|
|
fb57f95391 | ||
|
|
1ed276292b | ||
|
|
09e46b00cc | ||
|
|
695bc293a9 | ||
|
|
344ff7be88 | ||
|
|
014e3b3aff | ||
|
|
68c7baff90 | ||
|
|
6eaad2cd80 | ||
|
|
b321e38f5a | ||
|
|
a32dd63163 | ||
|
|
ef90afa5b9 | ||
|
|
7bd37dfe55 | ||
|
|
14ce56d2a5 | ||
|
|
01ee6f968d | ||
|
|
7f6dcde5e0 | ||
|
|
34fc573668 | ||
|
|
93cbf2a0e8 | ||
|
|
8194c6eb79 | ||
|
|
3194572156 | ||
|
|
e635816b7f | ||
|
|
2b3ecf22da |
32
.github/workflows/development-builds.yml
vendored
Normal file
32
.github/workflows/development-builds.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Linux development builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- buildscript
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: linux-development
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install deps
|
||||
run: sudo apt install debhelper devscripts dpkg-dev
|
||||
- name: Create tarball and source packages
|
||||
run: |
|
||||
version=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
|
||||
mkdir /tmp/gpg
|
||||
echo "$SIGNING_GPG_KEY" > /tmp/gpg/signing-gpg-key
|
||||
mkdir /tmp/fish-built
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built DEB_SIGN_KEYFILE=/tmp/gpg/signing-gpg-key ./build_tools/make_linux_packages.sh $version
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: linux-source-packages
|
||||
path: |
|
||||
/tmp/fish-built
|
||||
! /tmp/fish-built/fish-*/* # don't include the unpacked source directory
|
||||
@@ -1,3 +1,30 @@
|
||||
fish ?.?.? (released ???)
|
||||
=========================
|
||||
|
||||
Notable improvements and fixes
|
||||
------------------------------
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
|
||||
Improved terminal support
|
||||
-------------------------
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
|
||||
- ``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).
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden with ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
|
||||
fish 4.6.0 (released March 28, 2026)
|
||||
====================================
|
||||
|
||||
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -272,6 +272,7 @@ dependencies = [
|
||||
"fish-color",
|
||||
"fish-common",
|
||||
"fish-fallback",
|
||||
"fish-feature-flags",
|
||||
"fish-gettext",
|
||||
"fish-gettext-extraction",
|
||||
"fish-gettext-mo-file-parser",
|
||||
@@ -329,6 +330,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fish-build-helper",
|
||||
"fish-feature-flags",
|
||||
"fish-widestring",
|
||||
"libc",
|
||||
"nix",
|
||||
@@ -348,6 +350,13 @@ dependencies = [
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-feature-flags"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-gettext"
|
||||
version = "0.0.0"
|
||||
@@ -437,6 +446,7 @@ version = "0.0.0"
|
||||
name = "fish-widestring"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
@@ -23,6 +23,7 @@ fish-build-man-pages = { path = "crates/build-man-pages" }
|
||||
fish-color = { path = "crates/color" }
|
||||
fish-common = { path = "crates/common" }
|
||||
fish-fallback = { path = "crates/fallback" }
|
||||
fish-feature-flags = { path = "crates/feature-flags" }
|
||||
fish-gettext = { path = "crates/gettext" }
|
||||
fish-gettext-extraction = { path = "crates/gettext-extraction" }
|
||||
fish-gettext-maps = { path = "crates/gettext-maps" }
|
||||
@@ -108,6 +109,7 @@ fish-build-man-pages = { workspace = true, optional = true }
|
||||
fish-color.workspace = true
|
||||
fish-common.workspace = true
|
||||
fish-fallback.workspace = true
|
||||
fish-feature-flags.workspace = true
|
||||
fish-gettext = { workspace = true, optional = true }
|
||||
fish-gettext-extraction = { workspace = true, optional = true }
|
||||
fish-printf.workspace = true
|
||||
|
||||
@@ -8,11 +8,29 @@ if [ "$FISH_CHECK_LINT" = false ]; then
|
||||
lint=false
|
||||
fi
|
||||
|
||||
case "$(uname)" in
|
||||
MSYS*)
|
||||
is_cygwin=true
|
||||
cygwin_var=MSYS
|
||||
;;
|
||||
CYGWIN*)
|
||||
is_cygwin=true
|
||||
cygwin_var=CYGWIN
|
||||
;;
|
||||
*)
|
||||
is_cygwin=false
|
||||
;;
|
||||
esac
|
||||
|
||||
check_dependency_versions=false
|
||||
if [ "${FISH_CHECK_DEPENDENCY_VERSIONS:-false}" != false ]; then
|
||||
check_dependency_versions=true
|
||||
fi
|
||||
|
||||
green='\e[0;32m'
|
||||
yellow='\e[1;33m'
|
||||
reset='\e[m'
|
||||
|
||||
if $check_dependency_versions; then
|
||||
command -v curl
|
||||
command -v jq
|
||||
@@ -83,12 +101,45 @@ if $lint; then
|
||||
cargo clippy --workspace --all-targets $features
|
||||
done
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
|
||||
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
|
||||
# are dynamically linked against Rust's `std-xxx.dll` instead of being
|
||||
# statically link as they usually are.
|
||||
# On Cygwin, `PATH`is not properly updated to point to the `std-xxx.dll`
|
||||
# location, so we have to do it manually.
|
||||
# See:
|
||||
# - https://github.com/rust-lang/rust/issues/149050
|
||||
# - https://github.com/msys2/MSYS2-packages/issues/5784
|
||||
(
|
||||
if $is_cygwin; then
|
||||
export PATH="$PATH:$(rustc --print target-libdir)"
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
)
|
||||
cargo test --doc --workspace
|
||||
|
||||
if $lint; then
|
||||
cargo doc --workspace --no-deps
|
||||
fi
|
||||
FISH_GETTEXT_EXTRACTION_DIR=$gettext_template_dir "$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
|
||||
# Using "()" not "{}" because we do want a subshell (for the export)
|
||||
system_tests() (
|
||||
[ -n "$@" ] && export "$@"
|
||||
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
|
||||
|
||||
echo -e "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}"
|
||||
system_tests $cygwin_var=winsymlinks
|
||||
else
|
||||
echo -e "=== Running ${green}integration tests${reset}"
|
||||
system_tests
|
||||
fi
|
||||
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Script to generate the dput.cf for a set of Ubuntu series, prints the filename
|
||||
# Arguments are the PPA followed by the series names
|
||||
|
||||
set -e
|
||||
|
||||
outfile=$(mktemp --tmpdir dput.XXXXX.cf)
|
||||
|
||||
[ $# -lt 2 ] &&
|
||||
echo "$0: at least two arguments (a PPA and at least one series) are required" >&2 &&
|
||||
exit 1
|
||||
|
||||
ppa=$1
|
||||
shift
|
||||
|
||||
for series in "$@"; do
|
||||
cat >> "$outfile" <<EOF
|
||||
[fish-$ppa-$series]
|
||||
fqdn = ppa.launchpad.net
|
||||
method = ftp
|
||||
login = anonymous
|
||||
incoming = ~fish-shell/$ppa/ubuntu/$series
|
||||
|
||||
EOF
|
||||
done
|
||||
|
||||
echo "$outfile"
|
||||
83
build_tools/make_linux_packages.sh
Executable file
83
build_tools/make_linux_packages.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script takes a source tarball (from build_tools/make_tarball.sh) and a vendor tarball (from
|
||||
# build_tools/make_vendor_tarball.sh, generated if not present), and produces:
|
||||
# * Appropriately-named symlinks to look like a Debian package
|
||||
# * Debian .changes and .dsc files with plain names ($version-1) and supported Ubuntu prefixes
|
||||
# ($version-1~somedistro)
|
||||
# * An RPM spec file
|
||||
# By default, input and output files go in ~/fish_built, but this can be controlled with the
|
||||
# FISH_ARTEFACT_PATH environment variable.
|
||||
|
||||
{
|
||||
|
||||
set -e
|
||||
|
||||
version=$1
|
||||
[ -n "$version" ] || { echo "Version number required as argument" >&2; exit 1; }
|
||||
|
||||
|
||||
[ -n "$DEB_SIGN_KEYID$DEB_SIGN_KEYFILE" ] ||
|
||||
echo "Warning: neither DEB_SIGN_KEYID or DEB_SIGN_KEYFILE environment variables are set; you
|
||||
will need a signing key for the author of the most recent debian/changelog entry." >&2
|
||||
|
||||
workpath=${FISH_ARTEFACT_PATH:-~/fish_built}
|
||||
source_tarball="$workpath"/fish-"$version".tar.xz
|
||||
vendor_tarball="$workpath"/fish-"$version"-vendor.tar.xz
|
||||
|
||||
[ -e "$source_tarball" ] || { echo "Missing source tarball, expected at $source_tarball" >&2; exit 1; }
|
||||
cd "$workpath"
|
||||
|
||||
# Unpack the sources
|
||||
tar xf "$source_tarball"
|
||||
sourcepath="$workpath"/fish-"$version"
|
||||
|
||||
# Generate the vendor tarball if it is not already present
|
||||
[ -e "$vendor_tarball" ] || (cd "$sourcepath"; build_tools/make_vendor_tarball.sh;)
|
||||
|
||||
# This step requires network access, so do it early in case it fails
|
||||
# sh has no real array support
|
||||
ubuntu_versions=$(uv run --script "$sourcepath"/build_tools/supported_ubuntu_versions.py)
|
||||
|
||||
# Write the specfile
|
||||
[ -e "$workpath"/fish.spec ] && { echo "Cowardly refusing to overwite an existing fish.spec" >&2;
|
||||
exit 1; }
|
||||
rpmversion=$(echo "$version" |sed -e 's/-/+/' -e 's/-/./g')
|
||||
sed -e "s/@version@/$version/g" -e "s/@rpmversion@/$rpmversion/g" \
|
||||
< "$sourcepath"/fish.spec.in > "$workpath"/fish.spec
|
||||
|
||||
# Make the symlinks for Debian
|
||||
ln -s "$source_tarball" "$workpath"/fish_"$version".orig.tar.xz
|
||||
ln -s "$vendor_tarball" "$workpath"/fish_"$version".orig-cargo-vendor.tar.xz
|
||||
|
||||
# Set up the Debian source tree
|
||||
cd "$sourcepath"
|
||||
mkdir cargo-vendor
|
||||
tar -C cargo-vendor -x -f "$vendor_tarball"
|
||||
cp -r contrib/debian debian
|
||||
|
||||
# The vendor tarball contains a new .cargo/config.toml, which has the
|
||||
# vendoring overrides appended to it. dpkg-source will add this as a
|
||||
# patch using the flags in debian/
|
||||
cp cargo-vendor/.cargo/config.toml .cargo/config.toml
|
||||
|
||||
# Update the Debian changelog
|
||||
# The release scripts do this for release builds - skip if it has already been done
|
||||
if head -n1 debian/changelog | grep --invert-match --quiet --fixed-strings "$version"; then
|
||||
debchange --newversion "$version-1" --distribution unstable "Snapshot build"
|
||||
fi
|
||||
|
||||
# Builds the "plain" Debian package
|
||||
# debuild runs lintian, which takes ten minutes to run over the vendor directories
|
||||
# just use dpkg-buildpackage directly
|
||||
dpkg-buildpackage --build=source -d
|
||||
|
||||
# Build the Ubuntu packages
|
||||
# deb-reversion does not work on source packages, so do the whole thing ourselves
|
||||
for series in $ubuntu_versions; do
|
||||
sed -i -e "1 s/$version-1)/$version-1~$series)/" -e "1 s/unstable/$series/" debian/changelog
|
||||
dpkg-buildpackage --build=source -d
|
||||
sed -i -e "1 s/$version-1~$series)/$version-1)/" -e "1 s/$series/unstable/" debian/changelog
|
||||
done
|
||||
|
||||
}
|
||||
@@ -8,20 +8,6 @@
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
# We need GNU tar as that supports the --mtime and --transform options
|
||||
TAR=notfound
|
||||
for try in tar gtar gnutar; do
|
||||
if $try -Pcf /dev/null --mtime now /dev/null >/dev/null 2>&1; then
|
||||
TAR=$try
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$TAR" = "notfound" ]; then
|
||||
echo 'No suitable tar (supporting --mtime) found as tar/gtar/gnutar in PATH'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current directory, which we'll use for telling Cargo where to find the sources
|
||||
wd="$PWD"
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Build-Depends: debhelper-compat (= 13),
|
||||
python3-sphinx,
|
||||
# Test dependencies
|
||||
locales-all,
|
||||
man-db,
|
||||
man-db | man,
|
||||
python3
|
||||
# 4.6.2 is Debian 12/Ubuntu Noble 24.04; Ubuntu Jammy is 4.6.0.1
|
||||
Standards-Version: 4.6.2
|
||||
@@ -26,8 +26,8 @@ Architecture: any
|
||||
# for col and lock
|
||||
Depends: bsdextrautils,
|
||||
file,
|
||||
# for man
|
||||
man-db,
|
||||
# for showing built-in help pages
|
||||
man-db | man,
|
||||
# for kill
|
||||
procps,
|
||||
python3 (>=3.5),
|
||||
|
||||
@@ -8,6 +8,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
fish-feature-flags.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,12 +8,12 @@
|
||||
use std::cmp;
|
||||
use std::sync::{
|
||||
LazyLock,
|
||||
atomic::{AtomicIsize, Ordering},
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
/// Width of ambiguous East Asian characters and, as of TR11, all private-use characters.
|
||||
/// 1 is the typical default, but we accept any non-negative override via `$fish_ambiguous_width`.
|
||||
pub static FISH_AMBIGUOUS_WIDTH: AtomicIsize = AtomicIsize::new(1);
|
||||
pub static FISH_AMBIGUOUS_WIDTH: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
/// Width of emoji characters.
|
||||
///
|
||||
@@ -25,34 +25,33 @@
|
||||
/// Valid values are 1, and 2. 1 is the typical emoji width used in Unicode 8 while some newer
|
||||
/// terminals use a width of 2 since Unicode 9.
|
||||
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
|
||||
pub static FISH_EMOJI_WIDTH: AtomicIsize = AtomicIsize::new(2);
|
||||
pub static FISH_EMOJI_WIDTH: AtomicUsize = AtomicUsize::new(2);
|
||||
|
||||
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
|
||||
|
||||
// Big hack to use our versions of wcswidth where we know them to be broken, which is
|
||||
// EVERYWHERE (https://github.com/fish-shell/fish-shell/issues/2199)
|
||||
pub fn fish_wcwidth(c: char) -> isize {
|
||||
pub fn fish_wcwidth(c: char) -> Option<usize> {
|
||||
// Check for VS16 which selects emoji presentation. This "promotes" a character like U+2764
|
||||
// (width 1) to an emoji (probably width 2). So treat it as width 1 so the sums work. See #2652.
|
||||
// VS15 selects text presentation.
|
||||
let variation_selector_16 = '\u{FE0F}';
|
||||
let variation_selector_15 = '\u{FE0E}';
|
||||
if c == variation_selector_16 {
|
||||
return 1;
|
||||
return Some(1);
|
||||
} else if c == variation_selector_15 {
|
||||
return 0;
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
// Check for Emoji_Modifier property. Only the Fitzpatrick modifiers have this, in range
|
||||
// 1F3FB..1F3FF. This is a hack because such an emoji appearing on its own would be drawn as
|
||||
// width 2, but that's unlikely to be useful. See #8275.
|
||||
if ('\u{1F3FB}'..='\u{1F3FF}').contains(&c) {
|
||||
return 0;
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
let width = WC_LOOKUP_TABLE.classify(c);
|
||||
match width {
|
||||
WcWidth::NonCharacter | WcWidth::NonPrint | WcWidth::Combining | WcWidth::Unassigned => 0,
|
||||
Some(match width {
|
||||
WcWidth::NonPrint => return None,
|
||||
WcWidth::NonCharacter | WcWidth::Combining | WcWidth::Unassigned => 0,
|
||||
WcWidth::Ambiguous | WcWidth::PrivateUse => {
|
||||
// TR11: "All private-use characters are by default classified as Ambiguous".
|
||||
FISH_AMBIGUOUS_WIDTH.load(Ordering::Relaxed)
|
||||
@@ -60,25 +59,25 @@ pub fn fish_wcwidth(c: char) -> isize {
|
||||
WcWidth::One => 1,
|
||||
WcWidth::Two => 2,
|
||||
WcWidth::WidenedIn9 => FISH_EMOJI_WIDTH.load(Ordering::Relaxed),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// fish's internal versions of wcwidth and wcswidth
|
||||
pub fn fish_wcswidth(s: &wstr) -> isize {
|
||||
// ascii fast path; empty iterator returns true for .all()
|
||||
if s.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {
|
||||
return s.len() as isize;
|
||||
pub fn fish_wcswidth(s: &wstr) -> Option<usize> {
|
||||
fish_wcswidth_canonicalizing(s, std::convert::identity)
|
||||
}
|
||||
|
||||
pub fn fish_wcswidth_canonicalizing(s: &wstr, canonicalize: fn(char) -> char) -> Option<usize> {
|
||||
let chars = s.chars().map(canonicalize);
|
||||
// ascii fast path
|
||||
if chars.clone().all(|c| c.is_ascii() && !c.is_ascii_control()) {
|
||||
return Some(s.len());
|
||||
}
|
||||
|
||||
let mut result = 0;
|
||||
for c in s.chars() {
|
||||
let w = fish_wcwidth(c);
|
||||
if w < 0 {
|
||||
return -1;
|
||||
}
|
||||
result += w;
|
||||
for c in chars {
|
||||
result += fish_wcwidth(c)?;
|
||||
}
|
||||
result
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn wcscasecmp(lhs: &wstr, rhs: &wstr) -> cmp::Ordering {
|
||||
|
||||
13
crates/feature-flags/Cargo.toml
Normal file
13
crates/feature-flags/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "fish-feature-flags"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-widestring.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,15 +1,14 @@
|
||||
//! Flags to enable upcoming features
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(test)]
|
||||
use std::cell::RefCell;
|
||||
use fish_widestring::{L, WExt as _, wstr};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
/// The list of flags.
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FeatureFlag {
|
||||
/// Whether ^ is supported for stderr redirection.
|
||||
StderrNoCaret,
|
||||
@@ -63,10 +62,10 @@ pub struct FeatureMetadata {
|
||||
pub description: &'static wstr,
|
||||
|
||||
/// Default flag value.
|
||||
pub default_value: bool,
|
||||
default_value: bool,
|
||||
|
||||
/// Whether the value can still be changed or not.
|
||||
pub read_only: bool,
|
||||
read_only: bool,
|
||||
}
|
||||
|
||||
/// The metadata, indexed by flag.
|
||||
@@ -156,31 +155,26 @@ pub struct FeatureMetadata {
|
||||
];
|
||||
|
||||
thread_local!(
|
||||
#[cfg(test)]
|
||||
static LOCAL_FEATURES: RefCell<Option<Features>> = const { RefCell::new(None) };
|
||||
static LOCAL_OVERRIDE_STACK: RefCell<Vec<(FeatureFlag, bool)>> =
|
||||
const { RefCell::new(Vec::new()) };
|
||||
);
|
||||
|
||||
/// The singleton shared feature set.
|
||||
static FEATURES: Features = Features::new();
|
||||
|
||||
/// Perform a feature test on the global set of features.
|
||||
pub fn test(flag: FeatureFlag) -> bool {
|
||||
#[cfg(test)]
|
||||
{
|
||||
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).test(flag))
|
||||
pub fn feature_test(flag: FeatureFlag) -> bool {
|
||||
if let Some(value) = LOCAL_OVERRIDE_STACK.with(|stack| {
|
||||
for &(overridden_feature, value) in stack.borrow().iter().rev() {
|
||||
if flag == overridden_feature {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}) {
|
||||
return value;
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
FEATURES.test(flag)
|
||||
}
|
||||
}
|
||||
|
||||
pub use test as feature_test;
|
||||
|
||||
/// Set a flag.
|
||||
#[cfg(test)]
|
||||
pub fn set(flag: FeatureFlag, value: bool) {
|
||||
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).set(flag, value));
|
||||
FEATURES.test(flag)
|
||||
}
|
||||
|
||||
/// Parses a comma-separated feature-flag string, updating ourselves with the values.
|
||||
@@ -188,20 +182,7 @@ pub fn set(flag: FeatureFlag, value: bool) {
|
||||
/// The special group name "all" may be used for those who like to live on the edge.
|
||||
/// Unknown features are silently ignored.
|
||||
pub fn set_from_string<'a>(str: impl Into<&'a wstr>) {
|
||||
let wstr: &wstr = str.into();
|
||||
#[cfg(test)]
|
||||
{
|
||||
LOCAL_FEATURES.with(|fc| {
|
||||
fc.borrow()
|
||||
.as_ref()
|
||||
.unwrap_or(&FEATURES)
|
||||
.set_from_string(wstr);
|
||||
});
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
FEATURES.set_from_string(wstr);
|
||||
}
|
||||
FEATURES.set_from_string(str.into());
|
||||
}
|
||||
|
||||
impl Features {
|
||||
@@ -237,19 +218,14 @@ fn set(&self, flag: FeatureFlag, value: bool) {
|
||||
}
|
||||
|
||||
fn set_from_string(&self, str: &wstr) {
|
||||
let whitespace = L!("\t\n\0x0B\0x0C\r ").as_char_slice();
|
||||
for entry in str.as_char_slice().split(|c| *c == ',') {
|
||||
for entry in str.split(',') {
|
||||
let entry = entry.trim();
|
||||
if entry.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Trim leading and trailing whitespace
|
||||
let entry = &entry[entry.iter().take_while(|c| whitespace.contains(c)).count()..];
|
||||
let entry =
|
||||
&entry[..entry.len() - entry.iter().take_while(|c| whitespace.contains(c)).count()];
|
||||
|
||||
// A "no-" prefix inverts the sense.
|
||||
let (name, value) = match entry.strip_prefix(L!("no-").as_char_slice()) {
|
||||
let (name, value) = match entry.strip_prefix("no-") {
|
||||
Some(suffix) => (suffix, false),
|
||||
None => (entry, true),
|
||||
};
|
||||
@@ -275,28 +251,20 @@ fn set_from_string(&self, str: &wstr) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn scoped_test(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
|
||||
LOCAL_FEATURES.with(|fc| {
|
||||
assert!(
|
||||
fc.borrow().is_none(),
|
||||
"scoped_test() does not support nesting"
|
||||
);
|
||||
|
||||
let f = Features::new();
|
||||
f.set(flag, value);
|
||||
*fc.borrow_mut() = Some(f);
|
||||
|
||||
/// Run code with a feature overridden.
|
||||
/// This should only be used in tests.
|
||||
pub fn with_overridden_feature(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
|
||||
LOCAL_OVERRIDE_STACK.with(|stack| {
|
||||
stack.borrow_mut().push((flag, value));
|
||||
test_fn();
|
||||
|
||||
*fc.borrow_mut() = None;
|
||||
stack.borrow_mut().pop();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FeatureFlag, Features, METADATA, scoped_test, set, test};
|
||||
use crate::prelude::*;
|
||||
use super::{FeatureFlag, Features, METADATA, feature_test, with_overridden_feature};
|
||||
use fish_widestring::L;
|
||||
|
||||
#[test]
|
||||
fn test_feature_flags() {
|
||||
@@ -322,25 +290,19 @@ fn test_feature_flags() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scoped() {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
assert!(test(FeatureFlag::QuestionMarkNoGlob));
|
||||
fn test_overridden_feature() {
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
|
||||
});
|
||||
|
||||
set(FeatureFlag::QuestionMarkNoGlob, true);
|
||||
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
assert!(!test(FeatureFlag::QuestionMarkNoGlob));
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
assert!(!feature_test(FeatureFlag::QuestionMarkNoGlob));
|
||||
});
|
||||
|
||||
set(FeatureFlag::QuestionMarkNoGlob, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_nested_scopes_not_supported() {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {});
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
//! Helper functions for working with wcstring.
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString, OsString},
|
||||
os::unix::ffi::OsStringExt as _,
|
||||
};
|
||||
|
||||
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
|
||||
use fish_widestring::{ELLIPSIS_CHAR, decode_byte_from_char, prelude::*};
|
||||
use fish_widestring::{ELLIPSIS_CHAR, prelude::*};
|
||||
|
||||
/// Return the number of newlines in a string.
|
||||
pub fn count_newlines(s: &wstr) -> usize {
|
||||
@@ -340,145 +335,6 @@ pub fn string_fuzzy_match_string(
|
||||
StringFuzzyMatch::try_create(string, match_against, anchor_start)
|
||||
}
|
||||
|
||||
/// Implementation of wcs2bytes that accepts a callback.
|
||||
/// The first argument can be either a `&str` or `&wstr`.
|
||||
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
|
||||
/// input, doing one invocation per character.
|
||||
/// If `func` returns false, it stops; otherwise it continues.
|
||||
/// Return false if the callback returned false, otherwise true.
|
||||
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
|
||||
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
|
||||
let mut converted = [0_u8; 4];
|
||||
|
||||
for c in input.chars() {
|
||||
let bytes = if let Some(byte) = decode_byte_from_char(c) {
|
||||
converted[0] = byte;
|
||||
&converted[..=0]
|
||||
} else {
|
||||
c.encode_utf8(&mut converted).as_bytes()
|
||||
};
|
||||
if !func(bytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
|
||||
/// string.
|
||||
///
|
||||
/// This function decodes illegal character sequences in a reversible way using the private use
|
||||
/// area.
|
||||
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn wcs2osstring(input: &wstr) -> OsString {
|
||||
if input.is_empty() {
|
||||
return OsString::new();
|
||||
}
|
||||
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
OsString::from_vec(result)
|
||||
}
|
||||
|
||||
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
|
||||
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
|
||||
pub fn wcs2zstring(input: &wstr) -> CString {
|
||||
if input.is_empty() {
|
||||
return CString::default();
|
||||
}
|
||||
|
||||
let mut vec = Vec::with_capacity(input.len() + 1);
|
||||
str2bytes_callback(input, |buff| {
|
||||
vec.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
vec.push(b'\0');
|
||||
|
||||
match CString::from_vec_with_nul(vec) {
|
||||
Ok(cstr) => cstr,
|
||||
Err(err) => {
|
||||
// `input` contained a NUL in the middle; we can retrieve `vec`, though
|
||||
let mut vec = err.into_bytes();
|
||||
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
|
||||
vec.truncate(pos + 1);
|
||||
// Safety: We truncated after the first NUL
|
||||
unsafe { CString::from_vec_with_nul_unchecked(vec) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
|
||||
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
|
||||
str2bytes_callback(input, |buff| {
|
||||
output.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// A trait to make it more convenient to pass ascii/Unicode strings to functions that can take
|
||||
/// non-Unicode values. The result is nul-terminated and can be passed to OS functions.
|
||||
///
|
||||
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
|
||||
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
|
||||
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
|
||||
/// left with the original item if we're going to allocate from scratch in all cases.
|
||||
pub trait ToCString {
|
||||
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
|
||||
fn to_cstring(self) -> CString;
|
||||
}
|
||||
|
||||
impl ToCString for CString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCString for &CStr {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
|
||||
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &wstr {
|
||||
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
|
||||
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
|
||||
fn to_cstring(self) -> CString {
|
||||
self::wcs2zstring(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
|
||||
/// functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &WString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.as_utfstr().to_cstring()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
|
||||
impl ToCString for Vec<u8> {
|
||||
fn to_cstring(mut self) -> CString {
|
||||
self.push(b'\0');
|
||||
CString::from_vec_with_nul(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
|
||||
impl ToCString for &[u8] {
|
||||
fn to_cstring(self) -> CString {
|
||||
CString::new(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a string by runs of any of the separator characters provided in `seps`.
|
||||
/// Note the delimiters are the characters in `seps`, not `seps` itself.
|
||||
/// `seps` may contain the NUL character.
|
||||
@@ -665,12 +521,12 @@ fn next(&mut self) -> Option<Self::Item> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of -1.
|
||||
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of none.
|
||||
pub fn fish_wcwidth_visible(c: char) -> isize {
|
||||
if c == '\x08' {
|
||||
return -1;
|
||||
}
|
||||
fish_wcwidth(c).max(0)
|
||||
fish_wcwidth(c).unwrap_or_default().try_into().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -7,6 +7,7 @@ repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
unicode-width.workspace = true
|
||||
widestring.workspace = true
|
||||
|
||||
|
||||
@@ -6,16 +6,34 @@
|
||||
|
||||
pub mod word_char;
|
||||
|
||||
use std::{iter, slice};
|
||||
use std::{
|
||||
ffi::{CStr, CString, OsStr, OsString},
|
||||
iter,
|
||||
os::unix::ffi::{OsStrExt as _, OsStringExt as _},
|
||||
slice,
|
||||
};
|
||||
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utf32str as L, utfstr::CharsUtf32};
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr};
|
||||
}
|
||||
|
||||
// Highest legal ASCII value.
|
||||
pub const ASCII_MAX: char = 127 as char;
|
||||
|
||||
// Highest legal 16-bit Unicode value.
|
||||
pub const UCS2_MAX: char = '\u{FFFF}';
|
||||
|
||||
// Highest legal byte value.
|
||||
pub const BYTE_MAX: char = 0xFF as char;
|
||||
|
||||
// Unicode BOM value.
|
||||
pub const UTF8_BOM_WCHAR: char = '\u{FEFF}';
|
||||
|
||||
/// The character to use where the text has been truncated.
|
||||
pub const ELLIPSIS_CHAR: char = '\u{2026}'; // ('…')
|
||||
|
||||
pub const SPECIAL_KEY_ENCODE_BASE: char = '\u{F500}';
|
||||
// These are in the Unicode private-use range. We really shouldn't use this
|
||||
// range but have little choice in the matter given how our lexer/parser works.
|
||||
// We can't use non-characters for these two ranges because there are only 66 of
|
||||
@@ -28,9 +46,79 @@ pub mod prelude {
|
||||
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
|
||||
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
|
||||
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
|
||||
pub const ENCODE_DIRECT_BASE: char = '\u{F600}';
|
||||
pub const ENCODE_DIRECT_BASE: char = char_offset(SPECIAL_KEY_ENCODE_BASE, 256);
|
||||
pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256);
|
||||
|
||||
// Use Unicode "non-characters" for internal characters as much as we can. This
|
||||
// gives us 32 "characters" for internal use that we can guarantee should not
|
||||
// appear in our input stream. See http://www.unicode.org/faq/private_use.html.
|
||||
pub const RESERVED_CHAR_BASE: char = '\u{FDD0}';
|
||||
pub const RESERVED_CHAR_END: char = '\u{FDF0}';
|
||||
// Split the available non-character values into two ranges to ensure there are
|
||||
// no conflicts among the places we use these special characters.
|
||||
pub const EXPAND_RESERVED_BASE: char = RESERVED_CHAR_BASE;
|
||||
pub const EXPAND_RESERVED_END: char = char_offset(EXPAND_RESERVED_BASE, 16);
|
||||
pub const WILDCARD_RESERVED_BASE: char = EXPAND_RESERVED_END;
|
||||
pub const WILDCARD_RESERVED_END: char = char_offset(WILDCARD_RESERVED_BASE, 16);
|
||||
// Make sure the ranges defined above don't exceed the range for non-characters.
|
||||
// This is to make sure we didn't do something stupid in subdividing the
|
||||
// Unicode range for our needs.
|
||||
const _: () = assert!(WILDCARD_RESERVED_END <= RESERVED_CHAR_END);
|
||||
|
||||
/// Character representing any character except '/' (slash).
|
||||
pub const ANY_CHAR: char = char_offset(WILDCARD_RESERVED_BASE, 0);
|
||||
/// Character representing any character string not containing '/' (slash).
|
||||
pub const ANY_STRING: char = char_offset(WILDCARD_RESERVED_BASE, 1);
|
||||
/// Character representing any character string.
|
||||
pub const ANY_STRING_RECURSIVE: char = char_offset(WILDCARD_RESERVED_BASE, 2);
|
||||
/// This is a special pseudo-char that is not used other than to mark the
|
||||
/// end of the special characters so we can sanity check the enum range.
|
||||
#[allow(dead_code)]
|
||||
pub const ANY_SENTINEL: char = char_offset(WILDCARD_RESERVED_BASE, 3);
|
||||
|
||||
/// Character representing a home directory.
|
||||
pub const HOME_DIRECTORY: char = char_offset(EXPAND_RESERVED_BASE, 0);
|
||||
/// Character representing process expansion for %self.
|
||||
pub const PROCESS_EXPAND_SELF: char = char_offset(EXPAND_RESERVED_BASE, 1);
|
||||
/// Character representing variable expansion.
|
||||
pub const VARIABLE_EXPAND: char = char_offset(EXPAND_RESERVED_BASE, 2);
|
||||
/// Character representing variable expansion into a single element.
|
||||
pub const VARIABLE_EXPAND_SINGLE: char = char_offset(EXPAND_RESERVED_BASE, 3);
|
||||
/// Character representing the start of a bracket expansion.
|
||||
pub const BRACE_BEGIN: char = char_offset(EXPAND_RESERVED_BASE, 4);
|
||||
/// Character representing the end of a bracket expansion.
|
||||
pub const BRACE_END: char = char_offset(EXPAND_RESERVED_BASE, 5);
|
||||
/// Character representing separation between two bracket elements.
|
||||
pub const BRACE_SEP: char = char_offset(EXPAND_RESERVED_BASE, 6);
|
||||
/// Character that takes the place of any whitespace within non-quoted text in braces
|
||||
pub const BRACE_SPACE: char = char_offset(EXPAND_RESERVED_BASE, 7);
|
||||
/// Separate subtokens in a token with this character.
|
||||
pub const INTERNAL_SEPARATOR: char = char_offset(EXPAND_RESERVED_BASE, 8);
|
||||
/// Character representing an empty variable expansion. Only used transitively while expanding
|
||||
/// variables.
|
||||
pub const VARIABLE_EXPAND_EMPTY: char = char_offset(EXPAND_RESERVED_BASE, 9);
|
||||
|
||||
const _: () = assert!(
|
||||
EXPAND_RESERVED_END as u32 > VARIABLE_EXPAND_EMPTY as u32,
|
||||
"Characters used in expansions must stay within private use area"
|
||||
);
|
||||
|
||||
/// The string represented by PROCESS_EXPAND_SELF
|
||||
pub const PROCESS_EXPAND_SELF_STR: &wstr = L!("%self");
|
||||
|
||||
/// Return true if the character is in a range reserved for fish's private use.
|
||||
///
|
||||
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
|
||||
/// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted
|
||||
/// string. We don't want external input to be able to feed reserved characters into our
|
||||
/// lexer/parser or code evaluator.
|
||||
//
|
||||
// TODO: Actually implement the replacement as documented above.
|
||||
pub fn fish_reserved_codepoint(c: char) -> bool {
|
||||
(c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END)
|
||||
|| (c >= SPECIAL_KEY_ENCODE_BASE && c < ENCODE_DIRECT_END)
|
||||
}
|
||||
|
||||
/// Encode a literal byte in a UTF-32 character. This is required for e.g. the echo builtin, whose
|
||||
/// escape sequences can be used to construct raw byte sequences which are then interpreted as e.g.
|
||||
/// UTF-8 by the terminal. If we were to interpret each of those bytes as a codepoint and encode it
|
||||
@@ -43,6 +131,86 @@ pub fn encode_byte_to_char(byte: u8) -> char {
|
||||
.expect("private-use codepoint should be valid char")
|
||||
}
|
||||
|
||||
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
|
||||
/// string.
|
||||
///
|
||||
/// This function decodes illegal character sequences in a reversible way using the private use
|
||||
/// area.
|
||||
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn wcs2osstring(input: &wstr) -> OsString {
|
||||
if input.is_empty() {
|
||||
return OsString::new();
|
||||
}
|
||||
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
OsString::from_vec(result)
|
||||
}
|
||||
|
||||
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
|
||||
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
|
||||
pub fn wcs2zstring(input: &wstr) -> CString {
|
||||
if input.is_empty() {
|
||||
return CString::default();
|
||||
}
|
||||
|
||||
let mut vec = Vec::with_capacity(input.len() + 1);
|
||||
str2bytes_callback(input, |buff| {
|
||||
vec.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
vec.push(b'\0');
|
||||
|
||||
match CString::from_vec_with_nul(vec) {
|
||||
Ok(cstr) => cstr,
|
||||
Err(err) => {
|
||||
// `input` contained a NUL in the middle; we can retrieve `vec`, though
|
||||
let mut vec = err.into_bytes();
|
||||
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
|
||||
vec.truncate(pos + 1);
|
||||
// Safety: We truncated after the first NUL
|
||||
unsafe { CString::from_vec_with_nul_unchecked(vec) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
|
||||
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
|
||||
str2bytes_callback(input, |buff| {
|
||||
output.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// Implementation of wcs2bytes that accepts a callback.
|
||||
/// The first argument can be either a `&str` or `&wstr`.
|
||||
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
|
||||
/// input, doing one invocation per character.
|
||||
/// If `func` returns false, it stops; otherwise it continues.
|
||||
/// Return false if the callback returned false, otherwise true.
|
||||
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
|
||||
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
|
||||
let mut converted = [0_u8; 4];
|
||||
|
||||
for c in input.chars() {
|
||||
let bytes = if let Some(byte) = decode_byte_from_char(c) {
|
||||
converted[0] = byte;
|
||||
&converted[..=0]
|
||||
} else {
|
||||
c.encode_utf8(&mut converted).as_bytes()
|
||||
};
|
||||
if !func(bytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Decode a literal byte from a UTF-32 character.
|
||||
pub fn decode_byte_from_char(c: char) -> Option<u8> {
|
||||
if c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END {
|
||||
@@ -56,6 +224,65 @@ pub fn decode_byte_from_char(c: char) -> Option<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to make it more convenient to pass ascii/Unicode strings to functions that can take
|
||||
/// non-Unicode values. The result is nul-terminated and can be passed to OS functions.
|
||||
///
|
||||
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
|
||||
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
|
||||
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
|
||||
/// left with the original item if we're going to allocate from scratch in all cases.
|
||||
pub trait ToCString {
|
||||
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
|
||||
fn to_cstring(self) -> CString;
|
||||
}
|
||||
|
||||
impl ToCString for CString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCString for &CStr {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
|
||||
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &wstr {
|
||||
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
|
||||
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
|
||||
fn to_cstring(self) -> CString {
|
||||
self::wcs2zstring(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
|
||||
/// functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &WString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.as_utfstr().to_cstring()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
|
||||
impl ToCString for Vec<u8> {
|
||||
fn to_cstring(mut self) -> CString {
|
||||
self.push(b'\0');
|
||||
CString::from_vec_with_nul(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
|
||||
impl ToCString for &[u8] {
|
||||
fn to_cstring(self) -> CString {
|
||||
CString::new(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
mod decoder {
|
||||
use crate::{ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, char_offset, wstr};
|
||||
use buffer::Buffer;
|
||||
@@ -276,6 +503,82 @@ pub const fn char_offset(base: char, offset: u32) -> char {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the bytes in `input` into a [`WString`], encoding non-UTF-8 bytes into private-use-area
|
||||
/// code-points. Bytes which would be parsed into our reserved PUA range are encoded individually,
|
||||
/// to allow for correct round-tripping.
|
||||
pub fn bytes2wcstring(mut input: &[u8]) -> WString {
|
||||
if input.is_empty() {
|
||||
return WString::new();
|
||||
}
|
||||
|
||||
let mut result = WString::with_capacity(input.len());
|
||||
|
||||
fn append_escaped_str(output: &mut WString, input: &str) {
|
||||
for (i, c) in input.char_indices() {
|
||||
if fish_reserved_codepoint(c) {
|
||||
for byte in &input.as_bytes()[i..i + c.len_utf8()] {
|
||||
output.push(encode_byte_to_char(*byte));
|
||||
}
|
||||
} else {
|
||||
output.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while !input.is_empty() {
|
||||
match std::str::from_utf8(input) {
|
||||
Ok(parsed_str) => {
|
||||
append_escaped_str(&mut result, parsed_str);
|
||||
// The entire remaining input could be parsed, so we are done.
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
let (valid, after_valid) = input.split_at(e.valid_up_to());
|
||||
// SAFETY: The previous `str::from_utf8` call established that the prefix `valid`
|
||||
// is valid UTF-8. This prefix may be empty.
|
||||
let parsed_str = unsafe { std::str::from_utf8_unchecked(valid) };
|
||||
append_escaped_str(&mut result, parsed_str);
|
||||
// The length of the prefix of `after_valid` which is invalid UTF-8.
|
||||
// The remaining bytes of `input` (if any) will be parsed in subsequent iterations
|
||||
// of the loop, starting from the first byte that starts a valid UTF-8-encoded codepoint.
|
||||
// `error_len` can return `None`, if it sees a byte sequence that could be the
|
||||
// prefix of a valid code-point encoding at the end of the byte slice.
|
||||
// This is useful when the input is chunked, but we don't do that, so in this case
|
||||
// we use our custom encoding for all remaining bytes (at most 3).
|
||||
let error_len = e.error_len().unwrap_or(after_valid.len());
|
||||
for byte in &after_valid[..error_len] {
|
||||
result.push(encode_byte_to_char(*byte));
|
||||
}
|
||||
input = &after_valid[error_len..];
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Use this rather than [`WString::from_str`] when the input could contain PUA bytes we use to
|
||||
/// encode non-UTF-8 bytes. Otherwise, when decoding the resulting [`WString`], the PUA bytes in
|
||||
/// the input would be converted to non-UTF-8 bytes.
|
||||
pub fn str2wcstring<S: AsRef<str>>(input: S) -> WString {
|
||||
bytes2wcstring(input.as_ref().as_bytes())
|
||||
}
|
||||
|
||||
pub fn cstr2wcstring<C: AsRef<CStr>>(input: C) -> WString {
|
||||
bytes2wcstring(input.as_ref().to_bytes())
|
||||
}
|
||||
|
||||
pub fn osstr2wcstring<O: AsRef<OsStr>>(input: O) -> WString {
|
||||
bytes2wcstring(input.as_ref().as_bytes())
|
||||
}
|
||||
|
||||
/// # SAFETY
|
||||
///
|
||||
/// `input` must point to a valid NUL-terminated string.
|
||||
pub unsafe fn charptr2wcstring(input: *const libc::c_char) -> WString {
|
||||
let input: &[u8] = unsafe { CStr::from_ptr(input).to_bytes() };
|
||||
bytes2wcstring(input)
|
||||
}
|
||||
|
||||
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -251,7 +251,7 @@ Some *OPTION_SPEC* examples:
|
||||
|
||||
- ``n/name=?`` means that both ``-n`` and ``--name`` are valid. It accepts an optional value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
|
||||
|
||||
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
|
||||
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurrence. Each value will be the value given to the option, or the empty string if no value was given.
|
||||
|
||||
- ``name=+`` means that only ``--name`` is valid. It requires a value and can be used more than once. If the flag is seen then ``_flag_name`` will be set with the values associated with each occurrence.
|
||||
|
||||
|
||||
@@ -6,16 +6,17 @@ Synopsis
|
||||
|
||||
.. synopsis::
|
||||
|
||||
set
|
||||
set (-f | --function) (-l | --local) (-g | --global) (-U | --universal) [--no-event]
|
||||
set [-Uflg] NAME [VALUE ...]
|
||||
set [-Uflg] NAME[[INDEX ...]] [VALUE ...]
|
||||
set (-x | --export) (-u | --unexport) [-Uflg] NAME [VALUE ...]
|
||||
set (-a | --append) (-p | --prepend) [-Uflg] NAME VALUE ...
|
||||
set (-e | --erase) [-Uflg] [-xu] [NAME][[INDEX]] ...]
|
||||
set (-q | --query) [-Uflg] [-xu] [NAME][[INDEX]] ...]
|
||||
set [(-f | --function) (-l | --local) (-g | --global) (-U | --universal)]
|
||||
[(-x | --export) (-u | --unexport)]
|
||||
set (-S | --show) (-L | --long) [NAME ...]
|
||||
|
||||
set [-Uflg] [-xu] [--no-event] NAME [VALUE ...]
|
||||
set [-Uflg] [--no-event] NAME[[INDEX ...]] [VALUE ...]
|
||||
set (-a | --append) (-p | --prepend) [-Uflg] [--no-event] NAME VALUE ...
|
||||
set (-e | --erase) [-Uflg] [--no-event] NAME[[INDEX]] ...
|
||||
|
||||
set (-q | --query) [-Uflg] [-xu] NAME[[INDEX]] ...
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ It also provides a large number of program specific scripted completions. Most o
|
||||
|
||||
You can also write your own completions or install some you got from someone else. For that, see :doc:`Writing your own completions <completions>`.
|
||||
|
||||
Completion scripts are loaded on demand, like :ref:`functions are <syntax-function-autoloading>`. The difference is the ``$fish_complete_path`` :ref:`list <variables-lists>` is used instead of ``$fish_function_path``. Typically you can drop new completions in ~/.config/fish/completions/name-of-command.fish and fish will find them automatically.
|
||||
Completion scripts are loaded on demand, like :ref:`functions are <syntax-function-autoloading>`. The difference is the ``$fish_complete_path`` :ref:`list <variables-lists>` is used instead of ``$fish_function_path``. Typically you can drop new completions in ``~/.config/fish/completions/<name-of-command>.fish`` and fish will find them automatically.
|
||||
|
||||
.. _syntax-highlighting:
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,19 @@ function __dnf_list_installed_packages
|
||||
dnf repoquery --cacheonly "$cur*" --qf "%{name}\n" --installed </dev/null
|
||||
end
|
||||
|
||||
function __dnf_list_copr_repos
|
||||
set -l copr_repos (dnf copr list)
|
||||
|
||||
switch $argv[1]
|
||||
case enable
|
||||
string replace -f -- " (disabled)" "" $copr_repos
|
||||
case disable
|
||||
string match -v -- "*(disabled)*" $copr_repos
|
||||
case '*'
|
||||
string replace -- " (disabled)" "" $copr_repos
|
||||
end
|
||||
end
|
||||
|
||||
function __dnf_list_available_packages
|
||||
set -l tok (commandline -ct | string collect)
|
||||
set -l files (__fish_complete_suffix .rpm)
|
||||
@@ -86,6 +99,20 @@ complete -c dnf -n "__fish_seen_subcommand_from clean" -xa metadata -d "Removes
|
||||
complete -c dnf -n "__fish_seen_subcommand_from clean" -xa packages -d "Removes any cached packages"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from clean" -xa all -d "Removes all cache"
|
||||
|
||||
# Copr
|
||||
set -l coprcommands list enable disable remove debug
|
||||
complete -c dnf -n __fish_use_subcommand -xa copr -d "Manage Copr repositories"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -xa list -d "List Copr repositories"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -xa enable -d "Install a Copr repository"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -xa disable -d "Disable a Copr repository"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -xa remove -d "Remove a Copr repository"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -xa debug -d "Print system info for debugging"
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -l hub -d "Copr hub hostname"
|
||||
|
||||
for i in enable disable remove
|
||||
complete -c dnf -n "__fish_seen_subcommand_from copr; and __fish_seen_subcommand_from $i" -xa "(__dnf_list_copr_repos $i)"
|
||||
end
|
||||
|
||||
# Distro-sync
|
||||
complete -c dnf -n __fish_use_subcommand -xa distro-sync -d "Synchronizes packages to match the latest"
|
||||
|
||||
|
||||
@@ -2161,6 +2161,7 @@ complete -x -c git -n '__fish_git_using_command push' -l exec -d 'Same as --rece
|
||||
### rebase
|
||||
complete -f -c git -n __fish_git_needs_command -a rebase -d 'Reapply commit sequence on a new base'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command rebase'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n 'string match -rq -- "^-i|^--interactive" (commandline -xpc)' -ka '(__fish_git_recent_commits)'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l continue -d 'Restart the rebasing process'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l abort -d 'Abort the rebase operation'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l edit-todo -d 'Edit the todo list'
|
||||
@@ -2969,11 +2970,24 @@ complete -f -c git -n '__fish_git_using_command update-ref' -l create-reflog -d
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -l stdin -d 'Read instructions from stdin'
|
||||
complete -f -c git -n '__fish_git_using_command update-ref' -s z -d 'NUL-terminated format for stdin'
|
||||
|
||||
### verify-commit
|
||||
complete -f -c git -n __fish_git_needs_command -a verify-commit -d 'Check the GPG signature of commits'
|
||||
complete -f -c git -n '__fish_git_using_command verify-commit' -ka '(__fish_git_commits)'
|
||||
complete -f -c git -n '__fish_git_using_command verify-commit' -s v -l verbose -d 'Print commit contents'
|
||||
complete -f -c git -n '__fish_git_using_command verify-commit' -l raw -d 'Print raw gpg status output'
|
||||
|
||||
### verify-pack
|
||||
complete -f -c git -n __fish_git_needs_command -a verify-pack -d 'Validate packed Git archive files'
|
||||
complete -f -c git -n '__fish_git_using_command verify-pack' -s v -l verbose -d 'Show objects contained in pack'
|
||||
complete -f -c git -n '__fish_git_using_command verify-pack' -s s -l stat-only -d 'Only show histogram of delta chain length'
|
||||
|
||||
### verify-tag
|
||||
complete -f -c git -n __fish_git_needs_command -a verify-tag -d 'Check the GPG signature of tags'
|
||||
complete -f -c git -n '__fish_git_using_command verify-tag' -ka '(__fish_git_tags)'
|
||||
complete -f -c git -n '__fish_git_using_command verify-tag' -s v -l verbose -d 'Print tag contents'
|
||||
complete -f -c git -n '__fish_git_using_command verify-tag' -l raw -d 'Print raw gpg status output'
|
||||
complete -x -c git -n '__fish_git_using_command verify-tag' -l format -d 'Format to use for the output'
|
||||
|
||||
### write-tree
|
||||
complete -f -c git -n __fish_git_needs_command -a write-tree -d 'Create a tree object from the current index'
|
||||
complete -f -c git -n '__fish_git_using_command write-tree' -l missing-ok -d 'Allow missing objects'
|
||||
|
||||
@@ -1,44 +1 @@
|
||||
# Commands
|
||||
complete -c ngrok -f -a authtoken -d "Save authtoken to configuration file"
|
||||
complete -c ngrok -f -a credits -d "Prints author and licensing information"
|
||||
complete -c ngrok -f -a http -d "Start an HTTP tunnel"
|
||||
complete -c ngrok -f -a start -d "Start tunnels by name from the configuration file"
|
||||
complete -c ngrok -f -a tcp -d "Start a TCP tunnel"
|
||||
complete -c ngrok -f -a tls -d "Start a TLS tunnel"
|
||||
complete -c ngrok -f -a update -d "Update ngrok to the latest version"
|
||||
complete -c ngrok -f -a version -d "Print the version string"
|
||||
complete -c ngrok -f -a help -d "Shows a list of commands or help for one command"
|
||||
|
||||
# General Options
|
||||
complete -c ngrok -l help -e -f
|
||||
complete -c ngrok -l authtoken -r -d "ngrok.com authtoken identifying a user"
|
||||
complete -c ngrok -l config -r -d "path to config files; they are merged if multiple"
|
||||
complete -c ngrok -l log -x -a "false stderr stdout" -d "path to log file, 'stdout', 'stderr' or 'false'"
|
||||
complete -c ngrok -l log-format -x -a "term logfmt json" -d "log record format: 'term', 'logfmt', 'json'"
|
||||
complete -c ngrok -l log-level -r -a info -d "logging level"
|
||||
complete -c ngrok -l region -x -a "us eu au ap" -d "ngrok server region [us , eu, au, ap] (default: us)"
|
||||
|
||||
# http & tls's options
|
||||
complete -c ngrok -l hostname -r -d "host tunnel on custom hostname (requires DNS CNAME)"
|
||||
complete -c ngrok -l subdomain -r -d "host tunnel on a custom subdomain"
|
||||
|
||||
# http's options
|
||||
complete -c ngrok -l auth -r -d "enforce basic auth on tunnel endpoint, 'user:password'"
|
||||
complete -c ngrok -l bind-tls -x -a "both https http" -d "listen for http, https or both: true/false/both"
|
||||
complete -c ngrok -l host-header -r -d "set Host header; if 'rewrite' use local address hostname"
|
||||
complete -c ngrok -l inspect -d "enable/disable http introspection"
|
||||
|
||||
# tls's options
|
||||
complete -c ngrok -l client-cas -r -d "path to TLS certificate authority to verify client certs"
|
||||
complete -c ngrok -l crt -r -d "path to a TLS certificate for TLS termination"
|
||||
complete -c ngrok -l key -r -d "path to a TLS key for TLS termination"
|
||||
|
||||
# start's options
|
||||
complete -c ngrok -l all -d "start all tunnels in the configuration file"
|
||||
complete -c ngrok -l none -d "start running no tunnels"
|
||||
|
||||
# tcp's options
|
||||
complete -c ngrok -l remote-addr -r -d "bind remote address (requires you reserve an address)"
|
||||
|
||||
# update's options
|
||||
complete -c ngrok -l channel -x -a "stable beta" -d "update channel (stable, beta)"
|
||||
SHELL=/bin/fish ngrok completion 2>/dev/null | source
|
||||
|
||||
10
share/functions/__fish_cygwin_noacl.fish
Normal file
10
share/functions/__fish_cygwin_noacl.fish
Normal file
@@ -0,0 +1,10 @@
|
||||
# localization: skip(private)
|
||||
function __fish_cygwin_noacl
|
||||
# MSYS (default) and Cygwin (non-default) mounts may not support POSIX permissions.
|
||||
__fish_is_cygwin
|
||||
and {
|
||||
mount |
|
||||
string match "*on $(stat -c %m -- $argv[1]) *" |
|
||||
string match -qr "[(,]noacl[),]"
|
||||
}
|
||||
end
|
||||
4
share/functions/__fish_is_cygwin.fish
Normal file
4
share/functions/__fish_is_cygwin.fish
Normal file
@@ -0,0 +1,4 @@
|
||||
# localization: skip(private)
|
||||
function __fish_is_cygwin
|
||||
__fish_uname | string match -qr "^(MSYS|CYGWIN)"
|
||||
end
|
||||
@@ -9,6 +9,21 @@ function __fish_make_cache_dir --description "Create and return XDG_CACHE_HOME"
|
||||
# So if you call `__fish_make_cache_dir completions`,
|
||||
# this creates e.g. ~/.cache/fish/completions
|
||||
if not path is -d $xdg_cache_home/fish/"$argv[1]"
|
||||
mkdir -m 700 -p $xdg_cache_home/fish/"$argv[1]"
|
||||
set -l mkdir_options -m 700
|
||||
|
||||
# Can't set the permission in Cygwin on a `noacl` mount
|
||||
if __fish_is_cygwin
|
||||
# Find the first existing parent so we can `stat` it and get its mountpoint
|
||||
set -l existing_parent $xdg_cache_home/fish/"$argv[1]"
|
||||
while not path is -d $existing_parent
|
||||
set existing_parent (path dirname $existing_parent)
|
||||
end
|
||||
|
||||
if __fish_cygwin_noacl "$existing_parent"
|
||||
set mkdir_options
|
||||
end
|
||||
end
|
||||
|
||||
mkdir $mkdir_options -p $xdg_cache_home/fish/"$argv[1]"
|
||||
end; and echo $xdg_cache_home/fish/"$argv[1]"
|
||||
end
|
||||
|
||||
@@ -129,7 +129,9 @@ function fish_vi_exec_motion
|
||||
set motion_cmd commandline -f $motion
|
||||
end
|
||||
switch $motion[1]
|
||||
case forward-char backward-char
|
||||
case forward-char
|
||||
set -e seq_total[1]
|
||||
case backward-char
|
||||
$motion_cmd
|
||||
set -e seq_total[1]
|
||||
end
|
||||
|
||||
@@ -10,7 +10,10 @@ function isatty -d "Tests if a file descriptor is a tty"
|
||||
end
|
||||
|
||||
if set -q argv[2]
|
||||
printf (_ "%s: Too many arguments") isatty >&2
|
||||
{
|
||||
printf (_ "%s: Too many arguments") isatty
|
||||
echo
|
||||
} >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@ function psub --description "Read from stdin into a file and output the filename
|
||||
set -l funcname
|
||||
|
||||
if not status --is-command-substitution
|
||||
printf (_ "%s: Not inside of command substitution") psub >&2
|
||||
{
|
||||
printf (_ "%s: Not inside of command substitution") psub
|
||||
echo
|
||||
} >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ function setenv
|
||||
# `setenv` accepts only two arguments: the var name and the value. If there are more than two
|
||||
# args it is an error. The error message is verbatim from csh.
|
||||
if set -q argv[3]
|
||||
printf (_ '%s: Too many arguments\n') setenv >&2
|
||||
{
|
||||
printf (_ '%s: Too many arguments') setenv
|
||||
echo
|
||||
} >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
@@ -182,7 +182,10 @@ function umask --description "Set default file permission mask"
|
||||
return 1
|
||||
|
||||
case '*'
|
||||
printf (_ '%s: Too many arguments\n') umask >&2
|
||||
{
|
||||
printf (_ '%s: Too many arguments') umask
|
||||
echo
|
||||
} >&2
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -501,6 +501,19 @@ class Deroffer:
|
||||
return True
|
||||
return False
|
||||
|
||||
def device_control(self):
|
||||
# groff \X'...' device control escape (and \Z'...' zero-width).
|
||||
# help2man 1.50+ uses \X'tty: link URL' for hyperlinks.
|
||||
# We just skip the entire escape.
|
||||
if self.str_at(1) in "XZ" and self.str_at(2) == "'":
|
||||
self.skip_char(3)
|
||||
while self.str_at(0) and self.str_at(0) != "'":
|
||||
self.skip_char()
|
||||
if self.str_at(0) == "'":
|
||||
self.skip_char()
|
||||
return True
|
||||
return False
|
||||
|
||||
def var(self):
|
||||
reg = ""
|
||||
s0s1 = self.s[0:2]
|
||||
@@ -650,6 +663,8 @@ class Deroffer:
|
||||
return self.size()
|
||||
elif c in "hvwud":
|
||||
return self.numreq()
|
||||
elif c in "XZ":
|
||||
return self.device_control()
|
||||
elif c in "n*":
|
||||
return self.var()
|
||||
elif c == "(":
|
||||
@@ -1314,6 +1329,9 @@ def built_command(options, description):
|
||||
|
||||
|
||||
def remove_groff_formatting(data):
|
||||
# Strip groff \X'...' device control escapes (help2man 1.50+ hyperlinks)
|
||||
# and \Z'...' zero-width escapes.
|
||||
data = re.sub(r"\\[XZ]'[^']*'", "", data)
|
||||
data = data.replace("\\fI", "")
|
||||
data = data.replace("\\fP", "")
|
||||
data = data.replace("\\f1", "")
|
||||
|
||||
@@ -207,7 +207,7 @@ tt {
|
||||
border-top-left-radius: 5;
|
||||
border-bottom-left-radius: 5;
|
||||
|
||||
/* Pad one less than .master_element, to accomodate our border. */
|
||||
/* Pad one less than .master_element, to accommodate our border. */
|
||||
padding-top: 5px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 4px;
|
||||
|
||||
26
src/ast.rs
26
src/ast.rs
@@ -9,19 +9,21 @@
|
||||
*
|
||||
* Most clients will be interested in visiting the nodes of an ast.
|
||||
*/
|
||||
use crate::common::{UnescapeStringStyle, unescape_string};
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::parse_constants::{
|
||||
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, ParseError, ParseErrorCode,
|
||||
ParseErrorList, ParseKeyword, ParseTokenType, ParseTreeFlags, SOURCE_OFFSET_INVALID,
|
||||
SourceRange, StatementDecoration, token_type_user_presentable_description,
|
||||
};
|
||||
use crate::parse_tree::ParseToken;
|
||||
use crate::prelude::*;
|
||||
use crate::tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
|
||||
TokFlags, TokenType, Tokenizer, TokenizerError, variable_assignment_equals_pos,
|
||||
use crate::{
|
||||
flog::{flog, flogf},
|
||||
parse_constants::{
|
||||
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, ParseError, ParseErrorCode,
|
||||
ParseErrorList, ParseKeyword, ParseTokenType, ParseTreeFlags, SOURCE_OFFSET_INVALID,
|
||||
SourceRange, StatementDecoration, token_type_user_presentable_description,
|
||||
},
|
||||
parse_tree::ParseToken,
|
||||
prelude::*,
|
||||
tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
|
||||
TokFlags, TokenType, Tokenizer, TokenizerError, variable_assignment_equals_pos,
|
||||
},
|
||||
};
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
use macro_rules_attribute::derive;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::AsMut;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! The classes responsible for autoloading functions and completions.
|
||||
|
||||
use crate::common::{ScopeGuard, escape};
|
||||
use crate::env::Environment;
|
||||
use crate::flogf;
|
||||
use crate::io::IoChain;
|
||||
use crate::parser::Parser;
|
||||
use crate::wutil::{FileId, INVALID_FILE_ID, file_id_for_path};
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::{L, WExt as _, WString, wstr};
|
||||
use crate::{
|
||||
env::Environment,
|
||||
flogf,
|
||||
io::IoChain,
|
||||
parser::Parser,
|
||||
wutil::{FileId, INVALID_FILE_ID, file_id_for_path},
|
||||
};
|
||||
use fish_common::{ScopeGuard, escape};
|
||||
use fish_widestring::{L, WExt as _, WString, wcs2bytes, wstr};
|
||||
use lru::LruCache;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -133,7 +134,7 @@ pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) {
|
||||
parser.eval(&script_source, &IoChain::new());
|
||||
}
|
||||
AutoloadPath::Embedded(name) => {
|
||||
use crate::common::bytes2wcstring;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use std::sync::Arc;
|
||||
flogf!(autoload, "Loading embedded: %s", name);
|
||||
let emfile = Asset::get(name).expect("Embedded file not found");
|
||||
@@ -464,7 +465,7 @@ mod tests {
|
||||
fn test_autoload() {
|
||||
let _cleanup = test_init();
|
||||
use crate::fds::wopen_cloexec;
|
||||
use fish_wcstringutil::wcs2zstring;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use nix::fcntl::OFlag;
|
||||
|
||||
macro_rules! run {
|
||||
|
||||
@@ -20,27 +20,22 @@
|
||||
use fish::{
|
||||
ast,
|
||||
builtins::{
|
||||
error::Error,
|
||||
fish_indent, fish_key_reader,
|
||||
shared::{
|
||||
BUILTIN_ERR_MISSING, BUILTIN_ERR_UNEXP_ARG, BUILTIN_ERR_UNKNOWN, STATUS_CMD_ERROR,
|
||||
STATUS_CMD_OK, STATUS_CMD_UNKNOWN, VERSION_STRING_TEMPLATE,
|
||||
},
|
||||
},
|
||||
common::{
|
||||
PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, bytes2wcstring, escape, osstr2wcstring,
|
||||
save_term_foreground_process_group,
|
||||
shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, VERSION_STRING_TEMPLATE},
|
||||
},
|
||||
common::{PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME},
|
||||
env::{
|
||||
EnvMode, Statuses,
|
||||
config_paths::ConfigPaths,
|
||||
environment::{EnvStack, Environment as _, env_init},
|
||||
},
|
||||
eprintf,
|
||||
eprintf, err_fmt,
|
||||
event::{self, Event},
|
||||
flog::{self, activate_flog_categories_by_pattern, flog, flogf, set_flog_file_fd},
|
||||
fprintf, function, future_feature_flags as features,
|
||||
fprintf, function,
|
||||
history::{self, start_private_mode},
|
||||
io::IoChain,
|
||||
io::{FdOutputStream, IoChain, OutputStream},
|
||||
locale::set_libc_locales,
|
||||
nix::isatty,
|
||||
panic::panic_handler,
|
||||
@@ -55,25 +50,28 @@
|
||||
Pid, get_login, is_interactive_session, mark_login, mark_no_exec, proc_init,
|
||||
set_interactive_session,
|
||||
},
|
||||
reader::{reader_init, reader_read, term_copy_modes},
|
||||
reader::{reader_exit_signal, reader_init, reader_read, term_copy_modes},
|
||||
signal::{signal_clear_cancel, signal_unblock_all},
|
||||
threads::{self},
|
||||
topic_monitor,
|
||||
wutil::waccess,
|
||||
};
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use libc::STDIN_FILENO;
|
||||
use fish_common::{escape, save_term_foreground_process_group};
|
||||
use fish_widestring::{bytes2wcstring, osstr2wcstring, wcs2bytes};
|
||||
use libc::{STDERR_FILENO, STDIN_FILENO};
|
||||
use nix::{
|
||||
sys::resource::{UsageWho, getrusage},
|
||||
unistd::{AccessFlags, getpid},
|
||||
};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{env, ops::ControlFlow};
|
||||
use std::{
|
||||
env,
|
||||
ffi::{OsStr, OsString},
|
||||
fs::File,
|
||||
ops::ControlFlow,
|
||||
os::unix::prelude::*,
|
||||
path::Path,
|
||||
sync::{Arc, atomic::Ordering},
|
||||
};
|
||||
|
||||
/// container to hold the options specified within the command line
|
||||
#[derive(Default, Debug)]
|
||||
@@ -318,24 +316,24 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
|
||||
// Either remove it or make it work with flog.
|
||||
}
|
||||
'?' => {
|
||||
eprintf!(
|
||||
"%s\n\n",
|
||||
wgettext_fmt!(BUILTIN_ERR_UNKNOWN, "fish", args[w.wopt_index - 1])
|
||||
);
|
||||
err_fmt!(Error::UNKNOWN_OPT, args[w.wopt_index - 1])
|
||||
.cmd(L!("fish"))
|
||||
.append_to_msg('\n')
|
||||
.write_to(&mut OutputStream::Fd(FdOutputStream::new(STDERR_FILENO)));
|
||||
return ControlFlow::Break(1);
|
||||
}
|
||||
':' => {
|
||||
eprintf!(
|
||||
"%s\n\n",
|
||||
wgettext_fmt!(BUILTIN_ERR_MISSING, "fish", args[w.wopt_index - 1])
|
||||
);
|
||||
err_fmt!(Error::MISSING_OPT_ARG, args[w.wopt_index - 1])
|
||||
.cmd(L!("fish"))
|
||||
.append_to_msg('\n')
|
||||
.write_to(&mut OutputStream::Fd(FdOutputStream::new(STDERR_FILENO)));
|
||||
return ControlFlow::Break(1);
|
||||
}
|
||||
';' => {
|
||||
eprintf!(
|
||||
"%s\n\n",
|
||||
wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, "fish", args[w.wopt_index - 1])
|
||||
);
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, args[w.wopt_index - 1])
|
||||
.cmd(L!("fish"))
|
||||
.append_to_msg('\n')
|
||||
.write_to(&mut OutputStream::Fd(FdOutputStream::new(STDERR_FILENO)));
|
||||
return ControlFlow::Break(1);
|
||||
}
|
||||
_ => panic!("unexpected retval from WGetopter"),
|
||||
@@ -482,10 +480,10 @@ fn throwing_main() -> i32 {
|
||||
// command line takes precedence).
|
||||
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
||||
for s in features_var.as_list() {
|
||||
features::set_from_string(s.as_utfstr());
|
||||
fish_feature_flags::set_from_string(s.as_utfstr());
|
||||
}
|
||||
}
|
||||
features::set_from_string(opts.features.as_utfstr());
|
||||
fish_feature_flags::set_from_string(opts.features.as_utfstr());
|
||||
proc_init();
|
||||
reader_init(true);
|
||||
|
||||
@@ -627,6 +625,16 @@ fn throwing_main() -> i32 {
|
||||
}
|
||||
|
||||
history::save_all();
|
||||
|
||||
// If we deferred a fatal signal, re-raise it now so the parent sees WIFSIGNALED.
|
||||
let exit_sig = reader_exit_signal();
|
||||
if exit_sig != 0 {
|
||||
unsafe {
|
||||
libc::signal(exit_sig, libc::SIG_DFL);
|
||||
libc::raise(exit_sig);
|
||||
}
|
||||
}
|
||||
|
||||
if opts.print_rusage_self {
|
||||
print_rusage_self();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
use super::prelude::*;
|
||||
use crate::abbrs::{self, Abbreviation, Position};
|
||||
use crate::common::{EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_func_name};
|
||||
use crate::env::{EnvMode, EnvStackSetResult};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::re::{regex_make_anchored, to_boxed_chars};
|
||||
use fish_common::help_section;
|
||||
use crate::{
|
||||
abbrs::{self, Abbreviation, Position},
|
||||
builtins::error::Error,
|
||||
common::valid_func_name,
|
||||
env::{EnvMode, EnvStackSetResult},
|
||||
err_fmt, err_str,
|
||||
highlight::highlight_and_colorize,
|
||||
parser::ParserEnvSetMode,
|
||||
re::{regex_make_anchored, to_boxed_chars},
|
||||
};
|
||||
use fish_common::{EscapeStringStyle, escape, escape_string, help_section};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use pcre2::utf32::{Regex, RegexBuilder};
|
||||
|
||||
localizable_consts! {
|
||||
NAME_CANNOT_BE_EMPTY
|
||||
"%s %s: Name cannot be empty"
|
||||
"Name cannot be empty"
|
||||
|
||||
ABBR_CANNOT_HAVE_SPACES
|
||||
"%s %s: Abbreviation '%s' cannot have spaces in the word"
|
||||
"Abbreviation '%s' cannot have spaces in the word"
|
||||
}
|
||||
|
||||
const CMD: &wstr = L!("abbr");
|
||||
@@ -36,7 +41,7 @@ struct Options {
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn validate(&mut self, streams: &mut IoStreams) -> bool {
|
||||
fn validate(&mut self) -> Option<Error<'_>> {
|
||||
// Duplicate options?
|
||||
let mut cmds = vec![];
|
||||
if self.add {
|
||||
@@ -59,12 +64,7 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
|
||||
}
|
||||
|
||||
if cmds.len() > 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Cannot combine options %s",
|
||||
CMD,
|
||||
join(&cmds, L!(", "))
|
||||
));
|
||||
return false;
|
||||
return Some(err_fmt!("Cannot combine options %s", join(&cmds, L!(", "))));
|
||||
}
|
||||
|
||||
// If run with no options, treat it like --add if we have arguments,
|
||||
@@ -76,54 +76,29 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
|
||||
|
||||
localizable_consts! {
|
||||
OPTION_REQUIRES_ARG
|
||||
"%s: %s option requires %s"
|
||||
"%s option requires %s"
|
||||
}
|
||||
if !self.add && self.position.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTION_REQUIRES_ARG,
|
||||
CMD,
|
||||
"--position",
|
||||
"--add",
|
||||
));
|
||||
return false;
|
||||
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--position", "--add"));
|
||||
}
|
||||
if !self.add && self.regex_pattern.is_some() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(OPTION_REQUIRES_ARG, CMD, "--regex", "--add"));
|
||||
return false;
|
||||
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--regex", "--add"));
|
||||
}
|
||||
if !self.add && self.function.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTION_REQUIRES_ARG,
|
||||
CMD,
|
||||
"--function",
|
||||
"--add",
|
||||
));
|
||||
return false;
|
||||
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--function", "--add"));
|
||||
}
|
||||
if !self.add && self.set_cursor_marker.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTION_REQUIRES_ARG,
|
||||
CMD,
|
||||
"--set-cursor",
|
||||
"--add",
|
||||
));
|
||||
return false;
|
||||
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--set-cursor", "--add"));
|
||||
}
|
||||
if self
|
||||
.set_cursor_marker
|
||||
.as_ref()
|
||||
.is_some_and(|m| m.is_empty())
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: --set-cursor argument cannot be empty",
|
||||
CMD
|
||||
));
|
||||
return false;
|
||||
return Some(err_str!("--set-cursor argument cannot be empty"));
|
||||
}
|
||||
|
||||
true
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,12 +188,9 @@ fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &Parser) -> Builti
|
||||
fn abbr_list(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
let subcmd = L!("--list");
|
||||
if !opts.args.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: Unexpected argument -- '%s'",
|
||||
CMD,
|
||||
subcmd,
|
||||
&opts.args[0]
|
||||
));
|
||||
err_fmt!("Unexpected argument -- '%s'", &opts.args[0])
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
abbrs::with_abbrs(|abbrs| {
|
||||
@@ -237,29 +209,24 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
let subcmd = L!("--rename");
|
||||
|
||||
if opts.args.len() != 2 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: Requires exactly two arguments",
|
||||
CMD,
|
||||
subcmd
|
||||
));
|
||||
err_str!("Requires exactly two arguments")
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let old_name = &opts.args[0];
|
||||
let new_name = &opts.args[1];
|
||||
if old_name.is_empty() || new_name.is_empty() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(NAME_CANNOT_BE_EMPTY, CMD, subcmd));
|
||||
err_str!(NAME_CANNOT_BE_EMPTY)
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if contains_whitespace(new_name) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
ABBR_CANNOT_HAVE_SPACES,
|
||||
CMD,
|
||||
subcmd,
|
||||
new_name.as_utfstr()
|
||||
));
|
||||
err_fmt!(ABBR_CANNOT_HAVE_SPACES, new_name.as_utfstr())
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
abbrs::with_abbrs_mut(|abbrs| -> BuiltinResult {
|
||||
@@ -268,12 +235,12 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
.iter()
|
||||
.any(|a| a.name == *old_name && a.commands == opts.commands)
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: No abbreviation named %s with the specified command restrictions",
|
||||
CMD,
|
||||
subcmd,
|
||||
err_fmt!(
|
||||
"No abbreviation named %s with the specified command restrictions",
|
||||
old_name.as_utfstr()
|
||||
));
|
||||
)
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
if abbrs
|
||||
@@ -282,13 +249,13 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
.any(|a| a.name == *new_name && a.commands == opts.commands)
|
||||
{
|
||||
if opts.commands.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: Abbreviation %s already exists, cannot rename %s",
|
||||
CMD,
|
||||
subcmd,
|
||||
err_fmt!(
|
||||
"Abbreviation %s already exists, cannot rename %s",
|
||||
new_name.as_utfstr(),
|
||||
old_name.as_utfstr()
|
||||
));
|
||||
)
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
} else {
|
||||
let style = EscapeStringStyle::Script(Default::default());
|
||||
let mut cmd_list = WString::new();
|
||||
@@ -299,14 +266,14 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
cmd_list.push_utfstr(&escape_string(cmd, style));
|
||||
}
|
||||
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: Abbreviation %s already exists for commands %s, cannot rename %s",
|
||||
CMD,
|
||||
subcmd,
|
||||
err_fmt!(
|
||||
"Abbreviation %s already exists for commands %s, cannot rename %s",
|
||||
new_name.as_utfstr(),
|
||||
cmd_list.as_utfstr(),
|
||||
old_name.as_utfstr()
|
||||
));
|
||||
)
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
}
|
||||
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
@@ -339,28 +306,23 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
let subcmd = L!("--add");
|
||||
|
||||
if opts.args.len() < 2 && opts.function.is_none() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: Requires at least two arguments",
|
||||
CMD,
|
||||
subcmd
|
||||
));
|
||||
err_str!("Requires at least two arguments")
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if opts.args.is_empty() || opts.args[0].is_empty() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(NAME_CANNOT_BE_EMPTY, CMD, subcmd));
|
||||
err_str!(NAME_CANNOT_BE_EMPTY)
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let name = &opts.args[0];
|
||||
if name.chars().any(|c| c.is_whitespace()) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
ABBR_CANNOT_HAVE_SPACES,
|
||||
CMD,
|
||||
subcmd,
|
||||
name.as_utfstr()
|
||||
));
|
||||
err_fmt!(ABBR_CANNOT_HAVE_SPACES, name.as_utfstr())
|
||||
.subcmd(CMD, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -376,21 +338,16 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
let result = builder.build(to_boxed_chars(regex_pattern));
|
||||
|
||||
if let Err(error) = result {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_REGEX_COMPILE,
|
||||
CMD,
|
||||
error.error_message(),
|
||||
));
|
||||
let mut err = err_fmt!(Error::REGEX_COMPILE, error.error_message());
|
||||
if let Some(offset) = error.offset() {
|
||||
streams
|
||||
.err
|
||||
.append(&sprintf!("%s: %s\n", CMD, regex_pattern.as_utfstr()));
|
||||
err.append_assign_to_msg(&sprintf!("\n%s: %s", CMD, regex_pattern.as_utfstr()));
|
||||
// TODO: This is misaligned if `regex_pattern` contains characters which are not
|
||||
// exactly 1 terminal cell wide.
|
||||
// exactly 1 terminal cell wide or not on a single line.
|
||||
let mut marker = " ".repeat(offset.saturating_sub(1));
|
||||
marker.push('^');
|
||||
streams.err.append(&sprintf!("%s: %s\n", CMD, marker));
|
||||
err.append_assign_to_msg(&sprintf!("\n%s: %s", CMD, marker));
|
||||
}
|
||||
err.cmd(CMD).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let anchored = regex_make_anchored(regex_pattern);
|
||||
@@ -409,20 +366,16 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
}
|
||||
|
||||
if opts.function.is_some() && opts.args.len() > 1 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, L!("abbr")));
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS).cmd(CMD).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let replacement = if let Some(ref function) = opts.function {
|
||||
// Abbreviation function names disallow spaces.
|
||||
// This is to prevent accidental usage of e.g. `--function 'string replace'`
|
||||
if !valid_func_name(function) || contains_whitespace(function) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid function name: %s",
|
||||
CMD,
|
||||
function.as_utfstr()
|
||||
));
|
||||
err_fmt!("Invalid function name: %s", function.as_utfstr())
|
||||
.cmd(CMD)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
function.clone()
|
||||
@@ -446,10 +399,9 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
|
||||
}
|
||||
});
|
||||
if !opts.commands.is_empty() && position == Position::Command {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: --command cannot be combined with --position=command",
|
||||
CMD,
|
||||
));
|
||||
err_str!("--command cannot be combined with --position=command")
|
||||
.cmd(CMD)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -564,9 +516,9 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
'c' => opts.commands.push(w.woptarg.map(|x| x.to_owned()).unwrap()),
|
||||
'p' => {
|
||||
if opts.position.is_some() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Cannot specify multiple positions", CMD));
|
||||
err_str!("Cannot specify multiple positions")
|
||||
.cmd(CMD)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if w.woptarg == Some(L!("command")) {
|
||||
@@ -574,37 +526,34 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
} else if w.woptarg == Some(L!("anywhere")) {
|
||||
opts.position = Some(Position::Anywhere);
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid position '%s'",
|
||||
CMD,
|
||||
w.woptarg.unwrap_or_default()
|
||||
));
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"Position must be one of: %s",
|
||||
// Use a single argument here to avoid having to update translations when
|
||||
// the number of options changes.
|
||||
"command, anywhere",
|
||||
));
|
||||
err_fmt!("Invalid position '%s'", w.woptarg.unwrap_or_default())
|
||||
.append_to_msg('\n')
|
||||
.append_to_msg(&wgettext_fmt!(
|
||||
"Position must be one of: %s",
|
||||
// Use a single argument here to avoid having to update translations when
|
||||
// the number of options changes.
|
||||
"command, anywhere",
|
||||
))
|
||||
.cmd(CMD)
|
||||
.finish(streams);
|
||||
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
'r' => {
|
||||
if opts.regex_pattern.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Cannot specify multiple regex patterns",
|
||||
CMD
|
||||
));
|
||||
err_str!("Cannot specify multiple regex patterns")
|
||||
.cmd(CMD)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
opts.regex_pattern = w.woptarg.map(ToOwned::to_owned);
|
||||
}
|
||||
SET_CURSOR_SHORT => {
|
||||
if opts.set_cursor_marker.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Cannot specify multiple set-cursor options",
|
||||
CMD
|
||||
));
|
||||
err_str!("Cannot specify multiple set-cursor options")
|
||||
.cmd(CMD)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
// The default set-cursor indicator is '%'.
|
||||
@@ -624,19 +573,20 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
|
||||
'U' => {
|
||||
// Kept and made ineffective, so we warn.
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: Warning: Option '%s' was removed and is now ignored",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Warning: Option '%s' was removed and is now ignored",
|
||||
argv_read[w.wopt_index - 1]
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
)
|
||||
.cmd(CMD)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
}
|
||||
'h' => {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -660,7 +610,8 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
opts.args.push((*arg).into());
|
||||
}
|
||||
|
||||
if !opts.validate(streams) {
|
||||
if let Some(err) = opts.validate() {
|
||||
err.cmd(cmd).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
use crate::builtins::error::Error;
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack};
|
||||
use crate::exec::exec_subshell;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::wutil::fish_iswalnum;
|
||||
use crate::{err_fmt, err_str};
|
||||
|
||||
const VAR_NAME_PREFIX: &wstr = L!("_flag_");
|
||||
|
||||
localizable_consts!(
|
||||
BUILTIN_ERR_INVALID_OPT_SPEC
|
||||
"%s: Invalid option spec '%s' at char '%c'"
|
||||
"Invalid option spec '%s' at char '%c'"
|
||||
|
||||
MISSING_DOUBLE_HYPHEN_SEPARATOR
|
||||
"%s: Missing -- separator"
|
||||
"Missing -- separator"
|
||||
);
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -149,13 +151,10 @@ fn check_for_mutually_exclusive_flags(
|
||||
if flag1 > flag2 {
|
||||
std::mem::swap(&mut flag1, &mut flag2);
|
||||
}
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
opts.name,
|
||||
flag1,
|
||||
flag2
|
||||
));
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, flag1, flag2)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,11 +170,9 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
|
||||
for raw_xflags in &opts.raw_exclusive_flags {
|
||||
let xflags: Vec<_> = raw_xflags.split(',').collect();
|
||||
if xflags.len() < 2 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: exclusive flag string '%s' is not valid",
|
||||
opts.name,
|
||||
raw_xflags
|
||||
));
|
||||
err_fmt!("exclusive flag string '%s' is not valid", raw_xflags)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -189,11 +186,9 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
|
||||
// It's a long flag we store as its short flag equivalent.
|
||||
exclusive_set.push(*short_equiv);
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: exclusive flag '%s' is not valid",
|
||||
opts.name,
|
||||
flag
|
||||
));
|
||||
err_fmt!("exclusive flag '%s' is not valid", flag)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -218,12 +213,13 @@ fn parse_flag_modifiers<'args>(
|
||||
&& s.char_at(0) != '!'
|
||||
&& s.char_at(0) != '&'
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Implicit int short flag '%c' does not allow modifiers like '%c'",
|
||||
opts.name,
|
||||
err_fmt!(
|
||||
"Implicit int short flag '%c' does not allow modifiers like '%c'",
|
||||
opt_spec.short_flag,
|
||||
s.char_at(0)
|
||||
));
|
||||
)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -253,24 +249,18 @@ fn parse_flag_modifiers<'args>(
|
||||
|
||||
if s.char_at(0) == '!' {
|
||||
if opt_spec.arg_type == ArgType::NoArgument {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_INVALID_OPT_SPEC,
|
||||
opts.name,
|
||||
option_spec,
|
||||
s.char_at(0)
|
||||
));
|
||||
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(0))
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
}
|
||||
s = s.slice_from(1);
|
||||
opt_spec.validation_command = s;
|
||||
// Move cursor to the end so we don't expect a long flag.
|
||||
s = s.slice_from(s.char_count());
|
||||
} else if !s.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_INVALID_OPT_SPEC,
|
||||
opts.name,
|
||||
option_spec,
|
||||
s.char_at(0)
|
||||
));
|
||||
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(0))
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -280,11 +270,9 @@ fn parse_flag_modifiers<'args>(
|
||||
}
|
||||
|
||||
if opts.options.contains_key(&opt_spec.short_flag) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Short flag '%c' already defined",
|
||||
opts.name,
|
||||
opt_spec.short_flag
|
||||
));
|
||||
err_fmt!("Short flag '%c' already defined", opt_spec.short_flag)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -303,7 +291,7 @@ fn parse_option_spec_sep<'args>(
|
||||
) -> bool {
|
||||
localizable_consts! {
|
||||
IMPLICIT_INT_FLAG_ALREADY_DEFINED
|
||||
"%s: Implicit int flag '%c' already defined"
|
||||
"Implicit int flag '%c' already defined"
|
||||
}
|
||||
let mut s = *opt_spec_str;
|
||||
let mut i = 1usize;
|
||||
@@ -316,11 +304,9 @@ fn parse_option_spec_sep<'args>(
|
||||
*counter += 1;
|
||||
}
|
||||
if opts.implicit_int_flag != '\0' {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
IMPLICIT_INT_FLAG_ALREADY_DEFINED,
|
||||
opts.name,
|
||||
opts.implicit_int_flag
|
||||
));
|
||||
err_fmt!(IMPLICIT_INT_FLAG_ALREADY_DEFINED, opts.implicit_int_flag)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
opts.implicit_int_flag = opt_spec.short_flag;
|
||||
@@ -335,34 +321,26 @@ fn parse_option_spec_sep<'args>(
|
||||
opt_spec.short_flag_valid = false;
|
||||
i += 1;
|
||||
if i == s.char_count() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_INVALID_OPT_SPEC,
|
||||
opts.name,
|
||||
option_spec,
|
||||
s.char_at(i - 1)
|
||||
));
|
||||
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(i - 1))
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
i += 1; // the struct is initialized assuming short_flag_valid should be true
|
||||
if i == s.char_count() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_INVALID_OPT_SPEC,
|
||||
opts.name,
|
||||
option_spec,
|
||||
s.char_at(i - 1)
|
||||
));
|
||||
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(i - 1))
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
'#' => {
|
||||
if opts.implicit_int_flag != '\0' {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
IMPLICIT_INT_FLAG_ALREADY_DEFINED,
|
||||
opts.name,
|
||||
opts.implicit_int_flag
|
||||
));
|
||||
err_fmt!(IMPLICIT_INT_FLAG_ALREADY_DEFINED, opts.implicit_int_flag)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
opts.implicit_int_flag = opt_spec.short_flag;
|
||||
@@ -402,21 +380,21 @@ fn parse_option_spec<'args>(
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
if option_spec.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: An option spec must have at least a short or a long flag",
|
||||
opts.name
|
||||
));
|
||||
err_str!("An option spec must have at least a short or a long flag")
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut s = option_spec;
|
||||
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' && !(s.char_at(0) == '/' && s.len() > 1)
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Short flag '%c' invalid, must be alphanum or '#'",
|
||||
opts.name,
|
||||
err_fmt!(
|
||||
"Short flag '%c' invalid, must be alphanum or '#'",
|
||||
s.char_at(0)
|
||||
));
|
||||
)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -439,11 +417,9 @@ fn parse_option_spec<'args>(
|
||||
if long_flag_char_count > 0 {
|
||||
opt_spec.long_flag = s.slice_to(long_flag_char_count);
|
||||
if opts.long_to_short_flag.contains_key(opt_spec.long_flag) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Long flag '%s' already defined",
|
||||
opts.name,
|
||||
opt_spec.long_flag
|
||||
));
|
||||
err_fmt!("Long flag '%s' already defined", opt_spec.long_flag)
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -486,9 +462,9 @@ fn collect_option_specs<'args>(
|
||||
|
||||
loop {
|
||||
if *optind == argc {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(MISSING_DOUBLE_HYPHEN_SEPARATOR, cmd));
|
||||
err_str!(MISSING_DOUBLE_HYPHEN_SEPARATOR)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -508,9 +484,9 @@ fn collect_option_specs<'args>(
|
||||
let counter_max = 0xF8FFu32;
|
||||
|
||||
if counter > counter_max {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Too many long-only options", cmd));
|
||||
err_str!("Too many long-only options")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -540,12 +516,9 @@ fn parse_cmd_opts<'args>(
|
||||
's' => opts.stop_nonopt = true,
|
||||
'i' | 'u' => {
|
||||
if opts.unknown_handling != UnknownHandling::Error {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
"--ignore-unknown",
|
||||
"--move-unknown"
|
||||
));
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, "--ignore-unknown", "--move-unknown")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
opts.unknown_handling = if c == 'i' {
|
||||
@@ -564,11 +537,9 @@ fn parse_cmd_opts<'args>(
|
||||
} else if kind == L!("none") {
|
||||
ArgType::NoArgument
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid --unknown-arguments value '%s'",
|
||||
cmd,
|
||||
kind
|
||||
));
|
||||
err_fmt!("Invalid --unknown-arguments value '%s'", kind)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -581,11 +552,9 @@ fn parse_cmd_opts<'args>(
|
||||
opts.min_args = {
|
||||
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
|
||||
if x < 0 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid --min-args value '%s'",
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
err_fmt!("Invalid --min-args value '%s'", w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
x.try_into().unwrap()
|
||||
@@ -595,11 +564,9 @@ fn parse_cmd_opts<'args>(
|
||||
opts.max_args = {
|
||||
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
|
||||
if x < 0 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid --max-args value '%s'",
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
err_fmt!("Invalid --max-args value '%s'", w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
x.try_into().unwrap()
|
||||
@@ -610,6 +577,7 @@ fn parse_cmd_opts<'args>(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
args[w.wopt_index - 1],
|
||||
/* print_hints */ false,
|
||||
);
|
||||
@@ -648,9 +616,9 @@ fn parse_cmd_opts<'args>(
|
||||
|
||||
if argc == w.wopt_index {
|
||||
// The user didn't specify any option specs.
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(MISSING_DOUBLE_HYPHEN_SEPARATOR, cmd));
|
||||
err_str!(MISSING_DOUBLE_HYPHEN_SEPARATOR)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -932,6 +900,7 @@ fn argparse_parse_flags<'args>(
|
||||
parser,
|
||||
streams,
|
||||
&opts.name,
|
||||
None,
|
||||
args_read[w.wopt_index - 1],
|
||||
false,
|
||||
);
|
||||
@@ -961,11 +930,9 @@ fn argparse_parse_flags<'args>(
|
||||
streams,
|
||||
)?;
|
||||
} else if opts.unknown_handling == UnknownHandling::Error {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_UNKNOWN,
|
||||
opts.name,
|
||||
args_read[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
} else {
|
||||
// The option is unknown, so there's no long opt index it could have used
|
||||
@@ -1016,11 +983,9 @@ fn argparse_parse_flags<'args>(
|
||||
Some(w.argv[w.wopt_index - 1])
|
||||
} else {
|
||||
// the option is at the end of argv, so it has no argument
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_MISSING,
|
||||
opts.name,
|
||||
args_read[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::MISSING_OPT_ARG, args_read[w.wopt_index - 1])
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
} else {
|
||||
@@ -1032,11 +997,9 @@ fn argparse_parse_flags<'args>(
|
||||
&& is_long_flag
|
||||
&& arg_contents.contains('=')
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_UNEXP_ARG,
|
||||
opts.name,
|
||||
args_read[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, args_read[w.wopt_index - 1])
|
||||
.cmd(&opts.name)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -1129,22 +1092,16 @@ fn check_min_max_args_constraints(
|
||||
let cmd = &opts.name;
|
||||
|
||||
if opts.args.len() < opts.min_args {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_MIN_ARG_COUNT1,
|
||||
cmd,
|
||||
opts.min_args,
|
||||
opts.args.len()
|
||||
));
|
||||
err_fmt!(Error::MIN_ARG_COUNT, opts.min_args, opts.args.len())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
if opts.max_args != usize::MAX && opts.args.len() > opts.max_args {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_MAX_ARG_COUNT1,
|
||||
cmd,
|
||||
opts.max_args,
|
||||
opts.args.len()
|
||||
));
|
||||
err_fmt!(Error::MAX_ARG_COUNT, opts.max_args, opts.args.len())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::{collections::HashSet, rc::Rc};
|
||||
|
||||
use crate::proc::Pid;
|
||||
use crate::{builtins::error::Error, err_fmt, err_str, proc::Pid};
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
@@ -17,12 +17,13 @@ fn send_to_bg(
|
||||
let jobs = parser.jobs();
|
||||
if !jobs[job_pos].wants_job_control() {
|
||||
let job = &jobs[job_pos];
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Can't put job %s, '%s' to background because it is not under job control",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Can't put job %s, '%s' to background because it is not under job control",
|
||||
job.job_id().to_wstring(),
|
||||
job.command()
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -66,9 +67,7 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
||||
};
|
||||
|
||||
let Some(job_pos) = job_pos else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NO_SUITABLE_JOBS, cmd));
|
||||
err_str!(Error::NO_SUITABLE_JOBS).cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
|
||||
@@ -101,9 +100,9 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
||||
send_to_bg(parser, streams, cmd, job_pos)?;
|
||||
}
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_COULD_NOT_FIND_JOB, cmd, pid));
|
||||
err_fmt!(Error::COULD_NOT_FIND_JOB, pid)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
//! Implementation of the bind builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::{
|
||||
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::valid_var_name,
|
||||
err_fmt, err_raw, err_str,
|
||||
highlight::highlight_and_colorize,
|
||||
input::{
|
||||
InputMapping, InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings,
|
||||
},
|
||||
key::{
|
||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||
},
|
||||
};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::input::{
|
||||
InputMapping, InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings,
|
||||
};
|
||||
use crate::key::{
|
||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||
};
|
||||
use fish_common::help_section;
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape, escape_string, help_section};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
const DEFAULT_BIND_MODE: &wstr = L!("default");
|
||||
|
||||
const BIND_INSERT: c_int = 0;
|
||||
const BIND_ERASE: c_int = 1;
|
||||
const BIND_KEY_NAMES: c_int = 2;
|
||||
const BIND_FUNCTION_NAMES: c_int = 3;
|
||||
enum BindMode {
|
||||
Insert,
|
||||
Erase,
|
||||
KeyNames,
|
||||
FunctionNames,
|
||||
}
|
||||
|
||||
struct Options {
|
||||
all: bool,
|
||||
@@ -30,7 +35,7 @@ struct Options {
|
||||
user: bool,
|
||||
have_preset: bool,
|
||||
preset: bool,
|
||||
mode: c_int,
|
||||
mode: BindMode,
|
||||
bind_mode: Option<WString>,
|
||||
sets_bind_mode: Option<WString>,
|
||||
color: ColorEnabled,
|
||||
@@ -47,7 +52,7 @@ fn new() -> Options {
|
||||
user: false,
|
||||
have_preset: false,
|
||||
preset: false,
|
||||
mode: BIND_INSERT,
|
||||
mode: BindMode::Insert,
|
||||
bind_mode: None,
|
||||
sets_bind_mode: None,
|
||||
color: ColorEnabled::default(),
|
||||
@@ -261,7 +266,7 @@ fn compute_seq(&self, streams: &mut IoStreams, seq: &wstr) -> Option<Vec<Key>> {
|
||||
match parse_keys(seq) {
|
||||
Ok(keys) => Some(keys),
|
||||
Err(err) => {
|
||||
streams.err.append(&sprintf!("bind: %s\n", err));
|
||||
err_raw!(err).cmd(L!("bind")).finish(streams);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -312,12 +317,9 @@ fn insert(
|
||||
} else {
|
||||
// Inserting both on the other hand makes no sense.
|
||||
if self.opts.have_preset && self.opts.have_user {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
"--preset",
|
||||
"--user"
|
||||
));
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, "--preset", "--user")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -353,17 +355,13 @@ fn insert(
|
||||
);
|
||||
if !self.opts.silent {
|
||||
if seq.len() == 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: No binding found for key '%s'",
|
||||
cmd,
|
||||
seq[0]
|
||||
));
|
||||
err_fmt!("No binding found for key '%s'", seq[0])
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: No binding found for key sequence '%s'",
|
||||
cmd,
|
||||
eseq
|
||||
));
|
||||
err_fmt!("No binding found for key sequence '%s'", eseq)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -433,12 +431,13 @@ fn parse_cmd_opts(
|
||||
|
||||
let check_mode_name = |streams: &mut IoStreams, mode_name: &wstr| -> Result<(), ErrorCode> {
|
||||
if !valid_var_name(mode_name) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_BIND_MODE,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::BIND_MODE,
|
||||
mode_name,
|
||||
help_section!("language#shell-variable-and-function-names")
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Ok(())
|
||||
@@ -448,17 +447,18 @@ fn parse_cmd_opts(
|
||||
while let Some(c) = w.next_opt() {
|
||||
match c {
|
||||
'a' => opts.all = true,
|
||||
'e' => opts.mode = BIND_ERASE,
|
||||
'f' => opts.mode = BIND_FUNCTION_NAMES,
|
||||
'e' => opts.mode = BindMode::Erase,
|
||||
'f' => opts.mode = BindMode::FunctionNames,
|
||||
'h' => opts.print_help = true,
|
||||
'k' => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`",
|
||||
cmd,
|
||||
));
|
||||
err_str!(
|
||||
"the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`"
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'K' => opts.mode = BIND_KEY_NAMES,
|
||||
'K' => opts.mode = BindMode::KeyNames,
|
||||
'L' => {
|
||||
opts.list_modes = true;
|
||||
return Ok(SUCCESS);
|
||||
@@ -483,7 +483,7 @@ fn parse_cmd_opts(
|
||||
opts.user = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -534,7 +534,7 @@ pub fn bind(
|
||||
}
|
||||
|
||||
match self.opts.mode {
|
||||
BIND_ERASE => {
|
||||
BindMode::Erase => {
|
||||
// If we get both, we erase both.
|
||||
if self.opts.user
|
||||
&& self.erase(
|
||||
@@ -557,19 +557,13 @@ pub fn bind(
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
BIND_INSERT => {
|
||||
BindMode::Insert => {
|
||||
if self.insert(optind, argv, parser, streams) {
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
BIND_KEY_NAMES => self.key_names(streams),
|
||||
BIND_FUNCTION_NAMES => self.function_names(streams),
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Invalid state", cmd));
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
BindMode::KeyNames => self.key_names(streams),
|
||||
BindMode::FunctionNames => self.function_names(streams),
|
||||
}
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::err_str;
|
||||
|
||||
// Implementation of the block builtin.
|
||||
use super::prelude::*;
|
||||
|
||||
@@ -51,7 +53,7 @@ fn parse_options(
|
||||
opts.erase = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -84,17 +86,14 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
|
||||
|
||||
if opts.erase {
|
||||
if opts.scope != Scope::Unset {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Can not specify scope when removing block",
|
||||
cmd
|
||||
));
|
||||
err_str!("Can not specify scope when removing block")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if parser.global_event_blocks.load(Ordering::Relaxed) == 0 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: No blocks defined", cmd));
|
||||
err_str!("No blocks defined").cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
parser.global_event_blocks.fetch_sub(1, Ordering::Relaxed);
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::parser::{Block, BlockType};
|
||||
use crate::reader::reader_read;
|
||||
use crate::{err_fmt, err_str};
|
||||
use libc::STDIN_FILENO;
|
||||
|
||||
/// Implementation of the builtin breakpoint command, used to launch the interactive debugger.
|
||||
pub fn breakpoint(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
||||
let cmd = argv[0];
|
||||
if argv.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT1,
|
||||
cmd,
|
||||
0,
|
||||
argv.len() - 1
|
||||
));
|
||||
err_fmt!(Error::UNEXP_ARG_COUNT, 0, argv.len() - 1)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -28,10 +27,9 @@ pub fn breakpoint(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr])
|
||||
.block_at_index(1)
|
||||
.is_none_or(|b| b.typ() == BlockType::breakpoint)
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Command not valid at an interactive prompt",
|
||||
cmd,
|
||||
));
|
||||
err_str!("Command not valid at an interactive prompt")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_ILLEGAL_CMD);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::{builtins::error::Error, err_fmt};
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -29,7 +31,14 @@ pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
argv[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -53,11 +62,12 @@ pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
}
|
||||
|
||||
if opts.query && opts.list_names {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--query and --names are mutually exclusive")
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
use super::prelude::*;
|
||||
use crate::{
|
||||
env::{EnvMode, Environment as _},
|
||||
err_fmt, err_raw, err_str,
|
||||
fds::{BEST_O_SEARCH, wopen_dir},
|
||||
parser::ParserEnvSetMode,
|
||||
path::path_apply_cdpath,
|
||||
wutil::{normalize_path, wreadlink},
|
||||
};
|
||||
use errno::Errno;
|
||||
use fish_util::perror;
|
||||
use libc::{EACCES, ELOOP, ENOENT, ENOTDIR, EPERM};
|
||||
use nix::unistd::fchdir;
|
||||
use std::sync::Arc;
|
||||
@@ -19,7 +19,7 @@
|
||||
pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
|
||||
localizable_consts! {
|
||||
DIR_DOES_NOT_EXIST
|
||||
"%s: The directory '%s' does not exist"
|
||||
"The directory '%s' does not exist"
|
||||
}
|
||||
|
||||
let Some(&cmd) = args.first() else {
|
||||
@@ -45,9 +45,9 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
||||
&tmpstr
|
||||
}
|
||||
None => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Could not find home directory", cmd));
|
||||
err_str!("Could not find home directory")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -55,31 +55,21 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
||||
|
||||
// Stop `cd ""` from crashing
|
||||
if dir_in.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Empty directory '%s' does not exist",
|
||||
cmd,
|
||||
dir_in
|
||||
));
|
||||
let mut err = err_fmt!("Empty directory '%s' does not exist", dir_in).cmd(cmd);
|
||||
if !parser.is_interactive() {
|
||||
streams.err.append(&parser.current_line());
|
||||
err = err.stacktrace(parser);
|
||||
}
|
||||
err.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
let pwd = vars.get_pwd_slash();
|
||||
|
||||
let dirs = path_apply_cdpath(dir_in, &pwd, vars);
|
||||
if dirs.is_empty() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(DIR_DOES_NOT_EXIST, cmd, dir_in));
|
||||
|
||||
if !parser.is_interactive() {
|
||||
streams.err.append(&parser.current_line());
|
||||
}
|
||||
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
assert!(
|
||||
!dirs.is_empty(),
|
||||
"dirs should always contains a least an abs path, or a rel path, or '<PWD>/...'"
|
||||
);
|
||||
|
||||
let mut best_errno = 0;
|
||||
let mut broken_symlink = WString::new();
|
||||
@@ -142,44 +132,30 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
if best_errno == ENOTDIR {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: '%s' is not a directory", cmd, dir_in));
|
||||
let mut err = if best_errno == ENOTDIR {
|
||||
err_fmt!("'%s' is not a directory", dir_in)
|
||||
} else if !broken_symlink.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: '%s' is a broken symbolic link to '%s'",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"'%s' is a broken symbolic link to '%s'",
|
||||
broken_symlink,
|
||||
broken_symlink_target
|
||||
));
|
||||
)
|
||||
} else if best_errno == ELOOP {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Too many levels of symbolic links: '%s'",
|
||||
cmd,
|
||||
dir_in
|
||||
));
|
||||
err_fmt!("Too many levels of symbolic links: '%s'", dir_in)
|
||||
} else if best_errno == ENOENT {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(DIR_DOES_NOT_EXIST, cmd, dir_in));
|
||||
err_fmt!(DIR_DOES_NOT_EXIST, dir_in)
|
||||
} else if best_errno == EACCES || best_errno == EPERM {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Permission denied: '%s'", cmd, dir_in));
|
||||
err_fmt!("Permission denied: '%s'", dir_in)
|
||||
} else {
|
||||
errno::set_errno(Errno(best_errno));
|
||||
perror("cd");
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Unknown error trying to locate directory '%s'",
|
||||
cmd,
|
||||
dir_in
|
||||
));
|
||||
}
|
||||
err_raw!(builtin_strerror()).cmd(L!("cd")).finish(streams);
|
||||
err_fmt!("Unknown error trying to locate directory '%s'", dir_in)
|
||||
};
|
||||
|
||||
if !parser.is_interactive() {
|
||||
streams.err.append(&parser.current_line());
|
||||
err = err.stacktrace(parser);
|
||||
}
|
||||
err.cmd(cmd).finish(streams);
|
||||
|
||||
Err(STATUS_CMD_ERROR)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,14 @@ pub fn r#command(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
argv[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
use super::prelude::*;
|
||||
use super::read::TokenOutputMode;
|
||||
use crate::ast::{self, Kind, Leaf as _};
|
||||
use crate::common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use crate::complete::Completion;
|
||||
use crate::expand::{ExpandFlags, ExpandResultCode, expand_string};
|
||||
use crate::input::input_function_get_code;
|
||||
use crate::input_common::{CharEvent, ReadlineCmd};
|
||||
use crate::operation_context::{OperationContext, no_cancel};
|
||||
use crate::parse_constants::ParseTreeFlags;
|
||||
use crate::parse_util::{
|
||||
detect_parse_errors, get_job_extent, get_offset_from_line, get_process_extent,
|
||||
get_token_extent, lineno,
|
||||
use crate::{
|
||||
ast::{self, Kind, Leaf as _},
|
||||
builtins::error::Error,
|
||||
complete::Completion,
|
||||
err_fmt, err_str,
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_string},
|
||||
input::input_function_get_code,
|
||||
input_common::{CharEvent, ReadlineCmd},
|
||||
operation_context::{OperationContext, no_cancel},
|
||||
parse_constants::ParseTreeFlags,
|
||||
parse_util::{
|
||||
detect_parse_errors, get_job_extent, get_offset_from_line, get_process_extent,
|
||||
get_token_extent, lineno,
|
||||
},
|
||||
prelude::*,
|
||||
proc::is_interactive_session,
|
||||
reader::{
|
||||
JumpDirection, JumpPrecision, commandline_get_state, commandline_set_buffer,
|
||||
commandline_set_search_field, reader_execute_readline_cmd, reader_jump,
|
||||
reader_showing_suggestion,
|
||||
},
|
||||
tokenizer::{TOK_ACCEPT_UNFINISHED, TokenType, Tokenizer},
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{
|
||||
JumpDirection, JumpPrecision, commandline_get_state, commandline_set_buffer,
|
||||
commandline_set_search_field, reader_execute_readline_cmd, reader_jump,
|
||||
reader_showing_suggestion,
|
||||
};
|
||||
use crate::tokenizer::{TOK_ACCEPT_UNFINISHED, TokenType, Tokenizer};
|
||||
use fish_common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::join_strings;
|
||||
use std::ops::Range;
|
||||
|
||||
@@ -320,12 +324,13 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
'f' => function_mode = true,
|
||||
'x' | '\x02' | 'o' => {
|
||||
if token_mode.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--tokens options are mutually exclusive")
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
token_mode = Some(match c {
|
||||
@@ -372,7 +377,14 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
w.argv[w.wopt_index - 1],
|
||||
true,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -424,23 +436,25 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
|| selection_start_mode
|
||||
|| selection_end_mode
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if positional_args == 0 {
|
||||
builtin_missing_argument(parser, streams, cmd, L!("--function"), true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, L!("--function"), true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
type RL = ReadlineCmd;
|
||||
for arg in &w.argv[w.wopt_index..] {
|
||||
let Some(cmd) = input_function_get_code(arg) else {
|
||||
streams
|
||||
.err
|
||||
.append(&wgettext_fmt!("%s: Unknown input function '%s'", cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!("Unknown input function '%s'", arg)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
// Don't enqueue a repaint if we're currently in the middle of one,
|
||||
@@ -467,20 +481,20 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
|
||||
// Check for invalid switch combinations.
|
||||
if (selection_start_mode || selection_end_mode) && positional_args != 0 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if (search_mode || line_mode || column_mode || cursor_mode || paging_mode)
|
||||
&& positional_args > 1
|
||||
{
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -489,24 +503,29 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
// Special case - we allow to get/set cursor position relative to the process/job/token.
|
||||
&& ((buffer_part.is_none() && !search_field_mode) || !cursor_mode)
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if (token_mode.is_some() || cut_at_cursor) && positional_args != 0 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
"--cut-at-cursor and token options can not be used when setting the commandline"
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if search_field_mode && (buffer_part.is_some() || token_mode.is_some()) {
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -522,26 +541,20 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
|
||||
if append_mode == AppendMode::InsertSmart {
|
||||
if search_field_mode {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
"--insert-smart",
|
||||
"--search-field"
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, "--insert-smart", "--search-field")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
match buffer_part {
|
||||
TextScope::String | TextScope::Job | TextScope::Process => (),
|
||||
TextScope::Token => {
|
||||
// To-do: we can support it in command position.
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
"--insert-smart",
|
||||
"--current-token"
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, "--insert-smart", "--current-token")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -552,19 +565,19 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
let arg = w.argv[w.wopt_index];
|
||||
let new_coord = match fish_wcstol(arg) {
|
||||
Err(_) => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::NOT_NUMBER, arg)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
0
|
||||
}
|
||||
Ok(num) => num - 1,
|
||||
};
|
||||
let Ok(new_coord) = usize::try_from(new_coord) else {
|
||||
streams
|
||||
.err
|
||||
.append(&wgettext_fmt!("%s: line/column index starts at 1", cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("line/column index starts at 1")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
|
||||
@@ -572,10 +585,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
let Some(offset) =
|
||||
get_offset_from_line(&rstate.text, i32::try_from(new_coord).unwrap())
|
||||
else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: there is no line %s", cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!("there is no line %s", arg)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
offset
|
||||
@@ -587,12 +600,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
let next_line_offset =
|
||||
get_offset_from_line(&rstate.text, line_index + 1).unwrap_or(rstate.text.len());
|
||||
if line_offset + new_coord > next_line_offset {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: column %s exceeds line length",
|
||||
cmd,
|
||||
arg
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!("column %s exceeds line length", arg)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
line_offset + new_coord
|
||||
@@ -675,11 +686,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
current_cursor_pos = current_buffer.len();
|
||||
} else if parser.libdata().transient_commandline.is_some() {
|
||||
if cursor_mode && positional_args != 0 {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: setting cursor while evaluating 'complete --arguments' is not yet supported",
|
||||
cmd
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("setting cursor while evaluating 'complete --arguments' is not yet supported")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
transient = parser.libdata().transient_commandline.clone().unwrap();
|
||||
@@ -690,11 +700,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
current_cursor_pos = rstate.cursor_pos;
|
||||
} else {
|
||||
// There is no command line because we are not interactive.
|
||||
streams.err.append(cmd);
|
||||
streams
|
||||
.err
|
||||
.append(L!(": Can not set commandline in non-interactive mode\n"));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("Can not set commandline in non-interactive mode")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -742,10 +751,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
let arg = w.argv[w.wopt_index];
|
||||
let new_pos = match fish_wcstol(arg) {
|
||||
Err(_) => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::NOT_NUMBER, arg)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
0
|
||||
}
|
||||
Ok(num) => num,
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::ParseErrorList;
|
||||
use crate::parse_util::detect_errors_in_argument_list;
|
||||
use crate::parse_util::{detect_parse_errors, get_token_extent};
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{commandline_get_state, completion_apply_to_command_line};
|
||||
use crate::{
|
||||
common::bytes2wcstring,
|
||||
builtins::error::Error,
|
||||
complete::{
|
||||
CompleteFlags, CompleteOptionType, CompletionMode, complete_add, complete_print,
|
||||
complete_remove, complete_remove_all,
|
||||
CompleteFlags, CompleteOptionType, CompletionMode, CompletionRequestOptions, complete_add,
|
||||
complete_add_wrapper, complete_print, complete_remove, complete_remove_all,
|
||||
complete_remove_wrapper,
|
||||
},
|
||||
err_fmt, err_raw, err_str,
|
||||
highlight::highlight_and_colorize,
|
||||
operation_context::OperationContext,
|
||||
parse_constants::ParseErrorList,
|
||||
parse_util::{detect_errors_in_argument_list, detect_parse_errors, get_token_extent},
|
||||
proc::is_interactive_session,
|
||||
reader::{commandline_get_state, completion_apply_to_command_line},
|
||||
};
|
||||
use fish_common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::string_suffixes_string;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
// builtin_complete_* are a set of rather silly looping functions that make sure that all the proper
|
||||
// combinations of complete_add or complete_remove get called. This is needed since complete allows
|
||||
@@ -245,7 +246,7 @@ fn builtin_complete_print(
|
||||
pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
||||
localizable_consts! {
|
||||
OPTION_REQUIRES_NON_EMPTY_STRING
|
||||
"%s: %s requires a non-empty string"
|
||||
"%s requires a non-empty string"
|
||||
}
|
||||
|
||||
let cmd = argv[0];
|
||||
@@ -326,11 +327,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
cmd_to_complete.push(tmp);
|
||||
}
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid token '%s'",
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
err_fmt!("Invalid token '%s'", w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -347,11 +346,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
let arg = w.woptarg.unwrap();
|
||||
short_opt.extend(arg.chars());
|
||||
if arg.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTION_REQUIRES_NON_EMPTY_STRING,
|
||||
cmd,
|
||||
"-s",
|
||||
));
|
||||
err_fmt!(OPTION_REQUIRES_NON_EMPTY_STRING, "-s",)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -359,11 +356,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
let arg = w.woptarg.unwrap();
|
||||
gnu_opt.push(arg);
|
||||
if arg.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTION_REQUIRES_NON_EMPTY_STRING,
|
||||
cmd,
|
||||
"-l",
|
||||
));
|
||||
err_fmt!(OPTION_REQUIRES_NON_EMPTY_STRING, "-l",)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -371,11 +366,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
let arg = w.woptarg.unwrap();
|
||||
old_opt.push(arg);
|
||||
if arg.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTION_REQUIRES_NON_EMPTY_STRING,
|
||||
cmd,
|
||||
"-o",
|
||||
));
|
||||
err_fmt!(OPTION_REQUIRES_NON_EMPTY_STRING, "-o",)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +397,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -424,19 +417,21 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
|
||||
if result_mode.no_files && result_mode.force_files {
|
||||
if !have_x {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
"complete",
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
"'--no-files' and '--force-files'"
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
} else {
|
||||
// The reason for us not wanting files is `-x`,
|
||||
// which is short for `-rf`.
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
"complete",
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
"'--exclusive' and '--force-files'"
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
@@ -450,10 +445,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
// Or use one left-over arg as the command to complete
|
||||
cmd_to_complete.push(argv[argc - 1].to_owned());
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -461,14 +456,16 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
for condition_string in &condition {
|
||||
let mut errors = ParseErrorList::new();
|
||||
if detect_parse_errors(condition_string, Some(&mut errors), false).is_err() {
|
||||
let prefix = WString::from(L!("-n '")) + &condition_string[..] + L!("'");
|
||||
for error in errors {
|
||||
let prefix = cmd.to_owned() + L!(": -n '") + &condition_string[..] + L!("'");
|
||||
streams.err.appendln(&error.describe_with_prefix(
|
||||
err_raw!(&error.describe_with_prefix(
|
||||
condition_string,
|
||||
&prefix,
|
||||
parser.is_interactive(),
|
||||
false,
|
||||
));
|
||||
))
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
@@ -476,10 +473,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
|
||||
if !comp.is_empty() {
|
||||
if let Err(err_text) = detect_errors_in_argument_list(&comp, cmd) {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: %s: contains a syntax error", cmd, comp));
|
||||
streams.err.appendln(&err_text);
|
||||
let mut err = err_fmt!("%s: contains a syntax error", comp);
|
||||
err.append_assign_to_msg('\n');
|
||||
err.append_assign_to_msg(&err_text);
|
||||
err.cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -491,10 +488,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
|
||||
// No argument given, try to use the current commandline.
|
||||
let commandline_state = commandline_get_state(true);
|
||||
if !parser.interactive_initialized.load() && !is_interactive_session() {
|
||||
streams.err.append(cmd);
|
||||
streams
|
||||
.err
|
||||
.append(L!(": Can not get commandline in non-interactive mode\n"));
|
||||
err_str!("Can not get commandline in non-interactive mode")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
commandline_state.text
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::err_str;
|
||||
|
||||
// Implementation of the contains builtin.
|
||||
use super::prelude::*;
|
||||
|
||||
@@ -28,7 +30,7 @@ fn parse_options(
|
||||
'h' => opts.print_help = true,
|
||||
'i' => opts.print_index = true,
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -71,9 +73,7 @@ pub fn contains(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
}
|
||||
}
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Key not specified", cmd));
|
||||
err_str!("Key not specified").cmd(cmd).finish(streams);
|
||||
}
|
||||
|
||||
Err(STATUS_CMD_ERROR)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Implementation of the disown builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::builtins::shared::HelpOnlyCmdOpts;
|
||||
use crate::io::IoStreams;
|
||||
use crate::parser::Parser;
|
||||
use crate::proc::{Job, add_disowned_job};
|
||||
use crate::{builtins::shared::HelpOnlyCmdOpts, localization::wgettext_fmt};
|
||||
use crate::{err_fmt, err_str};
|
||||
use fish_widestring::wstr;
|
||||
use nix::sys::signal::{Signal, killpg};
|
||||
|
||||
@@ -21,12 +23,13 @@ fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) {
|
||||
if let Some(pgid) = pgid {
|
||||
let _ = killpg(pgid.as_nix_pid(), Some(Signal::SIGCONT));
|
||||
}
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: job %d ('%s') was stopped and has been signalled to continue.",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"job %d ('%s') was stopped and has been signalled to continue.",
|
||||
j.job_id(),
|
||||
j.command()
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
|
||||
// We cannot directly remove the job from the jobs() list as `disown` might be called
|
||||
@@ -64,9 +67,7 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
disown_job(cmd, streams, &job);
|
||||
retval = Ok(SUCCESS);
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NO_SUITABLE_JOBS, cmd));
|
||||
err_str!(Error::NO_SUITABLE_JOBS).cmd(cmd).finish(streams);
|
||||
retval = Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
} else {
|
||||
@@ -87,9 +88,9 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
};
|
||||
parser.job_get_from_pid(pid).or_else(|| {
|
||||
// Valid identifier but no such job
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_COULD_NOT_FIND_JOB, cmd, pid));
|
||||
err_fmt!(Error::COULD_NOT_FIND_JOB, pid)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
None
|
||||
})
|
||||
})
|
||||
|
||||
@@ -45,7 +45,7 @@ fn parse_options(
|
||||
's' => opts.print_spaces = false,
|
||||
'E' => opts.interpret_special_chars = false,
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::prelude::*;
|
||||
use crate::event;
|
||||
use crate::{err_str, event};
|
||||
|
||||
pub fn emit(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
|
||||
let Some(&cmd) = argv.first() else {
|
||||
@@ -14,9 +14,7 @@ pub fn emit(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
}
|
||||
|
||||
let Some(event_name) = argv.get(opts.optind) else {
|
||||
streams
|
||||
.err
|
||||
.append(&sprintf!(L!("%s: expected event name\n"), cmd));
|
||||
err_str!("expected event name").cmd(cmd).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
//! Implementation of the fg builtin.
|
||||
|
||||
use crate::builtins::error::Error;
|
||||
use crate::fds::make_fd_blocking;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::reader::{reader_save_screen_state, reader_write_title};
|
||||
use crate::tokenizer::tok_command;
|
||||
use crate::{env::EnvMode, tty_handoff::TtyHandoff};
|
||||
use crate::{err_fmt, err_str};
|
||||
use fish_util::perror;
|
||||
use libc::STDIN_FILENO;
|
||||
use nix::sys::termios::{self, tcsetattr};
|
||||
@@ -38,9 +40,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
&& ((job.is_stopped() || !job.is_foreground()) && job.wants_job_control())
|
||||
}) {
|
||||
None => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NO_SUITABLE_JOBS, cmd));
|
||||
err_str!(Error::NO_SUITABLE_JOBS).cmd(cmd).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Some((pos, j)) => {
|
||||
@@ -57,17 +57,13 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
found_job = parser.job_get_from_pid(pid).is_some();
|
||||
}
|
||||
|
||||
if found_job {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Ambiguous job", cmd));
|
||||
let err = if found_job {
|
||||
err_str!("Ambiguous job")
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: '%s' is not a job", cmd, argv[optind]));
|
||||
}
|
||||
err_fmt!("'%s' is not a job", argv[optind])
|
||||
};
|
||||
err.cmd(cmd).full_trailer(parser).finish(streams);
|
||||
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
job_pos = None;
|
||||
job = None;
|
||||
} else {
|
||||
@@ -77,21 +73,21 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
if j.as_ref()
|
||||
.is_none_or(|(_pos, j)| !j.is_constructed() || j.is_completed())
|
||||
{
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: No suitable job: %d", cmd, pid));
|
||||
err_fmt!("No suitable job: %d", pid)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
job_pos = None;
|
||||
job = None;
|
||||
} else {
|
||||
let (pos, j) = j.unwrap();
|
||||
job_pos = Some(pos);
|
||||
job = if !j.wants_job_control() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Can't put job %d, '%s' to foreground because it is not under job control",
|
||||
cmd,
|
||||
err_fmt!("Can't put job %d, '%s' to foreground because it is not under job control",
|
||||
pid,
|
||||
j.command()
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
None
|
||||
} else {
|
||||
Some(j)
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
//! The fish_indent program.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write as _};
|
||||
use std::os::unix::ffi::OsStrExt as _;
|
||||
|
||||
use crate::locale::set_libc_locales;
|
||||
use crate::panic::panic_handler;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::ast::{
|
||||
self, AsNode as _, Ast, Kind, Leaf as _, Node, NodeVisitor, SourceRangeList, Traversal,
|
||||
use crate::{
|
||||
ast::{self, AsNode as _, Ast, Kind, Leaf as _, Node, NodeVisitor, SourceRangeList, Traversal},
|
||||
builtins::error::Error,
|
||||
common::{PROGRAM_NAME, get_program_name},
|
||||
env::{EnvStack, env_init, environment::Environment as _},
|
||||
err_fmt, err_str,
|
||||
global_safety::RelaxedAtomicBool,
|
||||
highlight::{HighlightRole, HighlightSpec, colorize, highlight_shell},
|
||||
locale::set_libc_locales,
|
||||
operation_context::OperationContext,
|
||||
panic::panic_handler,
|
||||
parse_constants::{ParseTokenType, ParseTreeFlags, SourceRange},
|
||||
parse_util::{SPACES_PER_INDENT, apply_indents, compute_indents},
|
||||
prelude::*,
|
||||
print_help::print_help,
|
||||
threads,
|
||||
tokenizer::{TOK_SHOW_BLANK_LINES, TOK_SHOW_COMMENTS, TokenType, Tokenizer},
|
||||
topic_monitor::topic_monitor_init,
|
||||
wutil::fish_iswalnum,
|
||||
};
|
||||
use crate::common::{
|
||||
PROGRAM_NAME, ReadExt as _, UnescapeFlags, UnescapeStringStyle, bytes2wcstring,
|
||||
get_program_name, osstr2wcstring, unescape_string,
|
||||
};
|
||||
use crate::env::EnvStack;
|
||||
use crate::env::env_init;
|
||||
use crate::env::environment::Environment as _;
|
||||
use crate::expand::INTERNAL_SEPARATOR;
|
||||
use crate::future_feature_flags;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::highlight::{HighlightRole, HighlightSpec, colorize, highlight_shell};
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::{ParseTokenType, ParseTreeFlags, SourceRange};
|
||||
use crate::parse_util::{SPACES_PER_INDENT, apply_indents, compute_indents};
|
||||
use crate::prelude::*;
|
||||
use crate::print_help::print_help;
|
||||
use crate::threads;
|
||||
use crate::tokenizer::{TOK_SHOW_BLANK_LINES, TOK_SHOW_COMMENTS, TokenType, Tokenizer};
|
||||
use crate::topic_monitor::topic_monitor_init;
|
||||
use crate::wutil::fish_iswalnum;
|
||||
use assert_matches::assert_matches;
|
||||
use fish_wcstringutil::{count_preceding_backslashes, wcs2bytes};
|
||||
use fish_common::{ReadExt as _, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::count_preceding_backslashes;
|
||||
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
|
||||
use std::fmt::Write as _;
|
||||
use fish_widestring::{INTERNAL_SEPARATOR, bytes2wcstring, osstr2wcstring, wcs2bytes};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fmt::Write as _,
|
||||
fs,
|
||||
io::{Read, Write as _},
|
||||
os::unix::ffi::OsStrExt as _,
|
||||
};
|
||||
|
||||
/// Note: this got somewhat more complicated after introducing the new AST, because that AST no
|
||||
/// longer encodes detailed lexical information (e.g. every newline). This feels more complex
|
||||
@@ -945,7 +942,7 @@ fn throwing_main() -> i32 {
|
||||
// Only set these here so you can't set them via the builtin.
|
||||
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
||||
for s in features_var.as_list() {
|
||||
future_feature_flags::set_from_string(s.as_utfstr());
|
||||
fish_feature_flags::set_from_string(s.as_utfstr());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,19 +1023,15 @@ enum OutputType {
|
||||
'\x03' => output_type = OutputType::PygmentsCsv,
|
||||
'c' => output_type = OutputType::Check,
|
||||
';' => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_UNEXP_ARG,
|
||||
"fish_indent",
|
||||
w.argv[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, w.argv[w.wopt_index - 1])
|
||||
.cmd(L!("fish_indent"))
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
'?' => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_UNKNOWN,
|
||||
"fish_indent",
|
||||
w.argv[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::UNKNOWN_OPT, w.argv[w.wopt_index - 1])
|
||||
.cmd(L!("fish_indent"))
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
_ => panic!(),
|
||||
@@ -1054,18 +1047,14 @@ enum OutputType {
|
||||
while i < args.len() || (args.is_empty() && i == 0) {
|
||||
if args.is_empty() && i == 0 {
|
||||
if output_type == OutputType::File {
|
||||
streams.err.append(&sprintf!(
|
||||
"%s\n\n $ %s -w foo.fish\n",
|
||||
wgettext!("Expected file path to read/write for -w:"),
|
||||
get_program_name()
|
||||
));
|
||||
err_str!("Expected file path to read/write for -w:")
|
||||
.append_to_msg(&sprintf!("\n\n $ %s -w foo.fish\n", get_program_name()))
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
let Some(stdin_file) = streams.stdin_file.as_mut() else {
|
||||
let cmd = "fish_indent";
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_STDIN_CLOSED, cmd));
|
||||
let cmd = L!("fish_indent");
|
||||
err_str!(Error::STDIN_CLOSED).cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
let mut buf = vec![];
|
||||
@@ -1091,11 +1080,7 @@ enum OutputType {
|
||||
output_location = arg;
|
||||
}
|
||||
Err(err) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"Opening \"%s\" failed: %s",
|
||||
arg,
|
||||
err.to_string()
|
||||
));
|
||||
err_fmt!("Opening \"%s\" failed: %s", arg, err.to_string()).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -1172,11 +1157,12 @@ enum OutputType {
|
||||
let _ = file.write_all(&wcs2bytes(&output_wtext));
|
||||
}
|
||||
Err(err) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
err_fmt!(
|
||||
"Opening \"%s\" failed: %s",
|
||||
output_location,
|
||||
err.to_string()
|
||||
));
|
||||
)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
use libc::{STDIN_FILENO, VEOF, VINTR};
|
||||
|
||||
use crate::{
|
||||
builtins::shared::BUILTIN_ERR_UNKNOWN,
|
||||
common::{PROGRAM_NAME, get_program_name, osstr2wcstring, shell_modes},
|
||||
builtins::error::Error,
|
||||
common::{PROGRAM_NAME, get_program_name, shell_modes},
|
||||
env::{EnvStack, Environment as _, env_init},
|
||||
future_feature_flags,
|
||||
err_fmt, err_str,
|
||||
input_common::{
|
||||
CharEvent, ImplicitEvent, InputEventQueue, InputEventQueuer as _, KeyEvent,
|
||||
QueryResultEvent, match_key_event_to_key,
|
||||
@@ -27,13 +27,15 @@
|
||||
print_help::print_help,
|
||||
proc::set_interactive_session,
|
||||
reader::{
|
||||
check_exit_loop_maybe_warning, reader_init, reader_sighup, set_shell_modes, terminal_init,
|
||||
check_exit_loop_maybe_warning, reader_init, safe_reader_set_exit_signal, set_shell_modes,
|
||||
terminal_init,
|
||||
},
|
||||
threads,
|
||||
topic_monitor::topic_monitor_init,
|
||||
tty_handoff::TtyHandoff,
|
||||
};
|
||||
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
|
||||
use fish_widestring::osstr2wcstring;
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
@@ -96,7 +98,7 @@ fn process_input(
|
||||
use QueryResultEvent::*;
|
||||
let kevt = match input_queue.readch() {
|
||||
CharEvent::Implicit(ImplicitEvent::Eof) => {
|
||||
reader_sighup();
|
||||
safe_reader_set_exit_signal(libc::SIGHUP);
|
||||
continue;
|
||||
}
|
||||
CharEvent::Key(kevt) => kevt,
|
||||
@@ -212,19 +214,15 @@ fn parse_flags(
|
||||
*verbose = true;
|
||||
}
|
||||
';' => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_UNEXP_ARG,
|
||||
"fish_key_reader",
|
||||
w.argv[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, w.argv[w.wopt_index - 1])
|
||||
.cmd(L!("fish_key_reader"))
|
||||
.finish(streams);
|
||||
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
|
||||
}
|
||||
'?' => {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
BUILTIN_ERR_UNKNOWN,
|
||||
"fish_key_reader",
|
||||
w.argv[w.wopt_index - 1]
|
||||
));
|
||||
err_fmt!(Error::UNKNOWN_OPT, w.argv[w.wopt_index - 1])
|
||||
.cmd(L!("fish_key_reader"))
|
||||
.finish(streams);
|
||||
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
|
||||
}
|
||||
_ => panic!(),
|
||||
@@ -233,9 +231,7 @@ fn parse_flags(
|
||||
|
||||
let argc = args.len() - w.wopt_index;
|
||||
if argc != 0 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("Expected no arguments, got %d", argc));
|
||||
err_fmt!("Expected no arguments, got %d", argc).finish(streams);
|
||||
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
|
||||
}
|
||||
|
||||
@@ -262,7 +258,7 @@ pub fn fish_key_reader(
|
||||
}
|
||||
|
||||
if streams.stdin_fd() < 0 || !isatty(streams.stdin_fd()) {
|
||||
streams.err.appendln("Stdin must be attached to a tty.");
|
||||
err_str!("Stdin must be attached to a tty.").finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -295,29 +291,27 @@ fn throwing_main() -> i32 {
|
||||
reader_init(false);
|
||||
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
|
||||
for s in features_var.as_list() {
|
||||
future_feature_flags::set_from_string(s.as_utfstr());
|
||||
fish_feature_flags::set_from_string(s.as_utfstr());
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = Fd(FdOutputStream::new(STDOUT_FILENO));
|
||||
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
|
||||
let io_chain = IoChain::new();
|
||||
let mut streams = IoStreams::new(&mut out, &mut err, &io_chain);
|
||||
let streams = &mut IoStreams::new(&mut out, &mut err, &io_chain);
|
||||
|
||||
let mut continuous_mode = false;
|
||||
let mut verbose = false;
|
||||
|
||||
let args: Vec<WString> = std::env::args_os().map(osstr2wcstring).collect();
|
||||
if let ControlFlow::Break(s) =
|
||||
parse_flags(None, &mut streams, args, &mut continuous_mode, &mut verbose)
|
||||
parse_flags(None, streams, args, &mut continuous_mode, &mut verbose)
|
||||
{
|
||||
return s.builtin_status_code();
|
||||
}
|
||||
|
||||
if !isatty(STDIN_FILENO) {
|
||||
streams
|
||||
.err
|
||||
.appendln(wgettext!("Stdin must be attached to a tty."));
|
||||
err_str!("Stdin must be attached to a tty.").finish(streams);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -328,6 +322,5 @@ fn throwing_main() -> i32 {
|
||||
terminal_init(&vars, STDIN_FILENO).input_queue
|
||||
};
|
||||
|
||||
setup_and_process_keys(&mut streams, continuous_mode, verbose, input_queue)
|
||||
.builtin_status_code()
|
||||
setup_and_process_keys(streams, continuous_mode, verbose, input_queue).builtin_status_code()
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
use crate::env::environment::Environment as _;
|
||||
use crate::env::is_read_only;
|
||||
use crate::event::{self, EventDescription, EventHandler};
|
||||
use crate::function;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parse_tree::NodeRef;
|
||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||
use crate::proc::Pid;
|
||||
use crate::signal::Signal;
|
||||
use crate::{err_fmt, err_str, function};
|
||||
use nix::unistd::getpid;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -86,15 +86,13 @@ fn parse_cmd_opts(
|
||||
let mut validate_variable_name =
|
||||
|streams: &mut IoStreams, varname: &wstr, read_only_ok: bool| {
|
||||
if !valid_var_name(varname) {
|
||||
streams.err.append(&varname_error(cmd, varname));
|
||||
varname_error(cmd, varname).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if !read_only_ok && is_read_only(varname) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: variable '%s' is read-only",
|
||||
cmd,
|
||||
varname
|
||||
));
|
||||
err_fmt!("variable '%s' is read-only", varname)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Ok(())
|
||||
@@ -122,11 +120,9 @@ fn add_named_argument(
|
||||
if handling_named_arguments {
|
||||
add_named_argument(&mut validate_variable_name, streams, opts, woptarg)?;
|
||||
} else {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: unexpected positional argument",
|
||||
cmd,
|
||||
woptarg
|
||||
));
|
||||
err_fmt!("%s: unexpected positional argument", woptarg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -135,11 +131,9 @@ fn add_named_argument(
|
||||
}
|
||||
's' => {
|
||||
let Some(signal) = Signal::parse(w.woptarg.unwrap()) else {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: Unknown signal '%s'",
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
err_fmt!("Unknown signal '%s'", w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
opts.events.push(EventDescription::Signal { signal });
|
||||
@@ -163,10 +157,9 @@ fn add_named_argument(
|
||||
0
|
||||
};
|
||||
if caller_id == 0 {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: calling job for event handler not found",
|
||||
cmd
|
||||
));
|
||||
err_str!("calling job for event handler not found")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
e = EventDescription::CallerExit { caller_id };
|
||||
@@ -214,7 +207,14 @@ fn add_named_argument(
|
||||
opts.print_help = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
argv[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -245,11 +245,9 @@ fn add_named_argument(
|
||||
add_named_argument(&mut validate_variable_name, streams, opts, arg)?;
|
||||
}
|
||||
} else {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: unexpected positional argument",
|
||||
cmd,
|
||||
argv[optind],
|
||||
));
|
||||
err_fmt!("%s: unexpected positional argument", argv[optind],)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -271,19 +269,18 @@ fn validate_function_name(
|
||||
}
|
||||
*function_name = argv[1].to_owned();
|
||||
if !valid_func_name(function_name) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: invalid function name",
|
||||
cmd,
|
||||
function_name,
|
||||
));
|
||||
err_fmt!("%s: invalid function name", function_name,)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if parser_keywords_is_reserved(function_name) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: cannot use reserved keyword as function name",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"%s: cannot use reserved keyword as function name",
|
||||
function_name
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Ok(SUCCESS)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::common::escape_string;
|
||||
use crate::common::reformat_for_screen;
|
||||
use crate::common::valid_func_name;
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle};
|
||||
use crate::event::{self};
|
||||
use crate::function;
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parse_util::apply_indents;
|
||||
use crate::parse_util::compute_indents;
|
||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||
use crate::termsize::termsize_last;
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::{reformat_for_screen, valid_func_name},
|
||||
err_fmt, err_str,
|
||||
event::{self},
|
||||
function,
|
||||
highlight::highlight_and_colorize,
|
||||
parse_util::{apply_indents, compute_indents},
|
||||
parser_keywords::parser_keywords_is_reserved,
|
||||
termsize::termsize_last,
|
||||
};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FunctionsCmdOpts<'args> {
|
||||
@@ -84,7 +85,14 @@ fn parse_cmd_opts<'args>(
|
||||
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
argv[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -118,7 +126,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
|
||||
localizable_consts! {
|
||||
FUNCTION_DOES_NOT_EXIST
|
||||
"%s: Function '%s' does not exist"
|
||||
"Function '%s' does not exist"
|
||||
}
|
||||
|
||||
let mut opts = FunctionsCmdOpts::default();
|
||||
@@ -141,14 +149,18 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if opts.report_metadata && opts.no_metadata {
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -162,20 +174,19 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
|
||||
if let Some(desc) = opts.description {
|
||||
if args.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Expected exactly one function name",
|
||||
cmd
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("Expected exactly one function name")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let current_func = args[0];
|
||||
|
||||
if !function::exists(current_func, parser) {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(FUNCTION_DOES_NOT_EXIST, cmd, current_func));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(FUNCTION_DOES_NOT_EXIST, current_func)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -185,9 +196,8 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
|
||||
if opts.report_metadata {
|
||||
if args.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::UNPEXP_ARG_COUNT_WITH_CTX,
|
||||
// This error is
|
||||
// functions: --details: expected 1 arguments; got 2
|
||||
// The "--details" was "argv[optind - 1]" in the C++
|
||||
@@ -196,7 +206,9 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
"--details",
|
||||
1,
|
||||
args.len()
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let props = function::get_props_autoload(args[0], parser);
|
||||
@@ -268,12 +280,13 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
if !opts.handlers_type.unwrap_or(L!("")).is_empty()
|
||||
&& !event::EVENT_FILTER_NAMES.contains(&opts.handlers_type.unwrap())
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Expected %s for %s",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Expected %s for %s",
|
||||
"generic | variable | signal | exit | job-id",
|
||||
"--handlers-type",
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
event::print(streams, opts.handlers_type.unwrap_or(L!("")));
|
||||
@@ -310,42 +323,40 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
|
||||
|
||||
if opts.copy {
|
||||
if args.len() != 2 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Expected exactly two names (current function name, and new function name)",
|
||||
cmd
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("Expected exactly two names (current function name, and new function name)")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let current_func = args[0];
|
||||
let new_func = args[1];
|
||||
|
||||
if !function::exists(current_func, parser) {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(FUNCTION_DOES_NOT_EXIST, cmd, current_func));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(FUNCTION_DOES_NOT_EXIST, current_func)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
if !valid_func_name(new_func) || parser_keywords_is_reserved(new_func) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Illegal function name '%s'",
|
||||
cmd,
|
||||
new_func
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!("Illegal function name '%s'", new_func)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if function::exists(new_func, parser) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Function '%s' already exists. Cannot create copy of '%s'",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Function '%s' already exists. Cannot create copy of '%s'",
|
||||
new_func,
|
||||
current_func
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
if function::copy(current_func, new_func.into(), parser) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! Implementation of the history builtin.
|
||||
|
||||
use crate::builtins::error::Error;
|
||||
use crate::history::in_private_mode;
|
||||
use crate::history::{self, History, history_session_id};
|
||||
use crate::reader::commandline_get_state;
|
||||
use crate::{err_fmt, err_str};
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
@@ -94,12 +96,13 @@ fn set_hist_cmd(
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
if *hist_cmd != HistCmd::None {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::COMBO_EXCLUSIVE,
|
||||
hist_cmd.to_wstr(),
|
||||
sub_cmd.to_wstr()
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return false;
|
||||
}
|
||||
*hist_cmd = sub_cmd;
|
||||
@@ -114,22 +117,16 @@ fn check_for_unexpected_hist_args(
|
||||
) -> bool {
|
||||
if opts.search_type.is_some() || opts.show_time_format.is_some() || opts.null_terminate {
|
||||
let subcmd_str = opts.hist_cmd.to_wstr();
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: %s: subcommand takes no options",
|
||||
cmd,
|
||||
subcmd_str
|
||||
));
|
||||
err_str!("subcommand takes no options")
|
||||
.subcmd(cmd, subcmd_str)
|
||||
.finish(streams);
|
||||
return true;
|
||||
}
|
||||
if !args.is_empty() {
|
||||
let subcmd_str = opts.hist_cmd.to_wstr();
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
subcmd_str,
|
||||
0,
|
||||
args.len()
|
||||
));
|
||||
err_fmt!(Error::UNEXP_ARG_COUNT, 0, args.len())
|
||||
.subcmd(cmd, subcmd_str)
|
||||
.finish(streams);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
@@ -195,11 +192,9 @@ fn parse_cmd_opts(
|
||||
'n' => match fish_wcstol(w.woptarg.unwrap()) {
|
||||
Ok(x) => opts.max_items = Some(x as _), // todo!("historical behavior is to cast")
|
||||
Err(_) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_NOT_NUMBER,
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
err_fmt!(Error::NOT_NUMBER, w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
},
|
||||
@@ -210,7 +205,7 @@ fn parse_cmd_opts(
|
||||
opts.print_help = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -305,15 +300,12 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
// be handled only by the history function's interactive delete feature.
|
||||
if opts.search_type.unwrap_or(history::SearchType::Exact) != history::SearchType::Exact
|
||||
{
|
||||
streams
|
||||
.err
|
||||
.appendln(wgettext!("builtin history delete only supports --exact"));
|
||||
err_str!("builtin history delete only supports --exact").finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if !opts.case_sensitive {
|
||||
streams.err.appendln(wgettext!(
|
||||
"builtin history delete --exact requires --case-sensitive"
|
||||
));
|
||||
err_str!("builtin history delete --exact requires --case-sensitive")
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -341,10 +333,9 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
}
|
||||
|
||||
if in_private_mode(parser.vars()) {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: can't merge history in private mode",
|
||||
cmd
|
||||
));
|
||||
err_str!("can't merge history in private mode")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
history.incorporate_external_changes();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// Functions for executing the jobs builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle, escape_string, timef};
|
||||
use crate::io::IoStreams;
|
||||
use crate::job_group::{JobId, MaybeJobId};
|
||||
use crate::localization::{wgettext, wgettext_fmt};
|
||||
use crate::parser::Parser;
|
||||
use crate::proc::{HAVE_PROC_STAT, Job, clock_ticks_to_seconds, proc_get_jiffies};
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use crate::{
|
||||
err_fmt,
|
||||
io::IoStreams,
|
||||
job_group::{JobId, MaybeJobId},
|
||||
localization::{wgettext, wgettext_fmt},
|
||||
parser::Parser,
|
||||
proc::{HAVE_PROC_STAT, Job, clock_ticks_to_seconds, proc_get_jiffies},
|
||||
wutil::fish_wcstoi,
|
||||
};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string, timef};
|
||||
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
|
||||
use fish_widestring::{L, WExt as _, WString, wstr};
|
||||
use std::num::NonZeroU32;
|
||||
@@ -170,7 +173,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -202,11 +205,9 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
if arg.char_at(0) == '%' {
|
||||
match fish_wcstoi(&arg[1..]).ok().filter(|&job_id| job_id >= 0) {
|
||||
None => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: '%s' is not a valid job ID",
|
||||
cmd,
|
||||
arg
|
||||
));
|
||||
err_fmt!("'%s' is not a valid job ID", arg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Some(job_id) => {
|
||||
@@ -230,9 +231,9 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
found = true;
|
||||
} else {
|
||||
if mode != JobsPrintMode::PrintNothing {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: No suitable job: %s", cmd, arg));
|
||||
err_fmt!("No suitable job: %s", arg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use num_traits::pow;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::tinyexpr::te_interp;
|
||||
use crate::{builtins::error::Error, err_fmt, tinyexpr::te_interp};
|
||||
|
||||
/// The maximum number of points after the decimal that we'll print.
|
||||
const DEFAULT_SCALE: usize = 6;
|
||||
@@ -67,9 +67,9 @@ fn parse_cmd_opts(
|
||||
} else {
|
||||
let scale = fish_wcstoi(optarg).unwrap_or(-1);
|
||||
if scale < 0 || scale > 15 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: %s: invalid scale", cmd, optarg));
|
||||
err_fmt!("%s: invalid scale", optarg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
// We know the value is in the range [0, 15]
|
||||
@@ -87,9 +87,9 @@ fn parse_cmd_opts(
|
||||
} else if optarg.eq(L!("ceiling")) || optarg.eq(L!("ceil")) {
|
||||
opts.scale_mode = ScaleMode::Ceiling;
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: %s: invalid mode", cmd, optarg));
|
||||
err_fmt!("%s: invalid mode", optarg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -102,11 +102,9 @@ fn parse_cmd_opts(
|
||||
} else {
|
||||
let base = fish_wcstoi(optarg).unwrap_or(-1);
|
||||
if base != 8 && base != 16 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: %s: invalid base value",
|
||||
cmd,
|
||||
optarg
|
||||
));
|
||||
err_fmt!("%s: invalid base value", optarg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
// We know the value is 8 or 16.
|
||||
@@ -117,7 +115,14 @@ fn parse_cmd_opts(
|
||||
opts.print_help = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], print_hints);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
args[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -142,12 +147,12 @@ fn parse_cmd_opts(
|
||||
}
|
||||
|
||||
if have_scale && opts.scale != 0 && opts.base != 10 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
"non-zero scale value only valid
|
||||
for base 10"
|
||||
));
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
"non-zero scale value only valid for base 10"
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -243,27 +248,23 @@ fn evaluate_expression(
|
||||
return Ok(SUCCESS);
|
||||
};
|
||||
|
||||
streams
|
||||
.err
|
||||
.append(&sprintf!("%s: Error: %s\n", cmd, error_message));
|
||||
streams.err.append(&sprintf!("'%s'\n", expression));
|
||||
let mut err = err_fmt!("Error: %s", error_message);
|
||||
err.append_assign_to_msg(&sprintf!("\n'%s'\n", expression));
|
||||
err.cmd(cmd).finish(streams);
|
||||
|
||||
Err(STATUS_CMD_ERROR)
|
||||
}
|
||||
Err(err) => {
|
||||
streams.err.append(&sprintf!(
|
||||
L!("%s: Error: %s\n"),
|
||||
cmd,
|
||||
err.kind.describe_wstr()
|
||||
));
|
||||
streams.err.append(&sprintf!("'%s'\n", expression));
|
||||
let mut error = err_fmt!("Error: %s", err.kind.describe_wstr());
|
||||
error.append_assign_to_msg(&sprintf!("\n'%s'", expression));
|
||||
let padding = WString::from_chars(vec![' '; err.position + 1]);
|
||||
if err.len >= 2 {
|
||||
let tildes = WString::from_chars(vec!['~'; err.len - 2]);
|
||||
streams.err.append(&sprintf!("%s^%s^\n", padding, tildes));
|
||||
error.append_assign_to_msg(&sprintf!("\n%s^%s^", padding, tildes));
|
||||
} else {
|
||||
streams.err.append(&sprintf!("%s^\n", padding));
|
||||
error.append_assign_to_msg(&sprintf!("\n%s^", padding));
|
||||
}
|
||||
error.cmd(cmd).finish(streams);
|
||||
|
||||
Err(STATUS_CMD_ERROR)
|
||||
}
|
||||
@@ -293,9 +294,9 @@ pub fn math(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
}
|
||||
|
||||
if expression.is_empty() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1, 0));
|
||||
err_fmt!(Error::MIN_ARG_COUNT, 1, 0)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,3 +66,5 @@ mod prelude {
|
||||
NON_OPTION_CHAR, WGetopter, WOption, wopt,
|
||||
};
|
||||
}
|
||||
|
||||
pub use shared::*;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::builtins::error::Error;
|
||||
use crate::env::environment::Environment as _;
|
||||
use crate::{err_fmt, err_str};
|
||||
use std::fs::Metadata;
|
||||
use std::os::unix::prelude::{FileTypeExt as _, MetadataExt as _};
|
||||
use std::time::SystemTime;
|
||||
@@ -15,23 +17,6 @@
|
||||
use libc::{PATH_MAX, S_ISGID, S_ISUID, mode_t};
|
||||
use nix::unistd::{AccessFlags, Gid, Uid};
|
||||
|
||||
macro_rules! path_error {
|
||||
(
|
||||
$streams:expr,
|
||||
$string:expr
|
||||
$(, $args:expr)+
|
||||
$(,)?
|
||||
) => {
|
||||
$streams.err.append(L!("path "));
|
||||
$streams.err.appendln(&wgettext_fmt!($string, $($args),*));
|
||||
};
|
||||
}
|
||||
|
||||
fn path_unknown_option(parser: &Parser, streams: &mut IoStreams, subcmd: &wstr, opt: &wstr) {
|
||||
path_error!(streams, BUILTIN_ERR_UNKNOWN, subcmd, opt);
|
||||
builtin_print_error_trailer(parser, streams.err, L!("path"));
|
||||
}
|
||||
|
||||
// How many bytes we read() at once.
|
||||
// We use PATH_MAX here so we always get at least one path,
|
||||
// and so we can automatically detect NULL-separated input.
|
||||
@@ -233,7 +218,8 @@ fn parse_opts<'args>(
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> BuiltinResult {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("path");
|
||||
let subcmd = args[0];
|
||||
let mut args_read = Vec::with_capacity(args.len());
|
||||
args_read.extend_from_slice(args);
|
||||
|
||||
@@ -243,23 +229,27 @@ fn parse_opts<'args>(
|
||||
while let Some(c) = w.next_opt() {
|
||||
match c {
|
||||
':' => {
|
||||
streams.err.append(L!("path ")); // clone of string_error
|
||||
builtin_missing_argument(parser, streams, cmd, args_read[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
streams.err.append(L!("path ")); // clone of string_error
|
||||
builtin_unexpected_argument(
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
Some(subcmd),
|
||||
args_read[w.wopt_index - 1],
|
||||
false,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, args_read[w.wopt_index - 1])
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'?' => {
|
||||
path_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
|
||||
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
|
||||
.subcmd(cmd, subcmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'q' => {
|
||||
@@ -283,7 +273,9 @@ fn parse_opts<'args>(
|
||||
let types_args = split_string_tok(w.woptarg.unwrap(), L!(","), None);
|
||||
for t in types_args {
|
||||
let Ok(r#type) = t.try_into() else {
|
||||
path_error!(streams, "%s: Invalid type '%s'", "path", t);
|
||||
err_fmt!("Invalid type '%s'", t)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*types |= r#type;
|
||||
@@ -295,7 +287,9 @@ fn parse_opts<'args>(
|
||||
let perms_args = split_string_tok(w.woptarg.unwrap(), L!(","), None);
|
||||
for p in perms_args {
|
||||
let Ok(perm) = p.try_into() else {
|
||||
path_error!(streams, "%s: Invalid permission '%s'", "path", p);
|
||||
err_fmt!("Invalid permission '%s'", p)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*perms |= perm;
|
||||
@@ -359,7 +353,10 @@ fn parse_opts<'args>(
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
path_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
|
||||
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
|
||||
.subcmd(cmd, subcmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -375,14 +372,18 @@ fn parse_opts<'args>(
|
||||
}
|
||||
|
||||
if opts.arg1.is_none() && n_req_args == 1 {
|
||||
path_error!(streams, BUILTIN_ERR_ARG_COUNT0, cmd);
|
||||
err_str!(Error::MISSING_ARG)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we should not have optional args and be reading args from stdin.
|
||||
if streams.stdin_is_directly_redirected && args.len() > *optind {
|
||||
path_error!(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd);
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -698,7 +699,9 @@ fn path_sort(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
|
||||
}
|
||||
None => wbasename,
|
||||
Some(k) => {
|
||||
path_error!(streams, "%s: Invalid sort key '%s'", args[0], k);
|
||||
err_fmt!("Invalid sort key '%s'", k)
|
||||
.subcmd(L!("path"), args[0])
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
};
|
||||
@@ -949,10 +952,10 @@ pub fn path(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
|
||||
};
|
||||
let argc = args.len();
|
||||
if argc <= 1 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_SUBCMD, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::MISSING_SUBCMD)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -975,10 +978,10 @@ pub fn path(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
|
||||
"resolve" => path_resolve,
|
||||
"sort" => path_sort,
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, subcmd_name));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_SUBCMD)
|
||||
.subcmd(cmd, subcmd_name)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
// This file has been imported from source code of printf command in GNU Coreutils version 6.9.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error;
|
||||
use crate::locale::{Locale, get_numeric_locale};
|
||||
use crate::wutil::{
|
||||
errors::Error,
|
||||
@@ -56,6 +57,7 @@
|
||||
wcstoi::{Options as WcstoiOpts, wcstoi_partial},
|
||||
wstr_offset_in,
|
||||
};
|
||||
use crate::{err_fmt, err_str};
|
||||
use fish_printf::{ToArg as _, sprintf_locale};
|
||||
use fish_widestring::{decode_byte_from_char, encode_byte_to_char};
|
||||
|
||||
@@ -205,24 +207,21 @@ impl<'a, 'b> builtin_printf_state_t<'a, 'b> {
|
||||
fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
|
||||
// This check matches the historic `errcode != EINVAL` check from C++.
|
||||
// Note that empty or missing values will be silently treated as 0.
|
||||
if errcode != None && errcode != Some(Error::InvalidChar) && errcode != Some(Error::Empty) {
|
||||
if errcode.is_some_and(|err| err != Error::InvalidChar && err != Error::Empty) {
|
||||
match errcode.unwrap() {
|
||||
Error::Overflow => {
|
||||
self.fatal_error(wgettext_fmt!("%s: Number out of range", s));
|
||||
self.fatal_error(err_fmt!("%s: Number out of range", s));
|
||||
}
|
||||
Error::Empty => {
|
||||
self.fatal_error(wgettext_fmt!("%s: Number was empty", s));
|
||||
}
|
||||
Error::InvalidChar => {
|
||||
panic!("Unreachable");
|
||||
Error::InvalidChar | Error::Empty => {
|
||||
unreachable!("Unreachable");
|
||||
}
|
||||
}
|
||||
} else if !end.is_empty() {
|
||||
if s.as_ptr() == end.as_ptr() {
|
||||
self.fatal_error(wgettext_fmt!("%s: expected a numeric value", s));
|
||||
self.fatal_error(err_fmt!("%s: expected a numeric value", s));
|
||||
} else {
|
||||
// This isn't entirely fatal - the value should still be printed.
|
||||
self.nonfatal_error(wgettext_fmt!(
|
||||
self.nonfatal_error(err_fmt!(
|
||||
"%s: value not completely converted (can't convert '%s')",
|
||||
s,
|
||||
end
|
||||
@@ -231,7 +230,7 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
|
||||
// Do it if the unconverted digit is a valid hex digit,
|
||||
// because it could also be an "0x" -> "0" typo.
|
||||
if s.char_at(0) == '0' && iswxdigit(end.char_at(0)) {
|
||||
self.nonfatal_error(wgettext!(
|
||||
self.nonfatal_error(err_str!(
|
||||
"Hint: a leading '0' without an 'x' indicates an octal number"
|
||||
));
|
||||
}
|
||||
@@ -241,7 +240,7 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
|
||||
|
||||
fn handle_sprintf_error(&mut self, err: fish_printf::Error) {
|
||||
match err {
|
||||
fish_printf::Error::Overflow => self.fatal_error(wgettext!("Number out of range")),
|
||||
fish_printf::Error::Overflow => self.fatal_error(err_str!("Number out of range")),
|
||||
_ => panic!("unhandled error: {err:?}"),
|
||||
}
|
||||
}
|
||||
@@ -472,7 +471,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
|
||||
if (c_int::MIN as i64) <= width && width <= (c_int::MAX as i64) {
|
||||
field_width = Some(width);
|
||||
} else {
|
||||
self.fatal_error(wgettext_fmt!("invalid field width: %s", argv[0]));
|
||||
self.fatal_error(err_fmt!("invalid field width: %s", argv[0]));
|
||||
}
|
||||
argv = &argv[1..];
|
||||
argc -= 1;
|
||||
@@ -500,10 +499,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
|
||||
// so -1 is safe here even if prec < INT_MIN.
|
||||
precision = Some(-1);
|
||||
} else if (c_int::MAX as i64) < prec {
|
||||
self.fatal_error(wgettext_fmt!(
|
||||
"invalid precision: %s",
|
||||
argv[0]
|
||||
));
|
||||
self.fatal_error(err_fmt!("invalid precision: %s", argv[0]));
|
||||
} else {
|
||||
precision = Some(prec);
|
||||
}
|
||||
@@ -529,7 +525,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
|
||||
let directive = &directive_start[0..directive_start
|
||||
.len()
|
||||
.min(wstr_offset_in(f, directive_start) + 1)];
|
||||
self.fatal_error(wgettext_fmt!(
|
||||
self.fatal_error(err_fmt!(
|
||||
"%s: invalid conversion specification",
|
||||
directive
|
||||
));
|
||||
@@ -563,8 +559,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
|
||||
save_argc - argc
|
||||
}
|
||||
|
||||
fn nonfatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
|
||||
let errstr = errstr.as_ref();
|
||||
fn nonfatal_error(&mut self, err: error::Error) {
|
||||
// Don't error twice.
|
||||
if self.early_exit {
|
||||
return;
|
||||
@@ -576,7 +571,8 @@ fn nonfatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
|
||||
self.buff.clear();
|
||||
}
|
||||
|
||||
self.streams.err.append(errstr);
|
||||
let errstr = err.to_string();
|
||||
self.streams.err.append(&errstr);
|
||||
if !errstr.ends_with('\n') {
|
||||
self.streams.err.append('\n');
|
||||
}
|
||||
@@ -586,26 +582,8 @@ fn nonfatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
|
||||
self.exit_code = Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
fn fatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
|
||||
let errstr = errstr.as_ref();
|
||||
|
||||
// Don't error twice.
|
||||
if self.early_exit {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have output, write it so it appears first.
|
||||
if !self.buff.is_empty() {
|
||||
self.streams.out.append(&self.buff);
|
||||
self.buff.clear();
|
||||
}
|
||||
|
||||
self.streams.err.append(errstr);
|
||||
if !errstr.ends_with('\n') {
|
||||
self.streams.err.append('\n');
|
||||
}
|
||||
|
||||
self.exit_code = Err(STATUS_CMD_ERROR);
|
||||
fn fatal_error(&mut self, err: error::Error) {
|
||||
self.nonfatal_error(err);
|
||||
self.early_exit = true;
|
||||
}
|
||||
|
||||
@@ -629,7 +607,7 @@ fn print_esc(&mut self, escstart: &wstr, octal_0: bool) -> usize {
|
||||
p = &p[1..];
|
||||
}
|
||||
if esc_length == 0 {
|
||||
self.fatal_error(wgettext!("missing hexadecimal number in escape"));
|
||||
self.fatal_error(err_str!("missing hexadecimal number in escape"));
|
||||
}
|
||||
self.append_output(encode_byte_to_char((esc_value % 256) as u8));
|
||||
} else if is_octal_digit(p.char_at(0)) {
|
||||
@@ -658,7 +636,7 @@ fn print_esc(&mut self, escstart: &wstr, octal_0: bool) -> usize {
|
||||
if !iswxdigit(p.char_at(0)) {
|
||||
// Escape sequence must be done. Complain if we didn't get anything.
|
||||
if esc_length == 0 {
|
||||
self.fatal_error(wgettext!("Missing hexadecimal number in Unicode escape"));
|
||||
self.fatal_error(err_str!("Missing hexadecimal number in Unicode escape"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -681,7 +659,7 @@ fn print_esc(&mut self, escstart: &wstr, octal_0: bool) -> usize {
|
||||
}
|
||||
None => {
|
||||
let escaped_char_string = format!("\\{esc_char}{uni_value:0exp_esc_length$x}");
|
||||
self.fatal_error(wgettext_fmt!(
|
||||
self.fatal_error(err_fmt!(
|
||||
"Not a valid Unicode character: %s",
|
||||
escaped_char_string
|
||||
));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use errno::errno;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::{env::Environment as _, wutil::wrealpath};
|
||||
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");
|
||||
@@ -38,9 +38,9 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
|
||||
}
|
||||
|
||||
if w.wopt_index != argc {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_ARG_COUNT1, cmd, 0, argc - 1));
|
||||
err_fmt!(Error::UNEXP_ARG_COUNT, 0, argc - 1)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -52,12 +52,9 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
|
||||
if let Some(real_pwd) = wrealpath(&pwd) {
|
||||
pwd = real_pwd;
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: %s failed: %s",
|
||||
cmd,
|
||||
"realpath",
|
||||
errno().to_string()
|
||||
));
|
||||
err_fmt!("%s failed: %s", "realpath", errno().to_string())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::prelude::*;
|
||||
|
||||
use crate::wutil;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::{err_fmt, err_str, wutil};
|
||||
use fish_util::get_seeded_rng;
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::{Rng as _, RngCore as _};
|
||||
@@ -26,7 +27,14 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
argv[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -56,9 +64,7 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
|
||||
let i = w.wopt_index;
|
||||
if arg_count >= 1 && argv[i] == "choice" {
|
||||
if arg_count == 1 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: nothing to choose from", cmd));
|
||||
err_str!("nothing to choose from").cmd(cmd).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -69,18 +75,14 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
|
||||
fn parse_ll(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<i64, wutil::Error> {
|
||||
let res = fish_wcstol(num);
|
||||
if res.is_err() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, num));
|
||||
err_fmt!(Error::NOT_NUMBER, num).cmd(cmd).finish(streams);
|
||||
}
|
||||
res
|
||||
}
|
||||
fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wutil::Error> {
|
||||
let res = fish_wcstoul(num);
|
||||
if res.is_err() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, num));
|
||||
err_fmt!(Error::NOT_NUMBER, num).cmd(cmd).finish(streams);
|
||||
}
|
||||
res
|
||||
}
|
||||
@@ -124,11 +126,9 @@ fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wut
|
||||
match parse_ull(streams, cmd, argv[i + 1]) {
|
||||
Err(_) => return Err(STATUS_INVALID_ARGS),
|
||||
Ok(0) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: %s must be a positive integer",
|
||||
cmd,
|
||||
"STEP"
|
||||
));
|
||||
err_fmt!("%s must be a positive integer", "STEP")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Ok(x) => step = x,
|
||||
@@ -140,9 +140,7 @@ fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wut
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS).cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,28 @@
|
||||
//! Implementation of the read builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::UnescapeStringStyle;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::common::escape;
|
||||
use crate::common::read_blocked;
|
||||
use crate::common::unescape_string;
|
||||
use crate::common::valid_var_name;
|
||||
use crate::env::EnvMode;
|
||||
use crate::env::Environment as _;
|
||||
use crate::env::READ_BYTE_LIMIT;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::input_common::DecodeState;
|
||||
use crate::input_common::InvalidPolicy;
|
||||
use crate::input_common::decode_one_codepoint_utf8;
|
||||
use crate::nix::isatty;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::reader::ReaderConfig;
|
||||
use crate::reader::commandline_set_buffer;
|
||||
use crate::reader::{reader_pop, reader_push, reader_readline, set_shell_modes_temporarily};
|
||||
use crate::tokenizer::TOK_ACCEPT_UNFINISHED;
|
||||
use crate::tokenizer::TOK_ARGUMENT_LIST;
|
||||
use crate::tokenizer::Tok;
|
||||
use crate::tokenizer::Tokenizer;
|
||||
use crate::wutil;
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::valid_var_name,
|
||||
env::{EnvMode, EnvVar, EnvVarFlags, Environment as _, READ_BYTE_LIMIT},
|
||||
err_fmt, err_str,
|
||||
input_common::{DecodeState, InvalidPolicy, decode_utf8},
|
||||
nix::isatty,
|
||||
parse_execution::varname_error,
|
||||
parser::ParserEnvSetMode,
|
||||
reader::{
|
||||
ReaderConfig, commandline_set_buffer, reader_pop, reader_push, reader_readline,
|
||||
set_shell_modes_temporarily,
|
||||
},
|
||||
tokenizer::{TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, Tok, Tokenizer},
|
||||
wutil,
|
||||
};
|
||||
use fish_common::{UnescapeStringStyle, escape, read_blocked, unescape_string};
|
||||
use fish_util::perror;
|
||||
use fish_wcstringutil::{split_about, split_string_tok};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use libc::SEEK_CUR;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{num::NonZeroUsize, os::fd::RawFd, sync::atomic::Ordering};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) enum TokenOutputMode {
|
||||
@@ -55,7 +47,6 @@ struct Options {
|
||||
array: bool,
|
||||
silent: bool,
|
||||
split_null: bool,
|
||||
to_stdout: bool,
|
||||
nchars: Option<NonZeroUsize>,
|
||||
one_line: bool,
|
||||
}
|
||||
@@ -69,7 +60,7 @@ fn new() -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
const SHORT_OPTIONS: &wstr = L!("ac:d:fghiLln:p:sStuxzP:UR:L");
|
||||
const SHORT_OPTIONS: &wstr = L!("ac:d:fghLln:p:sStuxzP:UR:L");
|
||||
const LONG_OPTIONS: &[WOption] = &[
|
||||
wopt(L!("array"), ArgType::NoArgument, 'a'),
|
||||
wopt(L!("command"), ArgType::RequiredArgument, 'c'),
|
||||
@@ -121,13 +112,6 @@ fn parse_cmd_opts(
|
||||
'd' => {
|
||||
opts.delimiter = Some(w.woptarg.unwrap().to_owned());
|
||||
}
|
||||
'i' => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: usage of -i for --silent is deprecated. Please use -s or --silent instead.",
|
||||
cmd
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'f' => {
|
||||
opts.place.mode |= EnvMode::FUNCTION;
|
||||
}
|
||||
@@ -147,21 +131,17 @@ fn parse_cmd_opts(
|
||||
opts.nchars = match fish_wcstoi(w.woptarg.unwrap()) {
|
||||
Ok(n) if n >= 0 => NonZeroUsize::new(n.try_into().unwrap()),
|
||||
Err(wutil::Error::Overflow) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Argument '%s' is out of range",
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!("Argument '%s' is out of range", w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
_ => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_NOT_NUMBER,
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::NOT_NUMBER, w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -189,16 +169,17 @@ fn parse_cmd_opts(
|
||||
};
|
||||
if let Some(old_mode) = opts.token_mode {
|
||||
if old_mode != new_mode {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext_fmt!(
|
||||
"%s and %s are mutually exclusive",
|
||||
tokenize_flag(old_mode),
|
||||
tokenize_flag(new_mode),
|
||||
)
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -217,7 +198,7 @@ fn parse_cmd_opts(
|
||||
opts.split_null = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], true);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -404,7 +385,7 @@ fn read_one_char_at_a_time(
|
||||
}
|
||||
unconsumed.push(b[0]);
|
||||
nbytes += 1;
|
||||
match decode_one_codepoint_utf8(buff, InvalidPolicy::Passthrough, &unconsumed) {
|
||||
match decode_utf8(buff, InvalidPolicy::Passthrough, &unconsumed) {
|
||||
DecodeState::Incomplete => continue,
|
||||
DecodeState::Complete => {
|
||||
unconsumed.clear();
|
||||
@@ -451,32 +432,26 @@ fn validate_read_args(
|
||||
) -> BuiltinResult {
|
||||
localizable_consts! {
|
||||
OPTIONS_CANNOT_BE_COMBINED
|
||||
"%s: Options %s and %s cannot be used together"
|
||||
"Options %s and %s cannot be used together"
|
||||
}
|
||||
if opts.prompt.is_some() && opts.prompt_str.is_some() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(OPTIONS_CANNOT_BE_COMBINED, cmd, "-p", "-P",));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(OPTIONS_CANNOT_BE_COMBINED, "-p", "-P")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if opts.delimiter.is_some() && opts.one_line {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTIONS_CANNOT_BE_COMBINED,
|
||||
cmd,
|
||||
"--delimiter",
|
||||
"--line"
|
||||
));
|
||||
err_fmt!(OPTIONS_CANNOT_BE_COMBINED, "--delimiter", "--line")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if opts.one_line && opts.split_null {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
OPTIONS_CANNOT_BE_COMBINED,
|
||||
cmd,
|
||||
"-z",
|
||||
"--line"
|
||||
));
|
||||
err_fmt!(OPTIONS_CANNOT_BE_COMBINED, "-z", "--line")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -487,10 +462,10 @@ fn validate_read_args(
|
||||
}
|
||||
|
||||
if opts.place.mode.contains(EnvMode::UNEXPORT) && opts.place.mode.contains(EnvMode::EXPORT) {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::EXPORT_UNEXPORT)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -502,32 +477,17 @@ fn validate_read_args(
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_GLOCAL, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::MULTIPLE_SCOPES)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
let argc = argv.len();
|
||||
if !opts.array && argc < 1 && !opts.to_stdout {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1, argc));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if opts.array && argc != 1 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_ARG_COUNT1, cmd, 1, argc));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if opts.to_stdout && argc > 0 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MAX_ARG_COUNT1, cmd, 0, argc));
|
||||
if opts.array && argv.len() != 1 {
|
||||
err_fmt!(Error::UNEXP_ARG_COUNT, 1, argv.len())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -541,22 +501,20 @@ fn tokenize_flag(token_mode: TokenOutputMode) -> &'static wstr {
|
||||
|
||||
if let Some(token_mode) = opts.token_mode {
|
||||
if opts.delimiter.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::COMBO_EXCLUSIVE,
|
||||
"--delimiter",
|
||||
tokenize_flag(token_mode),
|
||||
));
|
||||
tokenize_flag(token_mode)
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if opts.one_line {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
"--line",
|
||||
tokenize_flag(token_mode),
|
||||
));
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, "--line", tokenize_flag(token_mode))
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -564,17 +522,14 @@ fn tokenize_flag(token_mode: TokenOutputMode) -> &'static wstr {
|
||||
// Verify all variable names.
|
||||
for arg in argv {
|
||||
if !valid_var_name(arg) {
|
||||
streams.err.append(&varname_error(cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
varname_error(cmd, arg).full_trailer(parser).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if EnvVar::flags_for(arg).contains(EnvVarFlags::READ_ONLY) {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: %s: cannot overwrite read-only variable",
|
||||
cmd,
|
||||
arg
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!("%s: cannot overwrite read-only variable", arg)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -590,16 +545,9 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
let (mut opts, optind) = parse_cmd_opts(argv, parser, streams)?;
|
||||
|
||||
let cmd = argv[0];
|
||||
let mut argv: &[&wstr] = argv;
|
||||
if !opts.to_stdout {
|
||||
argv = &argv[optind..];
|
||||
}
|
||||
let argv = &argv[optind..];
|
||||
let argc = argv.len();
|
||||
|
||||
if argv.is_empty() {
|
||||
opts.to_stdout = true;
|
||||
}
|
||||
|
||||
if opts.print_help {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return Ok(SUCCESS);
|
||||
@@ -609,9 +557,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
|
||||
// stdin may have been explicitly closed
|
||||
if streams.is_stdin_closed() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_STDIN_CLOSED, cmd));
|
||||
err_str!(Error::STDIN_CLOSED).cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -688,7 +634,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
return exit_res;
|
||||
}
|
||||
|
||||
if opts.to_stdout {
|
||||
if argv.is_empty() {
|
||||
streams.out.append(&buff);
|
||||
return exit_res;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::env::Environment as _;
|
||||
use crate::err_fmt;
|
||||
use crate::{
|
||||
path::path_apply_working_directory,
|
||||
wutil::{normalize_path, wrealpath},
|
||||
@@ -37,7 +38,7 @@ fn parse_options(
|
||||
's' => opts.no_symlinks = true,
|
||||
'h' => opts.print_help = true,
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -82,16 +83,11 @@ pub fn realpath(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
} else {
|
||||
let errno = errno();
|
||||
if errno.0 != 0 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"builtin %s: %s: %s",
|
||||
cmd,
|
||||
arg,
|
||||
errno.to_string()
|
||||
));
|
||||
err_fmt!("%s: %s", arg, errno.to_string())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("builtin %s: Invalid arg: %s", cmd, arg));
|
||||
err_fmt!("Invalid arg: %s", arg).cmd(cmd).finish(streams);
|
||||
}
|
||||
had_error = true;
|
||||
}
|
||||
@@ -106,12 +102,9 @@ pub fn realpath(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
|
||||
};
|
||||
streams.out.appendln(&normalize_path(&absolute_arg, false));
|
||||
} else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"builtin %s: %s failed: %s",
|
||||
cmd,
|
||||
"realpath",
|
||||
errno().to_string()
|
||||
));
|
||||
err_fmt!("%s failed: %s", "realpath", errno().to_string())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
had_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use crate::{builtins::error::Error, err_fmt, err_str};
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
@@ -27,7 +29,7 @@ fn parse_options(
|
||||
match c {
|
||||
'h' => opts.print_help = true,
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], true);
|
||||
return ControlFlow::Break(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -97,10 +99,10 @@ pub fn parse_return_value(
|
||||
return ControlFlow::Break(Ok(SUCCESS));
|
||||
}
|
||||
if optind + 1 < args.len() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return ControlFlow::Break(Err(STATUS_INVALID_ARGS));
|
||||
}
|
||||
if optind == args.len() {
|
||||
@@ -109,10 +111,10 @@ pub fn parse_return_value(
|
||||
match fish_wcstoi(args[optind]) {
|
||||
Ok(i) => ControlFlow::Continue(i),
|
||||
Err(_e) => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, args[1]));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::NOT_NUMBER, args[1])
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
ControlFlow::Break(Err(STATUS_INVALID_ARGS))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::EscapeFlags;
|
||||
use crate::common::EscapeStringStyle;
|
||||
use crate::common::escape;
|
||||
use crate::common::escape_string;
|
||||
use crate::common::valid_var_name;
|
||||
use crate::env::EnvStackSetResult;
|
||||
use crate::env::EnvVarFlags;
|
||||
use crate::env::INHERITED_VARS;
|
||||
use crate::event;
|
||||
use crate::event::Event;
|
||||
use crate::expand::expand_escape_string;
|
||||
use crate::expand::expand_escape_variable;
|
||||
use crate::history::History;
|
||||
use crate::history::history_session_id;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::{
|
||||
env::{EnvMode, EnvVar, Environment},
|
||||
builtins::error::Error,
|
||||
common::valid_var_name,
|
||||
env::{EnvMode, EnvStackSetResult, EnvVar, EnvVarFlags, Environment, INHERITED_VARS},
|
||||
err_fmt, err_str,
|
||||
event::{self, Event},
|
||||
expand::{expand_escape_string, expand_escape_variable},
|
||||
history::{History, history_session_id},
|
||||
parse_execution::varname_error,
|
||||
parser::ParserEnvSetMode,
|
||||
wutil::wcstoi::wcstoi_partial,
|
||||
};
|
||||
use fish_common::help_section;
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape, escape_string, help_section};
|
||||
use fish_widestring::ELLIPSIS_CHAR;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -158,7 +150,14 @@ fn parse(
|
||||
opts.preserve_failure_exit_status = false;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
args[w.wopt_index - 1],
|
||||
false,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -176,22 +175,24 @@ fn parse(
|
||||
let optind = w.wopt_index;
|
||||
// implicit drop(w); here
|
||||
if args[optind - 1].starts_with("-o") {
|
||||
// TODO: translate this
|
||||
streams.err.appendln(&sprintf!(
|
||||
"Fish does not have shell options. See `help %s`.",
|
||||
let mut err = err_fmt!(
|
||||
"fish does not have shell options. See `help %s`.",
|
||||
help_section!("fish_for_bash_users")
|
||||
));
|
||||
);
|
||||
|
||||
if optind < args.len() {
|
||||
err.append_assign_to_msg('\n');
|
||||
if args[optind] == "vi" {
|
||||
// Tell the vi users how to get what they need.
|
||||
streams
|
||||
.err
|
||||
.appendln(L!("To enable vi-mode, run `fish_vi_key_bindings`."));
|
||||
err.append_assign_to_msg(wgettext!(
|
||||
"To enable vi-mode, run `fish_vi_key_bindings`."
|
||||
));
|
||||
} else if args[optind] == "ed" {
|
||||
// This should be enough for make ed users feel at home
|
||||
streams.err.append(L!("?\n?\n?\n"));
|
||||
err.append_assign_to_msg(L!("?\n?\n?\n"));
|
||||
}
|
||||
}
|
||||
err.finish(streams);
|
||||
}
|
||||
|
||||
builtin_unknown_option(parser, streams, cmd, args[optind - 1], false);
|
||||
@@ -225,15 +226,19 @@ fn validate(
|
||||
) -> Result<(), ErrorCode> {
|
||||
// Can't query and erase or list.
|
||||
if opts.query && (opts.erase || opts.list) {
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
// We can't both list and erase variables.
|
||||
if opts.erase && opts.list {
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -246,35 +251,37 @@ fn validate(
|
||||
// ..unless we are erasing a variable, in which case we can erase from several in one go.
|
||||
&& !opts.erase
|
||||
{
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_GLOCAL, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::MULTIPLE_SCOPES)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
// Variables can only have one export status.
|
||||
if opts.exportv && opts.unexport {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::EXPORT_UNEXPORT)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
// Variables can only have one path status.
|
||||
if opts.pathvar && opts.unpathvar {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_PATHUNPATH, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::PATH_UNPATH)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
// Trying to erase and (un)export at the same time doesn't make sense.
|
||||
if opts.erase && (opts.exportv || opts.unexport) {
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -288,16 +295,18 @@ fn validate(
|
||||
|| opts.exportv
|
||||
|| opts.universal)
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::INVALID_OPT_COMBO)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if args.len() == optind && opts.erase {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING, cmd, L!("--erase")));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::MISSING_OPT_ARG, L!("--erase"))
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -318,11 +327,12 @@ fn warn_if_uvar_shadows_global(
|
||||
&& parser.is_interactive()
|
||||
&& parser.vars().getf(dest, EnvMode::GLOBAL).is_some()
|
||||
{
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: successfully set universal '%s'; but a global by that name shadows it",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"successfully set universal '%s'; but a global by that name shadows it",
|
||||
dest
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,32 +340,29 @@ fn handle_env_return(retval: EnvStackSetResult, cmd: &wstr, key: &wstr, streams:
|
||||
match retval {
|
||||
EnvStackSetResult::Ok => (),
|
||||
EnvStackSetResult::Perm => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Tried to change the read-only variable '%s'",
|
||||
cmd,
|
||||
key
|
||||
));
|
||||
err_fmt!("Tried to change the read-only variable '%s'", key)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
EnvStackSetResult::Scope => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Tried to modify the special variable '%s' with the wrong scope",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Tried to modify the special variable '%s' with the wrong scope",
|
||||
key
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
EnvStackSetResult::Invalid => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Tried to modify the special variable '%s' to an invalid value",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Tried to modify the special variable '%s' to an invalid value",
|
||||
key
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
}
|
||||
EnvStackSetResult::NotFound => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: The variable '%s' does not exist",
|
||||
cmd,
|
||||
key
|
||||
));
|
||||
// Only variable deletion can return a `NotFound` error, but that case is explicitly silenced
|
||||
unreachable!("variable not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,7 +401,9 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
"{}\n",
|
||||
match self {
|
||||
EnvArrayParseError::InvalidIndex(varname) =>
|
||||
wgettext_fmt!("%s: Invalid index starting at '%s'", "set", varname).to_string(),
|
||||
err_fmt!("Invalid index starting at '%s'", varname)
|
||||
.cmd(L!("set"))
|
||||
.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -729,18 +738,23 @@ fn show(cmd: &wstr, parser: &Parser, streams: &mut IoStreams, args: &[&wstr]) ->
|
||||
}
|
||||
} else {
|
||||
for arg in args.iter().copied() {
|
||||
let bracket = arg.find(L!("["));
|
||||
let arg = if let Some(idx) = bracket {
|
||||
&arg[..idx]
|
||||
} else {
|
||||
arg
|
||||
};
|
||||
|
||||
if !valid_var_name(arg) {
|
||||
streams.err.append(&varname_error(cmd, arg));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
varname_error(cmd, arg).full_trailer(parser).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if arg.contains('[') {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: `set --show` does not allow slices with the var names",
|
||||
cmd
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
if bracket.is_some() {
|
||||
err_str!("`set --show` does not allow slices with the var names")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
@@ -778,8 +792,9 @@ fn erase(
|
||||
};
|
||||
|
||||
if !valid_var_name(split.varname) {
|
||||
streams.err.append(&varname_error(cmd, split.varname));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
varname_error(cmd, split.varname)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let retval;
|
||||
@@ -939,10 +954,10 @@ fn set_internal(
|
||||
argv: &[&wstr],
|
||||
) -> BuiltinResult {
|
||||
if argv.is_empty() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::MIN_ARG_COUNT, 1, 0)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -957,16 +972,19 @@ fn set_internal(
|
||||
|
||||
// Is the variable valid?
|
||||
if !valid_var_name(split.varname) {
|
||||
streams.err.append(&varname_error(cmd, split.varname));
|
||||
let mut err = varname_error(cmd, split.varname);
|
||||
if let Some(pos) = split.varname.chars().position(|c| c == '=') {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: Did you mean `set %s %s`?",
|
||||
cmd,
|
||||
err.append_assign_to_msg('\n');
|
||||
let extra = err_fmt!(
|
||||
"Did you mean `set %s %s`?",
|
||||
&escape(&split.varname[..pos]),
|
||||
&escape(&split.varname[pos + 1..])
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.to_string();
|
||||
err.append_assign_to_msg(&extra);
|
||||
}
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err.full_trailer(parser).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -975,31 +993,31 @@ fn set_internal(
|
||||
// Indexes must be > 0. (Note split_var_and_indexes negates negative values).
|
||||
for ind in &split.indexes {
|
||||
if *ind <= 0 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: array index out of bounds", cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("array index out of bounds")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
// Append and prepend are disallowed.
|
||||
if opts.append || opts.prepend {
|
||||
streams.err.append(&wgettext_fmt!(
|
||||
"%s: Cannot use --append or --prepend when assigning to a slice",
|
||||
cmd
|
||||
));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!("Cannot use --append or --prepend when assigning to a slice")
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
// Argument count and index count must agree.
|
||||
if split.indexes.len() != argv.len() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: given %d indexes but %d values",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"given %d indexes but %d values",
|
||||
split.indexes.len(),
|
||||
argv.len()
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -1041,7 +1059,7 @@ pub fn set(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Buil
|
||||
list(&opts, parser, streams)
|
||||
} else if opts.show {
|
||||
show(cmd, parser, streams, args)
|
||||
} else if args.is_empty() {
|
||||
} else if args.is_empty() && !(opts.append || opts.prepend) {
|
||||
list(&opts, parser, streams)
|
||||
} else {
|
||||
set_internal(cmd, &opts, parser, streams, args)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// Implementation of the set_color builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::screen::{is_dumb, only_grayscale};
|
||||
use crate::terminal::Outputter;
|
||||
use crate::text_face::{self, PrintColorsArgs, TextFace, TextStyling, parse_text_face_and_options};
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
err_fmt,
|
||||
screen::{is_dumb, only_grayscale},
|
||||
terminal::Outputter,
|
||||
text_face::{self, PrintColorsArgs, TextFace, TextStyling, parse_text_face_and_options},
|
||||
};
|
||||
use fish_color::Color;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
fn print_colors(
|
||||
streams: &mut IoStreams,
|
||||
@@ -76,70 +80,40 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
builtin_print_help(parser, streams, argv[0]);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
Err(MissingOptArg) => {
|
||||
// Either "--background" or "--underline-color" are missing an argument.
|
||||
// Don't print an error, for consistency with "set_color".
|
||||
// In future we change both to actually print an error.
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(UnexpectedOptArg(option_index)) => {
|
||||
builtin_unexpected_argument(
|
||||
parser,
|
||||
streams,
|
||||
L!("set_color"),
|
||||
argv[option_index],
|
||||
true, /* print_hints */
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(InvalidOptArg(name, value)) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: %s: invalid option argument: %s",
|
||||
argv[0],
|
||||
name,
|
||||
value
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(UnknownColor(arg)) => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!("%s: Unknown color '%s'", argv[0], arg));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(UnknownUnderlineStyle(arg)) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: invalid underline style: %s",
|
||||
argv[0],
|
||||
arg
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(UnknownOption(unknown_option_index)) => {
|
||||
builtin_unknown_option(
|
||||
parser,
|
||||
streams,
|
||||
L!("set_color"),
|
||||
argv[unknown_option_index],
|
||||
true, /* print_hints */
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(InvalidFgArgCombination) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: %s: option cannot be used with a non-option argument",
|
||||
argv[0],
|
||||
"--foreground",
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
Err(InvalidFgPrintColorCombination) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
argv[0],
|
||||
"--foreground",
|
||||
"--print-colors",
|
||||
));
|
||||
Err(err) => {
|
||||
let error = match err {
|
||||
MissingOptArg => {
|
||||
// Either "--background" or "--underline-color" are missing an argument.
|
||||
// Don't print an error, for consistency with "set_color".
|
||||
// In future we change both to actually print an error.
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
UnexpectedOptArg(option_index) => {
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, argv[option_index]).full_trailer(parser)
|
||||
}
|
||||
InvalidOptArg(name, value) => {
|
||||
err_fmt!("%s: invalid option argument: %s", name, value)
|
||||
}
|
||||
UnknownColor(arg) => {
|
||||
err_fmt!("Unknown color '%s'", arg)
|
||||
}
|
||||
UnknownUnderlineStyle(arg) => {
|
||||
err_fmt!("invalid underline style: %s", arg)
|
||||
}
|
||||
UnknownOption(unknown_option_index) => {
|
||||
err_fmt!(Error::UNKNOWN_OPT, argv[unknown_option_index]).full_trailer(parser)
|
||||
}
|
||||
InvalidFgArgCombination => {
|
||||
err_fmt!(
|
||||
"%s: option cannot be used with a non-option argument",
|
||||
"--foreground"
|
||||
)
|
||||
}
|
||||
InvalidFgPrintColorCombination => {
|
||||
err_fmt!(Error::COMBO_EXCLUSIVE, "--foreground", "--print-colors",)
|
||||
}
|
||||
};
|
||||
error.cmd(argv[0]).finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
};
|
||||
|
||||
261
src/builtins/shared/error.rs
Normal file
261
src/builtins/shared/error.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
io::{IoStreams, OutputStream, StringOutputStream},
|
||||
parser::Parser,
|
||||
prelude::*,
|
||||
wgettext_fmt,
|
||||
};
|
||||
|
||||
use fish_widestring::wstr;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! err_fmt {
|
||||
(
|
||||
$string:expr // format string (literal or LocalizableString)
|
||||
$(, $args:expr)* // list of expressions
|
||||
$(,)? // optional trailing comma
|
||||
) => {
|
||||
$crate::builtins::error::Error::new(
|
||||
$crate::wgettext_fmt!($string, $($args),*).into()
|
||||
)
|
||||
};
|
||||
}
|
||||
pub use err_fmt;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! err_str {
|
||||
(
|
||||
$string:expr // format string (literal or LocalizableString)
|
||||
$(,)? // optional trailing comma
|
||||
) => {
|
||||
$crate::builtins::error::Error::new(std::borrow::Cow::Borrowed($crate::wgettext!($string)))
|
||||
};
|
||||
}
|
||||
pub use err_str;
|
||||
|
||||
/// Generate an `Error` from a string without localization. This is typically
|
||||
/// for error messages from external sources (e.g. C `strerror()`)
|
||||
#[macro_export]
|
||||
macro_rules! err_raw {
|
||||
(
|
||||
$string:expr // owned WString
|
||||
) => {
|
||||
$crate::builtins::error::Error::new($string.into())
|
||||
};
|
||||
}
|
||||
pub use err_raw;
|
||||
|
||||
pub struct Error<'a> {
|
||||
msg: Cow<'a, wstr>,
|
||||
|
||||
cmd: Option<&'a wstr>,
|
||||
subcmd: Option<&'a wstr>,
|
||||
parser: Option<&'a Parser>,
|
||||
hint: bool,
|
||||
}
|
||||
|
||||
impl<'a> Error<'a> {
|
||||
localizable_consts!(
|
||||
/// Error message on missing argument.
|
||||
pub MISSING_OPT_ARG
|
||||
"%s: option requires an argument"
|
||||
|
||||
/// Error message on unexpected argument.
|
||||
pub UNEXP_OPT_ARG
|
||||
"%s: option does not take an argument"
|
||||
|
||||
/// Error message on missing man page.
|
||||
pub MISSING_HELP
|
||||
"missing man page\nDocumentation may not be installed.\n`help %s` will show an online version"
|
||||
|
||||
/// Error message on multiple scope levels for variables.
|
||||
pub MULTIPLE_SCOPES
|
||||
"scope can be only one of: universal function global local"
|
||||
|
||||
/// Error message for specifying both export and unexport to set/read.
|
||||
pub EXPORT_UNEXPORT
|
||||
"cannot both export and unexport"
|
||||
|
||||
/// Error message for specifying both path and unpath to set/read.
|
||||
pub PATH_UNPATH
|
||||
"cannot both path and unpath"
|
||||
|
||||
/// Error message for unknown switch.
|
||||
pub UNKNOWN_OPT
|
||||
"%s: unknown option"
|
||||
|
||||
/// Error message for invalid bind mode name.
|
||||
pub BIND_MODE
|
||||
"%s: invalid mode name. See `help %s`"
|
||||
|
||||
/// Error message when too many arguments are supplied to a builtin.
|
||||
pub TOO_MANY_ARGUMENTS
|
||||
"too many arguments"
|
||||
|
||||
/// Error message when integer expected
|
||||
pub NOT_NUMBER
|
||||
"%s: invalid integer"
|
||||
|
||||
/// Command that requires a subcommand was invoked without a recognized subcommand.
|
||||
pub MISSING_SUBCMD
|
||||
"missing subcommand"
|
||||
|
||||
pub INVALID_SUBCMD
|
||||
"invalid subcommand"
|
||||
|
||||
pub INVALID_SUBSUBCMD
|
||||
"%s: invalid subcommand"
|
||||
|
||||
/// Error messages for unexpected args.
|
||||
pub MISSING_ARG
|
||||
"missing argument"
|
||||
|
||||
pub UNEXP_ARG_COUNT
|
||||
"expected %d arguments; got %d"
|
||||
|
||||
pub UNPEXP_ARG_COUNT_WITH_CTX
|
||||
"%s: expected %d arguments; got %d"
|
||||
|
||||
pub MIN_ARG_COUNT
|
||||
"expected >= %d arguments; got %d"
|
||||
|
||||
pub MAX_ARG_COUNT
|
||||
"expected <= %d arguments; got %d"
|
||||
|
||||
/// Error message for invalid variable name.
|
||||
pub INVALID_VARNAME
|
||||
"%s: invalid variable name. See `help %s`"
|
||||
|
||||
/// Error message on invalid combination of options.
|
||||
pub INVALID_OPT_COMBO
|
||||
"invalid option combination"
|
||||
|
||||
pub INVALID_OPT_COMBO_WITH_CTX
|
||||
"invalid option combination, %s"
|
||||
|
||||
pub COMBO_EXCLUSIVE
|
||||
"%s %s: options cannot be used together"
|
||||
|
||||
pub REGEX_COMPILE
|
||||
"Regular expression compile error: %s"
|
||||
|
||||
pub NO_SUITABLE_JOBS
|
||||
"There are no suitable jobs"
|
||||
|
||||
pub COULD_NOT_FIND_JOB
|
||||
"Could not find job '%d'"
|
||||
|
||||
pub STDIN_CLOSED
|
||||
"stdin is closed"
|
||||
|
||||
pub INVALID_MAX_MATCHES
|
||||
"Invalid max matches value '%s'"
|
||||
|
||||
pub INVALID_MAX_VALUE
|
||||
"Invalid max value '%s'"
|
||||
);
|
||||
|
||||
#[must_use]
|
||||
pub fn new(msg: Cow<'a, wstr>) -> Self {
|
||||
Error {
|
||||
msg,
|
||||
cmd: Default::default(),
|
||||
subcmd: Default::default(),
|
||||
parser: Default::default(),
|
||||
hint: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cmd(mut self, cmd: &'a wstr) -> Self {
|
||||
self.cmd = Some(cmd);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn subcmd(mut self, cmd: &'a wstr, subcmd: &'a wstr) -> Self {
|
||||
self.cmd = Some(cmd);
|
||||
self.subcmd = Some(subcmd);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn stacktrace(mut self, parser: &'a Parser) -> Self {
|
||||
self.parser = Some(parser);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn hint(mut self) -> Self {
|
||||
self.hint = true;
|
||||
self
|
||||
}
|
||||
|
||||
// Convenience function for both stacktrace and hint
|
||||
#[must_use]
|
||||
pub fn full_trailer(self, parser: &'a Parser) -> Self {
|
||||
self.stacktrace(parser).hint()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn append_to_msg(mut self, append: impl IntoCharIter) -> Self {
|
||||
self.append_assign_to_msg(append);
|
||||
self
|
||||
}
|
||||
pub fn append_assign_to_msg(&mut self, append: impl IntoCharIter) {
|
||||
let s = self.msg.to_mut();
|
||||
if !append.extend_wstring(s) {
|
||||
s.extend(append.chars());
|
||||
}
|
||||
}
|
||||
pub fn finish(self, streams: &mut IoStreams) {
|
||||
self.write_to(streams.err);
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> WString {
|
||||
let mut out = OutputStream::String(StringOutputStream::new());
|
||||
self.write_to(&mut out);
|
||||
out.take()
|
||||
}
|
||||
|
||||
pub fn write_to(&self, output: &mut OutputStream) {
|
||||
self.write_msg(output);
|
||||
self.write_stacktrace(output);
|
||||
self.write_hint(output);
|
||||
}
|
||||
|
||||
fn write_msg(&self, output: &mut OutputStream) {
|
||||
let str: &wstr = match (self.cmd, self.subcmd) {
|
||||
(None, _) => &self.msg,
|
||||
(Some(cmd), None) => &wgettext_fmt!("%s: %s", cmd, &self.msg),
|
||||
(Some(cmd), Some(subcmd)) => &wgettext_fmt!("%s %s: %s", cmd, subcmd, &self.msg),
|
||||
};
|
||||
output.appendln(str);
|
||||
}
|
||||
|
||||
fn write_stacktrace(&self, output: &mut OutputStream) {
|
||||
let Some(parser) = self.parser else {
|
||||
return;
|
||||
};
|
||||
let stacktrace = parser.current_line();
|
||||
if !stacktrace.is_empty() {
|
||||
output.append('\n');
|
||||
output.appendln(&stacktrace);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_hint(&self, output: &mut OutputStream) {
|
||||
if !self.hint {
|
||||
return;
|
||||
}
|
||||
let Some(cmd) = self.cmd else {
|
||||
return;
|
||||
};
|
||||
|
||||
output.appendln(&wgettext_fmt!(
|
||||
"(Type 'help %s' for related documentation)",
|
||||
cmd
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::{Named, bytes2wcstring, escape, get_by_sorted_name, str2wcstring};
|
||||
use crate::fds::BorrowedFdFile;
|
||||
use crate::io::OutputStream;
|
||||
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
|
||||
use crate::parse_util::argument_is_help;
|
||||
use crate::parser::{BlockType, LoopStatus};
|
||||
use crate::proc::{Pid, ProcStatus, no_exec};
|
||||
use crate::{builtins::*, wutil};
|
||||
use crate::{
|
||||
builtins::{prelude::*, *},
|
||||
err_fmt,
|
||||
fds::BorrowedFdFile,
|
||||
io::OutputStream,
|
||||
parse_constants::UNKNOWN_BUILTIN_ERR_MSG,
|
||||
parse_util::argument_is_help,
|
||||
parser::{BlockType, LoopStatus},
|
||||
proc::{Pid, ProcStatus, no_exec},
|
||||
wutil,
|
||||
};
|
||||
use errno::errno;
|
||||
use fish_common::assert_sorted_by_name;
|
||||
use fish_widestring::L;
|
||||
use fish_common::{Named, assert_sorted_by_name, escape, get_by_sorted_name};
|
||||
use fish_widestring::{L, bytes2wcstring, str2wcstring};
|
||||
use std::io::{BufRead as _, BufReader, Read as _};
|
||||
|
||||
pub type BuiltinCmd = fn(&Parser, &mut IoStreams, &mut [&wstr]) -> BuiltinResult;
|
||||
@@ -19,104 +21,6 @@
|
||||
L!("set_color green; echo -n read; set_color --reset; echo -n \"> \"");
|
||||
|
||||
localizable_consts!(
|
||||
/// Error message on missing argument.
|
||||
pub BUILTIN_ERR_MISSING
|
||||
"%s: %s: option requires an argument"
|
||||
|
||||
/// Error message on unexpected argument.
|
||||
pub BUILTIN_ERR_UNEXP_ARG
|
||||
"%s: %s: option does not take an argument"
|
||||
|
||||
/// Error message on missing man page.
|
||||
pub BUILTIN_ERR_MISSING_HELP
|
||||
"fish: %s: missing man page\nDocumentation may not be installed.\n`help %s` will show an online version"
|
||||
|
||||
/// Error message on multiple scope levels for variables.
|
||||
pub BUILTIN_ERR_GLOCAL
|
||||
"%s: scope can be only one of: universal function global local"
|
||||
|
||||
/// Error message for specifying both export and unexport to set/read.
|
||||
pub BUILTIN_ERR_EXPUNEXP
|
||||
"%s: cannot both export and unexport"
|
||||
|
||||
/// Error message for specifying both path and unpath to set/read.
|
||||
pub BUILTIN_ERR_PATHUNPATH
|
||||
"%s: cannot both path and unpath"
|
||||
|
||||
/// Error message for unknown switch.
|
||||
pub BUILTIN_ERR_UNKNOWN
|
||||
"%s: %s: unknown option"
|
||||
|
||||
/// Error message for invalid bind mode name.
|
||||
pub BUILTIN_ERR_BIND_MODE
|
||||
"%s: %s: invalid mode name. See `help %s`"
|
||||
|
||||
/// Error message when too many arguments are supplied to a builtin.
|
||||
pub BUILTIN_ERR_TOO_MANY_ARGUMENTS
|
||||
"%s: too many arguments"
|
||||
|
||||
/// Error message when integer expected
|
||||
pub BUILTIN_ERR_NOT_NUMBER
|
||||
"%s: %s: invalid integer"
|
||||
|
||||
/// Command that requires a subcommand was invoked without a recognized subcommand.
|
||||
pub BUILTIN_ERR_MISSING_SUBCMD
|
||||
"%s: missing subcommand"
|
||||
|
||||
pub BUILTIN_ERR_INVALID_SUBCMD
|
||||
"%s: %s: invalid subcommand"
|
||||
|
||||
pub BUILTIN_ERR_INVALID_SUBSUBCMD
|
||||
"%s %s: %s: invalid subcommand"
|
||||
|
||||
/// Error messages for unexpected args.
|
||||
pub BUILTIN_ERR_ARG_COUNT0
|
||||
"%s: missing argument"
|
||||
|
||||
pub BUILTIN_ERR_ARG_COUNT1
|
||||
"%s: expected %d arguments; got %d"
|
||||
|
||||
pub BUILTIN_ERR_ARG_COUNT2
|
||||
"%s: %s: expected %d arguments; got %d"
|
||||
|
||||
pub BUILTIN_ERR_MIN_ARG_COUNT1
|
||||
"%s: expected >= %d arguments; got %d"
|
||||
|
||||
pub BUILTIN_ERR_MAX_ARG_COUNT1
|
||||
"%s: expected <= %d arguments; got %d"
|
||||
|
||||
/// Error message for invalid variable name.
|
||||
pub BUILTIN_ERR_VARNAME
|
||||
"%s: %s: invalid variable name. See `help %s`"
|
||||
|
||||
/// Error message on invalid combination of options.
|
||||
pub BUILTIN_ERR_COMBO
|
||||
"%s: invalid option combination"
|
||||
|
||||
pub BUILTIN_ERR_COMBO2
|
||||
"%s: invalid option combination, %s"
|
||||
|
||||
pub BUILTIN_ERR_COMBO2_EXCLUSIVE
|
||||
"%s: %s %s: options cannot be used together"
|
||||
|
||||
pub BUILTIN_ERR_REGEX_COMPILE
|
||||
"%s: Regular expression compile error: %s"
|
||||
|
||||
pub BUILTIN_ERR_NO_SUITABLE_JOBS
|
||||
"%s: There are no suitable jobs"
|
||||
|
||||
pub BUILTIN_ERR_COULD_NOT_FIND_JOB
|
||||
"%s: Could not find job '%d'"
|
||||
|
||||
pub BUILTIN_ERR_STDIN_CLOSED
|
||||
"%s: stdin is closed"
|
||||
|
||||
pub BUILTIN_ERR_INVALID_MAX_MATCHES
|
||||
"%s: Invalid max matches value '%s'"
|
||||
|
||||
pub BUILTIN_ERR_INVALID_MAX_VALUE
|
||||
"%s: Invalid max value '%s'"
|
||||
|
||||
/// The send stuff to foreground message.
|
||||
pub FG_MSG
|
||||
"Send job %d (%s) to foreground"
|
||||
@@ -649,11 +553,17 @@ pub fn builtin_print_help(parser: &Parser, streams: &mut IoStreams, cmd: &wstr)
|
||||
}
|
||||
let name_esc = escape(cmd);
|
||||
let cmd = sprintf!("__fish_print_help %s ", &name_esc);
|
||||
let res = parser.eval(&cmd, streams.io_chain);
|
||||
let res = parser.eval_with(
|
||||
&cmd,
|
||||
streams.io_chain,
|
||||
streams.job_group.as_ref(),
|
||||
BlockType::top,
|
||||
false,
|
||||
);
|
||||
if res.status.normal_exited() && res.status.exit_code() == 2 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_HELP, name_esc, name_esc));
|
||||
err_fmt!(Error::MISSING_HELP, name_esc)
|
||||
.cmd(&name_esc)
|
||||
.finish(streams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,38 +575,38 @@ pub fn builtin_unknown_option(
|
||||
opt: &wstr,
|
||||
print_hints: bool, /*=true*/
|
||||
) {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_UNKNOWN, cmd, opt));
|
||||
let mut err = err_fmt!(Error::UNKNOWN_OPT, opt).cmd(cmd);
|
||||
if print_hints {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err = err.full_trailer(parser);
|
||||
}
|
||||
err.finish(streams);
|
||||
}
|
||||
|
||||
/// Perform error reporting for encounter with missing argument.
|
||||
/// Perform error reporting for encounter with missing argument for subcommands.
|
||||
pub fn builtin_missing_argument(
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
cmd: &wstr,
|
||||
subcmd: Option<&wstr>,
|
||||
mut opt: &wstr,
|
||||
print_hints: bool, /*=true*/
|
||||
) {
|
||||
if opt.char_at(0) == '-' && opt.char_at(1) != '-' {
|
||||
let mut err = if opt.char_at(0) == '-' && opt.char_at(1) != '-' {
|
||||
// if c in -qc '-qc' is missing the argument, now opt is just 'c'
|
||||
opt = &opt[opt.len() - 1..];
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_MISSING,
|
||||
cmd,
|
||||
L!("-").to_owned() + opt
|
||||
));
|
||||
err_fmt!(Error::MISSING_OPT_ARG, L!("-").to_owned() + opt)
|
||||
} else {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING, cmd, opt));
|
||||
err_fmt!(Error::MISSING_OPT_ARG, opt)
|
||||
};
|
||||
if let Some(subcmd) = subcmd {
|
||||
err = err.subcmd(cmd, subcmd);
|
||||
} else {
|
||||
err = err.cmd(cmd);
|
||||
}
|
||||
if print_hints {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err = err.full_trailer(parser);
|
||||
}
|
||||
err.finish(streams);
|
||||
}
|
||||
|
||||
/// Perform error reporting for encounter with an extra argument.
|
||||
@@ -707,12 +617,11 @@ pub fn builtin_unexpected_argument(
|
||||
opt: &wstr,
|
||||
print_hints: bool, /*=true*/
|
||||
) {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, cmd, opt));
|
||||
let mut err = err_fmt!(Error::UNEXP_OPT_ARG, opt).cmd(cmd);
|
||||
if print_hints {
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err = err.full_trailer(parser);
|
||||
}
|
||||
err.finish(streams);
|
||||
}
|
||||
|
||||
/// Print the backtrace and call for help that we use at the end of error messages.
|
||||
@@ -729,16 +638,8 @@ pub fn builtin_print_error_trailer(parser: &Parser, b: &mut OutputStream, cmd: &
|
||||
));
|
||||
}
|
||||
|
||||
/// This function works like perror, but it prints its result into the streams.err string instead
|
||||
/// to stderr. Used by the builtin commands.
|
||||
pub fn builtin_wperror(program_name: &wstr, streams: &mut IoStreams) {
|
||||
let err = errno();
|
||||
streams.err.append(program_name);
|
||||
streams.err.append(L!(": "));
|
||||
if err.0 != 0 {
|
||||
let werr = str2wcstring(err.to_string());
|
||||
streams.err.appendln(&werr);
|
||||
}
|
||||
pub fn builtin_strerror() -> WString {
|
||||
str2wcstring(errno().to_string())
|
||||
}
|
||||
|
||||
pub struct HelpOnlyCmdOpts {
|
||||
@@ -770,6 +671,7 @@ pub fn parse(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
None,
|
||||
args[w.wopt_index - 1],
|
||||
print_hints,
|
||||
);
|
||||
@@ -991,11 +893,9 @@ fn parsed_pid(
|
||||
match pid {
|
||||
Ok(pid @ 1..) => Ok(Pid::new(pid)),
|
||||
_ => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: '%s' is not a valid process ID",
|
||||
cmd,
|
||||
arg
|
||||
));
|
||||
err_fmt!("'%s' is not a valid process ID", arg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
Err(STATUS_INVALID_ARGS)
|
||||
}
|
||||
}
|
||||
@@ -1040,9 +940,9 @@ pub fn builtin_break_continue(
|
||||
}
|
||||
|
||||
if argc != 1 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]));
|
||||
err_fmt!(Error::UNKNOWN_OPT, argv[1])
|
||||
.cmd(argv[0])
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -1112,11 +1012,13 @@ pub fn parse_from_opt(
|
||||
arg: &wstr,
|
||||
) -> Result<Self, ErrorCode> {
|
||||
Self::try_from(arg).map_err(|()| {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'",
|
||||
arg
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
|
||||
STATUS_INVALID_ARGS
|
||||
})
|
||||
}
|
||||
5
src/builtins/shared/mod.rs
Normal file
5
src/builtins/shared/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod error;
|
||||
pub mod misc;
|
||||
|
||||
pub use error::*;
|
||||
pub use misc::*;
|
||||
@@ -1,15 +1,11 @@
|
||||
use std::os::fd::AsRawFd as _;
|
||||
|
||||
use crate::{
|
||||
common::{FilenameRef, escape},
|
||||
fds::wopen_cloexec,
|
||||
nix::isatty,
|
||||
parser::Block,
|
||||
reader::reader_read,
|
||||
};
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::{
|
||||
builtins::error::Error, err_fmt, err_raw, err_str, fds::wopen_cloexec, nix::isatty,
|
||||
parser::Block, reader::reader_read,
|
||||
};
|
||||
use fish_common::{FilenameRef, escape};
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use std::os::fd::AsRawFd as _;
|
||||
|
||||
/// The source builtin, sometimes called `.`. Evaluates the contents of a file in the current
|
||||
/// context.
|
||||
@@ -37,18 +33,15 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
|
||||
if argc == optind || args[optind] == "-" {
|
||||
if streams.is_stdin_closed() {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_STDIN_CLOSED, cmd));
|
||||
err_str!(Error::STDIN_CLOSED).cmd(cmd).finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
// Either a bare `source` which means to implicitly read from stdin or an explicit `-`.
|
||||
if argc == optind && isatty(streams.stdin_fd()) {
|
||||
// Don't implicitly read from the terminal.
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: missing filename argument or input redirection",
|
||||
cmd
|
||||
));
|
||||
err_str!("missing filename argument or input redirection")
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
func_filename = FilenameRef::new(L!("-").to_owned());
|
||||
@@ -60,12 +53,12 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
}
|
||||
Err(_) => {
|
||||
let esc = escape(args[optind]);
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Error encountered while sourcing file '%s':",
|
||||
cmd,
|
||||
&esc
|
||||
));
|
||||
builtin_wperror(cmd, streams);
|
||||
err_fmt!("Error encountered while sourcing file '%s':", &esc)
|
||||
.append_to_msg('\n')
|
||||
.append_to_msg(&err_raw!(&builtin_strerror()).cmd(cmd).to_string())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -97,11 +90,12 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
Ok(_) => BuiltinResult::from_dynamic(parser.get_last_status()),
|
||||
Err(err) => {
|
||||
let esc = escape(&func_filename);
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Error while reading file '%s'",
|
||||
cmd,
|
||||
err_fmt!(
|
||||
"Error while reading file '%s'",
|
||||
if esc == "-" { L!("<stdin>") } else { &esc }
|
||||
));
|
||||
)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::prelude::*;
|
||||
use crate::common::{bytes2wcstring, get_program_name, osstr2wcstring, str2wcstring};
|
||||
use crate::builtins::error;
|
||||
use crate::common::get_program_name;
|
||||
use crate::env::config_paths::get_fish_path;
|
||||
use crate::future_feature_flags::{self as features, feature_test};
|
||||
use crate::err_fmt;
|
||||
#[cfg(not(feature = "localize-messages"))]
|
||||
use crate::err_raw;
|
||||
use crate::proc::{
|
||||
JobControl, get_job_control_mode, get_login, is_interactive_session, set_job_control_mode,
|
||||
};
|
||||
@@ -9,8 +12,9 @@
|
||||
use crate::tty_handoff::{TERMINAL_OS_NAME, get_scroll_content_up_capability, xtversion};
|
||||
use crate::wutil::{Error, waccess, wbasename, wdirname, wrealpath};
|
||||
use cfg_if::cfg_if;
|
||||
use fish_feature_flags::{self as features, feature_test};
|
||||
use fish_util::wcsfilecmp_glob;
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::{bytes2wcstring, osstr2wcstring, str2wcstring, wcs2bytes};
|
||||
use nix::unistd::AccessFlags;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
@@ -105,12 +109,13 @@ impl StatusCmdOpts {
|
||||
fn try_set_status_cmd(&mut self, subcmd: StatusCmd, streams: &mut IoStreams) -> bool {
|
||||
match self.status_cmd.replace(subcmd) {
|
||||
Some(existing) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
"status",
|
||||
err_fmt!(
|
||||
error::Error::COMBO_EXCLUSIVE,
|
||||
existing.to_wstr(),
|
||||
subcmd.to_wstr(),
|
||||
));
|
||||
)
|
||||
.cmd(L!("status"))
|
||||
.finish(streams);
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
@@ -171,7 +176,7 @@ fn default() -> Self {
|
||||
];
|
||||
|
||||
localizable_consts! {
|
||||
BUILTIN_INVALID_JOB_CONTROL_MODE "%s: Invalid job control mode '%s'"
|
||||
BUILTIN_INVALID_JOB_CONTROL_MODE "Invalid job control mode '%s'"
|
||||
}
|
||||
|
||||
/// Print the features and their values.
|
||||
@@ -215,17 +220,15 @@ fn parse_cmd_opts(
|
||||
match fish_wcstoi(arg) {
|
||||
Ok(level) if level >= 0 => level,
|
||||
Err(Error::Overflow) | Ok(_) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s: Invalid level value '%s'",
|
||||
cmd,
|
||||
arg
|
||||
));
|
||||
err_fmt!("Invalid level value '%s'", arg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
|
||||
err_fmt!(error::Error::NOT_NUMBER, arg)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -251,11 +254,9 @@ fn parse_cmd_opts(
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
let Ok(job_mode) = w.woptarg.unwrap().try_into() else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_INVALID_JOB_CONTROL_MODE,
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
err_fmt!(BUILTIN_INVALID_JOB_CONTROL_MODE, w.woptarg.unwrap())
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
opts.new_job_control_mode = Some(job_mode);
|
||||
@@ -287,7 +288,7 @@ fn parse_cmd_opts(
|
||||
}
|
||||
'h' => opts.print_help = true,
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
@@ -363,9 +364,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
optind += 1;
|
||||
}
|
||||
None => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, args[1]));
|
||||
err_fmt!(error::Error::INVALID_SUBCMD)
|
||||
.subcmd(cmd, args[1])
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -399,35 +400,22 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
let job_control_mode = match opts.new_job_control_mode {
|
||||
Some(j) => {
|
||||
// Flag form used
|
||||
if !args.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
c.to_wstr(),
|
||||
0,
|
||||
args.len()
|
||||
));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
// Any extra args would have already failed, either as an
|
||||
// unrecognized subcmd, or as a "subcmd combo"
|
||||
assert!(args.is_empty(), "unexpected job-control args");
|
||||
j
|
||||
}
|
||||
None => {
|
||||
if args.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
c.to_wstr(),
|
||||
1,
|
||||
args.len()
|
||||
));
|
||||
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
|
||||
.subcmd(cmd, c.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let Ok(new_mode) = args[0].try_into() else {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_INVALID_JOB_CONTROL_MODE,
|
||||
cmd,
|
||||
args[0]
|
||||
));
|
||||
err_fmt!(BUILTIN_INVALID_JOB_CONTROL_MODE, args[0])
|
||||
.subcmd(cmd, c.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
};
|
||||
new_mode
|
||||
@@ -438,13 +426,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
STATUS_FEATURES => print_features(streams),
|
||||
c @ STATUS_TEST_FEATURE => {
|
||||
if args.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
c.to_wstr(),
|
||||
1,
|
||||
args.len()
|
||||
));
|
||||
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
|
||||
.subcmd(cmd, c.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let mut retval = TestFeatureRetVal::TEST_FEATURE_NOT_RECOGNIZED;
|
||||
@@ -460,13 +444,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
}
|
||||
c @ STATUS_GET_FILE => {
|
||||
if args.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
c.to_wstr(),
|
||||
1,
|
||||
args.len()
|
||||
));
|
||||
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
|
||||
.subcmd(cmd, c.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
let arg = wcs2bytes(args[0]);
|
||||
@@ -484,7 +464,8 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
STATUS_LANGUAGE => {
|
||||
cfg_if! {
|
||||
if #[cfg(not(feature = "localize-messages"))] {
|
||||
streams.err.append(L!("fish was built with the `localize-messages` feature disabled. The `status language` command is unavailable.\n"));
|
||||
err_raw!(L!("fish was built with the `localize-messages` feature disabled. The `status language` command is unavailable.").to_owned())
|
||||
.finish(streams);
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
} else {
|
||||
if args.is_empty() {
|
||||
@@ -513,9 +494,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
invalid => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBSUBCMD, cmd, subcmd.to_wstr(), invalid));
|
||||
err_fmt!(error::Error::INVALID_SUBSUBCMD, invalid)
|
||||
.subcmd(cmd, subcmd.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -557,22 +538,15 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
}
|
||||
c @ STATUS_TEST_TERMINAL_FEATURE => {
|
||||
if args.len() != 1 {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
c.to_wstr(),
|
||||
1,
|
||||
args.len()
|
||||
));
|
||||
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
|
||||
.subcmd(cmd, c.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
if args[0] != "scroll-content-up" {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
"%s %s: unrecognized feature '%s'",
|
||||
cmd,
|
||||
c.to_wstr(),
|
||||
args[0]
|
||||
));
|
||||
err_fmt!("unrecognized feature '%s'", args[0])
|
||||
.subcmd(cmd, c.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
return if get_scroll_content_up_capability() == Some(true) {
|
||||
@@ -584,13 +558,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
|
||||
ref s => {
|
||||
if !args.is_empty() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_ARG_COUNT2,
|
||||
cmd,
|
||||
s.to_wstr(),
|
||||
0,
|
||||
args.len()
|
||||
));
|
||||
err_fmt!(error::Error::UNEXP_ARG_COUNT, 0, args.len())
|
||||
.subcmd(cmd, s.to_wstr())
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
match s {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::screen::escape_code_length;
|
||||
use crate::{err_fmt, err_raw, err_str, screen::escape_code_length};
|
||||
use fish_wcstringutil::fish_wcwidth_visible;
|
||||
// Forward some imports to make subcmd implementations easier
|
||||
use super::prelude::*;
|
||||
@@ -21,35 +21,12 @@
|
||||
#[cfg(test)]
|
||||
mod test_helpers;
|
||||
|
||||
macro_rules! string_error {
|
||||
(
|
||||
$streams:expr,
|
||||
$string:expr
|
||||
$(, $args:expr)+
|
||||
$(,)?
|
||||
) => {
|
||||
$streams.err.append(L!("string "));
|
||||
$streams.err.appendln(&wgettext_fmt!($string, $($args),*));
|
||||
};
|
||||
}
|
||||
use string_error;
|
||||
|
||||
fn string_unknown_option(parser: &Parser, streams: &mut IoStreams, subcmd: &wstr, opt: &wstr) {
|
||||
string_error!(streams, BUILTIN_ERR_UNKNOWN, subcmd, opt);
|
||||
builtin_print_error_trailer(parser, streams.err, L!("string"));
|
||||
}
|
||||
|
||||
trait StringSubCommand<'args> {
|
||||
const SHORT_OPTIONS: &'static wstr;
|
||||
const LONG_OPTIONS: &'static [WOption<'static>];
|
||||
|
||||
/// Parse and store option specified by the associated short or long option.
|
||||
fn parse_opt(
|
||||
&mut self,
|
||||
name: &wstr,
|
||||
c: char,
|
||||
arg: Option<&'args wstr>,
|
||||
) -> Result<(), StringError>;
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringError<'_>>;
|
||||
|
||||
fn parse_opts(
|
||||
&mut self,
|
||||
@@ -57,7 +34,8 @@ fn parse_opts(
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> Result<usize, ErrorCode> {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
let mut args_read = Vec::with_capacity(args.len());
|
||||
args_read.extend_from_slice(args);
|
||||
|
||||
@@ -65,36 +43,34 @@ fn parse_opts(
|
||||
while let Some(c) = w.next_opt() {
|
||||
match c {
|
||||
':' => {
|
||||
streams.err.append(L!("string ")); // clone of string_error
|
||||
builtin_missing_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
Some(subcmd),
|
||||
args_read[w.wopt_index - 1],
|
||||
false,
|
||||
);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
';' => {
|
||||
streams.err.append(L!("string ")); // clone of string_error
|
||||
builtin_unexpected_argument(
|
||||
parser,
|
||||
streams,
|
||||
cmd,
|
||||
args_read[w.wopt_index - 1],
|
||||
false,
|
||||
);
|
||||
err_fmt!(Error::UNEXP_OPT_ARG, args_read[w.wopt_index - 1])
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'?' => {
|
||||
string_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
|
||||
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
|
||||
.subcmd(cmd, subcmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
c => {
|
||||
let retval = self.parse_opt(cmd, c, w.woptarg);
|
||||
let retval = self.parse_opt(c, w.woptarg);
|
||||
if let Err(e) = retval {
|
||||
e.print_error(&args_read, parser, streams, w.woptarg, w.wopt_index);
|
||||
return Err(e.retval());
|
||||
e.print_error(&args_read, streams, w.wopt_index);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +79,11 @@ fn parse_opts(
|
||||
Ok(w.wopt_index)
|
||||
}
|
||||
|
||||
fn parse_arg_number<'a, 'b>(arg: &'a wstr) -> Result<i64, StringError<'b>> {
|
||||
let n = fish_wcstol(arg).map_err(|_| err_fmt!(Error::NOT_NUMBER, arg))?;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// Take any positional arguments after options have been parsed.
|
||||
#[allow(unused_variables)]
|
||||
fn take_args(
|
||||
@@ -154,7 +135,9 @@ fn run_impl(
|
||||
self.take_args(&mut optind, args, streams)?;
|
||||
|
||||
if streams.stdin_is_directly_redirected && args.len() > optind {
|
||||
string_error!(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, args[0]);
|
||||
err_str!(Error::TOO_MANY_ARGUMENTS)
|
||||
.subcmd(L!("string"), args[0])
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -163,9 +146,8 @@ fn run_impl(
|
||||
}
|
||||
|
||||
/// This covers failing argument/option parsing
|
||||
enum StringError {
|
||||
InvalidArgs(WString),
|
||||
NotANumber,
|
||||
enum StringError<'a> {
|
||||
InvalidArgs(Error<'a>),
|
||||
UnknownOption,
|
||||
}
|
||||
|
||||
@@ -177,88 +159,66 @@ enum RegexError {
|
||||
|
||||
impl RegexError {
|
||||
fn print_error(&self, args: &[&wstr], streams: &mut IoStreams) {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
use RegexError::*;
|
||||
match self {
|
||||
Compile(pattern, e) => {
|
||||
string_error!(
|
||||
streams,
|
||||
BUILTIN_ERR_REGEX_COMPILE,
|
||||
cmd,
|
||||
&WString::from(e.error_message())
|
||||
);
|
||||
string_error!(streams, "%s: %s", cmd, pattern);
|
||||
// TODO: This is misaligned if `pattern` contains characters which are not exactly 1
|
||||
// terminal cell wide.
|
||||
let mut marker = " ".repeat(e.offset().unwrap_or(0).saturating_sub(1));
|
||||
let mut marker: WString =
|
||||
" ".repeat(e.offset().unwrap_or(0).saturating_sub(1)).into();
|
||||
marker.push('^');
|
||||
string_error!(streams, "%s: %s", cmd, marker);
|
||||
|
||||
err_fmt!(Error::REGEX_COMPILE, e.error_message())
|
||||
.append_to_msg('\n')
|
||||
.append_to_msg(&err_raw!(pattern).subcmd(cmd, subcmd).to_string())
|
||||
.append_to_msg('\n')
|
||||
.append_to_msg(&err_raw!(marker).subcmd(cmd, subcmd).to_string())
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
}
|
||||
InvalidCaptureGroupName(name) => {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
err_fmt!(
|
||||
"Modification of read-only variable \"%s\" is not allowed",
|
||||
name
|
||||
));
|
||||
)
|
||||
.finish(streams);
|
||||
}
|
||||
InvalidEscape(pattern) => {
|
||||
string_error!(
|
||||
streams,
|
||||
"%s",
|
||||
sprintf!(
|
||||
"%s: Invalid escape sequence in pattern \"%s\"",
|
||||
cmd,
|
||||
pattern
|
||||
)
|
||||
);
|
||||
err_fmt!("Invalid escape sequence in pattern \"%s\"", pattern)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::wutil::wcstoi::Error> for StringError {
|
||||
fn from(_: crate::wutil::wcstoi::Error) -> Self {
|
||||
StringError::NotANumber
|
||||
impl<'a> From<error::Error<'a>> for StringError<'a> {
|
||||
fn from(error: error::Error<'a>) -> Self {
|
||||
StringError::InvalidArgs(error)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! invalid_args {
|
||||
($msg:expr, $name:expr, $arg:expr) => {
|
||||
StringError::InvalidArgs(crate::localization::wgettext_fmt!(
|
||||
$msg,
|
||||
$name,
|
||||
$arg.unwrap()
|
||||
))
|
||||
};
|
||||
}
|
||||
use invalid_args;
|
||||
|
||||
impl StringError {
|
||||
fn print_error(
|
||||
&self,
|
||||
args: &[&wstr],
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
optarg: Option<&wstr>,
|
||||
optind: usize,
|
||||
) {
|
||||
let cmd = args[0];
|
||||
impl<'a> StringError<'a> {
|
||||
fn print_error(self, args: &[&wstr], streams: &mut IoStreams, optind: usize) {
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
use StringError::*;
|
||||
match self {
|
||||
InvalidArgs(msg) => {
|
||||
streams.err.appendln("string ".chars().chain(msg.chars()));
|
||||
}
|
||||
NotANumber => {
|
||||
string_error!(streams, BUILTIN_ERR_NOT_NUMBER, cmd, optarg.unwrap());
|
||||
InvalidArgs(err) => {
|
||||
err.subcmd(cmd, subcmd).finish(streams);
|
||||
}
|
||||
UnknownOption => {
|
||||
string_unknown_option(parser, streams, cmd, args[optind - 1]);
|
||||
// This would mean the subcmd's XXX_OPTIONS does not match
|
||||
// the list in its `parse_opt()` implementation
|
||||
unreachable!(
|
||||
"unexpected option '{}' for 'string {subcmd}'",
|
||||
args[optind - 1]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn retval(&self) -> ErrorCode {
|
||||
STATUS_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
@@ -317,10 +277,10 @@ pub fn string(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
let argc = args.len();
|
||||
|
||||
if argc <= 1 {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_SUBCMD, cmd));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_str!(Error::MISSING_SUBCMD)
|
||||
.cmd(cmd)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -366,10 +326,10 @@ pub fn string(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
}
|
||||
.run(parser, streams, args),
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, args[0]));
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
err_fmt!(Error::INVALID_SUBCMD)
|
||||
.subcmd(cmd, subcmd_name)
|
||||
.full_trailer(parser)
|
||||
.finish(streams);
|
||||
Err(STATUS_INVALID_ARGS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ impl StringSubCommand<'_> for Collect {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("Na");
|
||||
|
||||
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'a' => self.allow_empty = true,
|
||||
'N' => self.no_trim_newlines = true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Escape {
|
||||
@@ -14,14 +14,14 @@ impl StringSubCommand<'_> for Escape {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("n");
|
||||
|
||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'n' => self.no_quoted = true,
|
||||
NON_OPTION_CHAR => {
|
||||
self.style = arg
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid escape style '%s'", name, arg))?;
|
||||
.map_err(|_| err_fmt!("Invalid escape style '%s'", arg.unwrap()))?;
|
||||
}
|
||||
_ => return Err(StringError::UnknownOption),
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ impl<'args> StringSubCommand<'args> for Join<'args> {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("qn");
|
||||
|
||||
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'q' => self.quiet = true,
|
||||
'n' => self.no_empty = true,
|
||||
@@ -44,8 +44,13 @@ fn take_args(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
|
||||
let Some(arg) = args.get(*optind).copied() else {
|
||||
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, args[0]);
|
||||
err_str!(Error::MISSING_ARG)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*optind += 1;
|
||||
|
||||
@@ -13,7 +13,7 @@ impl StringSubCommand<'_> for Length {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("qV");
|
||||
|
||||
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'q' => self.quiet = true,
|
||||
'V' => self.visible = true,
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use super::*;
|
||||
use crate::common::str2wcstring;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::flog::flog;
|
||||
use crate::parse_util::unescape_wildcards;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::wildcard::{ANY_STRING, wildcard_match};
|
||||
use crate::{
|
||||
env::{EnvVar, EnvVarFlags},
|
||||
flog::flog,
|
||||
parse_util::unescape_wildcards,
|
||||
parser::ParserEnvSetMode,
|
||||
wildcard::wildcard_match,
|
||||
};
|
||||
use fish_widestring::{ANY_STRING, str2wcstring};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Match<'args> {
|
||||
@@ -38,7 +40,7 @@ impl<'args> StringSubCommand<'args> for Match<'args> {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("aegivqrnm:");
|
||||
|
||||
fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'a' => self.all = true,
|
||||
'e' => self.entire = true,
|
||||
@@ -55,11 +57,7 @@ fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), St
|
||||
.ok()
|
||||
.and_then(|v| NonZeroUsize::new(v as usize))
|
||||
.ok_or_else(|| {
|
||||
StringError::InvalidArgs(wgettext_fmt!(
|
||||
BUILTIN_ERR_INVALID_MAX_MATCHES,
|
||||
_n,
|
||||
arg
|
||||
))
|
||||
StringError::InvalidArgs(err_fmt!(Error::INVALID_MAX_MATCHES, arg))
|
||||
})?;
|
||||
Some(max)
|
||||
}
|
||||
@@ -75,9 +73,12 @@ fn take_args(
|
||||
args: &[&'args wstr],
|
||||
streams: &mut IoStreams,
|
||||
) -> Result<(), ErrorCode> {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
let Some(arg) = args.get(*optind).copied() else {
|
||||
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, cmd);
|
||||
err_fmt!(Error::MISSING_ARG)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*optind += 1;
|
||||
@@ -92,32 +93,36 @@ fn handle(
|
||||
optind: &mut usize,
|
||||
args: &[&wstr],
|
||||
) -> Result<(), ErrorCode> {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
|
||||
if self.entire && self.index {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--entire and --index are mutually exclusive")
|
||||
));
|
||||
)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if self.invert_match && self.groups_only {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--invert and --groups-only are mutually exclusive")
|
||||
));
|
||||
)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if self.entire && self.groups_only {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--entire and --groups-only are mutually exclusive")
|
||||
));
|
||||
)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
@@ -414,9 +419,9 @@ fn report_matches(&mut self, arg: &wstr, streams: &mut IoStreams) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_INVALID_ARGS};
|
||||
use crate::future_feature_flags::{FeatureFlag, scoped_test};
|
||||
use crate::tests::prelude::*;
|
||||
use crate::validate;
|
||||
use fish_feature_flags::{FeatureFlag, with_overridden_feature};
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
@@ -487,7 +492,7 @@ fn plain() {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn test_qmark_noglob_true() {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
validate!(["string", "match", "a*b?c", "axxb?c"], STATUS_CMD_OK, "axxb?c\n");
|
||||
validate!(["string", "match", "*?", "a"], STATUS_CMD_ERROR, "");
|
||||
validate!(["string", "match", "*?", "ab"], STATUS_CMD_ERROR, "");
|
||||
@@ -515,7 +520,7 @@ fn test_qmark_noglob_true() {
|
||||
#[serial]
|
||||
#[rustfmt::skip]
|
||||
fn test_qmark_glob() {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
validate!(["string", "match", "a*b?c", "axxbyc"], STATUS_CMD_OK, "axxbyc\n");
|
||||
validate!(["string", "match", "*?", "a"], STATUS_CMD_OK, "a\n");
|
||||
validate!(["string", "match", "*?", "ab"], STATUS_CMD_OK, "ab\n");
|
||||
|
||||
@@ -32,32 +32,29 @@ impl StringSubCommand<'_> for Pad {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("c:rCw:");
|
||||
|
||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'c' => {
|
||||
let [pad_char] = arg.unwrap().as_char_slice() else {
|
||||
return Err(invalid_args!(
|
||||
"%s: Padding should be a character '%s'",
|
||||
name,
|
||||
arg
|
||||
));
|
||||
let arg = arg.unwrap();
|
||||
let [pad_char] = arg.as_char_slice() else {
|
||||
return Err(err_fmt!("Padding should be a character '%s'", arg).into());
|
||||
};
|
||||
self.pad_char_width = match fish_wcwidth(*pad_char) {
|
||||
None | Some(0) => {
|
||||
return Err(
|
||||
err_fmt!("Invalid padding character of width zero '%s'", arg).into(),
|
||||
);
|
||||
}
|
||||
Some(w) => w,
|
||||
};
|
||||
let pad_char_width = fish_wcwidth(*pad_char);
|
||||
if pad_char_width <= 0 {
|
||||
return Err(invalid_args!(
|
||||
"%s: Invalid padding character of width zero '%s'",
|
||||
name,
|
||||
arg
|
||||
));
|
||||
}
|
||||
self.pad_char_width = pad_char_width as usize;
|
||||
self.char_to_pad = *pad_char;
|
||||
}
|
||||
'r' => self.pad_from = Direction::Right,
|
||||
'w' => {
|
||||
self.width = fish_wcstol(arg.unwrap())?
|
||||
let arg = arg.unwrap();
|
||||
self.width = Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid width value '%s'", name, arg))?;
|
||||
.map_err(|_| err_fmt!("Invalid width value '%s'", arg))?;
|
||||
}
|
||||
'C' => self.center = true,
|
||||
_ => return Err(StringError::UnknownOption),
|
||||
|
||||
@@ -17,20 +17,22 @@ impl StringSubCommand<'_> for Repeat {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("n:m:qN");
|
||||
|
||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'n' => {
|
||||
let arg = arg.unwrap();
|
||||
self.count = Some(
|
||||
fish_wcstol(arg.unwrap())?
|
||||
Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid count value '%s'", name, arg))?,
|
||||
.map_err(|_| err_fmt!("Invalid count value '%s'", arg))?,
|
||||
);
|
||||
}
|
||||
'm' => {
|
||||
let arg = arg.unwrap();
|
||||
self.max = Some(
|
||||
fish_wcstol(arg.unwrap())?
|
||||
Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!(BUILTIN_ERR_INVALID_MAX_VALUE, name, arg))?,
|
||||
.map_err(|_| err_fmt!(Error::INVALID_MAX_VALUE, arg))?,
|
||||
);
|
||||
}
|
||||
'q' => self.quiet = true,
|
||||
@@ -50,16 +52,21 @@ fn take_args(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let name = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
|
||||
let Some(arg) = args.get(*optind) else {
|
||||
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, name);
|
||||
err_fmt!(Error::MISSING_ARG)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*optind += 1;
|
||||
|
||||
let Ok(Ok(count)) = fish_wcstol(arg).map(|count| count.try_into()) else {
|
||||
string_error!(streams, "%s: Invalid count value '%s'", name, arg);
|
||||
err_fmt!("Invalid count value '%s'", arg)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use pcre2::utf32::{Regex, RegexBuilder};
|
||||
|
||||
use super::*;
|
||||
use crate::future_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Replace<'args> {
|
||||
@@ -28,7 +28,7 @@ impl<'args> StringSubCommand<'args> for Replace<'args> {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("afiqrm:");
|
||||
|
||||
fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'a' => self.all = true,
|
||||
'f' => self.filter = true,
|
||||
@@ -42,11 +42,7 @@ fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), St
|
||||
.ok()
|
||||
.and_then(|v| NonZeroUsize::new(v as usize))
|
||||
.ok_or_else(|| {
|
||||
StringError::InvalidArgs(wgettext_fmt!(
|
||||
BUILTIN_ERR_INVALID_MAX_MATCHES,
|
||||
_n,
|
||||
arg
|
||||
))
|
||||
StringError::InvalidArgs(err_fmt!(Error::INVALID_MAX_MATCHES, arg))
|
||||
})?;
|
||||
Some(max)
|
||||
}
|
||||
@@ -62,14 +58,19 @@ fn take_args(
|
||||
args: &[&'args wstr],
|
||||
streams: &mut IoStreams,
|
||||
) -> Result<(), ErrorCode> {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
let Some(pattern) = args.get(*optind).copied() else {
|
||||
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, cmd);
|
||||
err_str!(Error::MISSING_ARG)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*optind += 1;
|
||||
let Some(replacement) = args.get(*optind).copied() else {
|
||||
string_error!(streams, BUILTIN_ERR_ARG_COUNT1, cmd, 1, 2);
|
||||
err_fmt!(Error::UNEXP_ARG_COUNT, 1, 2)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*optind += 1;
|
||||
@@ -86,7 +87,8 @@ fn handle(
|
||||
optind: &mut usize,
|
||||
args: &[&wstr],
|
||||
) -> Result<(), ErrorCode> {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
|
||||
let replacer = match StringReplacer::new(self.pattern, self.replacement, self) {
|
||||
Ok(x) => x,
|
||||
@@ -102,12 +104,9 @@ fn handle(
|
||||
let (replaced, result) = match replacer.replace(arg) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
string_error!(
|
||||
streams,
|
||||
"%s: Regular expression substitute error: %s",
|
||||
cmd,
|
||||
e.error_message()
|
||||
);
|
||||
err_fmt!("Regular expression substitute error: %s", e.error_message())
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
};
|
||||
@@ -155,7 +154,7 @@ enum StringReplacer<'args, 'opts> {
|
||||
|
||||
impl<'args, 'opts> StringReplacer<'args, 'opts> {
|
||||
fn interpret_escape(arg: &'args wstr) -> Option<WString> {
|
||||
use crate::common::read_unquoted_escape;
|
||||
use fish_common::read_unquoted_escape;
|
||||
|
||||
let mut result: WString = WString::with_capacity(arg.len());
|
||||
let mut cursor = arg;
|
||||
@@ -194,7 +193,7 @@ fn new(
|
||||
replacement.to_owned()
|
||||
} else {
|
||||
Self::interpret_escape(replacement)
|
||||
.ok_or_else(|| RegexError::InvalidEscape(pattern.to_owned()))?
|
||||
.ok_or_else(|| RegexError::InvalidEscape(replacement.to_owned()))?
|
||||
};
|
||||
Self::Regex {
|
||||
replacement,
|
||||
|
||||
@@ -37,22 +37,18 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("c:m:Nlq");
|
||||
|
||||
fn parse_opt(
|
||||
&mut self,
|
||||
name: &wstr,
|
||||
c: char,
|
||||
arg: Option<&'args wstr>,
|
||||
) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'c' => {
|
||||
self.ellipsis = arg.unwrap();
|
||||
self.ellipsis_width = width_without_escapes(self.ellipsis, 0);
|
||||
}
|
||||
'm' => {
|
||||
let arg = arg.unwrap();
|
||||
self.max = Some(
|
||||
fish_wcstol(arg.unwrap())?
|
||||
Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!(BUILTIN_ERR_INVALID_MAX_VALUE, name, arg))?,
|
||||
.map_err(|_| err_fmt!(Error::INVALID_MAX_VALUE, arg))?,
|
||||
);
|
||||
}
|
||||
'N' => self.no_newline = true,
|
||||
|
||||
@@ -112,24 +112,26 @@ impl<'args> StringSubCommand<'args> for Split<'args> {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("qrm:nf:a");
|
||||
|
||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'q' => self.quiet = true,
|
||||
'r' => self.split_from = Direction::Right,
|
||||
'm' => {
|
||||
self.max = fish_wcstol(arg.unwrap())?
|
||||
let arg = arg.unwrap();
|
||||
self.max = Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!(BUILTIN_ERR_INVALID_MAX_VALUE, name, arg))?;
|
||||
.map_err(|_| err_fmt!(Error::INVALID_MAX_VALUE, arg))?;
|
||||
}
|
||||
'n' => self.no_empty = true,
|
||||
'f' => {
|
||||
self.fields = arg.unwrap().try_into().map_err(|e| match e {
|
||||
FieldParseError::Number => StringError::NotANumber,
|
||||
let arg = arg.unwrap();
|
||||
self.fields = arg.try_into().map_err(|e| match e {
|
||||
FieldParseError::Number => err_fmt!(Error::NOT_NUMBER, arg),
|
||||
FieldParseError::Range => {
|
||||
invalid_args!("%s: Invalid range value for field '%s'", name, arg)
|
||||
err_fmt!("Invalid range value for field '%s'", arg)
|
||||
}
|
||||
FieldParseError::Field => {
|
||||
invalid_args!("%s: Invalid fields value '%s'", name, arg)
|
||||
err_fmt!("Invalid fields value '%s'", arg)
|
||||
}
|
||||
})?;
|
||||
}
|
||||
@@ -148,8 +150,13 @@ fn take_args(
|
||||
if self.is_split0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
let Some(arg) = args.get(*optind).copied() else {
|
||||
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, args[0]);
|
||||
err_str!(Error::MISSING_ARG)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
*optind += 1;
|
||||
@@ -164,12 +171,15 @@ fn handle(
|
||||
optind: &mut usize,
|
||||
args: &[&'args wstr],
|
||||
) -> Result<(), ErrorCode> {
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
if self.fields.is_empty() && self.allow_empty {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
args[0],
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--allow-empty is only valid with --fields")
|
||||
));
|
||||
)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,27 +19,30 @@ impl StringSubCommand<'_> for Sub {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("l:qs:e:");
|
||||
|
||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'l' => {
|
||||
let arg = arg.unwrap();
|
||||
self.length = Some(
|
||||
fish_wcstol(arg.unwrap())?
|
||||
Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid length value '%s'", name, arg))?,
|
||||
.map_err(|_| err_fmt!("Invalid length value '%s'", arg))?,
|
||||
);
|
||||
}
|
||||
's' => {
|
||||
let arg = arg.unwrap();
|
||||
self.start = Some(
|
||||
fish_wcstol(arg.unwrap())?
|
||||
Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid start value '%s'", name, arg))?,
|
||||
.map_err(|_| err_fmt!("Invalid start value '%s'", arg))?,
|
||||
);
|
||||
}
|
||||
'e' => {
|
||||
let arg = arg.unwrap();
|
||||
self.end = Some(
|
||||
fish_wcstol(arg.unwrap())?
|
||||
Self::parse_arg_number(arg)?
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid end value '%s'", name, arg))?,
|
||||
.map_err(|_| err_fmt!("Invalid end value '%s'", arg))?,
|
||||
);
|
||||
}
|
||||
'q' => self.quiet = true,
|
||||
@@ -55,13 +58,15 @@ fn handle(
|
||||
optind: &mut usize,
|
||||
args: &[&wstr],
|
||||
) -> Result<(), ErrorCode> {
|
||||
let cmd = args[0];
|
||||
let cmd = L!("string");
|
||||
let subcmd = args[0];
|
||||
if self.length.is_some() && self.end.is_some() {
|
||||
streams.err.appendln(&wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2,
|
||||
cmd,
|
||||
err_fmt!(
|
||||
Error::INVALID_OPT_COMBO_WITH_CTX,
|
||||
wgettext!("--end and --length are mutually exclusive")
|
||||
));
|
||||
)
|
||||
.subcmd(cmd, subcmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use super::string;
|
||||
use crate::builtins::shared::BuiltinResultExt as _;
|
||||
use crate::io::IoChain;
|
||||
use crate::io::{IoStreams, OutputStream, StringOutputStream};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::{
|
||||
builtins::shared::BuiltinResultExt as _,
|
||||
io::{IoChain, IoStreams, OutputStream, StringOutputStream},
|
||||
prelude::*,
|
||||
tests::prelude::*,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! validate {
|
||||
( [$($argv:expr),*], $expected_rc:expr, $expected_out:expr ) => {
|
||||
{
|
||||
use $crate::common::escape;
|
||||
use fish_common::escape;
|
||||
use $crate::prelude::*;
|
||||
use $crate::builtins::string::test_helpers::string_test;
|
||||
let (actual_out, actual_rc) = string_test(vec![$(L!($argv)),*]);
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct Transform {
|
||||
impl StringSubCommand<'_> for Transform {
|
||||
const LONG_OPTIONS: &'static [WOption<'static>] = &[wopt(L!("quiet"), NoArgument, 'q')];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("q");
|
||||
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'q' => self.quiet = true,
|
||||
_ => return Err(StringError::UnknownOption),
|
||||
|
||||
@@ -28,12 +28,7 @@ impl<'args> StringSubCommand<'args> for Trim<'args> {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("c:lrq");
|
||||
|
||||
fn parse_opt(
|
||||
&mut self,
|
||||
_n: &wstr,
|
||||
c: char,
|
||||
arg: Option<&'args wstr>,
|
||||
) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'c' => self.chars_to_trim = arg.unwrap(),
|
||||
'l' => self.left = true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::common::{UnescapeStringStyle, unescape_string};
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Unescape {
|
||||
@@ -16,14 +16,14 @@ impl StringSubCommand<'_> for Unescape {
|
||||
];
|
||||
const SHORT_OPTIONS: &'static wstr = L!("n");
|
||||
|
||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
|
||||
match c {
|
||||
'n' => self.no_quoted = true,
|
||||
NON_OPTION_CHAR => {
|
||||
let arg = arg.unwrap();
|
||||
self.style = arg
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| invalid_args!("%s: Invalid style value '%s'", name, arg))?;
|
||||
.map_err(|_| err_fmt!("Invalid style value '%s'", arg))?;
|
||||
}
|
||||
_ => return Err(StringError::UnknownOption),
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user