Implement (but do not yet adopt) Environment in Rust

This implements the primary environment stack, and other environments such
as the null and snapshot environments, in Rust. These are used to implement
the push and pop from block scoped commands such as `for` and `begin`, and
also function calls.
This commit is contained in:
ridiculousfish 2023-04-29 19:58:51 -07:00 committed by Peter Ammon
parent 0681b6b53a
commit 8ec1467dda
13 changed files with 1814 additions and 23 deletions

View File

@ -96,6 +96,10 @@ pub fn with_abbrs_mut<R>(cb: impl FnOnce(&mut AbbreviationSet) -> R) -> R {
cb(&mut abbrs_g)
}
pub fn abbrs_get_set() -> MutexGuard<'static, AbbreviationSet> {
abbrs.lock().unwrap()
}
/// Controls where in the command line abbreviations may expand.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Position {

View File

@ -1,44 +1,435 @@
#![allow(unused_variables)]
//! Prototypes for functions for manipulating fish script variables.
use super::environment_impl::{
colon_split, uvars, EnvMutex, EnvMutexGuard, EnvScopedImpl, EnvStackImpl, ModResult,
UVAR_SCOPE_IS_GLOBAL,
};
use crate::abbrs::{abbrs_get_set, Abbreviation, Position};
use crate::common::{unescape_string, UnescapeStringStyle};
use crate::env::{EnvMode, EnvStackSetResult, EnvVar, Statuses};
use crate::event::Event;
use crate::ffi::{self, env_universal_t, universal_notifier_t};
use crate::flog::FLOG;
use crate::global_safety::RelaxedAtomicBool;
use crate::null_terminated_array::OwningNullTerminatedArray;
use crate::path::path_make_canonical;
use crate::wchar::{wstr, WExt, WString, L};
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use crate::wcstringutil::join_strings;
use crate::wutil::{wgetcwd, wgettext};
use crate::env::{EnvMode, EnvVar};
use crate::wchar::{wstr, WString};
use autocxx::WithinUniquePtr;
use cxx::UniquePtr;
use lazy_static::lazy_static;
use libc::c_int;
use std::sync::{Arc, Mutex};
// Limit `read` to 100 MiB (bytes not wide chars) by default. This can be overridden by the
// fish_read_limit variable.
const DEFAULT_READ_BYTE_LIMIT: usize = 100 * 1024 * 1024;
pub static mut read_byte_limit: usize = DEFAULT_READ_BYTE_LIMIT;
pub static mut curses_initialized: bool = true;
/// TODO: migrate to history once ported.
const DFLT_FISH_HISTORY_SESSION_ID: &wstr = L!("fish");
// Universal variables instance.
lazy_static! {
static ref UVARS: Mutex<UniquePtr<env_universal_t>> = Mutex::new(env_universal_t::new_unique());
}
/// Set when a universal variable has been modified but not yet been written to disk via sync().
static UVARS_LOCALLY_MODIFIED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
/// Convert an EnvVar to an FFI env_var_t.
fn env_var_to_ffi(var: EnvVar) -> cxx::UniquePtr<ffi::env_var_t> {
ffi::env_var_t::new_ffi(Box::into_raw(Box::from(var)).cast()).within_unique_ptr()
}
/// An environment is read-only access to variable values.
pub trait Environment {
/// Get a variable by name using default flags.
fn get(&self, name: &wstr) -> Option<EnvVar> {
todo!()
self.getf(name, EnvMode::DEFAULT)
}
fn getf(&self, name: &wstr, mode: EnvMode) -> Option<EnvVar> {
todo!()
/// Get a variable by name using the specified flags.
fn getf(&self, name: &wstr, mode: EnvMode) -> Option<EnvVar>;
/// Return the list of variable names.
fn get_names(&self, flags: EnvMode) -> Vec<WString>;
/// Returns PWD with a terminating slash.
fn get_pwd_slash(&self) -> WString {
// Return "/" if PWD is missing.
// See https://github.com/fish-shell/fish-shell/issues/5080
let Some(var) = self.get(L!("PWD")) else {
return WString::from("/");
};
let mut pwd = WString::new();
if var.is_empty() {
pwd = var.as_string();
}
if !pwd.ends_with('/') {
pwd.push('/');
}
pwd
}
/// Get a variable by name using default flags, unless it is empty.
fn get_unless_empty(&self, name: &wstr) -> Option<EnvVar> {
todo!()
self.getf_unless_empty(name, EnvMode::DEFAULT)
}
/// Get a variable by name using the given flags, unless it is empty.
fn getf_unless_empty(&self, name: &wstr, mode: EnvMode) -> Option<EnvVar> {
todo!()
let var = self.getf(name, mode)?;
if !var.is_empty() {
return Some(var);
}
None
}
}
pub enum EnvStackSetResult {
ENV_OK,
/// The null environment contains nothing.
pub struct EnvNull;
impl EnvNull {
pub fn new() -> EnvNull {
EnvNull
}
}
impl Environment for EnvNull {
fn getf(&self, _name: &wstr, _mode: EnvMode) -> Option<EnvVar> {
None
}
fn get_names(&self, _flags: EnvMode) -> Vec<WString> {
Vec::new()
}
}
/// An immutable environment, used in snapshots.
pub struct EnvScoped {
inner: EnvMutex<EnvScopedImpl>,
}
impl EnvScoped {
fn from_impl(inner: EnvMutex<EnvScopedImpl>) -> EnvScoped {
EnvScoped { inner }
}
fn lock(&self) -> EnvMutexGuard<EnvScopedImpl> {
self.inner.lock()
}
}
/// A mutable environment which allows scopes to be pushed and popped.
/// This backs the parser's "vars".
pub struct EnvStack {
inner: EnvMutex<EnvStackImpl>,
}
pub struct EnvStack {}
impl Environment for EnvStack {}
impl EnvStack {
fn new() -> EnvStack {
EnvStack {
inner: EnvStackImpl::new(),
}
}
fn lock(&self) -> EnvMutexGuard<EnvStackImpl> {
self.inner.lock()
}
/// \return whether we are the principal stack.
pub fn is_principal(&self) -> bool {
self as *const Self == Arc::as_ptr(&*PRINCIPAL_STACK)
}
/// Helpers to get and set the proc statuses.
/// These correspond to $status and $pipestatus.
pub fn get_last_statuses(&self) -> Statuses {
self.lock().base.get_last_statuses().clone()
}
pub fn get_last_status(&self) -> c_int {
self.lock().base.get_last_statuses().status
}
pub fn set_last_statuses(&self, statuses: Statuses) {
self.lock().base.set_last_statuses(statuses);
}
/// Sets the variable with the specified name to the given values.
pub fn set(&self, key: &wstr, mode: EnvMode, mut vals: Vec<WString>) -> EnvStackSetResult {
// Historical behavior.
if vals.len() == 1 && (key == "PWD" || key == "HOME") {
path_make_canonical(vals.first_mut().unwrap());
}
// Hacky stuff around PATH and CDPATH: #3914.
// Not MANPATH; see #4158.
// Replace empties with dot. Note we ignore pathvar here.
if key == "PATH" || key == "CDPATH" {
// Split on colons.
let mut munged_vals = colon_split(&vals);
// Replace empties with dots.
for val in munged_vals.iter_mut() {
if val.is_empty() {
val.push('.');
}
}
vals = munged_vals;
}
let ret: ModResult = self.lock().set(key, mode, vals);
if ret.status == EnvStackSetResult::ENV_OK {
// If we modified the global state, or we are principal, then dispatch changes.
// Important to not hold the lock here.
if ret.global_modified || self.is_principal() {
ffi::env_dispatch_var_change_ffi(&key.to_ffi() /* , self */);
}
}
// Mark if we modified a uvar.
if ret.uvar_modified {
UVARS_LOCALLY_MODIFIED.store(true);
}
ret.status
}
/// Sets the variable with the specified name to a single value.
pub fn set_one(&self, key: &wstr, mode: EnvMode, val: WString) -> EnvStackSetResult {
todo!()
self.set(key, mode, vec![val])
}
/// Sets the variable with the specified name to no values.
pub fn set_empty(&self, key: &wstr, mode: EnvMode) -> EnvStackSetResult {
self.set(key, mode, Vec::new())
}
/// Update the PWD variable based on the result of getcwd.
pub fn set_pwd_from_getcwd(&self) {
let cwd = wgetcwd();
if cwd.is_empty() {
FLOG!(
error,
wgettext!(
"Could not determine current working directory. Is your locale set correctly?"
)
);
}
self.set_one(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, cwd);
}
/// Remove environment variable.
///
/// \param key The name of the variable to remove
/// \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If
/// this is a user request, read-only variables can not be removed. The mode may also specify
/// the scope of the variable that should be erased.
///
/// \return the set result.
pub fn remove(&self, key: &wstr, mode: EnvMode) -> EnvStackSetResult {
let ret = self.lock().remove(key, mode);
#[allow(clippy::collapsible_if)]
if ret.status == EnvStackSetResult::ENV_OK {
if ret.global_modified || self.is_principal() {
// Important to not hold the lock here.
ffi::env_dispatch_var_change_ffi(&key.to_ffi() /*, self */);
}
}
if ret.uvar_modified {
UVARS_LOCALLY_MODIFIED.store(true);
}
ret.status
}
/// Push the variable stack. Used for implementing local variables for functions and for-loops.
pub fn push(&self, new_scope: bool) {
let mut imp = self.lock();
if new_scope {
imp.push_shadowing();
} else {
imp.push_nonshadowing();
}
}
/// Pop the variable stack. Used for implementing local variables for functions and for-loops.
pub fn pop(&self) {
let popped = self.lock().pop();
// Only dispatch variable changes if we are the principal environment.
if self.is_principal() {
// TODO: we would like to coalesce locale / curses changes, so that we only re-initialize
// once.
for key in popped {
ffi::env_dispatch_var_change_ffi(&key.to_ffi() /*, self */);
}
}
}
/// Returns an array containing all exported variables in a format suitable for execv.
pub fn export_array(&self) -> Arc<OwningNullTerminatedArray> {
self.lock().base.export_array()
}
/// Snapshot this environment. This means returning a read-only copy. Local variables are copied
/// but globals are shared (i.e. changes to global will be visible to this snapshot).
pub fn snapshot(&self) -> Box<dyn Environment> {
let scoped = EnvScoped::from_impl(self.lock().base.snapshot());
Box::new(scoped)
}
/// Synchronizes universal variable changes.
/// If \p always is set, perform synchronization even if there's no pending changes from this
/// instance (that is, look for changes from other fish instances).
/// \return a list of events for changed variables.
#[allow(clippy::vec_box)]
pub fn universal_sync(&self, always: bool) -> Vec<Box<Event>> {
if UVAR_SCOPE_IS_GLOBAL.load() {
return Vec::new();
}
if !always && !UVARS_LOCALLY_MODIFIED.load() {
return Vec::new();
}
UVARS_LOCALLY_MODIFIED.store(false);
let mut unused = autocxx::c_int(0);
let sync_res_ptr = uvars().as_mut().unwrap().sync_ffi().within_unique_ptr();
let sync_res = sync_res_ptr.as_ref().unwrap();
if sync_res.get_changed() {
universal_notifier_t::default_notifier_ffi(std::pin::Pin::new(&mut unused))
.post_notification();
}
// React internally to changes to special variables like LANG, and populate on-variable events.
let mut result = Vec::new();
#[allow(unreachable_code)]
for idx in 0..sync_res.count() {
let name = sync_res.get_key(idx).from_ffi();
ffi::env_dispatch_var_change_ffi(&name.to_ffi() /* , self */);
let evt = if sync_res.get_is_erase(idx) {
Event::variable_erase(name)
} else {
Event::variable_set(name)
};
result.push(Box::new(evt));
}
result
}
/// A variable stack that only represents globals.
/// Do not push or pop from this.
pub fn globals() -> &'static EnvStackRef {
&GLOBALS
}
/// Access the principal variable stack, associated with the principal parser.
pub fn principal() -> &'static EnvStackRef {
&PRINCIPAL_STACK
}
}
impl EnvStack {
pub fn globals() -> &'static dyn Environment {
todo!()
impl Environment for EnvScoped {
fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
self.lock().getf(key, mode)
}
fn get_names(&self, flags: EnvMode) -> Vec<WString> {
self.lock().get_names(flags)
}
fn get_pwd_slash(&self) -> WString {
self.lock().get_pwd_slash()
}
}
/// Necessary for Arc<EnvStack> to be sync.
/// Safety: again, the global lock.
unsafe impl Send for EnvStack {}
impl Environment for EnvStack {
fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
self.lock().getf(key, mode)
}
fn get_names(&self, flags: EnvMode) -> Vec<WString> {
self.lock().get_names(flags)
}
fn get_pwd_slash(&self) -> WString {
self.lock().get_pwd_slash()
}
}
pub type EnvStackRef = Arc<EnvStack>;
// A variable stack that only represents globals.
// Do not push or pop from this.
lazy_static! {
static ref GLOBALS: EnvStackRef = Arc::new(EnvStack::new());
}
// Our singleton "principal" stack.
lazy_static! {
static ref PRINCIPAL_STACK: EnvStackRef = Arc::new(EnvStack::new());
}
// Note: this is an incomplete port of env_init(); the rest remains in C++.
pub fn env_init(do_uvars: bool) {
if !do_uvars {
UVAR_SCOPE_IS_GLOBAL.store(true);
} else {
// let vars = EnvStack::principal();
// Set up universal variables using the default path.
let callbacks = uvars()
.as_mut()
.unwrap()
.initialize_ffi()
.within_unique_ptr();
let callbacks = callbacks.as_ref().unwrap();
for idx in 0..callbacks.count() {
ffi::env_dispatch_var_change_ffi(callbacks.get_key(idx) /* , vars */);
}
// Do not import variables that have the same name and value as
// an exported universal variable. See issues #5258 and #5348.
let mut table = uvars()
.as_ref()
.unwrap()
.get_table_ffi()
.within_unique_ptr();
for idx in 0..table.count() {
// autocxx gets confused when a value goes Rust -> Cxx -> Rust.
let uvar = table.as_mut().unwrap().get_var(idx).from_ffi();
if !uvar.exports() {
continue;
}
let name: &wstr = table.get_name(idx).as_wstr();
// Look for a global exported variable with the same name.
let global = EnvStack::globals().getf(name, EnvMode::GLOBAL | EnvMode::EXPORT);
if global.is_some() && global.unwrap().as_string() == uvar.as_string() {
EnvStack::globals().remove(name, EnvMode::GLOBAL | EnvMode::EXPORT);
}
}
// Import any abbreviations from uvars.
// Note we do not dynamically react to changes.
let prefix = L!("_fish_abbr_");
let prefix_len = prefix.char_count();
let from_universal = true;
let mut abbrs = abbrs_get_set();
for idx in 0..table.count() {
let name: &wstr = table.get_name(idx).as_wstr();
if !name.starts_with(prefix) {
continue;
}
let escaped_name = name.slice_from(prefix_len);
if let Some(name) = unescape_string(escaped_name, UnescapeStringStyle::Var) {
let key = name.clone();
let uvar = table.get_var(idx).from_ffi();
let replacement: WString = join_strings(uvar.as_list(), ' ');
abbrs.add(Abbreviation::new(
name,
key,
replacement,
Position::Command,
from_universal,
));
}
}
}
}

