Compare commits

...

9 Commits

Author SHA1 Message Date
Nahor
d2653b7cac tmux-set.fish: fix spurious CI failure
Part of #12556
2026-04-07 17:49:57 +08:00
Johannes Altmanninger
cf16949ce7 contrib/debian/control: remove insufficient mdoc dependency
On Debian, mandoc provides "/usr/bin/mman", not "/usr/bin/man", so that
package alone is not enough.  Users that want to use mandoc could use a
package that "Provides: man", for example by creating symlink to "mman".

See https://github.com/fish-shell/fish-shell/issues/12596#issuecomment-4188332803
2026-04-07 17:49:57 +08:00
Daniel Rainer
85311546de refactor: extract fish-feature-flags crate
Another step in splitting up the main library crate.

Note that this change requires removing the `#[cfg(test)]` annotations
around the `LOCAL_OVERRIDE_STACK` code, because otherwise the code would
be removed in test builds for other packages, making the `#[cfg(test)]`
functions unusable from other packages, and functions with such feature
gates in their body would have the code guarded by these gates removed
in test builds for tests in other packages.

Closes #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
c44aa32a15 cleanup: remove syntactic dependency on main crate
This is done in preparation for extracting this file into its own crate.

Part of #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
65bc9b9e3e refactor: stop aliasing feature_test function
Having a public function named `test` is quite unspecific. Exporting it
both as `test` and `feature_test` results in inconsistent usage. Fix
this by renaming the function to `feature_test` and removing the alias.

Part of #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
8125f78a84 refactor: use override stack for feature tests
Several features of fish can be toggled at runtime (in practice at
startup). To keep track of the active features, `FEATURES`, an array of
`AtomicBool` is used. This can safely be shared across threads without
requiring locks.

Some of our tests override certain features to test behavior with a
specific value of the feature. Prior to this commit, they did this by
using thread-local versions of `FEATURES` instead of the process-wide
version used in non-test builds. This approach has two downsides:
- It does not allow nested overrides.
- It prevents using the code across package boundaries.
The former is a fairly minor issue, since I don't think we need nested
overrides. The latter prevents splitting up our large library crate,
since `#[cfg(test)]`-guarded code can only be used within a single
package.

To resolve these issues, a new approach to feature overrides in
tests is introduced in this commit: Instead of having a thread-local
version of `FEATURES`, all code, whether test or not, uses the
process-wide `FEATURES`. For non-test code, there is no change. For test
code, `FEATURES` is now also used. To override features in tests, a new
`with_overridden_feature` function is added, which replaces
`scoped_test` and `set`. It works by maintaining a thread-local stack of
feature overrides (`LOCAL_OVERRIDE_STACK`). The overridden `FeatureFlag`
and its new value are pushed to the stack, then the code for which the
override should be active is run, and finally the stack is popped again.
Feature tests now have to scan the stack for the first appearance of the
`FeatureFlag`, or use the value in `FEATURES` if the stack does not
contain the `FeatureFlag`. In most cases, the stack will be empty or
contain very few elements, so scanning it should not take long. For now,
it's only active in test code, so non-test code is unaffected. The plan
is to change this when the feature flag code is extracted from the main
library crate. This would slightly slow down feature tests in non-test
code, but there the stack will always be empty, since we only override
features in tests.

Part of #12494
2026-04-07 17:49:57 +08:00
Daniel Rainer
f0f48b4859 cleanup: stop needlessly exporting struct fields
Part of #12494
2026-04-07 17:49:57 +08:00
David Adam
a009f87630 build_tools: add sh script to build linux packages 2026-04-07 10:57:41 +08:00
David Adam
edb66d4d4e remove dput_cf_gen, not actually helpful 2026-04-07 10:57:41 +08:00
26 changed files with 571 additions and 533 deletions

8
Cargo.lock generated
View File

@@ -272,6 +272,7 @@ dependencies = [
"fish-color",
"fish-common",
"fish-fallback",
"fish-feature-flags",
"fish-gettext",
"fish-gettext-extraction",
"fish-gettext-mo-file-parser",
@@ -348,6 +349,13 @@ dependencies = [
"widestring",
]
[[package]]
name = "fish-feature-flags"
version = "0.0.0"
dependencies = [
"fish-widestring",
]
[[package]]
name = "fish-gettext"
version = "0.0.0"

View File

@@ -23,6 +23,7 @@ fish-build-man-pages = { path = "crates/build-man-pages" }
fish-color = { path = "crates/color" }
fish-common = { path = "crates/common" }
fish-fallback = { path = "crates/fallback" }
fish-feature-flags = { path = "crates/feature-flags" }
fish-gettext = { path = "crates/gettext" }
fish-gettext-extraction = { path = "crates/gettext-extraction" }
fish-gettext-maps = { path = "crates/gettext-maps" }
@@ -108,6 +109,7 @@ fish-build-man-pages = { workspace = true, optional = true }
fish-color.workspace = true
fish-common.workspace = true
fish-fallback.workspace = true
fish-feature-flags.workspace = true
fish-gettext = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true }
fish-printf.workspace = true

View File

