mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-28 13:53:10 +00:00
Port env_dispatch to Rust and integrate with C++ code
This commit is contained in:
parent
cce78eeb43
commit
6638c78b30
18 changed files with 938 additions and 69 deletions
|
@ -114,7 +114,7 @@ set(FISH_BUILTIN_SRCS
|
||||||
# List of other sources.
|
# List of other sources.
|
||||||
set(FISH_SRCS
|
set(FISH_SRCS
|
||||||
src/ast.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp
|
src/ast.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/env.cpp src/env_universal_common.cpp src/event.cpp
|
||||||
src/exec.cpp src/expand.cpp src/fallback.cpp src/fish_indent_common.cpp src/fish_version.cpp
|
src/exec.cpp src/expand.cpp src/fallback.cpp src/fish_indent_common.cpp src/fish_version.cpp
|
||||||
src/flog.cpp src/function.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/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp
|
||||||
|
|
|
@ -40,9 +40,11 @@ fn main() {
|
||||||
let source_files = vec![
|
let source_files = vec![
|
||||||
"src/abbrs.rs",
|
"src/abbrs.rs",
|
||||||
"src/ast.rs",
|
"src/ast.rs",
|
||||||
"src/env/env_ffi.rs",
|
"src/builtins/shared.rs",
|
||||||
"src/event.rs",
|
|
||||||
"src/common.rs",
|
"src/common.rs",
|
||||||
|
"src/env/env_ffi.rs",
|
||||||
|
"src/env_dispatch.rs",
|
||||||
|
"src/event.rs",
|
||||||
"src/fd_monitor.rs",
|
"src/fd_monitor.rs",
|
||||||
"src/fd_readable_set.rs",
|
"src/fd_readable_set.rs",
|
||||||
"src/fds.rs",
|
"src/fds.rs",
|
||||||
|
@ -60,14 +62,13 @@ fn main() {
|
||||||
"src/signal.rs",
|
"src/signal.rs",
|
||||||
"src/smoke.rs",
|
"src/smoke.rs",
|
||||||
"src/termsize.rs",
|
"src/termsize.rs",
|
||||||
|
"src/threads.rs",
|
||||||
"src/timer.rs",
|
"src/timer.rs",
|
||||||
"src/tokenizer.rs",
|
"src/tokenizer.rs",
|
||||||
"src/topic_monitor.rs",
|
"src/topic_monitor.rs",
|
||||||
"src/threads.rs",
|
|
||||||
"src/trace.rs",
|
"src/trace.rs",
|
||||||
"src/util.rs",
|
"src/util.rs",
|
||||||
"src/wait_handle.rs",
|
"src/wait_handle.rs",
|
||||||
"src/builtins/shared.rs",
|
|
||||||
];
|
];
|
||||||
cxx_build::bridges(&source_files)
|
cxx_build::bridges(&source_files)
|
||||||
.flag_if_supported("-std=c++11")
|
.flag_if_supported("-std=c++11")
|
||||||
|
|
17
fish-rust/src/env/environment.rs
vendored
17
fish-rust/src/env/environment.rs
vendored
|
@ -5,6 +5,7 @@ use super::environment_impl::{
|
||||||
use crate::abbrs::{abbrs_get_set, Abbreviation, Position};
|
use crate::abbrs::{abbrs_get_set, Abbreviation, Position};
|
||||||
use crate::common::{unescape_string, UnescapeStringStyle};
|
use crate::common::{unescape_string, UnescapeStringStyle};
|
||||||
use crate::env::{EnvMode, EnvStackSetResult, EnvVar, Statuses};
|
use crate::env::{EnvMode, EnvStackSetResult, EnvVar, Statuses};
|
||||||
|
use crate::env_dispatch::env_dispatch_var_change;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use crate::ffi::{self, env_universal_t, universal_notifier_t};
|
use crate::ffi::{self, env_universal_t, universal_notifier_t};
|
||||||
use crate::flog::FLOG;
|
use crate::flog::FLOG;
|
||||||
|
@ -12,7 +13,7 @@ use crate::global_safety::RelaxedAtomicBool;
|
||||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||||
use crate::path::path_make_canonical;
|
use crate::path::path_make_canonical;
|
||||||
use crate::wchar::{wstr, WExt, WString, L};
|
use crate::wchar::{wstr, WExt, WString, L};
|
||||||
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
|
use crate::wchar_ffi::{AsWstr, WCharFromFFI};
|
||||||
use crate::wcstringutil::join_strings;
|
use crate::wcstringutil::join_strings;
|
||||||
use crate::wutil::{wgetcwd, wgettext};
|
use crate::wutil::{wgetcwd, wgettext};
|
||||||
|
|
||||||
|
@ -184,7 +185,7 @@ impl EnvStack {
|
||||||
// If we modified the global state, or we are principal, then dispatch changes.
|
// If we modified the global state, or we are principal, then dispatch changes.
|
||||||
// Important to not hold the lock here.
|
// Important to not hold the lock here.
|
||||||
if ret.global_modified || self.is_principal() {
|
if ret.global_modified || self.is_principal() {
|
||||||
ffi::env_dispatch_var_change_ffi(&key.to_ffi() /* , self */);
|
env_dispatch_var_change(key, self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mark if we modified a uvar.
|
// Mark if we modified a uvar.
|
||||||
|
@ -232,7 +233,7 @@ impl EnvStack {
|
||||||
if ret.status == EnvStackSetResult::ENV_OK {
|
if ret.status == EnvStackSetResult::ENV_OK {
|
||||||
if ret.global_modified || self.is_principal() {
|
if ret.global_modified || self.is_principal() {
|
||||||
// Important to not hold the lock here.
|
// Important to not hold the lock here.
|
||||||
ffi::env_dispatch_var_change_ffi(&key.to_ffi() /*, self */);
|
env_dispatch_var_change(key, self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ret.uvar_modified {
|
if ret.uvar_modified {
|
||||||
|
@ -259,7 +260,7 @@ impl EnvStack {
|
||||||
// TODO: we would like to coalesce locale / curses changes, so that we only re-initialize
|
// TODO: we would like to coalesce locale / curses changes, so that we only re-initialize
|
||||||
// once.
|
// once.
|
||||||
for key in popped {
|
for key in popped {
|
||||||
ffi::env_dispatch_var_change_ffi(&key.to_ffi() /*, self */);
|
env_dispatch_var_change(&key, self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,7 +303,7 @@ impl EnvStack {
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
for idx in 0..sync_res.count() {
|
for idx in 0..sync_res.count() {
|
||||||
let name = sync_res.get_key(idx).from_ffi();
|
let name = sync_res.get_key(idx).from_ffi();
|
||||||
ffi::env_dispatch_var_change_ffi(&name.to_ffi() /* , self */);
|
env_dispatch_var_change(&name, self);
|
||||||
let evt = if sync_res.get_is_erase(idx) {
|
let evt = if sync_res.get_is_erase(idx) {
|
||||||
Event::variable_erase(name)
|
Event::variable_erase(name)
|
||||||
} else {
|
} else {
|
||||||
|
@ -375,17 +376,17 @@ pub fn env_init(do_uvars: bool) {
|
||||||
if !do_uvars {
|
if !do_uvars {
|
||||||
UVAR_SCOPE_IS_GLOBAL.store(true);
|
UVAR_SCOPE_IS_GLOBAL.store(true);
|
||||||
} else {
|
} else {
|
||||||
// let vars = EnvStack::principal();
|
|
||||||
|
|
||||||
// Set up universal variables using the default path.
|
// Set up universal variables using the default path.
|
||||||
let callbacks = uvars()
|
let callbacks = uvars()
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.initialize_ffi()
|
.initialize_ffi()
|
||||||
.within_unique_ptr();
|
.within_unique_ptr();
|
||||||
|
let vars = EnvStack::principal();
|
||||||
let callbacks = callbacks.as_ref().unwrap();
|
let callbacks = callbacks.as_ref().unwrap();
|
||||||
for idx in 0..callbacks.count() {
|
for idx in 0..callbacks.count() {
|
||||||
ffi::env_dispatch_var_change_ffi(callbacks.get_key(idx) /* , vars */);
|
let name = callbacks.get_key(idx).from_ffi();
|
||||||
|
env_dispatch_var_change(&name, vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not import variables that have the same name and value as
|
// Do not import variables that have the same name and value as
|
||||||
|
|
820
fish-rust/src/env_dispatch.rs
Normal file
820
fish-rust/src/env_dispatch.rs
Normal file
|
@ -0,0 +1,820 @@
|
||||||
|
use crate::common::ToCString;
|
||||||
|
use crate::curses::{self, Term};
|
||||||
|
use crate::env::{setenv_lock, unsetenv_lock, EnvMode, EnvStack, Environment};
|
||||||
|
use crate::env::{CURSES_INITIALIZED, READ_BYTE_LIMIT, TERM_HAS_XN};
|
||||||
|
use crate::ffi::is_interactive_session;
|
||||||
|
use crate::flog::FLOGF;
|
||||||
|
use crate::output::ColorSupport;
|
||||||
|
use crate::wchar::L;
|
||||||
|
use crate::wchar::{wstr, WString};
|
||||||
|
use crate::wchar_ext::WExt;
|
||||||
|
use crate::wutil::fish_wcstoi;
|
||||||
|
use crate::wutil::wgettext;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
#[cxx::bridge]
|
||||||
|
mod env_dispatch_ffi {
|
||||||
|
extern "Rust" {
|
||||||
|
fn env_dispatch_init_ffi();
|
||||||
|
fn term_supports_setting_title() -> bool;
|
||||||
|
fn use_posix_spawn() -> bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of all locale environment variable names that might trigger (re)initializing of the locale
|
||||||
|
/// subsystem. These are only the variables we're possibly interested in.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const LOCALE_VARIABLES: [&wstr; 10] = [
|
||||||
|
L!("LANG"), L!("LANGUAGE"), L!("LC_ALL"),
|
||||||
|
L!("LC_COLLATE"), L!("LC_CTYPE"), L!("LC_MESSAGES"),
|
||||||
|
L!("LC_NUMERIC"), L!("LC_TIME"), L!("LOCPATH"),
|
||||||
|
L!("fish_allow_singlebyte_locale"),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const CURSES_VARIABLES: [&wstr; 3] = [
|
||||||
|
L!("TERM"), L!("TERMINFO"), L!("TERMINFO_DIRS")
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Whether to use `posix_spawn()` when possible.
|
||||||
|
static USE_POSIX_SPAWN: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Whether we think we can set the terminal title or not.
|
||||||
|
static CAN_SET_TERM_TITLE: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// The variable dispatch table. This is set at startup and cannot be modified after.
|
||||||
|
static VAR_DISPATCH_TABLE: once_cell::sync::Lazy<VarDispatchTable> =
|
||||||
|
once_cell::sync::Lazy::new(|| {
|
||||||
|
let mut table = VarDispatchTable::default();
|
||||||
|
|
||||||
|
for name in LOCALE_VARIABLES {
|
||||||
|
table.add_anon(name, handle_locale_change);
|
||||||
|
}
|
||||||
|
|
||||||
|
for name in CURSES_VARIABLES {
|
||||||
|
table.add_anon(name, handle_curses_change);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.add(L!("TZ"), handle_tz_change);
|
||||||
|
table.add_anon(L!("fish_term256"), handle_fish_term_change);
|
||||||
|
table.add_anon(L!("fish_term24bit"), handle_fish_term_change);
|
||||||
|
table.add_anon(L!("fish_escape_delay_ms"), update_wait_on_escape_ms);
|
||||||
|
table.add_anon(L!("fish_emoji_width"), guess_emoji_width);
|
||||||
|
table.add_anon(L!("fish_ambiguous_width"), handle_change_ambiguous_width);
|
||||||
|
table.add_anon(L!("LINES"), handle_term_size_change);
|
||||||
|
table.add_anon(L!("COLUMNS"), handle_term_size_change);
|
||||||
|
table.add_anon(L!("fish_complete_path"), handle_complete_path_change);
|
||||||
|
table.add_anon(L!("fish_function_path"), handle_function_path_change);
|
||||||
|
table.add_anon(L!("fish_read_limit"), handle_read_limit_change);
|
||||||
|
table.add_anon(L!("fish_history"), handle_fish_history_change);
|
||||||
|
table.add_anon(
|
||||||
|
L!("fish_autosuggestion_enabled"),
|
||||||
|
handle_autosuggestion_change,
|
||||||
|
);
|
||||||
|
table.add_anon(
|
||||||
|
L!("fish_use_posix_spawn"),
|
||||||
|
handle_fish_use_posix_spawn_change,
|
||||||
|
);
|
||||||
|
table.add_anon(L!("fish_trace"), handle_fish_trace);
|
||||||
|
table.add_anon(
|
||||||
|
L!("fish_cursor_selection_mode"),
|
||||||
|
handle_fish_cursor_selection_mode_change,
|
||||||
|
);
|
||||||
|
|
||||||
|
table
|
||||||
|
});
|
||||||
|
|
||||||
|
type NamedEnvCallback = fn(name: &wstr, env: &EnvStack);
|
||||||
|
type AnonEnvCallback = fn(env: &EnvStack);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct VarDispatchTable {
|
||||||
|
named_table: HashMap<&'static wstr, NamedEnvCallback>,
|
||||||
|
anon_table: HashMap<&'static wstr, AnonEnvCallback>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Delete this after input_common is ported (and pass the input_function function directly).
|
||||||
|
fn update_wait_on_escape_ms(vars: &EnvStack) {
|
||||||
|
let fish_escape_delay_ms = vars.get_unless_empty(L!("fish_escape_delay_ms"));
|
||||||
|
let var = crate::env::environment::env_var_to_ffi(fish_escape_delay_ms);
|
||||||
|
crate::ffi::update_wait_on_escape_ms_ffi(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarDispatchTable {
|
||||||
|
fn observes_var(&self, name: &wstr) -> bool {
|
||||||
|
self.named_table.contains_key(name) || self.anon_table.contains_key(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a callback for the variable `name`. We must not already be observing this variable.
|
||||||
|
pub fn add(&mut self, name: &'static wstr, callback: NamedEnvCallback) {
|
||||||
|
let prev = self.named_table.insert(name, callback);
|
||||||
|
assert!(
|
||||||
|
prev.is_none() && !self.anon_table.contains_key(name),
|
||||||
|
"Already observing {}",
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an callback for the variable `name`. We must not already be observing this variable.
|
||||||
|
pub fn add_anon(&mut self, name: &'static wstr, callback: AnonEnvCallback) {
|
||||||
|
let prev = self.anon_table.insert(name, callback);
|
||||||
|
assert!(
|
||||||
|
prev.is_none() && !self.named_table.contains_key(name),
|
||||||
|
"Already observing {}",
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch(&self, key: &wstr, vars: &EnvStack) {
|
||||||
|
if let Some(named) = self.named_table.get(key) {
|
||||||
|
(named)(key, vars);
|
||||||
|
}
|
||||||
|
if let Some(anon) = self.anon_table.get(key) {
|
||||||
|
(anon)(vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_timezone(var_name: &wstr, vars: &EnvStack) {
|
||||||
|
let var = vars.get_unless_empty(var_name).map(|v| v.as_string());
|
||||||
|
FLOGF!(
|
||||||
|
env_dispatch,
|
||||||
|
"handle_timezone() current timezone var:",
|
||||||
|
var_name,
|
||||||
|
"=>",
|
||||||
|
var.as_ref()
|
||||||
|
.map(|v| v.as_utfstr())
|
||||||
|
.unwrap_or(L!("MISSING/EMPTY")),
|
||||||
|
);
|
||||||
|
if let Some(value) = var {
|
||||||
|
setenv_lock(var_name, &value, true);
|
||||||
|
} else {
|
||||||
|
unsetenv_lock(var_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn tzset();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
tzset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the value of [`FISH_EMOJI_WIDTH`].
|
||||||
|
fn guess_emoji_width(vars: &EnvStack) {
|
||||||
|
use crate::fallback::FISH_EMOJI_WIDTH;
|
||||||
|
|
||||||
|
if let Some(width_str) = vars.get(L!("fish_emoji_width")) {
|
||||||
|
// The only valid values are 1 or 2; we default to 1 if it was an invalid int.
|
||||||
|
let new_width = fish_wcstoi(&width_str.as_string()).unwrap_or(1).clamp(1, 2);
|
||||||
|
FISH_EMOJI_WIDTH.store(new_width, Ordering::Relaxed);
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"Overriding default fish_emoji_width w/",
|
||||||
|
new_width
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let term = vars
|
||||||
|
.get(L!("TERM_PROGRAM"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.unwrap_or_else(WString::new);
|
||||||
|
// The format and contents of $TERM_PROGRAM_VERSION depend on $TERM_PROGRAM. Under
|
||||||
|
// Apple_Terminal, this is an integral value in the hundreds corresponding to the
|
||||||
|
// CFBundleVersion of Terminal.app; under iTerm, this is the version number which can contain
|
||||||
|
// multiple periods (e.g 3.4.19). Currently we only care about Apple_Terminal but the C++ code
|
||||||
|
// used wcstod() to parse at least the major.minor value of cases like the latter.
|
||||||
|
//
|
||||||
|
// TODO: Move this inside the Apple_Terminal branch and use i32::FromStr (i.e. str::parse())
|
||||||
|
// instead.
|
||||||
|
let version = vars
|
||||||
|
.get(L!("TERM_PROGRAM_VERSION"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.and_then(|v| {
|
||||||
|
let mut consumed = 0;
|
||||||
|
crate::wutil::wcstod::wcstod(&v, '.', &mut consumed).ok()
|
||||||
|
})
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
|
||||||
|
if term == "Apple_Terminal" && version as i32 >= 400 {
|
||||||
|
// Apple Terminal on High Sierra
|
||||||
|
FISH_EMOJI_WIDTH.store(2, Ordering::Relaxed);
|
||||||
|
FLOGF!(term_support, "default emoji width: 2 for", term);
|
||||||
|
} else if term == "iTerm.app" {
|
||||||
|
// iTerm2 now defaults to Unicode 9 sizes for anything after macOS 10.12
|
||||||
|
FISH_EMOJI_WIDTH.store(2, Ordering::Relaxed);
|
||||||
|
FLOGF!(term_support, "default emoji width 2 for iTerm2");
|
||||||
|
} else {
|
||||||
|
// Default to whatever the system's wcwidth gives for U+1F603, but only if it's at least
|
||||||
|
// 1 and at most 2.
|
||||||
|
let width = crate::fallback::wcwidth('😃').clamp(1, 2);
|
||||||
|
FISH_EMOJI_WIDTH.store(width, Ordering::Relaxed);
|
||||||
|
FLOGF!(term_support, "default emoji width:", width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// React to modifying the given variable.
|
||||||
|
pub fn env_dispatch_var_change(key: &wstr, vars: &EnvStack) {
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
// We want to ignore variable changes until the dispatch table is explicitly initialized.
|
||||||
|
if let Some(dispatch_table) = Lazy::get(&VAR_DISPATCH_TABLE) {
|
||||||
|
dispatch_table.dispatch(key, vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_fish_term_change(vars: &EnvStack) {
|
||||||
|
update_fish_color_support(vars);
|
||||||
|
crate::ffi::reader_schedule_prompt_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_change_ambiguous_width(vars: &EnvStack) {
|
||||||
|
let new_width = vars
|
||||||
|
.get(L!("fish_ambiguous_width"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
// We use the default value of 1 if it was an invalid int.
|
||||||
|
.and_then(|fish_ambiguous_width| fish_wcstoi(&fish_ambiguous_width).ok())
|
||||||
|
.unwrap_or(1)
|
||||||
|
// Clamp in case of negative values.
|
||||||
|
.max(0);
|
||||||
|
crate::fallback::FISH_AMBIGUOUS_WIDTH.store(new_width, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_term_size_change(vars: &EnvStack) {
|
||||||
|
crate::termsize::handle_columns_lines_var_change(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_fish_history_change(vars: &EnvStack) {
|
||||||
|
let fish_history = vars.get(L!("fish_history"));
|
||||||
|
let var = crate::env::env_var_to_ffi(fish_history);
|
||||||
|
crate::ffi::reader_change_history(&crate::ffi::history_session_id(var));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_fish_cursor_selection_mode_change(vars: &EnvStack) {
|
||||||
|
use crate::reader::CursorSelectionMode;
|
||||||
|
|
||||||
|
let inclusive = vars
|
||||||
|
.get(L!("fish_cursor_selection_mode"))
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.map(|v| v == "inclusive")
|
||||||
|
.unwrap_or(false);
|
||||||
|
let mode = if inclusive {
|
||||||
|
CursorSelectionMode::Inclusive
|
||||||
|
} else {
|
||||||
|
CursorSelectionMode::Exclusive
|
||||||
|
};
|
||||||
|
|
||||||
|
let mode = mode as u8;
|
||||||
|
crate::ffi::reader_change_cursor_selection_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_autosuggestion_change(vars: &EnvStack) {
|
||||||
|
// TODO: This was a call to reader_set_autosuggestion_enabled(vars) and
|
||||||
|
// reader::check_autosuggestion_enabled() should be private to the `reader` module.
|
||||||
|
crate::ffi::reader_set_autosuggestion_enabled_ffi(crate::reader::check_autosuggestion_enabled(
|
||||||
|
vars,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_function_path_change(_: &EnvStack) {
|
||||||
|
crate::ffi::function_invalidate_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_complete_path_change(_: &EnvStack) {
|
||||||
|
crate::ffi::complete_invalidate_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_tz_change(var_name: &wstr, vars: &EnvStack) {
|
||||||
|
handle_timezone(var_name, vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_locale_change(vars: &EnvStack) {
|
||||||
|
init_locale(vars);
|
||||||
|
// We need to re-guess emoji width because the locale might have changed to a multibyte one.
|
||||||
|
guess_emoji_width(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_curses_change(vars: &EnvStack) {
|
||||||
|
guess_emoji_width(vars);
|
||||||
|
init_curses(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_fish_use_posix_spawn_change(vars: &EnvStack) {
|
||||||
|
// Note that if the variable is missing or empty we default to true (if allowed).
|
||||||
|
if !allow_use_posix_spawn() {
|
||||||
|
USE_POSIX_SPAWN.store(false, Ordering::Relaxed);
|
||||||
|
} else if let Some(var) = vars.get(L!("fish_use_posix_spawn")) {
|
||||||
|
let use_posix_spawn =
|
||||||
|
var.is_empty() || crate::wcstringutil::bool_from_string(&var.as_string());
|
||||||
|
USE_POSIX_SPAWN.store(use_posix_spawn, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
USE_POSIX_SPAWN.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow the user to override the limits on how much data the `read` command will process. This is
|
||||||
|
/// primarily intended for testing, but could also be used directly by users in special situations.
|
||||||
|
fn handle_read_limit_change(vars: &EnvStack) {
|
||||||
|
let read_byte_limit = vars
|
||||||
|
.get_unless_empty(L!("fish_read_limit"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.and_then(|v| {
|
||||||
|
// We use fish_wcstoul() to support leading/trailing whitespace
|
||||||
|
match (crate::wutil::fish_wcstoul(&v).ok())
|
||||||
|
// wcstoul() returns a u64 but want a usize. Handle overflow on 32-bit platforms.
|
||||||
|
.and_then(|_u64| usize::try_from(_u64).ok())
|
||||||
|
{
|
||||||
|
Some(v) => Some(v),
|
||||||
|
None => {
|
||||||
|
// We intentionally warn here even in non-interactive mode.
|
||||||
|
FLOGF!(warning, "Ignoring invalid $fish_read_limit");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clippy should recognize comments in an empty match branch as a valid pattern!
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match read_byte_limit {
|
||||||
|
Some(new_limit) => READ_BYTE_LIMIT.store(new_limit, Ordering::Relaxed),
|
||||||
|
None => {
|
||||||
|
// TODO: reset READ_BYTE_LIMIT to the default value on receiving an invalid value
|
||||||
|
// instead of persisting the previous value, which may or may not have been the
|
||||||
|
// default.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_fish_trace(vars: &EnvStack) {
|
||||||
|
let enabled = vars.get_unless_empty(L!("fish_trace")).is_some();
|
||||||
|
crate::trace::trace_set_enabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env_dispatch_init(vars: &EnvStack) {
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
run_inits(vars);
|
||||||
|
// env_dispatch_var_change() purposely supresses change notifications until the dispatch table
|
||||||
|
// was initialized elsewhere (either explicitly as below or via deref of VAR_DISPATCH_TABLE).
|
||||||
|
Lazy::force(&VAR_DISPATCH_TABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env_dispatch_init_ffi() {
|
||||||
|
let vars = EnvStack::principal();
|
||||||
|
env_dispatch_init(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the subset of dispatch functions that need to be called at startup.
|
||||||
|
fn run_inits(vars: &EnvStack) {
|
||||||
|
init_locale(vars);
|
||||||
|
init_curses(vars);
|
||||||
|
guess_emoji_width(vars);
|
||||||
|
update_wait_on_escape_ms(vars);
|
||||||
|
handle_read_limit_change(vars);
|
||||||
|
handle_fish_use_posix_spawn_change(vars);
|
||||||
|
handle_fish_trace(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates our idea of whether we support term256 and term24bit (see issue #10222).
|
||||||
|
fn update_fish_color_support(vars: &EnvStack) {
|
||||||
|
// Detect or infer term256 support. If fish_term256 is set, we respect it. Otherwise, infer it
|
||||||
|
// from $TERM or use terminfo.
|
||||||
|
|
||||||
|
let term = vars
|
||||||
|
.get(L!("TERM"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.unwrap_or_else(WString::new);
|
||||||
|
let max_colors = curses::term().and_then(|term| term.max_colors);
|
||||||
|
let mut supports_256color = false;
|
||||||
|
let mut supports_24bit = false;
|
||||||
|
|
||||||
|
if let Some(fish_term256) = vars.get(L!("fish_term256")).map(|v| v.as_string()) {
|
||||||
|
// $fish_term256
|
||||||
|
supports_256color = crate::wcstringutil::bool_from_string(&fish_term256);
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"256-color support determined by $fish_term256:",
|
||||||
|
supports_256color
|
||||||
|
);
|
||||||
|
} else if term.find(L!("256color")).is_some() {
|
||||||
|
// TERM contains "256color": 256 colors explicitly supported.
|
||||||
|
supports_256color = true;
|
||||||
|
FLOGF!(term_support, "256-color support enabled for TERM", term);
|
||||||
|
} else if term.find(L!("xterm")).is_some() {
|
||||||
|
// Assume that all "xterm" terminals can handle 256
|
||||||
|
supports_256color = true;
|
||||||
|
FLOGF!(term_support, "256-color support enabled for TERM", term);
|
||||||
|
}
|
||||||
|
// See if terminfo happens to identify 256 colors
|
||||||
|
else if let Some(max_colors) = max_colors {
|
||||||
|
supports_256color = max_colors >= 256;
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"256-color support:",
|
||||||
|
max_colors,
|
||||||
|
"per termcap/terminfo entry for",
|
||||||
|
term
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(fish_term24bit) = vars.get(L!("fish_term24bit")).map(|v| v.as_string()) {
|
||||||
|
// $fish_term24bit
|
||||||
|
supports_24bit = crate::wcstringutil::bool_from_string(&fish_term24bit);
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"$fish_term24bit preference: 24-bit color",
|
||||||
|
if supports_24bit {
|
||||||
|
"enabled"
|
||||||
|
} else {
|
||||||
|
"disabled"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if vars.get(L!("STY")).is_some() || term.starts_with(L!("eterm")) {
|
||||||
|
// Screen and emacs' ansi-term swallow true-color sequences, so we ignore them unless
|
||||||
|
// force-enabled.
|
||||||
|
supports_24bit = false;
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"True-color support: disabled for eterm/screen"
|
||||||
|
);
|
||||||
|
} else if max_colors.unwrap_or(0) > 32767 {
|
||||||
|
// $TERM wins, xterm-direct reports 32767 colors and we assume that's the minimum as xterm
|
||||||
|
// is weird when it comes to color.
|
||||||
|
supports_24bit = true;
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"True-color support: enabled per termcap/terminfo for",
|
||||||
|
term,
|
||||||
|
"with",
|
||||||
|
max_colors.unwrap(),
|
||||||
|
"colors"
|
||||||
|
);
|
||||||
|
} else if let Some(ct) = vars.get(L!("COLORTERM")).map(|v| v.as_string()) {
|
||||||
|
// If someone sets $COLORTERM, that's the sort of color they want.
|
||||||
|
if ct == "truecolor" || ct == "24bit" {
|
||||||
|
supports_24bit = true;
|
||||||
|
}
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"True-color support",
|
||||||
|
if supports_24bit {
|
||||||
|
"enabled"
|
||||||
|
} else {
|
||||||
|
"disabled"
|
||||||
|
},
|
||||||
|
"per $COLORTERM",
|
||||||
|
ct
|
||||||
|
);
|
||||||
|
} else if vars.get(L!("KONSOLE_VERSION")).is_some()
|
||||||
|
|| vars.get(L!("KONSOLE_PROFILE_NAME")).is_some()
|
||||||
|
{
|
||||||
|
// All Konsole versions that use $KONSOLE_VERSION are new enough to support this, so no
|
||||||
|
// check is needed.
|
||||||
|
supports_24bit = true;
|
||||||
|
FLOGF!(term_support, "True-color support: enabled for Konsole");
|
||||||
|
} else if let Some(it) = vars.get(L!("ITERM_SESSION_ID")).map(|v| v.as_string()) {
|
||||||
|
// Supporting versions of iTerm include a colon here.
|
||||||
|
// We assume that if this is iTerm it can't also be st, so having this check inside is okay.
|
||||||
|
if !it.contains(':') {
|
||||||
|
supports_24bit = true;
|
||||||
|
FLOGF!(term_support, "True-color support: enabled for iTerm");
|
||||||
|
}
|
||||||
|
} else if term.starts_with("st-") {
|
||||||
|
supports_24bit = true;
|
||||||
|
FLOGF!(term_support, "True-color support: enabling for st");
|
||||||
|
} else if let Some(vte) = vars.get(L!("VTE_VERSION")).map(|v| v.as_string()) {
|
||||||
|
if fish_wcstoi(&vte).unwrap_or(0) > 3600 {
|
||||||
|
supports_24bit = true;
|
||||||
|
FLOGF!(
|
||||||
|
term_support,
|
||||||
|
"True-color support: enabled for VTE version",
|
||||||
|
vte
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut color_support = ColorSupport::NONE;
|
||||||
|
color_support.set(ColorSupport::TERM_256COLOR, supports_256color);
|
||||||
|
color_support.set(ColorSupport::TERM_24BIT, supports_24bit);
|
||||||
|
crate::output::output_set_color_support(color_support);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to initialize the terminfo/curses subsystem using our fallback terminal name. Do not set
|
||||||
|
/// `$TERM` to our fallback. We're only doing this in the hope of getting a functional shell.
|
||||||
|
/// If we launch an external command that uses `$TERM`, it should get the same value we were given,
|
||||||
|
/// if any.
|
||||||
|
fn initialize_curses_using_fallbacks(vars: &EnvStack) {
|
||||||
|
// xterm-256color is the most used terminal type by a massive margin, especially counting
|
||||||
|
// terminals that are mostly compatible.
|
||||||
|
const FALLBACKS: [&str; 4] = ["xterm-256color", "xterm", "ansi", "dumb"];
|
||||||
|
|
||||||
|
let current_term = vars
|
||||||
|
.get_unless_empty(L!("TERM"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.unwrap_or(Default::default());
|
||||||
|
|
||||||
|
for term in FALLBACKS {
|
||||||
|
// If $TERM is already set to the fallback name we're about to use, there's no point in
|
||||||
|
// seeing if the fallback name can be used.
|
||||||
|
if current_term == term {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `term` here is one of our hard-coded strings above; we can unwrap because we can
|
||||||
|
// guarantee it doesn't contain any interior NULs.
|
||||||
|
let term_cstr = CString::new(term).unwrap();
|
||||||
|
let success = curses::setup(Some(&term_cstr), libc::STDOUT_FILENO, |term| {
|
||||||
|
apply_term_hacks(vars, term)
|
||||||
|
})
|
||||||
|
.is_some();
|
||||||
|
if is_interactive_session() {
|
||||||
|
if success {
|
||||||
|
FLOGF!(warning, wgettext!("Using fallback terminal type"), term);
|
||||||
|
} else {
|
||||||
|
FLOGF!(
|
||||||
|
warning,
|
||||||
|
wgettext!("Could not set up terminal using the fallback terminal type"),
|
||||||
|
term,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply any platform- or environment-specific hacks to our curses [`Term`] instance.
|
||||||
|
fn apply_term_hacks(vars: &EnvStack, term: &mut Term) {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
// Hack in missing italics and dim capabilities omitted from macOS xterm-256color terminfo.
|
||||||
|
// Improves the user experience under Terminal.app and iTerm.
|
||||||
|
let term_prog = vars
|
||||||
|
.get(L!("TERM_PROGRAM"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.unwrap_or(WString::new());
|
||||||
|
if term_prog == "Apple_Terminal" || term_prog == "iTerm.app" {
|
||||||
|
if let Some(term_val) = vars.get(L!("TERM")).map(|v| v.as_string()) {
|
||||||
|
if term_val == "xterm-256color" {
|
||||||
|
const SITM_ESC: &[u8] = b"\x1B[3m";
|
||||||
|
const RITM_ESC: &[u8] = b"\x1B[23m";
|
||||||
|
const DIM_ESC: &[u8] = b"\x1B[2m";
|
||||||
|
|
||||||
|
if term.enter_italics_mode.is_none() {
|
||||||
|
term.enter_italics_mode = Some(SITM_ESC.to_cstring());
|
||||||
|
}
|
||||||
|
if term.exit_italics_mode.is_none() {
|
||||||
|
term.exit_italics_mode = Some(RITM_ESC.to_cstring());
|
||||||
|
}
|
||||||
|
if term.enter_dim_mode.is_none() {
|
||||||
|
term.enter_dim_mode = Some(DIM_ESC.to_cstring());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply any platform- or environment-specific hacks that don't involve a `Term` instance.
|
||||||
|
fn apply_non_term_hacks(vars: &EnvStack) {
|
||||||
|
// Midnight Commander tries to extract the last line of the prompt, and does so in a way that is
|
||||||
|
// broken if you do '\r' after it like we normally do.
|
||||||
|
// See https://midnight-commander.org/ticket/4258.
|
||||||
|
if vars.get(L!("MC_SID")).is_some() {
|
||||||
|
crate::ffi::screen_set_midnight_commander_hack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a pretty lame heuristic for detecting terminals that do not support setting the title.
|
||||||
|
/// If we recognise the terminal name as that of a virtual terminal, we assume it supports setting
|
||||||
|
/// the title. If we recognise it as that of a console, we assume it does not support setting the
|
||||||
|
/// title. Otherwise we check the ttyname and see if we believe it is a virtual terminal.
|
||||||
|
///
|
||||||
|
/// One situation in which this breaks down is with screen, since screen supports setting the
|
||||||
|
/// terminal title if the underlying terminal does so, but will print garbage on terminals that
|
||||||
|
/// don't. Since we can't see the underlying terminal below screen there is no way to fix this.
|
||||||
|
fn does_term_support_setting_title(vars: &EnvStack) -> bool {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const TITLE_TERMS: &[&wstr] = &[
|
||||||
|
L!("xterm"), L!("screen"), L!("tmux"), L!("nxterm"),
|
||||||
|
L!("rxvt"), L!("alacritty"), L!("wezterm"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let Some(term) = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let term: &wstr = term.as_ref();
|
||||||
|
|
||||||
|
let recognized = TITLE_TERMS.contains(&term)
|
||||||
|
|| term.starts_with(L!("xterm-"))
|
||||||
|
|| term.starts_with(L!("screen-"))
|
||||||
|
|| term.starts_with(L!("tmux-"));
|
||||||
|
if !recognized {
|
||||||
|
if [
|
||||||
|
L!("linux"),
|
||||||
|
L!("dumb"),
|
||||||
|
L!("vt100"), // NetBSD
|
||||||
|
L!("wsvt25"),
|
||||||
|
]
|
||||||
|
.contains(&term)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = [b'\0'; libc::PATH_MAX as usize];
|
||||||
|
let retval =
|
||||||
|
unsafe { libc::ttyname_r(libc::STDIN_FILENO, buf.as_mut_ptr().cast(), buf.len()) };
|
||||||
|
let buf = &buf[..buf.iter().position(|c| *c == b'\0').unwrap()];
|
||||||
|
if retval != 0
|
||||||
|
|| buf.windows(b"tty".len()).any(|w| w == b"tty")
|
||||||
|
|| buf.windows(b"/vc/".len()).any(|w| w == b"/vc/")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the curses subsystem
|
||||||
|
fn init_curses(vars: &EnvStack) {
|
||||||
|
for var_name in CURSES_VARIABLES {
|
||||||
|
if let Some(value) = vars
|
||||||
|
.getf_unless_empty(var_name, EnvMode::EXPORT)
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
{
|
||||||
|
FLOGF!(term_support, "curses var", var_name, "=", value);
|
||||||
|
setenv_lock(var_name, &value, true);
|
||||||
|
} else {
|
||||||
|
FLOGF!(term_support, "curses var", var_name, "is missing or empty");
|
||||||
|
unsetenv_lock(var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if curses::setup(None, libc::STDOUT_FILENO, |term| {
|
||||||
|
apply_term_hacks(vars, term)
|
||||||
|
})
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
if is_interactive_session() {
|
||||||
|
let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string());
|
||||||
|
FLOGF!(warning, wgettext!("Could not set up terminal."));
|
||||||
|
if let Some(term) = term {
|
||||||
|
FLOGF!(warning, wgettext!("TERM environment variable set to"), term);
|
||||||
|
FLOGF!(
|
||||||
|
warning,
|
||||||
|
wgettext!("Check that this terminal type is supported on this system.")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
FLOGF!(warning, wgettext!("TERM environment variable not set."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize_curses_using_fallbacks(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure hacks that apply regardless of whether we successfully init curses or not.
|
||||||
|
apply_non_term_hacks(vars);
|
||||||
|
|
||||||
|
// Store some global variables that reflect the term's capabilities
|
||||||
|
CAN_SET_TERM_TITLE.store(does_term_support_setting_title(vars), Ordering::Relaxed);
|
||||||
|
if let Some(term) = curses::term() {
|
||||||
|
TERM_HAS_XN.store(term.eat_newline_glitch, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_fish_color_support(vars);
|
||||||
|
// Invalidate the cached escape sequences since they may no longer be valid.
|
||||||
|
crate::ffi::screen_clear_layout_cache_ffi();
|
||||||
|
CURSES_INITIALIZED.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the locale subsystem
|
||||||
|
fn init_locale(vars: &EnvStack) {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const UTF8_LOCALES: &[&str] = &[
|
||||||
|
"C.UTF-8", "en_US.UTF-8", "en_GB.UTF-8", "de_DE.UTF-8", "C.utf8", "UTF-8",
|
||||||
|
];
|
||||||
|
|
||||||
|
let old_msg_locale = unsafe {
|
||||||
|
let old = libc::setlocale(libc::LC_MESSAGES, std::ptr::null());
|
||||||
|
// We have to make a copy because the subsequent setlocale() call to change the locale will
|
||||||
|
// invalidate the pointer from this setlocale() call.
|
||||||
|
CStr::from_ptr(old.cast()).to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
for var_name in LOCALE_VARIABLES {
|
||||||
|
let var = vars
|
||||||
|
.getf_unless_empty(var_name, EnvMode::EXPORT)
|
||||||
|
.map(|v| v.as_string());
|
||||||
|
if let Some(value) = var {
|
||||||
|
FLOGF!(env_locale, "locale var", var_name, "=", value);
|
||||||
|
setenv_lock(var_name, &value, true);
|
||||||
|
} else {
|
||||||
|
FLOGF!(env_locale, "locale var", var_name, "is missing or empty");
|
||||||
|
unsetenv_lock(var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let locale = unsafe { CStr::from_ptr(libc::setlocale(libc::LC_ALL, b"\0".as_ptr().cast())) };
|
||||||
|
|
||||||
|
// Try to get a multibyte-capable encoding.
|
||||||
|
// A "C" locale is broken for our purposes: any wchar function will break on it. So we try
|
||||||
|
// *really, really, really hard* to not have one.
|
||||||
|
let fix_locale = vars
|
||||||
|
.get_unless_empty(L!("fish_allow_singlebyte_locale"))
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.map(|allow_c| !crate::wcstringutil::bool_from_string(&allow_c))
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if fix_locale && crate::compat::MB_CUR_MAX() == 1 {
|
||||||
|
FLOGF!(env_locale, "Have singlebyte locale, trying to fix.");
|
||||||
|
for locale in UTF8_LOCALES {
|
||||||
|
unsafe {
|
||||||
|
let locale = CString::new(locale.to_owned()).unwrap();
|
||||||
|
libc::setlocale(libc::LC_CTYPE, locale.as_ptr());
|
||||||
|
}
|
||||||
|
if crate::compat::MB_CUR_MAX() > 1 {
|
||||||
|
FLOGF!(env_locale, "Fixed locale:", locale);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if crate::compat::MB_CUR_MAX() == 1 {
|
||||||
|
FLOGF!(env_locale, "Failed to fix locale.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We *always* use a C-locale for numbers because we want '.' (except for in printf).
|
||||||
|
unsafe {
|
||||||
|
libc::setlocale(libc::LC_NUMERIC, b"C\0".as_ptr().cast());
|
||||||
|
}
|
||||||
|
|
||||||
|
// See that we regenerate our special locale for numbers
|
||||||
|
crate::locale::invalidate_numeric_locale();
|
||||||
|
crate::common::fish_setlocale();
|
||||||
|
FLOGF!(
|
||||||
|
env_locale,
|
||||||
|
"init_locale() setlocale():",
|
||||||
|
locale.to_string_lossy()
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_msg_locale =
|
||||||
|
unsafe { CStr::from_ptr(libc::setlocale(libc::LC_MESSAGES, std::ptr::null())) };
|
||||||
|
FLOGF!(
|
||||||
|
env_locale,
|
||||||
|
"Old LC_MESSAGES locale:",
|
||||||
|
old_msg_locale.to_string_lossy()
|
||||||
|
);
|
||||||
|
FLOGF!(
|
||||||
|
env_locale,
|
||||||
|
"New LC_MESSAGES locale:",
|
||||||
|
new_msg_locale.to_string_lossy()
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "gettext")]
|
||||||
|
{
|
||||||
|
if old_msg_locale.as_c_str() != new_msg_locale {
|
||||||
|
// Make change known to GNU gettext.
|
||||||
|
extern "C" {
|
||||||
|
static mut _nl_msg_cat_cntr: libc::c_int;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
_nl_msg_cat_cntr += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_posix_spawn() -> bool {
|
||||||
|
USE_POSIX_SPAWN.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether or not we are running on an OS where we allow ourselves to use `posix_spawn()`.
|
||||||
|
const fn allow_use_posix_spawn() -> bool {
|
||||||
|
#![allow(clippy::if_same_then_else)]
|
||||||
|
#![allow(clippy::needless_bool)]
|
||||||
|
// OpenBSD's posix_spawn returns status 127 instead of erroring with ENOEXEC when faced with a
|
||||||
|
// shebang-less script. Disable posix_spawn on OpenBSD.
|
||||||
|
if cfg!(target_os = "openbsd") {
|
||||||
|
false
|
||||||
|
} else if cfg!(not(target_os = "linux")) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// The C++ code used __GLIBC_PREREQ(2, 24) && !defined(__UCLIBC__) to determine if we'll use
|
||||||
|
// posix_spawn() by default on Linux. Surprise! We don't have to worry about porting that
|
||||||
|
// logic here because the libc crate only supports 2.26+ atm.
|
||||||
|
// See https://github.com/rust-lang/libc/issues/1412
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if we think the terminal support setting its title.
|
||||||
|
pub fn term_supports_setting_title() -> bool {
|
||||||
|
CAN_SET_TERM_TITLE.load(Ordering::Relaxed)
|
||||||
|
}
|
|
@ -20,8 +20,8 @@ pub type wchar_t = u32;
|
||||||
include_cpp! {
|
include_cpp! {
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "complete.h"
|
||||||
#include "env.h"
|
#include "env.h"
|
||||||
#include "env_dispatch.h"
|
|
||||||
#include "env_universal_common.h"
|
#include "env_universal_common.h"
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "fallback.h"
|
#include "fallback.h"
|
||||||
|
@ -30,7 +30,9 @@ include_cpp! {
|
||||||
#include "flog.h"
|
#include "flog.h"
|
||||||
#include "function.h"
|
#include "function.h"
|
||||||
#include "highlight.h"
|
#include "highlight.h"
|
||||||
|
#include "history.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
#include "input_common.h"
|
||||||
#include "kill.h"
|
#include "kill.h"
|
||||||
#include "parse_constants.h"
|
#include "parse_constants.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
|
@ -38,6 +40,7 @@ include_cpp! {
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
#include "reader.h"
|
#include "reader.h"
|
||||||
|
#include "screen.h"
|
||||||
#include "tokenizer.h"
|
#include "tokenizer.h"
|
||||||
#include "wildcard.h"
|
#include "wildcard.h"
|
||||||
#include "wutil.h"
|
#include "wutil.h"
|
||||||
|
@ -55,7 +58,6 @@ include_cpp! {
|
||||||
|
|
||||||
generate_pod!("pipes_ffi_t")
|
generate_pod!("pipes_ffi_t")
|
||||||
generate!("environment_t")
|
generate!("environment_t")
|
||||||
generate!("env_dispatch_var_change_ffi")
|
|
||||||
generate!("env_stack_t")
|
generate!("env_stack_t")
|
||||||
generate!("env_var_t")
|
generate!("env_var_t")
|
||||||
generate!("env_universal_t")
|
generate!("env_universal_t")
|
||||||
|
@ -134,6 +136,19 @@ include_cpp! {
|
||||||
generate!("kill_entries_ffi")
|
generate!("kill_entries_ffi")
|
||||||
|
|
||||||
generate!("get_history_variable_text_ffi")
|
generate!("get_history_variable_text_ffi")
|
||||||
|
|
||||||
|
generate!("is_interactive_session")
|
||||||
|
generate!("set_interactive_session")
|
||||||
|
generate!("screen_set_midnight_commander_hack")
|
||||||
|
generate!("screen_clear_layout_cache_ffi")
|
||||||
|
generate!("reader_schedule_prompt_repaint")
|
||||||
|
generate!("reader_change_history")
|
||||||
|
generate!("history_session_id")
|
||||||
|
generate!("reader_change_cursor_selection_mode")
|
||||||
|
generate!("reader_set_autosuggestion_enabled_ffi")
|
||||||
|
generate!("function_invalidate_path")
|
||||||
|
generate!("complete_invalidate_path")
|
||||||
|
generate!("update_wait_on_escape_ms_ffi")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl parser_t {
|
impl parser_t {
|
||||||
|
|
|
@ -20,6 +20,7 @@ mod color;
|
||||||
mod compat;
|
mod compat;
|
||||||
mod curses;
|
mod curses;
|
||||||
mod env;
|
mod env;
|
||||||
|
mod env_dispatch;
|
||||||
mod event;
|
mod event;
|
||||||
mod expand;
|
mod expand;
|
||||||
mod fallback;
|
mod fallback;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Support for exposing the terminal size.
|
// Support for exposing the terminal size.
|
||||||
use crate::common::assert_sync;
|
use crate::common::assert_sync;
|
||||||
use crate::env::EnvMode;
|
use crate::env::{EnvMode, Environment};
|
||||||
use crate::ffi::{environment_t, parser_t, Repin};
|
use crate::ffi::{environment_t, parser_t, Repin};
|
||||||
use crate::flog::FLOG;
|
use crate::flog::FLOG;
|
||||||
use crate::wchar::{WString, L};
|
use crate::wchar::{WString, L};
|
||||||
|
@ -16,9 +16,11 @@ mod termsize_ffi {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Termsize {
|
pub struct Termsize {
|
||||||
/// Width of the terminal, in columns.
|
/// Width of the terminal, in columns.
|
||||||
|
// TODO: Change to u32
|
||||||
pub width: isize,
|
pub width: isize,
|
||||||
|
|
||||||
/// Height of the terminal, in rows.
|
/// Height of the terminal, in rows.
|
||||||
|
// TODO: Change to u32
|
||||||
pub height: isize,
|
pub height: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +226,34 @@ impl TermsizeContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note that COLUMNS and/or LINES global variables changed.
|
/// Note that COLUMNS and/or LINES global variables changed.
|
||||||
fn handle_columns_lines_var_change(&self, vars: &environment_t) {
|
fn handle_columns_lines_var_change(&self, vars: &dyn Environment) {
|
||||||
|
// Do nothing if we are the ones setting it.
|
||||||
|
if self.setting_env_vars.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Construct a new termsize from COLUMNS and LINES, then set it in our data.
|
||||||
|
let new_termsize = Termsize {
|
||||||
|
width: vars
|
||||||
|
.getf(L!("COLUMNS"), EnvMode::GLOBAL)
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.and_then(|v| fish_wcstoi(&v).ok().map(|h| h as isize))
|
||||||
|
.unwrap_or(Termsize::DEFAULT_WIDTH),
|
||||||
|
height: vars
|
||||||
|
.getf(L!("LINES"), EnvMode::GLOBAL)
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.and_then(|v| fish_wcstoi(&v).ok().map(|h| h as isize))
|
||||||
|
.unwrap_or(Termsize::DEFAULT_HEIGHT),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store our termsize as an environment override.
|
||||||
|
self.data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.mark_override_from_env(new_termsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note that COLUMNS and/or LINES global variables changed.
|
||||||
|
fn handle_columns_lines_var_change_ffi(&self, vars: &environment_t) {
|
||||||
// Do nothing if we are the ones setting it.
|
// Do nothing if we are the ones setting it.
|
||||||
if self.setting_env_vars.load(Ordering::Relaxed) {
|
if self.setting_env_vars.load(Ordering::Relaxed) {
|
||||||
return;
|
return;
|
||||||
|
@ -278,13 +307,16 @@ pub fn termsize_last() -> Termsize {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the COLUMNS or LINES variables are changed.
|
/// Called when the COLUMNS or LINES variables are changed.
|
||||||
/// The pointer is to an environment_t, but has the wrong type to satisfy cxx.
|
pub fn handle_columns_lines_var_change(vars: &dyn Environment) {
|
||||||
pub fn handle_columns_lines_var_change_ffi(vars_ptr: *const u8) {
|
|
||||||
assert!(!vars_ptr.is_null());
|
|
||||||
let vars: &environment_t = unsafe { &*(vars_ptr as *const environment_t) };
|
|
||||||
SHARED_CONTAINER.handle_columns_lines_var_change(vars);
|
SHARED_CONTAINER.handle_columns_lines_var_change(vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_columns_lines_var_change_ffi(vars_ptr: *const u8) {
|
||||||
|
assert!(!vars_ptr.is_null());
|
||||||
|
let vars: &environment_t = unsafe { &*(vars_ptr.cast()) };
|
||||||
|
SHARED_CONTAINER.handle_columns_lines_var_change_ffi(vars);
|
||||||
|
}
|
||||||
|
|
||||||
/// Called to initialize the termsize.
|
/// Called to initialize the termsize.
|
||||||
/// The pointer is to an environment_t, but has the wrong type to satisfy cxx.
|
/// The pointer is to an environment_t, but has the wrong type to satisfy cxx.
|
||||||
pub fn termsize_initialize_ffi(vars_ptr: *const u8) -> Termsize {
|
pub fn termsize_initialize_ffi(vars_ptr: *const u8) -> Termsize {
|
||||||
|
@ -349,13 +381,13 @@ add_test!("test_termsize", || {
|
||||||
// Now the tty's termsize doesn't matter.
|
// Now the tty's termsize doesn't matter.
|
||||||
parser.set_var(L!("COLUMNS"), &[L!("75")], env_global);
|
parser.set_var(L!("COLUMNS"), &[L!("75")], env_global);
|
||||||
parser.set_var(L!("LINES"), &[L!("150")], env_global);
|
parser.set_var(L!("LINES"), &[L!("150")], env_global);
|
||||||
ts.handle_columns_lines_var_change(parser.get_var_stack_env());
|
ts.handle_columns_lines_var_change_ffi(parser.get_var_stack_env());
|
||||||
assert_eq!(ts.last(), Termsize::new(75, 150));
|
assert_eq!(ts.last(), Termsize::new(75, 150));
|
||||||
assert_eq!(parser.var_as_string(L!("COLUMNS")).unwrap(), "75");
|
assert_eq!(parser.var_as_string(L!("COLUMNS")).unwrap(), "75");
|
||||||
assert_eq!(parser.var_as_string(L!("LINES")).unwrap(), "150");
|
assert_eq!(parser.var_as_string(L!("LINES")).unwrap(), "150");
|
||||||
|
|
||||||
parser.set_var(L!("COLUMNS"), &[L!("33")], env_global);
|
parser.set_var(L!("COLUMNS"), &[L!("33")], env_global);
|
||||||
ts.handle_columns_lines_var_change(parser.get_var_stack_env());
|
ts.handle_columns_lines_var_change_ffi(parser.get_var_stack_env());
|
||||||
assert_eq!(ts.last(), Termsize::new(33, 150));
|
assert_eq!(ts.last(), Termsize::new(33, 150));
|
||||||
|
|
||||||
// Oh it got SIGWINCH, now the tty matters again.
|
// Oh it got SIGWINCH, now the tty matters again.
|
||||||
|
|
|
@ -307,9 +307,8 @@ fn spawn_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) -> bool {
|
||||||
///
|
///
|
||||||
/// This function is always defined but is a no-op if not running under ASAN. This is to make it
|
/// This function is always defined but is a no-op if not running under ASAN. This is to make it
|
||||||
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
|
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
|
||||||
pub fn asan_maybe_exit(#[allow(unused)] code: i32) {
|
pub fn asan_maybe_exit(code: i32) {
|
||||||
#[cfg(feature = "asan")]
|
if cfg!(feature = "asan") {
|
||||||
{
|
|
||||||
asan_before_exit();
|
asan_before_exit();
|
||||||
unsafe {
|
unsafe {
|
||||||
libc::exit(code);
|
libc::exit(code);
|
||||||
|
@ -323,15 +322,9 @@ pub fn asan_maybe_exit(#[allow(unused)] code: i32) {
|
||||||
/// This function is always defined but is a no-op if not running under ASAN. This is to make it
|
/// This function is always defined but is a no-op if not running under ASAN. This is to make it
|
||||||
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
|
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
|
||||||
pub fn asan_before_exit() {
|
pub fn asan_before_exit() {
|
||||||
#[cfg(feature = "asan")]
|
if cfg!(feature = "asan") && !is_forked_child() {
|
||||||
if !is_forked_child() {
|
|
||||||
unsafe {
|
|
||||||
// Free ncurses terminal state
|
// Free ncurses terminal state
|
||||||
extern "C" {
|
crate::curses::reset();
|
||||||
fn env_cleanup();
|
|
||||||
}
|
|
||||||
env_cleanup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
#include "abbrs.h"
|
#include "abbrs.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "env_dispatch.h"
|
#include "env_dispatch.rs.h"
|
||||||
#include "env_universal_common.h"
|
#include "env_universal_common.h"
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "fallback.h" // IWYU pragma: keep
|
#include "fallback.h" // IWYU pragma: keep
|
||||||
|
@ -364,7 +364,7 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa
|
||||||
vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE);
|
vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE);
|
||||||
|
|
||||||
// Allow changes to variables to produce events.
|
// Allow changes to variables to produce events.
|
||||||
env_dispatch_init(vars);
|
env_dispatch_init_ffi(/* vars */);
|
||||||
|
|
||||||
init_input();
|
init_input();
|
||||||
|
|
||||||
|
|
|
@ -308,11 +308,6 @@ class env_stack_t final : public environment_t {
|
||||||
rust::Box<EnvStackRef> impl_;
|
rust::Box<EnvStackRef> impl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool get_use_posix_spawn();
|
|
||||||
|
|
||||||
/// Returns true if we think the terminal supports setting its title.
|
|
||||||
bool term_supports_setting_title();
|
|
||||||
|
|
||||||
#if INCLUDE_RUST_HEADERS
|
#if INCLUDE_RUST_HEADERS
|
||||||
struct EnvDyn;
|
struct EnvDyn;
|
||||||
/// Wrapper around rust's `&dyn Environment` deriving from `environment_t`.
|
/// Wrapper around rust's `&dyn Environment` deriving from `environment_t`.
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
// Prototypes for functions that react to environment variable changes
|
|
||||||
#ifndef FISH_ENV_DISPATCH_H
|
|
||||||
#define FISH_ENV_DISPATCH_H
|
|
||||||
|
|
||||||
#include "config.h" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
class environment_t;
|
|
||||||
class env_stack_t;
|
|
||||||
|
|
||||||
/// Initialize variable dispatch.
|
|
||||||
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
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "env.h"
|
#include "env.h"
|
||||||
|
#include "env_dispatch.rs.h"
|
||||||
#include "exec.h"
|
#include "exec.h"
|
||||||
#include "fallback.h" // IWYU pragma: keep
|
#include "fallback.h" // IWYU pragma: keep
|
||||||
#include "fds.h"
|
#include "fds.h"
|
||||||
|
@ -214,7 +215,7 @@ bool is_thompson_shell_script(const char *path) {
|
||||||
static bool can_use_posix_spawn_for_job(const std::shared_ptr<job_t> &job,
|
static bool can_use_posix_spawn_for_job(const std::shared_ptr<job_t> &job,
|
||||||
const dup2_list_t &dup2s) {
|
const dup2_list_t &dup2s) {
|
||||||
// Is it globally disabled?
|
// Is it globally disabled?
|
||||||
if (!get_use_posix_spawn()) return false;
|
if (!use_posix_spawn()) return false;
|
||||||
|
|
||||||
// Hack - do not use posix_spawn if there are self-fd redirections.
|
// Hack - do not use posix_spawn if there are self-fd redirections.
|
||||||
// For example if you were to write:
|
// For example if you were to write:
|
||||||
|
|
|
@ -1273,10 +1273,10 @@ void history_impl_t::incorporate_external_changes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the prefix for the files to be used for command and read history.
|
/// Return the prefix for the files to be used for command and read history.
|
||||||
wcstring history_session_id(const environment_t &vars) {
|
wcstring history_session_id(std::unique_ptr<env_var_t> fish_history) {
|
||||||
wcstring result = DFLT_FISH_HISTORY_SESSION_ID;
|
wcstring result = DFLT_FISH_HISTORY_SESSION_ID;
|
||||||
|
|
||||||
const auto var = vars.get(L"fish_history");
|
const auto var = std::move(fish_history);
|
||||||
if (var) {
|
if (var) {
|
||||||
wcstring session_id = var->as_string();
|
wcstring session_id = var->as_string();
|
||||||
if (session_id.empty()) {
|
if (session_id.empty()) {
|
||||||
|
@ -1294,6 +1294,13 @@ wcstring history_session_id(const environment_t &vars) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wcstring history_session_id(const environment_t &vars) {
|
||||||
|
auto fish_history = vars.get(L"fish_history");
|
||||||
|
auto var =
|
||||||
|
fish_history ? std::make_unique<env_var_t>(*fish_history) : std::unique_ptr<env_var_t>{};
|
||||||
|
return history_session_id(std::move(var));
|
||||||
|
}
|
||||||
|
|
||||||
path_list_t expand_and_detect_paths(const path_list_t &paths, const environment_t &vars) {
|
path_list_t expand_and_detect_paths(const path_list_t &paths, const environment_t &vars) {
|
||||||
ASSERT_IS_BACKGROUND_THREAD();
|
ASSERT_IS_BACKGROUND_THREAD();
|
||||||
std::vector<wcstring> result;
|
std::vector<wcstring> result;
|
||||||
|
|
|
@ -316,8 +316,14 @@ class history_search_t {
|
||||||
/** Saves the new history to disk. */
|
/** Saves the new history to disk. */
|
||||||
void history_save_all();
|
void history_save_all();
|
||||||
|
|
||||||
|
#if INCLUDE_RUST_HEADERS
|
||||||
/** Return the prefix for the files to be used for command and read history. */
|
/** Return the prefix for the files to be used for command and read history. */
|
||||||
wcstring history_session_id(const environment_t &vars);
|
wcstring history_session_id(const environment_t &vars);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** FFI version of above **/
|
||||||
|
class env_var_t;
|
||||||
|
wcstring history_session_id(std::unique_ptr<env_var_t> fish_history);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Given a list of proposed paths and a context, perform variable and home directory expansion,
|
Given a list of proposed paths and a context, perform variable and home directory expansion,
|
||||||
|
|
|
@ -140,6 +140,23 @@ void update_wait_on_escape_ms(const environment_t& vars) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update_wait_on_escape_ms_ffi(std::unique_ptr<env_var_t> fish_escape_delay_ms) {
|
||||||
|
if (!fish_escape_delay_ms) {
|
||||||
|
wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long tmp = fish_wcstol(fish_escape_delay_ms->as_string().c_str());
|
||||||
|
if (errno || tmp < 10 || tmp >= 5000) {
|
||||||
|
std::fwprintf(stderr,
|
||||||
|
L"ignoring fish_escape_delay_ms: value '%ls' "
|
||||||
|
L"is not an integer or is < 10 or >= 5000 ms\n",
|
||||||
|
fish_escape_delay_ms->as_string().c_str());
|
||||||
|
} else {
|
||||||
|
wait_on_escape_ms = static_cast<int>(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
maybe_t<char_event_t> input_event_queue_t::try_pop() {
|
maybe_t<char_event_t> input_event_queue_t::try_pop() {
|
||||||
if (queue_.empty()) {
|
if (queue_.empty()) {
|
||||||
return none();
|
return none();
|
||||||
|
|
|
@ -186,6 +186,7 @@ class char_event_t {
|
||||||
/// Adjust the escape timeout.
|
/// Adjust the escape timeout.
|
||||||
class environment_t;
|
class environment_t;
|
||||||
void update_wait_on_escape_ms(const environment_t &vars);
|
void update_wait_on_escape_ms(const environment_t &vars);
|
||||||
|
void update_wait_on_escape_ms_ffi(std::unique_ptr<env_var_t> fish_escape_delay_ms);
|
||||||
|
|
||||||
/// A class which knows how to produce a stream of input events.
|
/// A class which knows how to produce a stream of input events.
|
||||||
/// This is a base class; you may subclass it for its override points.
|
/// This is a base class; you may subclass it for its override points.
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "complete.h"
|
#include "complete.h"
|
||||||
#include "env.h"
|
#include "env.h"
|
||||||
|
#include "env_dispatch.rs.h"
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "exec.h"
|
#include "exec.h"
|
||||||
#include "expand.h"
|
#include "expand.h"
|
||||||
|
|
|
@ -174,10 +174,10 @@ void reader_change_cursor_selection_mode(cursor_selection_mode_t selection_mode)
|
||||||
void reader_change_cursor_selection_mode(uint8_t selection_mode);
|
void reader_change_cursor_selection_mode(uint8_t selection_mode);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct EnvDyn;
|
||||||
/// Enable or disable autosuggestions based on the associated variable.
|
/// Enable or disable autosuggestions based on the associated variable.
|
||||||
void reader_set_autosuggestion_enabled(const env_stack_t &vars);
|
void reader_set_autosuggestion_enabled(const env_stack_t &vars);
|
||||||
|
void reader_set_autosuggestion_enabled_ffi(bool enabled);
|
||||||
void reader_set_autosuggestion_enabled_ffi(bool);
|
|
||||||
|
|
||||||
/// Write the title to the titlebar. This function is called just before a new application starts
|
/// Write the title to the titlebar. This function is called just before a new application starts
|
||||||
/// executing and just after it finishes.
|
/// executing and just after it finishes.
|
||||||
|
|
Loading…
Reference in a new issue