1226
fish-rust/src/env/environment_impl.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
mod env_ffi;
pub mod environment;
mod environment_impl;
pub mod var;
pub use env_ffi::EnvStackSetResult;
pub use environment::*;
pub use var::*;

View File

@ -21,6 +21,8 @@ include_cpp! {
#include "builtin.h"
#include "common.h"
#include "env.h"
#include "env_dispatch.h"
#include "env_universal_common.h"
#include "event.h"
#include "fallback.h"
#include "fds.h"
@ -29,11 +31,13 @@ include_cpp! {
#include "function.h"
#include "highlight.h"
#include "io.h"
#include "kill.h"
#include "parse_constants.h"
#include "parser.h"
#include "parse_util.h"
#include "path.h"
#include "proc.h"
#include "reader.h"
#include "tokenizer.h"
#include "wildcard.h"
#include "wutil.h"
@ -51,8 +55,17 @@ include_cpp! {
generate_pod!("pipes_ffi_t")
generate!("environment_t")
generate!("env_dispatch_var_change_ffi")
generate!("env_stack_t")
generate!("env_var_t")
generate!("env_universal_t")
generate!("env_universal_sync_result_t")
generate!("callback_data_t")
generate!("universal_notifier_t")
generate!("var_table_ffi_t")
generate!("event_list_ffi_t")
generate!("make_pipes_ffi")
generate!("get_flog_file_fd")
@ -116,6 +129,10 @@ include_cpp! {
generate!("path_get_paths_ffi")
generate!("colorize_shell")
generate!("reader_status_count")
generate!("kill_entries_ffi")
generate!("get_history_variable_text_ffi")
}
impl parser_t {
@ -174,6 +191,8 @@ impl parser_t {
}
}
unsafe impl Send for env_universal_t {}
impl environment_t {
/// Helper to get a variable as a string, using the default flags.
pub fn get_as_string(&self, name: &wstr) -> Option<WString> {
@ -287,6 +306,7 @@ pub trait Repin {
// Implement Repin for our types.
impl Repin for block_t {}
impl Repin for env_stack_t {}
impl Repin for env_universal_t {}
impl Repin for io_streams_t {}
impl Repin for job_t {}
impl Repin for output_stream_t {}

View File

@ -1619,3 +1619,38 @@ void unsetenv_lock(const char *name) {
scoped_lock locker(s_setenv_lock);
unsetenv(name);
}
wcstring_list_ffi_t get_history_variable_text_ffi(const wcstring &fish_history_val) {
wcstring_list_ffi_t out{};
std::shared_ptr<history_t> history = commandline_get_state().history;
if (!history) {
// Effective duplication of history_session_id().
wcstring session_id{};
if (fish_history_val.empty()) {
// No session.
session_id.clear();
} else if (!valid_var_name(fish_history_val)) {
session_id = L"fish";
FLOGF(error,
_(L"History session ID '%ls' is not a valid variable name. "
L"Falling back to `%ls`."),
fish_history_val.c_str(), session_id.c_str());
} else {
// Valid session.
session_id = fish_history_val;
}
history = history_t::with_name(session_id);
}
if (history) {
history->get_history(out.vals);
}
return out;
}
event_list_ffi_t::event_list_ffi_t() = default;
void event_list_ffi_t::push(void *event_vp) {
auto event = static_cast<Event *>(event_vp);
assert(event && "Null event");
events.push_back(rust::Box<Event>::from_raw(event));
}

View File

@ -23,6 +23,20 @@
struct EnvVar;
#endif
/// FFI helper for events.
struct Event;
struct event_list_ffi_t {
event_list_ffi_t(const event_list_ffi_t &) = delete;
event_list_ffi_t &operator=(const event_list_ffi_t &) = delete;
event_list_ffi_t();
#if INCLUDE_RUST_HEADERS
std::vector<rust::Box<Event>> events{};
#endif
// Append an Event pointer, which came from Box::into_raw().
void push(void *event);
};
struct owning_null_terminated_array_t;
extern size_t read_byte_limit;
@ -342,4 +356,8 @@ void unsetenv_lock(const char *name);
/// This is a simple key->value map and not e.g. cut into paths.
const std::map<wcstring, wcstring> &env_get_inherited();
/// Populate the values in the "$history" variable.
/// fish_history_val is the value of the "$fish_history" variable, or "fish" if not set.
wcstring_list_ffi_t get_history_variable_text_ffi(const wcstring &fish_history_val);
#endif

View File

@ -195,6 +195,10 @@ void env_dispatch_var_change(const wcstring &key, env_stack_t &vars) {
s_var_dispatch_table->dispatch(key, vars);
}
void env_dispatch_var_change_ffi(const wcstring &key) {
return env_dispatch_var_change(key, env_stack_t::principal());
}
static void handle_fish_term_change(const env_stack_t &vars) {
update_fish_color_support(vars);
reader_schedule_prompt_repaint();

View File

@ -15,4 +15,8 @@ void env_dispatch_init(const environment_t &vars);
/// React to changes in variables like LANG which require running some code.
void env_dispatch_var_change(const wcstring &key, env_stack_t &vars);
/// FFI wrapper which always uses the principal stack.
/// TODO: pass in the variables directly.
void env_dispatch_var_change_ffi(const wcstring &key /*, env_stack_t &vars */);
#endif

View File

@ -236,6 +236,14 @@ maybe_t<env_var_t> env_universal_t::get(const wcstring &name) const {
return none();
}
std::unique_ptr<env_var_t> env_universal_t::get_ffi(const wcstring &name) const {
if (auto var = this->get(name)) {
return make_unique<env_var_t>(var.acquire());
} else {
return nullptr;
}
}
maybe_t<env_var_t::env_var_flags_t> env_universal_t::get_flags(const wcstring &name) const {
auto where = vars.find(name);
if (where != vars.end()) {
@ -1430,3 +1438,11 @@ bool universal_notifier_t::notification_fd_became_readable(int fd) {
UNUSED(fd);
return false;
}
var_table_ffi_t::var_table_ffi_t(const var_table_t &table) {
for (const auto &kv : table) {
this->names.push_back(kv.first);
this->vars.push_back(kv.second);
}
}
var_table_ffi_t::~var_table_ffi_t() = default;

View File

@ -29,9 +29,37 @@ struct callback_data_t {
/// \return whether this callback represents an erased variable.
bool is_erase() const { return !val.has_value(); }
};
using callback_data_list_t = std::vector<callback_data_t>;
/// Wrapper type for ffi purposes.
struct env_universal_sync_result_t {
// List of callbacks.
callback_data_list_t list;
// Return value of sync().
bool changed;
bool get_changed() const { return changed; }
size_t count() const { return list.size(); }
const wcstring &get_key(size_t idx) const { return list.at(idx).key; }
bool get_is_erase(size_t idx) const { return list.at(idx).is_erase(); }
};
/// FFI helper to import our var_table into Rust.
/// Parallel names of strings and environment variables.
struct var_table_ffi_t {
std::vector<wcstring> names;
std::vector<env_var_t> vars;
size_t count() const { return names.size(); }
const wcstring &get_name(size_t idx) const { return names.at(idx); }
const env_var_t &get_var(size_t idx) const { return vars.at(idx); }
explicit var_table_ffi_t(const var_table_t &table);
~var_table_ffi_t();
};
// List of fish universal variable formats.
// This is exposed for testing.
enum class uvar_format_t { fish_2_x, fish_3_0, future };
@ -44,9 +72,17 @@ class env_universal_t {
// Construct an empty universal variables.
env_universal_t() = default;
// Construct inside a unique_ptr.
static std::unique_ptr<env_universal_t> new_unique() {
return std::unique_ptr<env_universal_t>(new env_universal_t());
}
// Get the value of the variable with the specified name.
maybe_t<env_var_t> get(const wcstring &name) const;
// Cover over get() for FFI purposes.
std::unique_ptr<env_var_t> get_ffi(const wcstring &name) const;
// \return flags from the variable with the given name.
maybe_t<env_var_t::env_var_flags_t> get_flags(const wcstring &name) const;
@ -59,8 +95,14 @@ class env_universal_t {
// Gets variable names.
std::vector<wcstring> get_names(bool show_exported, bool show_unexported) const;
// Cover over get_names for FFI.
wcstring_list_ffi_t get_names_ffi(bool show_exported, bool show_unexported) const {
return get_names(show_exported, show_unexported);
}
/// Get a view on the universal variable table.
const var_table_t &get_table() const { return vars; }
var_table_ffi_t get_table_ffi() const { return var_table_ffi_t(vars); }
/// Initialize this uvars for the default path.
/// This should be called at most once on any given instance.
@ -70,10 +112,30 @@ class env_universal_t {
/// This is exposed for testing only.
void initialize_at_path(callback_data_list_t &callbacks, wcstring path);
/// FFI helpers.
env_universal_sync_result_t initialize_ffi() {
env_universal_sync_result_t res{};
initialize(res.list);
return res;
}
env_universal_sync_result_t initialize_at_path_ffi(wcstring path) {
env_universal_sync_result_t res{};
initialize_at_path(res.list, std::move(path));
return res;
}
/// Reads and writes variables at the correct path. Returns true if modified variables were
/// written.
bool sync(callback_data_list_t &callbacks);
/// FFI helper.
env_universal_sync_result_t sync_ffi() {
callback_data_list_t callbacks;
bool changed = sync(callbacks);
return env_universal_sync_result_t{std::move(callbacks), changed};
}
/// Populate a variable table \p out_vars from a \p s string.
/// This is exposed for testing only.
/// \return the format of the file that we read.
@ -199,6 +261,9 @@ class universal_notifier_t {
// Default instance. Other instances are possible for testing.
static universal_notifier_t &default_notifier();
// FFI helper so autocxx can "deduce" the lifetime.
static universal_notifier_t &default_notifier_ffi(int &) { return default_notifier(); }
// Does a fast poll(). Returns true if changed.
virtual bool poll();

View File

@ -57,3 +57,5 @@ std::vector<wcstring> kill_entries() {
auto kill_list = s_kill_list.acquire();
return std::vector<wcstring>{kill_list->begin(), kill_list->end()};
}
wcstring_list_ffi_t kill_entries_ffi() { return kill_entries(); }

View File

@ -6,6 +6,7 @@
#define FISH_KILL_H
#include "common.h"
#include "wutil.h"
/// Replace the specified string in the killring.
void kill_replace(const wcstring &old, const wcstring &newv);
@ -22,4 +23,7 @@ wcstring kill_yank();
/// Get copy of kill ring as vector of strings
std::vector<wcstring> kill_entries();
/// Rust-friendly kill entries.
wcstring_list_ffi_t kill_entries_ffi();
#endif