@@ -1,28 +0,0 @@
#!/bin/sh
# Script to generate the dput.cf for a set of Ubuntu series, prints the filename
# Arguments are the PPA followed by the series names
set -e
outfile=$(mktemp --tmpdir dput.XXXXX.cf)
[ $# -lt 2 ] &&
echo "$0: at least two arguments (a PPA and at least one series) are required" >&2 &&
exit 1
ppa=$1
shift
for series in "$@"; do
cat >> "$outfile" <<EOF
[fish-$ppa-$series]
fqdn = ppa.launchpad.net
method = ftp
login = anonymous
incoming = ~fish-shell/$ppa/ubuntu/$series
EOF
done
echo "$outfile"

View File

@@ -0,0 +1,83 @@
#!/bin/sh
# This script takes a source tarball (from build_tools/make_tarball.sh) and a vendor tarball (from
# build_tools/make_vendor_tarball.sh, generated if not present), and produces:
# * Appropriately-named symlinks to look like a Debian package
# * Debian .changes and .dsc files with plain names ($version-1) and supported Ubuntu prefixes
# ($version-1~somedistro)
# * An RPM spec file
# By default, input and output files go in ~/fish_built, but this can be controlled with the
# FISH_ARTEFACT_PATH environment variable.
{
set -e
version=$1
[ -n "$version" ] || { echo "Version number required as argument" >&2; exit 1; }
[ -n "$DEB_SIGN_KEYID$DEB_SIGN_KEYFILE" ] ||
echo "Warning: neither DEB_SIGN_KEYID or DEB_SIGN_KEYFILE environment variables are set; you
will need a signing key for the author of the most recent debian/changelog entry." >&2
workpath=${FISH_ARTEFACT_PATH:-~/fish_built}
source_tarball="$workpath"/fish-"$version".tar.xz
vendor_tarball="$workpath"/fish-"$version"-vendor.tar.xz
[ -e "$source_tarball" ] || { echo "Missing source tarball, expected at $source_tarball" >&2; exit 1; }
cd "$workpath"
# Unpack the sources
tar xf "$source_tarball"
sourcepath="$workpath"/fish-"$version"
# Generate the vendor tarball if it is not already present
[ -e "$vendor_tarball" ] || (cd "$sourcepath"; build_tools/make_vendor_tarball.sh;)
# This step requires network access, so do it early in case it fails
# sh has no real array support
ubuntu_versions=$(uv run --script "$sourcepath"/build_tools/supported_ubuntu_versions.py)
# Write the specfile
[ -e "$workpath"/fish.spec ] && { echo "Cowardly refusing to overwite an existing fish.spec" >&2;
exit 1; }
rpmversion=$(echo "$version" |sed -e 's/-/+/' -e 's/-/./g')
sed -e "s/@version@/$version/g" -e "s/@rpmversion@/$rpmversion/g" \
< "$sourcepath"/fish.spec.in > "$workpath"/fish.spec
# Make the symlinks for Debian
ln -s "$source_tarball" "$workpath"/fish_"$version".orig.tar.xz
ln -s "$vendor_tarball" "$workpath"/fish_"$version".orig-cargo-vendor.tar.xz
# Set up the Debian source tree
cd "$sourcepath"
mkdir cargo-vendor
tar -C cargo-vendor -x -f "$vendor_tarball"
cp -r contrib/debian debian
# The vendor tarball contains a new .cargo/config.toml, which has the
# vendoring overrides appended to it. dpkg-source will add this as a
# patch using the flags in debian/
cp cargo-vendor/.cargo/config.toml .cargo/config.toml
# Update the Debian changelog
# The release scripts do this for release builds - skip if it has already been done
if head -n1 debian/changelog | grep --invert-match --quiet --fixed-strings "$version"; then
debchange --newversion "$version-1" --distribution unstable "Snapshot build"
fi
# Builds the "plain" Debian package
# debuild runs lintian, which takes ten minutes to run over the vendor directories
# just use dpkg-buildpackage directly
dpkg-buildpackage --build=source -d
# Build the Ubuntu packages
# deb-reversion does not work on source packages, so do the whole thing ourselves
for series in $ubuntu_versions; do
sed -i -e "1 s/$version-1)/$version-1~$series)/" -e "1 s/unstable/$series/" debian/changelog
dpkg-buildpackage --build=source -d
sed -i -e "1 s/$version-1~$series)/$version-1)/" -e "1 s/$series/unstable/" debian/changelog
done
}

View File

@@ -13,7 +13,7 @@ Build-Depends: debhelper-compat (= 13),
python3-sphinx,
# Test dependencies
locales-all,
man-db | mandoc | man,
man-db | man,
python3
# 4.6.2 is Debian 12/Ubuntu Noble 24.04; Ubuntu Jammy is 4.6.0.1
Standards-Version: 4.6.2
@@ -27,7 +27,7 @@ Architecture: any
Depends: bsdextrautils,
file,
# for showing built-in help pages
man-db | mandoc | man,
man-db | man,
# for kill
procps,
python3 (>=3.5),

View File

@@ -0,0 +1,13 @@
[package]
name = "fish-feature-flags"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
fish-widestring.workspace = true
[lints]
workspace = true

View File

@@ -1,15 +1,14 @@
//! Flags to enable upcoming features
use crate::prelude::*;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
#[cfg(test)]
use std::cell::RefCell;
use fish_widestring::{L, WExt as _, wstr};
use std::{
cell::RefCell,
sync::atomic::{AtomicBool, Ordering},
};
/// The list of flags.
#[repr(u8)]
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FeatureFlag {
/// Whether ^ is supported for stderr redirection.
StderrNoCaret,
@@ -63,10 +62,10 @@ pub struct FeatureMetadata {
pub description: &'static wstr,
/// Default flag value.
pub default_value: bool,
default_value: bool,
/// Whether the value can still be changed or not.
pub read_only: bool,
read_only: bool,
}
/// The metadata, indexed by flag.
@@ -156,31 +155,26 @@ pub struct FeatureMetadata {
];
thread_local!(
#[cfg(test)]
static LOCAL_FEATURES: RefCell<Option<Features>> = const { RefCell::new(None) };
static LOCAL_OVERRIDE_STACK: RefCell<Vec<(FeatureFlag, bool)>> =
const { RefCell::new(Vec::new()) };
);
/// The singleton shared feature set.
static FEATURES: Features = Features::new();
/// Perform a feature test on the global set of features.
pub fn test(flag: FeatureFlag) -> bool {
#[cfg(test)]
{
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).test(flag))
pub fn feature_test(flag: FeatureFlag) -> bool {
if let Some(value) = LOCAL_OVERRIDE_STACK.with(|stack| {
for &(overridden_feature, value) in stack.borrow().iter().rev() {
if flag == overridden_feature {
return Some(value);
}
}
None
}) {
return value;
}
#[cfg(not(test))]
{
FEATURES.test(flag)
}
}
pub use test as feature_test;
/// Set a flag.
#[cfg(test)]
pub fn set(flag: FeatureFlag, value: bool) {
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).set(flag, value));
FEATURES.test(flag)
}
/// Parses a comma-separated feature-flag string, updating ourselves with the values.
@@ -188,20 +182,7 @@ pub fn set(flag: FeatureFlag, value: bool) {
/// The special group name "all" may be used for those who like to live on the edge.
/// Unknown features are silently ignored.
pub fn set_from_string<'a>(str: impl Into<&'a wstr>) {
let wstr: &wstr = str.into();
#[cfg(test)]
{
LOCAL_FEATURES.with(|fc| {
fc.borrow()
.as_ref()
.unwrap_or(&FEATURES)
.set_from_string(wstr);
});
}
#[cfg(not(test))]
{
FEATURES.set_from_string(wstr);
}
FEATURES.set_from_string(str.into());
}
impl Features {
@@ -270,28 +251,20 @@ fn set_from_string(&self, str: &wstr) {
}
}
#[cfg(test)]
pub fn scoped_test(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
LOCAL_FEATURES.with(|fc| {
assert!(
fc.borrow().is_none(),
"scoped_test() does not support nesting"
);
let f = Features::new();
f.set(flag, value);
*fc.borrow_mut() = Some(f);
/// Run code with a feature overridden.
/// This should only be used in tests.
pub fn with_overridden_feature(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
LOCAL_OVERRIDE_STACK.with(|stack| {
stack.borrow_mut().push((flag, value));
test_fn();
*fc.borrow_mut() = None;
stack.borrow_mut().pop();
});
}
#[cfg(test)]
mod tests {
use super::{FeatureFlag, Features, METADATA, scoped_test, set, test};
use crate::prelude::*;
use super::{FeatureFlag, Features, METADATA, feature_test, with_overridden_feature};
use fish_widestring::L;
#[test]
fn test_feature_flags() {
@@ -317,25 +290,19 @@ fn test_feature_flags() {
}
#[test]
fn test_scoped() {
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(test(FeatureFlag::QuestionMarkNoGlob));
fn test_overridden_feature() {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
});
set(FeatureFlag::QuestionMarkNoGlob, true);
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
assert!(!test(FeatureFlag::QuestionMarkNoGlob));
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
assert!(!feature_test(FeatureFlag::QuestionMarkNoGlob));
});
set(FeatureFlag::QuestionMarkNoGlob, false);
}
#[test]
#[should_panic]
fn test_nested_scopes_not_supported() {
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {});
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
});
});
}
}

