Compare commits

..

2 Commits

Author SHA1 Message Date
David Adam
08cd59727b CI: use build_tools/check.sh in Cirrus CI
08b03a733a removed CMake from the Docker images used for the
Cirrus builds.

It might be better to use fish_run_tests.sh in the Docker image, but
that requires some context which I'm not sure is set up properly in
Cirrus.
2025-10-05 23:25:04 +08:00
David Adam
8e5046061d build_tools/check.sh: add support for FISH_TEST_MAX_CONCURRENCY 2025-10-05 23:00:11 +08:00
185 changed files with 6961 additions and 7462 deletions

View File

@@ -1,6 +1,7 @@
image: alpine/edge
packages:
- cargo
- clang17-libclang
- cmake
- ninja
- pcre2-dev
@@ -23,4 +24,4 @@ tasks:
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests
ninja test

View File

@@ -20,4 +20,4 @@ tasks:
ninja
- test: |
cd fish/build
ninja fish_run_tests
ninja test

View File

@@ -5,6 +5,7 @@ packages:
- gettext
- gmake
- llvm
- terminfo-db
- ninja
- pcre2
- py311-pexpect
@@ -26,4 +27,4 @@ tasks:
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests
ninja test

View File

@@ -1,29 +0,0 @@
image: openbsd/latest
packages:
- cmake
- gcc
- gettext
- gmake
- llvm
- ninja
- pcre2
- py311-pexpect
- python
- rust
- tmux
sources:
- https://github.com/fish-shell/fish-shell
tasks:
- build: |
cd fish-shell
mkdir build
cd build
cmake -GNinja .. \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_DATADIR=share \
-DCMAKE_INSTALL_DOCDIR=share/doc/fish \
-DCMAKE_INSTALL_SYSCONFDIR=/etc
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests

View File

@@ -1,5 +1,3 @@
skip: $CIRRUS_REPO_OWNER == 'fish-shell' && $CIRRUS_BRANCH == 'master'
env:
CIRRUS_CLONE_DEPTH: 100
CI: 1
@@ -8,21 +6,26 @@ linux_task:
matrix:
- name: alpine
container: &step
image: ghcr.io/fish-shell/fish-ci/alpine:latest
image: ghcr.io/krobelus/fish-ci/alpine:latest
memory: 4GB
- name: jammy
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/jammy:latest
image: ghcr.io/krobelus/fish-ci/jammy:latest
# - name: jammy-asan
# container:
# <<: *step
# image: ghcr.io/krobelus/fish-ci/jammy-asan:latest
# - name: focal-32bit
# container:
# <<: *step
# image: ghcr.io/krobelus/fish-ci/focal-32bit:latest
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- ninja fish_run_tests
- FISH_TEST_MAX_CONCURRENCY=6 build_tools/check.sh
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
linux_arm_task:
@@ -30,16 +33,17 @@ linux_arm_task:
- name: focal-arm64
arm_container:
image: ghcr.io/fish-shell/fish-ci/focal-arm64
- name: jammy-armv7-32bit
arm_container:
image: ghcr.io/fish-shell/fish-ci/jammy-armv7-32bit
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
- FISH_TEST_MAX_CONCURRENCY=6 build_tools/check.sh
# CI task disabled during RIIR transition
only_if: false && $CIRRUS_REPO_OWNER == 'fish-shell'
freebsd_task:
matrix:
@@ -57,10 +61,7 @@ freebsd_task:
- pw user add -n fish-user -s /bin/csh -d /home/fish-user
- mkdir -p /home/fish-user
- chown -R fish-user /home/fish-user
- mkdir build && cd build
- chown -R fish-user ..
- chown -R fish-user .
- sudo -u fish-user -s whoami
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- sudo -u fish-user -s ninja -j 6 fish
- sudo -u fish-user -s ninja fish_run_tests
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 build_tools/check.sh
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'

View File

@@ -1,43 +0,0 @@
name: Install dependencies for system tests
inputs:
include_sphinx:
description: Whether to install Sphinx
required: true
default: false
include_pcre:
description: Whether to install the PCRE library
required: false
default: true
permissions:
contents: read
runs:
using: "composite"
steps:
- shell: bash
env:
include_sphinx: ${{ inputs.include_sphinx }}
include_pcre: ${{ inputs.include_pcre }}
run: |
set -x
: "optional dependencies"
sudo apt install \
gettext \
$(if $include_pcre; then echo libpcre2-dev; fi) \
$(if $include_sphinx; then echo python3-sphinx; fi) \
;
: "system test dependencies"
sudo apt install \
diffutils $(: "for diff") \
git \
gettext \
less \
$(if ${{ inputs.include_pcre }}; then echo libpcre2-dev; fi) \
python3-pexpect \
tmux \
wget \
;
- uses: ./.github/actions/install-sphinx-markdown-builder
if: ${{ inputs.include_sphinx == 'true' }}

View File

@@ -1,41 +0,0 @@
name: Rust Toolchain
inputs:
toolchain_channel:
description: Either "stable" or "msrv"
required: true
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- name: Set toolchain
env:
toolchain_channel: ${{ inputs.toolchain_channel }}
shell: bash
run: |
set -x
toolchain=$(
case "$toolchain_channel" in
(stable) echo 1.90 ;;
(msrv) echo 1.70 ;;
(*)
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
exit 1
;;
esac
)
printf 'TOOLCHAIN=%s\n' "$toolchain" >>"$GITHUB_ENV"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.TOOLCHAIN }}
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -14,8 +14,7 @@ permissions:
runs:
using: "composite"
steps:
- uses: ./.github/actions/rust-toolchain
- uses: dtolnay/rust-toolchain@1.70
with:
toolchain_channel: "msrv"
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}
components: ${{ inputs.components}}

View File

@@ -14,8 +14,7 @@ permissions:
runs:
using: "composite"
steps:
- uses: ./.github/actions/rust-toolchain
- uses: dtolnay/rust-toolchain@1.90
with:
toolchain_channel: "stable"
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -1,126 +0,0 @@
name: Build Docker test images
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: docker-builds
env:
REGISTRY: ghcr.io
NAMESPACE: fish-ci
ONLY_FOR_REPO_OWNER: fish-shell
jobs:
check-docker-changes:
if: github.repository_owner == env.ONLY_FOR_REPO_OWNER
runs-on: ubuntu-latest
outputs:
docker-changed: ${{ steps.changes.outputs.docker }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
docker:
- 'docker/**'
docker-build:
needs: check-docker-changes
if: github.repository_owner == env.ONLY_FOR_REPO_OWNER && needs.check-docker-changes.outputs.docker-changed == 'true'
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: alpine
- os: ubuntu-latest
target: centos9
- os: ubuntu-latest
target: fedora
- os: ubuntu-latest
target: focal-32bit
- os: ubuntu-24.04-arm
target: focal-arm64
- os: ubuntu-latest
target: focal
- os: ubuntu-24.04-arm
target: jammy-armv7-32bit
- os: ubuntu-latest
target: jammy-asan
- os: ubuntu-latest
target: jammy-tsan
- os: ubuntu-latest
target: jammy
- os: ubuntu-latest
target: noble
- os: ubuntu-latest
target: opensuse-tumbleweed
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Login to Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
flavor: |
latest=true
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: docker/context
push: true
file: docker/${{ matrix.target }}.Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
trigger-cirrus:
needs: [check-docker-changes, docker-build]
if: always() && github.repository_owner == env.ONLY_FOR_REPO_OWNER
runs-on: ubuntu-latest
steps:
- name: Trigger Cirrus CI
env:
CIRRUS_TOKEN: ${{ secrets.CIRRUS_TOKEN }}
run: |
set -x
# N.B. push-triggered workflows are usually from master.
branch=${{ github.ref_name }}
repository_id=${{ github.repository_id }}
curl -X POST \
-H "Authorization: Bearer $CIRRUS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation {
createBuild(input: {
repositoryId: \"$repository_id\",
branch: \"$branch\"
})
{ build { id } }
}"
}' \
https://api.cirrus-ci.com/graphql

View File

@@ -12,7 +12,6 @@ permissions:
jobs:
lock:
if: github.repository_owner == 'fish-shell'
permissions:
issues: write # for dessant/lock-threads to lock issues
pull-requests: write # for dessant/lock-threads to lock PRs

View File

@@ -1,4 +1,4 @@
name: Test
name: make fish_run_tests
on: [push, pull_request]
@@ -11,17 +11,18 @@ permissions:
jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_sphinx: true
- name: Generate a locale that uses a comma as decimal separator.
run: |
sudo apt install gettext libpcre2-dev python3-pexpect python3-sphinx tmux
# Generate a locale that uses a comma as decimal separator.
sudo locale-gen fr_FR.UTF-8
- uses: ./.github/actions/install-sphinx-markdown-builder
- name: cmake
run: |
mkdir build && cd build
@@ -42,20 +43,18 @@ jobs:
git --no-pager diff --exit-code || { echo 'There are uncommitted changes after regenerating the gettext PO files. Make sure to update them via `build_tools/update_translations.fish` after changing source files.'; exit 1; }
ubuntu-32bit-static-pcre2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
with:
targets: "i586-unknown-linux-gnu"
targets: "i586-unknown-linux-gnu" # rust-toolchain wants this comma-separated
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_pcre: false
include_sphinx: false
- name: Install g++-multilib
run: |
sudo apt install g++-multilib
sudo apt update
sudo apt install gettext python3-pexpect g++-multilib tmux
- name: cmake
env:
CFLAGS: "-m32"
@@ -70,6 +69,7 @@ jobs:
make -C build VERBOSE=1 fish_run_tests
ubuntu-asan:
runs-on: ubuntu-latest
env:
# Rust has two different memory sanitizers of interest; they can't be used at the same time:
@@ -79,6 +79,7 @@ jobs:
#
RUSTFLAGS: "-Zsanitizer=address"
# RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
steps:
- uses: actions/checkout@v4
# All -Z options require running nightly
@@ -88,11 +89,8 @@ jobs:
# this is comma-separated
components: rust-src
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_sphinx: false
- name: Install llvm
run: |
sudo apt install gettext libpcre2-dev python3-pexpect tmux
sudo apt install llvm # for llvm-symbolizer
- name: cmake
env:
@@ -121,8 +119,38 @@ jobs:
export LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$PWD/build_tools/lsan_suppressions.txt"
make -C build VERBOSE=1 fish_run_tests
# Our clang++ tsan builds are not recognizing safe rust patterns (such as the fact that Drop
# cannot be called while a thread is using the object in question). Rust has its own way of
# running TSAN, but for the duration of the port from C++ to Rust, we'll keep this disabled.
# ubuntu-threadsan:
#
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v4
# - uses: ./.github/actions/rust-toolchain@oldest-supported
# - name: Install deps
# run: |
# sudo apt install gettext libpcre2-dev python3-pexpect tmux
# - name: cmake
# env:
# FISH_CI_SAN: 1
# CC: clang
# run: |
# mkdir build && cd build
# cmake ..
# - name: make
# run: |
# make
# - name: make fish_run_tests
# run: |
# make -C build fish_run_tests
macos:
runs-on: macos-latest
env:
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
@@ -146,28 +174,3 @@ jobs:
- name: make fish_run_tests
run: |
make -C build VERBOSE=1 fish_run_tests
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
update: true
msystem: MSYS
- name: Install deps
# Not using setup-msys2 `install` option to make it easier to copy/paste
run: |
pacman --noconfirm -S --needed git rust
- name: cargo build
run: |
cargo build
- name: smoketest
# We can't use `cargo test` yet, there are just too many failures
# so this is just a quick check to make sure that fish can swim
run: |
set -x
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]

View File

@@ -1,4 +1,4 @@
name: Lint
name: Rust checks
on: [push, pull_request]
@@ -6,36 +6,25 @@ permissions:
contents: read
jobs:
format:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@stable
with:
components: rustfmt
- name: install dependencies
run: pip install ruff
- name: build fish
run: cargo build
- name: check format
run: PATH="target/debug:$PATH" build_tools/style.fish --all --check
- name: cargo fmt
run: cargo fmt --check
clippy:
clippy-stable:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- rust_version: "stable"
features: ""
- rust_version: "stable"
features: "--no-default-features"
- rust_version: "msrv"
features: ""
features: ["", "--no-default-features"]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain
- uses: ./.github/actions/rust-toolchain@stable
with:
toolchain_channel: ${{ matrix.rust_version }}
components: clippy
- name: Install deps
run: |
@@ -43,6 +32,19 @@ jobs:
- name: cargo clippy
run: cargo clippy --workspace --all-targets ${{ matrix.features }} -- --deny=warnings
clippy-msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@oldest-supported
with:
components: clippy
- name: Install deps
run: |
sudo apt install gettext
- name: cargo clippy
run: cargo clippy --workspace --all-targets -- --deny=warnings
rustdoc:
runs-on: ubuntu-latest
steps:

3
.gitignore vendored
View File

@@ -105,6 +105,3 @@ target/
# JetBrains editors.
.idea/
# AI slop
.claude/

View File

@@ -1,29 +1,16 @@
fish ?.?.? (released ???)
=========================
fish 4.1.3 (released ???)
fish 4.1.2 (released ???)
=========================
This release fixes the following regressions identified in 4.1.0:
- Crash on invalid :doc:`function <cmds/function>` command (:issue:`11912`).
as well as the following regressions identified in 4.0.0:
- Crash when passing negative PIDs to :doc:`wait <cmds/wait>` (:issue:`11929`).
fish 4.1.2 (released October 7, 2025)
=====================================
This release fixes the following regressions identified in 4.1.0:
- Fixed spurious error output when completing remote file paths for ``scp`` (:issue:`11860`).
- Fixed the :kbd:`alt-l` binding not formatting ``ls`` output correctly (one entry per line, no colors) (:issue:`11888`).
- Fixed an issue where focus events (currently only enabled in ``tmux``) would cause multiline prompts to be redrawn in the wrong line (:issue:`11870`).
- Stopped printing output that would cause a glitch on old versions of Midnight Commander (:issue:`11869`).
- Added a fix for some configurations of Zellij where :kbd:`escape` key processing was delayed (:issue:`11868`).
- Added a workaround for old versions of Zellij where :kbd:`escape` processing was delayed (:issue:`11868`).
- Fixed a case where the :doc:`web-based configuration tool <cmds/fish_config>` would generate invalid configuration (:issue:`11861`).
- Fixed a case where pasting into ``fish -c read`` would fail with a noisy error (:issue:`11836`).
- Fixed a case where upgrading fish would break old versions of fish that were still running.
In general, fish still needs to be restarted after it is upgraded,

View File

