Compare commits

...

25 Commits

Author SHA1 Message Date
Johannes Altmanninger
77a9466337 test/tmux-job: fix intermittent failure
This fails in CI because the wrong syntax fails to invoke a
subshell. Unfortunately it only reproduces without a subshell locally,
so remove it.
2026-06-26 17:25:09 +02:00
Johannes Altmanninger
2668e281d7 Revert "highlight: don't clone paths for case-insensitivity check"
This fails the borrow checker on macOS.

This reverts commit 9896a9f02e.
2026-06-26 17:14:03 +02:00
Johannes Altmanninger
5fc7deab16 Fix crash on unexpected tcsetpgrp failure (Rust port regression)
Fixes 77aeb6a2a8 (Port execution, 2023-10-08).
Fixes #12841
2026-06-26 16:51:13 +02:00
Johannes Altmanninger
9896a9f02e highlight: don't clone paths for case-insensitivity check 2026-06-26 16:45:12 +02:00
Johannes Altmanninger
deaa8bf3a9 highlight: remove unused exports 2026-06-26 16:45:12 +02:00
gitmpr
43e3aa4e6b highlight: add fish_color_builtin and fish_color_function
The highlighter already resolves command type (builtin, function,
external) on every keystroke but discarded that information, always
emitting HighlightRole::Command.

This surfaces the type by changing command_is_valid to return a
CommandType enum (Builtin, Function, Other, Invalid) and passing it to
color_command, which selects the matching HighlightRole. Both new roles
fall back to HighlightRole::Command (and thus fish_color_command) when
their variable is unset, so existing configurations are unaffected.

Closes #12775

Closes #12837
2026-06-26 16:23:26 +02:00
Johannes Altmanninger
8c1a625076 highlight is_potential_path: don't clone checked paths
No need to clone when inserting into checked_paths.
2026-06-26 16:23:26 +02:00
Johannes Altmanninger
0ee41cb55d highlight: refactor is_potential_cd_path a bit
The parent commit makes is_potential_path(), which comes with a
small increase in binary size.  Let's maybe avoid that by making
is_potential_cd_path() always pass a slice of string slices instead
of a slice of strings.  While at it, remove some allocations and
don't add "." twice.
2026-06-26 14:43:28 +02:00
Johannes Altmanninger
e6db10d1e0 highlight: stop cloning working directory 2026-06-26 14:43:28 +02:00
Johannes Altmanninger
391ede475b completions/firefox-developer-edition: fix if firefox is not executable
Fixes #12836
2026-06-26 14:43:28 +02:00
Khnome
cab89b5dfa dnf: add --skip-file-locks completion
Closes #12840
2026-06-26 14:43:28 +02:00
Khnome
77357c4e37 dnf: Fix auto completions not working after specifying a package
Closes #12830

Part of #12840
2026-06-26 14:43:28 +02:00
Khnome
53b185e102 dnf: support upgrade alias
Part of #12840
2026-06-26 14:43:28 +02:00
Johannes Altmanninger
5b992cf273 CHANGELOG: clarify that gettext is still used 2026-06-25 20:42:57 +02:00
Johannes Altmanninger
2c17c96e55 Revert "complete: tab-complete anywhere-position abbrs in non-command position"
"abbr --position=anywhere" completions showing up in file completion
clutters the completion pager with usually irrelevant information.

This reverts commit 4b2aba31ee.

Closes #12838
2026-06-25 20:42:34 +02:00
俺不叫化咕龙
22cb01d437 Use eq_ignore_ascii_case for char comparison 2026-06-24 21:03:24 -07:00
Nahor
972528648c contains: clarify options handling
The documentation sounded like options where handled in a fairly
standard way, meaning options and `--` could be anywhere in the
command line.
In reality, options have to be first, followed by `--`, then the key.

Document that behavior more explicitly, and add a test to detect if
this changes.
2026-06-24 09:52:53 -07:00
Johannes Altmanninger
16532cfaba start new cycle 2026-06-24 12:27:19 +02:00
Johannes Altmanninger
d573dd9f79 Release 4.8.0
Created by ./build_tools/release.sh 4.8.0
2026-06-24 11:59:34 +02:00
Johannes Altmanninger
be58b66c82 Update changelog 2026-06-24 09:10:39 +02:00
Johannes Altmanninger
fa8a2ee265 complete short_opt_pos: repeated short options are not invalid
complete -C"somecmd -xyz" will not suggest any of the short options
that are already present.  This is old behavior from 149594f974
(Initial revision, 2005-09-20) ("if( wcschr( arg, nextopt ) != 0 )").
It's questionable, since repeated options may be valid ("ssh -vvv"
etc.), and completions should generally err on the side of false
positives; but given that short options are usually easy to type,
and discovery is not really relevant if they are already on the
command line, this seems fine? Not sure.

Recently, fab397e754 (fix: filter invalid short option completions,
2026-06-18) made two changes:

1. "complete somecmd -s X -n false; complete -C'somecmd -X'" no longer
   prints completions. This is a bug fix that reduces confusion.
2. complete -C"somecmd -YY" where "Y" no longer completes other short
   options because we treat repetition as error. But it's not
   necessarily, and hiding unrelated completions seems wrong.

Revert change 2.

See https://github.com/fish-shell/fish-shell/pull/12821#issuecomment-4781863199
2026-06-24 08:49:59 +02:00
Johannes Altmanninger
6fd86a6e9b complete short_option_pos: use find again instead of raw loop
Reverts that part of ab397e7546 (fix: filter invalid short option
completions, 2026-06-18).

While at it, clean up a comment and assertion from eae1683033
(Completion: complete argument to last of a group of short options,
2019-09-10).
2026-06-24 08:49:59 +02:00
Johannes Altmanninger
becded65d2 completions/dnf: namespace helper functions properly 2026-06-23 18:43:35 +02:00
Johannes Altmanninger
36a6659390 docker: update fedora/suse dockerfiles 2026-06-23 18:11:58 +02:00
Drazape
eb8f53c48d fix: completions: fish_opt: correct flag: long(+:-)only
Closes #12827
2026-06-23 18:11:58 +02:00
33 changed files with 427 additions and 342 deletions

View File

@@ -1,38 +1,52 @@
fish ?.?.? (released ???)
=========================
Interactive improvements
------------------------
- Builtin and function commands can now be colored separately via new variables :envvar:`fish_color_builtin` and :envvar:`fish_color_function` (:issue:`12837`).
Regression fixes:
-----------------
- ``abbr --position=anywhere`` completions are no longer offered in argument position, to avoid cluttering the completion pager (:issue:`12838`).
fish 4.8.0 (released June 24, 2026)
===================================
Notable improvements and fixes
------------------------------
- Translatable messages defined in Rust source code may now be localized using `Fluent <https://projectfluent.org/>`__ instead of GNU gettext.
- Translatable messages defined in Rust source code can and should now be translated using `Fluent <https://projectfluent.org/>`__ instead of GNU gettext.
For now, GNU gettext continues to be used for translating messages defined in fish scripts.
To make Fluent easy to work with, we have added tooling based on the new `fluent-ftl-tools <https://codeberg.org/danielrainer/fluent-ftl-tools>`__ library.
See :ref:`Contributing Translations <localization>` (:issue:`11928`).
Deprecations and removed features
---------------------------------
- The ``--command`` and ``--path`` options in :doc:`complete <cmds/complete>` no longer unescape their argument.
- Builtin :doc:`complete's <cmds/complete>` ``--command`` and ``--path`` options no longer unescape their argument.
Interactive improvements
------------------------
- History search would sometimes forget about commands after those were re-run in concurrent sessions. This has been fixed (:issue:`10300`).
- On the first run after upgrading from an older version, fish will try harder to check if the current theme matches a historical default. If it does match, fish won't create ``~/.config/fish/conf.d/fish_frozen_theme.fish`` when upgrading from fish < 4.3.
Specifically, on systems where fish version 3.x was installed originally, fish 4.8 will avoid creating that file on upgrade (:issue:`12725`).
- ``fish_hg_prompt``, ``fish_git_prompt`` and ``fish_fossil_prompt`` now strip control characters from VCS state read off disk, matching ``prompt_pwd``.
- :doc:`bind <cmds/bind>` shows the file where bindings were defined (:issue:`12504`).
- Abbreviations with ``--position=anywhere`` can now be completed in argument position, not just in command position (:issue:`12630`).
- Path component movement (:kbd:`ctrl-w`) skips escaped characters.
- Completions no longer offer repeated short options (:issue:`12821`).
- Fixed an issue where control-C might fail to cancel certain functions (:issue:`12802`).
- Completion of short option groups will now handle ``--condition`` correctly (:issue:`12821`).
- Fixed an issue where :kbd:`ctrl-c` might fail to cancel certain functions (:issue:`12802`).
- On the first run after upgrading from an older version, fish will try harder to check if the current theme matches a historical default.
If it does match, fish won't create ``~/.config/fish/conf.d/fish_frozen_theme.fish`` when upgrading from fish < 4.3.
In particular, on systems where fish version 3.x was installed originally, fish will now avoid creating that file on upgrade (:issue:`12725`).
Scripting improvements
----------------------
- ``cd`` supports the ``-L`` and ``-P`` options, like other shells, to allow specifying whether symbolic links (symlinks) are resolved when changing directories (:issue:`7206`).
- ``cd`` with a relative path will now retry using the real current directory, if ``$PWD`` has been moved or deleted (:issue:`12700`).
- ``cd`` with a relative path will now retry using the real current directory, if ``$PWD`` has been moved (:issue:`12700`).
- Nested brace expansions now strip unquoted leading and trailing spaces from entries consistently (:issue:`12794`).
- :doc:`bind <cmds/bind>` shows the files where bindings were defined (:issue:`12504`).
Other improvements
------------------
- fish no longer creates universal variables by default; specifically, the ``__fish_initialized`` variable is no longer created.
- fish no longer creates the ``__fish_initialized`` universal variable on startup.
If you don't expect to need to downgrade to earlier versions, you can remove it with ``set --erase __fish_initialized``.
This means that fish now only creates universal variables if instructed by the user.
For distributors and developers
-------------------------------
@@ -53,7 +67,7 @@ Regression fixes:
- (from 4.4.0) Vi mode ``c,W`` key binding wrongly deleted trailing spaces (:issue:`12790`).
- (from 4.4.0) Vi mode ``x`` in :doc:`builtin read <cmds/read>` (:issue:`12724`).
- (from 4.3.3) Repeated tab would sometimes insert smartcase completions redundantly.
- (from 4.3.0) Pressing escape during command input would insert garbage text into the command line (:issue:`12379`).
- (from 4.3.0) Pressing escape during command execution could insert garbage text into the command line (:issue:`12379`).
fish 4.7.1 (released May 08, 2026)
==================================