View File

@@ -38,7 +38,7 @@
eprintf,
event::{self, Event},
flog::{self, activate_flog_categories_by_pattern, flog, flogf, set_flog_file_fd},
fprintf, function, future_feature_flags as features,
fprintf, function,
history::{self, start_private_mode},
io::IoChain,
locale::set_libc_locales,
@@ -482,10 +482,10 @@ fn throwing_main() -> i32 {
// command line takes precedence).
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
for s in features_var.as_list() {
features::set_from_string(s.as_utfstr());
fish_feature_flags::set_from_string(s.as_utfstr());
}
}
features::set_from_string(opts.features.as_utfstr());
fish_feature_flags::set_from_string(opts.features.as_utfstr());
proc_init();
reader_init(true);

View File

@@ -20,7 +20,6 @@
use crate::env::env_init;
use crate::env::environment::Environment as _;
use crate::expand::INTERNAL_SEPARATOR;
use crate::future_feature_flags;
use crate::global_safety::RelaxedAtomicBool;
use crate::highlight::{HighlightRole, HighlightSpec, colorize, highlight_shell};
use crate::operation_context::OperationContext;
@@ -945,7 +944,7 @@ fn throwing_main() -> i32 {
// Only set these here so you can't set them via the builtin.
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
for s in features_var.as_list() {
future_feature_flags::set_from_string(s.as_utfstr());
fish_feature_flags::set_from_string(s.as_utfstr());
}
}

View File

