mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-04-19 14:51:13 -03:00
Compare commits
104 Commits
update-def
...
Integratio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56bbdb3f39 | ||
|
|
ce4aa7669d | ||
|
|
af8d8d3d1b | ||
|
|
fecd0b4bf1 | ||
|
|
2d4a43302a | ||
|
|
5a4a913220 | ||
|
|
904ceba858 | ||
|
|
3c2adfbd4b | ||
|
|
9e4850b40a | ||
|
|
211b3f6670 | ||
|
|
d530e127f5 | ||
|
|
a6698098db | ||
|
|
f73b260a3a | ||
|
|
ffa7abd6ff | ||
|
|
b87ef689fa | ||
|
|
4fe70f6965 | ||
|
|
7001abca9f | ||
|
|
199316f1a3 | ||
|
|
78c9ab29cd | ||
|
|
91c9dbdd89 | ||
|
|
e737ad1f0f | ||
|
|
6f536c6304 | ||
|
|
a1b4b391b2 | ||
|
|
a27f615350 | ||
|
|
46ce8a1d2f | ||
|
|
03ccc88868 | ||
|
|
79cf4c3e0b | ||
|
|
701e1a3b02 | ||
|
|
b1ec703ceb | ||
|
|
553612f74a | ||
|
|
861decb003 | ||
|
|
3b8780aa6c | ||
|
|
7783505bee | ||
|
|
23c25ffe43 | ||
|
|
7619fa316c | ||
|
|
e2005c64b3 | ||
|
|
9ada3e6c16 | ||
|
|
bdba2c227d | ||
|
|
201882e72a | ||
|
|
1db0ff9f77 | ||
|
|
9258275fe6 | ||
|
|
6900b89c82 | ||
|
|
9c0086b7af | ||
|
|
e200abe39c | ||
|
|
0c8f1f4220 | ||
|
|
e593da1c2e | ||
|
|
6666c8f1cd | ||
|
|
3e61036911 | ||
|
|
f23a479b81 | ||
|
|
e274ff41d0 | ||
|
|
d2af306f3d | ||
|
|
0e7c7f1745 | ||
|
|
3fada80553 | ||
|
|
c7d4acbef8 | ||
|
|
e204a4c126 | ||
|
|
9af33802ec | ||
|
|
eecf0814a1 | ||
|
|
1ceebdf580 | ||
|
|
335f91babd | ||
|
|
ec66749369 | ||
|
|
6e9e33d81d | ||
|
|
f3ebc68d5d | ||
|
|
4be17bfefb | ||
|
|
6fd0025f38 | ||
|
|
052fc18db9 | ||
|
|
63a08e53e5 | ||
|
|
62ac23453e | ||
|
|
c052beb4dd | ||
|
|
e0cabacdaa | ||
|
|
59b9f57802 | ||
|
|
65fc2b539c | ||
|
|
3f1add9e21 | ||
|
|
68d2cafa6e | ||
|
|
b9d9e7edc6 | ||
|
|
6b17ec7dae | ||
|
|
08d796890a | ||
|
|
4f98ef36f6 | ||
|
|
a09c78491f | ||
|
|
02932d6b8c | ||
|
|
7cca98bda2 | ||
|
|
b77fc28692 | ||
|
|
3475531ef7 | ||
|
|
8222ed891b | ||
|
|
f787e6858c | ||
|
|
a014166795 | ||
|
|
f7e639504a | ||
|
|
028b60cad6 | ||
|
|
b11e22d905 | ||
|
|
33f8415785 | ||
|
|
5ccd155177 | ||
|
|
0f8d3a5174 | ||
|
|
c4a26cb2b1 | ||
|
|
7228cb15bf | ||
|
|
d5b46d6535 | ||
|
|
d4b4d44f14 | ||
|
|
b8cfd6d12b | ||
|
|
6fb22a4fd1 | ||
|
|
35849c57dc | ||
|
|
27504658ce | ||
|
|
db323348c7 | ||
|
|
edb1b5f333 | ||
|
|
2d8d377ddc | ||
|
|
057dd930b4 | ||
|
|
25b944e3e6 |
@@ -13,16 +13,20 @@ max_line_length = 100
|
||||
indent_style = tab
|
||||
|
||||
[*.{md,rst}]
|
||||
max_line_length = unset
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{sh,ac}]
|
||||
indent_size = 2
|
||||
[*.sh]
|
||||
indent_size = 4
|
||||
|
||||
[build_tools/release.sh]
|
||||
max_line_length = 72
|
||||
|
||||
[Dockerfile]
|
||||
indent_size = 2
|
||||
|
||||
[share/{completions,functions}/**.fish]
|
||||
max_line_length = off
|
||||
max_line_length = unset
|
||||
|
||||
[{COMMIT_EDITMSG,git-revise-todo}]
|
||||
max_line_length = 80
|
||||
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
|
||||
max_line_length = 72
|
||||
|
||||
14
.github/actions/install-sphinx-markdown-builder/action.yml
vendored
Normal file
14
.github/actions/install-sphinx-markdown-builder/action.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Install sphinx-markdown-builder
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
set -x
|
||||
commit=b259de1dc97573a71470a1d71c3d83535934136b
|
||||
pip install git+https://github.com/krobelus/sphinx-markdown-builder@"$commit"
|
||||
python -c 'import sphinx_markdown_builder'
|
||||
20
.github/actions/rust-toolchain@oldest-supported/action.yml
vendored
Normal file
20
.github/actions/rust-toolchain@oldest-supported/action.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Oldest Supported Rust Toolchain
|
||||
|
||||
inputs:
|
||||
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:
|
||||
- uses: dtolnay/rust-toolchain@1.70
|
||||
with:
|
||||
targets: ${{ inputs.targets }}
|
||||
components: ${{ inputs.components}}
|
||||
20
.github/actions/rust-toolchain@stable/action.yml
vendored
Normal file
20
.github/actions/rust-toolchain@stable/action.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Stable Rust Toolchain
|
||||
|
||||
inputs:
|
||||
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:
|
||||
- uses: dtolnay/rust-toolchain@1.89
|
||||
with:
|
||||
targets: ${{ inputs.targets }}
|
||||
components: ${{ inputs.components }}
|
||||
42
.github/workflows/mac_codesign.yml
vendored
42
.github/workflows/mac_codesign.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: macOS build and codesign
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Enables manual trigger from GitHub UI
|
||||
|
||||
jobs:
|
||||
build-and-code-sign:
|
||||
runs-on: macos-latest
|
||||
environment: macos-codesign
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust 1.73.0
|
||||
uses: dtolnay/rust-toolchain@1.73.0
|
||||
with:
|
||||
targets: x86_64-apple-darwin
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin
|
||||
- name: build-and-codesign
|
||||
run: |
|
||||
cargo install apple-codesign
|
||||
mkdir -p "$FISH_ARTEFACT_PATH"
|
||||
echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode > /tmp/app.p12
|
||||
echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode > /tmp/installer.p12
|
||||
echo "$MACOS_NOTARIZE_JSON" > /tmp/notarize.json
|
||||
./build_tools/make_pkg.sh -s -f /tmp/app.p12 -i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" -n -j /tmp/notarize.json
|
||||
rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json
|
||||
env:
|
||||
MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }}
|
||||
MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }}
|
||||
MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }}
|
||||
MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }}
|
||||
# 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.
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
FISH_ARTEFACT_PATH: /tmp/fish-built
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macOS Artefacts
|
||||
path: /tmp/fish-built/*
|
||||
if-no-files-found: error
|
||||
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
sudo apt install gettext libpcre2-dev python3-pexpect 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
|
||||
|
||||
190
.github/workflows/release.yml
vendored
Normal file
190
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
name: Create a new release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release (tag name)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
is-release-tag:
|
||||
name: Pre-release checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Check if the pushed tag looks like a release
|
||||
run: |
|
||||
set -x
|
||||
commit_subject=$(git log -1 --format=%s)
|
||||
tag=$(git describe)
|
||||
[ "$commit_subject" = "Release $tag" ]
|
||||
|
||||
|
||||
source-tarball:
|
||||
needs: [is-release-tag]
|
||||
name: Create the source tarball
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
tarball-name: ${{ steps.version.outputs.tarball-name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Install dependencies
|
||||
run: sudo apt install cmake gettext ninja-build python3-pip python3-sphinx
|
||||
- uses: ./.github/actions/install-sphinx-markdown-builder
|
||||
- name: Create tarball
|
||||
run: |
|
||||
set -x
|
||||
mkdir /tmp/fish-built
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
|
||||
relnotes=/tmp/fish-built/release-notes.md
|
||||
# Need history since the last release (i.e. tag) for stats.
|
||||
git fetch --tags
|
||||
git fetch --unshallow
|
||||
sh -x ./build_tools/release-notes.sh >"$relnotes"
|
||||
# Delete title
|
||||
sed -n 1p "$relnotes" | grep -q "^## fish .*"
|
||||
sed -n 2p "$relnotes" | grep -q '^$'
|
||||
sed -i 1,2d "$relnotes"
|
||||
- name: Upload tarball artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: source-tarball
|
||||
path: |
|
||||
/tmp/fish-built/fish-${{ inputs.version }}.tar.xz
|
||||
/tmp/fish-built/release-notes.md
|
||||
if-no-files-found: error
|
||||
|
||||
packages-for-linux:
|
||||
needs: [is-release-tag]
|
||||
name: Build single-file fish for Linux (experimental)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Install Rust Stable
|
||||
uses: ./.github/actions/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl
|
||||
- name: Install dependencies
|
||||
run: sudo apt install crossbuild-essential-arm64 musl-tools python3-sphinx
|
||||
- name: Build statically-linked executables
|
||||
run: |
|
||||
set -x
|
||||
CFLAGS="-D_FORTIFY_SOURCE=2" \
|
||||
CMAKE_WITH_GETTEXT=0 \
|
||||
CC=aarch64-linux-gnu-gcc \
|
||||
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-lgcc -C link-arg=-D_FORTIFY_SOURCE=0" \
|
||||
cargo build --release --target aarch64-unknown-linux-musl --bin fish
|
||||
cargo build --release --target x86_64-unknown-linux-musl --bin fish
|
||||
- name: Compress
|
||||
run: |
|
||||
set -x
|
||||
for arch in x86_64 aarch64; do
|
||||
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
|
||||
-C target/$arch-unknown-linux-musl/release fish
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Static builds for Linux
|
||||
path: fish-${{ inputs.version }}-linux-*.tar.xz
|
||||
if-no-files-found: error
|
||||
|
||||
create-draft-release:
|
||||
needs:
|
||||
- is-release-tag
|
||||
- source-tarball
|
||||
- packages-for-linux
|
||||
name: Create release draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: /tmp/artifacts
|
||||
- name: List artifacts
|
||||
run: find /tmp/artifacts -type f
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ inputs.version }}
|
||||
name: fish ${{ inputs.version }}
|
||||
body_path: /tmp/artifacts/release-notes.md
|
||||
draft: true
|
||||
files: |
|
||||
/tmp/artifacts/fish-${{ inputs.version }}.tar.xz
|
||||
/tmp/artifacts/fish-${{ inputs.version }}-linux-*.tar.xz
|
||||
|
||||
packages-for-macos:
|
||||
needs: [is-release-tag, create-draft-release]
|
||||
name: Build packages for macOS
|
||||
runs-on: macos-latest
|
||||
environment: macos-codesign
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Install Rust
|
||||
uses: ./.github/actions/rust-toolchain@oldest-supported
|
||||
with:
|
||||
targets: x86_64-apple-darwin
|
||||
- name: Install Rust Stable
|
||||
uses: ./.github/actions/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin
|
||||
- name: Build and codesign
|
||||
run: |
|
||||
die() { echo >&2 "$*"; exit 1; }
|
||||
[ -n "$MAC_CODESIGN_APP_P12_BASE64" ] || die "Missing MAC_CODESIGN_APP_P12_BASE64"
|
||||
[ -n "$MAC_CODESIGN_INSTALLER_P12_BASE64" ] || die "Missing MAC_CODESIGN_INSTALLER_P12_BASE64"
|
||||
[ -n "$MAC_CODESIGN_PASSWORD" ] || die "Missing MAC_CODESIGN_PASSWORD"
|
||||
[ -n "$MACOS_NOTARIZE_JSON" ] || die "Missing MACOS_NOTARIZE_JSON"
|
||||
set -x
|
||||
export FISH_ARTEFACT_PATH=/tmp/fish-built
|
||||
# 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.
|
||||
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
cargo install apple-codesign
|
||||
mkdir -p "$FISH_ARTEFACT_PATH"
|
||||
echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode >/tmp/app.p12
|
||||
echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode >/tmp/installer.p12
|
||||
echo "$MACOS_NOTARIZE_JSON" >/tmp/notarize.json
|
||||
./build_tools/make_macos_pkg.sh -s -f /tmp/app.p12 \
|
||||
-i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" \
|
||||
-n -j /tmp/notarize.json
|
||||
version=$(git describe)
|
||||
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.app.zip" ]
|
||||
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.pkg" ]
|
||||
rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json
|
||||
env:
|
||||
MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }}
|
||||
MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }}
|
||||
MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }}
|
||||
MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }}
|
||||
- name: Add macOS packages to the release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
version=$(git describe)
|
||||
gh release upload $version \
|
||||
/tmp/fish-built/fish-$version.app.zip \
|
||||
/tmp/fish-built/fish-$version.pkg
|
||||
47
.github/workflows/staticbuild.yml
vendored
47
.github/workflows/staticbuild.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: staticbuilds
|
||||
|
||||
on:
|
||||
# release:
|
||||
# types: [published]
|
||||
# schedule:
|
||||
# - cron: "14 13 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CTEST_PARALLEL_LEVEL: "1"
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: "4"
|
||||
|
||||
jobs:
|
||||
staticbuilds:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: dtolnay/rust-toolchain@1.70
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Prepare
|
||||
run: |
|
||||
sudo apt install python3-sphinx
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
sudo apt install musl-tools crossbuild-essential-arm64 -y
|
||||
- name: Build
|
||||
run: |
|
||||
CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2" CMAKE_WITH_GETTEXT=0 CC=aarch64-linux-gnu-gcc RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-lgcc -C link-arg=-D_FORTIFY_SOURCE=0" cargo build --release --target aarch64-unknown-linux-musl
|
||||
cargo build --release --target x86_64-unknown-linux-musl
|
||||
- name: Compress
|
||||
run: |
|
||||
tar -cazf fish-amd64.tar.xz -C target/x86_64-unknown-linux-musl/release/ fish{,_indent,_key_reader}
|
||||
tar -cazf fish-aarch64.tar.xz -C target/aarch64-unknown-linux-musl/release/ fish{,_indent,_key_reader}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fish
|
||||
path: |
|
||||
fish-amd64.tar.xz
|
||||
fish-aarch64.tar.xz
|
||||
retention-days: 14
|
||||
@@ -1,3 +1,57 @@
|
||||
fish 4.0.9 (released September 27, 2025)
|
||||
========================================
|
||||
|
||||
This release fixes:
|
||||
|
||||
- a regression in 4.0.6 causing shifted keys to not be inserted on some terminals (:issue:`11813`).
|
||||
- a regression in 4.0.6 causing the build to fail on systems where ``char`` is unsigned (:issue:`11804`).
|
||||
- a regression in 4.0.0 causing a crash on an invalid :doc:`bg <cmds/bg>` invocation.
|
||||
|
||||
--------------
|
||||
|
||||
fish 4.0.8 (released September 18, 2025)
|
||||
========================================
|
||||
|
||||
This release fixes a regression in 4.0.6 that caused user bindings to be shadowed by either fish's or a plugin's bindings (:issue:`11803`).
|
||||
|
||||
--------------
|
||||
|
||||
fish 4.0.6 (released September 12, 2025)
|
||||
========================================
|
||||
|
||||
This release of fish fixes a number of issues identified in fish 4.0.2:
|
||||
|
||||
- fish now properly inherits $PATH under Windows WSL2 (:issue:`11354`).
|
||||
- Remote filesystems are detected properly again on non-Linux systems.
|
||||
- the :doc:`printf <cmds/printf>` builtin no longer miscalculates width of multi-byte characters (:issue:`11412`).
|
||||
- For many years, fish has been "relocatable" -- it was possible to move the entire ``CMAKE_INSTALL_PREFIX`` and fish would use paths relative to its binary.
|
||||
Only gettext locale paths were still determined purely at compile time, which has been fixed.
|
||||
- the :doc:`commandline <cmds/commandline>` builtin failed to print the commandline set by a ``commandline -C`` invocation, which broke some completion scripts.
|
||||
This has been corrected (:issue:`11423`).
|
||||
- To work around terminals that fail to parse Operating System Command (OSC) sequences, a temporary feature flag has been added.
|
||||
It allows you to disable prompt marking (OSC 133) by running (once) ``set -Ua fish_features no-mark-prompt`` and restarting fish (:issue:`11749`).
|
||||
- The routines to save history and universal variables have seen some robustness improvements.
|
||||
- builtin :doc:`status current-command <cmds/status>` no longer prints a trailing blank line.
|
||||
- A crash displaying multi-line quoted command substitutions has been fixed (:issue:`11444`).
|
||||
- Commands like ``set fish_complete_path ...`` accidentally disabled completion autoloading, which has been corrected.
|
||||
- ``nmcli`` completions have been fixed to query network information dynamically instead of only when completing the first time.
|
||||
- Git completions no longer print an error when no `git-foo` executable is in :envvar:`PATH`.
|
||||
- Custom completions like ``complete foo -l long -xa ...`` that use the output of ``commandline -t``.
|
||||
on a command-line like ``foo --long=`` have been invalidated by a change in 4.0; the completion scripts have been adjusted accordingly (:issue:`11508`).
|
||||
- Some completions were misinterpreted, which caused garbage to be displayed in the completion list. This has been fixed.
|
||||
- fish no longer interprets invalid control sequences from the terminal as if they were :kbd:`alt-[` or :kbd:`alt-o` key strokes.
|
||||
- :doc:`bind <cmds/bind>` has been taught about the :kbd:`printscreen` and :kbd:`menu` keys.
|
||||
- :kbd:`alt-delete` now deletes the word right of the cursor.
|
||||
- :kbd:`ctrl-alt-h` erases the last word again (:issue:`11548`).
|
||||
- :kbd:`alt-left` :kbd:`alt-right` were misinterpreted because they send unexpected sequences on some terminals; a workaround has been added. (:issue:`11479`).
|
||||
- Key bindings like ``bind shift-A`` are no longer accepted; use ``bind shift-a`` or ``bind A``.
|
||||
- Key bindings like ``bind shift-a`` take precedence over ``bind A`` when the key event included the shift modifier.
|
||||
- Bindings using shift with non-ASCII letters (such as :kbd:`ctrl-shift-ä`) are now supported.
|
||||
- Bindings with modifiers such as ``bind ctrl-w`` work again on non-Latin keyboard layouts such as a Russian one.
|
||||
This is implemented by allowing key events such as :kbd:`ctrl-ц` to match bindings of the corresponding Latin key, using the kitty keyboard protocol's base layout key (:issue:`11520`).
|
||||
- Vi mode: The cursor position after pasting via :kbd:`p` has been corrected.
|
||||
- Vi mode: Trying to replace the last character via :kbd:`r` no longer replaces the last-but-one character (:issue:`11484`),
|
||||
|
||||
fish 4.0.2 (released April 20, 2025)
|
||||
====================================
|
||||
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -112,7 +112,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fish"
|
||||
version = "4.0.2"
|
||||
version = "4.0.9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
@@ -139,6 +139,8 @@ name = "fish-printf"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
@@ -567,6 +569,18 @@ version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -16,7 +16,7 @@ debug = true
|
||||
|
||||
[package]
|
||||
name = "fish"
|
||||
version = "4.0.2"
|
||||
version = "4.0.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
default-run = "fish"
|
||||
|
||||
16
build.rs
16
build.rs
@@ -227,7 +227,7 @@ fn has_small_stack(_: &Target) -> Result<bool, Box<dyn Error>> {
|
||||
}
|
||||
|
||||
fn setup_paths() {
|
||||
fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
|
||||
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);
|
||||
@@ -250,7 +250,7 @@ fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
|
||||
rsconf::rebuild_if_env_changed("PREFIX");
|
||||
rsconf::set_env_value("PREFIX", prefix.to_str().unwrap());
|
||||
|
||||
let datadir = get_path("DATADIR", "share/", prefix.clone());
|
||||
let datadir = get_path("DATADIR", "share/", &prefix);
|
||||
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
|
||||
rsconf::rebuild_if_env_changed("DATADIR");
|
||||
|
||||
@@ -261,7 +261,7 @@ fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
|
||||
};
|
||||
rsconf::set_env_value("DATADIR_SUBDIR", datadir_subdir);
|
||||
|
||||
let bindir = get_path("BINDIR", "bin/", prefix.clone());
|
||||
let bindir = get_path("BINDIR", "bin/", &prefix);
|
||||
rsconf::set_env_value("BINDIR", bindir.to_str().unwrap());
|
||||
rsconf::rebuild_if_env_changed("BINDIR");
|
||||
|
||||
@@ -270,16 +270,16 @@ fn get_path(name: &str, default: &str, onvar: PathBuf) -> PathBuf {
|
||||
// If we get our prefix from $HOME, we should use the system's /etc/
|
||||
// ~/.local/share/etc/ makes no sense
|
||||
if prefix_from_home { "/etc/" } else { "etc/" },
|
||||
datadir.clone(),
|
||||
&datadir,
|
||||
);
|
||||
rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap());
|
||||
rsconf::rebuild_if_env_changed("SYSCONFDIR");
|
||||
|
||||
let localedir = get_path("LOCALEDIR", "locale/", datadir.clone());
|
||||
let localedir = get_path("LOCALEDIR", "locale/", &datadir);
|
||||
rsconf::set_env_value("LOCALEDIR", localedir.to_str().unwrap());
|
||||
rsconf::rebuild_if_env_changed("LOCALEDIR");
|
||||
|
||||
let docdir = get_path("DOCDIR", "doc/fish", datadir.clone());
|
||||
let docdir = get_path("DOCDIR", "doc/fish", &datadir);
|
||||
rsconf::set_env_value("DOCDIR", docdir.to_str().unwrap());
|
||||
rsconf::rebuild_if_env_changed("DOCDIR");
|
||||
}
|
||||
@@ -292,7 +292,7 @@ fn get_version(src_dir: &Path) -> String {
|
||||
return var;
|
||||
}
|
||||
|
||||
let path = PathBuf::from(src_dir).join("version");
|
||||
let path = src_dir.join("version");
|
||||
if let Ok(strver) = read_to_string(path) {
|
||||
return strver.to_string();
|
||||
}
|
||||
@@ -321,7 +321,7 @@ fn get_version(src_dir: &Path) -> String {
|
||||
// or because it refused (safe.directory applies to `git describe`!)
|
||||
// So we read the SHA ourselves.
|
||||
fn get_git_hash() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let gitdir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".git");
|
||||
let gitdir = Path::new(env!("CARGO_MANIFEST_DIR")).join(".git");
|
||||
|
||||
// .git/HEAD contains ref: refs/heads/branch
|
||||
let headpath = gitdir.join("HEAD");
|
||||
|
||||
177
build_tools/make_macos_pkg.sh
Executable file
177
build_tools/make_macos_pkg.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Script to produce an OS X installer .pkg and .app(.zip)
|
||||
|
||||
usage() {
|
||||
echo "Build macOS packages, optionally signing and notarizing them."
|
||||
echo "Usage: $0 options"
|
||||
echo "Options:"
|
||||
echo " -s Enables code signing"
|
||||
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
|
||||
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
|
||||
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
|
||||
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
|
||||
echo " -n Enables notarization. This will fail if code signing is not also enabled."
|
||||
echo " -j <API_KEY.JSON> Path to JSON file generated with \`rcodesign encode-app-store-connect-api-key\` (required for notarization)"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
SIGN=
|
||||
NOTARIZE=
|
||||
|
||||
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
|
||||
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.9'
|
||||
|
||||
# As of this writing, the most recent Rust release supports macOS back to 10.12.
|
||||
# The first supported version of macOS on arm64 is 10.15, so any Rust is fine for arm64.
|
||||
# We wish to support back to 10.9 on x86-64; the last version of Rust to support that is
|
||||
# version 1.73.0.
|
||||
RUST_VERSION_X86_64=1.70.0
|
||||
|
||||
while getopts "sf:i:p:e:nj:" opt; do
|
||||
case $opt in
|
||||
s) SIGN=1;;
|
||||
f) P12_APP_FILE=$(realpath "$OPTARG");;
|
||||
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
|
||||
p) P12_PASSWORD="$OPTARG";;
|
||||
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
|
||||
n) NOTARIZE=1;;
|
||||
j) API_KEY_FILE=$(realpath "$OPTARG");;
|
||||
\?) usage;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$SIGN" ] && { [ -z "$P12_APP_FILE" ] || [ -z "$P12_INSTALL_FILE" ] || [ -z "$P12_PASSWORD" ]; }; then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
|
||||
|
||||
echo "Version is $VERSION"
|
||||
|
||||
PKGDIR=$(mktemp -d)
|
||||
echo "$PKGDIR"
|
||||
|
||||
SRC_DIR=$PWD
|
||||
OUTPUT_PATH=${FISH_ARTEFACT_PATH:-~/fish_built}
|
||||
|
||||
mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/intermediates" "$PKGDIR/dst"
|
||||
|
||||
# Build and install for arm64.
|
||||
# Pass FISH_USE_SYSTEM_PCRE2=OFF because a system PCRE2 on macOS will not be signed by fish,
|
||||
# and will probably not be built universal, so the package will fail to validate/run on other systems.
|
||||
# Note CMAKE_OSX_ARCHITECTURES is still relevant for the Mac app.
|
||||
{ cd "$PKGDIR/build_arm64" \
|
||||
&& cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
|
||||
-DWITH_GETTEXT=OFF \
|
||||
-DRust_CARGO_TARGET=aarch64-apple-darwin \
|
||||
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
|
||||
-DFISH_USE_SYSTEM_PCRE2=OFF \
|
||||
"$SRC_DIR" \
|
||||
&& env $ARM64_DEPLOY_TARGET make VERBOSE=1 -j 12 \
|
||||
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
|
||||
}
|
||||
|
||||
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
|
||||
# Set RUST_VERSION_X86_64 to the last version of Rust that supports macOS 10.9.
|
||||
{ cd "$PKGDIR/build_x86_64" \
|
||||
&& cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
|
||||
-DWITH_GETTEXT=OFF \
|
||||
-DRust_TOOLCHAIN="$RUST_VERSION_X86_64" \
|
||||
-DRust_CARGO_TARGET=x86_64-apple-darwin \
|
||||
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
|
||||
-DFISH_USE_SYSTEM_PCRE2=OFF "$SRC_DIR" \
|
||||
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
||||
|
||||
# Fatten them up.
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing executables"
|
||||
ARGS=(
|
||||
--p12-file "$P12_APP_FILE"
|
||||
--p12-password "$P12_PASSWORD"
|
||||
--code-signature-flags runtime
|
||||
--for-notarization
|
||||
)
|
||||
if [ -n "$ENTITLEMENTS_FILE" ]; then
|
||||
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
|
||||
fi
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
|
||||
done
|
||||
fi
|
||||
|
||||
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
|
||||
productbuild --package-path "$PKGDIR/intermediates" --distribution "$SRC_DIR/build_tools/osx_distribution.xml" --resources "$SRC_DIR/build_tools/osx_package_resources/" "$OUTPUT_PATH/fish-$VERSION.pkg"
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing installer"
|
||||
ARGS=(
|
||||
--p12-file "$P12_INSTALL_FILE"
|
||||
--p12-password "$P12_PASSWORD"
|
||||
--code-signature-flags runtime
|
||||
--for-notarization
|
||||
)
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "$OUTPUT_PATH/fish-$VERSION.pkg")
|
||||
fi
|
||||
|
||||
# Make the app
|
||||
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||
|
||||
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||
cd "$PKGDIR/build_arm64"
|
||||
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
|
||||
# macho-universal-create screws up the permissions.
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing app"
|
||||
ARGS=(
|
||||
--p12-file "$P12_APP_FILE"
|
||||
--p12-password "$P12_PASSWORD"
|
||||
--code-signature-flags runtime
|
||||
--for-notarization
|
||||
)
|
||||
if [ -n "$ENTITLEMENTS_FILE" ]; then
|
||||
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
|
||||
fi
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "fish.app")
|
||||
|
||||
fi
|
||||
|
||||
cp -R "fish.app" "$OUTPUT_PATH/fish-$VERSION.app"
|
||||
cd "$OUTPUT_PATH"
|
||||
|
||||
# Maybe notarize.
|
||||
if test -n "$NOTARIZE"; then
|
||||
echo "Notarizing"
|
||||
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.pkg"
|
||||
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.app"
|
||||
fi
|
||||
|
||||
# Zip it up.
|
||||
zip -r "fish-$VERSION.app.zip" "fish-$VERSION.app" && rm -Rf "fish-$VERSION.app"
|
||||
|
||||
rm -rf "$PKGDIR"
|
||||
@@ -3,18 +3,18 @@
|
||||
# Script to produce an OS X installer .pkg and .app(.zip)
|
||||
|
||||
usage() {
|
||||
echo "Build macOS packages, optionally signing and notarizing them."
|
||||
echo "Usage: $0 options"
|
||||
echo "Options:"
|
||||
echo " -s Enables code signing"
|
||||
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
|
||||
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
|
||||
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
|
||||
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
|
||||
echo " -n Enables notarization. This will fail if code signing is not also enabled."
|
||||
echo " -j <API_KEY.JSON> Path to JSON file generated with `rcodesign encode-app-store-connect-api-key` (required for notarization)"
|
||||
echo
|
||||
exit 1
|
||||
echo "Build macOS packages, optionally signing and notarizing them."
|
||||
echo "Usage: $0 options"
|
||||
echo "Options:"
|
||||
echo " -s Enables code signing"
|
||||
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
|
||||
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
|
||||
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
|
||||
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
|
||||
echo " -n Enables notarization. This will fail if code signing is not also enabled."
|
||||
echo " -j <API_KEY.JSON> Path to JSON file generated with \`rcodesign encode-app-store-connect-api-key\` (required for notarization)"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
set -x
|
||||
@@ -30,35 +30,35 @@ X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.9'
|
||||
# The first supported version of macOS on arm64 is 10.15, so any Rust is fine for arm64.
|
||||
# We wish to support back to 10.9 on x86-64; the last version of Rust to support that is
|
||||
# version 1.73.0.
|
||||
RUST_VERSION_X86_64=1.73.0
|
||||
RUST_VERSION_X86_64=1.70.0
|
||||
|
||||
while getopts "sf:i:p:e:nj:" opt; do
|
||||
case $opt in
|
||||
s) SIGN=1;;
|
||||
f) P12_APP_FILE=$(realpath "$OPTARG");;
|
||||
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
|
||||
p) P12_PASSWORD="$OPTARG";;
|
||||
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
|
||||
n) NOTARIZE=1;;
|
||||
j) API_KEY_FILE=$(realpath "$OPTARG");;
|
||||
\?) usage;;
|
||||
esac
|
||||
case $opt in
|
||||
s) SIGN=1;;
|
||||
f) P12_APP_FILE=$(realpath "$OPTARG");;
|
||||
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
|
||||
p) P12_PASSWORD="$OPTARG";;
|
||||
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
|
||||
n) NOTARIZE=1;;
|
||||
j) API_KEY_FILE=$(realpath "$OPTARG");;
|
||||
\?) usage;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$SIGN" ] && ([ -z "$P12_APP_FILE" ] || [-z "$P12_INSTALL_FILE"] || [ -z "$P12_PASSWORD" ]); then
|
||||
usage
|
||||
if [ -n "$SIGN" ] && { [ -z "$P12_APP_FILE" ] || [ -z "$P12_INSTALL_FILE" ] || [ -z "$P12_PASSWORD" ]; }; then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
|
||||
usage
|
||||
usage
|
||||
fi
|
||||
|
||||
VERSION=$(git describe --always --dirty 2>/dev/null)
|
||||
if test -z "$VERSION" ; then
|
||||
echo "Could not get version from git"
|
||||
if test -f version; then
|
||||
VERSION=$(cat version)
|
||||
fi
|
||||
echo "Could not get version from git"
|
||||
if test -f version; then
|
||||
VERSION=$(cat version)
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Version is $VERSION"
|
||||
@@ -76,7 +76,7 @@ mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/in
|
||||
# and will probably not be built universal, so the package will fail to validate/run on other systems.
|
||||
# Note CMAKE_OSX_ARCHITECTURES is still relevant for the Mac app.
|
||||
{ cd "$PKGDIR/build_arm64" \
|
||||
&& cmake \
|
||||
&& cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
|
||||
-DWITH_GETTEXT=OFF \
|
||||
@@ -91,7 +91,7 @@ mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/in
|
||||
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
|
||||
# Set RUST_VERSION_X86_64 to the last version of Rust that supports macOS 10.9.
|
||||
{ cd "$PKGDIR/build_x86_64" \
|
||||
&& cmake \
|
||||
&& cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
|
||||
-DWITH_GETTEXT=OFF \
|
||||
@@ -99,11 +99,11 @@ mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/in
|
||||
-DRust_CARGO_TARGET=x86_64-apple-darwin \
|
||||
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
|
||||
-DFISH_USE_SYSTEM_PCRE2=OFF "$SRC_DIR" \
|
||||
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
||||
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
||||
|
||||
# Fatten them up.
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/$(basename $FILE)"
|
||||
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
@@ -145,7 +145,7 @@ fi
|
||||
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||
cd "$PKGDIR/build_arm64"
|
||||
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename $FILE)"
|
||||
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
|
||||
# macho-universal-create screws up the permissions.
|
||||
|
||||
104
build_tools/release-notes.sh
Executable file
104
build_tools/release-notes.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
workspace_root=$(dirname "$0")/..
|
||||
|
||||
relnotes_tmp=$(mktemp -d)
|
||||
mkdir -p "$relnotes_tmp/fake-workspace" "$relnotes_tmp/out"
|
||||
(
|
||||
cd "$workspace_root"
|
||||
cp -r doc_src CONTRIBUTING.rst README.rst "$relnotes_tmp/fake-workspace"
|
||||
)
|
||||
version=$(sed 's,^fish \(\S*\) .*,\1,; 1q' "$workspace_root/CHANGELOG.rst")
|
||||
previous_version=$(
|
||||
cd "$workspace_root"
|
||||
awk <CHANGELOG.rst '
|
||||
( /^fish \S*\.\S*\.\S* \(released .*\)$/ &&
|
||||
NR > 1 &&
|
||||
# Skip tags that have not been created yet..
|
||||
system("git rev-parse --verify >/dev/null --quiet refs/tags/"$2) == 0 \
|
||||
) {
|
||||
print $2; ok = 1; exit
|
||||
}
|
||||
END { exit !ok }
|
||||
'
|
||||
)
|
||||
minor_version=${version%.*}
|
||||
previous_minor_version=${previous_version%.*}
|
||||
{
|
||||
sed -n 1,2p <"$workspace_root/CHANGELOG.rst"
|
||||
|
||||
ListCommitters() {
|
||||
comm "$@" "$relnotes_tmp/committers-then" "$relnotes_tmp/committers-now"
|
||||
}
|
||||
(
|
||||
cd "$workspace_root"
|
||||
git log "$previous_version" --format="%aN" | sort -u >"$relnotes_tmp/committers-then"
|
||||
git log "$previous_version".. --format="%aN" | sort -u >"$relnotes_tmp/committers-now"
|
||||
ListCommitters -13 >"$relnotes_tmp/committers-new"
|
||||
ListCommitters -12 >"$relnotes_tmp/committers-returning"
|
||||
)
|
||||
if [ "$minor_version" != "$previous_minor_version" ]; then
|
||||
(
|
||||
cd "$workspace_root"
|
||||
num_commits=$(git log --no-merges --format=%H "$previous_version".. | wc -l)
|
||||
num_authors=$(wc -l <"$relnotes_tmp/committers-now")
|
||||
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
|
||||
printf %s \
|
||||
"This release comprises $num_commits commits since $previous_version," \
|
||||
" contributed by $num_authors authors, $num_new_authors of which are new committers."
|
||||
echo
|
||||
echo
|
||||
)
|
||||
fi
|
||||
|
||||
printf %s "$(awk <"$workspace_root/CHANGELOG.rst" '
|
||||
NR <= 2 || /^\.\. ignore / { next }
|
||||
/^===/ { exit }
|
||||
{ print }
|
||||
' | sed '$d')" |
|
||||
sed -e '$s/^----*$//' # Remove spurious transitions at the end of the document.
|
||||
|
||||
if [ "$minor_version" != "$previous_minor_version" ]; then
|
||||
JoinEscaped() {
|
||||
sed 's/\S/\\&/g' |
|
||||
awk '
|
||||
NR != 1 { printf ",\n" }
|
||||
{ printf "%s", $0 }
|
||||
END { printf "\n" }
|
||||
'
|
||||
}
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "Thanks to everyone who contributed through issue discussions, code reviews, or code changes."
|
||||
echo
|
||||
printf "Welcome our new committers: "
|
||||
JoinEscaped <"$relnotes_tmp/committers-new"
|
||||
echo
|
||||
printf "Welcome back our returning committers: "
|
||||
JoinEscaped <"$relnotes_tmp/committers-returning"
|
||||
fi
|
||||
echo
|
||||
echo "---"
|
||||
echo
|
||||
echo "*Download links: To download the source code for fish, we suggest the file named \"fish-$version.tar.xz\". The file downloaded from \"Source code (tar.gz)\" will not build correctly.*"
|
||||
echo
|
||||
echo "*The files called fish-$version-linux-\*.tar.xz are experimental packages containing a single standalone ``fish`` binary for any Linux with the given CPU architecture.*"
|
||||
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst
|
||||
|
||||
sphinx-build >&2 -j auto \
|
||||
-W -E -b markdown -c "$workspace_root/doc_src" \
|
||||
-d "$relnotes_tmp/doctree" "$relnotes_tmp/fake-workspace/doc_src" "$relnotes_tmp/out" \
|
||||
-D markdown_http_base="https://fishshell.com/docs/$minor_version" \
|
||||
-D markdown_uri_doc_suffix=".html" \
|
||||
-D markdown_github_flavored=1 \
|
||||
"$@"
|
||||
|
||||
# Skip changelog header
|
||||
sed -n 1p "$relnotes_tmp/out/relnotes.md" | grep -Fxq "# Release notes"
|
||||
sed -n 2p "$relnotes_tmp/out/relnotes.md" | grep -Fxq ''
|
||||
sed 1,2d "$relnotes_tmp/out/relnotes.md"
|
||||
|
||||
rm -r "$relnotes_tmp"
|
||||
229
build_tools/release.sh
Executable file
229
build_tools/release.sh
Executable file
@@ -0,0 +1,229 @@
|
||||
#!/bin/sh
|
||||
|
||||
{
|
||||
|
||||
set -ex
|
||||
|
||||
version=$1
|
||||
repository_owner=fish-shell
|
||||
remote=origin
|
||||
if [ -n "$2" ]; then
|
||||
set -u
|
||||
repository_owner=$2
|
||||
remote=$3
|
||||
set +u
|
||||
[ $# -eq 3 ]
|
||||
fi
|
||||
|
||||
[ -n "$version" ]
|
||||
|
||||
for tool in \
|
||||
bundle \
|
||||
gh \
|
||||
jq \
|
||||
ruby \
|
||||
timeout \
|
||||
; do
|
||||
if ! command -v "$tool" >/dev/null; then
|
||||
echo >&2 "$0: missing command: $1"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
repo_root="$(dirname "$0")/.."
|
||||
fish_site=$repo_root/../fish-site
|
||||
|
||||
for path in . "$fish_site"
|
||||
do
|
||||
if ! git -C "$path" diff HEAD --quiet ||
|
||||
git ls-files --others --exclude-standard | grep .; then
|
||||
echo >&2 "$0: index and worktree must be clean"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if git tag | grep -qxF "$version"; then
|
||||
echo >&2 "$0: tag $version already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
integration_branch=$(
|
||||
git for-each-ref --points-at=HEAD 'refs/heads/Integration_*' \
|
||||
--format='%(refname:strip=2)'
|
||||
)
|
||||
[ -n "$integration_branch" ] ||
|
||||
git merge-base --is-ancestor $remote/master HEAD
|
||||
|
||||
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
|
||||
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
|
||||
|
||||
changelog_title="fish $version (released $(date +'%B %d, %Y'))"
|
||||
sed -i \
|
||||
-e "1c$changelog_title" \
|
||||
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
|
||||
CHANGELOG.rst
|
||||
|
||||
CommitVersion() {
|
||||
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
|
||||
cargo fetch --offline
|
||||
git add CHANGELOG.rst Cargo.toml Cargo.lock
|
||||
git commit -m "$2
|
||||
|
||||
Created by ./build_tools/release.sh $version"
|
||||
}
|
||||
|
||||
CommitVersion "$version" "Release $version"
|
||||
|
||||
# N.B. this is not GPG-signed.
|
||||
git tag --annotate --message="Release $version" $version
|
||||
|
||||
git push $remote $version
|
||||
|
||||
TIMEOUT=
|
||||
gh() {
|
||||
command ${TIMEOUT:+timeout $TIMEOUT} \
|
||||
gh --repo "$repository_owner/fish-shell" "$@"
|
||||
}
|
||||
|
||||
gh workflow run release.yml --ref="$version" \
|
||||
--raw-field="version=$version"
|
||||
|
||||
run_id=
|
||||
while [ -z "$run_id" ] && sleep 5
|
||||
do
|
||||
run_id=$(gh run list \
|
||||
--json=databaseId --jq=.[].databaseId \
|
||||
--workflow=release.yml --limit=1 \
|
||||
--commit="$(git rev-parse "$version^{commit}")")
|
||||
done
|
||||
|
||||
# Update fishshell.com
|
||||
tag_oid=$(git rev-parse "$version")
|
||||
tmpdir=$(mktemp -d)
|
||||
# TODO This works on draft releases only if "gh" is configured to
|
||||
# have write access to the fish-shell repository. Unless we are fine
|
||||
# publishing the release at this point, we should at least fail if
|
||||
# "gh" doesn't have write access.
|
||||
while ! \
|
||||
gh release download "$version" --dir="$tmpdir" \
|
||||
--pattern="fish-$version.tar.xz"
|
||||
do
|
||||
TIMEOUT=30 gh run watch "$run_id" ||:
|
||||
sleep 5
|
||||
done
|
||||
actual_tag_oid=$(git ls-remote "$remote" |
|
||||
awk '$2 == "refs/tags/'"$version"'" { print $1 }')
|
||||
[ "$tag_oid" = "$actual_tag_oid" ]
|
||||
( cd "$tmpdir" && tar xf fish-$version.tar.xz )
|
||||
CopyDocs() {
|
||||
rm -rf "$fish_site/site/docs/$1"
|
||||
cp -r "$tmpdir/fish-$version/user_doc/html" "$fish_site/site/docs/$1"
|
||||
git -C $fish_site add "site/docs/$1"
|
||||
}
|
||||
minor_version=${version%.*}
|
||||
CopyDocs "$minor_version"
|
||||
latest_release=$(
|
||||
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
|
||||
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
|
||||
's/-/~/g' | LC_ALL=C sort --version-sort)
|
||||
printf %s\\n "$releases" | tail -1
|
||||
)
|
||||
if [ "$version" = "$latest_release" ]; then
|
||||
CopyDocs current
|
||||
fi
|
||||
rm -rf "$tmpdir"
|
||||
(
|
||||
cd "$fish_site"
|
||||
make
|
||||
git add -u
|
||||
! git ls-files --others --exclude-standard | grep .
|
||||
git commit --message="$(printf %s "\
|
||||
| Release $version (docs)
|
||||
|
|
||||
| Created by ../fish-shell/build_tools/release.sh
|
||||
" | sed 's,^\s*| \?,,')"
|
||||
)
|
||||
|
||||
# Approve macos-codesign
|
||||
# TODO what if current user can't approve?
|
||||
gh_pending_deployments() {
|
||||
command gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/$repository_owner/fish-shell/actions/runs/$run_id/pending_deployments" \
|
||||
"$@"
|
||||
}
|
||||
while {
|
||||
environment_id=$(gh_pending_deployments | jq .[].environment.id)
|
||||
[ -z "$environment_id" ]
|
||||
}
|
||||
do
|
||||
sleep 5
|
||||
done
|
||||
echo '
|
||||
{
|
||||
"environment_ids": ['"$environment_id"'],
|
||||
"state": "approved",
|
||||
"comment": "Approved via ./build_tools/release.sh"
|
||||
}
|
||||
' |
|
||||
gh_pending_deployments -XPOST --input=-
|
||||
|
||||
# Await completion.
|
||||
gh run watch "$run_id"
|
||||
|
||||
while {
|
||||
! draft=$(gh release view "$version" --json=isDraft --jq=.isDraft) \
|
||||
|| [ "$draft" = true ]
|
||||
}
|
||||
do
|
||||
sleep 20
|
||||
done
|
||||
|
||||
(
|
||||
cd "$fish_site"
|
||||
make new-release
|
||||
git add -u
|
||||
! git ls-files --others --exclude-standard | grep .
|
||||
git commit --message="$(printf %s "\
|
||||
| Release $version (release list update)
|
||||
|
|
||||
| Created by ../fish-shell/build_tools/release.sh
|
||||
" | 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 git@github.com:$repository_owner/fish-site HEAD:master
|
||||
)
|
||||
|
||||
if [ -n "$integration_branch" ]; then
|
||||
git push $remote "$version^{commit}":refs/heads/$integration_branch
|
||||
else
|
||||
changelog=$(cat - CHANGELOG.rst <<EOF
|
||||
fish ?.?.? (released ???)
|
||||
=========================
|
||||
|
||||
EOF
|
||||
)
|
||||
printf %s\\n "$changelog" >CHANGELOG.rst
|
||||
CommitVersion ${version}-snapshot "start new cycle"
|
||||
git push $remote HEAD:master
|
||||
fi
|
||||
|
||||
# TODO This can currently require a TTY for editing and password
|
||||
# prompts.
|
||||
if [ "$repository_owner" = fish-shell ]; then {
|
||||
mail=$(mktemp)
|
||||
cat >$mail <<EOF
|
||||
From: $(git var GIT_AUTHOR_IDENT | sed 's/ [0-9]* +[0-9]*$//')
|
||||
Subject: fish $version released
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/$version
|
||||
EOF
|
||||
git send-email --suppress-cc=all --confirm=always $mail \
|
||||
--to="fish-users Mailing List <fish-users@lists.sourceforge.net>"
|
||||
rm $mail
|
||||
} fi
|
||||
|
||||
exit
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ add_executable(fish_macapp EXCLUDE_FROM_ALL
|
||||
|
||||
# Compute the version. Note this is done at generation time, not build time,
|
||||
# so cmake must be re-run after version changes for the app to be updated. But
|
||||
# generally this will be run by make_pkg.sh which always re-runs cmake.
|
||||
# generally this will be run by make_macos_pkg.sh which always re-runs cmake.
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
|
||||
COMMAND cut -d- -f1
|
||||
@@ -32,7 +32,7 @@ execute_process(
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
|
||||
# Note CMake appends .app, so the real output name will be fish.app.
|
||||
# Note CMake appends .app, so the real output name will be fish.app.
|
||||
# This target does not include the 'base' resource.
|
||||
set_target_properties(fish_macapp PROPERTIES OUTPUT_NAME "fish")
|
||||
|
||||
|
||||
1
debian/control
vendored
1
debian/control
vendored
@@ -20,7 +20,6 @@ Vcs-Browser: https://github.com/fish-shell/fish-shell
|
||||
|
||||
Package: fish
|
||||
Architecture: any
|
||||
Depends: bsdextrautils,
|
||||
Depends: bsdextrautils | bsdmainutils,
|
||||
file,
|
||||
gettext-base,
|
||||
|
||||
5
debian/rules
vendored
5
debian/rules
vendored
@@ -17,6 +17,9 @@ override_dh_auto_configure:
|
||||
dh_auto_configure --buildsystem=cmake -- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
|
||||
override_dh_clean:
|
||||
dh_clean
|
||||
dh_clean --exclude=Cargo.toml.orig
|
||||
-unlink .cargo
|
||||
-unlink vendor
|
||||
|
||||
override_dh_auto_test:
|
||||
make fish_run_tests
|
||||
|
||||
@@ -10,9 +10,19 @@ import glob
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
from sphinx.highlighting import lexers
|
||||
from sphinx.errors import SphinxWarning
|
||||
from docutils import nodes
|
||||
|
||||
try:
|
||||
import sphinx_markdown_builder
|
||||
|
||||
extensions = [
|
||||
"sphinx_markdown_builder",
|
||||
]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# -- Helper functions --------------------------------------------------------
|
||||
|
||||
|
||||
@@ -35,11 +45,14 @@ def issue_role(name, rawtext, text, lineno, inliner, options=None, content=None)
|
||||
return [link], []
|
||||
|
||||
|
||||
def remove_fish_indent_lexer(app):
|
||||
if app.builder.name in ("man", "markdown"):
|
||||
del lexers["fish-docs-samples"]
|
||||
|
||||
|
||||
# -- Load our extensions -------------------------------------------------
|
||||
def setup(app):
|
||||
# Our own pygments lexers
|
||||
from sphinx.highlighting import lexers
|
||||
|
||||
this_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, this_dir)
|
||||
from fish_indent_lexer import FishIndentLexer
|
||||
@@ -52,6 +65,8 @@ def setup(app):
|
||||
app.add_config_value("issue_url", default=None, rebuild="html")
|
||||
app.add_role("issue", issue_role)
|
||||
|
||||
app.connect("builder-inited", remove_fish_indent_lexer)
|
||||
|
||||
|
||||
# The default language to assume
|
||||
highlight_language = "fish-docs-samples"
|
||||
@@ -59,7 +74,7 @@ highlight_language = "fish-docs-samples"
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "fish-shell"
|
||||
copyright = "2024, fish-shell developers"
|
||||
copyright = "fish-shell developers"
|
||||
author = "fish-shell developers"
|
||||
issue_url = "https://github.com/fish-shell/fish-shell/issues"
|
||||
|
||||
@@ -72,7 +87,7 @@ elif "FISH_BUILD_VERSION" in os.environ:
|
||||
ret = os.environ["FISH_BUILD_VERSION"]
|
||||
else:
|
||||
ret = subprocess.check_output(
|
||||
("fish_indent", "--version"), stderr=subprocess.STDOUT
|
||||
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
|
||||
).decode("utf-8")
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
|
||||
@@ -9,3 +9,5 @@ license = "MIT"
|
||||
[dependencies]
|
||||
libc = "0.2.155"
|
||||
widestring = { version = "1.0.2", optional = true }
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
|
||||
@@ -73,7 +73,7 @@ macro_rules! sprintf {
|
||||
/// - `args`: Iterator over the arguments to format.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Result` which is `Ok` containing the number of characters written on success, or an `Error`.
|
||||
/// A `Result` which is `Ok` containing the width of the string written on success, or an `Error`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
use std::fmt::{self, Write};
|
||||
use std::mem;
|
||||
use std::result::Result;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
#[cfg(feature = "widestring")]
|
||||
use widestring::Utf32Str as wstr;
|
||||
@@ -382,7 +384,7 @@ pub fn sprintf_locale(
|
||||
}
|
||||
|
||||
// Read field width. We do not support $.
|
||||
let width = if s.at(0) == Some('*') {
|
||||
let desired_width = if s.at(0) == Some('*') {
|
||||
let arg_width = args.next().ok_or(Error::MissingArg)?.as_sint()?;
|
||||
s.advance_by(1);
|
||||
if arg_width < 0 {
|
||||
@@ -397,7 +399,7 @@ pub fn sprintf_locale(
|
||||
};
|
||||
|
||||
// Optionally read precision. We do not support $.
|
||||
let mut prec: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
|
||||
let mut desired_precision: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
|
||||
// "A negative precision is treated as though it were missing."
|
||||
// Here we assume the precision is always signed.
|
||||
s.advance_by(2);
|
||||
@@ -410,7 +412,7 @@ pub fn sprintf_locale(
|
||||
None
|
||||
};
|
||||
// Disallow precisions larger than i32::MAX, in keeping with C.
|
||||
if prec.unwrap_or(0) > i32::MAX as usize {
|
||||
if desired_precision.unwrap_or(0) > i32::MAX as usize {
|
||||
return Err(Error::Overflow);
|
||||
}
|
||||
|
||||
@@ -429,7 +431,7 @@ pub fn sprintf_locale(
|
||||
// "If a precision is given with a numeric conversion (d, i, o, u, i, x, and X),
|
||||
// the 0 flag is ignored." p is included here.
|
||||
let spec_is_numeric = matches!(conv_spec, CS::d | CS::u | CS::o | CS::p | CS::x | CS::X);
|
||||
if spec_is_numeric && prec.is_some() {
|
||||
if spec_is_numeric && desired_precision.is_some() {
|
||||
flags.zero_pad = false;
|
||||
}
|
||||
|
||||
@@ -443,13 +445,22 @@ pub fn sprintf_locale(
|
||||
CS::e | CS::f | CS::g | CS::a | CS::E | CS::F | CS::G | CS::A => {
|
||||
// Floating point types handle output on their own.
|
||||
let float = arg.as_float()?;
|
||||
let len = format_float(f, float, width, prec, flags, locale, conv_spec, buf)?;
|
||||
let len = format_float(
|
||||
f,
|
||||
float,
|
||||
desired_width,
|
||||
desired_precision,
|
||||
flags,
|
||||
locale,
|
||||
conv_spec,
|
||||
buf,
|
||||
)?;
|
||||
out_len = out_len.checked_add(len).ok_or(Error::Overflow)?;
|
||||
continue 'main;
|
||||
}
|
||||
CS::p => {
|
||||
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
|
||||
prec = prec.map(|p| p.max(PTR_HEX_DIGITS));
|
||||
desired_precision = desired_precision.map(|p| p.max(PTR_HEX_DIGITS));
|
||||
let uint = arg.as_uint()?;
|
||||
if uint != 0 {
|
||||
prefix = "0x";
|
||||
@@ -479,8 +490,8 @@ pub fn sprintf_locale(
|
||||
if uint != 0 {
|
||||
write!(buf, "{:o}", uint)?;
|
||||
}
|
||||
if flags.alt_form && prec.unwrap_or(0) <= buf.len() + 1 {
|
||||
prec = Some(buf.len() + 1);
|
||||
if flags.alt_form && desired_precision.unwrap_or(0) <= buf.len() + 1 {
|
||||
desired_precision = Some(buf.len() + 1);
|
||||
}
|
||||
buf
|
||||
}
|
||||
@@ -514,10 +525,38 @@ pub fn sprintf_locale(
|
||||
CS::s => {
|
||||
// also 'S'
|
||||
let s = arg.as_str(buf)?;
|
||||
let p = prec.unwrap_or(s.len()).min(s.len());
|
||||
prec = Some(p);
|
||||
flags.zero_pad = false;
|
||||
&s[..p]
|
||||
match desired_precision {
|
||||
Some(precision) => {
|
||||
// from man printf(3)
|
||||
// "the maximum number of characters to be printed from a string"
|
||||
// We interpret this to mean the maximum width when printed, as defined by
|
||||
// Unicode grapheme cluster width.
|
||||
let mut byte_len = 0;
|
||||
let mut width = 0;
|
||||
let mut graphemes = s.graphemes(true);
|
||||
// Iteratively add single grapheme clusters as long as the fit within the
|
||||
// width limited by precision.
|
||||
while width < precision {
|
||||
match graphemes.next() {
|
||||
Some(grapheme) => {
|
||||
let grapheme_width = grapheme.width();
|
||||
if width + grapheme_width <= precision {
|
||||
byte_len += grapheme.len();
|
||||
width += grapheme_width;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
let p = precision.min(width);
|
||||
desired_precision = Some(p);
|
||||
&s[..byte_len]
|
||||
}
|
||||
None => s,
|
||||
}
|
||||
}
|
||||
};
|
||||
// Numeric output should be empty iff the value is 0.
|
||||
@@ -528,23 +567,26 @@ pub fn sprintf_locale(
|
||||
// Decide if we want to apply thousands grouping to the body, and compute its size.
|
||||
// Note we have already errored out if grouped is set and this is non-numeric.
|
||||
let wants_grouping = flags.grouped && locale.thousands_sep.is_some();
|
||||
let body_len = match wants_grouping {
|
||||
let body_width = match wants_grouping {
|
||||
// We assume that text representing numbers is ASCII, so len == width.
|
||||
true => body.len() + locale.separator_count(body.len()),
|
||||
false => body.len(),
|
||||
false => body.width(),
|
||||
};
|
||||
|
||||
// Resolve the precision.
|
||||
// In the case of a non-numeric conversion, update the precision to at least the
|
||||
// length of the string.
|
||||
let prec = if !spec_is_numeric {
|
||||
prec.unwrap_or(body_len)
|
||||
let desired_precision = if !spec_is_numeric {
|
||||
desired_precision.unwrap_or(body_width)
|
||||
} else {
|
||||
prec.unwrap_or(1).max(body_len)
|
||||
desired_precision.unwrap_or(1).max(body_width)
|
||||
};
|
||||
|
||||
let prefix_len = prefix.len();
|
||||
let unpadded_width = prefix_len.checked_add(prec).ok_or(Error::Overflow)?;
|
||||
let width = width.max(unpadded_width);
|
||||
let prefix_width = prefix.width();
|
||||
let unpadded_width = prefix_width
|
||||
.checked_add(desired_precision)
|
||||
.ok_or(Error::Overflow)?;
|
||||
let width = desired_width.max(unpadded_width);
|
||||
|
||||
// Pad on the left with spaces to the desired width?
|
||||
if !flags.left_adj && !flags.zero_pad {
|
||||
@@ -560,7 +602,8 @@ pub fn sprintf_locale(
|
||||
}
|
||||
|
||||
// Pad on the left to the given precision?
|
||||
pad(f, '0', prec, body_len)?;
|
||||
// TODO: why pad with 0 here?
|
||||
pad(f, '0', desired_precision, body_width)?;
|
||||
|
||||
// Output the actual value, perhaps with grouping.
|
||||
if wants_grouping {
|
||||
|
||||
@@ -13,6 +13,7 @@ macro_rules! sprintf_check {
|
||||
$(,)? // optional trailing comma
|
||||
) => {
|
||||
{
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
let mut target = String::new();
|
||||
let mut args = [$($arg.to_arg()),*];
|
||||
let len = $crate::printf_c_locale(
|
||||
@@ -20,7 +21,7 @@ macro_rules! sprintf_check {
|
||||
$fmt.as_ref() as &str,
|
||||
&mut args,
|
||||
).expect("printf failed");
|
||||
assert!(len == target.len(), "Wrong length returned: {} vs {}", len, target.len());
|
||||
assert_eq!(len, target.width(), "Wrong length returned");
|
||||
target
|
||||
}
|
||||
};
|
||||
@@ -723,6 +724,18 @@ fn test_huge_precision_g() {
|
||||
sprintf_err!("%.2147483648g", f => Error::Overflow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_ascii() {
|
||||
assert_fmt!("%3s", "ö" => " ö");
|
||||
assert_fmt!("%3s", "🇺🇳" => " 🇺🇳");
|
||||
assert_fmt!("%.3s", "🇺🇳🇺🇳" => "🇺🇳");
|
||||
assert_fmt!("%.3s", "a🇺🇳" => "a🇺🇳");
|
||||
assert_fmt!("%.3s", "aa🇺🇳" => "aa");
|
||||
assert_fmt!("%3.3s", "aa🇺🇳" => " aa");
|
||||
assert_fmt!("%.1s", "𒈙a" => "𒈙");
|
||||
assert_fmt!("%3.3s", "👨👨👧👧" => " 👨👨👧👧");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_errors() {
|
||||
use Error::*;
|
||||
|
||||
@@ -72,7 +72,7 @@ function __fish_bind_complete
|
||||
printf '%sshift-\tShift modifier…\n' $prefix
|
||||
set -l key_names minus comma backspace delete escape \
|
||||
enter up down left right pageup pagedown home end insert tab \
|
||||
space f(seq 12)
|
||||
space menu printscreen f(seq 12)
|
||||
printf '%s\tNamed key\n' $prefix$key_names
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,10 +49,10 @@ function __fish_clj_tools -V bb_helper
|
||||
bb -e "$bb_helper" tools
|
||||
end
|
||||
|
||||
complete -c clj -s X -x -r -k -a "(__fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply exec fn/args"
|
||||
complete -c clj -s A -x -r -k -a "(__fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath"
|
||||
complete -c clj -s M -x -r -k -a "(__fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply main opts"
|
||||
complete -c clj -s T -x -r -k -a "(__fish_complete_list : __fish_clj_tools)" -d "Invoke tool by name or via aliases ala -X"
|
||||
complete -c clj -s X -x -r -k -a "(__fish_stripprefix='^-\w*X' __fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply exec fn/args"
|
||||
complete -c clj -s A -x -r -k -a "(__fish_stripprefix='^-\w*A' __fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath"
|
||||
complete -c clj -s M -x -r -k -a "(__fish_stripprefix='^-\w*M' __fish_complete_list : __fish_clj_aliases)" -d "Use concatenated aliases to modify classpath or supply main opts"
|
||||
complete -c clj -s T -x -r -k -a "(__fish_stripprefix='^-\w*T' __fish_complete_list : __fish_clj_tools)" -d "Invoke tool by name or via aliases ala -X"
|
||||
|
||||
complete -c clj -f -o Sdeps -r -d "Deps data to use as the last deps file to be merged"
|
||||
complete -c clj -f -o Spath -d "Compute classpath and echo to stdout only"
|
||||
|
||||
@@ -85,7 +85,7 @@ complete -c equery -n '__fish_seen_subcommand_from f files' -s s -l timestamp -d
|
||||
complete -c equery -n '__fish_seen_subcommand_from f files' -s t -l type -d "Include file type in output"
|
||||
complete -c equery -n '__fish_seen_subcommand_from f files' -l tree -d "Display results in a tree"
|
||||
complete -c equery -n '__fish_seen_subcommand_from f files' -s f -l filter -d "Filter output by file type" \
|
||||
-xa "(__fish_complete_list , __fish_equery_files_filter_args)"
|
||||
-xa "(__fish_stripprefix='^(--filter=|-\w*f)' __fish_complete_list , __fish_equery_files_filter_args)"
|
||||
|
||||
# has + hasuse
|
||||
complete -c equery -n '__fish_seen_subcommand_from a has h hasuse' -s I -l exclude-installed -d "Exclude installed pkgs from search path"
|
||||
|
||||
@@ -653,6 +653,7 @@ function __fish_git_aliased_command
|
||||
end
|
||||
end
|
||||
|
||||
set -g __fish_git_aliases
|
||||
git config -z --get-regexp 'alias\..*' | while read -lz alias cmdline
|
||||
set -l command (__fish_git_aliased_command $cmdline)
|
||||
string match -q --regex '\w+' -- $command; or continue
|
||||
@@ -778,7 +779,8 @@ function __fish_git_custom_commands
|
||||
# if any of these completion results match the name of the builtin git commands,
|
||||
# but it's simpler just to blacklist these names. They're unlikely to change,
|
||||
# and the failure mode is we accidentally complete a plumbing command.
|
||||
for name in (string replace -r "^.*/git-([^/]*)" '$1' $PATH/git-*)
|
||||
set -l git_subcommands $PATH/git-*
|
||||
for name in (string replace -r "^.*/git-([^/]*)" '$1' $git_subcommands)
|
||||
switch $name
|
||||
case cvsserver receive-pack shell upload-archive upload-pack
|
||||
# skip these
|
||||
@@ -2593,7 +2595,8 @@ end
|
||||
|
||||
# source git-* commands' autocompletion file if exists
|
||||
set -l __fish_git_custom_commands_completion
|
||||
for file in (path filter -xZ $PATH/git-* | path basename)
|
||||
set -l git_subcommands $PATH/git-*
|
||||
for file in (path filter -xZ $git_subcommands | path basename)
|
||||
# Already seen this command earlier in $PATH.
|
||||
contains -- $file $__fish_git_custom_commands_completion
|
||||
and continue
|
||||
|
||||
@@ -4,5 +4,5 @@ complete -c gpasswd -s d -l delete -d 'Remove user from group' -xa '(__fish_comp
|
||||
complete -c gpasswd -s h -l help -d 'Print help'
|
||||
complete -c gpasswd -s r -l remove-password -d 'Remove the GROUP\'s password'
|
||||
complete -c gpasswd -s R -l restrict -d 'Restrict access to GROUP to its members'
|
||||
complete -c gpasswd -s M -l members -d 'Set the list of members of GROUP' -xa '(__fish_complete_list , __fish_complete_users)'
|
||||
complete -c gpasswd -s A -l administrators -d 'set the list of administrators for GROUP' -xa '(__fish_complete_list , __fish_complete_users)'
|
||||
complete -c gpasswd -s M -l members -d 'Set the list of members of GROUP' -xa "(__fish_stripprefix='^(--members=|-\w*M)' __fish_complete_list , __fish_complete_users)"
|
||||
complete -c gpasswd -s A -l administrators -d 'set the list of administrators for GROUP' -xa "(__fish_stripprefix='^(--administrators=|-\w*A)' __fish_complete_list , __fish_complete_users)"
|
||||
|
||||
@@ -36,7 +36,7 @@ complete -c $command -s x -x \
|
||||
-n $compile_condition
|
||||
|
||||
complete -c $command -s W -l warning \
|
||||
-a '(__fish_complete_list , __fish_guild__complete_warnings)' \
|
||||
-a "(__fish_stripprefix='^(--warning=|-\w*W)' __fish_complete_list , __fish_guild__complete_warnings)" \
|
||||
-d 'Specify the warning level for a compilation' \
|
||||
-n $compile_condition
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ complete -c $command -o ds \
|
||||
-d 'Treat the last -s option as if it occurred at this point'
|
||||
|
||||
complete -c $command -l use-srfi \
|
||||
-a '(__fish_complete_list , __fish_guile__complete_srfis)' \
|
||||
-a "(__fish_stripprefix='^--use-srfi=' __fish_complete_list , __fish_guile__complete_srfis)" \
|
||||
-d 'Specify the SRFI modules to load'
|
||||
|
||||
for standard in 6 7
|
||||
|
||||
@@ -58,7 +58,7 @@ complete -c hashcat -l restore -d "Restore session from --session"
|
||||
complete -c hashcat -l restore-disable -d "Do not write restore file"
|
||||
complete -c hashcat -l restore-file-path -rF -d "Specific path to restore file"
|
||||
complete -c hashcat -s o -l outfile -rF -d "Define outfile for recovered hash"
|
||||
complete -c hashcat -l outfile-format -xa "(__fish_complete_list , __fish_hashcat_outfile_formats)" -d "Outfile formats to use"
|
||||
complete -c hashcat -l outfile-format -xa "(__fish_stripprefix='^--outfile-format=' __fish_complete_list , __fish_hashcat_outfile_formats)" -d "Outfile formats to use"
|
||||
complete -c hashcat -l outfile-autohex-disable -d "Disable the use of \$HEX[] in output plains"
|
||||
complete -c hashcat -l outfile-check-timer -x -d "Sets seconds between outfile checks"
|
||||
complete -c hashcat -l wordlist-autohex-disable -d "Disable the conversion of \$HEX[] from the wordlist"
|
||||
@@ -106,7 +106,7 @@ complete -c hashcat -l backend-ignore-metal -d "Do not try to open Metal interfa
|
||||
complete -c hashcat -l backend-ignore-opencl -d "Do not try to open OpenCL interface on startup"
|
||||
complete -c hashcat -s I -l backend-info -d "Show info about detected backend API devices"
|
||||
complete -c hashcat -s d -l backend-devices -x -d "Backend devices to use"
|
||||
complete -c hashcat -s D -l opencl-device-types -xa "(__fish_complete_list , __fish_hashcat_device_types)" -d "OpenCL device-types to use"
|
||||
complete -c hashcat -s D -l opencl-device-types -xa "(__fish_stripprefix='^(--opencl-device-types=|-\w*D)' __fish_complete_list , __fish_hashcat_device_types)" -d "OpenCL device-types to use"
|
||||
complete -c hashcat -s O -l optimized-kernel-enable -d "Enable optimized kernels (limits password length)"
|
||||
complete -c hashcat -s M -l multiply-accel-disable -d "Disable multiply kernel-accel with processor count"
|
||||
complete -c hashcat -s w -l workload-profile -d "Enable a specific workload profile" -xa "
|
||||
|
||||
@@ -9,7 +9,7 @@ function __fish_john_formats --description "Print JohnTheRipper hash formats"
|
||||
end
|
||||
|
||||
complete -c john -l help -d "print usage summary"
|
||||
complete -c john -l single -fa "(__fish_complete_list , __fish_john_rules)" -d "single crack mode"
|
||||
complete -c john -l single -fa "(__fish_stripprefix='^--single=' __fish_complete_list , __fish_john_rules)" -d "single crack mode"
|
||||
complete -c john -l single-seed -rf -d "add static seed word(s) for all salts in single mode"
|
||||
complete -c john -l single-wordlist -rF -d "short wordlist with static seed words/morphemes"
|
||||
complete -c john -l single-user-seed -rF -d "wordlist with seeds per username"
|
||||
@@ -35,8 +35,8 @@ complete -c john -l prince-case-permute -d "permute case of first letter"
|
||||
complete -c john -l prince-mmap -d "memory-map infile"
|
||||
complete -c john -l prince-keyspace -d "just show total keyspace that would be produced"
|
||||
complete -c john -l encoding -l input-encoding -fa "$__fish_john_encodings" -d "input encoding"
|
||||
complete -c john -l rules -fa "(__fish_complete_list , __fish_john_rules)" -d "enable word mangling rules"
|
||||
complete -c john -l rules-stack -fa "(__fish_complete_list , __fish_john_rules)" -d "stacked rules"
|
||||
complete -c john -l rules -fa "(__fish_stripprefix='^--rules=' __fish_complete_list , __fish_john_rules)" -d "enable word mangling rules"
|
||||
complete -c john -l rules-stack -fa "(__fish_stripprefix='^--rules-stack=' __fish_complete_list , __fish_john_rules)" -d "stacked rules"
|
||||
complete -c john -l rules-skip-nop -d "skip any NOP rules"
|
||||
complete -c john -l incremental -fa "(john --list=inc-modes 2>/dev/null)" -d "incremental mode"
|
||||
complete -c john -l incremental-charcount -rf -d "override CharCount for incremental mode"
|
||||
@@ -97,4 +97,4 @@ complete -c john -l internal-codepage -fa "$__fish_john_encodings" -d "codepage
|
||||
complete -c john -l target-encoding -fa "$__fish_john_encodings" -d "output encoding"
|
||||
complete -c john -l tune -fa "auto report N" -d "tuning options"
|
||||
complete -c john -l force-tty -d "set up terminal for reading keystrokes"
|
||||
complete -c john -l format -fa "(__fish_complete_list , __fish_john_formats)" -d "force hash type"
|
||||
complete -c john -l format -fa "(__fish_stripprefix='^--format=' __fish_complete_list , __fish_john_formats)" -d "force hash type"
|
||||
|
||||
@@ -41,7 +41,7 @@ complete -c losetup -s v -l verbose -d "Verbose mode"
|
||||
complete -c losetup -s J -l json -d "Use JSON --list output format"
|
||||
complete -c losetup -s l -l list -d "List info about all or specified"
|
||||
complete -c losetup -s n -l noheadings -d "Don't print headings for --list output"
|
||||
complete -c losetup -s O -l output -x -a "(__fish_complete_list , __fish_print_losetup_list_output)" -d "Specify columns to output for --list"
|
||||
complete -c losetup -s O -l output -x -a "(__fish_stripprefix='^(--output=|-\w*O)' __fish_complete_list , __fish_print_losetup_list_output)" -d "Specify columns to output for --list"
|
||||
complete -c losetup -l output-all -d "Output all columns"
|
||||
complete -c losetup -l raw -d "Use raw --list output format"
|
||||
complete -c losetup -s h -l help -d "Display help"
|
||||
|
||||
@@ -25,7 +25,7 @@ complete -c lpadmin -s o -xa printer-is-shared=true -d 'Sets dest to shared/publ
|
||||
complete -c lpadmin -s o -xa printer-is-shared=false -d 'Sets dest to shared/published or unshared/unpublished'
|
||||
complete -c lpadmin -s o -d 'Set IPP operation policy associated with dest' -xa "printer-policy=(test -r /etc/cups/cupsd.conf; and string replace -r --filter '<Policy (.*)>' '$1' < /etc/cups/cupsd.conf)"
|
||||
|
||||
complete -c lpadmin -s u -xa 'allow:all allow:none (__fish_complete_list , __fish_complete_users allow:)' -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa '(__fish_complete_list , __fish_complete_groups allow: @)' -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa 'deny:all deny:none (__fish_complete_list , __fish_complete_users deny:)' -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa '(__fish_complete_list , __fish_complete_groups deny: @)' -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa "allow:all allow:none (__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users allow:)" -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_groups allow: @)" -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa "deny:all deny:none (__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users deny:)" -d 'Sets user-level access control on a destination'
|
||||
complete -c lpadmin -s u -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_groups deny: @)" -d 'Sets user-level access control on a destination'
|
||||
|
||||
@@ -12,7 +12,7 @@ complete -c lsblk -s h -l help -d "usage information (this)"
|
||||
complete -c lsblk -s i -l ascii -d "use ascii characters only"
|
||||
complete -c lsblk -s m -l perms -d "output info about permissions"
|
||||
complete -c lsblk -s n -l noheadings -d "don't print headings"
|
||||
complete -c lsblk -s o -l output -d "output columns" -xa '( __fish_complete_list , __fish_print_lsblk_columns )'
|
||||
complete -c lsblk -s o -l output -d "output columns" -xa "(__fish_stripprefix='^(--output=|-\w*o)' __fish_complete_list , __fish_print_lsblk_columns)"
|
||||
complete -c lsblk -s P -l pairs -d "use key='value' output format"
|
||||
complete -c lsblk -s r -l raw -d "use raw output format"
|
||||
complete -c lsblk -s t -l topology -d "output info about topology"
|
||||
|
||||
@@ -11,9 +11,9 @@ i\t"ignore the device cache file"
|
||||
r\t"read the device cache file"
|
||||
u\t"read and update the device cache file"'
|
||||
|
||||
complete -c lsof -s g -d 'select by group (^ - negates)' -xa '(__fish_complete_list , __fish_complete_groups)'
|
||||
complete -c lsof -s g -d 'select by group (^ - negates)' -xa "(__fish_stripprefix='^-\w*g' __fish_complete_list , __fish_complete_groups)"
|
||||
complete -c lsof -s l -d 'Convert UIDs to login names'
|
||||
complete -c lsof -s p -d 'Select or exclude processes by pid' -xa '(__fish_complete_list , __fish_complete_pids)'
|
||||
complete -c lsof -s p -d 'Select or exclude processes by pid' -xa "(__fish_stripprefix='^-\w*p' __fish_complete_list , __fish_complete_pids)"
|
||||
complete -c lsof -s R -d 'Print PPID'
|
||||
complete -c lsof -s t -d 'Produce terse output (pids only, no header)'
|
||||
complete -c lsof -s u -d 'select by user (^ - negates)' -xa '(__fish_complete_list , __fish_complete_users)'
|
||||
complete -c lsof -s u -d 'select by user (^ - negates)' -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users)"
|
||||
|
||||
@@ -35,7 +35,7 @@ function __fish_complete_openssl_ciphers
|
||||
printf "%s\tCipher String\n" $cs
|
||||
end
|
||||
end
|
||||
complete -c ncat -l ssl-ciphers -x -a "(__fish_complete_list : __fish_complete_openssl_ciphers)" -d "Specify SSL ciphersuites"
|
||||
complete -c ncat -l ssl-ciphers -x -a "(__fish_stripprefix='^--ssl-ciphers=' __fish_complete_list : __fish_complete_openssl_ciphers)" -d "Specify SSL ciphersuites"
|
||||
complete -c ncat -l ssl-servername -x -a "(__fish_print_hostnames)" -d "Request distinct server name"
|
||||
complete -c ncat -l ssl-alpn -x -d "Specify ALPN protocol list"
|
||||
|
||||
|
||||
@@ -92,11 +92,11 @@ function __fish_complete_nmap_script
|
||||
end
|
||||
echo -e $__fish_nmap_script_completion_cache
|
||||
end
|
||||
complete -c nmap -l script -r -a "(__fish_complete_list , __fish_complete_nmap_script)"
|
||||
complete -c nmap -l script -r -a "(__fish_stripprefix='^--script=' __fish_complete_list , __fish_complete_nmap_script)"
|
||||
complete -c nmap -l script -r -d 'Runs a script scan'
|
||||
complete -c nmap -l script-args -d 'provide arguments to NSE scripts'
|
||||
complete -c nmap -l script-args-file -r -d 'load arguments to NSE scripts from a file'
|
||||
complete -c nmap -l script-help -r -a "(__fish_complete_list , __fish_complete_nmap_script)"
|
||||
complete -c nmap -l script-help -r -a "(__fish_stripprefix='^--script-help=' __fish_complete_list , __fish_complete_nmap_script)"
|
||||
complete -c nmap -l script-help -r -d "Shows help about scripts"
|
||||
complete -c nmap -l script-trace
|
||||
complete -c nmap -l script-updatedb
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
set -l nmoutput (nmcli -g NAME connection show --active 2>/dev/null)
|
||||
or exit # networkmanager isn't running, no point in completing
|
||||
set -l cname (string escape -- $nmoutput\t"Active connection")
|
||||
set -a cname (string escape -- (nmcli -g NAME connection show)\t"Connection")
|
||||
set -l ifname (string escape -- (nmcli -g DEVICE device status)\t"Interface name")
|
||||
set -l ssid (string escape -- (nmcli -g SSID device wifi list)\t"SSID")
|
||||
set -l bssid (string escape -- (nmcli -g BSSID device wifi list | string replace --all \\ '')\t"BSSID")
|
||||
set -l nmoutput '(nmcli -g NAME connection show --active 2>/dev/null)'
|
||||
set -l cname "$nmoutput"\t"Active connection"
|
||||
set -a cname '(nmcli -g NAME connection show 2>/dev/null)\t"Connection"'
|
||||
set -l ifname '(nmcli -g DEVICE device status 2>/dev/null)\t"Interface name"'
|
||||
set -l ssid '(nmcli -g SSID device wifi list 2>/dev/null)\t"SSID"'
|
||||
set -l bssid '(nmcli -g BSSID device wifi list 2>/dev/null | string replace --all \\\ "")\t"BSSID"'
|
||||
|
||||
set -l nmcli_commands general networking radio connection device agent monitor help
|
||||
set -l nmcli_general status hostname permissions logging help
|
||||
|
||||
@@ -10,7 +10,7 @@ if test "$gnu_linux" -eq 1
|
||||
# Some short options are GNU-only
|
||||
complete -c ps -s a -d "Select all processes except session leaders and terminal-less"
|
||||
complete -c ps -s A -d "Select all"
|
||||
complete -c ps -s C -d "Select by command" -ra '(__fish_complete_list , __fish_complete_proc)'
|
||||
complete -c ps -s C -d "Select by command" -ra "(__fish_stripprefix='^-\w*C' __fish_complete_list , __fish_complete_proc)"
|
||||
complete -c ps -s c -d 'Show different scheduler information for the -l option'
|
||||
complete -c ps -s d -d "Select all processes except session leaders"
|
||||
complete -c ps -s e -d "Select all"
|
||||
@@ -24,9 +24,9 @@ if test "$gnu_linux" -eq 1
|
||||
complete -c ps -s m -d 'Show threads after processes'
|
||||
complete -c ps -s N -d "Invert selection"
|
||||
complete -c ps -s n -d "Set namelist file" -r
|
||||
complete -c ps -s s -l sid -d "Select by session ID" -x -a "(__fish_complete_list , __fish_complete_pids)"
|
||||
complete -c ps -s s -l sid -d "Select by session ID" -x -a "(__fish_stripprefix='^(--sid=|-\w*s)' __fish_complete_list , __fish_complete_pids)"
|
||||
complete -c ps -s T -d "Show threads. With SPID"
|
||||
complete -c ps -s u -l user -d "Select by user" -x -a "(__fish_complete_list , __fish_complete_users)"
|
||||
complete -c ps -s u -l user -d "Select by user" -x -a "(__fish_stripprefix='^(--script=|-\w*u)' __fish_complete_list , __fish_complete_users)"
|
||||
complete -c ps -s V -l version -d "Display version and exit"
|
||||
complete -c ps -s y -d "Do not show flags"
|
||||
|
||||
@@ -39,7 +39,7 @@ if test "$gnu_linux" -eq 1
|
||||
complete -c ps -l info -d "Display debug info"
|
||||
complete -c ps -l lines -l rows -d "Set screen height" -r
|
||||
complete -c ps -l no-headers -d 'Print no headers'
|
||||
complete -c ps -l ppid -d "Select by parent PID" -x -a "(__fish_complete_list , __fish_complete_pids)"
|
||||
complete -c ps -l ppid -d "Select by parent PID" -x -a "(__fish_stripprefix='^--ppid=' __fish_complete_list , __fish_complete_pids)"
|
||||
complete -c ps -l sort -d 'Specify sort order' -r
|
||||
else
|
||||
# Assume BSD options otherwise
|
||||
@@ -81,6 +81,6 @@ end
|
||||
complete -c ps -s o -lformat$bsd_null -d "User defined format" -x
|
||||
complete -c ps -s Z -lcontext$bsd_null -d "Include security info"
|
||||
complete -c ps -s t -ltty$bsd_null -d "Select by tty" -r
|
||||
complete -c ps -s G -lgroup$bsd_null -d "Select by group" -x -a "(__fish_complete_list , __fish_complete_groups)"
|
||||
complete -c ps -s U -luser$bsd_null -d "Select by user" -x -a "(__fish_complete_list , __fish_complete_users)"
|
||||
complete -c ps -s p -lpid$bsd_null -d "Select by PID" -x -a "(__fish_complete_list , __fish_complete_pids)"
|
||||
complete -c ps -s G -lgroup$bsd_null -d "Select by group" -x -a "(__fish_stripprefix='^(--group=|-\w*G)' __fish_complete_list , __fish_complete_groups)"
|
||||
complete -c ps -s U -luser$bsd_null -d "Select by user" -x -a "(__fish_stripprefix='^(--user=|-\w*U)' __fish_complete_list , __fish_complete_users)"
|
||||
complete -c ps -s p -lpid$bsd_null -d "Select by PID" -x -a "(__fish_stripprefix='^(--pid=|-\w*p)' __fish_complete_list , __fish_complete_pids)"
|
||||
|
||||
@@ -15,7 +15,7 @@ complete -c setxkbmap -o keycodes -d 'Specifies keycodes component name' -xa "(s
|
||||
complete -c setxkbmap -o keymap -d 'Specifies name of keymap to load' -xa "(sed -r $filter /usr/share/X11/xkb/keymap.dir)"
|
||||
complete -c setxkbmap -o layout -d 'Specifies layout used to choose component names' -xa "(__fish_complete_setxkbmap layout)"
|
||||
complete -c setxkbmap -o model -d 'Specifies model used to choose component names' -xa "(__fish_complete_setxkbmap model)"
|
||||
complete -c setxkbmap -o option -d 'Adds an option used to choose component names' -xa "(__fish_complete_list , '__fish_complete_setxkbmap option')"
|
||||
complete -c setxkbmap -o option -d 'Adds an option used to choose component names' -xa "(__fish_stripprefix='^--option=' __fish_complete_list , '__fish_complete_setxkbmap option')"
|
||||
complete -c setxkbmap -o print -d 'Print a complete xkb_keymap description and exit'
|
||||
complete -c setxkbmap -o query -d 'Print the current layout settings and exit'
|
||||
complete -c setxkbmap -o rules -d 'Name of rules file to use' -x
|
||||
|
||||
@@ -25,7 +25,7 @@ complete -c ssh -s k -d "Disables forwarding of GSSAPI credentials"
|
||||
complete -c ssh -s L -d "Specify local port forwarding" -x
|
||||
complete -c ssh -s l -x -a "(__fish_complete_users)" -d User
|
||||
complete -c ssh -s M -d "Places the ssh client into master mode"
|
||||
complete -c ssh -s m -d "MAC algorithm" -xa "(__fish_complete_list , __fish_ssh_macs)"
|
||||
complete -c ssh -s m -d "MAC algorithm" -xa "(__fish_stripprefix='^-\w*m' __fish_complete_list , __fish_ssh_macs)"
|
||||
complete -c ssh -s N -d "Do not execute remote command"
|
||||
complete -c ssh -s n -d "Prevent reading from stdin"
|
||||
complete -c ssh -s O -d "Control an active connection multiplexing master process" -x
|
||||
|
||||
@@ -12,6 +12,6 @@ complete -c su -s G -l supp-group -x -a "(__fish_complete_groups)" -d "Specify a
|
||||
complete -c su -s m -s p -l preserve_environment -d "Preserve environment"
|
||||
complete -c su -s P -l pty -d "Create pseudo-terminal for the session"
|
||||
complete -c su -s s -l shell -x -a "(cat /etc/shells)" -d "Run the specified shell"
|
||||
complete -c su -s w -l whitelist-environment -x -a "(__fish_complete_list , __fish_complete_su_env_whitelist)" -d "Don't reset these environment variables"
|
||||
complete -c su -s w -l whitelist-environment -x -a "(__fish_stripprefix='^(--whitelist-environment=|-\w*w)' __fish_complete_list , __fish_complete_su_env_whitelist)" -d "Don't reset these environment variables"
|
||||
complete -c su -s h -l help -d "Display help and exit"
|
||||
complete -c su -s V -l version -d "Display version and exit"
|
||||
|
||||
@@ -40,6 +40,6 @@ complete -c systemd-cryptenroll -l fido2-with-user-presence -xa "yes no" -d "Req
|
||||
complete -c systemd-cryptenroll -l fido2-with-user-verification -xa "yes no" -d "Require user verification when unlocking the volume"
|
||||
complete -c systemd-cryptenroll -l tpm2-device -kxa "(__fish_cryptenroll_tpm2_devices)" -d "Enroll a TPM2 security chip"
|
||||
complete -c systemd-cryptenroll -l tpm2-pcrs -x -d "Bind the enrollment of TPM2 device to speficied PCRs"
|
||||
complete -c systemd-cryptenroll -l wipe-slot -kxa "(__fish_complete_list , __fish_cryptenroll_complete_wipe)" -d "Wipes one or more LUKS2 key slots"
|
||||
complete -c systemd-cryptenroll -l wipe-slot -kxa "(__fish_stripprefix='^--wipe-slot=' __fish_complete_list , __fish_cryptenroll_complete_wipe)" -d "Wipes one or more LUKS2 key slots"
|
||||
complete -c systemd-cryptenroll -l help -s h -d "Print a short help"
|
||||
complete -c systemd-cryptenroll -l version -d "Print a short version string"
|
||||
|
||||
@@ -5,7 +5,7 @@ complete -c usermod -s d -l home -d "Change user's login directory" -r
|
||||
complete -c usermod -s e -l expiredate -d "Date (YYYY-MM-DD) on which the user account will be disabled" -x
|
||||
complete -c usermod -s f -l inactive -d "Number of days after a password expires until the account is locked" -xa "(seq 0 365)"
|
||||
complete -c usermod -s g -l gid -d "Group name or number of the user's new initial login group" -xa "(__fish_complete_groups)"
|
||||
complete -c usermod -s G -l groups -d "List of groups which the user is also a member of" -xa "(__fish_complete_list , __fish_complete_groups)"
|
||||
complete -c usermod -s G -l groups -d "List of groups which the user is also a member of" -xa "(__fish_stripprefix='^(--groups=|-\w*G)' __fish_complete_list , __fish_complete_groups)"
|
||||
complete -c usermod -s l -l login -d "Change user's name" -x
|
||||
complete -c usermod -s L -l lock -d "Lock user's password" -f
|
||||
complete -c usermod -s m -l move-home -d "Move the content of the user's home directory to the new location" -f
|
||||
|
||||
@@ -50,7 +50,7 @@ complete -c $progname -s d -d 'Enable extra debugging shown to stderr'
|
||||
complete -c $progname -s h -d 'Show the help message'
|
||||
complete -c $progname -s i -d 'Ignore repositories defined in configuration files'
|
||||
complete -c $progname -s M -d 'For remote repositories, the data is fetched and stored in memory only'
|
||||
complete -c $progname -s p -d 'Match one or more package properties' -xa "(__fish_complete_list , __fish_print_xbps_pkg_props)"
|
||||
complete -c $progname -s p -d 'Match one or more package properties' -xa "(__fish_stripprefix='^-\w*p' __fish_complete_list , __fish_print_xbps_pkg_props)"
|
||||
complete -c $progname -s R -d 'Enable repository mode'
|
||||
complete -c $progname -l repository -d 'Append the specified repository to the top of the list'
|
||||
complete -c $progname -l regex -d 'Use Extended Regular Expressions'
|
||||
|
||||
@@ -14,15 +14,17 @@ where:
|
||||
set -q prefix[1]
|
||||
or set -l prefix ""
|
||||
set -l pat "$(commandline -t)"
|
||||
#set -l pat $argv[5]
|
||||
if set -q __fish_stripprefix[1]
|
||||
set pat "$(string replace -r -- "$__fish_stripprefix" "" $pat)"
|
||||
end
|
||||
switch $pat
|
||||
case "*$div*"
|
||||
for i in (echo $pat | sed "s/^\(.\+$div\)$iprefix.*\$/\1/")$iprefix(eval $cmd)
|
||||
string unescape -- $i
|
||||
for i in (string unescape -- $pat | sed "s/^\(.\+$div\)$iprefix.*\$/\1/")$iprefix(eval $cmd)
|
||||
printf %s\n $i
|
||||
end
|
||||
case '*'
|
||||
for i in $prefix$iprefix(eval $cmd)
|
||||
string unescape -- $i
|
||||
printf %s\n $i
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
function __fish_complete_pgrep -d 'Complete pgrep/pkill' --argument-names cmd
|
||||
complete -c $cmd -xa '(__fish_complete_proc)'
|
||||
complete -c $cmd -s f -d 'Match pattern against full command line'
|
||||
complete -c $cmd -s g -d 'Only match processes in the process group' -xa '(__fish_complete_list , __fish_complete_groups)'
|
||||
complete -c $cmd -s G -d "Only match processes whose real group ID is listed. Group 0 is translated into $cmd\'s own process group" -xa '(__fish_complete_list , __fish_complete_groups)'
|
||||
complete -c $cmd -s g -d 'Only match processes in the process group' -xa "(__fish_stripprefix='^-\w*g' __fish_complete_list , __fish_complete_groups)"
|
||||
complete -c $cmd -s G -d "Only match processes whose real group ID is listed. Group 0 is translated into $cmd\'s own process group" -xa "(__fish_stripprefix='^-\w*G' __fish_complete_list , __fish_complete_groups)"
|
||||
complete -c $cmd -s n -d 'Select only the newest process'
|
||||
complete -c $cmd -s o -d 'Select only the oldest process'
|
||||
complete -c $cmd -s P -d 'Only match processes whose parent process ID is listed' -xa '(__fish_complete_list , __fish_complete_pids)'
|
||||
complete -c $cmd -s P -d 'Only match processes whose parent process ID is listed' -xa "(__fish_stripprefix='^-\w*P' __fish_complete_list , __fish_complete_pids)"
|
||||
complete -c $cmd -s s -d "Only match processes whose process session ID is listed. Session ID 0 is translated into $cmd\'s own session ID."
|
||||
complete -c $cmd -s t -d 'Only match processes whose controlling terminal is listed. The terminal name should be specified without the "/dev/" prefix' -r
|
||||
complete -c $cmd -s u -d 'Only match processes whose effective user ID is listed' -xa '(__fish_complete_list , __fish_complete_users)'
|
||||
complete -c $cmd -s U -d 'Only match processes whose real user ID is listed' -xa '(__fish_complete_list , __fish_complete_users)'
|
||||
complete -c $cmd -s u -d 'Only match processes whose effective user ID is listed' -xa "(__fish_stripprefix='^-\w*u' __fish_complete_list , __fish_complete_users)"
|
||||
complete -c $cmd -s U -d 'Only match processes whose real user ID is listed' -xa "(__fish_stripprefix='^-\w*U' __fish_complete_list , __fish_complete_users)"
|
||||
complete -c $cmd -s v -d 'Negates the matching'
|
||||
complete -c $cmd -s x -d ' Only match processes whose name (or command line if -f is specified) exactly match the pattern'
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ function __fish_complete_ssh -d "common completions for ssh commands" --argument
|
||||
complete -c $command -s 6 -d "IPv6 only"
|
||||
complete -c $command -s A -d "Enables forwarding of the authentication agent"
|
||||
complete -c $command -s C -d "Compress all data"
|
||||
complete -c $command -s c -d "Encryption algorithm" -xa "(__fish_complete_list , __fish_ssh_ciphers)"
|
||||
complete -c $command -s c -d "Encryption algorithm" -xa "(__fish_stripprefix='^-\w*c' __fish_complete_list , __fish_ssh_ciphers)"
|
||||
complete -c $command -s F -d "Configuration file" -rF
|
||||
complete -c $command -s i -d "Identity key file" -rF
|
||||
complete -c $command -s J -d 'ProxyJump host' -xa "(__fish_complete_user_at_hosts)"
|
||||
|
||||
@@ -219,12 +219,17 @@ end" >$__fish_config_dir/config.fish
|
||||
end
|
||||
|
||||
# Notify terminals when $PWD changes via OSC 7 (issue #906).
|
||||
function __fish_update_cwd_osc --on-variable PWD --description 'Notify terminals when $PWD changes'
|
||||
set -l host $hostname
|
||||
if set -q KONSOLE_VERSION
|
||||
set host ''
|
||||
if not functions --query __fish_update_cwd_osc
|
||||
function __fish_update_cwd_osc --on-variable PWD --description 'Notify terminals when $PWD changes'
|
||||
set -l host $hostname
|
||||
if set -q KONSOLE_VERSION
|
||||
set host ''
|
||||
end
|
||||
if [ "$TERM" = dumb ]
|
||||
return
|
||||
end
|
||||
printf \e\]7\;file://%s%s\a $host (string escape --style=url -- $PWD)
|
||||
end
|
||||
printf \e\]7\;file://%s%s\a $host (string escape --style=url -- $PWD)
|
||||
end
|
||||
__fish_update_cwd_osc # Run once because we might have already inherited a PWD from an old tab
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
|
||||
function __fish_seen_subcommand_from
|
||||
set -l regex (string escape --style=regex -- (commandline -pxc)[2..] | string join '|')
|
||||
string match -rq -- "^$regex"'$' $argv
|
||||
string match -rq -- "^($regex)\$" $argv
|
||||
end
|
||||
|
||||
@@ -59,6 +59,9 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
|
||||
$legacy_bind --preset $argv \e\[1\;9C nextd-or-forward-word # iTerm2 < 3.5.12
|
||||
$legacy_bind --preset $argv \e\[1\;9D prevd-or-backward-word # iTerm2 < 3.5.12
|
||||
|
||||
bind --preset $argv alt-b prevd-or-backward-word
|
||||
bind --preset $argv alt-f nextd-or-forward-word
|
||||
|
||||
bind --preset $argv alt-up history-token-search-backward
|
||||
bind --preset $argv alt-down history-token-search-forward
|
||||
$legacy_bind --preset $argv \e\[1\;9A history-token-search-backward # iTerm2 < 3.5.12
|
||||
|
||||
@@ -51,17 +51,17 @@ function fish_default_key_bindings -d "emacs-like key binds"
|
||||
bind --preset $argv ctrl-/ undo
|
||||
bind --preset $argv ctrl-_ undo # XTerm idiosyncracy, can get rid of this once we go full CSI u
|
||||
bind --preset $argv ctrl-z undo
|
||||
bind --preset $argv ctrl-Z redo
|
||||
bind --preset $argv ctrl-shift-z redo
|
||||
bind --preset $argv alt-/ redo
|
||||
bind --preset $argv alt-t transpose-words
|
||||
bind --preset $argv alt-u upcase-word
|
||||
|
||||
bind --preset $argv alt-c capitalize-word
|
||||
bind --preset $argv alt-backspace backward-kill-word
|
||||
bind --preset $argv alt-delete kill-word
|
||||
bind --preset $argv ctrl-alt-h backward-kill-word
|
||||
bind --preset $argv ctrl-backspace backward-kill-word
|
||||
bind --preset $argv ctrl-delete kill-word
|
||||
bind --preset $argv alt-b prevd-or-backward-word
|
||||
bind --preset $argv alt-f nextd-or-forward-word
|
||||
|
||||
bind --preset $argv alt-\< beginning-of-buffer
|
||||
bind --preset $argv alt-\> end-of-buffer
|
||||
|
||||
@@ -247,7 +247,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
# in vim p means paste *after* current character, so go forward a char before pasting
|
||||
# also in vim, P means paste *at* current position (like at '|' with cursor = line),
|
||||
# \ so there's no need to go back a char, just paste it without moving
|
||||
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_modefish_cursor_end_modeinclusive' yank
|
||||
bind -s --preset p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' yank
|
||||
bind -s --preset P yank
|
||||
bind -s --preset g,p yank-pop
|
||||
|
||||
@@ -261,10 +261,10 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
# Lowercase r, enters replace_one mode
|
||||
#
|
||||
bind -s --preset -m replace_one r repaint-mode
|
||||
bind -s --preset -M replace_one -m default '' delete-char self-insert backward-char repaint-mode
|
||||
bind -s --preset -M replace_one -m default enter 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default ctrl-j 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default ctrl-m 'commandline -f delete-char; commandline -i \n; commandline -f backward-char; commandline -f repaint-mode'
|
||||
bind -s --preset -M replace_one -m default '' 'set -g fish_cursor_end_mode exclusive' delete-char self-insert backward-char repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default enter 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default ctrl-j 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default ctrl-m 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
|
||||
bind -s --preset -M replace_one -m default escape cancel repaint-mode
|
||||
bind -s --preset -M replace_one -m default ctrl-\[ cancel repaint-mode
|
||||
|
||||
|
||||
@@ -656,7 +656,8 @@ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
impl Acceptor for $name {
|
||||
#[allow(unused_variables)]
|
||||
fn accept<'a>(&'a self, visitor: &mut dyn NodeVisitor<'a>, reversed: bool) {
|
||||
accept_list_visitor!(Self, accept, visit, self, visitor, reversed, $contents);
|
||||
let _ =
|
||||
accept_list_visitor!(Self, accept, visit, self, visitor, reversed, $contents);
|
||||
}
|
||||
}
|
||||
impl AcceptorMut for $name {
|
||||
@@ -743,7 +744,7 @@ macro_rules! implement_acceptor_for_branch {
|
||||
impl Acceptor for $name {
|
||||
#[allow(unused_variables)]
|
||||
fn accept<'a>(&'a self, visitor: &mut dyn NodeVisitor<'a>, reversed: bool){
|
||||
visitor_accept_field!(
|
||||
let _ = visitor_accept_field!(
|
||||
Self,
|
||||
accept,
|
||||
visit,
|
||||
@@ -3718,7 +3719,7 @@ fn allocate<T: NodeMut + Default>(&self) -> Box<T> {
|
||||
// Return the resulting Node pointer. It is never null.
|
||||
fn allocate_visit<T: NodeMut + Default>(&mut self) -> Box<T> {
|
||||
let mut result = Box::<T>::default();
|
||||
self.visit_mut(&mut *result);
|
||||
let _ = self.visit_mut(&mut *result);
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
175
src/bin/fish.rs
175
src/bin/fish.rs
@@ -21,6 +21,8 @@
|
||||
#![allow(unstable_name_collisions)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
#[cfg(feature = "installable")]
|
||||
use fish::common::{get_executable_path, wcs2osstring};
|
||||
#[allow(unused_imports)]
|
||||
use fish::future::IsSomeAnd;
|
||||
use fish::{
|
||||
@@ -29,12 +31,12 @@
|
||||
BUILTIN_ERR_MISSING, BUILTIN_ERR_UNKNOWN, STATUS_CMD_OK, STATUS_CMD_UNKNOWN,
|
||||
},
|
||||
common::{
|
||||
escape, get_executable_path, save_term_foreground_process_group, scoped_push_replacer,
|
||||
str2wcstring, wcs2osstring, wcs2string, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME,
|
||||
escape, save_term_foreground_process_group, scoped_push_replacer, str2wcstring, wcs2string,
|
||||
PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME,
|
||||
},
|
||||
env::{
|
||||
environment::{env_init, EnvStack, Environment},
|
||||
ConfigPaths, EnvMode, Statuses,
|
||||
ConfigPaths, EnvMode, Statuses, CONFIG_PATHS,
|
||||
},
|
||||
eprintf,
|
||||
event::{self, Event},
|
||||
@@ -65,22 +67,16 @@
|
||||
use std::fs::File;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::{env, ops::ControlFlow};
|
||||
|
||||
const DOC_DIR: &str = env!("DOCDIR");
|
||||
const DATA_DIR: &str = env!("DATADIR");
|
||||
const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR");
|
||||
const SYSCONF_DIR: &str = env!("SYSCONFDIR");
|
||||
const BIN_DIR: &str = env!("BINDIR");
|
||||
|
||||
#[cfg(feature = "installable")]
|
||||
// Disable for clippy because otherwise it would require sphinx
|
||||
#[cfg(not(clippy))]
|
||||
fn install(confirm: bool, dir: PathBuf) -> bool {
|
||||
fn install(confirm: bool, dir: &PathBuf) -> bool {
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
@@ -192,7 +188,8 @@ fn install(confirm: bool, dir: PathBuf) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(any(clippy, not(feature = "installable")))]
|
||||
fn install(_confirm: bool, _dir: PathBuf) -> bool {
|
||||
#[allow(dead_code)]
|
||||
fn install(_confirm: bool, _dir: &PathBuf) -> bool {
|
||||
eprintln!("Fish was built without support for self-installation");
|
||||
return false;
|
||||
}
|
||||
@@ -264,128 +261,6 @@ fn print_rusage_self() {
|
||||
eprintln!(" signals: {signals}");
|
||||
}
|
||||
|
||||
fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||
// PORTING: why is this not just an associated method on ConfigPaths?
|
||||
|
||||
let mut paths = ConfigPaths::default();
|
||||
let mut done = false;
|
||||
let exec_path = get_executable_path(argv0.as_ref());
|
||||
if let Ok(exec_path) = exec_path.canonicalize() {
|
||||
FLOG!(
|
||||
config,
|
||||
format!("exec_path: {:?}, argv[0]: {:?}", exec_path, argv0.as_ref())
|
||||
);
|
||||
// TODO: we should determine program_name from argv0 somewhere in this file
|
||||
|
||||
// Detect if we're running right out of the CMAKE build directory
|
||||
if exec_path.starts_with(env!("CARGO_MANIFEST_DIR")) {
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
FLOG!(
|
||||
config,
|
||||
"Running out of target directory, using paths relative to CARGO_MANIFEST_DIR:\n",
|
||||
manifest_dir.display()
|
||||
);
|
||||
done = true;
|
||||
paths = ConfigPaths {
|
||||
data: manifest_dir.join("share"),
|
||||
sysconf: manifest_dir.join("etc"),
|
||||
doc: manifest_dir.join("user_doc/html"),
|
||||
bin: Some(exec_path.parent().unwrap().to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// The next check is that we are in a relocatable directory tree
|
||||
if exec_path.ends_with("bin/fish") {
|
||||
let base_path = exec_path.parent().unwrap().parent().unwrap();
|
||||
paths = ConfigPaths {
|
||||
// One obvious path is ~/.local (with fish in ~/.local/bin/).
|
||||
// If we picked ~/.local/share/fish as our data path,
|
||||
// we would install there and erase history.
|
||||
// So let's isolate us a bit more.
|
||||
#[cfg(feature = "installable")]
|
||||
data: base_path.join("share/fish/install"),
|
||||
#[cfg(not(feature = "installable"))]
|
||||
data: base_path.join("share/fish"),
|
||||
sysconf: base_path.join("etc/fish"),
|
||||
doc: base_path.join("share/doc/fish"),
|
||||
bin: Some(base_path.join("bin")),
|
||||
}
|
||||
} else if exec_path.ends_with("fish") {
|
||||
FLOG!(
|
||||
config,
|
||||
"'fish' not in a 'bin/', trying paths relative to source tree"
|
||||
);
|
||||
let base_path = exec_path.parent().unwrap();
|
||||
paths = ConfigPaths {
|
||||
#[cfg(feature = "installable")]
|
||||
data: base_path.join("share/install"),
|
||||
#[cfg(not(feature = "installable"))]
|
||||
data: base_path.join("share"),
|
||||
sysconf: base_path.join("etc"),
|
||||
doc: base_path.join("user_doc/html"),
|
||||
bin: Some(base_path.to_path_buf()),
|
||||
}
|
||||
}
|
||||
|
||||
if paths.data.exists() && paths.sysconf.exists() {
|
||||
// The docs dir may not exist; in that case fall back to the compiled in path.
|
||||
if !paths.doc.exists() {
|
||||
paths.doc = PathBuf::from(DOC_DIR);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// Fall back to what got compiled in.
|
||||
let data = if cfg!(feature = "installable") {
|
||||
let Some(home) = fish::env::get_home() else {
|
||||
FLOG!(
|
||||
error,
|
||||
"Cannot find home directory and will refuse to read configuration.\n",
|
||||
"Consider installing into a directory tree with `fish --install=PATH`."
|
||||
);
|
||||
return paths;
|
||||
};
|
||||
|
||||
PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR)
|
||||
} else {
|
||||
PathBuf::from(DATA_DIR).join(DATA_DIR_SUBDIR)
|
||||
};
|
||||
let bin = if cfg!(feature = "installable") {
|
||||
exec_path.parent().map(|x| x.to_path_buf())
|
||||
} else {
|
||||
Some(PathBuf::from(BIN_DIR))
|
||||
};
|
||||
|
||||
FLOG!(config, "Using compiled in paths:");
|
||||
paths = ConfigPaths {
|
||||
data,
|
||||
sysconf: PathBuf::from(SYSCONF_DIR).join("fish"),
|
||||
doc: DOC_DIR.into(),
|
||||
bin,
|
||||
}
|
||||
}
|
||||
|
||||
FLOGF!(
|
||||
config,
|
||||
"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \
|
||||
%ls\npaths.doc: %ls\npaths.bin: %ls",
|
||||
paths.data.display().to_string(),
|
||||
paths.sysconf.display().to_string(),
|
||||
paths.doc.display().to_string(),
|
||||
paths
|
||||
.bin
|
||||
.clone()
|
||||
.map(|x| x.display().to_string())
|
||||
.unwrap_or("|not found|".to_string()),
|
||||
);
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
// Source the file config.fish in the given directory.
|
||||
// Returns true if successful, false if not.
|
||||
fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool {
|
||||
@@ -463,7 +338,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
|
||||
);
|
||||
}
|
||||
|
||||
install(true, PathBuf::from(wcs2osstring(&datapath)));
|
||||
install(true, &PathBuf::from(wcs2osstring(&datapath)));
|
||||
// We try to go on if installation failed (or was rejected) here
|
||||
// If the assets are missing, we will trigger a later error,
|
||||
// if they are outdated, things will probably (tm) work somewhat.
|
||||
@@ -592,8 +467,9 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
|
||||
// path/share/fish/ is the data directory
|
||||
// path/etc/fish is sysconf????
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
let dir = PathBuf::from(wcs2osstring(path));
|
||||
if install(true, dir.join("share/fish/install")) {
|
||||
if install(true, &dir.join("share/fish/install")) {
|
||||
for sub in &["share/fish/install", "etc/fish", "bin"] {
|
||||
let p = dir.join(sub);
|
||||
let Ok(_) = fs::create_dir_all(p.clone()) else {
|
||||
@@ -626,14 +502,12 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let paths = Some(determine_config_directory_paths(OsString::from_vec(
|
||||
wcs2string(&args[0]),
|
||||
)));
|
||||
let paths = Some(&*CONFIG_PATHS);
|
||||
let Some(paths) = paths else {
|
||||
FLOG!(error, "Cannot find config paths");
|
||||
std::process::exit(1);
|
||||
};
|
||||
install(true, paths.data);
|
||||
install(true, &paths.data);
|
||||
}
|
||||
}
|
||||
'l' => opts.is_login = true,
|
||||
@@ -817,18 +691,19 @@ fn throwing_main() -> i32 {
|
||||
save_term_foreground_process_group();
|
||||
}
|
||||
|
||||
let mut paths: Option<ConfigPaths> = None;
|
||||
let mut paths: Option<&ConfigPaths> = None;
|
||||
// If we're not executing, there's no need to find the config.
|
||||
if !opts.no_exec {
|
||||
paths = Some(determine_config_directory_paths(OsString::from_vec(
|
||||
wcs2string(&args[0]),
|
||||
)));
|
||||
paths = Some(&*CONFIG_PATHS);
|
||||
env_init(
|
||||
paths.as_ref(),
|
||||
paths,
|
||||
/* do uvars */ !opts.no_config,
|
||||
/* default paths */ opts.no_config,
|
||||
);
|
||||
}
|
||||
paths
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Set features early in case other initialization depends on them.
|
||||
// Start with the ones set in the environment, then those set on the command line (so the
|
||||
@@ -845,7 +720,7 @@ fn throwing_main() -> i32 {
|
||||
|
||||
// Construct the root parser!
|
||||
let env = Rc::new(EnvStack::globals().create_child(true /* dispatches_var_changes */));
|
||||
let parser: &Parser = &Parser::new(env, CancelBehavior::Clear);
|
||||
let parser = &Parser::new(env, CancelBehavior::Clear);
|
||||
parser.set_syncs_uvars(!opts.no_config);
|
||||
|
||||
if !opts.no_exec && !opts.no_config {
|
||||
@@ -1008,7 +883,6 @@ fn fish_xdm_login_hack_hack_hack_hack(cmds: &mut [OsString], args: &[WString]) -
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut result = false;
|
||||
let cmd = &cmds[0];
|
||||
if cmd == "exec \"${@}\"" || cmd == "exec \"$@\"" {
|
||||
// We're going to construct a new command that starts with exec, and then has the
|
||||
@@ -1020,7 +894,8 @@ fn fish_xdm_login_hack_hack_hack_hack(cmds: &mut [OsString], args: &[WString]) -
|
||||
}
|
||||
|
||||
cmds[0] = new_cmd;
|
||||
result = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
eprintf, fprintf,
|
||||
input::input_terminfo_get_name,
|
||||
input_common::{
|
||||
terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent, InputEventQueue,
|
||||
InputEventQueuer,
|
||||
match_key_event_to_key, terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent,
|
||||
InputEventQueue, InputEventQueuer, KeyEvent,
|
||||
},
|
||||
key::{self, char_to_symbol, Key},
|
||||
key::{char_to_symbol, Key},
|
||||
panic::panic_handler,
|
||||
print_help::print_help,
|
||||
printf,
|
||||
@@ -37,15 +37,21 @@
|
||||
};
|
||||
|
||||
/// Return true if the recent sequence of characters indicates the user wants to exit the program.
|
||||
fn should_exit(recent_keys: &mut Vec<Key>, key: Key) -> bool {
|
||||
recent_keys.push(key);
|
||||
fn should_exit(recent_keys: &mut Vec<KeyEvent>, key_evt: KeyEvent) -> bool {
|
||||
recent_keys.push(key_evt);
|
||||
|
||||
for evt in [VINTR, VEOF] {
|
||||
let modes = shell_modes();
|
||||
let cc = Key::from_single_byte(modes.c_cc[evt]);
|
||||
|
||||
if key == cc {
|
||||
if recent_keys.iter().rev().nth(1) == Some(&cc) {
|
||||
if match_key_event_to_key(&key_evt, &cc).is_some() {
|
||||
if recent_keys
|
||||
.iter()
|
||||
.rev()
|
||||
.nth(1)
|
||||
.and_then(|&prev| match_key_event_to_key(&prev, &cc))
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
eprintf!(
|
||||
@@ -101,9 +107,6 @@ fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
|
||||
continue;
|
||||
};
|
||||
let c = kevt.key.codepoint;
|
||||
if c == key::Invalid {
|
||||
continue;
|
||||
}
|
||||
if verbose {
|
||||
printf!("# decoded from: ");
|
||||
for byte in kevt.seq.chars() {
|
||||
@@ -111,7 +114,27 @@ fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
|
||||
}
|
||||
printf!("\n");
|
||||
}
|
||||
printf!("bind %s 'do something'\n", kevt.key);
|
||||
let have_shifted_key = kevt.key.shifted_codepoint != '\0';
|
||||
let mut keys = vec![(kevt.key.key, "")];
|
||||
if have_shifted_key {
|
||||
let mut shifted_key = kevt.key.key;
|
||||
shifted_key.modifiers.shift = false;
|
||||
shifted_key.codepoint = kevt.key.shifted_codepoint;
|
||||
keys.push((shifted_key, "shifted key"));
|
||||
}
|
||||
if kevt.key.base_layout_codepoint != '\0' {
|
||||
let mut base_layout_key = kevt.key.key;
|
||||
base_layout_key.codepoint = kevt.key.base_layout_codepoint;
|
||||
keys.push((base_layout_key, "physical key"));
|
||||
}
|
||||
for (key, explanation) in keys {
|
||||
printf!(
|
||||
"bind %s 'do something'%s%s\n",
|
||||
key,
|
||||
if explanation.is_empty() { "" } else { " # " },
|
||||
explanation,
|
||||
);
|
||||
}
|
||||
if let Some(name) = sequence_name(&mut recent_chars1, c) {
|
||||
printf!("bind -k %ls 'do something'\n", name);
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@ fn send_to_bg(
|
||||
let err = {
|
||||
let job = &jobs[job_pos];
|
||||
wgettext_fmt!(
|
||||
"%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()
|
||||
)
|
||||
"%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()
|
||||
)
|
||||
};
|
||||
drop(jobs);
|
||||
builtin_print_help_error(parser, streams, cmd, &err);
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ fn builtin_complete_remove_cmd(
|
||||
|
||||
if !removed {
|
||||
// This means that all loops were empty.
|
||||
complete_remove_all(cmd.to_owned(), cmd_is_path);
|
||||
complete_remove_all(cmd.to_owned(), cmd_is_path, /*explicit=*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Optio
|
||||
let job_group = job.group();
|
||||
job_group.set_is_foreground(true);
|
||||
if job.entitled_to_terminal() {
|
||||
crate::input_common::terminal_protocols_disable_ifn();
|
||||
crate::input_common::terminal_protocols_disable_ifn(false);
|
||||
}
|
||||
let tmodes = job_group.tmodes.borrow();
|
||||
if job_group.wants_terminal() && tmodes.is_some() {
|
||||
|
||||
@@ -248,7 +248,7 @@ fn read_interactive(
|
||||
|
||||
reader_readline(parser, nchars)
|
||||
};
|
||||
terminal_protocols_disable_ifn();
|
||||
terminal_protocols_disable_ifn(false);
|
||||
if let Some(line) = mline {
|
||||
*buff = line;
|
||||
if nchars > 0 && nchars < buff.len() {
|
||||
|
||||
@@ -582,11 +582,10 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
|
||||
STATUS_CURRENT_CMD => {
|
||||
let command = &parser.libdata().status_vars.command;
|
||||
if !command.is_empty() {
|
||||
streams.out.append(command);
|
||||
streams.out.appendln(command);
|
||||
} else {
|
||||
streams.out.appendln(*PROGRAM_NAME.get().unwrap());
|
||||
}
|
||||
streams.out.append_char('\n');
|
||||
}
|
||||
STATUS_CURRENT_COMMANDLINE => {
|
||||
let commandline = &parser.libdata().status_vars.commandline;
|
||||
|
||||
@@ -1935,19 +1935,16 @@ fn complete_custom(&mut self, cmd: &wstr, cmdline: &wstr, ad: &mut CustomArgData
|
||||
// Perhaps set a transient commandline so that custom completions
|
||||
// builtin_commandline will refer to the wrapped command. But not if
|
||||
// we're doing autosuggestions.
|
||||
let mut _remove_transient = None;
|
||||
let wants_transient =
|
||||
(ad.wrap_depth > 0 || !ad.var_assignments.is_empty()) && !is_autosuggest;
|
||||
if wants_transient {
|
||||
let _remove_transient = (!is_autosuggest).then(|| {
|
||||
let parser = self.ctx.parser();
|
||||
parser
|
||||
.libdata_mut()
|
||||
.transient_commandlines
|
||||
.push(cmdline.to_owned());
|
||||
_remove_transient = Some(ScopeGuard::new((), move |_| {
|
||||
ScopeGuard::new((), move |_| {
|
||||
parser.libdata_mut().transient_commandlines.pop();
|
||||
}));
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Maybe apply variable assignments.
|
||||
let _restore_vars = self.apply_var_assignments(ad.var_assignments);
|
||||
@@ -2342,14 +2339,14 @@ pub fn complete_remove(cmd: WString, cmd_is_path: bool, option: &wstr, typ: Comp
|
||||
}
|
||||
|
||||
/// Removes all completions for a given command.
|
||||
pub fn complete_remove_all(cmd: WString, cmd_is_path: bool) {
|
||||
pub fn complete_remove_all(cmd: WString, cmd_is_path: bool, explicit: bool) {
|
||||
let mut completion_map = COMPLETION_MAP.lock().expect("mutex poisoned");
|
||||
let idx = CompletionEntryIndex {
|
||||
name: cmd,
|
||||
is_path: cmd_is_path,
|
||||
};
|
||||
let removed = completion_map.remove(&idx).is_some();
|
||||
if !removed && !idx.is_path {
|
||||
if explicit && !removed && !idx.is_path {
|
||||
COMPLETION_TOMBSTONES.lock().unwrap().insert(idx.name);
|
||||
}
|
||||
}
|
||||
@@ -2522,7 +2519,7 @@ pub fn complete_invalidate_path() {
|
||||
.expect("mutex poisoned")
|
||||
.get_autoloaded_commands();
|
||||
for cmd in cmds {
|
||||
complete_remove_all(cmd, false /* not a path */);
|
||||
complete_remove_all(cmd, /*cmd_is_path=*/ false, /*explicit=*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
168
src/env/config_paths.rs
vendored
Normal file
168
src/env/config_paths.rs
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
use super::ConfigPaths;
|
||||
use crate::env;
|
||||
use crate::{common::get_executable_path, FLOG, FLOGF};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const DOC_DIR: &str = env!("DOCDIR");
|
||||
const DATA_DIR: &str = env!("DATADIR");
|
||||
const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR");
|
||||
const SYSCONF_DIR: &str = env!("SYSCONFDIR");
|
||||
const BIN_DIR: &str = env!("BINDIR");
|
||||
const LOCALE_DIR: &str = env!("LOCALEDIR");
|
||||
|
||||
pub static CONFIG_PATHS: Lazy<ConfigPaths> = Lazy::new(|| {
|
||||
// Read the current executable and follow all symlinks to it.
|
||||
// OpenBSD has issues with `std::env::current_exe`, see gh-9086 and
|
||||
// https://github.com/rust-lang/rust/issues/60560
|
||||
let argv0 = PathBuf::from(std::env::args().next().unwrap());
|
||||
let argv0 = if argv0.exists() {
|
||||
argv0
|
||||
} else {
|
||||
std::env::current_exe().unwrap_or(argv0)
|
||||
};
|
||||
let argv0 = argv0.canonicalize().unwrap_or(argv0);
|
||||
determine_config_directory_paths(argv0)
|
||||
});
|
||||
|
||||
fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||
// PORTING: why is this not just an associated method on ConfigPaths?
|
||||
|
||||
let mut paths = ConfigPaths::default();
|
||||
let mut done = false;
|
||||
let exec_path = get_executable_path(argv0.as_ref());
|
||||
if let Ok(exec_path) = exec_path.canonicalize() {
|
||||
FLOG!(
|
||||
config,
|
||||
format!("exec_path: {:?}, argv[0]: {:?}", exec_path, argv0.as_ref())
|
||||
);
|
||||
// TODO: we should determine program_name from argv0 somewhere in this file
|
||||
|
||||
// Detect if we're running right out of the CMAKE build directory
|
||||
if exec_path.starts_with(env!("CARGO_MANIFEST_DIR")) {
|
||||
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
FLOG!(
|
||||
config,
|
||||
"Running out of target directory, using paths relative to CARGO_MANIFEST_DIR:\n",
|
||||
manifest_dir.display()
|
||||
);
|
||||
done = true;
|
||||
paths = ConfigPaths {
|
||||
data: manifest_dir.join("share"),
|
||||
sysconf: manifest_dir.join("etc"),
|
||||
doc: manifest_dir.join("user_doc/html"),
|
||||
bin: Some(exec_path.parent().unwrap().to_owned()),
|
||||
locale: Some(manifest_dir.join("share/locale")),
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// The next check is that we are in a relocatable directory tree
|
||||
if exec_path.ends_with("bin/fish") {
|
||||
let base_path = exec_path.parent().unwrap().parent().unwrap();
|
||||
#[cfg(feature = "installable")]
|
||||
let data = base_path.join("share/fish/install");
|
||||
#[cfg(not(feature = "installable"))]
|
||||
let data = base_path.join("share/fish");
|
||||
let locale =
|
||||
(!cfg!(feature = "installable")).then(|| base_path.join("share/locale"));
|
||||
paths = ConfigPaths {
|
||||
// One obvious path is ~/.local (with fish in ~/.local/bin/).
|
||||
// If we picked ~/.local/share/fish as our data path,
|
||||
// we would install there and erase history.
|
||||
// So let's isolate us a bit more.
|
||||
data,
|
||||
sysconf: base_path.join("etc/fish"),
|
||||
doc: base_path.join("share/doc/fish"),
|
||||
bin: Some(base_path.join("bin")),
|
||||
locale,
|
||||
}
|
||||
} else if exec_path.ends_with("fish") {
|
||||
FLOG!(
|
||||
config,
|
||||
"'fish' not in a 'bin/', trying paths relative to source tree"
|
||||
);
|
||||
let base_path = exec_path.parent().unwrap();
|
||||
#[cfg(feature = "installable")]
|
||||
let data = base_path.join("share/install");
|
||||
#[cfg(not(feature = "installable"))]
|
||||
let data = base_path.join("share");
|
||||
let locale = Some(data.join("locale"));
|
||||
|
||||
paths = ConfigPaths {
|
||||
data,
|
||||
sysconf: base_path.join("etc"),
|
||||
doc: base_path.join("user_doc/html"),
|
||||
bin: Some(base_path.to_path_buf()),
|
||||
locale,
|
||||
}
|
||||
}
|
||||
|
||||
if paths.data.exists() && paths.sysconf.exists() {
|
||||
// The docs dir may not exist; in that case fall back to the compiled in path.
|
||||
if !paths.doc.exists() {
|
||||
paths.doc = PathBuf::from(DOC_DIR);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// Fall back to what got compiled in.
|
||||
let data = if cfg!(feature = "installable") {
|
||||
let Some(home) = env::get_home() else {
|
||||
FLOG!(
|
||||
error,
|
||||
"Cannot find home directory and will refuse to read configuration.\n",
|
||||
"Consider installing into a directory tree with `fish --install=PATH`."
|
||||
);
|
||||
return paths;
|
||||
};
|
||||
|
||||
PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR)
|
||||
} else {
|
||||
Path::new(DATA_DIR).join(DATA_DIR_SUBDIR)
|
||||
};
|
||||
let bin = if cfg!(feature = "installable") {
|
||||
exec_path.parent().map(|x| x.to_path_buf())
|
||||
} else {
|
||||
Some(PathBuf::from(BIN_DIR))
|
||||
};
|
||||
let locale = if cfg!(feature = "installable") {
|
||||
None
|
||||
} else {
|
||||
Some(PathBuf::from(LOCALE_DIR))
|
||||
};
|
||||
|
||||
FLOG!(config, "Using compiled in paths:");
|
||||
paths = ConfigPaths {
|
||||
data,
|
||||
sysconf: Path::new(SYSCONF_DIR).join("fish"),
|
||||
doc: DOC_DIR.into(),
|
||||
bin,
|
||||
locale,
|
||||
}
|
||||
}
|
||||
|
||||
FLOGF!(
|
||||
config,
|
||||
"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \
|
||||
%ls\npaths.doc: %ls\npaths.bin: %ls\npaths.locale: %ls",
|
||||
paths.data.display().to_string(),
|
||||
paths.sysconf.display().to_string(),
|
||||
paths.doc.display().to_string(),
|
||||
paths
|
||||
.bin
|
||||
.clone()
|
||||
.map(|x| x.display().to_string())
|
||||
.unwrap_or("|not found|".to_string()),
|
||||
paths
|
||||
.locale
|
||||
.clone()
|
||||
.map(|x| x.display().to_string())
|
||||
.unwrap_or("|not found|".to_string()),
|
||||
);
|
||||
|
||||
paths
|
||||
}
|
||||
2
src/env/mod.rs
vendored
2
src/env/mod.rs
vendored
@@ -1,6 +1,8 @@
|
||||
mod config_paths;
|
||||
pub mod environment;
|
||||
mod environment_impl;
|
||||
pub mod var;
|
||||
pub use config_paths::CONFIG_PATHS;
|
||||
|
||||
use crate::common::ToCString;
|
||||
pub use environment::*;
|
||||
|
||||
9
src/env/var.rs
vendored
9
src/env/var.rs
vendored
@@ -50,10 +50,11 @@ fn from(val: EnvMode) -> Self {
|
||||
/// env_init.
|
||||
#[derive(Default)]
|
||||
pub struct ConfigPaths {
|
||||
pub data: PathBuf, // e.g., /usr/local/share
|
||||
pub sysconf: PathBuf, // e.g., /usr/local/etc
|
||||
pub doc: PathBuf, // e.g., /usr/local/share/doc/fish
|
||||
pub bin: Option<PathBuf>, // e.g., /usr/local/bin
|
||||
pub data: PathBuf, // e.g., /usr/local/share
|
||||
pub sysconf: PathBuf, // e.g., /usr/local/etc
|
||||
pub doc: PathBuf, // e.g., /usr/local/share/doc/fish
|
||||
pub bin: Option<PathBuf>, // e.g., /usr/local/bin
|
||||
pub locale: Option<PathBuf>, // e.g., /usr/local/share/locale
|
||||
}
|
||||
|
||||
/// A collection of status and pipestatus.
|
||||
|
||||
@@ -801,7 +801,7 @@ fn save(&mut self, directory: &wstr) -> bool {
|
||||
{
|
||||
let mut times: [libc::timespec; 2] = unsafe { std::mem::zeroed() };
|
||||
times[0].tv_nsec = libc::UTIME_OMIT; // don't change ctime
|
||||
if unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut times[1]) } != 0 {
|
||||
if unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut times[1]) } == 0 {
|
||||
unsafe {
|
||||
libc::futimens(private_fd.as_raw_fd(), ×[0]);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
print_exit_warning_for_jobs, InternalProc, Job, JobGroupRef, Pid, ProcStatus, Process,
|
||||
ProcessType, TtyTransfer,
|
||||
};
|
||||
use crate::reader::{reader_run_count, restore_term_mode};
|
||||
use crate::reader::{reader_run_count, safe_restore_term_mode};
|
||||
use crate::redirection::{dup2_list_resolve_chain, Dup2List};
|
||||
use crate::threads::{iothread_perform_cant_wait, is_forked_child};
|
||||
use crate::trace::trace_if_enabled_with_args;
|
||||
@@ -437,7 +437,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
||||
let actual_cmd = wcs2zstring(&p.actual_cmd);
|
||||
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
restore_term_mode(false);
|
||||
safe_restore_term_mode(false);
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &*envp);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn all_categories() -> Vec<&'static category_t> {
|
||||
(char_encoding, "char-encoding", "Character encoding issues");
|
||||
|
||||
(history, "history", "Command history events");
|
||||
(history_file, "history-file", "Reading/Writing the history file", true);
|
||||
(history_file, "history-file", "Reading/Writing the history file");
|
||||
|
||||
(profile_history, "profile-history", "History performance measurements");
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ pub enum FeatureFlag {
|
||||
|
||||
/// Whether keyboard protocols (kitty's CSI x u, xterm's modifyOtherKeys) are used
|
||||
keyboard_protocols,
|
||||
|
||||
/// Whether to write OSC 133 prompt markers
|
||||
mark_prompt,
|
||||
}
|
||||
|
||||
struct Features {
|
||||
@@ -118,6 +121,14 @@ pub struct FeatureMetadata {
|
||||
default_value: true,
|
||||
read_only: false,
|
||||
},
|
||||
FeatureMetadata {
|
||||
flag: FeatureFlag::mark_prompt,
|
||||
name: L!("mark-prompt"),
|
||||
groups: L!("4.0"),
|
||||
description: L!("Write OSC 133 prompt markers to the terminal"),
|
||||
default_value: true,
|
||||
read_only: false,
|
||||
},
|
||||
];
|
||||
|
||||
thread_local!(
|
||||
@@ -180,6 +191,7 @@ const fn new() -> Self {
|
||||
AtomicBool::new(METADATA[4].default_value),
|
||||
AtomicBool::new(METADATA[5].default_value),
|
||||
AtomicBool::new(METADATA[6].default_value),
|
||||
AtomicBool::new(METADATA[7].default_value),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use libc::{fchmod, fchown, flock, LOCK_EX, LOCK_SH, LOCK_UN};
|
||||
use libc::{fchmod, fchown, flock, EINTR, LOCK_EX, LOCK_SH, LOCK_UN};
|
||||
use lru::LruCache;
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use rand::Rng;
|
||||
@@ -1353,8 +1353,15 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
let start_time = SystemTime::now();
|
||||
let retval = unsafe { flock(raw_fd, lock_type) };
|
||||
let (ok, start_time) = loop {
|
||||
let start_time = SystemTime::now();
|
||||
if unsafe { flock(raw_fd, lock_type) } != -1 {
|
||||
break (true, start_time);
|
||||
}
|
||||
if errno::errno().0 != EINTR {
|
||||
break (false, start_time);
|
||||
}
|
||||
};
|
||||
if let Ok(duration) = start_time.elapsed() {
|
||||
if duration > Duration::from_millis(250) {
|
||||
FLOG!(
|
||||
@@ -1367,7 +1374,7 @@ unsafe fn maybe_lock_file(file: &mut File, lock_type: libc::c_int) -> bool {
|
||||
ABANDONED_LOCKING.store(true);
|
||||
}
|
||||
}
|
||||
retval != -1
|
||||
ok
|
||||
}
|
||||
|
||||
/// Unlock a history file.
|
||||
|
||||
124
src/input.rs
124
src/input.rs
@@ -3,8 +3,11 @@
|
||||
use crate::env::{Environment, CURSES_INITIALIZED};
|
||||
use crate::event;
|
||||
use crate::flog::FLOG;
|
||||
#[allow(unused_imports)]
|
||||
use crate::future::IsSomeAnd;
|
||||
use crate::input_common::{
|
||||
CharEvent, CharInputStyle, InputData, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
match_key_event_to_key, CharEvent, CharInputStyle, InputData, InputEventQueuer, KeyEvent,
|
||||
KeyMatchQuality, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
};
|
||||
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
|
||||
use crate::proc::job_reap;
|
||||
@@ -16,6 +19,7 @@
|
||||
use crate::wchar::prelude::*;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use std::ffi::CString;
|
||||
use std::mem;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex, MutexGuard,
|
||||
@@ -429,7 +433,7 @@ fn select_interrupted(&mut self) {
|
||||
if reader_reading_interrupted(self) != 0 {
|
||||
let vintr = shell_modes().c_cc[libc::VINTR];
|
||||
if vintr != 0 {
|
||||
self.push_front(CharEvent::from_key(Key::from_single_byte(vintr)));
|
||||
self.push_front(CharEvent::from_key(KeyEvent::from_single_byte(vintr)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -509,14 +513,19 @@ fn next(&mut self) -> CharEvent {
|
||||
|
||||
/// Check if the next event is the given character. This advances the index on success only.
|
||||
/// If `escaped` is set, then return false if this (or any other) character had a timeout.
|
||||
fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> bool {
|
||||
fn next_is_char(
|
||||
&mut self,
|
||||
style: &KeyNameStyle,
|
||||
key: Key,
|
||||
escaped: bool,
|
||||
) -> Option<KeyMatchQuality> {
|
||||
assert!(
|
||||
self.idx <= self.peeked.len(),
|
||||
"Index must not be larger than dequeued event count"
|
||||
);
|
||||
// See if we had a timeout already.
|
||||
if escaped && self.had_timeout {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
// Grab a new event if we have exhausted what we have already peeked.
|
||||
// Use either readch or readch_timed, per our param.
|
||||
@@ -527,7 +536,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
Some(evt) => evt,
|
||||
None => {
|
||||
self.had_timeout = true;
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -536,7 +545,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
Some(evt) => evt,
|
||||
None => {
|
||||
self.had_timeout = true;
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -546,9 +555,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
// Now we have peeked far enough; check the event.
|
||||
// If it matches the char, then increment the index.
|
||||
let evt = &self.peeked[self.idx];
|
||||
let Some(kevt) = evt.get_key() else {
|
||||
return false;
|
||||
};
|
||||
let kevt = evt.get_key()?;
|
||||
if kevt.seq == L!("\x1b") && key.modifiers == Modifiers::ALT {
|
||||
self.idx += 1;
|
||||
self.subidx = 0;
|
||||
@@ -556,13 +563,13 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
|
||||
}
|
||||
if *style == KeyNameStyle::Plain {
|
||||
if kevt.key == key {
|
||||
let result = match_key_event_to_key(&kevt.key, &key);
|
||||
if let Some(key_match) = &result {
|
||||
assert!(self.subidx == 0);
|
||||
self.idx += 1;
|
||||
FLOG!(reader, "matched full key", key);
|
||||
return true;
|
||||
FLOG!(reader, "matched full key", key, "kind", key_match);
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
let actual_seq = kevt.seq.as_char_slice();
|
||||
if !actual_seq.is_empty() {
|
||||
@@ -582,7 +589,7 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
actual_seq.len()
|
||||
)
|
||||
);
|
||||
return true;
|
||||
return Some(KeyMatchQuality::Exact);
|
||||
}
|
||||
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
|
||||
if self.subidx + 1 == actual_seq.len() {
|
||||
@@ -602,11 +609,11 @@ fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> boo
|
||||
self.subidx = 0;
|
||||
}
|
||||
FLOG!(reader, format!("matched {key} against raw escape sequence"));
|
||||
return true;
|
||||
return Some(KeyMatchQuality::Exact);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
/// Consume all events up to the current index.
|
||||
@@ -633,7 +640,12 @@ pub fn restart(&mut self) {
|
||||
}
|
||||
|
||||
/// Return true if this `peeker` matches a given sequence of char events given by `str`.
|
||||
fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
fn try_peek_sequence(
|
||||
&mut self,
|
||||
style: &KeyNameStyle,
|
||||
seq: &[Key],
|
||||
quality: &mut Vec<KeyMatchQuality>,
|
||||
) -> bool {
|
||||
assert!(
|
||||
!seq.is_empty(),
|
||||
"Empty sequence passed to try_peek_sequence"
|
||||
@@ -643,9 +655,10 @@ fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
// If we just read an escape, we need to add a timeout for the next char,
|
||||
// to distinguish between the actual escape key and an "alt"-modifier.
|
||||
let escaped = *style != KeyNameStyle::Plain && prev == Key::from_raw(key::Escape);
|
||||
if !self.next_is_char(style, *key, escaped) {
|
||||
let Some(spec) = self.next_is_char(style, *key, escaped) else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
quality.push(spec);
|
||||
prev = *key;
|
||||
}
|
||||
if self.subidx != 0 {
|
||||
@@ -662,16 +675,24 @@ fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
/// user's mapping list, then the preset list.
|
||||
/// Return none if nothing matches, or if we may have matched a longer sequence but it was
|
||||
/// interrupted by a readline event.
|
||||
pub fn find_mapping(
|
||||
pub fn find_mapping<'a>(
|
||||
&mut self,
|
||||
vars: &dyn Environment,
|
||||
ip: &InputMappingSet,
|
||||
ip: &'a InputMappingSet,
|
||||
) -> Option<InputMapping> {
|
||||
let mut generic: Option<&InputMapping> = None;
|
||||
let bind_mode = input_get_bind_mode(vars);
|
||||
let mut escape: Option<&InputMapping> = None;
|
||||
|
||||
struct MatchedMapping<'a> {
|
||||
mapping: &'a InputMapping,
|
||||
quality: Vec<KeyMatchQuality>,
|
||||
idx: usize,
|
||||
subidx: usize,
|
||||
}
|
||||
|
||||
let mut deferred: Option<MatchedMapping<'a>> = None;
|
||||
|
||||
let ml = ip.mapping_list.iter().chain(ip.preset_mapping_list.iter());
|
||||
let mut quality = vec![];
|
||||
for m in ml {
|
||||
if m.mode != bind_mode {
|
||||
continue;
|
||||
@@ -679,24 +700,41 @@ pub fn find_mapping(
|
||||
|
||||
// Defer generic mappings until the end.
|
||||
if m.is_generic() {
|
||||
if generic.is_none() {
|
||||
generic = Some(m);
|
||||
if deferred.is_none() {
|
||||
deferred = Some(MatchedMapping {
|
||||
mapping: m,
|
||||
quality: vec![],
|
||||
idx: self.idx,
|
||||
subidx: self.subidx,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// FLOG!(reader, "trying mapping", format!("{:?}", m));
|
||||
if self.try_peek_sequence(&m.key_name_style, &m.seq) {
|
||||
// A binding for just escape should also be deferred
|
||||
// so escape sequences take precedence.
|
||||
if m.seq == vec![Key::from_raw(key::Escape)] {
|
||||
if escape.is_none() {
|
||||
escape = Some(m);
|
||||
}
|
||||
} else {
|
||||
if self.try_peek_sequence(&m.key_name_style, &m.seq, &mut quality) {
|
||||
// // A binding for just escape should also be deferred
|
||||
// // so escape sequences take precedence.
|
||||
let is_escape = m.seq == vec![Key::from_raw(key::Escape)];
|
||||
let is_perfect_match = quality
|
||||
.iter()
|
||||
.all(|key_match| *key_match == KeyMatchQuality::Exact);
|
||||
if !is_escape && is_perfect_match {
|
||||
return Some(m.clone());
|
||||
}
|
||||
if deferred
|
||||
.as_ref()
|
||||
.is_none_or(|matched| !is_escape && quality >= matched.quality)
|
||||
{
|
||||
deferred = Some(MatchedMapping {
|
||||
mapping: m,
|
||||
quality: mem::take(&mut quality),
|
||||
idx: self.idx,
|
||||
subidx: self.subidx,
|
||||
});
|
||||
}
|
||||
}
|
||||
quality.clear();
|
||||
self.restart();
|
||||
}
|
||||
if self.char_sequence_interrupted() {
|
||||
@@ -705,17 +743,13 @@ pub fn find_mapping(
|
||||
return None;
|
||||
}
|
||||
|
||||
if escape.is_some() {
|
||||
// We need to reconsume the escape.
|
||||
self.next();
|
||||
return escape.cloned();
|
||||
}
|
||||
|
||||
if generic.is_some() {
|
||||
generic.cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
deferred
|
||||
.map(|matched| {
|
||||
self.idx = matched.idx;
|
||||
self.subidx = matched.subidx;
|
||||
matched.mapping
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,7 +875,7 @@ fn read_characters_no_readline(&mut self) -> CharEvent {
|
||||
let evt_to_return: CharEvent;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if evt.is_readline_or_command() {
|
||||
if evt.is_readline_or_command() || evt.is_check_exit() {
|
||||
saved_events.push(evt);
|
||||
} else {
|
||||
evt_to_return = evt;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
125
src/key.rs
125
src/key.rs
@@ -3,9 +3,9 @@
|
||||
use crate::{
|
||||
common::{escape_string, EscapeFlags, EscapeStringStyle},
|
||||
fallback::fish_wcwidth,
|
||||
reader::TERMINAL_MODE_ON_STARTUP,
|
||||
reader::safe_get_terminal_mode_on_startup,
|
||||
wchar::{decode_byte_from_char, prelude::*},
|
||||
wutil::{fish_is_pua, fish_wcstoi},
|
||||
wutil::fish_wcstoul,
|
||||
};
|
||||
|
||||
pub(crate) const Backspace: char = '\u{F500}'; // below ENCODE_DIRECT_BASE
|
||||
@@ -18,16 +18,19 @@
|
||||
pub(crate) const Right: char = '\u{F507}';
|
||||
pub(crate) const PageUp: char = '\u{F508}';
|
||||
pub(crate) const PageDown: char = '\u{F509}';
|
||||
pub(crate) const Home: char = '\u{F50a}';
|
||||
pub(crate) const End: char = '\u{F50b}';
|
||||
pub(crate) const Insert: char = '\u{F50c}';
|
||||
pub(crate) const Tab: char = '\u{F50d}';
|
||||
pub(crate) const Space: char = '\u{F50e}';
|
||||
pub const Invalid: char = '\u{F50f}';
|
||||
pub(crate) const Home: char = '\u{F50A}';
|
||||
pub(crate) const End: char = '\u{F50B}';
|
||||
pub(crate) const Insert: char = '\u{F50C}';
|
||||
pub(crate) const Tab: char = '\u{F50D}';
|
||||
pub(crate) const Space: char = '\u{F50E}';
|
||||
pub(crate) const Menu: char = '\u{F50F}';
|
||||
pub(crate) const PrintScreen: char = '\u{F510}';
|
||||
pub(crate) const MAX_FUNCTION_KEY: u32 = 12;
|
||||
pub(crate) fn function_key(n: u32) -> char {
|
||||
assert!((1..=12).contains(&n));
|
||||
char::from_u32(u32::from(Invalid) + n).unwrap()
|
||||
assert!((1..=MAX_FUNCTION_KEY).contains(&n));
|
||||
char::from_u32(u32::from('\u{F5FF}') - MAX_FUNCTION_KEY + (n - 1)).unwrap()
|
||||
}
|
||||
pub(crate) const Invalid: char = '\u{F5FF}';
|
||||
|
||||
const KEY_NAMES: &[(char, &wstr)] = &[
|
||||
('-', L!("minus")),
|
||||
@@ -47,6 +50,8 @@ pub(crate) fn function_key(n: u32) -> char {
|
||||
(Insert, L!("insert")),
|
||||
(Tab, L!("tab")),
|
||||
(Space, L!("space")),
|
||||
(Menu, L!("menu")),
|
||||
(PrintScreen, L!("printscreen")),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
@@ -66,11 +71,22 @@ const fn new() -> Self {
|
||||
sup: false,
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(crate) const CTRL: Self = {
|
||||
let mut m = Self::new();
|
||||
m.ctrl = true;
|
||||
m
|
||||
};
|
||||
pub(crate) const ALT: Self = {
|
||||
let mut m = Self::new();
|
||||
m.alt = true;
|
||||
m
|
||||
};
|
||||
pub const SHIFT: Self = {
|
||||
let mut m = Self::new();
|
||||
m.shift = true;
|
||||
m
|
||||
};
|
||||
pub(crate) fn is_some(&self) -> bool {
|
||||
*self != Self::new()
|
||||
}
|
||||
@@ -86,39 +102,33 @@ pub struct Key {
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub(crate) fn from_raw(codepoint: char) -> Self {
|
||||
pub(crate) const fn new(modifiers: Modifiers, codepoint: char) -> Self {
|
||||
Self {
|
||||
modifiers: Modifiers::default(),
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
}
|
||||
pub(crate) fn from_raw(codepoint: char) -> Self {
|
||||
Self::new(Modifiers::default(), codepoint)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn ctrl(codepoint: char) -> Key {
|
||||
let mut modifiers = Modifiers::new();
|
||||
modifiers.ctrl = true;
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
Key::new(modifiers, codepoint)
|
||||
}
|
||||
|
||||
pub(crate) const fn alt(codepoint: char) -> Key {
|
||||
let mut modifiers = Modifiers::new();
|
||||
modifiers.alt = true;
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
Key::new(modifiers, codepoint)
|
||||
}
|
||||
|
||||
pub(crate) const fn shift(codepoint: char) -> Key {
|
||||
let mut modifiers = Modifiers::new();
|
||||
modifiers.shift = true;
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
Key::new(modifiers, codepoint)
|
||||
}
|
||||
|
||||
impl Key {
|
||||
@@ -135,10 +145,7 @@ pub fn from_single_byte(c: u8) -> Self {
|
||||
pub fn canonicalize_control_char(c: u8) -> Option<Key> {
|
||||
let codepoint = canonicalize_keyed_control_char(char::from(c));
|
||||
if u32::from(codepoint) > 255 {
|
||||
return Some(Key {
|
||||
modifiers: Modifiers::default(),
|
||||
codepoint,
|
||||
});
|
||||
return Some(Key::from_raw(codepoint));
|
||||
}
|
||||
|
||||
if c < 32 {
|
||||
@@ -162,8 +169,10 @@ pub(crate) fn canonicalize_keyed_control_char(c: char) -> char {
|
||||
if c == ' ' {
|
||||
return Space;
|
||||
}
|
||||
if c == char::from(TERMINAL_MODE_ON_STARTUP.lock().unwrap().c_cc[VERASE]) {
|
||||
return Backspace;
|
||||
if let Some(tm) = safe_get_terminal_mode_on_startup() {
|
||||
if c == char::from(tm.c_cc[VERASE]) {
|
||||
return Backspace;
|
||||
}
|
||||
}
|
||||
if c == char::from(127) {
|
||||
// when it's not backspace
|
||||
@@ -207,19 +216,6 @@ pub(crate) fn canonicalize_key(mut key: Key) -> Result<Key, WString> {
|
||||
key.modifiers.ctrl = true;
|
||||
}
|
||||
}
|
||||
if key.modifiers.shift {
|
||||
if key.codepoint.is_ascii_alphabetic() {
|
||||
// Shift + ASCII letters is just the uppercase letter.
|
||||
key.modifiers.shift = false;
|
||||
key.codepoint = key.codepoint.to_ascii_uppercase();
|
||||
} else if !fish_is_pua(key.codepoint) {
|
||||
// Shift + any other printable character is not allowed.
|
||||
return Err(wgettext_fmt!(
|
||||
"Shift modifier is only supported on special keys and lowercase ASCII, not '%s'",
|
||||
key,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
@@ -289,25 +285,22 @@ pub(crate) fn parse_keys(value: &wstr) -> Result<Vec<Key>, WString> {
|
||||
.find_map(|(codepoint, name)| (name == key_name).then_some(*codepoint))
|
||||
.or_else(|| (key_name.len() == 1).then(|| key_name.as_char_slice()[0]));
|
||||
let key = if let Some(codepoint) = codepoint {
|
||||
canonicalize_key(Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
})?
|
||||
canonicalize_key(Key::new(modifiers, codepoint))?
|
||||
} else if codepoint.is_none() && key_name.starts_with('f') && key_name.len() <= 3 {
|
||||
let num = key_name.strip_prefix('f').unwrap();
|
||||
let codepoint = match fish_wcstoi(num) {
|
||||
Ok(n) if (1..=12).contains(&n) => function_key(u32::try_from(n).unwrap()),
|
||||
let codepoint = match fish_wcstoul(num) {
|
||||
Ok(n) if (1..=u64::from(MAX_FUNCTION_KEY)).contains(&n) => {
|
||||
function_key(u32::try_from(n).unwrap())
|
||||
}
|
||||
_ => {
|
||||
return Err(wgettext_fmt!(
|
||||
"only f1 through f12 are supported, not 'f%s'",
|
||||
"only f1 through f%d are supported, not 'f%s'",
|
||||
MAX_FUNCTION_KEY,
|
||||
num,
|
||||
));
|
||||
}
|
||||
};
|
||||
Key {
|
||||
modifiers,
|
||||
codepoint,
|
||||
}
|
||||
Key::new(modifiers, codepoint)
|
||||
} else {
|
||||
return Err(wgettext_fmt!(
|
||||
"cannot parse key '%s'",
|
||||
@@ -350,28 +343,6 @@ pub(crate) fn canonicalize_raw_escapes(keys: Vec<Key>) -> Vec<Key> {
|
||||
canonical
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub(crate) fn codepoint_text(&self) -> Option<char> {
|
||||
if self.modifiers.is_some() {
|
||||
return None;
|
||||
}
|
||||
let c = self.codepoint;
|
||||
if c == Space {
|
||||
return Some(' ');
|
||||
}
|
||||
if c == Enter {
|
||||
return Some('\n');
|
||||
}
|
||||
if c == Tab {
|
||||
return Some('\t');
|
||||
}
|
||||
if fish_is_pua(c) || u32::from(c) <= 27 {
|
||||
return None;
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
WString::from(*self).fmt(f)
|
||||
@@ -390,7 +361,7 @@ fn from(key: Key) -> Self {
|
||||
.iter()
|
||||
.find_map(|&(codepoint, name)| (codepoint == key.codepoint).then(|| name.to_owned()))
|
||||
.or_else(|| {
|
||||
(function_key(1)..=function_key(12))
|
||||
(function_key(1)..=function_key(MAX_FUNCTION_KEY))
|
||||
.contains(&key.codepoint)
|
||||
.then(|| {
|
||||
sprintf!(
|
||||
|
||||
10
src/libc.c
10
src/libc.c
@@ -1,13 +1,13 @@
|
||||
#include <locale.h>
|
||||
#include <paths.h>
|
||||
#include <paths.h> // _PATH_BSHELL
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdlib.h> // MB_CUR_MAX
|
||||
#include <sys/mount.h> // MNT_LOCAL
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
#include <sys/statvfs.h> // ST_LOCAL
|
||||
#include <unistd.h> // _CS_PATH, _PC_CASE_SENSITIVE
|
||||
|
||||
size_t C_MB_CUR_MAX() { return MB_CUR_MAX; }
|
||||
|
||||
|
||||
@@ -218,17 +218,30 @@ fn parse_util_locate_cmdsub(
|
||||
let mut last_dollar = None;
|
||||
let mut paran_begin = None;
|
||||
let mut paran_end = None;
|
||||
enum Quote {
|
||||
Real(char),
|
||||
VirtualDouble,
|
||||
}
|
||||
fn process_opening_quote(
|
||||
input: &[char],
|
||||
inout_is_quoted: &mut Option<&mut bool>,
|
||||
paran_count: i32,
|
||||
quoted_cmdsubs: &mut Vec<i32>,
|
||||
pos: usize,
|
||||
mut pos: usize,
|
||||
last_dollar: &mut Option<usize>,
|
||||
quote: char,
|
||||
quote: Quote,
|
||||
) -> Option<usize> {
|
||||
let quote = match quote {
|
||||
Quote::Real(q) => q,
|
||||
Quote::VirtualDouble => {
|
||||
pos = pos.saturating_sub(1);
|
||||
'"'
|
||||
}
|
||||
};
|
||||
let q_end = quote_end(input.into(), pos, quote)?;
|
||||
// Found a valid closing quote.
|
||||
if input[q_end] == '$' {
|
||||
// The closing quote is another quoted command substitution.
|
||||
*last_dollar = Some(q_end);
|
||||
quoted_cmdsubs.push(paran_count);
|
||||
}
|
||||
@@ -254,9 +267,9 @@ fn process_opening_quote(
|
||||
&mut quoted_cmdsubs,
|
||||
pos,
|
||||
&mut last_dollar,
|
||||
'"',
|
||||
Quote::VirtualDouble,
|
||||
)
|
||||
.unwrap_or(input.len());
|
||||
.map_or(input.len(), |pos| pos + 1);
|
||||
}
|
||||
|
||||
while pos < input.len() {
|
||||
@@ -270,7 +283,7 @@ fn process_opening_quote(
|
||||
&mut quoted_cmdsubs,
|
||||
pos,
|
||||
&mut last_dollar,
|
||||
c,
|
||||
Quote::Real(c),
|
||||
) {
|
||||
Some(q_end) => pos = q_end,
|
||||
None => break,
|
||||
@@ -307,21 +320,19 @@ fn process_opening_quote(
|
||||
if quoted_cmdsubs.last() == Some(¶n_count) {
|
||||
quoted_cmdsubs.pop();
|
||||
// Quoted command substitutions temporarily close double quotes.
|
||||
// In "foo$(bar)baz$(qux)"
|
||||
// We are here ^
|
||||
// After the ) in a quoted command substitution, we need to act as if
|
||||
// there was an invisible double quote.
|
||||
match quote_end(input.into(), pos, '"') {
|
||||
Some(q_end) => {
|
||||
// Found a valid closing quote.
|
||||
// Stop at $(qux), which is another quoted command substitution.
|
||||
if input[q_end] == '$' {
|
||||
quoted_cmdsubs.push(paran_count);
|
||||
}
|
||||
pos = q_end;
|
||||
}
|
||||
// In "foo$(bar)baz$(qux)", after the ), we need to act as if there was a double quote.
|
||||
match process_opening_quote(
|
||||
input,
|
||||
&mut inout_is_quoted,
|
||||
paran_count,
|
||||
&mut quoted_cmdsubs,
|
||||
pos,
|
||||
&mut last_dollar,
|
||||
Quote::VirtualDouble,
|
||||
) {
|
||||
Some(q_end) => pos = q_end,
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
is_token_begin = is_token_delimiter(c, input.get(pos + 1).copied());
|
||||
|
||||
@@ -614,7 +614,7 @@ pub fn eval_node<T: Node>(
|
||||
let mut execution_context =
|
||||
ExecutionContext::new(ps.clone(), block_io.clone(), Rc::clone(&line_counter));
|
||||
|
||||
terminal_protocols_disable_ifn();
|
||||
terminal_protocols_disable_ifn(false);
|
||||
|
||||
// Check the exec count so we know if anything got executed.
|
||||
let prev_exec_count = self.libdata().exec_count;
|
||||
|
||||
86
src/path.rs
86
src/path.rs
@@ -2,12 +2,10 @@
|
||||
//! for testing if a command with a given name can be found in the PATH, and various other
|
||||
//! path-related issues.
|
||||
|
||||
use crate::common::{is_windows_subsystem_for_linux as is_wsl, wcs2osstring, wcs2zstring, WSL};
|
||||
use crate::common::{wcs2osstring, wcs2zstring};
|
||||
use crate::env::{EnvMode, EnvStack, Environment};
|
||||
use crate::expand::{expand_tilde, HOME_DIRECTORY};
|
||||
use crate::flog::{FLOG, FLOGF};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use crate::libc::{MNT_LOCAL, ST_LOCAL};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wstat};
|
||||
use errno::{errno, set_errno, Errno};
|
||||
@@ -15,6 +13,7 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::ErrorKind;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::unix::prelude::*;
|
||||
|
||||
/// Returns the user configuration directory for fish. If the directory or one of its parents
|
||||
@@ -308,29 +307,6 @@ fn path_get_path_core<S: AsRef<wstr>>(cmd: &wstr, pathsv: &[S]) -> GetPathResult
|
||||
return GetPathResult::new(test_path(cmd).err(), cmd.to_owned());
|
||||
}
|
||||
|
||||
// WSLv1/WSLv2 tack on the entire Windows PATH to the end of the PATH environment variable, and
|
||||
// accessing these paths from WSL binaries is pathalogically slow. We also don't expect to find
|
||||
// any "normal" nix binaries under these paths, so we can skip them unless we are executing bins
|
||||
// with Windows-ish names. We try to keep paths manually added to $fish_user_paths by only
|
||||
// chopping off entries after the last "normal" PATH entry.
|
||||
let pathsv = if is_wsl(WSL::Any) && !cmd.contains('.') {
|
||||
let win_path_count = pathsv
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|p| {
|
||||
let p = p.as_ref();
|
||||
p.starts_with("/mnt/")
|
||||
&& p.chars()
|
||||
.nth("/mnt/x".len())
|
||||
.map(|c| c == '/')
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.count();
|
||||
&pathsv[..pathsv.len() - win_path_count]
|
||||
} else {
|
||||
pathsv
|
||||
};
|
||||
|
||||
let mut best = noent_res;
|
||||
for next_path in pathsv {
|
||||
let next_path: &wstr = next_path.as_ref();
|
||||
@@ -699,10 +675,11 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
|
||||
let narrow = wcs2zstring(path);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statfs(narrow.as_ptr(), &mut buf) } < 0 {
|
||||
let mut buf = MaybeUninit::uninit();
|
||||
if unsafe { libc::statfs(narrow.as_ptr(), buf.as_mut_ptr()) } < 0 {
|
||||
return DirRemoteness::unknown;
|
||||
}
|
||||
let buf = unsafe { buf.assume_init() };
|
||||
// Linux has constants for these like NFS_SUPER_MAGIC, SMB_SUPER_MAGIC, CIFS_MAGIC_NUMBER but
|
||||
// these are in varying headers. Simply hard code them.
|
||||
// Note that we treat FUSE filesystems as remote, which means we lock less on such filesystems.
|
||||
@@ -732,38 +709,49 @@ fn path_remoteness(path: &wstr) -> DirRemoteness {
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let st_local = ST_LOCAL();
|
||||
if st_local != 0 {
|
||||
// ST_LOCAL is a flag to statvfs, which is itself standardized.
|
||||
// In practice the only system to use this path is NetBSD.
|
||||
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
|
||||
fn remoteness_via_statfs<StatFS, Flags>(
|
||||
statfn: unsafe extern "C" fn(*const libc::c_char, *mut StatFS) -> libc::c_int,
|
||||
flagsfn: fn(&StatFS) -> Flags,
|
||||
is_local_flag: u64,
|
||||
path: &std::ffi::CStr,
|
||||
) -> DirRemoteness
|
||||
where
|
||||
u64: From<Flags>,
|
||||
{
|
||||
if is_local_flag == 0 {
|
||||
return DirRemoteness::unknown;
|
||||
}
|
||||
// statvfs::f_flag is `unsigned long`, which is 4-bytes on most 32-bit targets.
|
||||
#[cfg_attr(target_pointer_width = "64", allow(clippy::useless_conversion))]
|
||||
return if u64::from(buf.f_flag) & st_local != 0 {
|
||||
DirRemoteness::local
|
||||
} else {
|
||||
DirRemoteness::remote
|
||||
};
|
||||
}
|
||||
let mnt_local = MNT_LOCAL();
|
||||
if mnt_local != 0 {
|
||||
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statvfs(narrow.as_ptr(), &mut buf) } < 0 {
|
||||
let mut buf = MaybeUninit::uninit();
|
||||
if unsafe { (statfn)(path.as_ptr(), buf.as_mut_ptr()) } < 0 {
|
||||
return DirRemoteness::unknown;
|
||||
}
|
||||
let buf = unsafe { buf.assume_init() };
|
||||
// statfs::f_flag is hard-coded as 64-bits on 32/64-bit FreeBSD but it's a (4-byte)
|
||||
// long on 32-bit NetBSD.. and always 4-bytes on macOS (even on 64-bit builds).
|
||||
#[allow(clippy::useless_conversion)]
|
||||
return if u64::from(buf.f_flag) & mnt_local != 0 {
|
||||
if u64::from((flagsfn)(&buf)) & is_local_flag != 0 {
|
||||
DirRemoteness::local
|
||||
} else {
|
||||
DirRemoteness::remote
|
||||
};
|
||||
}
|
||||
}
|
||||
DirRemoteness::unknown
|
||||
// ST_LOCAL is a flag to statvfs, which is itself standardized.
|
||||
// In practice the only system to define it is NetBSD.
|
||||
#[cfg(target_os = "netbsd")]
|
||||
let remoteness = remoteness_via_statfs(
|
||||
libc::statvfs,
|
||||
|stat: &libc::statvfs| stat.f_flag,
|
||||
crate::libc::ST_LOCAL(),
|
||||
&narrow,
|
||||
);
|
||||
#[cfg(not(target_os = "netbsd"))]
|
||||
let remoteness = remoteness_via_statfs(
|
||||
libc::statfs,
|
||||
|stat: &libc::statfs| stat.f_flags,
|
||||
crate::libc::MNT_LOCAL(),
|
||||
&narrow,
|
||||
);
|
||||
remoteness
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
use std::rc::Rc;
|
||||
#[cfg(target_has_atomic = "64")]
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{AtomicI32, AtomicU32, AtomicU8};
|
||||
use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU32, AtomicU8, Ordering};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -67,6 +66,7 @@
|
||||
use crate::flog::{FLOG, FLOGF};
|
||||
#[allow(unused_imports)]
|
||||
use crate::future::IsSomeAnd;
|
||||
use crate::future_feature_flags::{feature_test, FeatureFlag};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::highlight::{
|
||||
autosuggest_validate_from_history, highlight_shell, HighlightRole, HighlightSpec,
|
||||
@@ -152,9 +152,9 @@ enum ExitState {
|
||||
pub static SHELL_MODES: Lazy<Mutex<libc::termios>> =
|
||||
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
||||
|
||||
/// Mode on startup, which we restore on exit.
|
||||
pub static TERMINAL_MODE_ON_STARTUP: Lazy<Mutex<libc::termios>> =
|
||||
Lazy::new(|| Mutex::new(unsafe { std::mem::zeroed() }));
|
||||
/// The valid terminal modes on startup. This is set once and not modified after.
|
||||
/// Warning: this is read from the SIGTERM handler! Hence the raw global.
|
||||
static TERMINAL_MODE_ON_STARTUP: AtomicPtr<libc::termios> = AtomicPtr::new(std::ptr::null_mut());
|
||||
|
||||
/// Mode we use to execute programs.
|
||||
static TTY_MODES_FOR_EXTERNAL_CMDS: Lazy<Mutex<libc::termios>> =
|
||||
@@ -171,6 +171,12 @@ enum ExitState {
|
||||
/// This is set from a signal handler.
|
||||
static SIGHUP_RECEIVED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
// Get the terminal mode on startup. This is "safe" because it's async-signal safe.
|
||||
pub fn safe_get_terminal_mode_on_startup() -> Option<&'static libc::termios> {
|
||||
// Safety: set atomically and not modified after.
|
||||
unsafe { TERMINAL_MODE_ON_STARTUP.load(Ordering::Acquire).as_ref() }
|
||||
}
|
||||
|
||||
/// A singleton snapshot of the reader state. This is factored out for thread-safety reasons:
|
||||
/// it may be fetched on a background thread.
|
||||
fn commandline_state_snapshot() -> MutexGuard<'static, CommandlineState> {
|
||||
@@ -659,13 +665,15 @@ fn read_i(parser: &Parser) -> i32 {
|
||||
data.command_line.clear();
|
||||
data.update_buff_pos(EditableLineTag::Commandline, None);
|
||||
data.command_line_changed(EditableLineTag::Commandline);
|
||||
// OSC 133 "Command start"
|
||||
write!(
|
||||
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
|
||||
"\x1b]133;C;cmdline_url={}\x07",
|
||||
escape_string(&command, EscapeStringStyle::Url),
|
||||
)
|
||||
.unwrap();
|
||||
if feature_test(FeatureFlag::mark_prompt) {
|
||||
// OSC 133 "Command start"
|
||||
write!(
|
||||
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
|
||||
"\x1b]133;C;cmdline_url={}\x07",
|
||||
escape_string(&command, EscapeStringStyle::Url),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
event::fire_generic(parser, L!("fish_preexec").to_owned(), vec![command.clone()]);
|
||||
let eval_res = reader_run_command(parser, &command);
|
||||
signal_clear_cancel();
|
||||
@@ -678,12 +686,14 @@ fn read_i(parser: &Parser) -> i32 {
|
||||
parser.libdata_mut().exit_current_script = false;
|
||||
|
||||
// OSC 133 "Command finished"
|
||||
write!(
|
||||
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
|
||||
"\x1b]133;D;{}\x07",
|
||||
parser.get_last_status()
|
||||
)
|
||||
.unwrap();
|
||||
if feature_test(FeatureFlag::mark_prompt) {
|
||||
write!(
|
||||
BufferedOuputter::new(&mut Outputter::stdoutput().borrow_mut()),
|
||||
"\x1b]133;D;{}\x07",
|
||||
parser.get_last_status()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
event::fire_generic(parser, L!("fish_postexec").to_owned(), vec![command]);
|
||||
// Allow any pending history items to be returned in the history array.
|
||||
data.history.resolve_pending();
|
||||
@@ -813,8 +823,15 @@ fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> i32 {
|
||||
/// Initialize the reader.
|
||||
pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
// Save the initial terminal mode.
|
||||
let mut terminal_mode_on_startup = TERMINAL_MODE_ON_STARTUP.lock().unwrap();
|
||||
unsafe { libc::tcgetattr(STDIN_FILENO, &mut *terminal_mode_on_startup) };
|
||||
// Note this field is read by a signal handler, so do it atomically, with a leaked mode.
|
||||
let mut terminal_mode_on_startup = unsafe { std::mem::zeroed::<libc::termios>() };
|
||||
let ret = unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut terminal_mode_on_startup) };
|
||||
// TODO: rationalize behavior if initial tcgetattr() fails.
|
||||
if ret == 0 {
|
||||
// Must be mut because AtomicPtr doesn't have const variant.
|
||||
let leaked: *mut libc::termios = Box::leak(Box::new(terminal_mode_on_startup));
|
||||
TERMINAL_MODE_ON_STARTUP.store(leaked, Ordering::Release);
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
assert!(AT_EXIT.get().is_none());
|
||||
@@ -826,7 +843,7 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
|
||||
// Set the mode used for program execution, initialized to the current mode.
|
||||
let mut tty_modes_for_external_cmds = TTY_MODES_FOR_EXTERNAL_CMDS.lock().unwrap();
|
||||
*tty_modes_for_external_cmds = *terminal_mode_on_startup;
|
||||
*tty_modes_for_external_cmds = terminal_mode_on_startup;
|
||||
term_fix_external_modes(&mut tty_modes_for_external_cmds);
|
||||
|
||||
// Disable flow control by default.
|
||||
@@ -838,7 +855,6 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
|
||||
term_fix_modes(&mut shell_modes());
|
||||
|
||||
drop(terminal_mode_on_startup);
|
||||
drop(tty_modes_for_external_cmds);
|
||||
|
||||
// Set up our fixed terminal modes once,
|
||||
@@ -850,9 +866,11 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pca): this is run in our "AT_EXIT" handler from a SIGTERM handler.
|
||||
// It must be made async-signal-safe (or not invoked).
|
||||
pub fn reader_deinit(in_signal_handler: bool, restore_foreground_pgroup: bool) {
|
||||
restore_term_mode(in_signal_handler);
|
||||
crate::input_common::terminal_protocols_disable_ifn();
|
||||
safe_restore_term_mode(in_signal_handler);
|
||||
crate::input_common::terminal_protocols_disable_ifn(in_signal_handler);
|
||||
if restore_foreground_pgroup {
|
||||
restore_term_foreground_process_group_for_exit();
|
||||
}
|
||||
@@ -861,20 +879,15 @@ pub fn reader_deinit(in_signal_handler: bool, restore_foreground_pgroup: bool) {
|
||||
/// Restore the term mode if we own the terminal and are interactive (#8705).
|
||||
/// It's important we do this before restore_foreground_process_group,
|
||||
/// otherwise we won't think we own the terminal.
|
||||
pub fn restore_term_mode(in_signal_handler: bool) {
|
||||
/// THIS FUNCTION IS CALLED FROM A SIGNAL HANDLER. IT MUST BE ASYNC-SIGNAL-SAFE.
|
||||
pub fn safe_restore_term_mode(in_signal_handler: bool) {
|
||||
if !is_interactive_session() || unsafe { libc::getpgrp() != libc::tcgetpgrp(STDIN_FILENO) } {
|
||||
return;
|
||||
}
|
||||
|
||||
if unsafe {
|
||||
libc::tcsetattr(
|
||||
STDIN_FILENO,
|
||||
TCSANOW,
|
||||
&*TERMINAL_MODE_ON_STARTUP.lock().unwrap(),
|
||||
) == -1
|
||||
} && errno().0 == EIO
|
||||
{
|
||||
redirect_tty_output(in_signal_handler);
|
||||
if let Some(modes) = safe_get_terminal_mode_on_startup() {
|
||||
if unsafe { libc::tcsetattr(STDIN_FILENO, TCSANOW, modes) == -1 } && errno().0 == EIO {
|
||||
redirect_tty_output(in_signal_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -980,7 +993,7 @@ pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) {
|
||||
data.rls = Some(ReadlineLoopState::new());
|
||||
}
|
||||
data.save_screen_state();
|
||||
data.handle_char_event(Some(ch));
|
||||
let _ = data.handle_char_event(Some(ch));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5812,7 +5825,7 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
||||
token_range.end += cmdsub_range.start;
|
||||
|
||||
// Wildcard expansion and completion below check for cancellation.
|
||||
terminal_protocols_disable_ifn();
|
||||
terminal_protocols_disable_ifn(false);
|
||||
|
||||
// Check if we have a wildcard within this string; if so we first attempt to expand the
|
||||
// wildcard; if that succeeds we don't then apply user completions (#8593).
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! The current implementation is less smart than ncurses allows and can not for example move blocks
|
||||
//! of text around to handle text insertion.
|
||||
|
||||
use crate::future_feature_flags::{feature_test, FeatureFlag};
|
||||
use crate::pager::{PageRendering, Pager, PAGER_MIN_HEIGHT};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::LinkedList;
|
||||
@@ -931,8 +932,11 @@ fn update(
|
||||
} else if left_prompt != zelf.actual_left_prompt || (zelf.scrolled && is_final_rendering) {
|
||||
zelf.r#move(0, 0);
|
||||
let mut start = 0;
|
||||
let osc_133_prompt_start =
|
||||
|zelf: &mut Screen| zelf.write_bytes(b"\x1b]133;A;special_key=1\x07");
|
||||
let osc_133_prompt_start = |zelf: &mut Screen| {
|
||||
if feature_test(FeatureFlag::mark_prompt) {
|
||||
zelf.write_bytes(b"\x1b]133;A;special_key=1\x07");
|
||||
}
|
||||
};
|
||||
if left_prompt_layout.line_breaks.is_empty() {
|
||||
osc_133_prompt_start(&mut zelf);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::env::EnvStack;
|
||||
use crate::input::{EventQueuePeeker, InputMappingSet, KeyNameStyle, DEFAULT_BIND_MODE};
|
||||
use crate::input_common::{CharEvent, InputData, InputEventQueuer};
|
||||
use crate::input_common::{CharEvent, InputData, InputEventQueuer, KeyEvent};
|
||||
use crate::key::Key;
|
||||
use crate::wchar::prelude::*;
|
||||
use std::rc::Rc;
|
||||
@@ -52,8 +52,10 @@ fn test_input() {
|
||||
);
|
||||
|
||||
// Push the desired binding to the queue.
|
||||
for c in desired_binding {
|
||||
input.input_data.queue_char(CharEvent::from_key(c));
|
||||
for key in desired_binding {
|
||||
input
|
||||
.input_data
|
||||
.queue_char(CharEvent::from_key(KeyEvent::from(key)));
|
||||
}
|
||||
|
||||
let mut peeker = EventQueuePeeker::new(&mut input);
|
||||
|
||||
@@ -439,5 +439,10 @@ macro_rules! validate {
|
||||
0, r#"echo "$()"'"#,
|
||||
0, "\n"
|
||||
);
|
||||
validate!(
|
||||
0, r#"""#,
|
||||
0, "\n",
|
||||
0, r#"$()"$() ""#
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::common::{charptr2wcstring, truncate_at_nul, wcs2zstring, PACKAGE_NAME};
|
||||
#[cfg(test)]
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::{
|
||||
common::{charptr2wcstring, truncate_at_nul, wcs2zstring, PACKAGE_NAME},
|
||||
env::CONFIG_PATHS,
|
||||
};
|
||||
use errno::{errno, set_errno};
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
|
||||
@@ -47,8 +51,11 @@ pub fn fish_textdomain(_domainname: &CStr) -> *mut c_char {
|
||||
|
||||
// Really init wgettext.
|
||||
fn wgettext_really_init() {
|
||||
let Some(ref localepath) = CONFIG_PATHS.locale else {
|
||||
return;
|
||||
};
|
||||
let package_name = CString::new(PACKAGE_NAME).unwrap();
|
||||
let localedir = CString::new(env!("LOCALEDIR")).unwrap();
|
||||
let localedir = CString::new(localepath.as_os_str().as_bytes()).unwrap();
|
||||
fish_bindtextdomain(&package_name, &localedir);
|
||||
fish_textdomain(&package_name);
|
||||
}
|
||||
|
||||
21
tests/checks/autoload.fish
Normal file
21
tests/checks/autoload.fish
Normal file
@@ -0,0 +1,21 @@
|
||||
#RUN: %fish %s
|
||||
|
||||
set -g fish_complete_path c1 c2
|
||||
mkdir -p c1 c2
|
||||
|
||||
function foo; end
|
||||
for i in c1 c2
|
||||
echo >$i/foo.fish "echo auto-loading $i/foo.fish"
|
||||
end
|
||||
complete -C "foo " >/dev/null
|
||||
# CHECK: auto-loading c1/foo.fish
|
||||
complete -C "foo " >/dev/null
|
||||
# already loaded
|
||||
|
||||
set -g fish_complete_path c2
|
||||
complete -C "foo " >/dev/null
|
||||
# CHECK: auto-loading c2/foo.fish
|
||||
|
||||
set -g fish_complete_path c1 c2
|
||||
complete -C "foo " >/dev/null
|
||||
# CHECK: auto-loading c1/foo.fish
|
||||
@@ -151,4 +151,19 @@ bind \n 2>&1
|
||||
bind _\cx_\ci_\ei_\\_\'_ 'echo foo'
|
||||
# CHECKERR: bind: cannot parse key '_\cx_\t_\ei_\\_'_'
|
||||
|
||||
bind A
|
||||
# CHECKERR: bind: No binding found for key 'A'
|
||||
|
||||
bind shift-a
|
||||
# CHECKERR: bind: No binding found for key 'shift-a'
|
||||
|
||||
bind shift-A
|
||||
# CHECKERR: bind: No binding found for key 'shift-A'
|
||||
|
||||
bind ctrl-shift-a
|
||||
# CHECKERR: bind: No binding found for key 'ctrl-shift-a'
|
||||
|
||||
bind ctrl-shift-ä
|
||||
# CHECKERR: bind: No binding found for key 'ctrl-shift-ä'
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -627,3 +627,22 @@ complete -C'testcommand '
|
||||
# CHECK: check{{\t}}Check the frobnicator
|
||||
# CHECK: search{{\t}}Search for frobs
|
||||
# CHECK: show{{\t}}Show all frobs
|
||||
|
||||
complete complete-list -xa '(__fish_complete_list , "seq 2")'
|
||||
complete -C "complete-list 1,"
|
||||
# CHECK: 1,1
|
||||
# CHECK: 1,2
|
||||
complete complete-list -s l -l number-list -xa '(__fish_stripprefix="^(--number-list=|-\w*l)" __fish_complete_list , "seq 2")'
|
||||
complete -C "complete-list --number-list=1,"
|
||||
# CHECK: --number-list=1,1
|
||||
# CHECK: --number-list=1,2
|
||||
complete -C "complete-list -abcl1,"
|
||||
# CHECK: -abcl1,1
|
||||
# CHECK: -abcl1,2
|
||||
|
||||
function esc_in_description
|
||||
echo completion\t'escaped \n newline'
|
||||
end
|
||||
complete complete-list -l desc -xa '(__fish_complete_list , esc_in_description)'
|
||||
complete -C 'complete-list --desc '
|
||||
# CHECK: completion{{\t}}escaped {{\\n}} newline
|
||||
|
||||
@@ -18,3 +18,16 @@ or echo "pgroups were the same, job control did not work"
|
||||
$fish -c 'status job-control full ; $fth report_foreground' &
|
||||
wait
|
||||
#CHECKERR: background
|
||||
|
||||
$fish -c '
|
||||
function __fish_print_help
|
||||
if set -q argv[2]
|
||||
echo Error-message: $argv[2] >&2
|
||||
end
|
||||
echo Documentation for $argv[1] >&2
|
||||
return 1
|
||||
end
|
||||
sleep .2 & bg %1
|
||||
'
|
||||
#CHECKERR: Error-message: bg: Can't put job 1, 'sleep .2 &' to background because it is not under job control
|
||||
#CHECKERR: Documentation for bg
|
||||
|
||||
@@ -154,3 +154,21 @@ printf '%b\n' '\0057foo\0057bar\0057'
|
||||
|
||||
printf %18446744073709551616s
|
||||
# CHECKERR: Number out of range
|
||||
|
||||
# Test non-ASCII behavior
|
||||
printf '|%3s|\n' 'ö'
|
||||
# CHECK: | ö|
|
||||
printf '|%3s|\n' '🇺🇳'
|
||||
#CHECK: | 🇺🇳|
|
||||
printf '|%.3s|\n' '🇺🇳🇺🇳'
|
||||
#CHECK: |🇺🇳|
|
||||
printf '|%.3s|\n' 'a🇺🇳'
|
||||
#CHECK: |a🇺🇳|
|
||||
printf '|%.3s|\n' 'aa🇺🇳'
|
||||
#CHECK: |aa|
|
||||
printf '|%3.3s|\n' 'aa🇺🇳'
|
||||
#CHECK: | aa|
|
||||
printf '|%.1s|\n' '𒈙a'
|
||||
#CHECK: |𒈙|
|
||||
printf '|%3.3s|\n' '👨👨👧👧'
|
||||
#CHECK: | 👨👨👧👧|
|
||||
|
||||
16
tests/checks/sphinx-markdown-changelog.fish
Normal file
16
tests/checks/sphinx-markdown-changelog.fish
Normal file
@@ -0,0 +1,16 @@
|
||||
#RUN: %fish %s
|
||||
#REQUIRES: command -v sphinx-build
|
||||
#REQUIRES: python -c 'import sphinx_markdown_builder'
|
||||
|
||||
set -l workspace_root (status dirname)/../..
|
||||
if not test -e $workspace_root/.git
|
||||
return
|
||||
end
|
||||
|
||||
$workspace_root/build_tools/release-notes.sh -q >?relnotes.md
|
||||
or {
|
||||
echo "Failed to build Markdown release notes."
|
||||
return
|
||||
}
|
||||
sed -n 1p relnotes.md | grep -q '^## fish \S* (released .*)'
|
||||
or echo "Unexpected changelog title"
|
||||
@@ -38,6 +38,9 @@ status --job-control=1none
|
||||
# Now set it to a valid mode.
|
||||
status job-control none
|
||||
|
||||
status current-command | sed s/^/^/
|
||||
# CHECK: ^fish
|
||||
|
||||
# Check status -u outside functions
|
||||
status current-function
|
||||
#CHECK: Not a function
|
||||
@@ -60,6 +63,7 @@ status features
|
||||
#CHECK: remove-percent-self off 4.0 %self is no longer expanded (use $fish_pid)
|
||||
#CHECK: test-require-arg off 4.0 builtin test requires an argument
|
||||
#CHECK: keyboard-protocols on 4.0 Use keyboard protocols (kitty, xterm's modifyotherkeys
|
||||
#CHECK: mark-prompt on 4.0 Write OSC 133 prompt markers to the terminal
|
||||
status test-feature stderr-nocaret
|
||||
echo $status
|
||||
#CHECK: 0
|
||||
|
||||
21
tests/checks/tmux-vi-key-bindings.fish
Normal file
21
tests/checks/tmux-vi-key-bindings.fish
Normal file
@@ -0,0 +1,21 @@
|
||||
#RUN: %fish %s
|
||||
#REQUIRES: command -v tmux
|
||||
|
||||
set -g isolated_tmux_fish_extra_args -C '
|
||||
set -g fish_key_bindings fish_vi_key_bindings
|
||||
'
|
||||
isolated-tmux-start
|
||||
|
||||
isolated-tmux send-keys 'echo 124' Escape
|
||||
tmux-sleep # disambiguate escape from alt
|
||||
isolated-tmux send-keys v b y p i 3
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p
|
||||
# CHECK: [I] prompt 0> echo 1241234
|
||||
|
||||
isolated-tmux send-keys Escape
|
||||
tmux-sleep # disambiguate escape from alt
|
||||
isolated-tmux send-keys e r 5
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p
|
||||
# CHECK: [N] prompt 0> echo 1241235
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from pexpect_helper import SpawnedProc
|
||||
from pexpect_helper import SpawnedProc, control
|
||||
|
||||
sp = SpawnedProc()
|
||||
send, sendline, sleep, expect_prompt, expect_re, expect_str = (
|
||||
@@ -78,3 +78,15 @@ sendline("echo bar")
|
||||
expect_re("\n.*bar")
|
||||
sendline("echo fo\t")
|
||||
expect_re("foooo")
|
||||
|
||||
# Custom completions that access the command line.
|
||||
sendline("complete -e :; complete : -a '(echo (commandline -ct)-completed)'")
|
||||
send(": abcd" + control("b") * 2 + "\t")
|
||||
expect_str(": abcd-completed")
|
||||
send(control("u"))
|
||||
# Another one.
|
||||
sendline("mkdir -p foo/bar; touch foo/bar/baz.fish")
|
||||
send("source foo/b/baz.fish")
|
||||
send(control("b") * 9 + "\t")
|
||||
expect_str("source foo/bar/baz.fish")
|
||||
send(control("u"))
|
||||
|
||||
Reference in New Issue
Block a user