mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-10 07:04:29 +00:00
Port future_feature_flags.cpp to Rust
This is early work but I guess there's no harm in pushing it? Some thoughts on the conventions: Types that live only inside Rust follow Rust naming convention ("FeatureMetadata"). Types that live on both sides of the language boundary follow the existing naming ("feature_flag_t"). The alternative is to define a type alias ("using feature_flag_t = rust::FeatureFlag") but that doesn't seem to be supported in "[cxx::bridge]" blocks. We could put it in a header ("future_feature_flags.h"). "feature_metadata_t" is a variant of "FeatureMetadata" that can cross the language boundary. This has the advantage that we can avoid tainting "FeatureMetadata" with "CxxString" and such. This is an experimental approach, probably not what we should do in general.
This commit is contained in:
parent
132d99a27b
commit
83fd7ea7c4
17 changed files with 303 additions and 247 deletions
|
@ -118,7 +118,7 @@ set(FISH_SRCS
|
|||
src/ast.cpp src/abbrs.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp
|
||||
src/env.cpp src/env_dispatch.cpp src/env_universal_common.cpp src/event.cpp
|
||||
src/exec.cpp src/expand.cpp src/fallback.cpp src/fd_monitor.cpp src/fish_version.cpp
|
||||
src/flog.cpp src/function.cpp src/future_feature_flags.cpp src/highlight.cpp
|
||||
src/flog.cpp src/function.cpp src/highlight.cpp
|
||||
src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp
|
||||
src/io.cpp src/iothread.cpp src/job_group.cpp src/kill.cpp
|
||||
src/null_terminated_array.cpp src/operation_context.cpp src/output.cpp
|
||||
|
|
|
@ -22,6 +22,7 @@ fn main() -> miette::Result<()> {
|
|||
"src/fd_readable_set.rs",
|
||||
"src/ffi_init.rs",
|
||||
"src/ffi_tests.rs",
|
||||
"src/future_feature_flags.rs",
|
||||
"src/smoke.rs",
|
||||
"src/topic_monitor.rs",
|
||||
"src/util.rs",
|
||||
|
|
|
@ -18,6 +18,7 @@ mod ffi2 {
|
|||
/// Entry point for Rust-specific initialization.
|
||||
fn rust_init() {
|
||||
crate::topic_monitor::topic_monitor_init();
|
||||
crate::future_feature_flags::future_feature_flags_init();
|
||||
}
|
||||
|
||||
/// FFI bridge for activate_flog_categories_by_pattern().
|
||||
|
|
256
fish-rust/src/future_feature_flags.rs
Normal file
256
fish-rust/src/future_feature_flags.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
//! Flags to enable upcoming features
|
||||
|
||||
use crate::ffi::wcharz_t;
|
||||
use crate::wchar::wstr;
|
||||
use crate::wchar_ffi::WCharToFFI;
|
||||
use std::array;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use widestring_suffix::widestrs;
|
||||
|
||||
#[cxx::bridge]
|
||||
mod future_feature_flags_ffi {
|
||||
extern "C++" {
|
||||
include!("wutil.h");
|
||||
type wcharz_t = super::wcharz_t;
|
||||
}
|
||||
|
||||
/// The list of flags.
|
||||
#[repr(u8)]
|
||||
enum feature_flag_t {
|
||||
/// Whether ^ is supported for stderr redirection.
|
||||
stderr_nocaret,
|
||||
|
||||
/// Whether ? is supported as a glob.
|
||||
qmark_noglob,
|
||||
|
||||
/// Whether string replace -r double-unescapes the replacement.
|
||||
string_replace_backslash,
|
||||
|
||||
/// Whether "&" is not-special if followed by a word character.
|
||||
ampersand_nobg_in_token,
|
||||
}
|
||||
|
||||
/// Metadata about feature flags.
|
||||
struct feature_metadata_t {
|
||||
flag: feature_flag_t,
|
||||
name: UniquePtr<CxxWString>,
|
||||
groups: UniquePtr<CxxWString>,
|
||||
description: UniquePtr<CxxWString>,
|
||||
default_value: bool,
|
||||
read_only: bool,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type features_t;
|
||||
fn test(self: &features_t, flag: feature_flag_t) -> bool;
|
||||
fn set(self: &mut features_t, flag: feature_flag_t, value: bool);
|
||||
fn set_from_string(self: &mut features_t, str: wcharz_t);
|
||||
fn fish_features() -> *const features_t;
|
||||
fn feature_test(flag: feature_flag_t) -> bool;
|
||||
fn mutable_fish_features() -> *mut features_t;
|
||||
fn feature_metadata() -> [feature_metadata_t; 4];
|
||||
}
|
||||
}
|
||||
|
||||
pub use future_feature_flags_ffi::{feature_flag_t, feature_metadata_t};
|
||||
|
||||
pub struct features_t {
|
||||
// Values for the flags.
|
||||
// These are atomic to "fix" a race reported by tsan where tests of feature flags and other
|
||||
// tests which use them conceptually race.
|
||||
values: [AtomicBool; metadata.len()],
|
||||
}
|
||||
|
||||
/// Metadata about feature flags.
|
||||
struct FeatureMetadata {
|
||||
/// The flag itself.
|
||||
flag: feature_flag_t,
|
||||
|
||||
/// User-presentable short name of the feature flag.
|
||||
name: &'static wstr,
|
||||
|
||||
/// Comma-separated list of feature groups.
|
||||
groups: &'static wstr,
|
||||
|
||||
/// User-presentable description of the feature flag.
|
||||
description: &'static wstr,
|
||||
|
||||
/// Default flag value.
|
||||
default_value: bool,
|
||||
|
||||
/// Whether the value can still be changed or not.
|
||||
read_only: bool,
|
||||
}
|
||||
|
||||
impl From<&FeatureMetadata> for feature_metadata_t {
|
||||
fn from(md: &FeatureMetadata) -> feature_metadata_t {
|
||||
feature_metadata_t {
|
||||
flag: md.flag,
|
||||
name: md.name.to_ffi(),
|
||||
groups: md.groups.to_ffi(),
|
||||
description: md.description.to_ffi(),
|
||||
default_value: md.default_value,
|
||||
read_only: md.read_only,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The metadata, indexed by flag.
|
||||
#[widestrs]
|
||||
const metadata: [FeatureMetadata; 4] = [
|
||||
FeatureMetadata {
|
||||
flag: feature_flag_t::stderr_nocaret,
|
||||
name: "stderr-nocaret"L,
|
||||
groups: "3.0"L,
|
||||
description: "^ no longer redirects stderr (historical, can no longer be changed)"L,
|
||||
default_value: true,
|
||||
read_only: true,
|
||||
},
|
||||
FeatureMetadata {
|
||||
flag: feature_flag_t::qmark_noglob,
|
||||
name: "qmark-noglob"L,
|
||||
groups: "3.0"L,
|
||||
description: "? no longer globs"L,
|
||||
default_value: false,
|
||||
read_only: false,
|
||||
},
|
||||
FeatureMetadata {
|
||||
flag: feature_flag_t::string_replace_backslash,
|
||||
name: "regex-easyesc"L,
|
||||
groups: "3.1"L,
|
||||
description: "string replace -r needs fewer \\'s"L,
|
||||
default_value: true,
|
||||
read_only: false,
|
||||
},
|
||||
FeatureMetadata {
|
||||
flag: feature_flag_t::ampersand_nobg_in_token,
|
||||
name: "ampersand-nobg-in-token"L,
|
||||
groups: "3.4"L,
|
||||
description: "& only backgrounds if followed by a separator"L,
|
||||
default_value: true,
|
||||
read_only: false,
|
||||
},
|
||||
];
|
||||
|
||||
/// The singleton shared feature set.
|
||||
static mut global_features: *const UnsafeCell<features_t> = std::ptr::null();
|
||||
|
||||
pub fn future_feature_flags_init() {
|
||||
unsafe {
|
||||
// Leak it for now.
|
||||
global_features = Box::into_raw(Box::new(UnsafeCell::new(features_t::new())));
|
||||
}
|
||||
}
|
||||
|
||||
impl features_t {
|
||||
fn new() -> Self {
|
||||
features_t {
|
||||
values: array::from_fn(|i| AtomicBool::new(metadata[i].default_value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether a flag is set.
|
||||
pub fn test(&self, flag: feature_flag_t) -> bool {
|
||||
self.values[flag.repr as usize].load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Set a flag.
|
||||
pub fn set(&mut self, flag: feature_flag_t, value: bool) {
|
||||
self.values[flag.repr as usize].store(value, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Parses a comma-separated feature-flag string, updating ourselves with the values.
|
||||
/// Feature names or group names may be prefixed with "no-" to disable them.
|
||||
/// The special group name "all" may be used for those who like to live on the edge.
|
||||
/// Unknown features are silently ignored.
|
||||
#[widestrs]
|
||||
pub fn set_from_string(&mut self, str: wcharz_t) {
|
||||
let str: &wstr = str.into();
|
||||
let whitespace = "\t\n\0x0B\0x0C\r "L.as_char_slice();
|
||||
for entry in str.as_char_slice().split(|c| *c == ',') {
|
||||
if entry.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Trim leading and trailing whitespace
|
||||
let entry = &entry[entry.iter().take_while(|c| whitespace.contains(c)).count()..];
|
||||
let entry =
|
||||
&entry[..entry.len() - entry.iter().take_while(|c| whitespace.contains(c)).count()];
|
||||
|
||||
// A "no-" prefix inverts the sense.
|
||||
let (name, value) = match entry.strip_prefix("no-"L.as_char_slice()) {
|
||||
Some(suffix) => (suffix, false),
|
||||
None => (entry, true),
|
||||
};
|
||||
// Look for a feature with this name. If we don't find it, assume it's a group name and set
|
||||
// all features whose group contain it. Do nothing even if the string is unrecognized; this
|
||||
// is to allow uniform invocations of fish (e.g. disable a feature that is only present in
|
||||
// future versions).
|
||||
// The special name 'all' may be used for those who like to live on the edge.
|
||||
if let Some(md) = metadata.iter().find(|md| md.name == name) {
|
||||
// Only change it if it's not read-only.
|
||||
// Don't complain if it is, this is typically set from a variable.
|
||||
if !md.read_only {
|
||||
self.set(md.flag, value);
|
||||
}
|
||||
} else {
|
||||
for md in &metadata {
|
||||
if md.groups == name || name == "all"L {
|
||||
if !md.read_only {
|
||||
self.set(md.flag, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the global set of features for fish. This is const to prevent accidental mutation.
|
||||
pub fn fish_features() -> *const features_t {
|
||||
unsafe { (*global_features).get() }
|
||||
}
|
||||
|
||||
/// Perform a feature test on the global set of features.
|
||||
pub fn feature_test(flag: feature_flag_t) -> bool {
|
||||
unsafe { &*(*global_features).get() }.test(flag)
|
||||
}
|
||||
|
||||
/// Return the global set of features for fish, but mutable. In general fish features should be set
|
||||
/// at startup only.
|
||||
pub fn mutable_fish_features() -> *mut features_t {
|
||||
unsafe { (*global_features).get() }
|
||||
}
|
||||
|
||||
// The metadata, indexed by flag.
|
||||
pub fn feature_metadata() -> [feature_metadata_t; metadata.len()] {
|
||||
array::from_fn(|i| (&metadata[i]).into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[widestrs]
|
||||
fn test_feature_flags() {
|
||||
use crate::wchar_ffi::wcharz;
|
||||
|
||||
let mut f = features_t::new();
|
||||
f.set_from_string(wcharz!("stderr-nocaret,nonsense"L));
|
||||
assert!(f.test(feature_flag_t::stderr_nocaret));
|
||||
f.set_from_string(wcharz!("stderr-nocaret,no-stderr-nocaret,nonsense"L));
|
||||
assert!(f.test(feature_flag_t::stderr_nocaret));
|
||||
|
||||
// Ensure every metadata is represented once.
|
||||
let mut counts: [usize; metadata.len()] = [0; metadata.len()];
|
||||
for md in &metadata {
|
||||
counts[md.flag.repr as usize] += 1;
|
||||
}
|
||||
for count in counts {
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
metadata[feature_flag_t::stderr_nocaret.repr as usize].name,
|
||||
"stderr-nocaret"L
|
||||
);
|
||||
}
|
|
@ -12,6 +12,7 @@ mod ffi;
|
|||
mod ffi_init;
|
||||
mod ffi_tests;
|
||||
mod flog;
|
||||
mod future_feature_flags;
|
||||
mod signal;
|
||||
mod smoke;
|
||||
mod topic_monitor;
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
#include "../common.h"
|
||||
#include "../enum_map.h"
|
||||
#include "../fallback.h" // IWYU pragma: keep
|
||||
#include "../future_feature_flags.h"
|
||||
#include "../io.h"
|
||||
#include "../maybe.h"
|
||||
#include "../parser.h"
|
||||
#include "../proc.h"
|
||||
#include "../wgetopt.h"
|
||||
#include "../wutil.h" // IWYU pragma: keep
|
||||
#include "future_feature_flags.rs.h"
|
||||
|
||||
enum status_cmd_t {
|
||||
STATUS_CURRENT_CMD = 1,
|
||||
|
@ -156,12 +156,12 @@ static bool set_status_cmd(const wchar_t *cmd, status_cmd_opts_t &opts, status_c
|
|||
/// Print the features and their values.
|
||||
static void print_features(io_streams_t &streams) {
|
||||
auto max_len = std::numeric_limits<int>::min();
|
||||
for (const auto &md : features_t::metadata)
|
||||
max_len = std::max(max_len, static_cast<int>(wcslen(md.name)));
|
||||
for (const auto &md : features_t::metadata) {
|
||||
for (const auto &md : feature_metadata())
|
||||
max_len = std::max(max_len, static_cast<int>(md.name->size()));
|
||||
for (const auto &md : feature_metadata()) {
|
||||
int set = feature_test(md.flag);
|
||||
streams.out.append_format(L"%-*ls%-3s %ls %ls\n", max_len + 1, md.name, set ? "on" : "off",
|
||||
md.groups, md.description);
|
||||
streams.out.append_format(L"%-*ls%-3s %ls %ls\n", max_len + 1, md.name->c_str(),
|
||||
set ? "on" : "off", md.groups->c_str(), md.description->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,11 +365,12 @@ maybe_t<int> builtin_status(parser_t &parser, io_streams_t &streams, const wchar
|
|||
streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 1, args.size());
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
auto metadata = features_t::metadata_for(args.front().c_str());
|
||||
if (!metadata) {
|
||||
retval = TEST_FEATURE_NOT_RECOGNIZED;
|
||||
} else {
|
||||
retval = feature_test(metadata->flag) ? TEST_FEATURE_ON : TEST_FEATURE_OFF;
|
||||
retval = TEST_FEATURE_NOT_RECOGNIZED;
|
||||
for (const auto &md : feature_metadata()) {
|
||||
if (*md.name == args.front()) {
|
||||
retval = feature_test(md.flag) ? TEST_FEATURE_ON : TEST_FEATURE_OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include "../common.h"
|
||||
#include "../env.h"
|
||||
#include "../fallback.h" // IWYU pragma: keep
|
||||
#include "../future_feature_flags.h"
|
||||
#include "../io.h"
|
||||
#include "../maybe.h"
|
||||
#include "../parse_util.h"
|
||||
|
@ -30,6 +29,7 @@
|
|||
#include "../wgetopt.h"
|
||||
#include "../wildcard.h"
|
||||
#include "../wutil.h" // IWYU pragma: keep
|
||||
#include "future_feature_flags.rs.h"
|
||||
|
||||
// Empirically determined.
|
||||
// This is probably down to some pipe buffer or some such,
|
||||
|
@ -1240,7 +1240,7 @@ class regex_replacer_t final : public string_replacer_t {
|
|||
regex_replacer_t(const wchar_t *argv0, re::regex_t regex, const wcstring &replacement_,
|
||||
const options_t &opts, io_streams_t &streams)
|
||||
: string_replacer_t(argv0, opts, streams), regex(std::move(regex)) {
|
||||
if (feature_test(features_t::string_replace_backslash)) {
|
||||
if (feature_test(feature_flag_t::string_replace_backslash)) {
|
||||
replacement = replacement_;
|
||||
} else {
|
||||
replacement = interpret_escapes(replacement_);
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
#include "expand.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "flog.h"
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "global_safety.h"
|
||||
#include "iothread.h"
|
||||
#include "signals.h"
|
||||
|
@ -863,7 +863,7 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
|
|||
const bool escape_printables = !(flags & ESCAPE_NO_PRINTABLES);
|
||||
const bool no_quoted = static_cast<bool>(flags & ESCAPE_NO_QUOTED);
|
||||
const bool no_tilde = static_cast<bool>(flags & ESCAPE_NO_TILDE);
|
||||
const bool no_qmark = feature_test(features_t::qmark_noglob);
|
||||
const bool no_qmark = feature_test(feature_flag_t::qmark_noglob);
|
||||
const bool symbolic = static_cast<bool>(flags & ESCAPE_SYMBOLIC) && (MB_CUR_MAX > 1);
|
||||
assert((!symbolic || !escape_printables) && "symbolic implies escape-no-printables");
|
||||
|
||||
|
@ -1401,7 +1401,7 @@ static bool unescape_string_internal(const wchar_t *const input, const size_t in
|
|||
break;
|
||||
}
|
||||
case L'?': {
|
||||
if (unescape_special && !feature_test(features_t::qmark_noglob)) {
|
||||
if (unescape_special && !feature_test(feature_flag_t::qmark_noglob)) {
|
||||
to_append_or_none = ANY_CHAR;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -49,7 +49,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|||
#include "fish_version.h"
|
||||
#include "flog.h"
|
||||
#include "function.h"
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "global_safety.h"
|
||||
#include "history.h"
|
||||
#include "io.h"
|
||||
|
@ -500,10 +500,10 @@ int main(int argc, char **argv) {
|
|||
// command line takes precedence).
|
||||
if (auto features_var = env_stack_t::globals().get(L"fish_features")) {
|
||||
for (const wcstring &s : features_var->as_list()) {
|
||||
mutable_fish_features().set_from_string(s);
|
||||
mutable_fish_features()->set_from_string(s.c_str());
|
||||
}
|
||||
}
|
||||
mutable_fish_features().set_from_string(opts.features);
|
||||
mutable_fish_features()->set_from_string(opts.features.c_str());
|
||||
proc_init();
|
||||
misc_init();
|
||||
reader_init();
|
||||
|
|
|
@ -43,7 +43,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|||
#include "ffi_init.rs.h"
|
||||
#include "fish_version.h"
|
||||
#include "flog.h"
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "global_safety.h"
|
||||
#include "highlight.h"
|
||||
#include "maybe.h"
|
||||
|
@ -886,7 +886,7 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
if (auto features_var = env_stack_t::globals().get(L"fish_features")) {
|
||||
for (const wcstring &s : features_var->as_list()) {
|
||||
mutable_fish_features().set_from_string(s);
|
||||
mutable_fish_features()->set_from_string(s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
#include "ffi_init.rs.h"
|
||||
#include "ffi_tests.rs.h"
|
||||
#include "function.h"
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "global_safety.h"
|
||||
#include "highlight.h"
|
||||
#include "history.h"
|
||||
|
@ -1945,28 +1945,6 @@ static void test_utf8() {
|
|||
#endif
|
||||
}
|
||||
|
||||
static void test_feature_flags() {
|
||||
say(L"Testing future feature flags");
|
||||
using ft = features_t;
|
||||
ft f;
|
||||
f.set_from_string(L"stderr-nocaret,nonsense");
|
||||
do_test(f.test(ft::stderr_nocaret));
|
||||
f.set_from_string(L"stderr-nocaret,no-stderr-nocaret,nonsense");
|
||||
do_test(f.test(ft::stderr_nocaret));
|
||||
|
||||
// Ensure every metadata is represented once.
|
||||
size_t counts[ft::flag_count] = {};
|
||||
for (const auto &md : ft::metadata) {
|
||||
counts[md.flag]++;
|
||||
}
|
||||
for (size_t c : counts) {
|
||||
do_test(c == 1);
|
||||
}
|
||||
do_test(ft::metadata[ft::stderr_nocaret].name == wcstring(L"stderr-nocaret"));
|
||||
do_test(ft::metadata_for(L"stderr-nocaret") == &ft::metadata[ft::stderr_nocaret]);
|
||||
do_test(ft::metadata_for(L"not-a-flag") == nullptr);
|
||||
}
|
||||
|
||||
static void test_escape_sequences() {
|
||||
say(L"Testing escape_sequences");
|
||||
layout_cache_t lc;
|
||||
|
@ -3243,15 +3221,15 @@ static void test_wildcards() {
|
|||
unescape_string_in_place(&wc, UNESCAPE_SPECIAL);
|
||||
do_test(!wildcard_has(wc) && wildcard_has_internal(wc));
|
||||
|
||||
auto &feat = mutable_fish_features();
|
||||
auto saved = feat.test(features_t::flag_t::qmark_noglob);
|
||||
feat.set(features_t::flag_t::qmark_noglob, false);
|
||||
auto feat = mutable_fish_features();
|
||||
auto saved = feat->test(feature_flag_t::qmark_noglob);
|
||||
feat->set(feature_flag_t::qmark_noglob, false);
|
||||
do_test(wildcard_has(L"?"));
|
||||
do_test(!wildcard_has(L"\\?"));
|
||||
feat.set(features_t::flag_t::qmark_noglob, true);
|
||||
feat->set(feature_flag_t::qmark_noglob, true);
|
||||
do_test(!wildcard_has(L"?"));
|
||||
do_test(!wildcard_has(L"\\?"));
|
||||
feat.set(features_t::flag_t::qmark_noglob, saved);
|
||||
feat->set(feature_flag_t::qmark_noglob, saved);
|
||||
}
|
||||
|
||||
static void test_complete() {
|
||||
|
@ -5712,8 +5690,8 @@ static void test_highlighting() {
|
|||
{L"\\U110000", highlight_role_t::error},
|
||||
});
|
||||
#endif
|
||||
const auto saved_flags = fish_features();
|
||||
mutable_fish_features().set(features_t::ampersand_nobg_in_token, true);
|
||||
bool saved_flag = feature_test(feature_flag_t::ampersand_nobg_in_token);
|
||||
mutable_fish_features()->set(feature_flag_t::ampersand_nobg_in_token, true);
|
||||
for (const highlight_component_list_t &components : highlight_tests) {
|
||||
// Generate the text.
|
||||
wcstring text;
|
||||
|
@ -5758,7 +5736,7 @@ static void test_highlighting() {
|
|||
}
|
||||
}
|
||||
}
|
||||
mutable_fish_features() = saved_flags;
|
||||
mutable_fish_features()->set(feature_flag_t::ampersand_nobg_in_token, saved_flag);
|
||||
vars.remove(L"VARIABLE_IN_COMMAND", ENV_DEFAULT);
|
||||
vars.remove(L"VARIABLE_IN_COMMAND2", ENV_DEFAULT);
|
||||
}
|
||||
|
@ -6210,7 +6188,7 @@ static void test_string() {
|
|||
run_one_string_test(t.argv, t.expected_rc, t.expected_out);
|
||||
}
|
||||
|
||||
const auto saved_flags = fish_features();
|
||||
bool saved_flag = feature_test(feature_flag_t::qmark_noglob);
|
||||
const struct string_test qmark_noglob_tests[] = {
|
||||
{{L"string", L"match", L"a*b?c", L"axxb?c", nullptr}, STATUS_CMD_OK, L"axxb?c\n"},
|
||||
{{L"string", L"match", L"*?", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
||||
|
@ -6218,7 +6196,7 @@ static void test_string() {
|
|||
{{L"string", L"match", L"?*", L"a", nullptr}, STATUS_CMD_ERROR, L""},
|
||||
{{L"string", L"match", L"?*", L"ab", nullptr}, STATUS_CMD_ERROR, L""},
|
||||
{{L"string", L"match", L"a*\\?", L"abc?", nullptr}, STATUS_CMD_ERROR, L""}};
|
||||
mutable_fish_features().set(features_t::qmark_noglob, true);
|
||||
mutable_fish_features()->set(feature_flag_t::qmark_noglob, true);
|
||||
for (const auto &t : qmark_noglob_tests) {
|
||||
run_one_string_test(t.argv, t.expected_rc, t.expected_out);
|
||||
}
|
||||
|
@ -6230,11 +6208,11 @@ static void test_string() {
|
|||
{{L"string", L"match", L"?*", L"a", nullptr}, STATUS_CMD_OK, L"a\n"},
|
||||
{{L"string", L"match", L"?*", L"ab", nullptr}, STATUS_CMD_OK, L"ab\n"},
|
||||
{{L"string", L"match", L"a*\\?", L"abc?", nullptr}, STATUS_CMD_OK, L"abc?\n"}};
|
||||
mutable_fish_features().set(features_t::qmark_noglob, false);
|
||||
mutable_fish_features()->set(feature_flag_t::qmark_noglob, false);
|
||||
for (const auto &t : qmark_glob_tests) {
|
||||
run_one_string_test(t.argv, t.expected_rc, t.expected_out);
|
||||
}
|
||||
mutable_fish_features() = saved_flags;
|
||||
mutable_fish_features()->set(feature_flag_t::qmark_noglob, saved_flag);
|
||||
}
|
||||
|
||||
/// Helper for test_timezone_env_vars().
|
||||
|
@ -7148,7 +7126,6 @@ static const test_t s_tests[]{
|
|||
{TEST_GROUP("cancellation"), test_cancellation},
|
||||
{TEST_GROUP("indents"), test_indents},
|
||||
{TEST_GROUP("utf8"), test_utf8},
|
||||
{TEST_GROUP("feature_flags"), test_feature_flags},
|
||||
{TEST_GROUP("escape_sequences"), test_escape_sequences},
|
||||
{TEST_GROUP("pcre2_escape"), test_pcre2_escape},
|
||||
{TEST_GROUP("lru"), test_lru},
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include "future_feature_flags.h"
|
||||
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
|
||||
#include "wcstringutil.h"
|
||||
|
||||
features_t::features_t() {
|
||||
for (const metadata_t &md : metadata) {
|
||||
this->set(md.flag, md.default_value);
|
||||
}
|
||||
}
|
||||
|
||||
/// The set of features applying to this instance.
|
||||
features_t features_t::global_features;
|
||||
|
||||
const features_t::metadata_t features_t::metadata[features_t::flag_count] = {
|
||||
{stderr_nocaret, L"stderr-nocaret", L"3.0",
|
||||
L"^ no longer redirects stderr (historical, can no longer be changed)", true,
|
||||
true /* read-only */},
|
||||
{qmark_noglob, L"qmark-noglob", L"3.0", L"? no longer globs", false, false},
|
||||
{string_replace_backslash, L"regex-easyesc", L"3.1", L"string replace -r needs fewer \\'s",
|
||||
true, false},
|
||||
{ampersand_nobg_in_token, L"ampersand-nobg-in-token", L"3.4",
|
||||
L"& only backgrounds if followed by a separator", true, false},
|
||||
};
|
||||
|
||||
const struct features_t::metadata_t *features_t::metadata_for(const wchar_t *name) {
|
||||
assert(name && "null flag name");
|
||||
for (const auto &md : metadata) {
|
||||
if (!std::wcscmp(name, md.name)) return &md;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void features_t::set_from_string(const wcstring &str) {
|
||||
wcstring_list_t entries = split_string(str, L',');
|
||||
const wchar_t *whitespace = L"\t\n\v\f\r ";
|
||||
for (wcstring entry : entries) {
|
||||
if (entry.empty()) continue;
|
||||
|
||||
// Trim leading and trailing whitespace
|
||||
entry.erase(0, entry.find_first_not_of(whitespace));
|
||||
entry.erase(entry.find_last_not_of(whitespace) + 1);
|
||||
|
||||
const wchar_t *name = entry.c_str();
|
||||
bool value = true;
|
||||
// A "no-" prefix inverts the sense.
|
||||
if (string_prefixes_string(L"no-", name)) {
|
||||
value = false;
|
||||
name += const_strlen("no-");
|
||||
}
|
||||
// Look for a feature with this name. If we don't find it, assume it's a group name and set
|
||||
// all features whose group contain it. Do nothing even if the string is unrecognized; this
|
||||
// is to allow uniform invocations of fish (e.g. disable a feature that is only present in
|
||||
// future versions).
|
||||
// The special name 'all' may be used for those who like to live on the edge.
|
||||
if (const metadata_t *md = metadata_for(name)) {
|
||||
// Only change it if it's not read-only.
|
||||
// Don't complain if it is, this is typically set from a variable.
|
||||
if (!md->read_only) {
|
||||
this->set(md->flag, value);
|
||||
}
|
||||
} else {
|
||||
for (const metadata_t &md : metadata) {
|
||||
if (std::wcsstr(md.groups, name) || !std::wcscmp(name, L"all")) {
|
||||
if (!md.read_only) {
|
||||
this->set(md.flag, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
// Flags to enable upcoming features
|
||||
#ifndef FISH_FUTURE_FEATURE_FLAGS_H
|
||||
#define FISH_FUTURE_FEATURE_FLAGS_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
class features_t {
|
||||
public:
|
||||
/// The list of flags.
|
||||
enum flag_t {
|
||||
/// Whether ^ is supported for stderr redirection.
|
||||
stderr_nocaret,
|
||||
|
||||
/// Whether ? is supported as a glob.
|
||||
qmark_noglob,
|
||||
|
||||
/// Whether string replace -r double-unescapes the replacement.
|
||||
string_replace_backslash,
|
||||
|
||||
/// Whether "&" is not-special if followed by a word character.
|
||||
ampersand_nobg_in_token,
|
||||
|
||||
/// The number of flags.
|
||||
flag_count
|
||||
};
|
||||
|
||||
/// Return whether a flag is set.
|
||||
bool test(flag_t f) const {
|
||||
assert(f >= 0 && f < flag_count && "Invalid flag");
|
||||
return values[f].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/// Set a flag.
|
||||
void set(flag_t f, bool value) {
|
||||
assert(f >= 0 && f < flag_count && "Invalid flag");
|
||||
values[f].store(value, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/// Parses a comma-separated feature-flag string, updating ourselves with the values.
|
||||
/// Feature names or group names may be prefixed with "no-" to disable them.
|
||||
/// The special group name "all" may be used for those who like to live on the edge.
|
||||
/// Unknown features are silently ignored.
|
||||
void set_from_string(const wcstring &str);
|
||||
|
||||
/// Metadata about feature flags.
|
||||
struct metadata_t {
|
||||
/// The flag itself.
|
||||
features_t::flag_t flag;
|
||||
|
||||
/// User-presentable short name of the feature flag.
|
||||
const wchar_t *name;
|
||||
|
||||
/// Comma-separated list of feature groups.
|
||||
const wchar_t *groups;
|
||||
|
||||
/// User-presentable description of the feature flag.
|
||||
const wchar_t *description;
|
||||
|
||||
/// Default flag value.
|
||||
const bool default_value;
|
||||
|
||||
/// Whether the value can still be changed or not.
|
||||
const bool read_only;
|
||||
};
|
||||
|
||||
/// The metadata, indexed by flag.
|
||||
static const metadata_t metadata[flag_count];
|
||||
|
||||
/// Return the metadata for a particular name, or nullptr if not found.
|
||||
static const struct metadata_t *metadata_for(const wchar_t *name);
|
||||
|
||||
/// The singleton shared feature set.
|
||||
static features_t global_features;
|
||||
|
||||
features_t();
|
||||
|
||||
features_t(const features_t &rhs) { *this = rhs; }
|
||||
|
||||
void operator=(const features_t &rhs) {
|
||||
for (int i = 0; i < flag_count; i++) {
|
||||
flag_t f = static_cast<flag_t>(i);
|
||||
this->set(f, rhs.test(f));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Values for the flags.
|
||||
// These are atomic to "fix" a race reported by tsan where tests of feature flags and other
|
||||
// tests which use them conceptually race.
|
||||
std::atomic<bool> values[flag_count]{};
|
||||
};
|
||||
|
||||
/// Return the global set of features for fish. This is const to prevent accidental mutation.
|
||||
inline const features_t &fish_features() { return features_t::global_features; }
|
||||
|
||||
/// Perform a feature test on the global set of features.
|
||||
inline bool feature_test(features_t::flag_t f) { return fish_features().test(f); }
|
||||
|
||||
/// Return the global set of features for fish, but mutable. In general fish features should be set
|
||||
/// at startup only.
|
||||
inline features_t &mutable_fish_features() { return features_t::global_features; }
|
||||
|
||||
#endif
|
|
@ -25,7 +25,7 @@
|
|||
#include "expand.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "function.h"
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "history.h"
|
||||
#include "maybe.h"
|
||||
#include "operation_context.h"
|
||||
|
@ -665,7 +665,7 @@ static void color_string_internal(const wcstring &buffstr, highlight_spec_t base
|
|||
break;
|
||||
}
|
||||
case L'?': {
|
||||
if (!feature_test(features_t::qmark_noglob)) {
|
||||
if (!feature_test(feature_flag_t::qmark_noglob)) {
|
||||
colors[in_pos] = highlight_role_t::operat;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include "common.h"
|
||||
#include "expand.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "operation_context.h"
|
||||
#include "parse_constants.h"
|
||||
#include "parse_tree.h"
|
||||
|
@ -486,7 +486,7 @@ void parse_util_token_extent(const wchar_t *buff, size_t cursor_pos, const wchar
|
|||
wcstring parse_util_unescape_wildcards(const wcstring &str) {
|
||||
wcstring result;
|
||||
result.reserve(str.size());
|
||||
bool unesc_qmark = !feature_test(features_t::qmark_noglob);
|
||||
bool unesc_qmark = !feature_test(feature_flag_t::qmark_noglob);
|
||||
|
||||
const wchar_t *const cs = str.c_str();
|
||||
for (size_t i = 0; cs[i] != L'\0'; i++) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#include "common.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
// _(s) is already wgettext(s).c_str(), so let's not convert back to wcstring
|
||||
|
@ -110,7 +110,7 @@ static bool tok_is_string_character(wchar_t c, maybe_t<wchar_t> next) {
|
|||
return false;
|
||||
}
|
||||
case L'&': {
|
||||
if (!feature_test(features_t::ampersand_nobg_in_token)) return false;
|
||||
if (!feature_test(feature_flag_t::ampersand_nobg_in_token)) return false;
|
||||
bool next_is_string = next.has_value() && tok_is_string_character(*next, none());
|
||||
// Unlike in other shells, '&' is not special if followed by a string character.
|
||||
return next_is_string;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include "enum_set.h"
|
||||
#include "expand.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "future_feature_flags.h"
|
||||
#include "future_feature_flags.rs.h"
|
||||
#include "maybe.h"
|
||||
#include "path.h"
|
||||
#include "wcstringutil.h"
|
||||
|
@ -53,7 +53,7 @@ bool wildcard_has_internal(const wchar_t *s, size_t len) {
|
|||
bool wildcard_has(const wchar_t *str, size_t len) {
|
||||
assert(str != nullptr);
|
||||
const wchar_t *end = str + len;
|
||||
bool qmark_is_wild = !feature_test(features_t::qmark_noglob);
|
||||
bool qmark_is_wild = !feature_test(feature_flag_t::qmark_noglob);
|
||||
// Fast check for * or ?; if none there is no wildcard.
|
||||
// Note some strings contain * but no wildcards, e.g. if they are quoted.
|
||||
if (std::find(str, end, L'*') == end && (!qmark_is_wild || std::find(str, end, L'?') == end)) {
|
||||
|
|
Loading…
Reference in a new issue