@@ -15,7 +15,6 @@
builtins::shared::BUILTIN_ERR_UNKNOWN_OPT,
common::{PROGRAM_NAME, get_program_name, osstr2wcstring, shell_modes},
env::{EnvStack, Environment as _, env_init},
future_feature_flags,
input_common::{
CharEvent, ImplicitEvent, InputEventQueue, InputEventQueuer as _, KeyEvent,
QueryResultEvent, match_key_event_to_key,
@@ -295,7 +294,7 @@ fn throwing_main() -> i32 {
reader_init(false);
if let Some(features_var) = EnvStack::globals().get(L!("fish_features")) {
for s in features_var.as_list() {
future_feature_flags::set_from_string(s.as_utfstr());
fish_feature_flags::set_from_string(s.as_utfstr());
}
}

View File

@@ -1,7 +1,6 @@
use super::prelude::*;
use crate::common::{bytes2wcstring, get_program_name, osstr2wcstring, str2wcstring};
use crate::env::config_paths::get_fish_path;
use crate::future_feature_flags::{self as features, feature_test};
use crate::proc::{
JobControl, get_job_control_mode, get_login, is_interactive_session, set_job_control_mode,
};
@@ -9,6 +8,7 @@
use crate::tty_handoff::{TERMINAL_OS_NAME, get_scroll_content_up_capability, xtversion};
use crate::wutil::{Error, waccess, wbasename, wdirname, wrealpath};
use cfg_if::cfg_if;
use fish_feature_flags::{self as features, feature_test};
use fish_util::wcsfilecmp_glob;
use fish_wcstringutil::wcs2bytes;
use nix::unistd::AccessFlags;

View File

@@ -414,9 +414,9 @@ fn report_matches(&mut self, arg: &wstr, streams: &mut IoStreams) {
#[cfg(test)]
mod tests {
use crate::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_INVALID_ARGS};
use crate::future_feature_flags::{FeatureFlag, scoped_test};
use crate::tests::prelude::*;
use crate::validate;
use fish_feature_flags::{FeatureFlag, with_overridden_feature};
#[test]
#[serial]
@@ -487,7 +487,7 @@ fn plain() {
#[serial]
#[rustfmt::skip]
fn test_qmark_noglob_true() {
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
validate!(["string", "match", "a*b?c", "axxb?c"], STATUS_CMD_OK, "axxb?c\n");
validate!(["string", "match", "*?", "a"], STATUS_CMD_ERROR, "");
validate!(["string", "match", "*?", "ab"], STATUS_CMD_ERROR, "");
@@ -515,7 +515,7 @@ fn test_qmark_noglob_true() {
#[serial]
#[rustfmt::skip]
fn test_qmark_glob() {
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
validate!(["string", "match", "a*b?c", "axxbyc"], STATUS_CMD_OK, "axxbyc\n");
validate!(["string", "match", "*?", "a"], STATUS_CMD_OK, "a\n");
validate!(["string", "match", "*?", "ab"], STATUS_CMD_OK, "ab\n");

View File

@@ -3,7 +3,7 @@
use pcre2::utf32::{Regex, RegexBuilder};
use super::*;
use crate::future_feature_flags::{FeatureFlag, feature_test};
use fish_feature_flags::{FeatureFlag, feature_test};
#[derive(Default)]
pub struct Replace<'args> {

View File

@@ -1,6 +1,6 @@
use super::prelude::*;
use crate::future_feature_flags::{FeatureFlag, feature_test};
use crate::should_flog;
use fish_feature_flags::{FeatureFlag, feature_test};
mod test_expressions {
use nix::unistd::{AccessFlags, Gid, Uid};

View File

@@ -4,7 +4,6 @@
BRACE_BEGIN, BRACE_END, BRACE_SEP, BRACE_SPACE, HOME_DIRECTORY, INTERNAL_SEPARATOR,
PROCESS_EXPAND_SELF, PROCESS_EXPAND_SELF_STR, VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE,
};
use crate::future_feature_flags::{FeatureFlag, feature_test};
use crate::global_safety::AtomicRef;
use crate::global_safety::RelaxedAtomicBool;
use crate::key;
@@ -15,6 +14,7 @@
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use crate::wutil::fish_iswalnum;
use fish_fallback::fish_wcwidth;
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_wcstringutil::wcs2bytes;
use fish_widestring::{
ENCODE_DIRECT_END, decode_byte_from_char, encode_byte_to_char, subslice_position,

View File

@@ -14,7 +14,6 @@
use crate::complete::{CompleteFlags, Completion, CompletionList, CompletionReceiver};
use crate::env::{EnvVar, Environment};
use crate::exec::exec_subshell_for_expand;
use crate::future_feature_flags::{FeatureFlag, feature_test};
use crate::history::{History, history_session_id};
use crate::operation_context::OperationContext;
use crate::parse_constants::{ParseError, ParseErrorCode, ParseErrorList, SOURCE_LOCATION_UNKNOWN};
@@ -26,6 +25,7 @@
use crate::wutil::{Options, normalize_path, wcstoi_partial};
use bitflags::bitflags;
use fish_common::{EXPAND_RESERVED_BASE, EXPAND_RESERVED_END};
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_util::wcsfilecmp_glob;
use fish_wcstringutil::{join_strings, trim};
use fish_widestring::char_offset;

View File

@@ -12,7 +12,6 @@
ExpandFlags, ExpandResultCode, PROCESS_EXPAND_SELF_STR, expand_one, expand_to_command_and_args,
};
use crate::function;
use crate::future_feature_flags::{FeatureFlag, feature_test};
use crate::highlight::file_tester::FileTester;
use crate::history::all_paths_are_valid;
use crate::operation_context::OperationContext;
@@ -31,6 +30,7 @@
use crate::tokenizer::{PipeOrRedir, variable_assignment_equals_pos};
use fish_color::Color;
use fish_common::{ASCII_MAX, EXPAND_RESERVED_BASE, EXPAND_RESERVED_END};
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_wcstringutil::string_prefixes_string;
use fish_widestring::{L, WExt as _, WString, wstr};
use std::collections::HashMap;
@@ -1314,12 +1314,12 @@ mod tests {
use super::{HighlightColorResolver, HighlightRole, HighlightSpec, highlight_shell};
use crate::common::ScopeGuard;
use crate::env::{EnvMode, EnvSetMode, EnvVar, EnvVarFlags, Environment as _};
use crate::future_feature_flags::{self, FeatureFlag};
use crate::highlight::parse_text_face_for_highlight;
use crate::operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext};
use crate::prelude::*;
use crate::tests::prelude::*;
use crate::text_face::{ResettableStyle, UnderlineStyle};
use fish_feature_flags::{FeatureFlag, with_overridden_feature};
use libc::PATH_MAX;
// Helper to return a string whose length greatly exceeds PATH_MAX.
@@ -1418,404 +1418,400 @@ macro_rules! validate {
let mut redirection_valid_path = HighlightSpec::with_fg(HighlightRole::redirection);
redirection_valid_path.valid_path = true;
let saved_flag = future_feature_flags::test(FeatureFlag::AmpersandNoBgInToken);
future_feature_flags::set(FeatureFlag::AmpersandNoBgInToken, true);
let _restore_saved_flag = ScopeGuard::new((), |_| {
future_feature_flags::set(FeatureFlag::AmpersandNoBgInToken, saved_flag);
with_overridden_feature(FeatureFlag::AmpersandNoBgInToken, true, || {
let fg = HighlightSpec::with_fg;
// Verify variables and wildcards in commands using /bin/cat.
let vars = parser.vars();
let local_mode = EnvSetMode::new_at_early_startup(EnvMode::LOCAL);
vars.set_one(L!("CDPATH"), local_mode, L!("./cdpath-entry").to_owned());
// NOTE n, nv are suffix of /usr/bin/env
vars.set_one(L!("VARIABLE_IN_COMMAND"), local_mode, L!("n").to_owned());
vars.set_one(L!("VARIABLE_IN_COMMAND2"), local_mode, L!("nv").to_owned());
let _cleanup = ScopeGuard::new((), |_| {
vars.remove(L!("VARIABLE_IN_COMMAND"), EnvSetMode::default());
vars.remove(L!("VARIABLE_IN_COMMAND2"), EnvSetMode::default());
});
validate!(
("echo", fg(HighlightRole::command)),
("./foo", param_valid_path),
("&", fg(HighlightRole::statement_terminator)),
);
validate!(
("command", fg(HighlightRole::keyword)),
("echo", fg(HighlightRole::command)),
("abc", fg(HighlightRole::param)),
("foo", param_valid_path),
("&", fg(HighlightRole::statement_terminator)),
);
validate!(
("echo", fg(HighlightRole::command)),
("foo&bar", fg(HighlightRole::param)),
("foo", fg(HighlightRole::param), ns),
("&", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
("&>", fg(HighlightRole::redirection)),
);
validate!(
("if command", fg(HighlightRole::keyword)),
("ls", fg(HighlightRole::command)),
("; ", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
("abc", fg(HighlightRole::param)),
("; ", fg(HighlightRole::statement_terminator)),
("/bin/definitely_not_a_command", fg(HighlightRole::error)),
("; ", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("if", fg(HighlightRole::keyword)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("else", fg(HighlightRole::keyword)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
// Verify that cd shows errors for non-directories.
validate!(
("cd", fg(HighlightRole::command)),
("dir", param_valid_path),
);
validate!(
("cd", fg(HighlightRole::command)),
("foo", fg(HighlightRole::error)),
);
validate!(
("cd", fg(HighlightRole::command)),
("--help", fg(HighlightRole::option)),
("-h", fg(HighlightRole::option)),
("definitely_not_a_directory", fg(HighlightRole::error)),
);
validate!(
("cd", fg(HighlightRole::command)),
("dir-in-cdpath", param_valid_path),
);
// Command substitutions.
validate!(
("echo", fg(HighlightRole::command)),
("param1", fg(HighlightRole::param)),
("-l", fg(HighlightRole::option)),
("--", fg(HighlightRole::option)),
("-l", fg(HighlightRole::param)),
("(", fg(HighlightRole::operat)),
("ls", fg(HighlightRole::command)),
("-l", fg(HighlightRole::option)),
("--", fg(HighlightRole::option)),
("-l", fg(HighlightRole::param)),
("param2", fg(HighlightRole::param)),
(")", fg(HighlightRole::operat)),
("|", fg(HighlightRole::statement_terminator)),
("cat", fg(HighlightRole::command)),
);
validate!(
("true", fg(HighlightRole::command)),
("$(", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(")", fg(HighlightRole::operat)),
);
validate!(
("true", fg(HighlightRole::command)),
("\"before", fg(HighlightRole::quote)),
("$(", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
("param1", fg(HighlightRole::param)),
(")", fg(HighlightRole::operat)),
("after\"", fg(HighlightRole::quote)),
("param2", fg(HighlightRole::param)),
);
validate!(
("true", fg(HighlightRole::command)),
("\"", fg(HighlightRole::error)),
("unclosed quote", fg(HighlightRole::quote)),
("$(", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(")", fg(HighlightRole::operat)),
);
// Redirections substitutions.
validate!(
("echo", fg(HighlightRole::command)),
("param1", fg(HighlightRole::param)),
// Input redirection.
("<", fg(HighlightRole::redirection)),
("/dev/null", redirection_valid_path),
// Output redirection to a valid fd.
("1>&2", fg(HighlightRole::redirection)),
// Output redirection to an invalid fd.
("2>&", fg(HighlightRole::redirection)),
("LO", fg(HighlightRole::error)),
// Just a param, not a redirection.
("test/blah", fg(HighlightRole::param)),
// Input redirection from directory.
("<", fg(HighlightRole::redirection)),
("test/", fg(HighlightRole::error)),
// Output redirection to an invalid path.
("3>", fg(HighlightRole::redirection)),
("/not/a/valid/path/nope", fg(HighlightRole::error)),
// Output redirection to directory.
("3>", fg(HighlightRole::redirection)),
("test/nope/", fg(HighlightRole::error)),
// Redirections to overflow fd.
("99999999999999999999>&2", fg(HighlightRole::error)),
("2>&", fg(HighlightRole::redirection)),
("99999999999999999999", fg(HighlightRole::error)),
// Output redirection containing a command substitution.
("4>", fg(HighlightRole::redirection)),
("(", fg(HighlightRole::operat)),
("echo", fg(HighlightRole::command)),
("test/somewhere", fg(HighlightRole::param)),
(")", fg(HighlightRole::operat)),
// Just another param.
("param2", fg(HighlightRole::param)),
);
validate!(
("for", fg(HighlightRole::keyword)),
("x", fg(HighlightRole::param)),
("in", fg(HighlightRole::keyword)),
("set-by-for-1", fg(HighlightRole::param)),
("set-by-for-2", fg(HighlightRole::param)),
(";", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
(">", fg(HighlightRole::redirection)),
("$x", fg(HighlightRole::redirection)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("set", fg(HighlightRole::command)),
("x", fg(HighlightRole::param)),
("set-by-set", fg(HighlightRole::param)),
(";", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
(">", fg(HighlightRole::redirection)),
("$x", fg(HighlightRole::redirection)),
("2>", fg(HighlightRole::redirection)),
("$totally_not_x", fg(HighlightRole::error)),
("<", fg(HighlightRole::redirection)),
("$x_but_its_an_impostor", fg(HighlightRole::error)),
);
validate!(
("x", fg(HighlightRole::param), ns),
("=", fg(HighlightRole::operat), ns),
("set-by-variable-override", fg(HighlightRole::param), ns),
("echo", fg(HighlightRole::command)),
(">", fg(HighlightRole::redirection)),
("$x", fg(HighlightRole::redirection)),
);
validate!(
("end", fg(HighlightRole::error)),
(";", fg(HighlightRole::statement_terminator)),
("if", fg(HighlightRole::keyword)),
("end", fg(HighlightRole::error)),
);
validate!(
("echo", fg(HighlightRole::command)),
("'", fg(HighlightRole::error)),
("single_quote", fg(HighlightRole::quote)),
("$stuff", fg(HighlightRole::quote)),
);
validate!(
("echo", fg(HighlightRole::command)),
("\"", fg(HighlightRole::error)),
("double_quote", fg(HighlightRole::quote)),
("$stuff", fg(HighlightRole::operat)),
);
validate!(
("echo", fg(HighlightRole::command)),
("$foo", fg(HighlightRole::operat)),
("\"", fg(HighlightRole::quote)),
("$bar", fg(HighlightRole::operat)),
("\"", fg(HighlightRole::quote)),
("$baz[", fg(HighlightRole::operat)),
("1 2..3", fg(HighlightRole::param)),
("]", fg(HighlightRole::operat)),
);
validate!(
("for", fg(HighlightRole::keyword)),
("i", fg(HighlightRole::param)),
("in", fg(HighlightRole::keyword)),
("1 2 3", fg(HighlightRole::param)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("echo", fg(HighlightRole::command)),
("$$foo[", fg(HighlightRole::operat)),
("1", fg(HighlightRole::param)),
("][", fg(HighlightRole::operat)),
("2", fg(HighlightRole::param)),
("]", fg(HighlightRole::operat)),
("[3]", fg(HighlightRole::param)), // two dollar signs, so last one is not an expansion
);
validate!(
("cat", fg(HighlightRole::command)),
("/dev/null", param_valid_path),
("|", fg(HighlightRole::statement_terminator)),
// This is bogus, but we used to use "less" here and that doesn't have to be installed.
("cat", fg(HighlightRole::command)),
("2>", fg(HighlightRole::redirection)),
);
// Highlight path-prefixes only at the cursor.
validate!(
("cat", fg(HighlightRole::command)),
("/dev/nu", fg(HighlightRole::param)),
("/dev/nu", param_valid_path),
);
validate!(
("if", fg(HighlightRole::keyword)),
("true", fg(HighlightRole::command)),
("&&", fg(HighlightRole::operat)),
("false", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("or", fg(HighlightRole::operat)),
("false", fg(HighlightRole::command)),
("||", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("and", fg(HighlightRole::operat)),
("not", fg(HighlightRole::operat)),
("!", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("echo", fg(HighlightRole::command)),
("%self", fg(HighlightRole::operat)),
("not%self", fg(HighlightRole::param)),
("self%not", fg(HighlightRole::param)),
);
validate!(
("false", fg(HighlightRole::command)),
("&|", fg(HighlightRole::statement_terminator)),
("true", fg(HighlightRole::command)),
);
validate!(
("HOME", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
(".", fg(HighlightRole::param), ns),
("VAR1", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
("VAL1", fg(HighlightRole::param), ns),
("VAR", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
("false", fg(HighlightRole::command)),
("|&", fg(HighlightRole::statement_terminator)),
("true", fg(HighlightRole::command)),
("stuff", fg(HighlightRole::param)),
);
validate!(
("echo", fg(HighlightRole::command)), // (
(")", fg(HighlightRole::error)),
);
validate!(
("echo", fg(HighlightRole::command)),
("stuff", fg(HighlightRole::param)),
("# comment", fg(HighlightRole::comment)),
);
validate!(
("echo", fg(HighlightRole::command)),
("--", fg(HighlightRole::option)),
("-s", fg(HighlightRole::param)),
);
// Overlong paths don't crash (#7837).
let overlong = get_overlong_path();
validate!(
("touch", fg(HighlightRole::command)),
(&overlong, fg(HighlightRole::param)),
);
validate!(
("a", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
);
// Highlighting works across escaped line breaks (#8444).
validate!(
("echo", fg(HighlightRole::command)),
("$FISH_\\\n", fg(HighlightRole::operat)),
("VERSION", fg(HighlightRole::operat), ns),
);
// NOTE: we assume /usr/bin/env exists on the system here
validate!(
("/usr/bin/en", fg(HighlightRole::command), ns),
("*", fg(HighlightRole::operat), ns)
);
validate!(
("/usr/bin/e", fg(HighlightRole::command), ns),
("*", fg(HighlightRole::operat), ns)
);
validate!(
("/usr/bin/e", fg(HighlightRole::command), ns),
("{$VARIABLE_IN_COMMAND}", fg(HighlightRole::operat), ns),
("*", fg(HighlightRole::operat), ns)
);
validate!(
("/usr/bin/e", fg(HighlightRole::command), ns),
("$VARIABLE_IN_COMMAND2", fg(HighlightRole::operat), ns)
);
validate!(("$EMPTY_VARIABLE", fg(HighlightRole::error)));
validate!(("\"$EMPTY_VARIABLE\"", fg(HighlightRole::error)));
validate!(
("echo", fg(HighlightRole::command)),
("\\UFDFD", fg(HighlightRole::escape)),
);
validate!(
("echo", fg(HighlightRole::command)),
("\\U10FFFF", fg(HighlightRole::escape)),
);
validate!(
("echo", fg(HighlightRole::command)),
("\\U110000", fg(HighlightRole::error)),
);
validate!(
(">", fg(HighlightRole::error)),
("echo", fg(HighlightRole::error)),
);
});
let fg = HighlightSpec::with_fg;
// Verify variables and wildcards in commands using /bin/cat.
let vars = parser.vars();
let local_mode = EnvSetMode::new_at_early_startup(EnvMode::LOCAL);
vars.set_one(L!("CDPATH"), local_mode, L!("./cdpath-entry").to_owned());
// NOTE n, nv are suffix of /usr/bin/env
vars.set_one(L!("VARIABLE_IN_COMMAND"), local_mode, L!("n").to_owned());
vars.set_one(L!("VARIABLE_IN_COMMAND2"), local_mode, L!("nv").to_owned());
let _cleanup = ScopeGuard::new((), |_| {
vars.remove(L!("VARIABLE_IN_COMMAND"), EnvSetMode::default());
vars.remove(L!("VARIABLE_IN_COMMAND2"), EnvSetMode::default());
});
validate!(
("echo", fg(HighlightRole::command)),
("./foo", param_valid_path),
("&", fg(HighlightRole::statement_terminator)),
);
validate!(
("command", fg(HighlightRole::keyword)),
("echo", fg(HighlightRole::command)),
("abc", fg(HighlightRole::param)),
("foo", param_valid_path),
("&", fg(HighlightRole::statement_terminator)),
);
validate!(
("echo", fg(HighlightRole::command)),
("foo&bar", fg(HighlightRole::param)),
("foo", fg(HighlightRole::param), ns),
("&", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
("&>", fg(HighlightRole::redirection)),
);
validate!(
("if command", fg(HighlightRole::keyword)),
("ls", fg(HighlightRole::command)),
("; ", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
("abc", fg(HighlightRole::param)),
("; ", fg(HighlightRole::statement_terminator)),
("/bin/definitely_not_a_command", fg(HighlightRole::error)),
("; ", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("if", fg(HighlightRole::keyword)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("else", fg(HighlightRole::keyword)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
// Verify that cd shows errors for non-directories.
validate!(
("cd", fg(HighlightRole::command)),
("dir", param_valid_path),
);
validate!(
("cd", fg(HighlightRole::command)),
("foo", fg(HighlightRole::error)),
);
validate!(
("cd", fg(HighlightRole::command)),
("--help", fg(HighlightRole::option)),
("-h", fg(HighlightRole::option)),
("definitely_not_a_directory", fg(HighlightRole::error)),
);
validate!(
("cd", fg(HighlightRole::command)),
("dir-in-cdpath", param_valid_path),
);
// Command substitutions.
validate!(
("echo", fg(HighlightRole::command)),
("param1", fg(HighlightRole::param)),
("-l", fg(HighlightRole::option)),
("--", fg(HighlightRole::option)),
("-l", fg(HighlightRole::param)),
("(", fg(HighlightRole::operat)),
("ls", fg(HighlightRole::command)),
("-l", fg(HighlightRole::option)),
("--", fg(HighlightRole::option)),
("-l", fg(HighlightRole::param)),
("param2", fg(HighlightRole::param)),
(")", fg(HighlightRole::operat)),
("|", fg(HighlightRole::statement_terminator)),
("cat", fg(HighlightRole::command)),
);
validate!(
("true", fg(HighlightRole::command)),
("$(", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(")", fg(HighlightRole::operat)),
);
validate!(
("true", fg(HighlightRole::command)),
("\"before", fg(HighlightRole::quote)),
("$(", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
("param1", fg(HighlightRole::param)),
(")", fg(HighlightRole::operat)),
("after\"", fg(HighlightRole::quote)),
("param2", fg(HighlightRole::param)),
);
validate!(
("true", fg(HighlightRole::command)),
("\"", fg(HighlightRole::error)),
("unclosed quote", fg(HighlightRole::quote)),
("$(", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(")", fg(HighlightRole::operat)),
);
// Redirections substitutions.
validate!(
("echo", fg(HighlightRole::command)),
("param1", fg(HighlightRole::param)),
// Input redirection.
("<", fg(HighlightRole::redirection)),
("/dev/null", redirection_valid_path),
// Output redirection to a valid fd.
("1>&2", fg(HighlightRole::redirection)),
// Output redirection to an invalid fd.
("2>&", fg(HighlightRole::redirection)),
("LO", fg(HighlightRole::error)),
// Just a param, not a redirection.
("test/blah", fg(HighlightRole::param)),
// Input redirection from directory.
("<", fg(HighlightRole::redirection)),
("test/", fg(HighlightRole::error)),
// Output redirection to an invalid path.
("3>", fg(HighlightRole::redirection)),
("/not/a/valid/path/nope", fg(HighlightRole::error)),
// Output redirection to directory.
("3>", fg(HighlightRole::redirection)),
("test/nope/", fg(HighlightRole::error)),
// Redirections to overflow fd.
("99999999999999999999>&2", fg(HighlightRole::error)),
("2>&", fg(HighlightRole::redirection)),
("99999999999999999999", fg(HighlightRole::error)),
// Output redirection containing a command substitution.
("4>", fg(HighlightRole::redirection)),
("(", fg(HighlightRole::operat)),
("echo", fg(HighlightRole::command)),
("test/somewhere", fg(HighlightRole::param)),
(")", fg(HighlightRole::operat)),
// Just another param.
("param2", fg(HighlightRole::param)),
);
validate!(
("for", fg(HighlightRole::keyword)),
("x", fg(HighlightRole::param)),
("in", fg(HighlightRole::keyword)),
("set-by-for-1", fg(HighlightRole::param)),
("set-by-for-2", fg(HighlightRole::param)),
(";", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
(">", fg(HighlightRole::redirection)),
("$x", fg(HighlightRole::redirection)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("set", fg(HighlightRole::command)),
("x", fg(HighlightRole::param)),
("set-by-set", fg(HighlightRole::param)),
(";", fg(HighlightRole::statement_terminator)),
("echo", fg(HighlightRole::command)),
(">", fg(HighlightRole::redirection)),
("$x", fg(HighlightRole::redirection)),
("2>", fg(HighlightRole::redirection)),
("$totally_not_x", fg(HighlightRole::error)),
("<", fg(HighlightRole::redirection)),
("$x_but_its_an_impostor", fg(HighlightRole::error)),
);
validate!(
("x", fg(HighlightRole::param), ns),
("=", fg(HighlightRole::operat), ns),
("set-by-variable-override", fg(HighlightRole::param), ns),
("echo", fg(HighlightRole::command)),
(">", fg(HighlightRole::redirection)),
("$x", fg(HighlightRole::redirection)),
);
validate!(
("end", fg(HighlightRole::error)),
(";", fg(HighlightRole::statement_terminator)),
("if", fg(HighlightRole::keyword)),
("end", fg(HighlightRole::error)),
);
validate!(
("echo", fg(HighlightRole::command)),
("'", fg(HighlightRole::error)),
("single_quote", fg(HighlightRole::quote)),
("$stuff", fg(HighlightRole::quote)),
);
validate!(
("echo", fg(HighlightRole::command)),
("\"", fg(HighlightRole::error)),
("double_quote", fg(HighlightRole::quote)),
("$stuff", fg(HighlightRole::operat)),
);
validate!(
("echo", fg(HighlightRole::command)),
("$foo", fg(HighlightRole::operat)),
("\"", fg(HighlightRole::quote)),
("$bar", fg(HighlightRole::operat)),
("\"", fg(HighlightRole::quote)),
("$baz[", fg(HighlightRole::operat)),
("1 2..3", fg(HighlightRole::param)),
("]", fg(HighlightRole::operat)),
);
validate!(
("for", fg(HighlightRole::keyword)),
("i", fg(HighlightRole::param)),
("in", fg(HighlightRole::keyword)),
("1 2 3", fg(HighlightRole::param)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("echo", fg(HighlightRole::command)),
("$$foo[", fg(HighlightRole::operat)),
("1", fg(HighlightRole::param)),
("][", fg(HighlightRole::operat)),
("2", fg(HighlightRole::param)),
("]", fg(HighlightRole::operat)),
("[3]", fg(HighlightRole::param)), // two dollar signs, so last one is not an expansion
);
validate!(
("cat", fg(HighlightRole::command)),
("/dev/null", param_valid_path),
("|", fg(HighlightRole::statement_terminator)),
// This is bogus, but we used to use "less" here and that doesn't have to be installed.
("cat", fg(HighlightRole::command)),
("2>", fg(HighlightRole::redirection)),
);
// Highlight path-prefixes only at the cursor.
validate!(
("cat", fg(HighlightRole::command)),
("/dev/nu", fg(HighlightRole::param)),
("/dev/nu", param_valid_path),
);
validate!(
("if", fg(HighlightRole::keyword)),
("true", fg(HighlightRole::command)),
("&&", fg(HighlightRole::operat)),
("false", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("or", fg(HighlightRole::operat)),
("false", fg(HighlightRole::command)),
("||", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("and", fg(HighlightRole::operat)),
("not", fg(HighlightRole::operat)),
("!", fg(HighlightRole::operat)),
("true", fg(HighlightRole::command)),
(";", fg(HighlightRole::statement_terminator)),
("end", fg(HighlightRole::keyword)),
);
validate!(
("echo", fg(HighlightRole::command)),
("%self", fg(HighlightRole::operat)),
("not%self", fg(HighlightRole::param)),
("self%not", fg(HighlightRole::param)),
);
validate!(
("false", fg(HighlightRole::command)),
("&|", fg(HighlightRole::statement_terminator)),
("true", fg(HighlightRole::command)),
);
validate!(
("HOME", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
(".", fg(HighlightRole::param), ns),
("VAR1", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
("VAL1", fg(HighlightRole::param), ns),
("VAR", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
("false", fg(HighlightRole::command)),
("|&", fg(HighlightRole::statement_terminator)),
("true", fg(HighlightRole::command)),
("stuff", fg(HighlightRole::param)),
);
validate!(
("echo", fg(HighlightRole::command)), // (
(")", fg(HighlightRole::error)),
);
validate!(
("echo", fg(HighlightRole::command)),
("stuff", fg(HighlightRole::param)),
("# comment", fg(HighlightRole::comment)),
);
validate!(
("echo", fg(HighlightRole::command)),
("--", fg(HighlightRole::option)),
("-s", fg(HighlightRole::param)),
);
// Overlong paths don't crash (#7837).
let overlong = get_overlong_path();
validate!(
("touch", fg(HighlightRole::command)),
(&overlong, fg(HighlightRole::param)),
);
validate!(
("a", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
);
// Highlighting works across escaped line breaks (#8444).
validate!(
("echo", fg(HighlightRole::command)),
("$FISH_\\\n", fg(HighlightRole::operat)),
("VERSION", fg(HighlightRole::operat), ns),
);
// NOTE: we assume /usr/bin/env exists on the system here
validate!(
("/usr/bin/en", fg(HighlightRole::command), ns),
("*", fg(HighlightRole::operat), ns)
);
validate!(
("/usr/bin/e", fg(HighlightRole::command), ns),
("*", fg(HighlightRole::operat), ns)
);
validate!(
("/usr/bin/e", fg(HighlightRole::command), ns),
("{$VARIABLE_IN_COMMAND}", fg(HighlightRole::operat), ns),
("*", fg(HighlightRole::operat), ns)
);
validate!(
("/usr/bin/e", fg(HighlightRole::command), ns),
("$VARIABLE_IN_COMMAND2", fg(HighlightRole::operat), ns)
);
validate!(("$EMPTY_VARIABLE", fg(HighlightRole::error)));
validate!(("\"$EMPTY_VARIABLE\"", fg(HighlightRole::error)));
validate!(
("echo", fg(HighlightRole::command)),
("\\UFDFD", fg(HighlightRole::escape)),
);
validate!(
("echo", fg(HighlightRole::command)),
("\\U10FFFF", fg(HighlightRole::escape)),
);
validate!(
("echo", fg(HighlightRole::command)),
("\\U110000", fg(HighlightRole::error)),
);
validate!(
(">", fg(HighlightRole::error)),
("echo", fg(HighlightRole::error)),
);
}
/// Tests that trailing spaces after a command don't inherit the underline formatting of the

View File

@@ -5,7 +5,6 @@
use crate::env::{EnvStack, Environment as _};
use crate::fd_readable_set::{FdReadableSet, Timeout};
use crate::flog::{FloggableDebug, FloggableDisplay, flog};
use crate::future_feature_flags::{FeatureFlag, test as feature_test};
use crate::key::{
self, Key, Modifiers, ViewportPosition, alt, canonicalize_control_char,
canonicalize_keyed_control_char, char_to_symbol, function_key, shift,
@@ -18,6 +17,7 @@
};
use crate::universal_notifier::default_notifier;
use crate::wutil::{fish_is_pua, fish_wcstol};
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_widestring::encode_byte_to_char;
use nix::sys::{select::FdSet, signal::SigSet, time::TimeSpec};
use std::cell::{RefCell, RefMut};

View File

@@ -3,12 +3,12 @@
use crate::{
common::{EscapeFlags, EscapeStringStyle, escape_string},
flog::FloggableDebug,
future_feature_flags::{FeatureFlag, test as feature_test},
prelude::*,
reader::safe_get_terminal_mode_on_startup,
wutil::fish_wcstoul,
};
use fish_fallback::fish_wcwidth;
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_widestring::decode_byte_from_char;
pub(crate) const Backspace: char = '\u{F500}'; // below ENCODE_DIRECT_BASE

View File

@@ -28,7 +28,6 @@
pub mod fork_exec;
pub mod fs;
pub mod function;
pub mod future_feature_flags;
pub mod global_safety;
pub mod highlight;
pub mod history;

View File

@@ -13,7 +13,6 @@
VARIABLE_EXPAND, VARIABLE_EXPAND_EMPTY, VARIABLE_EXPAND_SINGLE, expand_one,
expand_to_command_and_args,
};
use crate::future_feature_flags::{FeatureFlag, feature_test};
use crate::operation_context::OperationContext;
use crate::parse_constants::{
ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE_QUOTED1, ERROR_BRACKETED_VARIABLE1,
@@ -30,6 +29,7 @@
};
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use fish_common::help_section;
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_wcstringutil::{count_newlines, truncate};
use std::ops::Range;
use std::{iter, ops};

View File

@@ -46,7 +46,6 @@
use crate::fd_readable_set::poll_fd_readable;
use crate::fds::{make_fd_blocking, wopen_cloexec};
use crate::flog::{flog, flogf};
use crate::future_feature_flags::{self, FeatureFlag};
use crate::global_safety::RelaxedAtomicBool;
use crate::highlight::{
HighlightRole, HighlightSpec, autosuggest_validate_from_history, highlight_shell,
@@ -121,6 +120,7 @@
use fish_common::{UTF8_BOM_WCHAR, help_section};
use fish_fallback::fish_wcwidth;
use fish_fallback::lowercase;
use fish_feature_flags::FeatureFlag;
use fish_util::{perror, write_to_fd};
use fish_wcstringutil::{
CaseSensitivity, IsPrefix, StringFuzzyMatch, count_preceding_backslashes, is_prefix,
@@ -239,7 +239,7 @@ fn redirect_tty_after_sighup() {
}
fn querying_allowed(vars: &dyn Environment) -> bool {
future_feature_flags::test(FeatureFlag::QueryTerm)
fish_feature_flags::feature_test(FeatureFlag::QueryTerm)
&& !is_dumb()
&& {
// TODO(term-workaround)

View File

@@ -1,12 +1,12 @@
// Generic output functions.
use crate::common::{self, EscapeStringStyle, escape_string};
use crate::future_feature_flags::{self, FeatureFlag};
use crate::prelude::*;
use crate::screen::{is_dumb, only_grayscale};
use crate::text_face::{ResettableStyle, TextFace, TextStyling, UnderlineStyle};
use crate::threads::MainThread;
use bitflags::bitflags;
use fish_color::{Color, Color24};
use fish_feature_flags::FeatureFlag;
use fish_wcstringutil::{wcs2bytes, wcs2bytes_appending};
use std::cell::{RefCell, RefMut};
use std::ops::{Deref, DerefMut};
@@ -181,7 +181,7 @@ fn osc_0_or_1_terminal_title(out: &mut Outputter, is_1: bool, title: &[WString])
}
fn osc_133_prompt_start(out: &mut Outputter) -> bool {
if !future_feature_flags::test(FeatureFlag::MarkPrompt) {
if !fish_feature_flags::feature_test(FeatureFlag::MarkPrompt) {
return false;
}
static TEST_BALLOON: OnceLock<()> = OnceLock::new();
@@ -194,7 +194,7 @@ fn osc_133_prompt_start(out: &mut Outputter) -> bool {
}
fn osc_133_prompt_end(out: &mut Outputter) -> bool {
if !future_feature_flags::test(FeatureFlag::MarkPrompt) {
if !fish_feature_flags::feature_test(FeatureFlag::MarkPrompt) {
return false;
}
write_to_output!(out, "\x1b]133;B\x07");
@@ -202,7 +202,7 @@ fn osc_133_prompt_end(out: &mut Outputter) -> bool {
}
fn osc_133_command_start(out: &mut Outputter, command: &wstr) -> bool {
if !future_feature_flags::test(FeatureFlag::MarkPrompt) {
if !fish_feature_flags::feature_test(FeatureFlag::MarkPrompt) {
return false;
}
write_to_output!(
@@ -214,7 +214,7 @@ fn osc_133_command_start(out: &mut Outputter, command: &wstr) -> bool {
}
fn osc_133_command_finished(out: &mut Outputter, exit_status: libc::c_int) -> bool {
if !future_feature_flags::test(FeatureFlag::MarkPrompt) {
if !fish_feature_flags::feature_test(FeatureFlag::MarkPrompt) {
return false;
}
write_to_output!(out, "\x1b]133;D;{}\x07", exit_status);

View File

@@ -3,11 +3,11 @@
use crate::ast::unescape_keyword;
use crate::common::valid_var_name_char;
use crate::future_feature_flags::{FeatureFlag, feature_test};
use crate::parse_constants::SOURCE_OFFSET_INVALID;
use crate::parser_keywords::parser_keywords_is_subcommand;
use crate::prelude::*;
use crate::redirection::RedirectionMode;
use fish_feature_flags::{FeatureFlag, feature_test};
use libc::{STDIN_FILENO, STDOUT_FILENO};
use nix::fcntl::OFlag;
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Range};

View File

@@ -13,12 +13,11 @@
};
use crate::complete::{CompleteFlags, Completion, CompletionReceiver, PROG_COMPLETE_SEP};
use crate::expand::ExpandFlags;
use crate::future_feature_flags::FeatureFlag;
use crate::future_feature_flags::feature_test;
use crate::prelude::*;
use crate::wutil::dir_iter::DirEntryType;
use crate::wutil::{dir_iter::DirEntry, lwstat, waccess};
use fish_fallback::wcscasecmp;
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_wcstringutil::{
CaseSensitivity, string_fuzzy_match_string, string_suffixes_string_case_insensitive,
strip_executable_suffix,
@@ -1231,7 +1230,7 @@ pub fn wildcard_has(s: impl AsRef<wstr>) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use crate::future_feature_flags::scoped_test;
use fish_feature_flags::with_overridden_feature;
#[test]
fn test_wildcards() {
@@ -1244,12 +1243,12 @@ fn test_wildcards() {
let wc = unescape_string(wc, UnescapeStringStyle::Script(UnescapeFlags::SPECIAL)).unwrap();
assert!(!wildcard_has(&wc) && wildcard_has_internal(&wc));
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
assert!(wildcard_has(L!("?")));
assert!(!wildcard_has(L!("\\?")));
});
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
assert!(!wildcard_has(L!("?")));
assert!(!wildcard_has(L!("\\?")));
});

View File

@@ -3,6 +3,7 @@
isolated-tmux-start
isolated-tmux send-keys 'set -g g_u_var foobar' Enter
tmux-sleep
isolated-tmux send-keys 'set -U g_u_var barfoo' Enter
tmux-sleep