@@ -110,7 +110,7 @@ before committing your change. That will run our autoformatters:
- ``rustfmt`` for Rust
- ``fish_indent`` (shipped with fish) for fish script
- ``ruff format`` for python
- ``black`` for python
If youve already committed your changes thats okay since it will then
check the files in the most recent commit. This can be useful after
@@ -343,7 +343,7 @@ command-line and graphical user interface programs. For simple use, you can use
Open up the PO file, for example ``po/sv.po``, and you'll see something like::
msgid "%s: No suitable job\n"
msgid "%ls: No suitable job\n"
msgstr ""
The ``msgid`` here is the "name" of the string to translate, typically the English string to translate.
@@ -351,10 +351,10 @@ The second line (``msgstr``) is where your translation goes.
For example::
msgid "%s: No suitable job\n"
msgstr "%s: Inget passande jobb\n"
msgid "%ls: No suitable job\n"
msgstr "%ls: Inget passande jobb\n"
Any ``%s`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string should have the same placeholders in the same order.
Any ``%s`` / ``%ls`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string should have the same placeholders in the same order.
Also any escaped characters, like that ``\n`` newline at the end, should be kept so the translation has the same behavior.
@@ -381,7 +381,7 @@ macros:
::
streams.out.append(wgettext_fmt!("%s: There are no jobs\n", argv[0]));
streams.out.append(wgettext_fmt!("%ls: There are no jobs\n", argv[0]));
All messages in fish script must be enclosed in single or double quote
characters for our message extraction script to find them.

5
Cargo.lock generated
View File

@@ -42,9 +42,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
@@ -109,7 +109,6 @@ version = "4.1.0-snapshot"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"errno",
"fish-build-helper",
"fish-build-man-pages",

View File

@@ -11,7 +11,6 @@ repository = "https://github.com/fish-shell/fish-shell"
[workspace.dependencies]
bitflags = "2.5.0"
cc = "1.0.94"
cfg-if = "1.0.3"
errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" }
@@ -77,7 +76,6 @@ readme = "README.rst"
[dependencies]
bitflags.workspace = true
cfg-if.workspace = true
errno.workspace = true
fish-build-helper.workspace = true
fish-build-man-pages = { workspace = true, optional = true }

View File