2
Cargo.lock generated
View File

@@ -334,7 +334,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fish"
version = "4.7.1"
version = "4.8.0"
dependencies = [
"assert_matches",
"bitflags",

View File

@@ -106,7 +106,7 @@ debug = true
[package]
name = "fish"
version = "4.7.1"
version = "4.8.0"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"

View File

@@ -1,3 +1,11 @@
fish (4.8.0-1) stable; urgency=medium
* Release of new version 4.8.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.8.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Wed, 24 Jun 2026 11:59:34 +0200
fish (4.7.1-1) stable; urgency=medium
* Release of new version 4.7.1.

View File

@@ -6,7 +6,7 @@ Synopsis
.. synopsis::
contains [OPTIONS] KEY [VALUES ...]
contains [OPTIONS] [--] KEY [VALUES ...]
Description
-----------
@@ -22,7 +22,9 @@ The following options are available:
**-h** or **--help**
Displays help about using this command.
Note that ``contains`` interprets all arguments starting with a **-** as an option to ``contains``, until an **--** argument is reached.
Options must be passed before *KEY*.
All arguments after *KEY* will be considered a value, regardless if they start with a ``-`` or not, including ``--``.
If *KEY* itself starts with a ``-``, use a ``--`` argument to separate it from the options.
See the examples below.
@@ -62,3 +64,10 @@ While this will check if function ``hasargs`` is being ran with the **-q** optio
The **--** here stops ``contains`` from treating **-q** to an option to itself.
Instead it treats it as a normal string to check.
::
contains -i foo -q -- foo
This returns 3, since all arguments after the key are considered a value.

View File

@@ -129,6 +129,8 @@ Variable Meaning
========================================== =====================================================================
.. envvar:: fish_color_normal default color
.. envvar:: fish_color_command commands like echo
.. envvar:: fish_color_builtin builtin commands like cd and set - this falls back on the command color if unset
.. envvar:: fish_color_function user-defined functions - this falls back on the command color if unset
.. envvar:: fish_color_keyword keywords like if - this falls back on the command color if unset
.. envvar:: fish_color_quote quoted text like ``"abc"``
.. envvar:: fish_color_redirection IO redirections like >/dev/null
@@ -157,6 +159,7 @@ Variable Meaning
If a variable isn't set or is empty after subtracting any ``--theme=THEME`` options,
fish usually tries ``$fish_color_normal``, except for:
- ``$fish_color_builtin`` and ``$fish_color_function``, where they try ``$fish_color_command`` first.
- ``$fish_color_keyword``, where it tries ``$fish_color_command`` first.
- ``$fish_color_option``, where it tries ``$fish_color_param`` first.
- For ``$fish_color_valid_path``, if that doesn't have a color, but only modifiers, it adds those to the color that would otherwise be used,

View File

@@ -2,15 +2,17 @@ FROM fedora:latest
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
RUN dnf install --assumeyes \
cargo \
diffutils \
gcc-c++ \
git-core \
openssl \
pcre2-devel \
procps \
python3 \
python3-pip \
openssl \
procps \
sudo && \
rust \
sudo \
&& \
dnf clean all
RUN pip3 install pexpect

View File

@@ -7,7 +7,6 @@ ENV LC_ALL=C.UTF-8
RUN zypper --non-interactive install \
bash \
diffutils \
gcc-c++ \
git-core \
pcre2-devel \
python311 \

View File

@@ -61662,6 +61662,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61662,6 +61662,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61791,6 +61791,9 @@ msgstr "Ignorer le pré-traitement des blancs et commentaires"
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61665,6 +61665,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61658,6 +61658,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61663,6 +61663,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61659,6 +61659,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61683,6 +61683,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -61660,6 +61660,9 @@ msgstr ""
msgid "Skipping JUnit Tests"
msgstr ""
msgid "Skips acquiring file locks"
msgstr ""
msgid "Skips autoloader generation"
msgstr ""

View File

@@ -2,15 +2,15 @@
# Completions for the dnf command
#
function __dnf_is_dnf5
function __fish_dnf_is_dnf5
path resolve -- $PATH/dnf | path filter | string match -q -- '*/dnf5'
end
function __dnf_list_installed_packages
function __fish_dnf_list_installed_packages
dnf repoquery --cacheonly "$cur*" --qf "%{name}\n" --installed </dev/null
end
function __dnf_list_copr_repos
function __fish_dnf_list_copr_repos
set -l copr_repos (dnf copr list)
switch $argv[1]
@@ -23,7 +23,7 @@ function __dnf_list_copr_repos
end
end
function __dnf_list_available_packages
function __fish_dnf_list_available_packages
set -l tok (commandline -ct | string collect)
set -l files (__fish_complete_suffix .rpm)
if string match -q -- '*/*' $tok
@@ -32,7 +32,7 @@ function __dnf_list_available_packages
return
end
set -l results
if __dnf_is_dnf5
if __fish_dnf_is_dnf5
# dnf5 provides faster completions than repoquery, but does not maintain the
# same sqlite db as dnf4
set results (dnf --complete=2 dnf install "$tok*")
@@ -58,8 +58,8 @@ function __dnf_list_available_packages
string join \n $results
end
function __dnf_list_transactions
if not __dnf_is_dnf5 && type -q sqlite3
function __fish_dnf_list_transactions
if not __fish_dnf_is_dnf5 && type -q sqlite3
sqlite3 /var/lib/dnf/history.sqlite "SELECT id, cmdline FROM trans" 2>/dev/null | string replace "|" \t
end
end
@@ -69,6 +69,7 @@ set -l dnf_install_cmds install in
set -l dnf_remove_cmds remove rm
set -l dnf_reinstall_cmds reinstall rei
set -l dnf_info_cmds info if
set -l dnf_upgrade_cmds upgrade up
# Alias
complete -c dnf -n __fish_use_subcommand -xa alias -d "Manage aliases"
@@ -78,7 +79,7 @@ complete -c dnf -n "__fish_seen_subcommand_from alias" -xa delete -d "Delete an
# Autoremove
complete -c dnf -n __fish_use_subcommand -xa autoremove -d "Removes unneeded packages"
complete -c dnf -n "__fish_seen_subcommand_from autoremove" -xa "(__dnf_list_installed_packages)"
complete -c dnf -n "__fish_seen_subcommand_from autoremove" -xa "(__fish_dnf_list_installed_packages)"
# Check
complete -c dnf -n __fish_use_subcommand -xa check -d "Check for problems in packagedb"
@@ -110,7 +111,7 @@ complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcom
complete -c dnf -n "__fish_seen_subcommand_from copr; and not __fish_seen_subcommand_from $coprcommands" -l hub -d "Copr hub hostname"
for i in enable disable remove
complete -c dnf -n "__fish_seen_subcommand_from copr; and __fish_seen_subcommand_from $i" -xa "(__dnf_list_copr_repos $i)"
complete -c dnf -n "__fish_seen_subcommand_from copr; and __fish_seen_subcommand_from $i" -xa "(__fish_dnf_list_copr_repos $i)"
end
# Distro-sync
@@ -118,7 +119,7 @@ complete -c dnf -n __fish_use_subcommand -xa distro-sync -d "Synchronizes packag
# Downgrade
complete -c dnf -n __fish_use_subcommand -xa downgrade -d "Downgrades the specified package"
complete -c dnf -n "__fish_seen_subcommand_from downgrade" -xa "(__dnf_list_installed_packages)"
complete -c dnf -n "__fish_seen_subcommand_from downgrade" -xa "(__fish_dnf_list_installed_packages)"
# Group
complete -c dnf -n __fish_use_subcommand -xa group -d "Manage groups"
@@ -156,18 +157,18 @@ complete -c dnf -n "__fish_seen_subcommand_from history" -xa undo -d "Undoes the
complete -c dnf -n "__fish_seen_subcommand_from history" -xa userinstalled -d "Lists all user installed packages"
for i in info redo rollback undo
complete -c dnf -n "__fish_seen_subcommand_from history; and __fish_seen_subcommand_from $i" -xa "(__dnf_list_transactions)"
complete -c dnf -n "__fish_seen_subcommand_from history; and __fish_seen_subcommand_from $i" -xa "(__fish_dnf_list_transactions)"
end
# Info
complete -c dnf -n __fish_use_subcommand -xa "$dnf_info_cmds" -d "Describes the given package"
complete -c dnf -n "__fish_seen_subcommand_from $dnf_info_cmds; and not __fish_seen_subcommand_from history" \
-k -xa "(__dnf_list_available_packages)"
-k -xa "(__fish_dnf_list_available_packages)"
# Install
complete -c dnf -n __fish_use_subcommand -xa "$dnf_install_cmds" -d "Install package"
complete -c dnf -n "__fish_seen_subcommand_from $dnf_install_cmds" \
-k -xa "(__dnf_list_available_packages)"
-k -xa "(__fish_dnf_list_available_packages)"
# List
complete -c dnf -n __fish_use_subcommand -xa list -d "Lists all packages"
@@ -227,12 +228,12 @@ complete -c dnf -n __fish_use_subcommand -xa provides -d "Finds packages providi
# Reinstall
complete -c dnf -n __fish_use_subcommand -xa "$dnf_reinstall_cmds" -d "Reinstalls a package"
complete -c dnf -n "__fish_seen_subcommand_from $dnf_reinstall_cmds" \
-xa "(__dnf_list_installed_packages)"
-xa "(__fish_dnf_list_installed_packages)"
# Remove
complete -c dnf -n __fish_use_subcommand -xa "$dnf_remove_cmds" -d "Remove packages"
complete -c dnf -n "__fish_seen_subcommand_from $dnf_remove_cmds" \
-xa "(__dnf_list_installed_packages)"
-xa "(__fish_dnf_list_installed_packages)"
complete -c dnf -n "__fish_seen_subcommand_from remove" -l duplicates -d "Removes older version of duplicated packages"
complete -c dnf -n "__fish_seen_subcommand_from remove" -l oldinstallonly -d "Removes old installonly packages"
@@ -339,29 +340,29 @@ complete -c dnf -n "__fish_seen_subcommand_from updateinfo" -l installed
complete -c dnf -n "__fish_seen_subcommand_from updateinfo" -l updates
# Upgrade
complete -c dnf -n __fish_use_subcommand -xa upgrade -d "Updates packages"
complete -c dnf -n "__fish_seen_subcommand_from upgrade" -xa "(__dnf_list_installed_packages)"
complete -c dnf -n __fish_use_subcommand -xa "$dnf_upgrade_cmds" -d "Updates packages"
complete -c dnf -n "__fish_seen_subcommand_from $dnf_upgrade_cmds" -xa "(__fish_dnf_list_installed_packages)"
# Upgrade-Minimal
complete -c dnf -n __fish_use_subcommand -xa upgrade-minimal -d "Updates packages"
complete -c dnf -n "__fish_seen_subcommand_from upgrade-minimal" -xa "(__dnf_list_installed_packages)"
complete -c dnf -n "__fish_seen_subcommand_from upgrade-minimal" -xa "(__fish_dnf_list_installed_packages)"
# Versionlock
if test -f /etc/dnf/plugins/versionlock.conf
function __dnf_current_versionlock_list
function __fish_dnf_current_versionlock_list
dnf versionlock list | grep -v metadata
end
complete -c dnf -n __fish_use_subcommand -xa versionlock -d "DNF versionlock plugin"
# - add
complete -c dnf -n "__fish_seen_subcommand_from versionlock" -xa add -d "Add a versionlock for all available packages matching the spec"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from add" -xa "(__dnf_list_installed_packages)"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from add" -xa "(__fish_dnf_list_installed_packages)"
# - exclude
complete -c dnf -n "__fish_seen_subcommand_from versionlock" -xa exclude -d "Add an exclude (within versionlock) for the available packages matching the spec"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from exclude" -xa "(__dnf_list_installed_packages)"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from exclude" -xa "(__fish_dnf_list_installed_packages)"
# - delete
complete -c dnf -n "__fish_seen_subcommand_from versionlock" -xa delete -d "Remove any matching versionlock entries"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from delete" -xa "(__dnf_current_versionlock_list)"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from delete" -xa "(__fish_dnf_current_versionlock_list)"
# - list
complete -c dnf -n "__fish_seen_subcommand_from versionlock" -xa list -d "List the current versionlock entries"
complete -c dnf -n "__fish_seen_subcommand_from versionlock; and __fish_seen_subcommand_from list" -xa "(false)"
@@ -371,55 +372,54 @@ if test -f /etc/dnf/plugins/versionlock.conf
end
# Options:
# Using __fish_no_arguments here so that users are not completely overloaded with
# available options when using subcommands (e.g. repoquery) (40 vs 100ish)
complete -c dnf -n __fish_no_arguments -s 4 -d "Use IPv4 only"
complete -c dnf -n __fish_no_arguments -s 6 -d "Use IPv6 only"
complete -c dnf -n __fish_no_arguments -l advisory -l advisories -d "Include packages corresponding to the advisory ID"
complete -c dnf -n __fish_no_arguments -l allowerasing -d "Allow erasing of installed packages to resolve dependencies"
complete -c dnf -n __fish_no_arguments -l assumeno -d "Answer no for all questions"
complete -c dnf -n __fish_no_arguments -s b -l best -d "Try the best available package versions in transactions"
complete -c dnf -n __fish_no_arguments -l bugfix -d "Include packages that fix a bugfix issue"
complete -c dnf -n __fish_no_arguments -l bz -l bzs -d "Include packages that fix a Bugzilla ID"
complete -c dnf -n __fish_no_arguments -s C -l cacheonly -d "Run entirely from system cache"
complete -c dnf -n __fish_no_arguments -l color -xa "always never auto" -d "Control whether color is used"
complete -c dnf -n __fish_no_arguments -s c -l config -d "Configuration file location"
complete -c dnf -n __fish_no_arguments -l cve -l cves -d "Include packages that fix a CVE"
complete -c dnf -n __fish_no_arguments -s d -l debuglevel -d "Debugging output level"
complete -c dnf -n __fish_no_arguments -l debugsolver -d "Dump dependency solver debugging info"
complete -c dnf -n __fish_no_arguments -l disableexcludes -l disableexcludepkgs -d "Disable excludes"
complete -c dnf -n __fish_no_arguments -l disable -l set-disabled -d "Disable specified repositories"
complete -c dnf -n __fish_no_arguments -l disableplugin -d "Disable the listed plugins specified"
complete -c dnf -n __fish_no_arguments -l disablerepo -d "Disable specified repositories"
complete -c dnf -n __fish_no_arguments -l downloaddir -l destdir -d "Change downloaded packages to provided directory"
complete -c dnf -n __fish_no_arguments -l downloadonly -d "Download packages without performing any transaction"
complete -c dnf -n __fish_no_arguments -l enable -l set-enabled -d "Enable specified repositories"
complete -c dnf -n __fish_no_arguments -l enableplugin -d "Enable the listed plugins"
complete -c dnf -n __fish_no_arguments -l enablerepo -d "Enable additional repositories"
complete -c dnf -n __fish_no_arguments -l enhancement -d "Include enhancement relevant packages"
complete -c dnf -n __fish_no_arguments -s x -l exclude -d "Exclude packages specified"
complete -c dnf -n __fish_no_arguments -l forcearch -d "Force the use of the specified architecture"
complete -c dnf -n __fish_no_arguments -s h -l help -l help-i -d "Show the help"
complete -c dnf -n __fish_no_arguments -l installroot -d "Specifies an alternative installroot"
complete -c dnf -n __fish_no_arguments -l newpackage -d "Include newpackage relevant packages"
complete -c dnf -n __fish_no_arguments -l noautoremove -d "Disable autoremove"
complete -c dnf -n __fish_no_arguments -l nobest -d "Set best option to False"
complete -c dnf -n __fish_no_arguments -l nodocs -d "Do not install documentation"
complete -c dnf -n __fish_no_arguments -l nogpgcheck -d "Skip checking GPG signatures on packages"
complete -c dnf -n __fish_no_arguments -l noplugins -d "Disable all plugins"
complete -c dnf -n __fish_no_arguments -l obsoletes -d "Enables obsoletes processing logic"
complete -c dnf -n __fish_no_arguments -s q -l quiet -d "Quiet mode"
complete -c dnf -n __fish_no_arguments -s R -l randomwait -d "Maximum command wait time"
complete -c dnf -n __fish_no_arguments -l refresh -d "Set metadata as expired before running the command"
complete -c dnf -n __fish_no_arguments -l releasever -d "Configure the distribution release"
complete -c dnf -n __fish_no_arguments -l repofrompath -d "Specify repository to add to the repositories for this query"
complete -c dnf -n __fish_no_arguments -l repo -l repoid -d "Enable just specific repositories by an id or a glob"
complete -c dnf -n __fish_no_arguments -l rpmverbosity -d "RPM debug scriptlet output level"
complete -c dnf -n __fish_no_arguments -l sec-severity -l secseverity -d "Includes packages that provide a fix for an issue of the specified severity"
complete -c dnf -n __fish_no_arguments -l security -d "Includes packages that provide a fix for a security issue"
complete -c dnf -n __fish_no_arguments -l setopt -d "Override a configuration option"
complete -c dnf -n __fish_no_arguments -l skip-broken -d "Skips broken packages"
complete -c dnf -n __fish_no_arguments -l showduplicates -d "Shows duplicate packages"
complete -c dnf -n __fish_no_arguments -s v -l verbose -d "Verbose mode"
complete -c dnf -n __fish_no_arguments -l version -d "Shows DNF version and exit"
complete -c dnf -n __fish_no_arguments -s y -l assumeyes -d "Answer yes for all questions"
complete -c dnf -s 4 -d "Use IPv4 only"
complete -c dnf -s 6 -d "Use IPv6 only"
complete -c dnf -l advisory -l advisories -d "Include packages corresponding to the advisory ID"
complete -c dnf -l allowerasing -d "Allow erasing of installed packages to resolve dependencies"
complete -c dnf -l assumeno -d "Answer no for all questions"
complete -c dnf -s b -l best -d "Try the best available package versions in transactions"
complete -c dnf -l bugfix -d "Include packages that fix a bugfix issue"
complete -c dnf -l bz -l bzs -d "Include packages that fix a Bugzilla ID"
complete -c dnf -s C -l cacheonly -d "Run entirely from system cache"
complete -c dnf -l color -xa "always never auto" -d "Control whether color is used"
complete -c dnf -s c -l config -d "Configuration file location"
complete -c dnf -l cve -l cves -d "Include packages that fix a CVE"
complete -c dnf -s d -l debuglevel -d "Debugging output level"
complete -c dnf -l debugsolver -d "Dump dependency solver debugging info"
complete -c dnf -l disableexcludes -l disableexcludepkgs -d "Disable excludes"
complete -c dnf -l disable -l set-disabled -d "Disable specified repositories"
complete -c dnf -l disableplugin -d "Disable the listed plugins specified"
complete -c dnf -l disablerepo -d "Disable specified repositories"
complete -c dnf -l downloaddir -l destdir -d "Change downloaded packages to provided directory"
complete -c dnf -l downloadonly -d "Download packages without performing any transaction"
complete -c dnf -l enable -l set-enabled -d "Enable specified repositories"
complete -c dnf -l enableplugin -d "Enable the listed plugins"
complete -c dnf -l enablerepo -d "Enable additional repositories"
complete -c dnf -l enhancement -d "Include enhancement relevant packages"
complete -c dnf -s x -l exclude -d "Exclude packages specified"
complete -c dnf -l forcearch -d "Force the use of the specified architecture"
complete -c dnf -s h -l help -l help-i -d "Show the help"
complete -c dnf -l installroot -d "Specifies an alternative installroot"
complete -c dnf -l newpackage -d "Include newpackage relevant packages"
complete -c dnf -l noautoremove -d "Disable autoremove"
complete -c dnf -l nobest -d "Set best option to False"
complete -c dnf -l nodocs -d "Do not install documentation"
complete -c dnf -l nogpgcheck -d "Skip checking GPG signatures on packages"
complete -c dnf -l noplugins -d "Disable all plugins"
complete -c dnf -l obsoletes -d "Enables obsoletes processing logic"
complete -c dnf -s q -l quiet -d "Quiet mode"
complete -c dnf -s R -l randomwait -d "Maximum command wait time"
complete -c dnf -l refresh -d "Set metadata as expired before running the command"
complete -c dnf -l releasever -d "Configure the distribution release"
complete -c dnf -l repofrompath -d "Specify repository to add to the repositories for this query"
complete -c dnf -l repo -l repoid -d "Enable just specific repositories by an id or a glob"
complete -c dnf -l rpmverbosity -d "RPM debug scriptlet output level"
complete -c dnf -l sec-severity -l secseverity -d "Includes packages that provide a fix for an issue of the specified severity"
complete -c dnf -l security -d "Includes packages that provide a fix for a security issue"
complete -c dnf -l setopt -d "Override a configuration option"
complete -c dnf -l skip-broken -d "Skips broken packages"
complete -c dnf -l showduplicates -d "Shows duplicate packages"
complete -c dnf -s v -l verbose -d "Verbose mode"
complete -c dnf -l version -d "Shows DNF version and exit"
complete -c dnf -s y -l assumeyes -d "Answer yes for all questions"
complete -c dnf -s y -l skip-file-locks -d "Skips acquiring file locks"

View File

@@ -1 +1 @@
complete -c firefox-developer-edition -w firefox
__fish_complete_firefox firefox-developer-edition

View File

@@ -1,41 +1 @@
# X11 options
complete -c firefox -l display -r -d "X display to use"
complete -c firefox -l sync -d "Make X calls synchronous"
complete -c firefox -l g-fatal-warnings -d "Make all warnings fatal"
# Firefox options
complete -c firefox -s h -l help -d "Print this message"
complete -c firefox -s v -l version -d "Print Firefox version"
complete -c firefox -l full-version -d "Print Firefox version, build and platform build ids"
complete -c firefox -s P -r -d "Start this profile" -fa '(string replace -rf "^Name=(.*)" \'$1\' < ~/.mozilla/firefox/profiles.ini)'
complete -c firefox -l profile -r -d "Start with profile at <path>"
complete -c firefox -l migration -d "Start with migration wizard"
complete -c firefox -l ProfileManager -d "Start with ProfileManager"
complete -c firefox -l no-remote -d "Do not accept or send remote commands implies --new-instance"
complete -c firefox -l new-instance -d "Open new instance, not a new window in running instance"
complete -c firefox -l safe-mode -d "Disables extensions and themes for this session"
complete -c firefox -l allow-downgrade -d "Allows downgrading a profile"
complete -c firefox -l MOZ_LOG -rf -d "Treated as MOZ_LOG=<modules> environment variable, overrides it"
complete -c firefox -l MOZ_LOG_FILE -r -d "Treated as MOZ_LOG_FILE=<file> environment variable, overrides it"
complete -c firefox -l headless -d "Run without a GUI"
complete -c firefox -l jsdebugger -d "Open the Browser Toolbox"
complete -c firefox -l wait-for-jsdebugger -d "Spin event loop until JS debugger connects"
complete -c firefox -l start-debugger-server -d "Start the devtools server on a TCP port or Unix domain socket path"
complete -c firefox -l browser -d "Open a browser window"
complete -c firefox -l new-window -r -d "Open <url> in a new window"
complete -c firefox -l new-tab -r -d "Open <url> in a new tab"
complete -c firefox -l private-window -r -d "Open <url> in a new private window"
complete -c firefox -l preferences -d "Open Preferences dialog"
complete -c firefox -l screenshot -d "Save screenshot to <path> or in working directory"
complete -c firefox -rf -l window-size -d "Width and optionally height of screenshot"
complete -c firefox -l search -r -d "Search <term> with your default search engine"
complete -c firefox -l setDefaultBrowser -d "Set this app as the default browser"
complete -c firefox -l first-startup -d "Run post-install actions before opening a new window"
complete -c firefox -l kiosk -d "Start the browser in kiosk mode"
complete -c firefox -l disable-pinch -d "Disable touch-screen and touch-pad pinch gestures"
complete -c firefox -l jsconsole -d "Open the Browser Console"
complete -c firefox -l devtools -d "Open DevTools on initial load"
complete -c firefox -l marionette -d "Enable remote control server"
complete -c firefox -f -l remote-debugging-port -d "Start the Firefox Remote Agent"
complete -c firefox -f -l remote-allow-hosts -r -d "Values of the Host header to allow for incoming requests"
complete -c firefox -f -l remote-allow-origins -r -d "Values of the Origin header to allow for incoming requests"
__fish_complete_firefox firefox

View File

@@ -8,7 +8,7 @@ complete --command fish_opt --short-option h --long-option help --description 'S
complete --command fish_opt --short-option s --long-option short --no-files --require-parameter --description 'Specify short option'
complete --command fish_opt --short-option l --long-option long --no-files --require-parameter --description 'Specify long option'
complete --command fish_opt --long-option longonly --description 'Use only long option'
complete --command fish_opt --long-option long-only --description 'Use only long option'
complete --command fish_opt --short-option o --long-option optional-val -n $CONDITION --description 'Don\'t require value'
complete --command fish_opt --short-option r --long-option required-val -n $CONDITION --description 'Require value'
complete --command fish_opt --short-option m --long-option multiple-vals --description 'Store all values'

View File

@@ -77,6 +77,8 @@ function __fish_complete_special_vars
fish_user_paths "A list of dirs to prepend to PATH"
__fish_complete_special_vars_ifndef fish_color_option 'defaults to $fish_color_param'
__fish_complete_special_vars_ifndef fish_color_keyword 'defaults to $fish_color_command'
__fish_complete_special_vars_ifndef fish_color_builtin 'defaults to $fish_color_command'
__fish_complete_special_vars_ifndef fish_color_function 'defaults to $fish_color_command'
end
#

View File

@@ -0,0 +1,44 @@
# localization: tier3
function __fish_complete_firefox -a firefox
# X11 options
complete $firefox -l display -r -d "X display to use"
complete $firefox -l sync -d "Make X calls synchronous"
complete $firefox -l g-fatal-warnings -d "Make all warnings fatal"
# Firefox options
complete $firefox -s h -l help -d "Print this message"
complete $firefox -s v -l version -d "Print Firefox version"
complete $firefox -l full-version -d "Print Firefox version, build and platform build ids"
complete $firefox -s P -r -d "Start this profile" -fa '(string replace -rf "^Name=(.*)" \'$1\' < ~/.mozilla/firefox/profiles.ini)'
complete $firefox -l profile -r -d "Start with profile at <path>"
complete $firefox -l migration -d "Start with migration wizard"
complete $firefox -l ProfileManager -d "Start with ProfileManager"
complete $firefox -l no-remote -d "Do not accept or send remote commands implies --new-instance"
complete $firefox -l new-instance -d "Open new instance, not a new window in running instance"
complete $firefox -l safe-mode -d "Disables extensions and themes for this session"
complete $firefox -l allow-downgrade -d "Allows downgrading a profile"
complete $firefox -l MOZ_LOG -rf -d "Treated as MOZ_LOG=<modules> environment variable, overrides it"
complete $firefox -l MOZ_LOG_FILE -r -d "Treated as MOZ_LOG_FILE=<file> environment variable, overrides it"
complete $firefox -l headless -d "Run without a GUI"
complete $firefox -l jsdebugger -d "Open the Browser Toolbox"
complete $firefox -l wait-for-jsdebugger -d "Spin event loop until JS debugger connects"
complete $firefox -l start-debugger-server -d "Start the devtools server on a TCP port or Unix domain socket path"
complete $firefox -l browser -d "Open a browser window"
complete $firefox -l new-window -r -d "Open <url> in a new window"
complete $firefox -l new-tab -r -d "Open <url> in a new tab"
complete $firefox -l private-window -r -d "Open <url> in a new private window"
complete $firefox -l preferences -d "Open Preferences dialog"
complete $firefox -l screenshot -d "Save screenshot to <path> or in working directory"
complete $firefox -rf -l window-size -d "Width and optionally height of screenshot"
complete $firefox -l search -r -d "Search <term> with your default search engine"
complete $firefox -l setDefaultBrowser -d "Set this app as the default browser"
complete $firefox -l first-startup -d "Run post-install actions before opening a new window"
complete $firefox -l kiosk -d "Start the browser in kiosk mode"
complete $firefox -l disable-pinch -d "Disable touch-screen and touch-pad pinch gestures"
complete $firefox -l jsconsole -d "Open the Browser Console"
complete $firefox -l devtools -d "Open DevTools on initial load"
complete $firefox -l marionette -d "Enable remote control server"
complete $firefox -f -l remote-debugging-port -d "Start the Firefox Remote Agent"
complete $firefox -f -l remote-allow-hosts -r -d "Values of the Host header to allow for incoming requests"
complete $firefox -f -l remote-allow-origins -r -d "Values of the Origin header to allow for incoming requests"
end

View File

@@ -1291,6 +1291,8 @@ fn html_class_name_for_color(spec: HighlightSpec) -> &'static wstr {
HighlightRole::Normal => L!("fish_color_normal"),
HighlightRole::Error => L!("fish_color_error"),
HighlightRole::Command => L!("fish_color_command"),
HighlightRole::Builtin => L!("fish_color_builtin"),
HighlightRole::Function => L!("fish_color_function"),
HighlightRole::StatementTerminator => L!("fish_color_statement_terminator"),
HighlightRole::Param => L!("fish_color_param"),
HighlightRole::Option => L!("fish_color_option"),

View File

@@ -1,5 +1,5 @@
use crate::{
abbrs::{Position, with_abbrs},
abbrs::with_abbrs,
ast::unescape_keyword,
autoload::{Autoload, AutoloadResult},
builtins::{builtin_exists, builtin_get_desc, builtin_get_names},
@@ -689,7 +689,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
return;
}
self.complete_cmd(WString::new());
self.complete_abbr(L!(""), true);
self.complete_abbr(L!(""));
return;
};
@@ -744,7 +744,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
return;
}
// Complete command filename.
self.complete_abbr(current_token, true);
self.complete_abbr(current_token);
self.complete_cmd(current_token.to_owned());
return;
}
@@ -784,17 +784,10 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
}
}
#[derive(Eq, PartialEq)]
enum DoFile {
No,
Yes,
Only,
}
let mut do_file = DoFile::No;
let mut do_file = false;
let mut handle_as_special_cd = false;
if in_redirection {
do_file = DoFile::Only;
do_file = true;
} else {
// Try completing as an argument.
let mut arg_data = CustomArgData::new(&mut var_assignments);
@@ -824,15 +817,11 @@ enum DoFile {
command_range,
&mut arg_data,
);
do_file = if arg_data.do_file {
DoFile::Yes
} else {
DoFile::No
};
do_file = arg_data.do_file;
// If we're autosuggesting, and the token is empty, don't do file suggestions.
if is_autosuggest && arg_data.current_argument.is_empty() {
do_file = DoFile::No;
do_file = false;
}
}
@@ -844,14 +833,10 @@ enum DoFile {
// Maybe apply variable assignments.
let block = self.apply_var_assignments(&var_assignments);
if !self.ctx.check_cancel() {
if do_file != DoFile::Only {
self.complete_abbr(current_argument, false);
}
// This function wants the unescaped string.
self.complete_param_expand(
current_argument,
do_file != DoFile::No,
do_file,
handle_as_special_cd,
cur_tok.is_unterminated_brace,
);
@@ -908,26 +893,17 @@ fn short_option_pos(&mut self, arg: &wstr, options: &[CompleteEntryOpt]) -> Opti
return None;
}
let mut seen_short_options = HashSet::new();
for (pos, arg_char) in arg.chars().enumerate().skip(1) {
let mut matched = None;
for o in options {
if o.typ == CompleteOptionType::Short
let matched = options.iter().find(|o| {
o.typ == CompleteOptionType::Short
&& o.option.char_at(0) == arg_char
&& self.conditions_test(&o.conditions)
{
matched = Some(o);
break;
}
}
});
if let Some(matched) = matched {
if matched.result_mode.requires_param {
return Some(pos);
}
if !seen_short_options.insert(arg_char) {
return None;
}
} else {
// The first character after the dash is not a valid option.
if pos == 1 {
@@ -1196,18 +1172,15 @@ fn complete_cmd(&mut self, str_cmd: WString) {
}
}
/// Attempt to complete a non-regex abbreviation for the given string.
fn complete_abbr(&mut self, cmd: &wstr, is_command_position: bool) {
/// Attempt to complete an abbreviation for the given string.
fn complete_abbr(&mut self, cmd: &wstr) {
// Copy the list of names and descriptions so as not to hold the lock across the call to
// complete_strings.
let mut possible_comp = Vec::new();
let mut descs = HashMap::new();
with_abbrs(|set| {
for abbr in set.list() {
if abbr.is_regex() {
continue;
}
if abbr.position == Position::Anywhere || is_command_position {
if !abbr.is_regex() {
possible_comp.push(Completion::from_completion(abbr.key.clone()));
descs.insert(abbr.key.clone(), abbr.replacement.clone());
}
@@ -2229,8 +2202,10 @@ fn param_match(e: &CompleteEntryOpt, optstr: &wstr) -> bool {
}
}
/// Test if a string is an option with an argument, like --color=auto or -I/usr/include.
/// Test if a string is an option with an argument, like --color=auto or -std=c++26.
/// Short options are handled by the caller.
fn param_match2(e: &CompleteEntryOpt, optstr: &wstr) -> Option<usize> {
assert!(e.typ != CompleteOptionType::Short);
// We may get a complete_entry_opt_t with no options if it's just arguments.
if e.option.is_empty() {
return None;
@@ -2250,7 +2225,6 @@ fn param_match2(e: &CompleteEntryOpt, optstr: &wstr) -> Option<usize> {
// Short options are like -DNDEBUG. Long options are like --color=auto. So check for an equal
// sign for long options.
assert!(e.typ != CompleteOptionType::Short);
if optstr.char_at(cursor) != '=' {
return None;
}

View File

@@ -22,19 +22,19 @@
use libc::PATH_MAX;
use nix::unistd::AccessFlags;
use std::{
collections::{HashMap, HashSet},
collections::{HashMap, hash_map},
os::fd::RawFd,
};
// This is used only internally to this file, and is exposed only for testing.
#[derive(Clone, Copy, Default)]
pub struct PathFlags {
struct PathFlags {
// The path must be to a directory.
pub require_dir: bool,
require_dir: bool,
// Expand any leading tilde in the path.
pub expand_tilde: bool,
expand_tilde: bool,
// Normalize directories before resolving, as "cd".
pub for_cd: bool,
for_cd: bool,
}
// When a file test is OK, we may also return whether this was a file.
@@ -48,15 +48,15 @@ pub struct PathFlags {
/// The result of a file test.
pub type FileTestResult = Result<IsFile, IsErr>;
pub struct FileTester<'src, 'opctx> {
pub struct FileTester<'src, 'wd, 'opctx> {
// The working directory, for resolving paths against.
working_directory: WString,
working_directory: &'wd wstr,
// The operation context.
pub(super) ctx: &'opctx mut OperationContext<'src>,
pub ctx: &'opctx mut OperationContext<'src>,
}
impl<'src, 'opctx> FileTester<'src, 'opctx> {
pub fn new(working_directory: WString, ctx: &'opctx mut OperationContext<'src>) -> Self {
impl<'src, 'wd, 'opctx> FileTester<'src, 'wd, 'opctx> {
pub fn new(working_directory: &'wd wstr, ctx: &'opctx mut OperationContext<'src>) -> Self {
Self {
working_directory,
ctx,
@@ -110,7 +110,7 @@ pub fn test_cd_path(&mut self, token: &wstr, is_prefix: bool) -> FileTestResult
let valid_path = is_potential_cd_path(
&param,
is_prefix,
&self.working_directory,
self.working_directory,
self.ctx,
PathFlags {
expand_tilde: true,
@@ -144,7 +144,7 @@ pub fn test_redirection_target(
// Ok, we successfully expanded our target. Now verify that it works with this
// redirection. We will probably need it as a path (but not in the case of fd
// redirections). Note that the target is now unescaped.
let target_path = path_apply_working_directory(&target, &self.working_directory);
let target_path = path_apply_working_directory(&target, self.working_directory);
match mode {
RedirectionMode::Fd => {
if target == "-" {
@@ -224,10 +224,10 @@ pub fn test_redirection_target(
/// cdpath). This does I/O!
///
/// We expect the path to already be unescaped.
pub fn is_potential_path(
fn is_potential_path(
potential_path_fragment: &wstr,
at_cursor: bool,
directories: &[WString],
directories: &[&wstr],
ctx: &OperationContext<'_>,
flags: PathFlags,
) -> bool {
@@ -274,7 +274,8 @@ pub fn is_potential_path(
// Don't test the same path multiple times, which can happen if the path is absolute and the
// CDPATH contains multiple entries.
let mut checked_paths = HashSet::new();
// TODO This should be a HashSet https://github.com/rust-lang/rust/issues/60896.
let mut checked_paths = HashMap::new();
// Keep a cache of which paths / filesystems are case sensitive.
let mut case_sensitivity_cache = CaseSensitivityCache::new();
@@ -289,11 +290,17 @@ pub fn is_potential_path(
abs_path = normalize_path(&abs_path, /*allow_leading_double_slashes=*/ true);
}
// Skip this if it's empty or we've already checked it.
if abs_path.is_empty() || checked_paths.contains(&abs_path) {
if abs_path.is_empty() {
continue;
}
checked_paths.insert(abs_path.clone());
let abs_path = {
use hash_map::Entry::*;
match checked_paths.entry(abs_path) {
Occupied(_occupied_entry) => continue,
Vacant(vacant) => vacant.insert_entry(()),
}
};
let abs_path = abs_path.key();
// If the user is still typing the argument, we want to highlight it if it's the prefix
// of a valid path. This means we need to potentially walk all files in some directory.
@@ -302,15 +309,15 @@ pub fn is_potential_path(
// 2. If the cursor is not at the argument, it means the user is definitely not typing it,
// so we can skip the prefix-match.
if must_be_full_dir || !at_cursor {
if let Ok(md) = wstat(&abs_path) {
if let Ok(md) = wstat(abs_path) {
if !at_cursor || md.file_type().is_dir() {
return true;
}
}
} else {
// We do not end with a slash; it does not have to be a directory.
let dir_name = wdirname(&abs_path);
let filename_fragment = wbasename(&abs_path);
let dir_name = wdirname(abs_path);
let filename_fragment = wbasename(abs_path);
if dir_name == "/" && filename_fragment == "/" {
// cd ///.... No autosuggestion.
return true;
@@ -351,7 +358,7 @@ pub fn is_potential_path(
// Given a string, return whether it prefixes a path that we could cd into. Return that path in
// out_path. Expects path to be unescaped.
pub fn is_potential_cd_path(
fn is_potential_cd_path(
path: &wstr,
at_cursor: bool,
working_directory: &wstr,
@@ -359,27 +366,36 @@ pub fn is_potential_cd_path(
mut flags: PathFlags,
) -> bool {
let mut directories = vec![];
let mut storage = vec![];
if string_prefixes_string(L!("./"), path) {
// Ignore the CDPATH in this case; just use the working directory.
directories.push(working_directory.to_owned());
directories.push(working_directory);
} else {
// Get the CDPATH.
let cdpath = ctx.vars().get_unless_empty(L!("CDPATH"));
let mut pathsv = match cdpath {
None => vec![L!(".").to_owned()],
Some(cdpath) => cdpath.as_list().to_vec(),
};
// The current $PWD is always valid.
pathsv.push(L!(".").to_owned());
for mut next_path in pathsv {
if next_path.is_empty() {
next_path = L!(".").to_owned();
}
fn add_path(storage: &mut Vec<WString>, working_directory: &wstr, next_path: &wstr) {
// Ensure that we use the working directory for relative cdpaths like ".".
directories.push(path_apply_working_directory(&next_path, working_directory));
storage.push(path_apply_working_directory(next_path, working_directory));
}
// Get the CDPATH.
let mut have_curdir = false;
if let Some(cdpath) = ctx.vars().get_unless_empty(L!("CDPATH")) {
for next_path in cdpath.as_list() {
let mut next_path = next_path.as_utfstr();
if next_path.is_empty() {
next_path = L!(".");
}
if next_path == L!(".") {
have_curdir = true;
}
add_path(&mut storage, working_directory, next_path);
}
}
// The current $PWD is always valid.
if !have_curdir {
add_path(&mut storage, working_directory, L!("."));
}
directories = storage.iter().map(|s| s.as_utfstr()).collect();
}
// Call is_potential_path with all of these directories.
@@ -394,7 +410,7 @@ pub fn is_potential_cd_path(
/// Returns:
/// false: the filesystem is not case insensitive
/// true: the file system is case insensitive
pub type CaseSensitivityCache = HashMap<WString, bool>;
type CaseSensitivityCache = HashMap<WString, bool>;
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn fs_is_case_insensitive(
@@ -414,7 +430,7 @@ fn fs_is_case_insensitive(
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub fn fs_is_case_insensitive(
fn fs_is_case_insensitive(
_path: &wstr,
_fd: RawFd,
_case_sensitivity_cache: &mut CaseSensitivityCache,
@@ -453,17 +469,11 @@ fn op_context() -> OperationContext<'static> {
OperationContext::empty()
}
fn file_tester<'a>(
ctx: &'a mut OperationContext<'static>,
tempdir: &TempDir,
) -> FileTester<'static, 'a> {
FileTester::new(osstr2wcstring(tempdir.path()), ctx)
}
#[test]
fn test_ispath() {
let (tempdir, ctx) = (temp_dir(), &mut op_context());
let tester = file_tester(ctx, &tempdir);
let wd = osstr2wcstring(tempdir.path());
let tester = FileTester::new(&wd, ctx);
let file_path = filepath(&tempdir, "file.txt");
File::create(file_path).unwrap();
@@ -512,7 +522,8 @@ fn test_ispath() {
#[test]
fn test_iscdpath() {
let (tempdir, ctx) = (temp_dir(), &mut op_context());
let mut tester = file_tester(ctx, &tempdir);
let wd = osstr2wcstring(tempdir.path());
let mut tester = FileTester::new(&wd, ctx);
// Note cd (unlike file paths) should report IsErr for invalid cd paths,
// rather than IsFile(false).
@@ -543,7 +554,8 @@ fn test_iscdpath() {
fn test_redirections() {
// Note we use is_ok and is_err since we don't care about the IsFile part.
let (tempdir, ctx) = (temp_dir(), &mut op_context());
let mut tester = file_tester(ctx, &tempdir);
let wd = osstr2wcstring(tempdir.path());
let mut tester = FileTester::new(&wd, ctx);
let file_path = filepath(&tempdir, "file.txt");
File::create(&file_path).unwrap();
@@ -738,8 +750,8 @@ fn test_is_potential_path() {
std::fs::write("test/is_potential_path_test/aardvark", []).unwrap();
std::fs::write("test/is_potential_path_test/gamma", []).unwrap();
let wd = L!("test/is_potential_path_test/").to_owned();
let wds = [L!(".").to_owned(), wd];
let wd = L!("test/is_potential_path_test/");
let wds = [L!("."), wd];
let vars = EnvStack::new();
let ctx = OperationContext::background(&vars, EXPANSION_LIMIT_DEFAULT);

View File

@@ -101,7 +101,7 @@ pub fn highlight_shell<'src, 'ctx>(
cursor: Option<usize>,
) {
let working_directory = ctx.vars().get_pwd_slash();
let mut highlighter = Highlighter::new(buff, cursor, ctx, working_directory, io_ok);
let mut highlighter = Highlighter::new(buff, cursor, ctx, &working_directory, io_ok);
*color = highlighter.highlight();
}
@@ -228,12 +228,21 @@ pub(crate) fn parse_text_face_for_highlight(var: &EnvVar) -> Option<TextFace> {
})
}
fn command_is_valid(
#[derive(Clone, Copy, PartialEq, Eq)]
enum CommandKind {
Builtin,
Function,
Plain,
}
fn command_kind(
cmd: &wstr,
decoration: StatementDecoration,
working_directory: &wstr,
vars: &dyn Environment,
) -> bool {
) -> Option<CommandKind> {
use CommandKind::*;
// Determine which types we check, based on the decoration.
let mut builtin_ok = true;
let mut function_ok = true;
@@ -257,36 +266,32 @@ fn command_is_valid(
implicit_cd_ok = false;
}
// Check them.
let mut is_valid = false;
// Builtins
if !is_valid && builtin_ok {
is_valid = builtin_exists(cmd);
if builtin_ok && builtin_exists(cmd) {
return Some(Builtin);
}
// Functions
if !is_valid && function_ok {
is_valid = function::exists_no_autoload(cmd);
if function_ok && function::exists_no_autoload(cmd) {
return Some(Function);
}
// Abbreviations
if !is_valid && abbreviation_ok {
is_valid = with_abbrs(|set| set.has_match(cmd, abbrs::Position::Command, L!("")));
if abbreviation_ok && with_abbrs(|set| set.has_match(cmd, abbrs::Position::Command, L!(""))) {
return Some(Plain);
}
// Regular commands
if !is_valid && command_ok {
is_valid = path_get_path(cmd, vars).is_some();
if command_ok && path_get_path(cmd, vars).is_some() {
return Some(Plain);
}
// Implicit cd
if !is_valid && implicit_cd_ok {
is_valid = path_as_implicit_cd(cmd, working_directory, vars).is_some();
if implicit_cd_ok && path_as_implicit_cd(cmd, working_directory, vars).is_some() {
return Some(Plain);
}
// Return what we got.
is_valid
None
}
fn has_expand_reserved(s: &wstr) -> bool {
@@ -480,7 +485,9 @@ fn color_string_internal(buffstr: &wstr, base_color: HighlightSpec, colors: &mut
[
HighlightSpec::with_fg(HighlightRole::Param),
HighlightSpec::with_fg(HighlightRole::Option),
HighlightSpec::with_fg(HighlightRole::Command)
HighlightSpec::with_fg(HighlightRole::Command),
HighlightSpec::with_fg(HighlightRole::Builtin),
HighlightSpec::with_fg(HighlightRole::Function),
]
.contains(&base_color),
"Unexpected base color"
@@ -695,7 +702,7 @@ enum Mode {
pub type ColorArray = Vec<HighlightSpec>;
/// Syntax highlighter helper.
struct Highlighter<'src, 'ctx> {
struct Highlighter<'src, 'wd, 'ctx> {
// The string we're highlighting. Note this is a reference member variable (to avoid copying)!
buff: &'src wstr,
// The position of the cursor within the string.
@@ -703,9 +710,9 @@ struct Highlighter<'src, 'ctx> {
// Whether it's OK to do I/O.
io_ok: bool,
// Working directory.
working_directory: WString,
working_directory: &'wd wstr,
// Our component for testing strings for being potential file paths.
file_tester: FileTester<'src, 'ctx>,
file_tester: FileTester<'src, 'wd, 'ctx>,
// The resulting colors.
color_array: ColorArray,
// A stack of variables that the current commandline probably defines. We mark redirections
@@ -714,15 +721,15 @@ struct Highlighter<'src, 'ctx> {
done: bool,
}
impl<'src, 'ctx> Highlighter<'src, 'ctx> {
impl<'src, 'wd, 'ctx> Highlighter<'src, 'wd, 'ctx> {
pub fn new(
buff: &'src wstr,
cursor: Option<usize>,
ctx: &'ctx mut OperationContext<'src>,
working_directory: WString,
working_directory: &'wd wstr,
can_do_io: bool,
) -> Self {
let file_tester = FileTester::new(working_directory.clone(), ctx);
let file_tester = FileTester::new(working_directory, ctx);
Self {
buff,
cursor,
@@ -801,13 +808,19 @@ fn io_still_ok(&self) -> bool {
}
// Color a command.
fn color_command(&mut self, node: &ast::String_) {
fn color_command(&mut self, node: &ast::String_, cmd_kind: CommandKind) {
let source_range = node.source_range();
let cmd_str = self.get_source(source_range);
let role = match cmd_kind {
CommandKind::Builtin => HighlightRole::Builtin,
CommandKind::Function => HighlightRole::Function,
CommandKind::Plain => HighlightRole::Command,
};
color_string_internal(
cmd_str,
HighlightSpec::with_fg(HighlightRole::Command),
HighlightSpec::with_fg(role),
&mut self.color_array[source_range.as_usize()],
);
}
@@ -861,7 +874,7 @@ fn color_as_argument(&mut self, node: &dyn ast::Node, options_allowed: bool /* =
cmdsub_contents,
arg_cursor,
self.file_tester.ctx,
self.working_directory.clone(),
self.working_directory,
self.io_still_ok(),
);
let subcolors = cmdsub_highlighter.highlight();
@@ -1049,12 +1062,12 @@ fn visit_decorated_statement(&mut self, stmt: &DecoratedStatement) {
let cmd = stmt.command.source(self.buff);
let mut expanded_cmd = WString::new();
let mut is_valid_cmd = false;
let mut cmd_kind = None;
if !self.io_still_ok() {
// We cannot check if the command is invalid, so just assume it's valid.
is_valid_cmd = true;
cmd_kind = Some(CommandKind::Plain);
} else if variable_assignment_equals_pos(cmd).is_some() {
is_valid_cmd = true;
cmd_kind = Some(CommandKind::Plain);
} else {
// Check to see if the command is valid.
// Try expanding it. If we cannot, it's an error.
@@ -1063,10 +1076,10 @@ fn visit_decorated_statement(&mut self, stmt: &DecoratedStatement) {
{
expanded_cmd = expanded;
if !has_expand_reserved(&expanded_cmd) {
is_valid_cmd = command_is_valid(
cmd_kind = command_kind(
&expanded_cmd,
stmt.decoration(),
&self.working_directory,
self.working_directory,
self.file_tester.ctx.vars(),
);
}
@@ -1074,8 +1087,8 @@ fn visit_decorated_statement(&mut self, stmt: &DecoratedStatement) {
}
// Color our statement.
if is_valid_cmd {
self.color_command(&stmt.command);
if let Some(cmd_kind) = cmd_kind {
self.color_command(&stmt.command, cmd_kind);
} else {
self.color_node(&stmt.command, HighlightSpec::with_fg(HighlightRole::Error));
}
@@ -1161,7 +1174,7 @@ fn contains_pending_variable(pending_variables: &[&wstr], haystack: &wstr) -> bo
false
}
impl<'src, 'ctx, 'a> NodeVisitor<'a> for Highlighter<'src, 'ctx> {
impl<'src, 'wd, 'ctx, 'a> NodeVisitor<'a> for Highlighter<'src, 'wd, 'ctx> {
fn visit(&mut self, node: &'a dyn Node) {
if let Some(keyword) = node.as_keyword() {
return self.visit_keyword(keyword);
@@ -1206,6 +1219,8 @@ fn get_highlight_var_name(role: HighlightRole) -> &'static wstr {
HighlightRole::Normal => L!("fish_color_normal"),
HighlightRole::Error => L!("fish_color_error"),
HighlightRole::Command => L!("fish_color_command"),
HighlightRole::Builtin => L!("fish_color_builtin"),
HighlightRole::Function => L!("fish_color_function"),
HighlightRole::Keyword => L!("fish_color_keyword"),
HighlightRole::StatementTerminator => L!("fish_color_end"),
HighlightRole::Param => L!("fish_color_param"),
@@ -1256,7 +1271,9 @@ fn get_fallback(role: HighlightRole) -> HighlightRole {
| HighlightRole::PagerPrefix
| HighlightRole::PagerCompletion
| HighlightRole::PagerDescription => HighlightRole::Normal,
HighlightRole::Keyword => HighlightRole::Command,
HighlightRole::Builtin | HighlightRole::Function | HighlightRole::Keyword => {
HighlightRole::Command
}
HighlightRole::Option => HighlightRole::Param,
HighlightRole::PagerSecondaryBackground => HighlightRole::PagerBackground,
HighlightRole::PagerSecondaryPrefix | HighlightRole::PagerSelectedPrefix => {
@@ -1279,8 +1296,10 @@ fn get_fallback(role: HighlightRole) -> HighlightRole {
pub enum HighlightRole {
#[default]
Normal, // normal text
Error, // error
Command, // command
Error, // error
Command, // command
Builtin, // builtin command
Function, // user-defined function
Keyword,
StatementTerminator, // process separator
Param, // command parameter (argument)
@@ -1451,7 +1470,7 @@ macro_rules! validate {
});
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("./foo", param_valid_path),
("&", fg(HighlightRole::StatementTerminator)),
);
@@ -1465,11 +1484,11 @@ macro_rules! validate {
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("foo&bar", fg(HighlightRole::Param)),
("foo", fg(HighlightRole::Param), ns),
("&", fg(HighlightRole::StatementTerminator)),
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("&>", fg(HighlightRole::Redirection)),
);
@@ -1477,7 +1496,7 @@ macro_rules! validate {
("if command", fg(HighlightRole::Keyword)),
("ls", fg(HighlightRole::Command)),
("; ", fg(HighlightRole::StatementTerminator)),
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("abc", fg(HighlightRole::Param)),
("; ", fg(HighlightRole::StatementTerminator)),
("/bin/definitely_not_a_command", fg(HighlightRole::Error)),
@@ -1487,52 +1506,52 @@ macro_rules! validate {
validate!(
("if", fg(HighlightRole::Keyword)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
(";", fg(HighlightRole::StatementTerminator)),
("else", fg(HighlightRole::Keyword)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
(";", fg(HighlightRole::StatementTerminator)),
("end", fg(HighlightRole::Keyword)),
);
// Verify that cd shows errors for non-directories.
validate!(
("cd", fg(HighlightRole::Command)),
("cd", fg(HighlightRole::Builtin)),
("dir", param_valid_path),
);
validate!(
("cd", fg(HighlightRole::Command)),
("cd", fg(HighlightRole::Builtin)),
("foo", fg(HighlightRole::Error)),
);
validate!(
("cd", fg(HighlightRole::Command)),
("cd", fg(HighlightRole::Builtin)),
("--help", fg(HighlightRole::Option)),
("-h", fg(HighlightRole::Option)),
("definitely_not_a_directory", fg(HighlightRole::Error)),
);
validate!(
("cd", fg(HighlightRole::Command)),
("cd", fg(HighlightRole::Builtin)),
("--logical", fg(HighlightRole::Option)),
("still_definitely_not_a_directory", fg(HighlightRole::Error)),
);
validate!(
("cd", fg(HighlightRole::Command)),
("cd", fg(HighlightRole::Builtin)),
("dir-in-cdpath", param_valid_path),
);
// Command substitutions.
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("param1", fg(HighlightRole::Param)),
("-l", fg(HighlightRole::Option)),
("--", fg(HighlightRole::Option)),
("-l", fg(HighlightRole::Param)),
("(", fg(HighlightRole::Operat)),
("ls", fg(HighlightRole::Command)),
("ls", fg(HighlightRole::Function)),
("-l", fg(HighlightRole::Option)),
("--", fg(HighlightRole::Option)),
("-l", fg(HighlightRole::Param)),
@@ -1542,33 +1561,33 @@ macro_rules! validate {
("cat", fg(HighlightRole::Command)),
);
validate!(
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
("$(", fg(HighlightRole::Operat)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
(")", fg(HighlightRole::Operat)),
);
validate!(
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
("\"before", fg(HighlightRole::Quote)),
("$(", fg(HighlightRole::Operat)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
("param1", fg(HighlightRole::Param)),
(")", fg(HighlightRole::Operat)),
("after\"", fg(HighlightRole::Quote)),
("param2", fg(HighlightRole::Param)),
);
validate!(
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
("\"", fg(HighlightRole::Error)),
("unclosed quote", fg(HighlightRole::Quote)),
("$(", fg(HighlightRole::Operat)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
(")", fg(HighlightRole::Operat)),
);
// Redirections substitutions.
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("param1", fg(HighlightRole::Param)),
// Input redirection.
("<", fg(HighlightRole::Redirection)),
@@ -1596,7 +1615,7 @@ macro_rules! validate {
// Output redirection containing a command substitution.
("4>", fg(HighlightRole::Redirection)),
("(", fg(HighlightRole::Operat)),
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("test/somewhere", fg(HighlightRole::Param)),
(")", fg(HighlightRole::Operat)),
// Just another param.
@@ -1604,7 +1623,7 @@ macro_rules! validate {
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
(">", fg(HighlightRole::Redirection)),
("-no-such-file", fg(HighlightRole::Redirection)),
(">", fg(HighlightRole::Redirection)),
@@ -1618,7 +1637,7 @@ macro_rules! validate {
("set-by-for-1", fg(HighlightRole::Param)),
("set-by-for-2", fg(HighlightRole::Param)),
(";", fg(HighlightRole::StatementTerminator)),
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
(">", fg(HighlightRole::Redirection)),
("$x", fg(HighlightRole::Redirection)),
(";", fg(HighlightRole::StatementTerminator)),
@@ -1626,11 +1645,11 @@ macro_rules! validate {
);
validate!(
("set", fg(HighlightRole::Command)),
("set", fg(HighlightRole::Builtin)),
("x", fg(HighlightRole::Param)),
("set-by-set", fg(HighlightRole::Param)),
(";", fg(HighlightRole::StatementTerminator)),
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
(">", fg(HighlightRole::Redirection)),
("$x", fg(HighlightRole::Redirection)),
("2>", fg(HighlightRole::Redirection)),
@@ -1643,7 +1662,7 @@ macro_rules! validate {
("x", fg(HighlightRole::Param), ns),
("=", fg(HighlightRole::Operat), ns),
("set-by-variable-override", fg(HighlightRole::Param), ns),
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
(">", fg(HighlightRole::Redirection)),
("$x", fg(HighlightRole::Redirection)),
);
@@ -1656,21 +1675,21 @@ macro_rules! validate {
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("'", fg(HighlightRole::Error)),
("single_quote", fg(HighlightRole::Quote)),
("$stuff", fg(HighlightRole::Quote)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("\"", fg(HighlightRole::Error)),
("double_quote", fg(HighlightRole::Quote)),
("$stuff", fg(HighlightRole::Operat)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("$foo", fg(HighlightRole::Operat)),
("\"", fg(HighlightRole::Quote)),
("$bar", fg(HighlightRole::Operat)),
@@ -1690,7 +1709,7 @@ macro_rules! validate {
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("$$foo[", fg(HighlightRole::Operat)),
("1", fg(HighlightRole::Param)),
("][", fg(HighlightRole::Operat)),
@@ -1717,34 +1736,34 @@ macro_rules! validate {
validate!(
("if", fg(HighlightRole::Keyword)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
("&&", fg(HighlightRole::Operat)),
("false", fg(HighlightRole::Command)),
("false", fg(HighlightRole::Builtin)),
(";", fg(HighlightRole::StatementTerminator)),
("or", fg(HighlightRole::Operat)),
("false", fg(HighlightRole::Command)),
("false", fg(HighlightRole::Builtin)),
("||", fg(HighlightRole::Operat)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
(";", fg(HighlightRole::StatementTerminator)),
("and", fg(HighlightRole::Operat)),
("not", fg(HighlightRole::Operat)),
("!", fg(HighlightRole::Operat)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
(";", fg(HighlightRole::StatementTerminator)),
("end", fg(HighlightRole::Keyword)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("%self", fg(HighlightRole::Operat)),
("not%self", fg(HighlightRole::Param)),
("self%not", fg(HighlightRole::Param)),
);
validate!(
("false", fg(HighlightRole::Command)),
("false", fg(HighlightRole::Builtin)),
("&|", fg(HighlightRole::StatementTerminator)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
);
validate!(
@@ -1756,25 +1775,25 @@ macro_rules! validate {
("VAL1", fg(HighlightRole::Param), ns),
("VAR", fg(HighlightRole::Param)),
("=", fg(HighlightRole::Operat), ns),
("false", fg(HighlightRole::Command)),
("false", fg(HighlightRole::Builtin)),
("|&", fg(HighlightRole::StatementTerminator)),
("true", fg(HighlightRole::Command)),
("true", fg(HighlightRole::Builtin)),
("stuff", fg(HighlightRole::Param)),
);
validate!(
("echo", fg(HighlightRole::Command)), // (
("echo", fg(HighlightRole::Builtin)), // (
(")", fg(HighlightRole::Error)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("stuff", fg(HighlightRole::Param)),
("# comment", fg(HighlightRole::Comment)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("--", fg(HighlightRole::Option)),
("-s", fg(HighlightRole::Param)),
);
@@ -1793,7 +1812,7 @@ macro_rules! validate {
// Highlighting works across escaped line breaks (#8444).
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("$FISH_\\\n", fg(HighlightRole::Operat)),
("VERSION", fg(HighlightRole::Operat), ns),
);
@@ -1824,15 +1843,15 @@ macro_rules! validate {
validate!(("\"$EMPTY_VARIABLE\"", fg(HighlightRole::Error)));
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("\\UFDFD", fg(HighlightRole::Escape)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("\\U10FFFF", fg(HighlightRole::Escape)),
);
validate!(
("echo", fg(HighlightRole::Command)),
("echo", fg(HighlightRole::Builtin)),
("\\U110000", fg(HighlightRole::Error)),
);

View File

@@ -1,5 +1,4 @@
mod file_tester;
#[allow(clippy::module_inception)]
mod highlight;
pub use file_tester::is_potential_path;
pub use highlight::*;

View File

@@ -1331,8 +1331,8 @@ pub fn detect_errors_in_argument(
if src.len() == 2
&& src[0] == '\\'
&& (src[1] == 'c'
|| src[1].to_lowercase().eq(['u'])
|| src[1].to_lowercase().eq(['x']))
|| src[1].eq_ignore_ascii_case(&'u')
|| src[1].eq_ignore_ascii_case(&'x'))
{
append_syntax_error!(
out_errors,

View File

@@ -535,7 +535,7 @@ fn try_transfer(jg: &JobGroup) -> bool {
flogf!(
warning,
"Could not send job %d ('%s') with pgid %d to foreground",
jg.job_id.to_wstring(),
jg.job_id,
jg.command,
pgid
);

View File

@@ -229,6 +229,8 @@ contains -i string a b c d; or echo nothing
#CHECK: nothing
contains -i -- string a b c string d
#CHECK: 4
contains -i a -q -- a b c
#CHECK: 3
contains -i -- -- a b c; or echo nothing
#CHECK: nothing
contains -i -- -- a b c -- v

View File

@@ -153,10 +153,11 @@ end
complete -c repeated_short_options -f -s h
complete -c repeated_short_options -f -s v
complete -c repeated_short_options -f -s x
complete -C'repeated_short_options -xx' | count
# CHECK: 0
complete -C'repeated_short_options -xxh' | count
# CHECK: 0
complete -C'repeated_short_options -xx'
# CHECK: -xxh
# CHECK: -xxv
complete -C'repeated_short_options -xxh'
# CHECK: -xxhv
complete -C'repeated_short_options -x'
# CHECK: -xh
# CHECK: -xv
@@ -670,21 +671,10 @@ abbr cat cat
complete -C ca | string match -r '^cat(?:\t.*)?$'
# CHECK: cat{{\t}}Abbreviation: cat
# anywhere-position abbrs complete in non-command position
abbr --position anywhere __test_pgr '| grep -i'
complete -C'echo __test_pgr' | string match -r '^__test_pgr.*'
# CHECK: __test_pgr{{\t}}Abbreviation: | grep -i
# anywhere-position abbrs still complete in command position (regression guard)
complete -C__test_pgr | string match -r '^__test_pgr.*'
# CHECK: __test_pgr{{\t}}Abbreviation: | grep -i
# command-position-only abbrs do NOT complete in non-command position
abbr --position command __test_cmd_only 'git status'
complete -C'echo __test_cmd' | string match -rq '^__test_cmd'
echo $status
# CHECK: 1
abbr --erase __test_pgr __test_cmd_only
touch somefile
abbr --position anywhere gr '| grep -i'
complete -C'rm '
# CHECK: somefile
complete complete-list -xa '(__fish_complete_list , "seq 2")'
complete -C "complete-list 1,"

View File

@@ -1,4 +1,4 @@
#RUN: %fish %s
#RUN: fish=%fish %fish %s
#REQUIRES: command -v tmux
isolated-tmux-start
@@ -18,7 +18,26 @@ isolated-tmux capture-pane -p
# (I've seen this print " world", I guess it depends on tmux version and how big it thinks the terminal is)
# CHECK: {{.*}} world
isolated-tmux send-keys C-u C-l "sleep 3 | cat &" Enter "bg %1" Enter
isolated-tmux send-keys C-u C-l "sleep 1 | cat &" Enter "bg %1" Enter
tmux-sleep
isolated-tmux capture-pane -p | string match '*to background*'
# CHECK: Send job 1 'sleep 3 | cat &' to background
# CHECK: Send job 1 'sleep 1 | cat &' to background
sleep 1.5
isolated-tmux send-keys C-l '
status job-control full
for x in (seq 10)
sleep 0.0001 &
fg
end
'
tmux-sleep
isolated-tmux capture-pane -p |
string match -rv '^fg: There are no suitable jobs' |
string match -rv '^killpg\(\d+, SIGCONT\): No such process$' |
string match -rv '^Send job \d+ \(sleep 0.0001 &\) to foreground$' |
string match -rv '^warning: Could not send job \d+ \(\'sleep 0.0001 &\'\) with pgid \d+ to foreground' |
string match -rv '^tcsetpgrp: No such process'
# CHECK: prompt 3>