@@ -128,7 +128,7 @@ Compiling fish requires:
Sphinx is also optionally required to build the documentation from a
cloned git repository.
Additionally, running the full test suite requires diff, git, Python 3.5+, pexpect, less, tmux and wget.
Additionally, running the full test suite requires Python 3.5+, tmux, and the pexpect package.
Building from source with CMake
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1,6 +1,6 @@
#![allow(clippy::uninlined_format_args)]
use fish_build_helper::{env_var, fish_build_dir, workspace_root};
use fish_build_helper::{fish_build_dir, workspace_root};
use rsconf::Target;
use std::env;
use std::path::{Path, PathBuf};
@@ -16,7 +16,7 @@ fn main() {
// language server.
rsconf::set_env_value(
"FISH_RESOLVED_BUILD_DIR",
"FISH_BUILD_DIR",
// If set by CMake, this might include symlinks. Since we want to compare this to the
// dir fish is executed in we need to canonicalize it.
canonicalize(fish_build_dir()).to_str().unwrap(),
@@ -30,9 +30,9 @@ fn main() {
);
// Some build info
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env_var("TARGET").unwrap());
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap());
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env::var("TARGET").unwrap());
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env::var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env::var("PROFILE").unwrap());
let version = &get_version(&env::current_dir().unwrap());
// Per https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script,
@@ -114,7 +114,7 @@ fn detect_apple(_: &Target) -> bool {
fn detect_cygwin(_: &Target) -> bool {
// Cygwin target is usually cross-compiled.
env_var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin"
std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin"
}
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
@@ -126,7 +126,7 @@ fn detect_cygwin(_: &Target) -> bool {
fn detect_bsd(_: &Target) -> bool {
// Instead of using `uname`, we can inspect the TARGET env variable set by Cargo. This lets us
// support cross-compilation scenarios.
let mut target = env_var("TARGET").unwrap();
let mut target = std::env::var("TARGET").unwrap();
if !target.chars().all(|c| c.is_ascii_lowercase()) {
target = target.to_ascii_lowercase();
}
@@ -177,57 +177,51 @@ fn setup_paths() {
#[cfg(windows)]
use unix_path::{Path, PathBuf};
fn overridable_path(env_var_name: &str, f: impl FnOnce(Option<String>) -> PathBuf) -> PathBuf {
rsconf::rebuild_if_env_changed(env_var_name);
let path = f(env_var(env_var_name));
rsconf::set_env_value(env_var_name, path.to_str().unwrap());
path
}
fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
let path = PathBuf::from(path);
if path.is_relative() {
parent_if_relative.join(path)
} else {
path
fn get_path(name: &str, default: &str, onvar: &Path) -> PathBuf {
let mut var = PathBuf::from(env::var(name).unwrap_or(default.to_string()));
if var.is_relative() {
var = onvar.join(var);
}
var
}
let prefix = overridable_path("PREFIX", |env_prefix| {
PathBuf::from(env_prefix.unwrap_or("/usr/local".to_string()))
});
let prefix = PathBuf::from(env::var("PREFIX").unwrap_or("/usr/local".to_string()));
rsconf::rebuild_if_env_changed("PREFIX");
rsconf::set_env_value("PREFIX", prefix.to_str().unwrap());
let datadir = join_if_relative(&prefix, env_var("DATADIR").unwrap_or("share/".to_string()));
rsconf::rebuild_if_env_changed("DATADIR");
#[cfg(not(feature = "embed-data"))]
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
let datadir = get_path("DATADIR", "share/", &prefix);
overridable_path("SYSCONFDIR", |env_sysconfdir| {
join_if_relative(
&datadir,
env_sysconfdir.unwrap_or(
// Embedded builds use "/etc," not "./share/etc".
if cfg!(feature = "embed-data") {
"/etc/"
} else {
"etc/"
}
.to_string(),
),
)
});
let sysconfdir = get_path(
"SYSCONFDIR",
// Embedded builds use "/etc," not "./share/etc".
if cfg!(feature = "embed-data") {
"/etc/"
} else {
"etc/"
},
&datadir,
);
rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("SYSCONFDIR");
#[cfg(not(feature = "embed-data"))]
{
overridable_path("BINDIR", |env_bindir| {
join_if_relative(&prefix, env_bindir.unwrap_or("bin/".to_string()))
});
overridable_path("LOCALEDIR", |env_localedir| {
join_if_relative(&datadir, env_localedir.unwrap_or("locale/".to_string()))
});
overridable_path("DOCDIR", |env_docdir| {
join_if_relative(&datadir, env_docdir.unwrap_or("doc/fish".to_string()))
});
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DATADIR");
let bindir = get_path("BINDIR", "bin/", &prefix);
rsconf::set_env_value("BINDIR", bindir.to_str().unwrap());
rsconf::rebuild_if_env_changed("BINDIR");
let localedir = get_path("LOCALEDIR", "locale/", &datadir);
let localedir = localedir.to_str().unwrap();
assert!(!localedir.is_empty(), "empty LOCALEDIR is not supported");
rsconf::set_env_value("LOCALEDIR", localedir);
rsconf::rebuild_if_env_changed("LOCALEDIR");
let docdir = get_path("DOCDIR", "doc/fish", &datadir);
rsconf::set_env_value("DOCDIR", docdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DOCDIR");
}
}
@@ -235,7 +229,7 @@ fn get_version(src_dir: &Path) -> String {
use std::fs::read_to_string;
use std::process::Command;
if let Some(var) = env_var("FISH_BUILD_VERSION") {
if let Ok(var) = std::env::var("FISH_BUILD_VERSION") {
return var;
}

View File

@@ -49,10 +49,7 @@ if [ -n "$FISH_TEST_MAX_CONCURRENCY" ]; then
fi
template_file=$(mktemp)
(
export FISH_GETTEXT_EXTRACTION_FILE="$template_file"
cargo build --workspace --all-targets --features=gettext-extract
)
FISH_GETTEXT_EXTRACTION_FILE=$template_file cargo build --workspace --all-targets --features=gettext-extract
if $lint; then
PATH="$build_dir:$PATH" "$workspace_root/build_tools/style.fish" --all --check
for features in "" --no-default-features; do

View File

@@ -32,7 +32,6 @@ done
repo_root="$(dirname "$0")/.."
fish_site=$repo_root/../fish-site
fish_site_repo=git@github.com:$repository_owner/fish-site
for path in . "$fish_site"
do
@@ -43,13 +42,6 @@ do
fi
done
(
cd "$fish_site"
[ "$(git rev-parse HEAD)" = \
"$(git ls-remote "$fish_site_repo" refs/heads/master |
awk '{print $1}')" ]
)
if git tag | grep -qxF "$version"; then
echo >&2 "$0: tag $version already exists"
exit 1
@@ -161,7 +153,7 @@ gh_api_repo() {
command gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/$repository_owner/fish-shell/$path" \
"/repos/$repository_owner/fish-shell/$1" \
"$@"
}
@@ -212,7 +204,7 @@ done
" | sed 's,^\s*| \?,,')"
# This takes care to support remote names that are different from
# fish-shell remote name. Also, support detached HEAD state.
git push "$fish_site_repo" HEAD:master
git push git@github.com:$repository_owner/fish-site HEAD:master
)
if [ -n "$integration_branch" ]; then {
@@ -233,7 +225,7 @@ milestone_number=$(
gh_api_repo milestones?state=open |
jq '.[] | select(.title == "fish '"$version"'") | .number'
)
gh_api_repo milestones/$milestone_number --method PATCH \
gh_api_repo --method PATCH milestones/$milestone_number \
--raw-field state=closed
next_patch_version=$(
@@ -244,7 +236,7 @@ next_patch_version=$(
'
)
if [ -n "$next_patch_version" ]; then
gh_api_repo milestones --method POST \
gh_api_repo --method POST milestones \
--raw-field title="fish $next_patch_version"
fi

View File

@@ -44,7 +44,7 @@ if test $all = yes
end
end
set fish_files $workspace_root/{benchmarks,build_tools,etc,share}/**.fish
set python_files $workspace_root
set python_files {doc_src,share,tests}/**.py
else
# Format the files specified as arguments.
set -l files $argv
@@ -76,19 +76,19 @@ if set -q fish_files[1]
end
if set -q python_files[1]
if not type -q ruff
if not type -q black
echo
echo $yellow'Please install `ruff` to style python'$normal
echo $yellow'Please install `black` to style python'$normal
exit 127
end
echo === Running "$green"ruff format"$normal"
echo === Running "$green"black"$normal"
if set -l -q _flag_check
if not ruff format --check $python_files
if not black --check $python_files
echo $red"Python files are not formatted correctly."$normal
exit 1
end
else
ruff format $python_files
black $python_files
end
end
@@ -101,7 +101,7 @@ end
echo === Running "$green"rustfmt"$normal"
if set -l -q _flag_check
if set -l -q _flag_all
if not cargo fmt --all --check
if not cargo fmt --check
echo $red"Rust files are not formatted correctly."$normal
exit 1
end
@@ -115,7 +115,7 @@ if set -l -q _flag_check
end
else
if set -l -q _flag_all
cargo fmt --all
cargo fmt
else
if set -q rust_files[1]
rustfmt $rust_files

View File

@@ -1,21 +1,4 @@
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt, path::Path};
pub fn env_var(name: &str) -> Option<String> {
let err = match env::var(name) {
Ok(p) => return Some(p),
Err(err) => err,
};
use env::VarError::*;
match err {
NotPresent => None,
NotUnicode(os_string) => {
panic!(
"Environment variable {name} is not valid Unicode: {:?}",
os_string.as_bytes()
)
}
}
}
use std::{borrow::Cow, env, path::Path};
pub fn workspace_root() -> &'static Path {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
@@ -29,7 +12,7 @@ fn cargo_target_dir() -> Cow<'static, Path> {
}
pub fn fish_build_dir() -> Cow<'static, Path> {
// This is set if using CMake.
// FISH_BUILD_DIR is set by CMake, if we are using it.
option_env!("FISH_BUILD_DIR")
.map(|d| Cow::Borrowed(Path::new(d)))
.unwrap_or(cargo_target_dir())

View File

@@ -14,9 +14,12 @@ fn main() {
#[cfg(not(clippy))]
fn build_man(man_dir: &Path) {
use std::process::{Command, Stdio};
use std::{
env,
process::{Command, Stdio},
};
use fish_build_helper::{env_var, workspace_root};
use fish_build_helper::workspace_root;
let workspace_root = workspace_root();
@@ -44,7 +47,7 @@ fn build_man(man_dir: &Path) {
let _ = std::fs::create_dir_all(sec1_str);
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
if env::var("FISH_BUILD_DOCS") == Ok("0".to_string()) {
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
@@ -61,7 +64,7 @@ fn build_man(man_dir: &Path) {
.spawn()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if env_var("FISH_BUILD_DOCS") == Some("1".to_string()) {
if env::var("FISH_BUILD_DOCS") == Ok("1".to_string()) {
panic!("Could not find sphinx-build to build man pages.\nInstall sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0.");
}
rsconf::warn!("Cannot find sphinx-build to build man pages.");

View File

@@ -1,11 +1,10 @@
use std::{
env,
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Stdio},
};
use fish_build_helper::env_var;
fn main() {
let cache_dir =
PathBuf::from(fish_build_helper::fish_build_dir()).join("fish-localization-map-cache");
@@ -28,7 +27,7 @@ fn embed_localizations(cache_dir: &Path) {
std::fs::create_dir_all(cache_dir).unwrap();
let localization_map_path =
Path::new(&env_var("OUT_DIR").unwrap()).join("localization_maps.rs");
Path::new(&env::var("OUT_DIR").unwrap()).join("localization_maps.rs");
let mut localization_map_file = BufWriter::new(File::create(&localization_map_path).unwrap());
// This will become a map which maps from language identifiers to maps containing localizations

View File

@@ -15,7 +15,7 @@ pub enum Arg<'a> {
#[cfg(feature = "widestring")]
WString(WString),
UInt(u64),
SInt(i64),
SInt(i64, u8), // signed integers track their width as the number of bits
Float(f64),
USizeRef(&'a mut usize), // for use with %n
}
@@ -59,7 +59,7 @@ pub fn as_str<'s>(&'s self, storage: &'s mut String) -> Result<&'s str, Error>
pub fn as_uint(&self) -> Result<u64, Error> {
match *self {
Arg::UInt(u) => Ok(u),
Arg::SInt(i) => i.try_into().map_err(|_| Error::Overflow),
Arg::SInt(i, _w) => i.try_into().map_err(|_| Error::Overflow),
_ => Err(Error::BadArgType),
}
}
@@ -68,18 +68,25 @@ pub fn as_uint(&self) -> Result<u64, Error> {
pub fn as_sint(&self) -> Result<i64, Error> {
match *self {
Arg::UInt(u) => u.try_into().map_err(|_| Error::Overflow),
Arg::SInt(i) => Ok(i),
Arg::SInt(i, _w) => Ok(i),
_ => Err(Error::BadArgType),
}
}
/// Unwraps [`Arg::UInt`] to [`u64`].
/// Unwraps [`Arg::SInt`] and casts the [`i64`] to [`u64`].
/// Calling this on other variants of `[Arg]` is an error.
pub fn as_wrapping_sint(&self) -> Result<u64, Error> {
// If this is a signed value, then return the sign (true if negative) and the magnitude,
// masked to the value's width. This allows for e.g. -1 to be returned as 0xFF, 0xFFFF, etc.
// depending on the original width.
// If this is an unsigned value, simply return (false, u64).
pub fn as_wrapping_sint(&self) -> Result<(bool, u64), Error> {
match *self {
Arg::UInt(u) => Ok(u),
Arg::SInt(i) => Ok(i as u64),
Arg::UInt(u) => Ok((false, u)),
Arg::SInt(i, w) => {
// Need to shift twice in case w is 64.
debug_assert!(w > 0);
let mask = ((1u64 << (w - 1)) << 1).wrapping_sub(1);
let ui = (i as u64) & mask;
Ok((i < 0, ui))
}
_ => Err(Error::BadArgType),
}
}
@@ -90,7 +97,7 @@ pub fn as_float(&self) -> Result<f64, Error> {
match *self {
Arg::Float(f) => Ok(f),
Arg::UInt(u) => Ok(u as f64),
Arg::SInt(i) => Ok(i as f64),
Arg::SInt(i, _w) => Ok(i as f64),
_ => Err(Error::BadArgType),
}
}
@@ -174,7 +181,7 @@ macro_rules! impl_to_arg {
$(
impl<'a> ToArg<'a> for $t {
fn to_arg(self) -> Arg<'a> {
Arg::SInt(self as i64)
Arg::SInt(self as i64, <$t>::BITS as u8)
}
}
)*
@@ -204,6 +211,8 @@ mod tests {
#[test]
fn test_to_arg() {
const SIZE_WIDTH: u8 = isize::BITS as u8;
assert!(matches!("test".to_arg(), Arg::Str("test")));
assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
#[cfg(feature = "widestring")]
@@ -215,17 +224,17 @@ fn test_to_arg() {
assert!(matches!('x'.to_arg(), Arg::UInt(120)));
let mut usize_val: usize = 0;
assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
assert!(matches!(42i8.to_arg(), Arg::SInt(42)));
assert!(matches!(42i16.to_arg(), Arg::SInt(42)));
assert!(matches!(42i32.to_arg(), Arg::SInt(42)));
assert!(matches!(42i64.to_arg(), Arg::SInt(42)));
assert!(matches!(42isize.to_arg(), Arg::SInt(42)));
assert!(matches!(42i8.to_arg(), Arg::SInt(42, 8)));
assert!(matches!(42i16.to_arg(), Arg::SInt(42, 16)));
assert!(matches!(42i32.to_arg(), Arg::SInt(42, 32)));
assert!(matches!(42i64.to_arg(), Arg::SInt(42, 64)));
assert!(matches!(42isize.to_arg(), Arg::SInt(42, SIZE_WIDTH)));
assert_eq!((-42i8).to_arg(), Arg::SInt(-42));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42));
assert_eq!((-42i32).to_arg(), Arg::SInt(-42));
assert_eq!((-42i64).to_arg(), Arg::SInt(-42));
assert_eq!((-42isize).to_arg(), Arg::SInt(-42));
assert_eq!((-42i8).to_arg(), Arg::SInt(-42, 8));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42, 16));
assert_eq!((-42i32).to_arg(), Arg::SInt(-42, 32));
assert_eq!((-42i64).to_arg(), Arg::SInt(-42, 64));
assert_eq!((-42isize).to_arg(), Arg::SInt(-42, SIZE_WIDTH));
assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
assert!(matches!(42u16.to_arg(), Arg::UInt(42)));

View File

@@ -472,7 +472,7 @@ pub fn sprintf_locale(
// If someone passes us a negative value, format it with the width
// we were given.
let lower = conv_spec.is_lower();
let uint = arg.as_wrapping_sint()?;
let (_, uint) = arg.as_wrapping_sint()?;
if uint != 0 {
if flags.alt_form {
prefix = if lower { "0x" } else { "0X" };

View File

@@ -77,7 +77,7 @@ fn write_str(&mut self, _s: &str) -> fmt::Result {
#[test]
fn smoke() {
assert_fmt!("Hello, %s!", "world" => "Hello, world!");
assert_fmt!("Hello, %ls!", "world" => "Hello, world!"); // length modifier
assert_fmt!("Hello, %ls!", "world" => "Hello, world!");
assert_fmt!("Hello, world! %d %%%%", 3 => "Hello, world! 3 %%");
assert_fmt!("" => "");
}
@@ -225,7 +225,7 @@ fn test_int() {
assert_fmt!("%d", -123 => "-123");
assert_fmt!("~%d~", 148 => "~148~");
assert_fmt!("00%dxx", -91232 => "00-91232xx");
assert_fmt!("%x", -9232 => "ffffffffffffdbf0");
assert_fmt!("%x", -9232 => "ffffdbf0");
assert_fmt!("%X", 432 => "1B0");
assert_fmt!("%09X", 432 => "0000001B0");
assert_fmt!("%9X", 432 => " 1B0");
@@ -234,7 +234,6 @@ fn test_int() {
assert_fmt!("%2o", 4 => " 4");
assert_fmt!("% 12d", -4 => " -4");
assert_fmt!("% 12d", 48 => " 48");
// with length modifier
assert_fmt!("%ld", -4_i64 => "-4");
assert_fmt!("%lld", -4_i64 => "-4");
assert_fmt!("%lX", -4_i64 => "FFFFFFFFFFFFFFFC");
@@ -249,7 +248,6 @@ fn test_int() {
assert_fmt!("%9X", 492 => " 1EC");
assert_fmt!("% 12u", 4 => " 4");
assert_fmt!("% 12u", 48 => " 48");
// with length modifier
assert_fmt!("%lu", 4_u64 => "4");
assert_fmt!("%llu", 4_u64 => "4");
assert_fmt!("%lX", 4_u64 => "4");
@@ -416,7 +414,6 @@ fn test_float() {
assert_fmt1!("%f", 0.0, "0.000000");
assert_fmt1!("%g", 0.0, "0");
assert_fmt1!("%#g", 0.0, "0.00000");
// with length modifier
assert_fmt1!("%la", 0.0, "0x0p+0");
assert_fmt1!("%le", 0.0, "0.000000e+00");
assert_fmt1!("%lf", 0.0, "0.000000");
@@ -433,7 +430,7 @@ fn test_float() {
assert_fmt1!("%.4f", 1.03125, "1.0312"); /* 0x1.08p0 */
assert_fmt1!("%.2f", 1.375, "1.38");
assert_fmt1!("%.1f", 1.375, "1.4");
assert_fmt1!("%.1lf", 1.375, "1.4"); // length modifier
assert_fmt1!("%.1lf", 1.375, "1.4");
assert_fmt1!("%.15f", 1.1, "1.100000000000000");
assert_fmt1!("%.16f", 1.1, "1.1000000000000001");
assert_fmt1!("%.17f", 1.1, "1.10000000000000009");
@@ -758,8 +755,8 @@ fn test_errors() {
sprintf_err!("%1", => BadFormatString);
sprintf_err!("%%%k", => BadFormatString);
sprintf_err!("%B", => BadFormatString);
sprintf_err!("%lC", 'q' => BadFormatString); // length modifier
sprintf_err!("%lS", 'q' => BadFormatString); // length modifier
sprintf_err!("%lC", 'q' => BadFormatString);
sprintf_err!("%lS", 'q' => BadFormatString);
sprintf_err!("%d", => MissingArg);
sprintf_err!("%d %u", 1 => MissingArg);
sprintf_err!("%*d", 5 => MissingArg);

View File

@@ -42,6 +42,6 @@ The typical use is to run something, stop it with ctrl-z, and then continue it i
If only 123 and 789 exist, it will still background them and print an error about 456.
``bg 123 banana`` or ``bg banana 123`` will complain that "banana" is not a valid process ID.
``bg 123 banana`` or ``bg banana 123`` will complain that "banana" is not a valid job specifier.
``bg %2`` will background job 2.

View File

@@ -48,7 +48,7 @@ This refuses to store any immediate "vault", "mysql" or "ls" calls. Commands sta
function fish_should_add_to_history
# I don't want `git pull`s in my history when I'm in a specific repository
if string match -qr '^git pull' -- "$argv"
if string match -qr '^git pull'
and string match -qr "^/home/me/my-secret-project/" -- (pwd -P)
return 1
end

View File

@@ -21,7 +21,7 @@ A function is a list of commands that will be executed when the name of the func
The following options are available:
**-a** *NAMES* or **--argument-names** *NAMES*
Assigns the value of successive command-line arguments to the names given in *NAMES* (separated by spaces). These are the same arguments given in :envvar:`argv`, and are still available there (unless ``--inherit-variable argv`` was used or one of the given *NAMES* is ``argv``). See also :ref:`Argument Handling <variables-argv>`.
Has to be the last option. Assigns the value of successive command-line arguments to the names given in *NAMES* (separated by space). These are the same arguments given in :envvar:`argv`, and are still available there. See also :ref:`Argument Handling <variables-argv>`.
**-d** *DESCRIPTION* or **--description** *DESCRIPTION*
A description of what the function does, suitable as a completion description.
@@ -40,7 +40,7 @@ The following options are available:
Run this function when the variable *VARIABLE_NAME* changes value. Note that :program:`fish` makes no guarantees on any particular timing or even that the function will be run for every single ``set``. Rather it will be run when the variable has been set at least once, possibly skipping some values or being run when the variable has been set to the same value (except for universal variables set in other shells - only changes in the value will be picked up for those).
**-j** *PID* or **--on-job-exit** *PID*
Run this function when the job containing a child process with the given process ID *PID* exits. Instead of a PID, the string 'caller' can be specified. This is only allowed when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
Run this function when the job containing a child process with the given process identifier *PID* exits. Instead of a PID, the string 'caller' can be specified. This is only allowed when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
This will not trigger for :doc:`disowned <disown>` jobs.
**-p** *PID* or **--on-process-exit** *PID*

View File

@@ -3,10 +3,8 @@ LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV PIP_ROOT_USER_ACTION=ignore
RUN apk add --no-cache \
cmake ninja \
bash \
cargo \
g++ \
@@ -16,15 +14,11 @@ RUN apk add --no-cache \
musl-dev \
pcre2-dev \
py3-pexpect \
py3-pip \
python3 \
rust \
rustfmt \
sudo \
tmux
RUN pip install --break-system-packages black
RUN addgroup -g 1000 fishuser
RUN adduser \

View File

@@ -6,15 +6,12 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
curl \
g++-multilib \
gettext \
git \
locales \
openssl \
pkg-config \
python3 \
python3-pexpect \

View File

@@ -6,17 +6,14 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
clang \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
rustc \

View File

@@ -6,14 +6,12 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
gettext \
git \
locales \
openssl \
pkg-config \
python3 \
python3-pexpect \

View File

@@ -6,10 +6,8 @@ ENV LC_ALL=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
file \
g++ \
@@ -17,11 +15,10 @@ RUN apt-get update \
git \
libpcre2-dev \
locales \
openssl \
pkg-config \
python3 \
python3-pexpect \
rustc \
rust \
sudo \
tmux \
&& locale-gen en_US.UTF-8 \

View File

@@ -5,16 +5,13 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
sudo \

View File

@@ -5,16 +5,13 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
sudo \

View File

@@ -5,28 +5,22 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
clang \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
rustc \
sudo \
tmux \
python3-pip \
&& locale-gen en_US.UTF-8 \
&& apt-get clean
RUN pip install black
RUN groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
&& adduser fishuser sudo \

View File

@@ -5,16 +5,12 @@ ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
adduser \
&& apt-get -y install \
build-essential \
ca-certificates \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
python3 \
python3-pexpect \
tmux \

1662
po/de.po

File diff suppressed because it is too large Load Diff

1616
po/en.po

File diff suppressed because it is too large Load Diff

1624
po/fr.po

File diff suppressed because it is too large Load Diff

1600
po/pl.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1596
po/sv.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,46 @@
set -l bind_optspecs \
a/all \
e/erase \
M/mode= \
m/sets-mode= \
preset \
s/silent \
user
function __fish_bind_test1
set -l args
set -l use_keys no
for i in (commandline -pxc)
switch $i
case -k --k --ke --key
set use_keys yes
case "-*"
case "*"
set -a args $i
end
end
switch $use_keys
case yes
switch (count $args)
case 1
return 0
end
end
return 1
end
function __fish_bind_test2
set -l args
for i in (commandline -pxc)
switch $i
case "-*"
case "*"
set -a args $i
end
end
switch (count $args)
case 2
return 0
end
return 1
function __fish_bind_has_keys --inherit-variable bind_optspecs
argparse $bind_optspecs -- $argv 2>/dev/null
or return
test (count $argv) -ge 2
end
complete -c bind -f
@@ -26,10 +56,11 @@ complete -c bind -s s -l silent -d 'Operate silently'
complete -c bind -l preset -d 'Operate on preset bindings'
complete -c bind -l user -d 'Operate on user bindings'
complete -c bind -n '__fish_bind_has_keys (commandline -pcx)' -a '(bind --function-names)' -d 'Function name' -x
complete -c bind -n __fish_bind_test2 -a '(bind --function-names)' -d 'Function name' -x
function __fish_bind_complete --inherit-variable bind_optspecs
argparse $bind_optspecs -- (commandline -xpc)[2..] 2>/dev/null
function __fish_bind_complete
argparse M/mode= m/sets-mode= preset user s/silent \
a/all function-names list-modes e/erase -- (commandline -xpc)[2..] 2>/dev/null
or return 1
set -l token (commandline -ct)
if test (count $argv) = 0 && set -l prefix (string match -r -- '(.*,)?(ctrl-|alt-|shift-|super-)*' $token)

View File

@@ -41,7 +41,7 @@ complete -c find -o empty -d "File is empty and is either a regular file or a di
complete -c find -o executable -d "File is executable"
complete -c find -o false -d "Always false"
complete -c find -o fstype -d "File is on filesystem of specified type" -a "(__fish_print_filesystems)" -x
complete -c find -o gid -d "Numeric group ID of file" -x -a "(__fish_complete_group_ids)"
complete -c find -o gid -d "Numeric group id of file" -x -a "(__fish_complete_group_ids)"
complete -c find -o group -d "Group name of file" -x -a "(__fish_complete_groups)"
complete -c find -o ilname -d "File is symlink matching specified case insensitive pattern" -x

View File

@@ -1468,7 +1468,7 @@ complete -x -c git -n '__fish_git_using_command daemon' -l user-path -d 'Allow ~
complete -f -c git -n '__fish_git_using_command daemon' -l verbose -d 'Log all details'
complete -f -c git -n '__fish_git_using_command daemon' -l reuseaddr -d 'Reuse address when binding to listening server'
complete -f -c git -n '__fish_git_using_command daemon' -l detach -d 'Detach from shell'
complete -x -c git -n '__fish_git_using_command daemon' -l reuseaddr -d 'Save the process ID in file'
complete -x -c git -n '__fish_git_using_command daemon' -l reuseaddr -d 'Save the process id in file'
complete -x -c git -n '__fish_git_using_command daemon' -l user -d 'Change daemon\'s uid'
complete -x -c git -n '__fish_git_using_command daemon' -l group -d 'Change daemon\'s gid'
complete -x -c git -n '__fish_git_using_command daemon' -l enable -a 'upload-pack upload-archive receive-pack' -d 'Enable service'

View File

@@ -1,4 +1,6 @@
complete -c help -x -a '(__fish_print_commands)' -d 'Help for this command'
if test -d "$__fish_data_dir/man/man1/"
complete -c help -x -a '(__fish_print_commands)' -d 'Help for this command'
end
# Help topics in index.html
# This was semi-automated with `grep 'class="anchor"' -A1 /usr/share/doc/fish/index.html

View File

@@ -4,7 +4,7 @@ if string match -eq 'GNU coreutils' (id --version 2>&1)
complete id -s Z -l context -d "Print security context"
complete id -s z -l zero -d "Delimit entries with NUL"
complete id -s n -l name -d "Print name, not number"
complete id -s g -l group -d "Print effective group ID"
complete id -s g -l group -d "Print effective group id"
complete id -s G -l groups -d "Print all group ids"
complete id -s r -l real -d "Print real ID, not effective"
complete id -s u -l user -d "Print effective user ID"
@@ -24,7 +24,7 @@ else
complete id -s F -d "Print full name of the user"
complete id -s G -d "Print all group ids"
complete id -s P -d "Print as passwd file entry"
complete id -s g -d "Print effective group ID"
complete id -s g -d "Print effective group id"
complete id -s n -d "Print name, not number"
complete id -s p -d "Human-readable output"
complete id -s r -d "Print real ID, not effective"

View File

@@ -548,8 +548,8 @@ function __fish_complete_ip
attach "Attach process to network namespace" \
delete "Delete network namespace" \
set "Change network namespace attributes" \
identify "Display network namespace for a process ID" \
pids "Display process IDs of processes running in network namespace" \
identify "Display network namespace for a process id" \
pids "Display process ids of processes running in network namespace" \
monitor "Report as network namespace names are added and deleted" \
exec "Execute command in network namespace" \
help "Display help" \

View File

@@ -1,6 +1,6 @@
complete -c jobs -s h -l help -d 'Display help and exit'
complete -c jobs -s p -l pid -d "Show the process ID of each process in the job"
complete -c jobs -s g -l group -d "Show group ID of job"
complete -c jobs -s p -l pid -d "Show the process id of each process in the job"
complete -c jobs -s g -l group -d "Show group id of job"
complete -c jobs -s c -l command -d "Show commandname of each job"
complete -c jobs -s l -l last -d "Only show status for last job to be started"
complete -c jobs -s q -l quiet -l query -d "Check if a job exists without output"

View File

@@ -110,7 +110,7 @@ set -l maybe_filter_private_vars '
)'
# We do not *filter* these by the given scope because you might want to set e.g. a global to shadow a universal.
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -U | $maybe_filter_private_vars | string replace ' ' \t'Universal Variable: ')"
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -g | $maybe_filter_private_vars | string replace -r '^((?:history|fish_killring) ).*' '\$1' | string replace ' ' \t'Global Variable: ')"
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -g | $maybe_filter_private_vars | string replace -r '^((?:history|fish_killring) ).*' '$1' | string replace ' ' \t'Global Variable: ')"
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(set -l | $maybe_filter_private_vars | string replace ' ' \t'Local Variable: ')"
# Complete some fish configuration variables even if they aren't set.
complete -c set -n '__fish_is_nth_token 1; and not __fish_seen_argument -s e -l erase' -x -a "(__fish_complete_special_vars)"

View File

@@ -5,11 +5,7 @@ function __fish_cache_put
set -l dir (path dirname $cache_file)
chown --reference=$dir $cache_file 2>/dev/null ||
chown (
if stat --version 2>&1 | string match -q 'BusyBox*'
stat -c '%u:%g' $dir
else
stat --format '%u:%g' $dir 2>/dev/null ||
stat -f '%u:%g' $dir
end
stat --format '%u:%g' $dir 2>/dev/null ||
stat -f '%u:%g' $dir
) $cache_file
end

View File

@@ -10,8 +10,7 @@ set -l erase_line "$(
function __fish_echo --inherit-variable erase_line --description 'run the given command after the current commandline and redraw the prompt'
set -l line (commandline --line)
string >&2 repeat -N \n --count=(math (commandline | count) - $line + 1)
printf %s $erase_line >&2
$argv >&2
printf %s\n $erase_line($argv) >&2
string >&2 repeat -N \n --count=(math (count (fish_prompt)) - 1)
string >&2 repeat -N \n --count=(math $line - 1)
commandline -f repaint

View File

@@ -1,16 +0,0 @@
# localization: skip(private)
function __fish_list_files
set -l dir $argv[1]
if set -q __fish_data_dir[1]
# Construct a directory prefix without trailing slash.
if test -n "$dir"
set dir $__fish_data_dir/$dir
else
set dir $__fish_data_dir
end
set -l files $dir/**
string replace -- $dir/ '' $files
else
status list-files $dir
end
end

View File

@@ -1,11 +1,14 @@
# localization: skip(private)
function __fish_print_commands --description "Print a list of documented fish commands"
if set -q __fish_data_dir[1] && test -d $__fish_data_dir/man/man1/
printf %s\n $__fish_data_dir/man/man1/**.1*
else
status list-files man/man1/ 2>/dev/null
end |
string replace -r '.*/' '' |
for file in $__fish_data_dir/man/man1/**.1*
string replace -r '.*/' '' -- $file |
string replace -r '.1(.gz)?$' '' |
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes|terminal-compatibility)$'
end
end
status list-files man/man1/ 2>/dev/null |
string replace -r '.*/' '' -- $file |
string replace -r '.1(.gz)?$' '' |
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes|terminal-compatibility)$'
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes)$'
end

View File

@@ -1,10 +0,0 @@
# localization: skip(private)
function __fish_with_file
set -l file $argv[1]
set -l cmd $argv[2..]
if set -q __fish_data_dir[1]
$cmd $__fish_data_dir/$file
else
status get-file $file | $cmd
end
end

View File

@@ -1,5 +1,4 @@
# localization: tier3
function __ssh_history_completions -d "Retrieve `user@host` entries from history"
# Accept the typical hostname/ip chars, but no ":" at the end
history --prefix ssh --max=100 | string replace -rf '.* ([A-Za-z0-9._:-]+@[A-Za-z0-9._:-]*[A-Za-z0-9._-]).*' '$1'
history --prefix ssh --max=100 | string replace -rf '.* ([A-Za-z0-9._:-]+@[A-Za-z0-9._:-]+).*' '$1'
end

View File

@@ -103,7 +103,7 @@ function fish_add_path --description "Add paths to the PATH"
else
if set -q _flag_verbose
# print a message in verbose mode
printf (_ "No paths to add, not setting anything.\n")
printf (_ "No paths to add, not setting anything.\n") "$p"
end
return 1
end

View File

@@ -91,7 +91,7 @@ function fish_config --description "Launch fish's web based configuration"
switch $cmd
case show
set -l fish (status fish-path)
set -l prompts (dirs=$prompt_dir __fish_config_matching tools/web_config/sample_prompts .fish $argv)
set -l prompts (__fish_config_matching tools/web_config/sample_prompts .fish $argv)
for p in $prompts
set -l promptname (string replace -r '.*/([^/]*).fish$' '$1' $p)
echo -s (set_color --underline) $promptname (set_color normal)
@@ -240,7 +240,8 @@ function fish_config --description "Launch fish's web based configuration"
case show
set -l fish (status fish-path)
set -l themes \
(dirs=$dirs __fish_config_matching tools/web_config/themes .theme $argv)
(path filter $dirs/$argv.theme) \
(__fish_config_matching tools/web_config/themes .theme $argv)
set -l used_themes
echo -s (set_color normal; set_color --underline) Current (set_color normal)
@@ -383,16 +384,17 @@ function __fish_config_matching
set -l suffix $argv[2]
set -e argv[1..2]
set -l paths
if not set -q argv[1]
set paths $dirs/*$suffix
else
set paths (path filter $dirs/$argv$suffix)
end
if not set -q __fish_data_dir[1]
if set -q __fish_data_dir[1]
if not set -q argv[1]
set -a paths (status list-files $prefix)
set paths $__fish_data_dir/$prefix/*$suffix
else
set -a paths (status list-files $prefix | grep -Fx -e"$prefix/"$argv$suffix)
set paths (path filter $__fish_data_dir/$prefix/$argv$suffix)
end
else
if not set -q argv[1]
set paths (status list-files $prefix)
else
set paths (status list-files $prefix | grep -Fx -e"$prefix/"$argv$suffix)
end
end
string join \n $paths

View File

@@ -10,7 +10,7 @@ function funced --description 'Edit function definition'
end
if not set -q argv[1]
printf (_ "%s: Expected at least %d args, got only %d\n") funced 1 0
printf (_ "%ls: Expected at least %d args, got only %d\n") funced 1 0
return 1
end

View File

@@ -17,7 +17,7 @@ function funcsave --description "Save the current definition of all specified fu
end
if not set -q argv[1]
printf (_ "%s: Expected at least %d args, got only %d\n") funcsave 1 0 >&2
printf (_ "%ls: Expected at least %d args, got only %d\n") funcsave 1 0 >&2
return 1
end

View File

@@ -185,11 +185,11 @@ function history --description "display or manipulate interactive command histor
case clear # clear the interactive command history
if test -n "$search_mode"
or set -q show_time[1]
printf (_ "%s: %s: subcommand takes no options\n") history $hist_cmd >&2
printf (_ "%ls: %ls: subcommand takes no options\n") history $hist_cmd >&2
return 1
end
if set -q argv[1]
printf (_ "%s: %s: expected %d arguments; got %d\n") history $hist_cmd 0 (count $argv) >&2
printf (_ "%ls: %ls: expected %d arguments; got %d\n") history $hist_cmd 0 (count $argv) >&2
return 1
end
@@ -214,7 +214,7 @@ function history --description "display or manipulate interactive command histor
builtin history append $search_mode $show_time $max_count $_flag_case_sensitive $_flag_reverse $_flag_null -- $newitem
case '*'
printf "%s: unexpected subcommand '%s'\n" $cmd $hist_cmd
printf "%ls: unexpected subcommand '%ls'\n" $cmd $hist_cmd
return 2
end
end

View File

@@ -15,7 +15,7 @@ if not command -sq open
end
if not set -q argv[1]
printf (_ "%s: Expected at least %d args, got only %d\n") open 1 0 >&2
printf (_ "%ls: Expected at least %d args, got only %d\n") open 1 0 >&2
return 1
end

View File

@@ -21,6 +21,7 @@ from __future__ import print_function
from deroff import Deroffer
import argparse
import bz2
import codecs
import errno
import gzip
import os
@@ -733,7 +734,7 @@ class TypeDeroffManParser(ManParser):
# Raises IOError if it cannot be opened
def file_is_overwritable(path):
result = False
file = open(path, "r", encoding="utf-8")
file = codecs.open(path, "r", encoding="utf-8")
for line in file:
# Skip leading empty lines
line = line.strip()
@@ -892,7 +893,7 @@ def parse_manpage_at_path(manpage_path, output_directory):
else:
fullpath = os.path.join(output_directory, CMDNAME + ".fish")
try:
output_file = open(fullpath, "w", encoding="utf-8")
output_file = codecs.open(fullpath, "w", encoding="utf-8")
except IOError as err:
add_diagnostic(
"Unable to open file '%s': error(%d): %s"

View File

@@ -68,6 +68,14 @@ def find_executable(exe, paths=()):
return proposed_path
def isMacOS10_12_5_OrLater():
"""Return whether this system is macOS 10.12.5 or a later version."""
try:
return [int(x) for x in platform.mac_ver()[0].split(".")] >= [10, 12, 5]
except ValueError:
return False
def is_wsl():
"""Return whether we are running under the Windows Subsystem for Linux"""
if "linux" in platform.system().lower() and os.access("/proc/version", os.R_OK):
@@ -1415,7 +1423,7 @@ fish_bin_path = None
fish_bin_name = "fish.exe" if is_windows() else "fish"
if not fish_bin_dir:
print("The $__fish_bin_dir environment variable is not set. Looking in $PATH...")
print("The $__fish_bin_dir environment variable is not set. " "Looking in $PATH...")
fish_bin_path = find_executable(fish_bin_name)
if not fish_bin_path:
print("fish could not be found. Is fish installed correctly?")
@@ -1536,9 +1544,8 @@ print("%sHit ENTER to stop.%s" % (ENTER_BOLD_MODE, EXIT_ATTRIBUTE_MODE))
def runThing():
if os.environ.get("BROWSER") == "true":
# Don't start a browser in this case (see issue #11926)
pass
if isMacOS10_12_5_OrLater():
subprocess.check_call(["open", fileurl])
elif is_wsl():
cmd_path = find_executable("cmd.exe", COMMON_WSL_CMD_PATHS)
if cmd_path:
@@ -1599,15 +1606,7 @@ def capture_enter(port):
def get_windows_signal():
"""Using socket as a replacement for stdin on Windows."""
# The intent is to get a free port between 8000 and 9000, like for the HTTP
# server. But we already know that port 8000..PORT are not available. So
# starting from `PORT+1` is more efficient.
# More importantly though, Windows allows multiple sockets to bind to the
# same port in some circumstances (see SO_EXCLUSIVEADDRUSE documentation),
# and thus allows the signal socket to bind to the same port as HTTP.
# A browser may then end up reaching the wrong socket and causing
# fish_config to shutdown prematurely.
(sig, sig_port) = create_socket(PORT + 1, 9000)
(sig, sig_port) = create_socket(8000, 9000)
threading.Thread(target=capture_enter, args=(sig_port,)).start()
return sig

View File

@@ -163,10 +163,10 @@ fn describe(&self) -> WString {
let mut res = ast_kind_to_string(self.kind()).to_owned();
if let Some(n) = self.as_token() {
let token_type = n.token_type().to_wstr();
sprintf!(=> &mut res, " '%s'", token_type);
sprintf!(=> &mut res, " '%ls'", token_type);
} else if let Some(n) = self.as_keyword() {
let keyword = n.keyword().to_wstr();
sprintf!(=> &mut res, " '%s'", keyword);
sprintf!(=> &mut res, " '%ls'", keyword);
}
res
}
@@ -1447,23 +1447,23 @@ pub fn dump(&self, orig: &wstr) -> WString {
if let Kind::Argument(n) = node.kind() {
result += "argument";
if let Some(argsrc) = n.try_source(orig) {
sprintf!(=> &mut result, ": '%s'", argsrc);
sprintf!(=> &mut result, ": '%ls'", argsrc);
}
} else if let Some(n) = node.as_keyword() {
sprintf!(=> &mut result, "keyword: %s", n.keyword().to_wstr());
sprintf!(=> &mut result, "keyword: %ls", n.keyword().to_wstr());
} else if let Some(n) = node.as_token() {
let desc = match n.token_type() {
ParseTokenType::string => {
let mut desc = WString::from_str("string");
if let Some(strsource) = n.try_source(orig) {
sprintf!(=> &mut desc, ": '%s'", strsource);
sprintf!(=> &mut desc, ": '%ls'", strsource);
}
desc
}
ParseTokenType::redirection => {
let mut desc = WString::from_str("redirection");
if let Some(strsource) = n.try_source(orig) {
sprintf!(=> &mut desc, ": '%s'", strsource);
sprintf!(=> &mut desc, ": '%ls'", strsource);
}
desc
}
@@ -1804,7 +1804,7 @@ fn visit_mut<N: NodeMut>(&mut self, node: &mut N) -> VisitResult {
fn will_visit_fields_of<N: NodeMut>(&mut self, node: &mut N) {
FLOGF!(
ast_construction,
"%*swill_visit %s",
"%*swill_visit %ls",
self.spaces(),
"",
node.describe()
@@ -1881,7 +1881,7 @@ fn did_visit_fields_of<'a, N: NodeMut>(&'a mut self, node: &'a mut N, flow: Visi
self,
header_kw_range,
ParseErrorCode::generic,
"Missing end to balance this %s",
"Missing end to balance this %ls",
enclosing_stmt
);
} else {
@@ -1889,7 +1889,7 @@ fn did_visit_fields_of<'a, N: NodeMut>(&'a mut self, node: &'a mut N, flow: Visi
self,
token,
ParseErrorCode::generic,
"Expected %s, but found %s",
"Expected %ls, but found %ls",
keywords_user_presentable_description(error.allowed_keywords),
error.token.user_presentable_description(),
);
@@ -1907,14 +1907,14 @@ fn visit_optional_mut<N: NodeMut + CheckParse>(&mut self, node: &mut Option<N>)
fn keywords_user_presentable_description(kws: &'static [ParseKeyword]) -> WString {
assert!(!kws.is_empty(), "Should not be empty list");
if kws.len() == 1 {
return sprintf!("keyword '%s'", kws[0]);
return sprintf!("keyword '%ls'", kws[0]);
}
let mut res = L!("keywords ").to_owned();
for (i, kw) in kws.iter().enumerate() {
if i != 0 {
res += L!(" or ");
}
res += &sprintf!("'%s'", *kw)[..];
res += &sprintf!("'%ls'", *kw)[..];
}
res
}
@@ -2032,7 +2032,7 @@ fn list_kind_chomps_newlines(&self, kind: Kind) -> bool {
internal_error!(
self,
list_kind_chomps_newlines,
"Type %s not handled",
"Type %ls not handled",
ast_kind_to_string(kind)
);
}
@@ -2082,7 +2082,7 @@ fn list_kind_chomps_semis(&self, kind: Kind) -> bool {
internal_error!(
self,
list_kind_chomps_semis,
"Type %s not handled",
"Type %ls not handled",
ast_kind_to_string(kind)
);
}
@@ -2155,7 +2155,7 @@ fn consume_token_type(&mut self, typ: ParseTokenType) -> SourceRange {
self,
tok,
ParseErrorCode::generic,
"Expected %s, but found %s",
"Expected %ls, but found %ls",
token_type_user_presentable_description(typ, ParseKeyword::None),
tok.user_presentable_description()
);
@@ -2180,7 +2180,7 @@ fn consume_excess_token_generating_error(&mut self) {
self,
tok,
ParseErrorCode::generic,
"Expected %s, but found %s",
"Expected %ls, but found %ls",
token_type_user_presentable_description(ParseTokenType::string, ParseKeyword::None),
tok.user_presentable_description()
);
@@ -2219,7 +2219,7 @@ fn consume_excess_token_generating_error(&mut self) {
internal_error!(
self,
consume_excess_token_generating_error,
"Token %s should not have prevented parsing a job list",
"Token %ls should not have prevented parsing a job list",
tok.user_presentable_description()
);
}
@@ -2244,7 +2244,7 @@ fn consume_excess_token_generating_error(&mut self) {
self,
tok,
ParseErrorCode::generic,
"Expected a string, but found %s",
"Expected a string, but found %ls",
tok.user_presentable_description()
);
}
@@ -2253,7 +2253,7 @@ fn consume_excess_token_generating_error(&mut self) {
self,
tok,
ParseErrorCode::from(tok.tok_error),
"%s",
"%ls",
tok.tok_error
);
}
@@ -2275,7 +2275,7 @@ fn consume_excess_token_generating_error(&mut self) {
internal_error!(
self,
consume_excess_token_generating_error,
"Unexpected excess token type: %s",
"Unexpected excess token type: %ls",
tok.user_presentable_description()
);
}
@@ -2301,7 +2301,7 @@ fn populate_list<Contents, List>(&mut self, list: &mut List, exhaust_stream: boo
// Mark in the list that it was unwound.
FLOGF!(
ast_construction,
"%*sunwinding %s",
"%*sunwinding %ls",
self.spaces(),
"",
ast_kind_to_string(list.kind())
@@ -2379,7 +2379,7 @@ fn populate_list<Contents, List>(&mut self, list: &mut List, exhaust_stream: boo
FLOGF!(
ast_construction,
"%*s%s size: %u",
"%*s%ls size: %lu",
self.spaces(),
"",
ast_kind_to_string(list.kind()),
@@ -2403,7 +2403,7 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> Statement {
slf,
slf.peek_token(0),
ParseErrorCode::generic,
"Expected %s, but found %s",
"Expected %s, but found %ls",
token_type_user_presentable_description(
ParseTokenType::end,
ParseKeyword::None
@@ -2428,7 +2428,7 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> Statement {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected a command, but found %s",
"Expected a command, but found %ls",
self.peek_token(0).user_presentable_description()
);
return got_error(self);
@@ -2517,7 +2517,7 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> Statement {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected a command, but found %s",
"Expected a command, but found %ls",
self.peek_token(0).user_presentable_description()
);
return got_error(self);
@@ -2659,7 +2659,7 @@ fn visit_token(&mut self, token: &mut dyn Token) {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected %s, but found %s",
"Expected %ls, but found %ls",
token_types_user_presentable_description(token.allowed_tokens()),
self.peek_token(0).user_presentable_description()
);
@@ -2703,7 +2703,7 @@ fn visit_keyword(&mut self, keyword: &mut dyn Keyword) -> VisitResult {
self,
self.peek_token(0),
ParseErrorCode::generic,
"Expected %s, but found %s",
"Expected %ls, but found %ls",
keywords_user_presentable_description(allowed_keywords),
self.peek_token(0).user_presentable_description(),
);

View File

@@ -116,12 +116,12 @@ pub fn resolve_command(&mut self, cmd: &wstr, env: &dyn Environment) -> Option<A
match &path {
#[cfg(feature = "embed-data")]
AutoloadPath::Embedded(_) => {
FLOGF!(autoload, "Embedded: %s", cmd);
FLOGF!(autoload, "Embedded: %ls", cmd);
}
AutoloadPath::Path(path) => {
FLOGF!(
autoload,
"Loading %s from var %s from path %s",
"Loading %ls from var %ls from path %ls",
cmd,
self.env_var_name,
path
@@ -152,14 +152,14 @@ pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) {
AutoloadPath::Embedded(name) => {
use crate::common::str2wcstring;
use std::sync::Arc;
FLOGF!(autoload, "Loading embedded: %s", name);
FLOGF!(autoload, "Loading embedded: %ls", name);
let emfile = Asset::get(name).expect("Embedded file not found");
let src = str2wcstring(&emfile.data);
let mut widename = L!("embedded:").to_owned();
widename.push_str(name);
let ret = parser.eval_file_wstr(src, Arc::new(widename), &IoChain::new(), None);
if let Err(msg) = ret {
eprintf!("%s", msg);
eprintf!("%ls", msg);
}
}
}
@@ -527,8 +527,8 @@ fn touch_file(path: &wstr) {
.is_none());
assert!(autoload.get_autoloaded_commands().is_empty());
run!("touch %s/file1.fish", p1);
run!("touch %s/file2.fish", p2);
run!("touch %ls/file1.fish", p1);
run!("touch %ls/file2.fish", p2);
autoload.invalidate_cache();
assert!(!autoload.autoload_in_progress(L!("file1")));
@@ -586,11 +586,11 @@ fn touch_file(path: &wstr) {
autoload.resolve_command_impl(L!("file1"), paths),
AutoloadResult::Loaded
));
touch_file(&sprintf!("%s/file1.fish", p1));
touch_file(&sprintf!("%ls/file1.fish", p1));
autoload.invalidate_cache();
assert!(autoload.resolve_command_impl(L!("file1"), paths).is_some());
autoload.mark_autoload_finished(L!("file1"));
run!(L!("rm -Rf %s"), p1);
run!(L!("rm -Rf %s"), p2);
run!(L!("rm -Rf %ls"), p1);
run!(L!("rm -Rf %ls"), p2);
}

View File

@@ -156,7 +156,7 @@ fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool {
if waccess(&config_pathname, libc::R_OK) != 0 {
FLOGF!(
config,
"not sourcing %s (not readable or does not exist)",
"not sourcing %ls (not readable or does not exist)",
escaped_pathname
);
return false;
@@ -182,7 +182,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
let ret = parser.eval_file_wstr(src, fname, &IoChain::new(), None);
parser.libdata_mut().within_fish_init = false;
if let Err(msg) = ret {
eprintf!("%s", msg);
eprintf!("%ls", msg);
}
}
#[cfg(not(feature = "embed-data"))]
@@ -196,7 +196,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
let escaped_pathname = escape(&datapath);
FLOGF!(
error,
"Fish cannot find its asset files in '%s'.\n\
"Fish cannot find its asset files in '%ls'.\n\
Refusing to read configuration because of this.",
escaped_pathname,
);
@@ -287,7 +287,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
activate_flog_categories_by_pattern(w.woptarg.unwrap());
for cat in flog::categories::all_categories() {
if cat.enabled.load(Ordering::Relaxed) {
printf!("Debug enabled for category: %s\n", cat.name);
printf!("Debug enabled for category: %ls\n", cat.name);
}
}
}
@@ -315,7 +315,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
for cat in cats.iter() {
let desc = cat.description.localize();
// this is left-justified
printf!("%-*s %s\n", name_width, cat.name, desc);
printf!("%-*ls %ls\n", name_width, cat.name, desc);
}
return ControlFlow::Break(0);
}
@@ -342,21 +342,21 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
}
'?' => {
eprintf!(
"%s\n",
"%ls\n",
wgettext_fmt!(BUILTIN_ERR_UNKNOWN, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
}
':' => {
eprintf!(
"%s\n",
"%ls\n",
wgettext_fmt!(BUILTIN_ERR_MISSING, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
}
';' => {
eprintf!(
"%s\n",
"%ls\n",
wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
@@ -623,7 +623,7 @@ fn throwing_main() -> i32 {
if res.is_err() {
FLOGF!(
warning,
wgettext!("Error while reading file %s\n"),
wgettext!("Error while reading file %ls\n"),
path.to_string_lossy()
);
}
@@ -637,7 +637,10 @@ fn throwing_main() -> i32 {
parser.get_last_status()
};
event::fire(parser, Event::process_exit(Pid::new(getpid()), exit_status));
event::fire(
parser,
Event::process_exit(Pid::new(getpid()).unwrap(), exit_status),
);
// Trigger any exit handlers.
event::fire_generic(

View File

@@ -48,7 +48,7 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
if cmds.len() > 1 {
streams.err.append(wgettext_fmt!(
"%s: Cannot combine options %s\n",
"%ls: Cannot combine options %ls\n",
CMD,
join(&cmds, L!(", "))
));
@@ -63,26 +63,28 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
}
if !self.add && self.position.is_some() {
streams
.err
.append(wgettext_fmt!("%s: --position option requires --add\n", CMD));
streams.err.append(wgettext_fmt!(
"%ls: --position option requires --add\n",
CMD
));
return false;
}
if !self.add && self.regex_pattern.is_some() {
streams
.err
.append(wgettext_fmt!("%s: --regex option requires --add\n", CMD));
.append(wgettext_fmt!("%ls: --regex option requires --add\n", CMD));
return false;
}
if !self.add && self.function.is_some() {
streams
.err
.append(wgettext_fmt!("%s: --function option requires --add\n", CMD));
streams.err.append(wgettext_fmt!(
"%ls: --function option requires --add\n",
CMD
));
return false;
}
if !self.add && self.set_cursor_marker.is_some() {
streams.err.append(wgettext_fmt!(
"%s: --set-cursor option requires --add\n",
"%ls: --set-cursor option requires --add\n",
CMD
));
return false;
@@ -94,7 +96,7 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
.unwrap_or(false)
{
streams.err.append(wgettext_fmt!(
"%s: --set-cursor argument cannot be empty\n",
"%ls: --set-cursor argument cannot be empty\n",
CMD
));
return false;
@@ -180,7 +182,7 @@ fn abbr_list(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
const subcmd: &wstr = L!("--list");
if !opts.args.is_empty() {
streams.err.append(wgettext_fmt!(
"%s %s: Unexpected argument -- '%s'\n",
"%ls %ls: Unexpected argument -- '%ls'\n",
CMD,
subcmd,
&opts.args[0]
@@ -204,7 +206,7 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
if opts.args.len() != 2 {
streams.err.append(wgettext_fmt!(
"%s %s: Requires exactly two arguments\n",
"%ls %ls: Requires exactly two arguments\n",
CMD,
subcmd
));
@@ -213,15 +215,17 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
let old_name = &opts.args[0];
let new_name = &opts.args[1];
if old_name.is_empty() || new_name.is_empty() {
streams
.err
.append(wgettext_fmt!("%s %s: Name cannot be empty\n", CMD, subcmd));
streams.err.append(wgettext_fmt!(
"%ls %ls: Name cannot be empty\n",
CMD,
subcmd
));
return Err(STATUS_INVALID_ARGS);
}
if contains_whitespace(new_name) {
streams.err.append(wgettext_fmt!(
"%s %s: Abbreviation '%s' cannot have spaces in the word\n",
"%ls %ls: Abbreviation '%ls' cannot have spaces in the word\n",
CMD,
subcmd,
new_name.as_utfstr()
@@ -231,7 +235,7 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
abbrs::with_abbrs_mut(|abbrs| -> BuiltinResult {
if !abbrs.has_name(old_name) {
streams.err.append(wgettext_fmt!(
"%s %s: No abbreviation named %s\n",
"%ls %ls: No abbreviation named %ls\n",
CMD,
subcmd,
old_name.as_utfstr()
@@ -240,7 +244,7 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
if abbrs.has_name(new_name) {
streams.err.append(wgettext_fmt!(
"%s %s: Abbreviation %s already exists, cannot rename %s\n",
"%ls %ls: Abbreviation %ls already exists, cannot rename %ls\n",
CMD,
subcmd,
new_name.as_utfstr(),
@@ -276,7 +280,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
if opts.args.len() < 2 && opts.function.is_none() {
streams.err.append(wgettext_fmt!(
"%s %s: Requires at least two arguments\n",
"%ls %ls: Requires at least two arguments\n",
CMD,
subcmd
));
@@ -284,15 +288,17 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
if opts.args.is_empty() || opts.args[0].is_empty() {
streams
.err
.append(wgettext_fmt!("%s %s: Name cannot be empty\n", CMD, subcmd));
streams.err.append(wgettext_fmt!(
"%ls %ls: Name cannot be empty\n",
CMD,
subcmd
));
return Err(STATUS_INVALID_ARGS);
}
let name = &opts.args[0];
if name.chars().any(|c| c.is_whitespace()) {
streams.err.append(wgettext_fmt!(
"%s %s: Abbreviation '%s' cannot have spaces in the word\n",
"%ls %ls: Abbreviation '%ls' cannot have spaces in the word\n",
CMD,
subcmd,
name.as_utfstr()
@@ -313,15 +319,17 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
if let Err(error) = result {
streams.err.append(wgettext_fmt!(
"%s: Regular expression compile error: %s\n",
"%ls: Regular expression compile error: %ls\n",
CMD,
error.error_message(),
));
if let Some(offset) = error.offset() {
streams
.err
.append(wgettext_fmt!("%s: %s\n", CMD, regex_pattern.as_utfstr()));
streams.err.append(sprintf!("%s: %*s\n", CMD, offset, "^"));
.append(wgettext_fmt!("%ls: %ls\n", CMD, regex_pattern.as_utfstr()));
streams
.err
.append(sprintf!("%ls: %*ls\n", CMD, offset, "^"));
}
return Err(STATUS_INVALID_ARGS);
}
@@ -351,7 +359,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
// This is to prevent accidental usage of e.g. `--function 'string replace'`
if !valid_func_name(function) || contains_whitespace(function) {
streams.err.append(wgettext_fmt!(
"%s: Invalid function name: %s\n",
"%ls: Invalid function name: %ls\n",
CMD,
function.as_utfstr()
));
@@ -379,7 +387,7 @@ 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",
"%ls: --command cannot be combined with --position command",
CMD,
));
return Err(STATUS_INVALID_ARGS);
@@ -493,7 +501,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'p' => {
if opts.position.is_some() {
streams.err.append(wgettext_fmt!(
"%s: Cannot specify multiple positions\n",
"%ls: Cannot specify multiple positions\n",
CMD
));
return Err(STATUS_INVALID_ARGS);
@@ -504,7 +512,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
opts.position = Some(Position::Anywhere);
} else {
streams.err.append(wgettext_fmt!(
"%s: Invalid position '%s'\n",
"%ls: Invalid position '%ls'\n",
CMD,
w.woptarg.unwrap_or_default()
));
@@ -517,7 +525,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'r' => {
if opts.regex_pattern.is_some() {
streams.err.append(wgettext_fmt!(
"%s: Cannot specify multiple regex patterns\n",
"%ls: Cannot specify multiple regex patterns\n",
CMD
));
return Err(STATUS_INVALID_ARGS);
@@ -527,7 +535,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
SET_CURSOR_SHORT => {
if opts.set_cursor_marker.is_some() {
streams.err.append(wgettext_fmt!(
"%s: Cannot specify multiple set-cursor options\n",
"%ls: Cannot specify multiple set-cursor options\n",
CMD
));
return Err(STATUS_INVALID_ARGS);
@@ -550,7 +558,7 @@ 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",
"%ls: Warning: Option '%ls' was removed and is now ignored",
cmd,
argv_read[w.wopt_index - 1]
));

View File

@@ -10,7 +10,7 @@
localizable_consts!(
BUILTIN_ERR_INVALID_OPT_SPEC
"%s: Invalid option spec '%s' at char '%c'\n"
"%ls: Invalid option spec '%ls' at char '%lc'\n"
);
#[derive(Default)]
@@ -146,7 +146,7 @@ fn check_for_mutually_exclusive_flags(
std::mem::swap(&mut flag1, &mut flag2);
}
streams.err.append(wgettext_fmt!(
"%s: %s %s: options cannot be used together\n",
"%ls: %ls %ls: options cannot be used together\n",
opts.name,
flag1,
flag2
@@ -168,7 +168,7 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
let xflags: Vec<_> = raw_xflags.split(',').collect();
if xflags.len() < 2 {
streams.err.append(wgettext_fmt!(
"%s: exclusive flag string '%s' is not valid\n",
"%ls: exclusive flag string '%ls' is not valid\n",
opts.name,
raw_xflags
));
@@ -186,7 +186,7 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
exclusive_set.push(*short_equiv);
} else {
streams.err.append(wgettext_fmt!(
"%s: exclusive flag '%s' is not valid\n",
"%ls: exclusive flag '%ls' is not valid\n",
opts.name,
flag
));
@@ -215,7 +215,7 @@ fn parse_flag_modifiers<'args>(
&& s.char_at(0) != '&'
{
streams.err.append(wgettext_fmt!(
"%s: Implicit int short flag '%c' does not allow modifiers like '%c'\n",
"%ls: Implicit int short flag '%lc' does not allow modifiers like '%lc'\n",
opts.name,
opt_spec.short_flag,
s.char_at(0)
@@ -277,7 +277,7 @@ fn parse_flag_modifiers<'args>(
if opts.options.contains_key(&opt_spec.short_flag) {
streams.err.append(wgettext_fmt!(
"%s: Short flag '%c' already defined\n",
"%ls: Short flag '%lc' already defined\n",
opts.name,
opt_spec.short_flag
));
@@ -309,7 +309,7 @@ fn parse_option_spec_sep<'args>(
}
if opts.implicit_int_flag != '\0' {
streams.err.append(wgettext_fmt!(
"%s: Implicit int flag '%c' already defined\n",
"%ls: Implicit int flag '%lc' already defined\n",
opts.name,
opts.implicit_int_flag
));
@@ -351,7 +351,7 @@ fn parse_option_spec_sep<'args>(
'#' => {
if opts.implicit_int_flag != '\0' {
streams.err.append(wgettext_fmt!(
"%s: Implicit int flag '%c' already defined\n",
"%ls: Implicit int flag '%lc' already defined\n",
opts.name,
opts.implicit_int_flag
));
@@ -395,7 +395,7 @@ fn parse_option_spec<'args>(
) -> bool {
if option_spec.is_empty() {
streams.err.append(wgettext_fmt!(
"%s: An option spec must have at least a short or a long flag\n",
"%ls: An option spec must have at least a short or a long flag\n",
opts.name
));
return false;
@@ -405,7 +405,7 @@ fn parse_option_spec<'args>(
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' && !(s.char_at(0) == '/' && s.len() > 1)
{
streams.err.append(wgettext_fmt!(
"%s: Short flag '%c' invalid, must be alphanum or '#'\n",
"%ls: Short flag '%lc' invalid, must be alphanum or '#'\n",
opts.name,
s.char_at(0)
));
@@ -432,7 +432,7 @@ fn parse_option_spec<'args>(
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.append(wgettext_fmt!(
"%s: Long flag '%s' already defined\n",
"%ls: Long flag '%ls' already defined\n",
opts.name,
opt_spec.long_flag
));
@@ -480,7 +480,7 @@ fn collect_option_specs<'args>(
if *optind == argc {
streams
.err
.append(wgettext_fmt!("%s: Missing -- separator\n", cmd));
.append(wgettext_fmt!("%ls: Missing -- separator\n", cmd));
return Err(STATUS_INVALID_ARGS);
}
@@ -502,7 +502,7 @@ fn collect_option_specs<'args>(
if counter > counter_max {
streams
.err
.append(wgettext_fmt!("%s: Too many long-only options\n", cmd));
.append(wgettext_fmt!("%ls: Too many long-only options\n", cmd));
return Err(STATUS_INVALID_ARGS);
}
@@ -557,7 +557,7 @@ fn parse_cmd_opts<'args>(
ArgType::NoArgument
} else {
streams.err.append(wgettext_fmt!(
"%s: Invalid --unknown-arguments value '%s'\n",
"%ls: Invalid --unknown-arguments value '%ls'\n",
cmd,
kind
));
@@ -574,7 +574,7 @@ fn parse_cmd_opts<'args>(
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.append(wgettext_fmt!(
"%s: Invalid --min-args value '%s'\n",
"%ls: Invalid --min-args value '%ls'\n",
cmd,
w.woptarg.unwrap()
));
@@ -588,7 +588,7 @@ fn parse_cmd_opts<'args>(
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.append(wgettext_fmt!(
"%s: Invalid --max-args value '%s'\n",
"%ls: Invalid --max-args value '%ls'\n",
cmd,
w.woptarg.unwrap()
));
@@ -642,7 +642,7 @@ fn parse_cmd_opts<'args>(
// The user didn't specify any option specs.
streams
.err
.append(wgettext_fmt!("%s: Missing -- separator\n", cmd));
.append(wgettext_fmt!("%ls: Missing -- separator\n", cmd));
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -1,7 +1,5 @@
// Implementation of the bg builtin.
use std::collections::HashSet;
use crate::proc::Pid;
use super::prelude::*;
@@ -18,7 +16,7 @@ fn send_to_bg(
if !jobs[job_pos].wants_job_control() {
let job = &jobs[job_pos];
streams.err.append(wgettext_fmt!(
"%s: Can't put job %s, '%s' to background because it is not under job control\n",
"%ls: Can't put job %s, '%ls' to background because it is not under job control\n",
cmd,
job.job_id().to_wstring(),
job.command()
@@ -28,7 +26,7 @@ fn send_to_bg(
let job = &jobs[job_pos];
streams.err.append(wgettext_fmt!(
"Send job %s '%s' to background\n",
"Send job %s '%ls' to background\n",
job.job_id().to_wstring(),
job.command()
));
@@ -68,7 +66,7 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
let Some(job_pos) = job_pos else {
streams
.err
.append(wgettext_fmt!("%s: There are no suitable jobs\n", cmd));
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
return Err(STATUS_CMD_ERROR);
};
@@ -81,9 +79,14 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
let mut retval: BuiltinResult = Ok(SUCCESS);
let pids: Vec<Pid> = args[opts.optind..]
.iter()
.filter_map(|arg| match parse_pid(streams, cmd, arg) {
Ok(pid) => Some(pid),
.filter_map(|arg| match fish_wcstoi(arg).map(Pid::new) {
Ok(Some(pid)) => Some(pid),
_ => {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n",
cmd,
arg
));
retval = Err(STATUS_INVALID_ARGS);
None
}
@@ -94,16 +97,13 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
// Background all existing jobs that match the pids.
// Non-existent jobs aren't an error, but information about them is useful.
let mut seen = HashSet::new();
for pid in pids {
if let Some((job_pos, job)) = parser.job_get_with_index_from_pid(pid) {
if seen.insert(&*job as *const _) {
send_to_bg(parser, streams, cmd, job_pos)?;
}
if let Some((job_pos, _job)) = parser.job_get_with_index_from_pid(pid) {
send_to_bg(parser, streams, cmd, job_pos)?;
} else {
streams
.err
.append(wgettext_fmt!("%s: Could not find job '%d'\n", cmd, pid));
.append(wgettext_fmt!("%ls: Could not find job '%d'\n", cmd, pid));
}
}

View File

@@ -350,13 +350,13 @@ fn insert(
if !self.opts.silent {
if seq.len() == 1 {
streams.err.append(wgettext_fmt!(
"%s: No binding found for key '%s'\n",
"%ls: No binding found for key '%ls'\n",
cmd,
seq[0]
));
} else {
streams.err.append(wgettext_fmt!(
"%s: No binding found for key sequence '%s'\n",
"%ls: No binding found for key sequence '%ls'\n",
cmd,
eseq
));
@@ -432,7 +432,7 @@ fn parse_cmd_opts(
'h' => opts.print_help = true,
'k' => {
streams.err.append(wgettext_fmt!(
"%s: the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`\n",
"%ls: the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`\n",
cmd,
));
return Err(STATUS_INVALID_ARGS);
@@ -555,7 +555,7 @@ pub fn bind(
_ => {
streams
.err
.append(wgettext_fmt!("%s: Invalid state\n", cmd));
.append(wgettext_fmt!("%ls: Invalid state\n", cmd));
return Err(STATUS_CMD_ERROR);
}
}

View File

@@ -90,7 +90,7 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
if opts.erase {
if opts.scope != Scope::Unset {
streams.err.append(wgettext_fmt!(
"%s: Can not specify scope when removing block\n",
"%ls: Can not specify scope when removing block\n",
cmd
));
return Err(STATUS_INVALID_ARGS);
@@ -99,7 +99,7 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
if parser.global_event_blocks.load(Ordering::Relaxed) == 0 {
streams
.err
.append(wgettext_fmt!("%s: No blocks defined\n", cmd));
.append(wgettext_fmt!("%ls: No blocks defined\n", cmd));
return Err(STATUS_CMD_ERROR);
}
parser.global_event_blocks.fetch_sub(1, Ordering::Relaxed);

View File

@@ -1,5 +0,0 @@
use super::prelude::*;
pub fn r#break(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
builtin_break_continue(parser, streams, argv)
}

View File

@@ -1,44 +0,0 @@
use super::prelude::*;
use crate::parser::{Block, BlockType};
use crate::reader::reader_read;
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.append(wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT1,
cmd,
0,
argv.len() - 1
));
return Err(STATUS_INVALID_ARGS);
}
// If we're not interactive then we can't enter the debugger. So treat this command as a no-op.
if !parser.is_interactive() {
return Err(STATUS_CMD_ERROR);
}
// Ensure we don't allow creating a breakpoint at an interactive prompt. There may be a simpler
// or clearer way to do this but this works.
{
if parser
.block_at_index(1)
.map_or(true, |b| b.typ() == BlockType::breakpoint)
{
streams.err.append(wgettext_fmt!(
"%s: Command not valid at an interactive prompt\n",
cmd,
));
return Err(STATUS_ILLEGAL_CMD);
}
}
let bpb = parser.push_block(Block::breakpoint_block());
let io_chain = &streams.io_chain;
reader_read(parser, STDIN_FILENO, io_chain)?;
parser.pop_block(bpb);
BuiltinResult::from_dynamic(parser.get_last_status())
}

View File

@@ -39,7 +39,7 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
None => {
streams
.err
.append(wgettext_fmt!("%s: Could not find home directory\n", cmd));
.append(wgettext_fmt!("%ls: Could not find home directory\n", cmd));
return Err(STATUS_CMD_ERROR);
}
}
@@ -48,7 +48,7 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
// Stop `cd ""` from crashing
if dir_in.is_empty() {
streams.err.append(wgettext_fmt!(
"%s: Empty directory '%s' does not exist\n",
"%ls: Empty directory '%ls' does not exist\n",
cmd,
dir_in
));
@@ -63,7 +63,7 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
let dirs = path_apply_cdpath(dir_in, &pwd, vars);
if dirs.is_empty() {
streams.err.append(wgettext_fmt!(
"%s: The directory '%s' does not exist\n",
"%ls: The directory '%ls' does not exist\n",
cmd,
dir_in
));
@@ -132,37 +132,41 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
}
if best_errno == ENOTDIR {
streams
.err
.append(wgettext_fmt!("%s: '%s' is not a directory\n", cmd, dir_in));
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a directory\n",
cmd,
dir_in
));
} else if !broken_symlink.is_empty() {
streams.err.append(wgettext_fmt!(
"%s: '%s' is a broken symbolic link to '%s'\n",
"%ls: '%ls' is a broken symbolic link to '%ls'\n",
cmd,
broken_symlink,
broken_symlink_target
));
} else if best_errno == ELOOP {
streams.err.append(wgettext_fmt!(
"%s: Too many levels of symbolic links: '%s'\n",
"%ls: Too many levels of symbolic links: '%ls'\n",
cmd,
dir_in
));
} else if best_errno == ENOENT {
streams.err.append(wgettext_fmt!(
"%s: The directory '%s' does not exist\n",
"%ls: The directory '%ls' does not exist\n",
cmd,
dir_in
));
} else if best_errno == EACCES || best_errno == EPERM {
streams
.err
.append(wgettext_fmt!("%s: Permission denied: '%s'\n", cmd, dir_in));
streams.err.append(wgettext_fmt!(
"%ls: Permission denied: '%ls'\n",
cmd,
dir_in
));
} else {
errno::set_errno(Errno(best_errno));
wperror(L!("cd"));
streams.err.append(wgettext_fmt!(
"%s: Unknown error trying to locate directory '%s'\n",
"%ls: Unknown error trying to locate directory '%ls'\n",
cmd,
dir_in
));

View File

@@ -393,7 +393,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(cmd) = input_function_get_code(arg) else {
streams
.err
.append(wgettext_fmt!("%s: Unknown input function '%s'", cmd, arg));
.append(wgettext_fmt!("%ls: Unknown input function '%ls'", cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
return Err(STATUS_INVALID_ARGS);
};
@@ -517,7 +517,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Ok(new_coord) = usize::try_from(new_coord) else {
streams
.err
.append(wgettext_fmt!("%s: line/column index starts at 1", cmd));
.append(wgettext_fmt!("%ls: line/column index starts at 1", cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
return Err(STATUS_INVALID_ARGS);
};
@@ -529,7 +529,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
) else {
streams
.err
.append(wgettext_fmt!("%s: there is no line %s\n", cmd, arg));
.append(wgettext_fmt!("%ls: there is no line %ls\n", cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
return Err(STATUS_INVALID_ARGS);
};
@@ -544,7 +544,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
.unwrap_or(rstate.text.len());
if line_offset + new_coord > next_line_offset {
streams.err.append(wgettext_fmt!(
"%s: column %s exceeds line length\n",
"%ls: column %ls exceeds line length\n",
cmd,
arg
));
@@ -601,7 +601,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(selection) = rstate.selection else {
return Err(STATUS_CMD_ERROR);
};
streams.out.append(sprintf!("%u\n", selection.start));
streams.out.append(sprintf!("%lu\n", selection.start));
return Ok(SUCCESS);
}
@@ -609,7 +609,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(selection) = rstate.selection else {
return Err(STATUS_CMD_ERROR);
};
streams.out.append(sprintf!("%u\n", selection.end));
streams.out.append(sprintf!("%lu\n", selection.end));
return Ok(SUCCESS);
}
@@ -635,7 +635,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
transient = parser.libdata().transient_commandline.clone().unwrap();
current_buffer = &transient;
current_cursor_pos = transient.len();
} else if parser.interactive_initialized.load() || is_interactive_session() {
} else if is_interactive_session() {
current_buffer = &rstate.text;
current_cursor_pos = rstate.cursor_pos;
} else {
@@ -716,7 +716,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
} else {
streams
.out
.append(sprintf!("%u\n", current_cursor_pos - range.start));
.append(sprintf!("%lu\n", current_cursor_pos - range.start));
}
return Ok(SUCCESS);
}

View File

@@ -296,7 +296,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
}
} else {
streams.err.append(wgettext_fmt!(
"%s: Invalid token '%s'\n",
"%ls: Invalid token '%ls'\n",
cmd,
w.woptarg.unwrap()
));
@@ -318,7 +318,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if arg.is_empty() {
streams
.err
.append(wgettext_fmt!("%s: -s requires a non-empty string\n", cmd,));
.append(wgettext_fmt!("%ls: -s requires a non-empty string\n", cmd,));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -328,7 +328,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if arg.is_empty() {
streams
.err
.append(wgettext_fmt!("%s: -l requires a non-empty string\n", cmd,));
.append(wgettext_fmt!("%ls: -l requires a non-empty string\n", cmd,));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -338,7 +338,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if arg.is_empty() {
streams
.err
.append(wgettext_fmt!("%s: -o requires a non-empty string\n", cmd,));
.append(wgettext_fmt!("%ls: -o requires a non-empty string\n", cmd,));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -442,7 +442,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if let Err(err_text) = parse_util_detect_errors_in_argument_list(&comp, &prefix) {
streams.err.append(wgettext_fmt!(
"%s: %s: contains a syntax error\n",
"%ls: %ls: contains a syntax error\n",
cmd,
comp
));
@@ -458,7 +458,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
None => {
// No argument given, try to use the current commandline.
let commandline_state = commandline_get_state(true);
if !parser.interactive_initialized.load() && !is_interactive_session() {
if !is_interactive_session() {
streams.err.append(cmd);
streams
.err

View File

@@ -73,7 +73,7 @@ pub fn contains(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
} else {
streams
.err
.append(wgettext_fmt!("%s: Key not specified\n", cmd));
.append(wgettext_fmt!("%ls: Key not specified\n", cmd));
}
return Err(STATUS_CMD_ERROR);

View File

@@ -1,5 +0,0 @@
use super::prelude::*;
pub fn r#continue(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
builtin_break_continue(parser, streams, argv)
}

View File

@@ -3,8 +3,12 @@
use super::prelude::*;
use crate::io::IoStreams;
use crate::parser::Parser;
use crate::proc::{add_disowned_job, Job};
use crate::{builtins::shared::HelpOnlyCmdOpts, wchar::wstr, wutil::wgettext_fmt};
use crate::proc::{add_disowned_job, Job, Pid};
use crate::{
builtins::shared::HelpOnlyCmdOpts,
wchar::wstr,
wutil::{fish_wcstoi, wgettext_fmt},
};
use libc::SIGCONT;
/// Helper for builtin_disown.
@@ -23,7 +27,7 @@ fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) {
}
}
streams.err.append(wgettext_fmt!(
"%s: job %d ('%s') was stopped and has been signalled to continue.\n",
"%ls: job %d ('%ls') was stopped and has been signalled to continue.\n",
cmd,
j.job_id(),
j.command()
@@ -67,7 +71,7 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
} else {
streams
.err
.append(wgettext_fmt!("%s: There are no suitable jobs\n", cmd));
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
retval = Err(STATUS_CMD_ERROR);
}
} else {
@@ -76,23 +80,31 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
// If one argument is not a valid pid (i.e. integer >= 0), fail without disowning anything,
// but still print errors for all of them.
// Non-existent jobs aren't an error, but information about them is useful.
let mut jobs: Vec<_> = args[opts.optind..]
let mut jobs: Vec<_> = args[1..]
.iter()
.filter_map(|arg| {
let pid = match parse_pid(streams, cmd, arg) {
Ok(pid) => pid,
Err(code) => {
retval = Err(code);
return None;
// Attempt to convert the argument to a PID.
match fish_wcstoi(arg).ok().and_then(Pid::new) {
None => {
// Invalid identifier
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n",
cmd,
arg
));
retval = Err(STATUS_INVALID_ARGS);
None
}
};
parser.job_get_from_pid(pid).or_else(|| {
// Valid identifier but no such job
streams
.err
.append(wgettext_fmt!("%s: Could not find job '%d'\n", cmd, pid));
None
})
Some(pid) => parser.job_get_from_pid(pid).or_else(|| {
// Valid identifier but no such job
streams.err.append(wgettext_fmt!(
"%ls: Could not find job '%d'\n",
cmd,
pid
));
None
}),
}
})
.collect();

View File

@@ -16,7 +16,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));
.append(sprintf!(L!("%ls: expected event name\n"), cmd));
return Err(STATUS_INVALID_ARGS);
};

View File

@@ -1,5 +0,0 @@
use super::prelude::*;
pub fn r#false(_parser: &Parser, _streams: &mut IoStreams, _argv: &mut [&wstr]) -> BuiltinResult {
Err(STATUS_CMD_ERROR)
}

View File

@@ -1,6 +1,7 @@
//! Implementation of the fg builtin.
use crate::fds::make_fd_blocking;
use crate::proc::Pid;
use crate::reader::{reader_save_screen_state, reader_write_title};
use crate::tokenizer::tok_command;
use crate::wutil::perror;
@@ -37,7 +38,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
None => {
streams
.err
.append(wgettext_fmt!("%s: There are no suitable jobs\n", cmd));
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
return Err(STATUS_INVALID_ARGS);
}
Some((pos, j)) => {
@@ -48,35 +49,47 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
} else if optind + 1 < argv.len() {
// Specifying more than one job to put to the foreground is a syntax error, we still
// try to locate the job $argv[1], since we need to determine which error message to
// emit (ambiguous job specification vs malformed job ID).
// emit (ambiguous job specification vs malformed job id).
let mut found_job = false;
if let Ok(pid) = parse_pid(streams, cmd, argv[optind]) {
if let Ok(Some(pid)) = fish_wcstoi(argv[optind]).map(Pid::new) {
found_job = parser.job_get_from_pid(pid).is_some();
};
if found_job {
streams
.err
.append(wgettext_fmt!("%s: Ambiguous job\n", cmd));
.append(wgettext_fmt!("%ls: Ambiguous job\n", cmd));
} else {
streams
.err
.append(wgettext_fmt!("%s: '%s' is not a job\n", cmd, argv[optind]));
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a job\n",
cmd,
argv[optind]
));
}
builtin_print_error_trailer(parser, streams.err, cmd);
job_pos = None;
job = None;
} else {
match parse_pid(streams, cmd, argv[optind]) {
match fish_wcstoi(argv[optind]) {
Err(_) => {
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, argv[optind]));
job_pos = None;
job = None;
builtin_print_error_trailer(parser, streams.err, cmd);
}
Ok(pid) => {
let j = parser.job_get_with_index_from_pid(pid);
let raw_pid = pid;
let pid = Pid::new(pid.abs());
let j = pid.and_then(|pid| parser.job_get_with_index_from_pid(pid));
if j.as_ref()
.map_or(true, |(_pos, j)| !j.is_constructed() || j.is_completed())
{
streams
.err
.append(wgettext_fmt!("%s: No suitable job: %d\n", cmd, pid));
.append(wgettext_fmt!("%ls: No suitable job: %d\n", cmd, raw_pid));
job_pos = None;
job = None
} else {
@@ -84,23 +97,18 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
job_pos = Some(pos);
job = if !j.wants_job_control() {
streams.err.append(wgettext_fmt!(
"%s: Can't put job %d, '%s' to foreground because it is not under job control\n",
cmd,
pid,
j.command()
));
"%ls: Can't put job %d, '%ls' to foreground because it is not under job control\n",
cmd,
raw_pid,
j.command()
));
None
} else {
Some(j)
};
}
}
Err(_err) => {
job_pos = None;
job = None;
builtin_print_error_trailer(parser, streams.err, cmd);
}
};
}
};
let Some(job) = job else {

View File

@@ -997,7 +997,7 @@ enum OutputType {
if args.is_empty() && i == 0 {
if output_type == OutputType::File {
streams.err.appendln(wgettext_fmt!(
"Expected file path to read/write for -w:\n\n $ %s -w foo.fish",
"Expected file path to read/write for -w:\n\n $ %ls -w foo.fish",
PROGRAM_NAME.get().unwrap()
));
return Err(STATUS_CMD_ERROR);
@@ -1299,7 +1299,7 @@ fn html_colorize(text: &wstr, colors: &[HighlightSpec]) -> Vec<u8> {
html.push_str("</span>");
}
if i == 0 || color != last_color {
sprintf!(=> &mut html, "<span class=\"%s\">", html_class_name_for_color(color));
sprintf!(=> &mut html, "<span class=\"%ls\">", html_class_name_for_color(color));
}
last_color = color;

View File

@@ -200,7 +200,7 @@ fn parse_flags(
}
'v' => {
streams.out.appendln(wgettext_fmt!(
"%s, version %s",
"%ls, version %s",
PROGRAM_NAME.get().unwrap(),
crate::BUILD_VERSION
));

View File

@@ -77,7 +77,7 @@ fn parse_cmd_opts(
argv: &mut [&wstr],
parser: &Parser,
streams: &mut IoStreams,
) -> BuiltinResult {
) -> c_int {
let cmd = L!("function");
let print_hints = false;
let mut handling_named_arguments = false;
@@ -94,20 +94,20 @@ fn parse_cmd_opts(
if handling_named_arguments {
if is_read_only(&woptarg) {
streams.err.append(wgettext_fmt!(
"%s: variable '%s' is read-only\n",
"%ls: variable '%ls' is read-only\n",
cmd,
woptarg
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
opts.named_arguments.push(woptarg);
} else {
streams.err.append(wgettext_fmt!(
"%s: %s: unexpected positional argument",
"%ls: %ls: unexpected positional argument",
cmd,
woptarg
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
}
'd' => {
@@ -116,11 +116,11 @@ fn parse_cmd_opts(
's' => {
let Some(signal) = Signal::parse(w.woptarg.unwrap()) else {
streams.err.append(wgettext_fmt!(
"%s: Unknown signal '%s'",
"%ls: Unknown signal '%ls'",
cmd,
w.woptarg.unwrap()
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
};
opts.events.push(EventDescription::Signal { signal });
}
@@ -130,7 +130,7 @@ fn parse_cmd_opts(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, name));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
opts.events.push(EventDescription::Variable { name });
}
@@ -149,26 +149,34 @@ fn parse_cmd_opts(
};
if caller_id == 0 {
streams.err.append(wgettext_fmt!(
"%s: calling job for event handler not found",
"%ls: calling job for event handler not found",
cmd
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
e = EventDescription::CallerExit { caller_id };
} else if opt == 'p' && woptarg == "%self" {
let pid = Pid::new(getpid());
e = EventDescription::ProcessExit { pid: Some(pid) };
e = EventDescription::ProcessExit { pid };
} else {
let pid = parse_pid_may_be_zero(streams, cmd, woptarg)?;
let Ok(pid @ 0..) = fish_wcstoi(woptarg) else {
streams.err.append(wgettext_fmt!(
"%ls: %ls: invalid process id",
cmd,
woptarg
));
return STATUS_INVALID_ARGS;
};
if opt == 'p' {
e = EventDescription::ProcessExit { pid };
e = EventDescription::ProcessExit { pid: Pid::new(pid) };
} else {
// TODO: rationalize why a default of 0 is sensible.
let internal_job_id = pid
.and_then(|pid| job_id_for_pid(pid, parser))
.unwrap_or_default();
let internal_job_id = match Pid::new(pid) {
Some(pid) => job_id_for_pid(pid, parser).unwrap_or(0),
None => 0,
};
e = EventDescription::JobExit {
pid,
pid: Pid::new(pid),
internal_job_id,
};
}
@@ -179,11 +187,11 @@ fn parse_cmd_opts(
let name = w.woptarg.unwrap().to_owned();
if is_read_only(&name) {
streams.err.append(wgettext_fmt!(
"%s: variable '%s' is read-only\n",
"%ls: variable '%ls' is read-only\n",
cmd,
name
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
handling_named_arguments = true;
opts.named_arguments.push(name);
@@ -200,7 +208,7 @@ fn parse_cmd_opts(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, woptarg));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
opts.inherit_vars.push(woptarg.to_owned());
}
@@ -209,7 +217,7 @@ fn parse_cmd_opts(
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
';' => {
builtin_unexpected_argument(
@@ -219,11 +227,11 @@ fn parse_cmd_opts(
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
other => {
panic!("Unexpected retval from WGetopter: {}", other);
@@ -232,39 +240,27 @@ fn parse_cmd_opts(
}
*optind = w.wopt_index;
Ok(SUCCESS)
STATUS_CMD_OK
}
fn validate_function_name(
argv: &mut [&wstr],
function_name: &mut WString,
cmd: &wstr,
streams: &mut IoStreams,
) -> BuiltinResult {
if argv.len() < 2 {
streams
.err
.append(wgettext_fmt!("%ls: function name required", cmd));
return Err(STATUS_INVALID_ARGS);
}
*function_name = argv[1].to_owned();
fn validate_function_name(function_name: &wstr, cmd: &wstr, streams: &mut IoStreams) -> c_int {
if !valid_func_name(function_name) {
streams.err.append(wgettext_fmt!(
"%s: %s: invalid function name",
"%ls: %ls: invalid function name",
cmd,
function_name,
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
if parser_keywords_is_reserved(function_name) {
streams.err.append(wgettext_fmt!(
"%s: %s: cannot use reserved keyword as function name",
"%ls: %ls: cannot use reserved keyword as function name",
cmd,
function_name
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
Ok(SUCCESS)
STATUS_CMD_OK
}
/// Define a function. Calls into `function.rs` to perform the heavy lifting of defining a
@@ -275,7 +271,7 @@ pub fn function(
streams: &mut IoStreams,
c_args: &mut [&wstr],
func_node: NodeRef<BlockStatement>,
) -> BuiltinResult {
) -> c_int {
// The wgetopt function expects 'function' as the first argument. Make a new vec with
// that property. This is needed because this builtin has a different signature than the other
// builtins.
@@ -285,17 +281,23 @@ pub fn function(
let cmd = argv[0];
// A valid function name has to be the first argument.
let mut function_name = WString::new();
validate_function_name(argv, &mut function_name, cmd, streams)?;
let function_name = argv[1].to_owned();
let mut retval = validate_function_name(&function_name, cmd, streams);
if retval != STATUS_CMD_OK {
return retval;
}
let argv = &mut argv[1..];
let mut opts = FunctionCmdOpts::default();
let mut optind = 0;
parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams)?;
retval = parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams);
if retval != STATUS_CMD_OK {
return retval;
}
if opts.print_help {
builtin_print_error_trailer(parser, streams.err, cmd);
return Ok(SUCCESS);
return STATUS_CMD_OK;
}
if argv.len() != optind {
@@ -306,17 +308,17 @@ pub fn function(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, arg));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
opts.named_arguments.push(arg.to_owned());
}
} else {
streams.err.append(wgettext_fmt!(
"%s: %s: unexpected positional argument",
"%ls: %ls: unexpected positional argument",
cmd,
argv[optind],
));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
}
@@ -341,7 +343,7 @@ pub fn function(
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, named));
return Err(STATUS_INVALID_ARGS);
return STATUS_INVALID_ARGS;
}
}
@@ -396,5 +398,5 @@ pub fn function(
}
}
Ok(SUCCESS)
STATUS_CMD_OK
}

View File

@@ -172,7 +172,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if let Some(desc) = opts.description {
if args.len() != 1 {
streams.err.append(wgettext_fmt!(
"%s: Expected exactly one function name\n",
"%ls: Expected exactly one function name\n",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
@@ -182,7 +182,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !function::exists(current_func, parser) {
streams.err.append(wgettext_fmt!(
"%s: Function '%s' does not exist\n",
"%ls: Function '%ls' does not exist\n",
cmd,
current_func
));
@@ -280,7 +280,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
&& !event::EVENT_FILTER_NAMES.contains(&opts.handlers_type.unwrap())
{
streams.err.append(wgettext_fmt!(
"%s: Expected generic | variable | signal | exit | job-id for --handlers-type\n",
"%ls: Expected generic | variable | signal | exit | job-id for --handlers-type\n",
cmd
));
return Err(STATUS_INVALID_ARGS);
@@ -320,7 +320,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if opts.copy {
if args.len() != 2 {
streams.err.append(wgettext_fmt!(
"%s: Expected exactly two names (current function name, and new function name)\n",
"%ls: Expected exactly two names (current function name, and new function name)\n",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
@@ -331,7 +331,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !function::exists(current_func, parser) {
streams.err.append(wgettext_fmt!(
"%s: Function '%s' does not exist\n",
"%ls: Function '%ls' does not exist\n",
cmd,
current_func
));
@@ -341,7 +341,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !valid_func_name(new_func) || parser_keywords_is_reserved(new_func) {
streams.err.append(wgettext_fmt!(
"%s: Illegal function name '%s'\n",
"%ls: Illegal function name '%ls'\n",
cmd,
new_func
));
@@ -351,7 +351,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if function::exists(new_func, parser) {
streams.err.append(wgettext_fmt!(
"%s: Function '%s' already exists. Cannot create copy of '%s'\n",
"%ls: Function '%ls' already exists. Cannot create copy of '%ls'\n",
cmd,
new_func,
current_func
@@ -391,7 +391,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
}
Some(path) => {
comment.push_utfstr(&wgettext_fmt!(
"Defined in %s @ line %d",
"Defined in %ls @ line %d",
path,
props.definition_lineno()
));
@@ -406,7 +406,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
}
Some(path) => {
comment.push_utfstr(&wgettext_fmt!(
", copied in %s @ line %d",
", copied in %ls @ line %d",
path,
props.copy_definition_lineno()
));
@@ -420,7 +420,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !comment.is_empty() {
def.push_utfstr(&sprintf!(
"# %s\n%s",
"# %ls\n%ls",
comment,
props.annotated_definition(arg)
));

View File

@@ -1,14 +0,0 @@
use super::prelude::*;
/// Used for the fish `_` builtin for requesting translations.
/// For scripts in `share/`, the corresponding strings are extracted from the scripts using
/// `build_tools/fish_xgettext.fish`.
/// Strings not present in our repo would require a custom MO file for translation to be possible.
pub fn gettext(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
for arg in &argv[1..] {
streams.out.append(
crate::wutil::LocalizableString::from_external_source((*arg).to_owned()).localize(),
);
}
Ok(SUCCESS)
}

View File

@@ -113,7 +113,7 @@ fn check_for_unexpected_hist_args(
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.append(wgettext_fmt!(
"%s: %s: subcommand takes no options\n",
"%ls: %ls: subcommand takes no options\n",
cmd,
subcmd_str
));
@@ -335,7 +335,7 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
if in_private_mode(parser.vars()) {
streams.err.append(wgettext_fmt!(
"%s: can't merge history in private mode\n",
"%ls: can't merge history in private mode\n",
cmd
));
return Err(STATUS_INVALID_ARGS);

View File

@@ -5,7 +5,7 @@
use crate::io::IoStreams;
use crate::job_group::{JobId, MaybeJobId};
use crate::parser::Parser;
use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job};
use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job, Pid};
use crate::wchar_ext::WExt;
use crate::wgetopt::{wopt, ArgType, WGetopter, WOption};
use crate::wutil::wgettext;
@@ -22,7 +22,7 @@ enum JobsPrintMode {
Default, // print lots of general info
PrintPid, // print pid of each process in job
PrintCommand, // print command name of each process in job
PrintGroup, // print group ID of job
PrintGroup, // print group id of job
PrintNothing, // print nothing (exit status only)
}
@@ -110,7 +110,7 @@ fn builtin_jobs_print(j: &Job, mode: JobsPrintMode, header: bool, streams: &mut
}
for p in j.processes() {
out += &sprintf!("%s\n", p.argv0().unwrap())[..];
out += &sprintf!("%ls\n", p.argv0().unwrap())[..];
}
streams.out.append(out);
}
@@ -196,7 +196,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
match fish_wcstoi(&arg[1..]).ok().filter(|&job_id| job_id >= 0) {
None => {
streams.err.append(wgettext_fmt!(
"%s: '%s' is not a valid job ID\n",
"%ls: '%ls' is not a valid job id\n",
cmd,
arg
));
@@ -214,8 +214,19 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
}
} else {
let pid = parse_pid(streams, cmd, arg)?;
j = parser.job_get_from_pid(pid)
match fish_wcstoi(arg).ok().and_then(Pid::new) {
None => {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid process id\n",
cmd,
arg
));
return Err(STATUS_INVALID_ARGS);
}
Some(job_id) => {
j = parser.job_get_from_pid(job_id);
}
}
}
if let Some(j) = j.filter(|j| !j.is_completed() && j.is_constructed()) {
@@ -225,7 +236,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if mode != JobsPrintMode::PrintNothing {
streams
.err
.append(wgettext_fmt!("%s: No suitable job: %s\n", cmd, arg));
.append(wgettext_fmt!("%ls: No suitable job: %ls\n", cmd, arg));
}
return Err(STATUS_CMD_ERROR);
}
@@ -245,7 +256,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if !streams.out_is_redirected && mode != JobsPrintMode::PrintNothing {
streams
.out
.append(wgettext_fmt!("%s: There are no jobs\n", argv[0]));
.append(wgettext_fmt!("%ls: There are no jobs\n", argv[0]));
}
return Err(STATUS_CMD_ERROR);
}

View File

@@ -69,7 +69,7 @@ fn parse_cmd_opts(
if scale < 0 || scale > 15 {
streams
.err
.append(wgettext_fmt!("%s: %s: invalid scale\n", cmd, optarg));
.append(wgettext_fmt!("%ls: %ls: invalid scale\n", cmd, optarg));
return Err(STATUS_INVALID_ARGS);
}
// We know the value is in the range [0, 15]
@@ -89,7 +89,7 @@ fn parse_cmd_opts(
} else {
streams
.err
.append(wgettext_fmt!("%s: %s: invalid mode\n", cmd, optarg));
.append(wgettext_fmt!("%ls: %ls: invalid mode\n", cmd, optarg));
return Err(STATUS_INVALID_ARGS);
}
}
@@ -103,7 +103,7 @@ fn parse_cmd_opts(
let base = fish_wcstoi(optarg).unwrap_or(-1);
if base != 8 && base != 16 {
streams.err.append(wgettext_fmt!(
"%s: %s: invalid base value\n",
"%ls: %ls: invalid base value\n",
cmd,
optarg
));
@@ -159,7 +159,7 @@ fn format_double(mut v: f64, opts: &Options) -> WString {
if opts.base == 16 {
v = v.trunc();
let mneg = if v.is_sign_negative() { "-" } else { "" };
return sprintf!("%s0x%x", mneg, v.abs() as u64);
return sprintf!("%s0x%lx", mneg, v.abs() as u64);
} else if opts.base == 8 {
v = v.trunc();
if v == 0.0 {
@@ -167,7 +167,7 @@ fn format_double(mut v: f64, opts: &Options) -> WString {
return WString::from_str("0");
}
let mneg = if v.is_sign_negative() { "-" } else { "" };
return sprintf!("%s0%o", mneg, v.abs() as u64);
return sprintf!("%s0%lo", mneg, v.abs() as u64);
}
v *= pow(10f64, opts.scale);
@@ -245,24 +245,24 @@ fn evaluate_expression(
streams
.err
.append(sprintf!("%s: Error: %s\n", cmd, error_message));
streams.err.append(sprintf!("'%s'\n", expression));
.append(sprintf!("%ls: Error: %ls\n", cmd, error_message));
streams.err.append(sprintf!("'%ls'\n", expression));
Err(STATUS_CMD_ERROR)
}
Err(err) => {
streams.err.append(sprintf!(
L!("%s: Error: %s\n"),
L!("%ls: Error: %ls\n"),
cmd,
err.kind.describe_wstr()
));
streams.err.append(sprintf!("'%s'\n", expression));
streams.err.append(sprintf!("'%ls'\n", 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));
streams.err.append(sprintf!("%ls^%ls^\n", padding, tildes));
} else {
streams.err.append(sprintf!("%s^\n", padding));
streams.err.append(sprintf!("%ls^\n", padding));
}
Err(STATUS_CMD_ERROR)

View File

@@ -5,28 +5,23 @@
pub mod bg;
pub mod bind;
pub mod block;
pub mod r#break;
pub mod breakpoint;
pub mod builtin;
pub mod cd;
pub mod command;
pub mod commandline;
pub mod complete;
pub mod contains;
pub mod r#continue;
pub mod count;
pub mod disown;
pub mod echo;
pub mod emit;
pub mod eval;
pub mod exit;
pub mod r#false;
pub mod fg;
pub mod fish_indent;
pub mod fish_key_reader;
pub mod function;
pub mod functions;
pub mod r#gettext;
pub mod history;
pub mod jobs;
pub mod math;
@@ -43,7 +38,6 @@
pub mod status;
pub mod string;
pub mod test;
pub mod r#true;
pub mod r#type;
pub mod ulimit;
pub mod wait;

View File

@@ -283,7 +283,7 @@ 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'\n", "path", t);
path_error!(streams, "%ls: Invalid type '%ls'\n", "path", t);
return Err(STATUS_INVALID_ARGS);
};
*types |= r#type;
@@ -295,7 +295,7 @@ 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'\n", "path", p);
path_error!(streams, "%ls: Invalid permission '%ls'\n", "path", p);
return Err(STATUS_INVALID_ARGS);
};
*perms |= perm;
@@ -709,7 +709,7 @@ fn path_sort(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
}
None => wbasename,
Some(k) => {
path_error!(streams, "%s: Invalid sort key '%s'\n", args[0], k);
path_error!(streams, "%ls: Invalid sort key '%ls'\n", args[0], k);
return Err(STATUS_INVALID_ARGS);
}
};

View File

@@ -208,10 +208,10 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
if errcode != None && errcode != Some(Error::InvalidChar) && errcode != Some(Error::Empty) {
match errcode.unwrap() {
Error::Overflow => {
self.fatal_error(sprintf!("%s: %s", s, wgettext!("Number out of range")));
self.fatal_error(sprintf!("%ls: %ls", s, wgettext!("Number out of range")));
}
Error::Empty => {
self.fatal_error(sprintf!("%s: %s", s, wgettext!("Number was empty")));
self.fatal_error(sprintf!("%ls: %ls", s, wgettext!("Number was empty")));
}
Error::InvalidChar => {
panic!("Unreachable");
@@ -219,11 +219,11 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
}
} 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(wgettext_fmt!("%ls: expected a numeric value", s));
} else {
// This isn't entirely fatal - the value should still be printed.
self.nonfatal_error(wgettext_fmt!(
"%s: value not completely converted (can't convert '%s')",
"%ls: value not completely converted (can't convert '%ls')",
s,
end
));
@@ -472,7 +472,10 @@ 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(wgettext_fmt!(
"invalid field width: %ls",
argv[0]
));
}
argv = &argv[1..];
argc -= 1;
@@ -501,7 +504,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
precision = Some(-1);
} else if (c_int::MAX as i64) < prec {
self.fatal_error(wgettext_fmt!(
"invalid precision: %s",
"invalid precision: %ls",
argv[0]
));
} else {
@@ -527,7 +530,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
let conversion = f.char_at(0);
if (conversion as usize) > 0xFF || !ok[conversion as usize] {
self.fatal_error(wgettext_fmt!(
"%.*s: invalid conversion specification",
"%.*ls: invalid conversion specification",
wstr_offset_in(f, directive_start) + 1,
directive_start
));

Some files were not shown because too many files have changed in this diff Show More