Remove FFI code and C++ files

There's a lot more to remove, like
- cxx/autocxx
- now-unused CMake code
- C++ pcre
- C++ entry points
- remaining mentions of "ffi"
This commit is contained in:
Johannes Altmanninger
2024-01-01 21:29:05 +01:00
parent ab98566c67
commit 102ab2c90d
141 changed files with 681 additions and 18308 deletions

View File

@@ -102,24 +102,7 @@ endif()
# List of other sources.
set(FISH_SRCS
src/ast.cpp
src/builtin.cpp
src/color.cpp
src/common.cpp
src/env.cpp
src/event.cpp
src/expand.cpp
src/fallback.cpp
src/fish_version.cpp
src/flog.cpp
src/highlight.cpp
src/output.cpp
src/parse_util.cpp
src/path.cpp
src/rustffi.cpp
src/wcstringutil.cpp
src/wgetopt.cpp
src/wutil.cpp
src/empty.cpp
)
# Header files are just globbed.

View File

@@ -98,53 +98,10 @@ fn main() {
// This allows "Rust to be used from C++"
// This must come before autocxx so that cxx can emit its cxx.h header.
let source_files = vec![
"fish-rust/src/abbrs.rs",
"fish-rust/src/ast.rs",
"fish-rust/src/builtins/shared.rs",
"fish-rust/src/common.rs",
"fish-rust/src/complete.rs",
"fish-rust/src/editable_line.rs",
"fish-rust/src/env_dispatch.rs",
"fish-rust/src/env/env_ffi.rs",
"fish-rust/src/event.rs",
"fish-rust/src/exec.rs",
"fish-rust/src/expand.rs",
"fish-rust/src/fd_monitor.rs",
"fish-rust/src/fd_readable_set.rs",
"fish-rust/src/fds.rs",
"fish-rust/src/ffi_init.rs",
"fish-rust/src/fish_indent.rs",
"fish-rust/src/fish_key_reader.rs",
"fish-rust/src/fish_indent.rs",
"fish-rust/src/fish.rs",
"fish-rust/src/function.rs",
"fish-rust/src/future_feature_flags.rs",
"fish-rust/src/highlight.rs",
"fish-rust/src/history.rs",
"fish-rust/src/input_ffi.rs",
"fish-rust/src/io.rs",
"fish-rust/src/job_group.rs",
"fish-rust/src/kill.rs",
"fish-rust/src/operation_context.rs",
"fish-rust/src/output.rs",
"fish-rust/src/pager.rs",
"fish-rust/src/parse_constants.rs",
"fish-rust/src/parser.rs",
"fish-rust/src/parse_tree.rs",
"fish-rust/src/parse_util.rs",
"fish-rust/src/print_help.rs",
"fish-rust/src/proc.rs",
"fish-rust/src/reader.rs",
"fish-rust/src/redirection.rs",
"fish-rust/src/screen.rs",
"fish-rust/src/signal.rs",
"fish-rust/src/termsize.rs",
"fish-rust/src/threads.rs",
"fish-rust/src/timer.rs",
"fish-rust/src/tokenizer.rs",
"fish-rust/src/trace.rs",
"fish-rust/src/util.rs",
"fish-rust/src/universal_notifier/mod.rs",
"fish-rust/src/wildcard.rs",
];
cxx_build::bridges(&source_files)
.flag_if_supported("-std=c++11")

View File

@@ -5,86 +5,13 @@
};
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use cxx::CxxWString;
use once_cell::sync::Lazy;
use crate::abbrs::abbrs_ffi::abbrs_replacer_t;
use crate::parse_constants::SourceRange;
#[cfg(test)]
use crate::tests::prelude::*;
use pcre2::utf32::Regex;
use self::abbrs_ffi::{abbreviation_t, abbrs_position_t, abbrs_replacement_t};
#[cxx::bridge]
mod abbrs_ffi {
extern "C++" {
include!("parse_constants.h");
type SourceRange = crate::parse_constants::SourceRange;
}
enum abbrs_position_t {
command,
anywhere,
}
struct abbrs_replacer_t {
replacement: UniquePtr<CxxWString>,
is_function: bool,
set_cursor_marker: UniquePtr<CxxWString>,
has_cursor_marker: bool,
}
struct abbrs_replacement_t {
range: SourceRange,
text: UniquePtr<CxxWString>,
cursor: usize,
has_cursor: bool,
}
struct abbreviation_t {
key: UniquePtr<CxxWString>,
replacement: UniquePtr<CxxWString>,
is_regex: bool,
}
extern "Rust" {
type GlobalAbbrs<'a>;
#[cxx_name = "abbrs_list"]
fn abbrs_list_ffi() -> Vec<abbreviation_t>;
#[cxx_name = "abbrs_match"]
fn abbrs_match_ffi(token: &CxxWString, position: abbrs_position_t)
-> Vec<abbrs_replacer_t>;
#[cxx_name = "abbrs_has_match"]
fn abbrs_has_match_ffi(token: &CxxWString, position: abbrs_position_t) -> bool;
#[cxx_name = "abbrs_replacement_from"]
fn abbrs_replacement_from_ffi(
range: SourceRange,
text: &CxxWString,
set_cursor_marker: &CxxWString,
has_cursor_marker: bool,
) -> abbrs_replacement_t;
#[cxx_name = "abbrs_get_set"]
unsafe fn abbrs_get_set_ffi<'a>() -> Box<GlobalAbbrs<'a>>;
unsafe fn add<'a>(
self: &mut GlobalAbbrs<'_>,
name: &CxxWString,
key: &CxxWString,
replacement: &CxxWString,
position: abbrs_position_t,
from_universal: bool,
);
unsafe fn erase<'a>(self: &mut GlobalAbbrs<'_>, name: &CxxWString);
}
}
static ABBRS: Lazy<Mutex<AbbreviationSet>> = Lazy::new(|| Mutex::new(Default::default()));
pub fn with_abbrs<R>(cb: impl FnOnce(&AbbreviationSet) -> R) -> R {
@@ -108,16 +35,6 @@ pub enum Position {
Anywhere, // expand in any token
}
impl From<abbrs_position_t> for Position {
fn from(value: abbrs_position_t) -> Self {
match value {
abbrs_position_t::anywhere => Position::Anywhere,
abbrs_position_t::command => Position::Command,
_ => panic!("invalid abbrs_position_t"),
}
}
}
#[derive(Debug)]
pub struct Abbreviation {
// Abbreviation name. This is unique within the abbreviation set.
@@ -209,18 +126,6 @@ pub struct Replacer {
pub set_cursor_marker: Option<WString>,
}
impl From<Replacer> for abbrs_replacer_t {
fn from(value: Replacer) -> Self {
let has_cursor_marker = value.set_cursor_marker.is_some();
Self {
replacement: value.replacement.to_ffi(),
is_function: value.is_function,
set_cursor_marker: value.set_cursor_marker.unwrap_or_default().to_ffi(),
has_cursor_marker,
}
}
}
pub struct Replacement {
/// The original range of the token in the command line.
pub range: SourceRange,
@@ -361,86 +266,10 @@ pub fn abbrs_match(token: &wstr, position: Position) -> Vec<Replacer> {
.collect()
}
fn abbrs_match_ffi(token: &CxxWString, position: abbrs_position_t) -> Vec<abbrs_replacer_t> {
with_abbrs(|set| set.r#match(token.as_wstr(), position.into()))
.into_iter()
.map(|r| r.into())
.collect()
}
fn abbrs_has_match_ffi(token: &CxxWString, position: abbrs_position_t) -> bool {
with_abbrs(|set| set.has_match(token.as_wstr(), position.into()))
}
fn abbrs_list_ffi() -> Vec<abbreviation_t> {
with_abbrs(|set| -> Vec<abbreviation_t> {
let list = set.list();
let mut result = Vec::with_capacity(list.len());
for abbr in list {
result.push(abbreviation_t {
key: abbr.key.to_ffi(),
replacement: abbr.replacement.to_ffi(),
is_regex: abbr.is_regex(),
})
}
result
})
}
fn abbrs_get_set_ffi<'a>() -> Box<GlobalAbbrs<'a>> {
let abbrs_g = ABBRS.lock().unwrap();
Box::new(GlobalAbbrs { g: abbrs_g })
}
fn abbrs_replacement_from_ffi(
range: SourceRange,
text: &CxxWString,
set_cursor_marker: &CxxWString,
has_cursor_marker: bool,
) -> abbrs_replacement_t {
let cursor_marker = if has_cursor_marker {
Some(set_cursor_marker.from_ffi())
} else {
None
};
let replacement = Replacement::new(range, text.from_ffi(), cursor_marker);
abbrs_replacement_t {
range,
text: replacement.text.to_ffi(),
cursor: replacement.cursor.unwrap_or_default(),
has_cursor: replacement.cursor.is_some(),
}
}
pub struct GlobalAbbrs<'a> {
g: MutexGuard<'a, AbbreviationSet>,
}
impl<'a> GlobalAbbrs<'a> {
fn add(
&mut self,
name: &CxxWString,
key: &CxxWString,
replacement: &CxxWString,
position: abbrs_position_t,
from_universal: bool,
) {
self.g.add(Abbreviation::new(
name.from_ffi(),
key.from_ffi(),
replacement.from_ffi(),
position.into(),
from_universal,
));
}
fn erase(&mut self, name: &CxxWString) {
self.g.erase(name.as_wstr());
}
}
#[test]
#[serial]
fn rename_abbrs() {

File diff suppressed because it is too large Load Diff

View File

@@ -54,7 +54,6 @@ mod prelude {
io::{IoStreams, SeparationType},
parser::Parser,
wchar::prelude::*,
wchar_ffi::{c_str, AsWstr, WCharFromFFI, WCharToFFI},
wgetopt::{
wgetopter_t, wopt, woption,
woption_argument_t::{self, *},

View File

@@ -337,7 +337,6 @@ fn handle_env_return(retval: EnvStackSetResult, cmd: &wstr, key: &wstr, streams:
key
));
}
_ => panic!("unexpected vars.set() ret val"),
}
}
@@ -822,7 +821,6 @@ fn env_result_to_status(retval: EnvStackSetResult) -> Option<c_int> {
EnvStackSetResult::ENV_SCOPE => 2,
EnvStackSetResult::ENV_INVALID => 3,
EnvStackSetResult::ENV_NOT_FOUND => 4,
_ => panic!(),
})
}

View File

@@ -1,8 +1,7 @@
use super::prelude::*;
use crate::builtins::*;
use crate::common::{escape, get_by_sorted_name, str2wcstring, Named};
use crate::ffi::Repin;
use crate::io::{IoChain, IoFd, OutputStream, OutputStreamFfi};
use crate::io::{IoChain, IoFd, OutputStream};
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
use crate::parse_util::parse_util_argument_is_help;
use crate::parser::{Block, BlockType, LoopStatus};
@@ -10,7 +9,6 @@
use crate::reader::reader_read;
use crate::wchar::{wstr, WString, L};
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
use cxx::CxxWString;
use errno::errno;
use libc::{c_int, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
@@ -18,7 +16,6 @@
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::os::fd::FromRawFd;
use std::pin::Pin;
use std::sync::Arc;
use widestring_suffix::widestrs;
@@ -970,140 +967,3 @@ fn builtin_gettext(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]
}
STATUS_CMD_OK
}
#[cxx::bridge]
#[allow(clippy::needless_lifetimes)]
mod builtins_ffi {
extern "C++" {
include!("io.h");
include!("parser.h");
type IoStreams<'a> = crate::io::IoStreams<'a>;
type OutputStreamFfi<'a> = crate::io::OutputStreamFfi<'a>;
type Parser = crate::parser::Parser;
}
extern "Rust" {
#[cxx_name = "builtin_print_help"]
unsafe fn builtin_print_help_ffi<'a>(
parser: &Parser,
streams: Pin<&mut IoStreams<'a>>,
name: &CxxWString,
);
#[cxx_name = "builtin_print_help_error"]
unsafe fn builtin_print_help_error_ffi<'a>(
parser: &Parser,
streams: Pin<&mut IoStreams<'a>>,
name: &CxxWString,
error_message: &CxxWString,
);
#[cxx_name = "builtin_unknown_option"]
unsafe fn builtin_unknown_option_ffi<'a>(
parser: &Parser,
streams: Pin<&mut IoStreams<'a>>,
cmd: &CxxWString,
opt: &CxxWString,
print_hints: bool,
);
#[cxx_name = "builtin_missing_argument"]
fn builtin_missing_argument_ffi(
parser: &Parser,
streams: Pin<&mut IoStreams>,
cmd: &CxxWString,
opt: &CxxWString,
print_hints: bool,
);
#[cxx_name = "builtin_print_error_trailer"]
unsafe fn builtin_print_error_trailer_ffi<'a>(
parser: &Parser,
b: Pin<&mut OutputStreamFfi<'a>>,
cmd: &CxxWString,
);
}
}
pub fn run_builtin_ffi(
builtin_fn: fn(
*const autocxx::c_void,
*mut autocxx::c_void,
*mut autocxx::c_void,
) -> autocxx::c_int,
parser: &Parser,
streams: &mut IoStreams,
args: &mut [&wstr],
) -> Option<c_int> {
let mut zstrings = vec![];
for arg in args {
let mut zstring: Vec<char> = arg.chars().collect();
zstring.push('\0');
zstrings.push(zstring);
}
let mut zstrs = vec![];
for zstring in &zstrings {
zstrs.push(zstring.as_ptr());
}
zstrs.push(std::ptr::null());
let args = zstrs.as_mut_ptr();
let ret = (builtin_fn)(
parser as *const Parser as *const autocxx::c_void,
streams as *mut IoStreams as *mut autocxx::c_void,
args.cast(),
);
Some(i32::from(ret))
}
fn builtin_print_help_ffi(parser: &Parser, streams: Pin<&mut IoStreams>, name: &CxxWString) {
builtin_print_help(parser, streams.unpin(), name.as_wstr())
}
fn builtin_print_help_error_ffi(
parser: &Parser,
streams: Pin<&mut IoStreams>,
name: &CxxWString,
error_message: &CxxWString,
) {
builtin_print_help_error(
parser,
streams.unpin(),
name.as_wstr(),
error_message.as_wstr(),
)
}
fn builtin_unknown_option_ffi(
parser: &Parser,
streams: Pin<&mut IoStreams>,
cmd: &CxxWString,
opt: &CxxWString,
print_hints: bool,
) {
builtin_unknown_option(
parser,
streams.unpin(),
cmd.as_wstr(),
opt.as_wstr(),
print_hints,
);
}
fn builtin_missing_argument_ffi(
parser: &Parser,
streams: Pin<&mut IoStreams>,
cmd: &CxxWString,
opt: &CxxWString,
print_hints: bool,
) {
builtin_missing_argument(
parser,
streams.unpin(),
cmd.as_wstr(),
opt.as_wstr(),
print_hints,
);
}
fn builtin_print_error_trailer_ffi(
parser: &Parser,
b: Pin<&mut OutputStreamFfi>,
cmd: &CxxWString,
) {
builtin_print_error_trailer(parser, b.unpin().0, cmd.as_wstr())
}

View File

@@ -428,38 +428,6 @@ fn term256_color_for_rgb(color: Color24) -> u8 {
(16 + convert_color(color, COLORS)).try_into().unwrap()
}
/// FFI junk.
use crate::ffi::rgb_color_t;
impl rgb_color_t {
/// Convert from a C++ rgb_color_t to a Rust RgbColor.
#[allow(clippy::wrong_self_convention)]
pub fn from_ffi(&self) -> RgbColor {
let typ = if self.is_normal() {
Type::Normal
} else if self.is_reset() {
Type::Reset
} else if self.is_none() {
Type::None
} else if self.is_named() {
let idx = self.to_name_index();
Type::Named { idx }
} else if self.is_rgb() {
let [r, g, b] = self.to_color24().rgb;
Type::Rgb(Color24 { r, g, b })
} else {
unreachable!("Unknown color type")
};
let flags = Flags {
bold: self.is_bold(),
underline: self.is_underline(),
italics: self.is_italics(),
dim: self.is_dim(),
reverse: self.is_reverse(),
};
RgbColor { typ, flags }
}
}
#[cfg(test)]
mod tests {
use crate::color::{Color24, Flags, RgbColor, Type};

View File

@@ -6,20 +6,17 @@
PROCESS_EXPAND_SELF, PROCESS_EXPAND_SELF_STR, VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE,
};
use crate::fallback::fish_wcwidth;
use crate::ffi;
use crate::flog::FLOG;
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::global_safety::RelaxedAtomicBool;
use crate::termsize::Termsize;
use crate::wchar::{decode_byte_from_char, encode_byte_to_char, prelude::*};
use crate::wchar_ffi::WCharToFFI;
use crate::wcstringutil::wcs2string_callback;
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use crate::wutil::encoding::{mbrtowc, wcrtomb, zero_mbstate, AT_LEAST_MB_LEN_MAX};
use crate::wutil::fish_iswalnum;
use bitflags::bitflags;
use core::slice;
use cxx::{CxxWString, UniquePtr};
use libc::{EINTR, EIO, O_WRONLY, SIGTTOU, SIG_IGN, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use num_traits::ToPrimitive;
use once_cell::sync::{Lazy, OnceCell};
@@ -2162,80 +2159,3 @@ macro_rules! eprintf {
fprintf!(libc::STDERR_FILENO, $format $(, $arg)*)
}
}
#[cxx::bridge]
mod common_ffi {
extern "C++" {
include!("wutil.h");
include!("common.h");
type escape_string_style_t = crate::ffi::escape_string_style_t;
}
extern "Rust" {
#[cxx_name = "unescape_string"]
fn unescape_string_ffi(
input: *const wchar_t,
len: usize,
escape_special: u32,
style: escape_string_style_t,
) -> UniquePtr<CxxWString>;
#[cxx_name = "escape_string_script"]
fn escape_string_script_ffi(
input: *const wchar_t,
len: usize,
flags: u32,
) -> UniquePtr<CxxWString>;
#[cxx_name = "escape_string_url"]
fn escape_string_url_ffi(input: *const wchar_t, len: usize) -> UniquePtr<CxxWString>;
#[cxx_name = "escape_string_var"]
fn escape_string_var_ffi(input: *const wchar_t, len: usize) -> UniquePtr<CxxWString>;
}
}
fn unescape_string_ffi(
input: *const ffi::wchar_t,
len: usize,
escape_special: u32,
style: ffi::escape_string_style_t,
) -> UniquePtr<CxxWString> {
let style = match style {
ffi::escape_string_style_t::STRING_STYLE_SCRIPT => {
UnescapeStringStyle::Script(UnescapeFlags::from_bits(escape_special).unwrap())
}
ffi::escape_string_style_t::STRING_STYLE_URL => UnescapeStringStyle::Url,
ffi::escape_string_style_t::STRING_STYLE_VAR => UnescapeStringStyle::Var,
_ => panic!(),
};
let input = unsafe { slice::from_raw_parts(input, len) };
let input = wstr::from_slice(input).unwrap();
match unescape_string(input, style) {
Some(result) => result.to_ffi(),
None => UniquePtr::null(),
}
}
fn escape_string_script_ffi(
input: *const ffi::wchar_t,
len: usize,
flags: u32,
) -> UniquePtr<CxxWString> {
let input = unsafe { slice::from_raw_parts(input, len) };
escape_string_script(
wstr::from_slice(input).unwrap(),
EscapeFlags::from_bits(flags).unwrap(),
)
.to_ffi()
}
fn escape_string_var_ffi(input: *const ffi::wchar_t, len: usize) -> UniquePtr<CxxWString> {
let input = unsafe { slice::from_raw_parts(input, len) };
escape_string_var(wstr::from_slice(input).unwrap()).to_ffi()
}
fn escape_string_url_ffi(input: *const ffi::wchar_t, len: usize) -> UniquePtr<CxxWString> {
let input = unsafe { slice::from_raw_parts(input, len) };
escape_string_url(wstr::from_slice(input).unwrap()).to_ffi()
}

View File

@@ -3,7 +3,6 @@
cmp::Ordering,
collections::{BTreeMap, HashMap, HashSet},
mem,
pin::Pin,
sync::{
atomic::{self, AtomicUsize},
Mutex,
@@ -11,13 +10,8 @@
time::{Duration, Instant},
};
use crate::{
common::charptr2wcstring,
util::wcsfilecmp,
wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI},
};
use crate::{common::charptr2wcstring, util::wcsfilecmp};
use bitflags::bitflags;
use cxx::{CxxWString, UniquePtr};
use once_cell::sync::Lazy;
use printf_compat::sprintf;
use widestring::U32CString;
@@ -2533,90 +2527,14 @@ pub fn complete_get_wrap_targets(command: &wstr) -> Vec<WString> {
wrappers.get(command).cloned().unwrap_or_default()
}
pub struct CompletionListFfi(pub CompletionList);
pub use complete_ffi::CompletionRequestOptions;
#[cxx::bridge]
mod complete_ffi {
extern "C++" {
include!("complete.h");
include!("parser.h");
type Parser = crate::parser::Parser;
type OperationContext<'a> = crate::operation_context::OperationContext<'a>;
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
}
extern "Rust" {
type Completion;
type CompletionListFfi;
fn new_completion() -> Box<Completion>;
fn new_completion_with(
completion: &CxxWString,
description: &CxxWString,
flags: u8,
) -> Box<Completion>;
fn completion(self: &Completion) -> UniquePtr<CxxWString>;
fn description(self: &Completion) -> UniquePtr<CxxWString>;
fn flags(self: &Completion) -> u8;
fn set_flags(self: &mut Completion, value: u8);
fn replaces_commandline(self: &Completion) -> bool;
fn match_is_exact_or_prefix(self: &Completion) -> bool;
fn completion_erase(self: &mut Completion, begin: usize, end: usize);
fn rank(self: &Completion) -> u32;
#[cxx_name = "clone"]
fn clone_ffi(self: &Completion) -> Box<Completion>;
fn new_completion_list() -> Box<CompletionListFfi>;
fn size(self: &CompletionListFfi) -> usize;
fn empty(self: &CompletionListFfi) -> bool;
fn at(self: &CompletionListFfi, i: usize) -> &Completion;
fn at_mut(self: &mut CompletionListFfi, i: usize) -> &mut Completion;
fn clear(self: &mut CompletionListFfi);
fn complete_invalidate_path();
fn reverse(self: &mut CompletionListFfi);
fn push_back(self: &mut CompletionListFfi, completion: &Completion);
fn sort_and_prioritize(self: &mut CompletionListFfi, flags: CompletionRequestOptions);
#[cxx_name = "complete_load"]
fn complete_load_ffi(cmd: &CxxWString, parser: &Parser) -> bool;
#[cxx_name = "complete"]
fn complete_ffi(
search_string: &CxxWString,
complete_flags: CompletionRequestOptions,
ctx: &OperationContext<'static>,
needs_load: &mut UniquePtr<wcstring_list_ffi_t>,
) -> Box<CompletionListFfi>;
#[cxx_name = "append_completion"]
fn append_completion_ffi(completions: Pin<&mut CompletionListFfi>, comp: &CxxWString);
}
#[derive(Clone, Copy)]
pub struct CompletionRequestOptions {
/// Requesting autosuggestion
pub autosuggestion: bool,
/// Make descriptions
pub descriptions: bool,
/// If set, we do not require a prefix match
pub fuzzy_match: bool,
}
extern "Rust" {
fn completion_request_options_autosuggest() -> CompletionRequestOptions;
fn completion_request_options_normal() -> CompletionRequestOptions;
}
}
fn complete_ffi(
search_string: &CxxWString,
complete_flags: CompletionRequestOptions,
ctx: &OperationContext<'static>,
needs_load: &mut UniquePtr<crate::ffi::wcstring_list_ffi_t>,
) -> Box<CompletionListFfi> {
let (completions, to_load) = complete(search_string.as_wstr(), complete_flags, ctx);
if !needs_load.is_null() {
*needs_load = to_load.to_ffi();
}
Box::new(CompletionListFfi(completions))
#[derive(Clone, Copy)]
pub struct CompletionRequestOptions {
/// Requesting autosuggestion
pub autosuggestion: bool,
/// Make descriptions
pub descriptions: bool,
/// If set, we do not require a prefix match
pub fuzzy_match: bool,
}
fn completion_request_options_autosuggest() -> CompletionRequestOptions {
@@ -2636,63 +2554,13 @@ fn default() -> Self {
}
}
unsafe impl cxx::ExternType for CompletionListFfi {
type Id = cxx::type_id!("CompletionListFfi");
type Kind = cxx::kind::Opaque;
}
unsafe impl cxx::ExternType for Completion {
type Id = cxx::type_id!("Completion");
type Kind = cxx::kind::Opaque;
}
fn new_completion() -> Box<Completion> {
Box::new(Completion::new(
"".into(),
"".into(),
StringFuzzyMatch::exact_match(),
CompleteFlags::default(),
))
}
fn new_completion_with(
completion: &CxxWString,
description: &CxxWString,
flags: u8,
) -> Box<Completion> {
Box::new(Completion::new(
completion.from_ffi(),
description.from_ffi(),
StringFuzzyMatch::exact_match(),
CompleteFlags::from_bits(flags).unwrap(),
))
}
fn new_completion_list() -> Box<CompletionListFfi> {
Box::new(CompletionListFfi(CompletionList::new()))
}
fn append_completion_ffi(completions: Pin<&mut CompletionListFfi>, comp: &CxxWString) {
completions.get_mut().0.push(Completion::new(
comp.from_ffi(),
"".into(),
StringFuzzyMatch::exact_match(),
CompleteFlags::default(),
));
}
impl Completion {
fn completion(&self) -> UniquePtr<CxxWString> {
self.completion.to_ffi()
}
fn description(&self) -> UniquePtr<CxxWString> {
self.description.to_ffi()
}
fn flags(&self) -> u8 {
self.flags.bits()
}
fn set_flags(&mut self, value: u8) {
self.flags = CompleteFlags::from_bits(value).unwrap();
}
fn clone_ffi(&self) -> Box<Completion> {
Box::new(self.clone())
}
fn match_is_exact_or_prefix(&self) -> bool {
self.r#match.is_exact_or_prefix()
}
@@ -2700,33 +2568,3 @@ fn completion_erase(&mut self, begin: usize, end: usize) {
self.completion.replace_range(begin..end, L!(""))
}
}
impl CompletionListFfi {
fn size(&self) -> usize {
self.0.len()
}
fn empty(&self) -> bool {
self.0.is_empty()
}
fn at(&self, i: usize) -> &Completion {
&self.0[i]
}
fn at_mut(&mut self, i: usize) -> &mut Completion {
&mut self.0[i]
}
fn reverse(&mut self) {
self.0.reverse();
}
fn clear(&mut self) {
self.0.clear();
}
fn push_back(&mut self, completion: &Completion) {
self.0.push(completion.clone());
}
fn sort_and_prioritize(&mut self, flags: CompletionRequestOptions) {
sort_and_prioritize(&mut self.0, flags);
}
}
fn complete_load_ffi(cmd: &CxxWString, parser: &Parser) -> bool {
complete_load(cmd.as_wstr(), parser)
}

View File

@@ -1,12 +1,7 @@
use std::pin::Pin;
use cxx::{CxxWString, UniquePtr};
#[allow(unused_imports)]
use crate::future::IsSomeAnd;
use crate::highlight::{HighlightSpec, HighlightSpecListFFI};
use crate::highlight::HighlightSpec;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{WCharFromFFI, WCharToFFI};
/// An edit action that can be undone.
#[derive(Clone, Eq, PartialEq)]
@@ -344,88 +339,3 @@ fn cursor_position_after_edit(edit: &Edit) -> usize {
let removed = chars_deleted_left_of_cursor(edit);
cursor.saturating_sub(removed)
}
#[cxx::bridge]
mod editable_line_ffi {
extern "C++" {
include!("editable_line.h");
include!("highlight.h");
pub type HighlightSpec = crate::highlight::HighlightSpec;
pub type HighlightSpecListFFI = crate::highlight::HighlightSpecListFFI;
}
extern "Rust" {
type Edit;
fn new_edit(start: usize, end: usize, replacement: &CxxWString) -> Box<Edit>;
#[cxx_name = "apply_edit"]
fn apply_edit_ffi(
target: &CxxWString,
mut colors: Pin<&mut HighlightSpecListFFI>,
edit: Box<Edit>,
) -> UniquePtr<CxxWString>;
}
extern "Rust" {
type UndoHistory;
}
extern "Rust" {
type EditableLine;
fn new_editable_line() -> Box<EditableLine>;
fn empty(&self) -> bool;
#[cxx_name = "text"]
fn text_ffi(&self) -> UniquePtr<CxxWString>;
#[cxx_name = "clone"]
fn clone_ffi(&self) -> Box<EditableLine>;
fn position(&self) -> usize;
fn set_position(&mut self, position: usize);
fn clear(&mut self);
fn undo(&mut self) -> bool;
fn redo(&mut self) -> bool;
fn size(&self) -> usize;
#[cxx_name = "push_edit"]
fn push_edit_ffi(&mut self, edit: Box<Edit>, allow_coalesce: bool);
fn begin_edit_group(&mut self);
fn end_edit_group(&mut self);
#[cxx_name = "at"]
fn at_ffi(&self, index: usize) -> u32;
#[cxx_name = "set_colors"]
fn set_colors_ffi(&mut self, colors: &HighlightSpecListFFI);
}
}
fn new_edit(start: usize, end: usize, replacement: &CxxWString) -> Box<Edit> {
Box::new(Edit::new(start..end, replacement.from_ffi()))
}
fn new_editable_line() -> Box<EditableLine> {
Box::default()
}
impl EditableLine {
fn empty(&self) -> bool {
self.is_empty()
}
fn text_ffi(&self) -> UniquePtr<CxxWString> {
self.text().to_ffi()
}
fn clone_ffi(&self) -> Box<Self> {
Box::new(self.clone())
}
fn size(&self) -> usize {
self.len()
}
#[allow(clippy::boxed_local)]
fn push_edit_ffi(&mut self, edit: Box<Edit>, allow_coalesce: bool) {
self.push_edit(*edit, allow_coalesce);
}
fn at_ffi(&self, index: usize) -> u32 {
self.at(index) as _
}
fn set_colors_ffi(&mut self, colors: &HighlightSpecListFFI) {
self.set_colors(colors.0.clone())
}
}
fn apply_edit_ffi(
target: &CxxWString,
mut colors: Pin<&mut HighlightSpecListFFI>,
edit: Box<Edit>,
) -> UniquePtr<CxxWString> {
let mut target = target.from_ffi();
apply_edit(&mut target, &mut colors.0, &edit);
target.to_ffi()
}

View File

@@ -1,437 +0,0 @@
use super::environment::{self, EnvDyn, EnvNull, EnvStack, EnvStackRef, Environment};
use super::var::{ElectricVar, EnvVar, EnvVarFlags, Statuses};
use crate::env::EnvMode;
use crate::ffi::{wchar_t, wcharz_t, wcstring_list_ffi_t};
use crate::signal::Signal;
use crate::wchar_ffi::WCharToFFI;
use crate::wchar_ffi::{AsWstr, WCharFromFFI};
use cxx::{CxxVector, CxxWString, UniquePtr};
use lazy_static::lazy_static;
use std::ffi::c_int;
use std::pin::Pin;
use crate::env::misc_init;
impl From<EnvStackSetResult> for c_int {
fn from(r: EnvStackSetResult) -> Self {
match r {
EnvStackSetResult::ENV_OK => 0,
EnvStackSetResult::ENV_PERM => 1,
EnvStackSetResult::ENV_SCOPE => 2,
EnvStackSetResult::ENV_INVALID => 3,
EnvStackSetResult::ENV_NOT_FOUND => 4,
_ => panic!(),
}
}
}
#[allow(clippy::module_inception)]
#[cxx::bridge]
mod env_ffi {
/// Return values for `EnvStack::set()`.
#[repr(u8)]
#[cxx_name = "env_stack_set_result_t"]
#[derive(Debug)]
enum EnvStackSetResult {
ENV_OK,
ENV_PERM,
ENV_SCOPE,
ENV_INVALID,
ENV_NOT_FOUND,
}
extern "C++" {
include!("env.h");
include!("wutil.h");
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
type wcharz_t = super::wcharz_t;
}
extern "Rust" {
type EnvVar;
fn is_empty(&self) -> bool;
fn exports(&self) -> bool;
fn is_read_only(&self) -> bool;
fn is_pathvar(&self) -> bool;
#[cxx_name = "equals"]
fn equals_ffi(&self, rhs: &EnvVar) -> bool;
#[cxx_name = "as_string"]
fn as_string_ffi(&self) -> UniquePtr<CxxWString>;
#[cxx_name = "as_list"]
fn as_list_ffi(&self) -> UniquePtr<wcstring_list_ffi_t>;
#[cxx_name = "to_list"]
fn to_list_ffi(&self, out: Pin<&mut wcstring_list_ffi_t>);
#[cxx_name = "get_delimiter"]
fn get_delimiter_ffi(&self) -> wchar_t;
#[cxx_name = "get_flags"]
fn get_flags_ffi(&self) -> u8;
#[cxx_name = "clone_box"]
fn clone_box_ffi(&self) -> Box<EnvVar>;
#[cxx_name = "env_var_create"]
fn env_var_create_ffi(vals: &wcstring_list_ffi_t, flags: u8) -> Box<EnvVar>;
#[cxx_name = "env_var_create_from_name"]
fn env_var_create_from_name_ffi(
name: wcharz_t,
values: &wcstring_list_ffi_t,
) -> Box<EnvVar>;
}
extern "Rust" {
type EnvNull;
#[cxx_name = "getf"]
fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar;
#[cxx_name = "get_names"]
fn get_names_ffi(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>);
#[cxx_name = "env_null_create"]
fn env_null_create_ffi() -> Box<EnvNull>;
}
extern "Rust" {
type Statuses;
#[cxx_name = "get_status"]
fn get_status_ffi(&self) -> i32;
#[cxx_name = "statuses_just"]
fn statuses_just_ffi(s: i32) -> Box<Statuses>;
#[cxx_name = "get_pipestatus"]
fn get_pipestatus_ffi(&self) -> &Vec<i32>;
#[cxx_name = "get_kill_signal"]
fn get_kill_signal_ffi(&self) -> i32;
}
extern "Rust" {
#[cxx_name = "EnvDyn"]
type EnvDynFFI;
fn get(&self, name: &CxxWString) -> *mut EnvVar;
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar;
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>);
}
extern "Rust" {
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI;
fn env_stack_globals() -> &'static EnvStackRefFFI;
fn env_stack_principal() -> &'static EnvStackRefFFI;
fn set_one(&self, name: &CxxWString, flags: u16, value: &CxxWString) -> EnvStackSetResult;
fn get(&self, name: &CxxWString) -> *mut EnvVar;
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar;
fn get_unless_empty(&self, name: &CxxWString) -> *mut EnvVar;
fn getf_unless_empty(&self, name: &CxxWString, flags: u16) -> *mut EnvVar;
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>);
fn is_principal(&self) -> bool;
fn get_last_statuses(&self) -> Box<Statuses>;
fn set_last_statuses(&self, status: i32, kill_signal: i32, pipestatus: &CxxVector<i32>);
fn set(
&self,
name: &CxxWString,
flags: u16,
vals: &wcstring_list_ffi_t,
) -> EnvStackSetResult;
fn remove(&self, name: &CxxWString, flags: u16) -> EnvStackSetResult;
fn get_pwd_slash(&self) -> UniquePtr<CxxWString>;
fn set_pwd_from_getcwd(&self);
fn push(&self, new_scope: bool);
fn pop(&self);
fn snapshot(&self) -> Box<EnvDynFFI>;
// Access a variable stack that only represents globals.
// Do not push or pop from this.
fn env_get_globals_ffi() -> Box<EnvStackRefFFI>;
// Access the principal variable stack.
fn env_get_principal_ffi() -> Box<EnvStackRefFFI>;
}
extern "Rust" {
#[cxx_name = "var_is_electric"]
fn var_is_electric_ffi(name: &CxxWString) -> bool;
#[cxx_name = "rust_env_init"]
fn rust_env_init_ffi(do_uvars: bool);
fn misc_init();
#[cxx_name = "env_flags_for"]
fn env_flags_for_ffi(name: wcharz_t) -> u8;
}
}
pub use env_ffi::EnvStackSetResult;
impl Default for EnvStackSetResult {
fn default() -> Self {
EnvStackSetResult::ENV_OK
}
}
/// FFI bits.
impl EnvVar {
pub fn equals_ffi(&self, rhs: &EnvVar) -> bool {
self == rhs
}
pub fn as_string_ffi(&self) -> UniquePtr<CxxWString> {
self.as_string().to_ffi()
}
pub fn as_list_ffi(&self) -> UniquePtr<wcstring_list_ffi_t> {
self.as_list().to_ffi()
}
pub fn to_list_ffi(&self, mut out: Pin<&mut wcstring_list_ffi_t>) {
out.as_mut().clear();
for val in self.as_list() {
out.as_mut().push(val);
}
}
pub fn clone_box_ffi(&self) -> Box<Self> {
Box::new(self.clone())
}
pub fn get_flags_ffi(&self) -> u8 {
self.get_flags().bits()
}
pub fn get_delimiter_ffi(self: &EnvVar) -> wchar_t {
self.get_delimiter().into()
}
}
fn env_var_create_ffi(vals: &wcstring_list_ffi_t, flags: u8) -> Box<EnvVar> {
Box::new(EnvVar::new_vec(
vals.from_ffi(),
EnvVarFlags::from_bits(flags).expect("invalid flags"),
))
}
pub fn env_var_create_from_name_ffi(name: wcharz_t, values: &wcstring_list_ffi_t) -> Box<EnvVar> {
Box::new(EnvVar::new_from_name_vec(name.as_wstr(), values.from_ffi()))
}
fn env_null_create_ffi() -> Box<EnvNull> {
Box::new(EnvNull::new())
}
/// FFI wrapper around EnvDyn
pub struct EnvDynFFI(pub EnvDyn);
impl EnvDynFFI {
fn get(&self, name: &CxxWString) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(&self.0, name, 0)
}
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(&self.0, name, mode)
}
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) {
EnvironmentFFI::get_names_ffi(&self.0, flags, out)
}
}
unsafe impl cxx::ExternType for EnvDynFFI {
type Id = cxx::type_id!("EnvDyn"); // CXX name!
type Kind = cxx::kind::Opaque;
}
/// FFI wrapper around EnvStackRef.
#[derive(Clone)]
pub struct EnvStackRefFFI(pub EnvStackRef);
lazy_static! {
static ref GLOBALS: EnvStackRefFFI = EnvStackRefFFI(EnvStack::globals().clone());
}
lazy_static! {
static ref PRINCIPAL_STACK: EnvStackRefFFI = EnvStackRefFFI(EnvStack::principal().clone());
}
fn env_stack_globals() -> &'static EnvStackRefFFI {
&GLOBALS
}
fn env_stack_principal() -> &'static EnvStackRefFFI {
&PRINCIPAL_STACK
}
impl EnvStackRefFFI {
fn set_one(&self, name: &CxxWString, flags: u16, value: &CxxWString) -> EnvStackSetResult {
self.0.set_one(
name.as_wstr(),
EnvMode::from_bits(flags).unwrap(),
value.from_ffi(),
)
}
fn get(&self, name: &CxxWString) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(&*self.0, name, 0)
}
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(&*self.0, name, mode)
}
fn get_unless_empty(&self, name: &CxxWString) -> *mut EnvVar {
EnvironmentFFI::getf_unless_empty_ffi(&*self.0, name, 0)
}
fn getf_unless_empty(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_unless_empty_ffi(&*self.0, name, mode)
}
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) {
EnvironmentFFI::get_names_ffi(&*self.0, flags, out)
}
fn is_principal(&self) -> bool {
self.0.is_principal()
}
fn get_pwd_slash(&self) -> UniquePtr<CxxWString> {
self.0.get_pwd_slash().to_ffi()
}
fn push(&self, new_scope: bool) {
self.0.push(new_scope)
}
fn pop(&self) {
self.0.pop()
}
fn get_last_statuses(&self) -> Box<Statuses> {
Box::new(self.0.get_last_statuses())
}
fn set_last_statuses(&self, status: i32, kill_signal: i32, pipestatus: &CxxVector<i32>) {
let statuses = Statuses {
status,
kill_signal: if kill_signal == 0 {
None
} else {
Some(Signal::new(kill_signal))
},
pipestatus: pipestatus.as_slice().to_vec(),
};
self.0.set_last_statuses(statuses)
}
fn set_pwd_from_getcwd(&self) {
self.0.set_pwd_from_getcwd()
}
fn set(&self, name: &CxxWString, flags: u16, vals: &wcstring_list_ffi_t) -> EnvStackSetResult {
let mode = EnvMode::from_bits(flags).expect("Invalid mode bits");
self.0.set(name.as_wstr(), mode, vals.from_ffi())
}
fn remove(&self, name: &CxxWString, flags: u16) -> EnvStackSetResult {
let mode = EnvMode::from_bits(flags).expect("Invalid mode bits");
self.0.remove(name.as_wstr(), mode)
}
fn snapshot(&self) -> Box<EnvDynFFI> {
Box::new(EnvDynFFI(self.0.snapshot()))
}
}
unsafe impl cxx::ExternType for EnvStackRefFFI {
type Id = cxx::type_id!("EnvStackRef"); // CXX name!
type Kind = cxx::kind::Opaque;
}
unsafe impl cxx::ExternType for Statuses {
type Id = cxx::type_id!("Statuses");
type Kind = cxx::kind::Opaque;
}
fn statuses_just_ffi(s: i32) -> Box<Statuses> {
Box::new(Statuses::just(s))
}
impl Statuses {
fn get_status_ffi(&self) -> i32 {
self.status
}
fn get_pipestatus_ffi(&self) -> &Vec<i32> {
&self.pipestatus
}
fn get_kill_signal_ffi(&self) -> i32 {
match self.kill_signal {
Some(sig) => sig.code(),
None => 0,
}
}
}
fn env_get_globals_ffi() -> Box<EnvStackRefFFI> {
Box::new(EnvStackRefFFI(EnvStack::globals().clone()))
}
fn env_get_principal_ffi() -> Box<EnvStackRefFFI> {
Box::new(EnvStackRefFFI(EnvStack::principal().clone()))
}
// We have to implement these directly to make cxx happy, even though they're implemented in the EnvironmentFFI trait.
impl EnvNull {
pub fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(self, name, mode)
}
pub fn get_names_ffi(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) {
EnvironmentFFI::get_names_ffi(self, flags, out)
}
}
trait EnvironmentFFI: Environment {
/// FFI helper.
/// This returns either null, or the result of Box.into_raw().
/// This is a workaround for the difficulty of passing an Option through FFI.
fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
match self.getf(
name.as_wstr(),
EnvMode::from_bits(mode).expect("Invalid mode bits"),
) {
None => std::ptr::null_mut(),
Some(var) => Box::into_raw(Box::new(var)),
}
}
fn getf_unless_empty_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
match self.getf(
name.as_wstr(),
EnvMode::from_bits(mode).expect("Invalid mode bits"),
) {
None => std::ptr::null_mut(),
Some(var) => {
if var.is_empty() {
std::ptr::null_mut()
} else {
Box::into_raw(Box::new(var))
}
}
}
}
fn get_names_ffi(&self, mode: u16, mut out: Pin<&mut wcstring_list_ffi_t>) {
let names = self.get_names(EnvMode::from_bits(mode).expect("Invalid mode bits"));
for name in names {
out.as_mut().push(name.to_ffi());
}
}
}
impl<T: Environment> EnvironmentFFI for T {}
fn var_is_electric_ffi(name: &CxxWString) -> bool {
ElectricVar::for_name(name.as_wstr()).is_some()
}
fn rust_env_init_ffi(do_uvars: bool) {
environment::env_init(None, do_uvars, false);
}
fn env_flags_for_ffi(name: wcharz_t) -> u8 {
EnvVar::flags_for(name.as_wstr()).bits()
}

View File

@@ -6,7 +6,7 @@
use crate::abbrs::{abbrs_get_set, Abbreviation, Position};
use crate::common::{str2wcstring, unescape_string, wcs2zstring, UnescapeStringStyle};
use crate::compat::{stdout_stream, C_PATH_BSHELL, _PATH_BSHELL};
use crate::env::{EnvMode, EnvStackSetResult, EnvVar, Statuses};
use crate::env::{EnvMode, EnvVar, Statuses};
use crate::env_dispatch::{env_dispatch_init, env_dispatch_var_change};
use crate::env_universal_common::{CallbackDataList, EnvUniversal};
use crate::event::Event;
@@ -49,6 +49,34 @@
/// 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);
/// Return values for `EnvStack::set()`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EnvStackSetResult {
ENV_OK,
ENV_PERM,
ENV_SCOPE,
ENV_INVALID,
ENV_NOT_FOUND,
}
impl Default for EnvStackSetResult {
fn default() -> Self {
EnvStackSetResult::ENV_OK
}
}
impl From<EnvStackSetResult> for c_int {
fn from(r: EnvStackSetResult) -> Self {
match r {
EnvStackSetResult::ENV_OK => 0,
EnvStackSetResult::ENV_PERM => 1,
EnvStackSetResult::ENV_SCOPE => 2,
EnvStackSetResult::ENV_INVALID => 3,
EnvStackSetResult::ENV_NOT_FOUND => 4,
}
}
}
/// An environment is read-only access to variable values.
pub trait Environment {
/// Get a variable by name using default flags.

View File

@@ -1,10 +1,8 @@
mod env_ffi;
pub mod environment;
mod environment_impl;
pub mod var;
use crate::common::ToCString;
pub use env_ffi::{EnvDynFFI, EnvStackRefFFI, EnvStackSetResult};
pub use environment::*;
use std::sync::{
atomic::{AtomicBool, AtomicUsize},

View File

@@ -22,15 +22,6 @@
use std::ptr;
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]
@@ -354,11 +345,6 @@ pub fn env_dispatch_init(vars: &EnvStack) {
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);

View File

@@ -4,98 +4,29 @@
//! defined when these functions produce output or perform memory allocations, since such functions
//! may not be safely called by signal handlers.
use crate::ffi::wcstring_list_ffi_t;
use cxx::{CxxWString, UniquePtr};
use libc::pid_t;
use std::num::NonZeroU32;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use crate::common::{escape, scoped_push_replacer, ScopeGuard};
use crate::flog::FLOG;
use crate::io::{IoChain, IoStreams};
use crate::job_group::{JobId, MaybeJobId};
use crate::job_group::MaybeJobId;
use crate::parser::{Block, Parser};
use crate::signal::{signal_check_cancel, signal_handle, Signal};
use crate::termsize;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{wcharz_t, AsWstr, WCharFromFFI, WCharToFFI};
#[cxx::bridge]
mod event_ffi {
extern "C++" {
include!("wutil.h");
include!("parser.h");
include!("io.h");
type wcharz_t = crate::ffi::wcharz_t;
type Parser = crate::parser::Parser;
type IoStreams<'a> = crate::io::IoStreams<'a>;
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
}
enum event_type_t {
any,
signal,
variable,
process_exit,
job_exit,
caller_exit,
generic,
}
struct event_description_t {
typ: event_type_t,
signal: i32,
pid: i32,
internal_job_id: u64,
caller_id: u64,
str_param1: UniquePtr<CxxWString>,
}
extern "Rust" {
type EventHandler;
type Event;
fn new_event_generic(desc: wcharz_t) -> Box<Event>;
fn new_event_variable_erase(name: &CxxWString) -> Box<Event>;
fn new_event_variable_set(name: &CxxWString) -> Box<Event>;
fn new_event_process_exit(pid: i32, status: i32) -> Box<Event>;
fn new_event_job_exit(pgid: i32, jid: u64) -> Box<Event>;
fn new_event_caller_exit(internal_job_id: u64, job_id: i32) -> Box<Event>;
#[cxx_name = "clone"]
fn clone_ffi(self: &Event) -> Box<Event>;
#[cxx_name = "event_add_handler"]
fn event_add_handler_ffi(desc: &event_description_t, name: &CxxWString);
#[cxx_name = "event_remove_function_handlers"]
fn event_remove_function_handlers_ffi(name: &CxxWString) -> usize;
#[cxx_name = "event_get_function_handler_descs"]
fn event_get_function_handler_descs_ffi(name: &CxxWString) -> Vec<event_description_t>;
fn desc(self: &EventHandler) -> event_description_t;
fn function_name(self: &EventHandler) -> UniquePtr<CxxWString>;
fn set_removed(self: &mut EventHandler);
fn event_fire_generic_ffi(
parser: &Parser,
name: &CxxWString,
arguments: &wcstring_list_ffi_t,
);
#[cxx_name = "event_fire_delayed"]
fn fire_delayed(parser: &Parser);
#[cxx_name = "event_print"]
fn event_print_ffi(streams: Pin<&mut IoStreams>, type_filter: &CxxWString);
#[cxx_name = "event_enqueue_signal"]
fn enqueue_signal(signal: i32);
#[cxx_name = "event_is_signal_observed"]
fn is_signal_observed(sig: i32) -> bool;
}
pub enum event_type_t {
any,
signal,
variable,
process_exit,
job_exit,
caller_exit,
generic,
}
pub use event_ffi::{event_description_t, event_type_t};
pub const ANY_PID: pid_t = 0;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -191,64 +122,6 @@ fn from(desc: &EventDescription) -> Self {
}
}
impl From<&event_description_t> for EventDescription {
fn from(desc: &event_description_t) -> Self {
match desc.typ {
event_type_t::any => EventDescription::Any,
event_type_t::signal => EventDescription::Signal {
signal: Signal::new(desc.signal),
},
event_type_t::variable => EventDescription::Variable {
name: desc.str_param1.from_ffi(),
},
event_type_t::process_exit => EventDescription::ProcessExit { pid: desc.pid },
event_type_t::job_exit => EventDescription::JobExit {
pid: desc.pid,
internal_job_id: desc.internal_job_id,
},
event_type_t::caller_exit => EventDescription::CallerExit {
caller_id: desc.caller_id,
},
event_type_t::generic => EventDescription::Generic {
param: desc.str_param1.from_ffi(),
},
_ => panic!("invalid event description"),
}
}
}
impl From<&EventDescription> for event_description_t {
fn from(desc: &EventDescription) -> Self {
let mut result = event_description_t {
typ: desc.into(),
signal: Default::default(),
pid: Default::default(),
internal_job_id: Default::default(),
caller_id: Default::default(),
str_param1: match desc.str_param1() {
Some(param) => param.to_ffi(),
None => UniquePtr::null(),
},
};
match *desc {
EventDescription::Any => (),
EventDescription::Signal { signal } => result.signal = signal.code(),
EventDescription::Variable { .. } => (),
EventDescription::ProcessExit { pid } => result.pid = pid,
EventDescription::JobExit {
pid,
internal_job_id,
} => {
result.pid = pid;
result.internal_job_id = internal_job_id;
}
EventDescription::CallerExit { caller_id } => result.caller_id = caller_id,
EventDescription::Generic { .. } => (),
}
result
}
}
#[derive(Debug)]
pub struct EventHandler {
/// Properties of the event to match.
@@ -327,12 +200,6 @@ fn matches(&self, event: &Event) -> bool {
type EventHandlerList = Vec<Arc<EventHandler>>;
impl EventHandler {
fn desc(&self) -> event_description_t {
(&self.desc).into()
}
fn function_name(self: &EventHandler) -> UniquePtr<CxxWString> {
self.function_name.to_ffi()
}
fn set_removed(self: &mut EventHandler) {
self.removed.store(true, Ordering::Relaxed);
}
@@ -416,49 +283,6 @@ fn is_blocked(&self, parser: &Parser) -> bool {
}
}
fn new_event_generic(desc: wcharz_t) -> Box<Event> {
Box::new(Event::generic(desc.into()))
}
fn new_event_variable_erase(name: &CxxWString) -> Box<Event> {
Box::new(Event::variable_erase(name.from_ffi()))
}
fn new_event_variable_set(name: &CxxWString) -> Box<Event> {
Box::new(Event::variable_set(name.from_ffi()))
}
fn new_event_process_exit(pid: i32, status: i32) -> Box<Event> {
Box::new(Event::process_exit(pid, status))
}
fn new_event_job_exit(pgid: i32, jid: u64) -> Box<Event> {
Box::new(Event::job_exit(pgid, jid))
}
fn new_event_caller_exit(internal_job_id: u64, job_id: i32) -> Box<Event> {
Box::new(Event::caller_exit(
internal_job_id,
MaybeJobId(if job_id == -1 {
None
} else {
Some(JobId::new(
NonZeroU32::new(u32::try_from(job_id).unwrap()).unwrap(),
))
}),
))
}
impl Event {
fn clone_ffi(&self) -> Box<Event> {
Box::new(self.clone())
}
}
fn event_add_handler_ffi(desc: &event_description_t, name: &CxxWString) {
add_handler(EventHandler::new(desc.into(), Some(name.from_ffi())));
}
/// All the signals we are interested in are in the 1-32 range (with 32 being the typical SIGRTMAX),
/// but we can expand it to 64 just to be safe. All code checks if a signal value is within bounds
/// before handling it.
@@ -623,10 +447,6 @@ pub fn remove_function_handlers(name: &wstr) -> usize {
remove_handlers_if(|h| h.function_name == name)
}
fn event_remove_function_handlers_ffi(name: &CxxWString) -> usize {
remove_function_handlers(name.as_wstr())
}
/// Return all event handlers for the given function.
pub fn get_function_handlers(name: &wstr) -> EventHandlerList {
EVENT_HANDLERS
@@ -638,13 +458,6 @@ pub fn get_function_handlers(name: &wstr) -> EventHandlerList {
.collect()
}
fn event_get_function_handler_descs_ffi(name: &CxxWString) -> Vec<event_description_t> {
get_function_handlers(name.as_wstr())
.iter()
.map(|h| event_description_t::from(&h.desc))
.collect()
}
/// Perform the specified event. Since almost all event firings will not be matched by even a single
/// event handler, we make sure to optimize the 'no matches' path. This means that nothing is
/// allocated/initialized unless needed.
@@ -869,10 +682,6 @@ pub fn print(streams: &mut IoStreams, type_filter: &wstr) {
}
}
fn event_print_ffi(streams: Pin<&mut IoStreams>, type_filter: &CxxWString) {
print(streams.get_mut(), type_filter.as_wstr());
}
/// Fire a generic event with the specified name.
pub fn fire_generic(parser: &Parser, name: WString, arguments: Vec<WString>) {
fire(
@@ -883,7 +692,3 @@ pub fn fire_generic(parser: &Parser, name: WString, arguments: Vec<WString>) {
},
)
}
fn event_fire_generic_ffi(parser: &Parser, name: &CxxWString, arguments: &wcstring_list_ffi_t) {
fire_generic(parser, name.from_ffi(), arguments.from_ffi());
}

View File

@@ -16,7 +16,6 @@
use crate::env_dispatch::use_posix_spawn;
use crate::fds::make_fd_blocking;
use crate::fds::{make_autoclose_pipes, open_cloexec, AutoCloseFd, AutoClosePipes, PIPE_ERROR};
use crate::ffi::wcstring_list_ffi_t;
use crate::flog::FLOGF;
use crate::fork_exec::blocked_signals_for_job;
use crate::fork_exec::postfork::{
@@ -47,13 +46,9 @@
use crate::trace::trace_if_enabled_with_args;
use crate::wchar::{wstr, WString, L};
use crate::wchar_ext::ToWString;
use crate::wchar_ffi::AsWstr;
use crate::wchar_ffi::WCharToFFI;
use crate::wutil::{fish_wcstol, perror};
use crate::wutil::{wgettext, wgettext_fmt};
use cxx::{CxxWString, UniquePtr};
use errno::{errno, set_errno};
use libc::c_int;
use libc::{
c_char, EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, O_NOCTTY,
O_RDONLY, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO,
@@ -1501,34 +1496,3 @@ fn exec_subshell_internal(
*break_expand = false;
eval_res.status.status_value()
}
#[cxx::bridge]
mod exec_ffi {
extern "C++" {
include!("wutil.h");
include!("parser.h");
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
type Parser = crate::parser::Parser;
}
extern "Rust" {
#[cxx_name = "exec_subshell"]
fn exec_subshell_ffi(
cmd: &CxxWString,
parser: &Parser,
outputs: &mut UniquePtr<wcstring_list_ffi_t>,
apply_exit_status: bool,
) -> i32;
}
}
fn exec_subshell_ffi(
cmd: &CxxWString,
parser: &Parser,
outputs: &mut UniquePtr<wcstring_list_ffi_t>,
apply_exit_status: bool,
) -> c_int {
let mut tmp = vec![];
let ret = exec_subshell(cmd.as_wstr(), parser, Some(&mut tmp), apply_exit_status);
*outputs = tmp.to_ffi();
ret
}

View File

@@ -13,7 +13,7 @@
EXPAND_RESERVED_END,
};
use crate::complete::{CompleteFlags, Completion, CompletionList, CompletionReceiver};
use crate::env::{EnvStackRefFFI, EnvVar, Environment};
use crate::env::{EnvVar, Environment};
use crate::exec::exec_subshell_for_expand;
use crate::history::{history_session_id, History};
use crate::operation_context::OperationContext;
@@ -22,13 +22,11 @@
use crate::path::path_apply_working_directory;
use crate::util::wcsfilecmp_glob;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{WCharFromFFI, WCharToFFI};
use crate::wcstringutil::{join_strings, trim};
use crate::wildcard::{wildcard_expand_string, wildcard_has_internal};
use crate::wildcard::{WildcardResult, ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use crate::wutil::{normalize_path, wcstoi_partial, Options};
use bitflags::bitflags;
use cxx::{CxxWString, UniquePtr};
bitflags! {
/// Set of flags controlling expansions.
@@ -99,8 +97,6 @@ pub struct ExpandFlags : u16 {
"Characters used in expansions must stay within private use area"
);
pub use expand_ffi::{ExpandResult, ExpandResultCode};
impl ExpandResult {
pub fn new(result: ExpandResultCode) -> Self {
Self { result, status: 0 }
@@ -1571,59 +1567,28 @@ fn unexpand_tildes(&self, input: &wstr, completions: &mut CompletionList) {
}
}
#[cxx::bridge]
mod expand_ffi {
extern "C++" {
include!("operation_context.h");
include!("parse_constants.h");
include!("env.h");
include!("complete.h");
type OperationContext<'a> = crate::operation_context::OperationContext<'a>;
type ParseErrorListFfi = crate::parse_constants::ParseErrorListFfi;
#[cxx_name = "EnvDyn"]
type EnvDynFFI = crate::env::EnvDynFFI;
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI = crate::env::EnvStackRefFFI;
type CompletionListFfi = crate::complete::CompletionListFfi;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExpandResultCode {
/// There was an error, for example, unmatched braces.
error,
/// Expansion succeeded.
ok,
/// Expansion was cancelled (e.g. control-C).
cancel,
/// Expansion succeeded, but a wildcard in the string matched no files,
/// so the output is empty.
wildcard_no_match,
}
/// These are the possible return values for expand_string.
#[must_use]
#[derive(Debug)]
pub struct ExpandResult {
/// The result of expansion.
pub result: ExpandResultCode,
/// If expansion resulted in an error, this is an appropriate value with which to populate
/// $status.
// todo!("should be c_int?");
pub status: i32,
}
extern "Rust" {
#[cxx_name = "expand_home_directory"]
fn expand_home_directory_ffi(
input: &CxxWString,
vars: &EnvStackRefFFI,
) -> UniquePtr<CxxWString>;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExpandResultCode {
/// There was an error, for example, unmatched braces.
error,
/// Expansion succeeded.
ok,
/// Expansion was cancelled (e.g. control-C).
cancel,
/// Expansion succeeded, but a wildcard in the string matched no files,
/// so the output is empty.
wildcard_no_match,
}
fn expand_home_directory_ffi(input: &CxxWString, vars: &EnvStackRefFFI) -> UniquePtr<CxxWString> {
let mut s = input.from_ffi();
expand_home_directory(&mut s, &*vars.0);
s.to_ffi()
/// These are the possible return values for expand_string.
#[must_use]
#[derive(Debug)]
pub struct ExpandResult {
/// The result of expansion.
pub result: ExpandResultCode,
/// If expansion resulted in an error, this is an appropriate value with which to populate
/// $status.
// todo!("should be c_int?");
pub status: i32,
}

View File

@@ -3,11 +3,9 @@
use std::sync::{Arc, Mutex, Weak};
use std::time::{Duration, Instant};
pub use self::fd_monitor_ffi::ItemWakeReason;
use crate::common::exit_without_destructors;
use crate::fd_readable_set::FdReadableSet;
use crate::fds::AutoCloseFd;
use crate::ffi::void_ptr;
use crate::flog::FLOG;
use crate::threads::assert_is_background_thread;
use crate::wutil::perror;
@@ -19,61 +17,15 @@
#[cfg(HAVE_EVENTFD)]
use libc::{EFD_CLOEXEC, EFD_NONBLOCK};
#[cxx::bridge]
mod fd_monitor_ffi {
/// Reason for waking an item
#[repr(u8)]
#[cxx_name = "item_wake_reason_t"]
#[derive(PartialEq, Eq)]
enum ItemWakeReason {
/// The fd became readable (or was HUP'd)
Readable,
/// The requested timeout was hit
Timeout,
/// The item was "poked" (woken up explicitly)
Poke,
}
extern "Rust" {
#[cxx_name = "fd_monitor_item_id_t"]
type FdMonitorItemId;
}
extern "Rust" {
#[cxx_name = "fd_monitor_item_t"]
type FdMonitorItem;
#[cxx_name = "make_fd_monitor_item_t"]
fn new_fd_monitor_item_ffi(
fd: i32,
timeout_usecs: u64,
callback: *const u8,
param: *const u8,
) -> Box<FdMonitorItem>;
}
extern "Rust" {
#[cxx_name = "fd_monitor_t"]
type FdMonitor;
#[cxx_name = "make_fd_monitor_t"]
fn new_fd_monitor_ffi() -> Box<FdMonitor>;
#[cxx_name = "add_item"]
fn add_item_ffi(
&mut self,
fd: i32,
timeout_usecs: u64,
callback: *const u8,
param: *const u8,
) -> u64;
#[cxx_name = "poke_item"]
fn poke_item_ffi(&self, item_id: u64);
#[cxx_name = "add"]
pub fn add_ffi(&mut self, item: Box<FdMonitorItem>) -> u64;
}
/// Reason for waking an item
#[derive(PartialEq, Eq)]
pub enum ItemWakeReason {
/// The fd became readable (or was HUP'd)
Readable,
/// The requested timeout was hit
Timeout,
/// The item was "poked" (woken up explicitly)
Poke,
}
/// An event signaller implemented using a file descriptor, so it can plug into
@@ -239,7 +191,6 @@ fn from(value: u64) -> Self {
}
}
type FfiCallback = extern "C" fn(*mut AutoCloseFd, u8, void_ptr);
pub type NativeCallback = Box<dyn Fn(&mut AutoCloseFd, ItemWakeReason) + Send + Sync>;
/// The callback type used by [`FdMonitorItem`]. It is passed a mutable reference to the
@@ -254,7 +205,6 @@ fn from(value: u64) -> Self {
enum FdMonitorCallback {
None,
Native(NativeCallback),
Ffi(FfiCallback /* fn ptr */, void_ptr /* param */),
}
/// An item containing an fd and callback, which can be monitored to watch when it becomes readable
@@ -321,12 +271,6 @@ fn service_item(&mut self, fds: &FdReadableSet, now: &Instant) -> ItemAction {
match &self.callback {
FdMonitorCallback::None => panic!("Callback not assigned!"),
FdMonitorCallback::Native(callback) => (callback)(&mut self.fd, reason),
FdMonitorCallback::Ffi(callback, param) => {
// Safety: identical objects are generated on both sides by cxx bridge as
// integers of the same size (minimum size to fit the enum).
let reason = unsafe { std::mem::transmute(reason) };
(callback)(&mut self.fd as *mut _, reason, *param)
}
}
if !self.fd.is_valid() {
result = ItemAction::Remove;
@@ -345,12 +289,6 @@ fn maybe_poke_item(&mut self, pokelist: &[FdMonitorItemId]) -> ItemAction {
match &self.callback {
FdMonitorCallback::None => panic!("Callback not assigned!"),
FdMonitorCallback::Native(callback) => (callback)(&mut self.fd, ItemWakeReason::Poke),
FdMonitorCallback::Ffi(callback, param) => {
// Safety: identical objects are generated on both sides by cxx bridge as
// integers of the same size (minimum size to fit the enum).
let reason = unsafe { std::mem::transmute(ItemWakeReason::Poke) };
(callback)(&mut self.fd as *mut _, reason, *param)
}
}
// Return `ItemAction::Remove` if the callback closed the fd
match self.fd.is_valid() {
@@ -379,15 +317,6 @@ pub fn new(
pub fn set_callback(&mut self, callback: NativeCallback) {
self.callback = FdMonitorCallback::Native(callback);
}
fn set_callback_ffi(&mut self, callback: *const u8, param: *const u8) {
// Safety: we are just marshalling our function pointers with identical definitions on both
// sides of the ffi bridge as void pointers to keep cxx bridge happy. Whether we invoke the
// raw function as a void pointer or as a typed fn that helps us keep track of what we're
// doing is unsafe in all cases, so might as well make the best of it.
let callback = unsafe { std::mem::transmute(callback) };
self.callback = FdMonitorCallback::Ffi(callback, param.into());
}
}
impl Default for FdMonitorItem {
@@ -402,32 +331,6 @@ fn default() -> Self {
}
}
// cxx bridge does not support "static member functions" in C++ or rust, so we need a top-level fn.
fn new_fd_monitor_ffi() -> Box<FdMonitor> {
Box::new(FdMonitor::new())
}
// cxx bridge does not support "static member functions" in C++ or rust, so we need a top-level fn.
fn new_fd_monitor_item_ffi(
fd: RawFd,
timeout_usecs: u64,
callback: *const u8,
param: *const u8,
) -> Box<FdMonitorItem> {
// Safety: we are just marshalling our function pointers with identical definitions on both
// sides of the ffi bridge as void pointers to keep cxx bridge happy. Whether we invoke the
// raw function as a void pointer or as a typed fn that helps us keep track of what we're
// doing is unsafe in all cases, so might as well make the best of it.
let callback = unsafe { std::mem::transmute(callback) };
let mut item = FdMonitorItem::default();
item.fd.reset(fd);
item.callback = FdMonitorCallback::Ffi(callback, param.into());
if timeout_usecs != FdReadableSet::kNoTimeout {
item.timeout = Some(Duration::from_micros(timeout_usecs));
}
return Box::new(item);
}
/// A thread-safe class which can monitor a set of fds, invoking a callback when any becomes
/// readable (or has been HUP'd) or when per-item-configurable timeouts are reached.
pub struct FdMonitor {
@@ -475,11 +378,6 @@ struct BackgroundFdMonitor {
}
impl FdMonitor {
#[allow(clippy::boxed_local)]
pub fn add_ffi(&self, item: Box<FdMonitorItem>) -> u64 {
self.add(*item).0
}
/// Add an item to the monitor. Returns the [`FdMonitorItemId`] assigned to the item.
pub fn add(&self, mut item: FdMonitorItem) -> FdMonitorItemId {
assert!(item.fd.is_valid());
@@ -523,29 +421,6 @@ pub fn add(&self, mut item: FdMonitorItem) -> FdMonitorItemId {
item_id
}
/// Avoid requiring a separate UniquePtr for each item C++ wants to add to the set by giving an
/// all-in-one entry point that can initialize the item on our end and insert it to the set.
fn add_item_ffi(
&mut self,
fd: RawFd,
timeout_usecs: u64,
callback: *const u8,
param: *const u8,
) -> u64 {
// Safety: we are just marshalling our function pointers with identical definitions on both
// sides of the ffi bridge as void pointers to keep cxx bridge happy. Whether we invoke the
// raw function as a void pointer or as a typed fn that helps us keep track of what we're
// doing is unsafe in all cases, so might as well make the best of it.
let callback = unsafe { std::mem::transmute(callback) };
let mut item = FdMonitorItem::default();
item.fd.reset(fd);
item.callback = FdMonitorCallback::Ffi(callback, param.into());
if timeout_usecs != FdReadableSet::kNoTimeout {
item.timeout = Some(Duration::from_micros(timeout_usecs));
}
self.add(item).0
}
/// Mark that the item with the given ID needs to be woken up explicitly.
pub fn poke_item(&self, item_id: FdMonitorItemId) {
assert!(item_id.0 > 0, "Invalid item id!");
@@ -564,10 +439,6 @@ pub fn poke_item(&self, item_id: FdMonitorItemId) {
}
}
fn poke_item_ffi(&self, item_id: u64) {
self.poke_item(FdMonitorItemId(item_id))
}
pub fn new() -> Self {
Self {
data: Arc::new(Mutex::new(SharedData {

View File

@@ -3,20 +3,6 @@
pub use fd_readable_set_t as FdReadableSet;
#[cxx::bridge]
mod fd_readable_set_ffi {
extern "Rust" {
type fd_readable_set_t;
fn new_fd_readable_set() -> Box<fd_readable_set_t>;
fn clear(&mut self);
fn add(&mut self, fd: i32);
fn test(&self, fd: i32) -> bool;
fn check_readable(&mut self, timeout_usec: u64) -> i32;
fn is_fd_readable(fd: i32, timeout_usec: u64) -> bool;
fn poll_fd_readable(fd: i32) -> bool;
}
}
/// Create a new fd_readable_set_t.
pub fn new_fd_readable_set() -> Box<fd_readable_set_t> {
Box::new(fd_readable_set_t::new())

View File

@@ -58,19 +58,6 @@ fn flush(&mut self) -> std::io::Result<()> {
}
}
#[cxx::bridge]
mod autoclose_fd_t {
extern "Rust" {
#[cxx_name = "autoclose_fd_t2"]
type AutoCloseFd;
fn new_autoclose_fd(fd: i32) -> Box<AutoCloseFd>;
#[cxx_name = "valid"]
fn is_valid(&self) -> bool;
fn close(&mut self);
fn fd(&self) -> i32;
}
}
fn new_autoclose_fd(fd: i32) -> Box<AutoCloseFd> {
Box::new(AutoCloseFd::new(fd))
}

View File

@@ -1,156 +1,8 @@
use crate::io::{IoStreams, OutputStreamFfi};
use crate::wchar;
#[rustfmt::skip]
use ::std::pin::Pin;
#[rustfmt::skip]
use ::std::slice;
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharFromFFI;
use autocxx::prelude::*;
// autocxx has been hacked up to know about this.
pub type wchar_t = u32;
include_cpp! {
#include "color.h"
#include "common.h"
#include "env.h"
#include "env_dispatch.h"
#include "env_universal_common.h"
#include "event.h"
#include "exec.h"
#include "fallback.h"
#include "fds.h"
#include "flog.h"
#include "function.h"
#include "io.h"
#include "parse_constants.h"
#include "parser.h"
#include "parse_util.h"
#include "path.h"
#include "pager.h"
#include "proc.h"
#include "reader.h"
#include "screen.h"
#include "tokenizer.h"
#include "wutil.h"
safety!(unsafe_ffi)
generate_pod!("wcharz_t")
generate!("wcstring_list_ffi_t")
generate!("highlight_spec_t")
generate!("rgb_color_t")
generate_pod!("color24_t")
generate_pod!("escape_string_style_t")
}
/// Allow wcharz_t to be "into" wstr.
impl From<wcharz_t> for &wstr {
fn from(w: wcharz_t) -> Self {
let len = w.length();
#[allow(clippy::unnecessary_cast)]
let v = unsafe { slice::from_raw_parts(w.str_ as *const u32, len) };
wstr::from_slice(v).expect("Invalid UTF-32")
}
}
/// Allow wcharz_t to be "into" WString.
impl From<wcharz_t> for WString {
fn from(w: wcharz_t) -> Self {
let w: &wstr = w.into();
w.to_owned()
}
}
/// Allow wcstring_list_ffi_t to be "into" Vec<WString>.
impl From<&wcstring_list_ffi_t> for Vec<wchar::WString> {
fn from(w: &wcstring_list_ffi_t) -> Self {
let mut result = Vec::with_capacity(w.size());
for i in 0..w.size() {
result.push(w.at(i).from_ffi());
}
result
}
}
/// A bogus trait for turning &mut Foo into Pin<&mut Foo>.
/// autocxx enforces that non-const methods must be called through Pin,
/// but this means we can't pass around mutable references to types like Parser.
/// We also don't want to assert that Parser is Unpin.
/// So we just allow constructing a pin from a mutable reference; none of the C++ code.
/// It's worth considering disabling this in cxx; for now we use this trait.
/// Eventually Parser and IoStreams will not require Pin so we just unsafe-it away.
pub trait Repin {
fn pin(&mut self) -> Pin<&mut Self> {
unsafe { Pin::new_unchecked(self) }
}
fn unpin(self: Pin<&mut Self>) -> &mut Self {
unsafe { self.get_unchecked_mut() }
}
}
// Implement Repin for our types.
impl Repin for IoStreams<'_> {}
impl Repin for wcstring_list_ffi_t {}
impl Repin for rgb_color_t {}
impl Repin for OutputStreamFfi<'_> {}
pub use ffi::*;
/// A version of [`* const core::ffi::c_void`] (or [`* const libc::c_void`], if you prefer) that
/// implements `Copy` and `Clone`, because those two don't. Used to represent a `void *` ptr for ffi
/// purposes.
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct void_ptr(pub *const core::ffi::c_void);
impl core::fmt::Debug for void_ptr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:p}", &self.0)
}
}
unsafe impl Send for void_ptr {}
unsafe impl Sync for void_ptr {}
impl core::convert::From<*const core::ffi::c_void> for void_ptr {
fn from(value: *const core::ffi::c_void) -> Self {
Self(value as *const _)
}
}
impl core::convert::From<*const u8> for void_ptr {
fn from(value: *const u8) -> Self {
Self(value as *const _)
}
}
impl core::convert::From<*const autocxx::c_void> for void_ptr {
fn from(value: *const autocxx::c_void) -> Self {
Self(value as *const _)
}
}
impl core::convert::From<void_ptr> for *const u8 {
fn from(value: void_ptr) -> Self {
value.0 as *const _
}
}
impl core::convert::From<void_ptr> for *const core::ffi::c_void {
fn from(value: void_ptr) -> Self {
value.0 as *const _
}
}
impl core::convert::From<void_ptr> for *const autocxx::c_void {
fn from(value: void_ptr) -> Self {
value.0 as *const _
}
}

View File

@@ -1,18 +1,10 @@
/// Bridged functions concerned with initialization.
use crate::ffi::wcharz_t;
use crate::locale;
#[cxx::bridge]
mod ffi2 {
extern "C++" {
include!("wutil.h");
type wcharz_t = super::wcharz_t;
}
extern "Rust" {
fn rust_init();
fn rust_activate_flog_categories_by_pattern(wc_ptr: wcharz_t);
fn rust_set_flog_file_fd(fd: i32);
fn rust_invalidate_numeric_locale();
}
@@ -24,11 +16,6 @@ fn rust_init() {
crate::threads::init();
}
/// FFI bridge for activate_flog_categories_by_pattern().
fn rust_activate_flog_categories_by_pattern(wc_ptr: wcharz_t) {
crate::flog::activate_flog_categories_by_pattern(wc_ptr.into());
}
/// FFI bridge for setting FLOG file descriptor.
fn rust_set_flog_file_fd(fd: i32) {
crate::flog::set_flog_file_fd(fd as libc::c_int);

View File

@@ -994,7 +994,6 @@ fn highlight_role_to_string(role: HighlightRole) -> &'static wstr {
HighlightRole::pager_selected_prefix => L!("pager_selected_prefix"),
HighlightRole::pager_selected_completion => L!("pager_selected_completion"),
HighlightRole::pager_selected_description => L!("pager_selected_description"),
_ => unreachable!(),
}
}

View File

@@ -6,7 +6,7 @@
use crate::nix::getpid;
use crate::redirection::Dup2List;
use crate::signal::signal_reset_handlers;
use libc::{c_char, c_int, pid_t};
use libc::{c_char, pid_t};
use std::ffi::CStr;
/// The number of times to try to call fork() before giving up.
@@ -546,60 +546,3 @@ fn get_interpreter<'a>(command: &CStr, buffer: &'a mut [u8]) -> Option<&'a CStr>
};
Some(CStr::from_bytes_with_nul(&buffer[offset..idx.max(offset)]).unwrap())
}
/// Set up redirections and signal handling in the child process.
mod ffi {
use super::*;
#[no_mangle]
pub extern "C" fn child_setup_process(
claim_tty_from: pid_t,
sigmask: *const libc::sigset_t,
is_forked: bool,
dup2s: *const Dup2List,
) -> i32 {
let sigmask = unsafe { sigmask.as_ref() };
let dup2s = unsafe { &*dup2s };
super::child_setup_process(claim_tty_from, sigmask, is_forked, dup2s)
}
#[no_mangle]
pub extern "C" fn safe_report_exec_error(
err: i32,
actual_cmd: *const c_char,
argvv: *const *const c_char,
envv: *const *const c_char,
) {
super::safe_report_exec_error(err, actual_cmd, argvv, envv)
}
#[no_mangle]
pub extern "C" fn execute_fork() -> pid_t {
super::execute_fork()
}
#[no_mangle]
pub extern "C" fn execute_setpgid(pid: pid_t, pgroup: pid_t, is_parent: bool) -> i32 {
super::execute_setpgid(pid, pgroup, is_parent)
}
#[no_mangle]
pub extern "C" fn report_setpgid_error(
err: i32,
is_parent: bool,
pid: pid_t,
desired_pgid: pid_t,
job_id: c_int,
command_str: *const c_char,
argv0_str: *const c_char,
) {
super::report_setpgid_error(
err,
is_parent,
pid,
desired_pgid,
job_id.into(),
command_str,
argv0_str,
)
}
}

View File

@@ -5,7 +5,7 @@
use crate::proc::Job;
use crate::redirection::Dup2List;
use crate::signal::get_signals_with_handlers;
use errno::{self, set_errno, Errno};
use errno::{self, Errno};
use libc::{self, c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
use std::ffi::{CStr, CString};
@@ -219,27 +219,3 @@ fn get_path_bshell() -> CString {
// which fail to run Thompson shell scripts; we simply assume it is /bin/sh.
CString::new("/bin/sh").unwrap()
}
impl Drop for PosixSpawner {
fn drop(&mut self) {
// Necessary to define this for FFI purposes, to avoid link errors.
}
}
impl PosixSpawner {
/// Returns a pid, or -1, in which case errno is set.
fn spawn_ffi(
&mut self,
cmd: *const c_char,
argv: *const *mut c_char,
envp: *const *mut c_char,
) -> i32 {
match self.spawn(cmd, argv, envp) {
Ok(pid) => pid,
Err(err) => {
set_errno(err);
-1
}
}
}
}

View File

@@ -9,17 +9,13 @@
use crate::env::{EnvStack, Environment};
use crate::event::{self, EventDescription};
use crate::global_safety::RelaxedAtomicBool;
use crate::parse_tree::{NodeRef, ParsedSourceRefFFI};
use crate::parse_tree::NodeRef;
use crate::parser::Parser;
use crate::parser_keywords::parser_keywords_is_reserved;
use crate::wchar::prelude::*;
use crate::wchar_ffi::wcstring_list_ffi_t;
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use crate::wutil::{dir_iter::DirIter, gettext::wgettext_expr, sprintf};
use cxx::{CxxWString, UniquePtr};
use once_cell::sync::Lazy;
use std::collections::{HashMap, HashSet};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
@@ -496,203 +492,3 @@ pub fn annotated_definition(&self, name: &wstr) -> WString {
out
}
}
pub struct FunctionPropertiesRefFFI(pub Arc<FunctionProperties>);
impl FunctionPropertiesRefFFI {
fn definition_file(&self) -> UniquePtr<CxxWString> {
if let Some(file) = self.0.definition_file() {
file.to_ffi()
} else {
UniquePtr::null()
}
}
fn definition_lineno(&self) -> i32 {
self.0.definition_lineno()
}
fn copy_definition_lineno(&self) -> i32 {
self.0.copy_definition_lineno()
}
fn shadow_scope(&self) -> bool {
self.0.shadow_scope
}
fn named_arguments(&self) -> UniquePtr<wcstring_list_ffi_t> {
self.0.named_arguments.to_ffi()
}
fn get_description(&self) -> UniquePtr<CxxWString> {
self.0.description.to_ffi()
}
fn annotated_definition(&self, name: &CxxWString) -> UniquePtr<CxxWString> {
self.0.annotated_definition(name.as_wstr()).to_ffi()
}
fn is_autoload(&self) -> bool {
self.0.is_autoload.load()
}
fn is_copy(&self) -> bool {
self.0.is_copy
}
fn get_block_statement_node_ffi(&self) -> *const u8 {
let stmt: &ast::BlockStatement = &self.0.func_node;
stmt as *const ast::BlockStatement as *const u8
}
fn parsed_source_ffi(&self) -> *mut u8 {
let source = self.0.func_node.parsed_source_ref();
let res = Box::new(ParsedSourceRefFFI(Some(source)));
Box::into_raw(res) as *mut u8
}
fn copy_definition_file_ffi(&self) -> UniquePtr<CxxWString> {
if let Some(file) = self.0.copy_definition_file() {
file.to_ffi()
} else {
UniquePtr::null()
}
}
}
#[allow(clippy::boxed_local)]
fn function_add_ffi(name: &CxxWString, props: Box<FunctionPropertiesRefFFI>) {
add(name.from_ffi(), props.0);
}
fn function_remove_ffi(name: &CxxWString) {
remove(name.as_wstr());
}
fn function_get_props_ffi(name: &CxxWString) -> *mut FunctionPropertiesRefFFI {
let props = get_props(name.as_wstr());
if let Some(props) = props {
Box::into_raw(Box::new(FunctionPropertiesRefFFI(props)))
} else {
std::ptr::null_mut()
}
}
fn function_get_props_autoload_ffi(
name: &CxxWString,
parser: &Parser,
) -> *mut FunctionPropertiesRefFFI {
let props = get_props_autoload(name.as_wstr(), parser);
if let Some(props) = props {
Box::into_raw(Box::new(FunctionPropertiesRefFFI(props)))
} else {
std::ptr::null_mut()
}
}
fn function_load_ffi(name: &CxxWString, parser: &Parser) -> bool {
load(name.as_wstr(), parser)
}
fn function_set_desc_ffi(name: &CxxWString, desc: &CxxWString, parser: &Parser) {
set_desc(name.as_wstr(), desc.from_ffi(), parser);
}
fn function_exists_ffi(cmd: &CxxWString, parser: &Parser) -> bool {
exists(cmd.as_wstr(), parser)
}
fn function_exists_no_autoload_ffi(cmd: &CxxWString) -> bool {
exists_no_autoload(cmd.as_wstr())
}
fn function_get_names_ffi(get_hidden: bool, mut out: Pin<&mut wcstring_list_ffi_t>) {
let names = get_names(get_hidden);
for name in names {
out.as_mut().push(name.to_ffi());
}
}
fn function_copy_ffi(name: &CxxWString, new_name: &CxxWString, parser: &Parser) -> bool {
copy(name.as_wstr(), new_name.from_ffi(), parser)
}
#[cxx::bridge]
mod function_ffi {
extern "C++" {
include!("ast.h");
include!("parse_tree.h");
include!("parser.h");
include!("wutil.h");
type Parser = crate::parser::Parser;
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
}
extern "Rust" {
#[cxx_name = "function_properties_t"]
type FunctionPropertiesRefFFI;
fn definition_file(&self) -> UniquePtr<CxxWString>;
fn definition_lineno(&self) -> i32;
fn copy_definition_lineno(&self) -> i32;
fn shadow_scope(&self) -> bool;
fn named_arguments(&self) -> UniquePtr<wcstring_list_ffi_t>;
fn get_description(&self) -> UniquePtr<CxxWString>;
fn annotated_definition(&self, name: &CxxWString) -> UniquePtr<CxxWString>;
fn is_autoload(&self) -> bool;
fn is_copy(&self) -> bool;
#[cxx_name = "copy_definition_file"]
fn copy_definition_file_ffi(&self) -> UniquePtr<CxxWString>;
/// Returns unowned pointer to BlockStatement, cast to a u8.
#[cxx_name = "get_block_statement_node"]
fn get_block_statement_node_ffi(&self) -> *const u8;
/// Returns rust::Box<ParsedSourceRefFFI>::into_raw(), cast to a u8.
#[cxx_name = "parsed_source"]
fn parsed_source_ffi(self: &FunctionPropertiesRefFFI) -> *mut u8;
#[cxx_name = "function_add"]
fn function_add_ffi(name: &CxxWString, props: Box<FunctionPropertiesRefFFI>);
#[cxx_name = "function_remove"]
fn function_remove_ffi(name: &CxxWString);
/// Returns a Box<FunctionPropertiesRefFFI>::into_raw(), or nullptr if None.
#[cxx_name = "function_get_props_raw"]
fn function_get_props_ffi(name: &CxxWString) -> *mut FunctionPropertiesRefFFI;
/// Returns a Box<FunctionPropertiesRefFFI>::into_raw(), or nullptr if None.
#[cxx_name = "function_get_props_autoload_raw"]
fn function_get_props_autoload_ffi(
name: &CxxWString,
parser: &Parser,
) -> *mut FunctionPropertiesRefFFI;
#[cxx_name = "function_load"]
fn function_load_ffi(name: &CxxWString, parser: &Parser) -> bool;
#[cxx_name = "function_set_desc"]
fn function_set_desc_ffi(name: &CxxWString, desc: &CxxWString, parser: &Parser);
#[cxx_name = "function_exists"]
fn function_exists_ffi(cmd: &CxxWString, parser: &Parser) -> bool;
#[cxx_name = "function_exists_no_autoload"]
fn function_exists_no_autoload_ffi(cmd: &CxxWString) -> bool;
#[cxx_name = "function_get_names"]
fn function_get_names_ffi(get_hidden: bool, out: Pin<&mut wcstring_list_ffi_t>);
#[cxx_name = "function_copy"]
fn function_copy_ffi(name: &CxxWString, new_name: &CxxWString, parser: &Parser) -> bool;
#[cxx_name = "function_invalidate_path"]
fn invalidate_path();
}
}
unsafe impl cxx::ExternType for FunctionPropertiesRefFFI {
type Id = cxx::type_id!("function_properties_t");
type Kind = cxx::kind::Opaque;
}

View File

@@ -1,43 +1,26 @@
//! Flags to enable upcoming features
use crate::ffi::wcharz_t;
use crate::wchar::prelude::*;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
#[cxx::bridge]
mod future_feature_flags_ffi {
extern "C++" {
include!("wutil.h");
type wcharz_t = super::wcharz_t;
}
/// The list of flags.
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum FeatureFlag {
/// Whether ^ is supported for stderr redirection.
stderr_nocaret,
/// The list of flags.
#[repr(u8)]
enum FeatureFlag {
/// Whether ^ is supported for stderr redirection.
stderr_nocaret,
/// Whether ? is supported as a glob.
qmark_noglob,
/// Whether ? is supported as a glob.
qmark_noglob,
/// Whether string replace -r double-unescapes the replacement.
string_replace_backslash,
/// 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,
}
extern "Rust" {
#[cxx_name = "feature_test"]
fn test(flag: FeatureFlag) -> bool;
#[cxx_name = "feature_set_from_string"]
fn set_from_string(str: wcharz_t);
}
/// Whether "&" is not-special if followed by a word character.
ampersand_nobg_in_token,
}
pub use future_feature_flags_ffi::FeatureFlag;
struct Features {
// Values for the flags.
// These are atomic to "fix" a race reported by tsan where tests of feature flags and other
@@ -165,11 +148,11 @@ const fn new() -> Self {
}
fn test(&self, flag: FeatureFlag) -> bool {
self.values[flag.repr as usize].load(Ordering::SeqCst)
self.values[flag as usize].load(Ordering::SeqCst)
}
fn set(&self, flag: FeatureFlag, value: bool) {
self.values[flag.repr as usize].store(value, Ordering::SeqCst)
self.values[flag as usize].store(value, Ordering::SeqCst)
}
#[widestrs]
@@ -244,14 +227,14 @@ fn test_feature_flags() {
// 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;
counts[md.flag as usize] += 1;
}
for count in counts {
assert_eq!(count, 1);
}
assert_eq!(
METADATA[FeatureFlag::stderr_nocaret.repr as usize].name,
METADATA[FeatureFlag::stderr_nocaret as usize].name,
"stderr-nocaret"L
);
}

View File

@@ -5,14 +5,13 @@
Leaf, List, Node, NodeVisitor, Redirection, Token, Type, VariableAssignment,
};
use crate::builtins::shared::builtin_exists;
use crate::color::{self, RgbColor};
use crate::color::RgbColor;
use crate::common::{
unescape_string_in_place, valid_var_name, valid_var_name_char, UnescapeFlags, ASCII_MAX,
EXPAND_RESERVED_BASE, EXPAND_RESERVED_END,
};
use crate::compat::_PC_CASE_SENSITIVE;
use crate::editable_line::EditableLine;
use crate::env::{EnvStackRefFFI, Environment};
use crate::env::Environment;
use crate::expand::{
expand_one, expand_tilde, expand_to_command_and_args, ExpandFlags, ExpandResultCode,
HOME_DIRECTORY, PROCESS_EXPAND_SELF_STR,
@@ -21,7 +20,6 @@
BRACE_BEGIN, BRACE_END, BRACE_SEP, INTERNAL_SEPARATOR, PROCESS_EXPAND_SELF, VARIABLE_EXPAND,
VARIABLE_EXPAND_SINGLE,
};
use crate::ffi::rgb_color_t;
use crate::function;
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::history::{all_paths_are_valid, HistoryItem};
@@ -40,7 +38,6 @@
use crate::tokenizer::{variable_assignment_equals_pos, PipeOrRedir};
use crate::wchar::{wstr, WString, L};
use crate::wchar_ext::WExt;
use crate::wchar_ffi::AsWstr;
use crate::wcstringutil::{
string_prefixes_string, string_prefixes_string_case_insensitive, string_suffixes_string,
};
@@ -50,12 +47,10 @@
use crate::wutil::{normalize_path, waccess, wstat};
use crate::wutil::{wbasename, wdirname};
use bitflags::bitflags;
use cxx::{CxxWString, SharedPtr};
use libc::{ENOENT, PATH_MAX, R_OK, W_OK};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::os::fd::RawFd;
use std::pin::Pin;
impl HighlightSpec {
pub fn new() -> Self {
@@ -1120,7 +1115,6 @@ fn visit_keyword(&mut self, node: &dyn Keyword) {
| ParseKeyword::kw_exclam
| ParseKeyword::kw_time => role = HighlightRole::operat,
ParseKeyword::none => (),
_ => panic!(),
};
self.color_node(node.leaf_as_node(), HighlightSpec::with_fg(role));
}
@@ -1306,7 +1300,6 @@ fn visit_redirection(&mut self, redir: &Redirection) {
target_is_valid = file_is_writable
&& !(file_exists && oper.mode == RedirectionMode::noclob);
}
_ => panic!(),
}
}
self.color_node(
@@ -1537,7 +1530,6 @@ fn get_highlight_var_name(role: HighlightRole) -> &'static wstr {
HighlightRole::pager_selected_prefix => L!("fish_pager_color_selected_prefix"),
HighlightRole::pager_selected_completion => L!("fish_pager_color_selected_completion"),
HighlightRole::pager_selected_description => L!("fish_pager_color_selected_description"),
_ => unreachable!(),
}
}
@@ -1576,7 +1568,6 @@ fn get_fallback(role: HighlightRole) -> HighlightRole {
HighlightRole::pager_description
}
HighlightRole::pager_selected_background => HighlightRole::search_match,
_ => unreachable!(),
}
}
@@ -1612,8 +1603,6 @@ fn fs_is_case_insensitive(
result
}
pub use highlight_ffi::{HighlightRole, HighlightSpec};
impl Default for HighlightRole {
fn default() -> Self {
Self::normal
@@ -1631,222 +1620,48 @@ fn default() -> Self {
}
}
#[cxx::bridge]
mod highlight_ffi {
/// Describes the role of a span of text.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(u8)]
pub enum HighlightRole {
normal, // normal text
error, // error
command, // command
keyword,
statement_terminator, // process separator
param, // command parameter (argument)
option, // argument starting with "-", up to a "--"
comment, // comment
search_match, // search match
operat, // operator
escape, // escape sequences
quote, // quoted string
redirection, // redirection
autosuggestion, // autosuggestion
selection,
/// Describes the role of a span of text.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(u8)]
pub enum HighlightRole {
normal, // normal text
error, // error
command, // command
keyword,
statement_terminator, // process separator
param, // command parameter (argument)
option, // argument starting with "-", up to a "--"
comment, // comment
search_match, // search match
operat, // operator
escape, // escape sequences
quote, // quoted string
redirection, // redirection
autosuggestion, // autosuggestion
selection,
// Pager support.
// NOTE: pager.cpp relies on these being in this order.
pager_progress,
pager_background,
pager_prefix,
pager_completion,
pager_description,
pager_secondary_background,
pager_secondary_prefix,
pager_secondary_completion,
pager_secondary_description,
pager_selected_background,
pager_selected_prefix,
pager_selected_completion,
pager_selected_description,
}
/// Simply value type describing how a character should be highlighted..
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct HighlightSpec {
pub foreground: HighlightRole,
pub background: HighlightRole,
pub valid_path: bool,
pub force_underline: bool,
}
extern "Rust" {
#[cxx_name = "clone"]
fn clone_ffi(self: &HighlightSpec) -> Box<HighlightSpec>;
fn new_highlight_spec() -> Box<HighlightSpec>;
fn editable_line_colors(editable_line: &EditableLine) -> &[HighlightSpec];
}
extern "C++" {
include!("highlight.h");
include!("history.h");
include!("color.h");
include!("operation_context.h");
include!("editable_line.h");
type HistoryItem = crate::history::HistoryItem;
type OperationContext<'a> = crate::operation_context::OperationContext<'a>;
type rgb_color_t = crate::ffi::rgb_color_t;
#[cxx_name = "EnvDyn"]
type EnvDynFFI = crate::env::EnvDynFFI;
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI = crate::env::EnvStackRefFFI;
type EditableLine = crate::editable_line::EditableLine;
}
extern "Rust" {
#[cxx_name = "autosuggest_validate_from_history"]
fn autosuggest_validate_from_history_ffi(
item: &HistoryItem,
working_directory: &CxxWString,
ctx: &OperationContext<'static>,
) -> bool;
}
extern "Rust" {
type HighlightColorResolver;
fn new_highlight_color_resolver() -> Box<HighlightColorResolver>;
#[cxx_name = "resolve_spec"]
fn resolve_spec_ffi(
&mut self,
highlight: &HighlightSpec,
is_background: bool,
vars: &EnvStackRefFFI,
out: Pin<&mut rgb_color_t>,
);
}
extern "Rust" {
type HighlightSpecListFFI;
fn new_highlight_spec_list() -> Box<HighlightSpecListFFI>;
fn highlight_shell_ffi(
bff: &CxxWString,
ctx: &OperationContext<'_>,
io_ok: bool,
cursor: SharedPtr<usize>,
) -> Box<HighlightSpecListFFI>;
fn size(&self) -> usize;
fn at(&self, index: usize) -> &HighlightSpec;
#[cxx_name = "colorize"]
fn colorize_ffi(
text: &CxxWString,
colors: &HighlightSpecListFFI,
vars: &EnvStackRefFFI,
) -> Vec<u8>;
fn push(&mut self, highlight: &HighlightSpec);
}
// Pager support.
// NOTE: pager.cpp relies on these being in this order.
pager_progress,
pager_background,
pager_prefix,
pager_completion,
pager_description,
pager_secondary_background,
pager_secondary_prefix,
pager_secondary_completion,
pager_secondary_description,
pager_selected_background,
pager_selected_prefix,
pager_selected_completion,
pager_selected_description,
}
fn colorize_ffi(
text: &CxxWString,
colors: &HighlightSpecListFFI,
vars: &EnvStackRefFFI,
) -> Vec<u8> {
colorize(text.as_wstr(), &colors.0, &*vars.0)
}
#[derive(Default)]
pub struct HighlightSpecListFFI(pub Vec<HighlightSpec>);
unsafe impl cxx::ExternType for HighlightSpecListFFI {
type Id = cxx::type_id!("HighlightSpecListFFI");
type Kind = cxx::kind::Opaque;
}
fn new_highlight_spec_list() -> Box<HighlightSpecListFFI> {
Box::default()
}
impl HighlightSpecListFFI {
fn size(&self) -> usize {
self.0.len()
}
fn at(&self, index: usize) -> &HighlightSpec {
&self.0[index]
}
fn push(&mut self, highlight: &HighlightSpec) {
self.0.push(*highlight)
}
}
fn highlight_shell_ffi(
buff: &CxxWString,
ctx: &OperationContext<'_>,
io_ok: bool,
cursor: SharedPtr<usize>,
) -> Box<HighlightSpecListFFI> {
let cursor = cursor.as_ref().cloned();
let mut color = vec![];
highlight_shell(buff.as_wstr(), &mut color, ctx, io_ok, cursor);
Box::new(HighlightSpecListFFI(color))
}
impl HighlightColorResolver {
fn resolve_spec_ffi(
&mut self,
highlight: &HighlightSpec,
is_background: bool,
vars: &EnvStackRefFFI,
mut out: Pin<&mut rgb_color_t>,
) {
let color = self.resolve_spec(highlight, is_background, &*vars.0);
match color.typ {
color::Type::None => (),
color::Type::Named { idx } => {
out.as_mut().set_is_named();
out.as_mut().set_name_idx(idx);
}
color::Type::Rgb(color) => {
out.as_mut().set_is_rgb();
out.as_mut().set_color(color.r, color.g, color.b);
}
color::Type::Normal => out.as_mut().set_is_normal(),
color::Type::Reset => out.as_mut().set_is_reset(),
}
if color.flags.bold {
out.as_mut().set_bold(true);
}
if color.flags.underline {
out.as_mut().set_underline(true);
}
if color.flags.italics {
out.as_mut().set_italics(true);
}
if color.flags.dim {
out.as_mut().set_dim(true);
}
if color.flags.reverse {
out.as_mut().set_reverse(true);
}
}
}
fn autosuggest_validate_from_history_ffi(
item: &HistoryItem,
working_directory: &CxxWString,
ctx: &OperationContext<'static>,
) -> bool {
autosuggest_validate_from_history(item, working_directory.as_wstr(), ctx)
}
fn new_highlight_color_resolver() -> Box<HighlightColorResolver> {
Box::new(HighlightColorResolver::new())
}
impl HighlightSpec {
fn clone_ffi(&self) -> Box<HighlightSpec> {
Box::new(*self)
}
}
fn new_highlight_spec() -> Box<HighlightSpec> {
Box::default()
}
unsafe impl cxx::ExternType for EditableLine {
type Id = cxx::type_id!("EditableLine");
type Kind = cxx::kind::Opaque;
}
fn editable_line_colors(editable_line: &EditableLine) -> &[HighlightSpec] {
editable_line.colors()
/// Simply value type describing how a character should be highlighted..
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct HighlightSpec {
pub foreground: HighlightRole,
pub background: HighlightRole,
pub valid_path: bool,
pub force_underline: bool,
}

View File

@@ -21,7 +21,7 @@
borrow::Cow,
collections::{BTreeMap, HashMap, HashSet, VecDeque},
ffi::CString,
io::{BufRead, BufReader, Read, Write},
io::{BufRead, Read, Write},
mem,
num::NonZeroUsize,
ops::ControlFlow,
@@ -31,7 +31,6 @@
};
use bitflags::bitflags;
use cxx::{CxxWString, UniquePtr};
use libc::{
fchmod, fchown, flock, fstat, ftruncate, lseek, LOCK_EX, LOCK_SH, LOCK_UN, O_APPEND, O_CREAT,
O_RDONLY, O_WRONLY, SEEK_SET,
@@ -41,32 +40,29 @@
use widestring_suffix::widestrs;
use crate::{
ast::{ast_ffi::StatementDecoration, Ast, Node},
ast::{Ast, Node},
common::{
str2wcstring, unescape_string, valid_var_name, wcs2zstring, write_loop, CancelChecker,
UnescapeStringStyle,
},
env::{EnvMode, EnvStack, EnvStackRefFFI, Environment},
env::{EnvMode, EnvStack, Environment},
expand::{expand_one, ExpandFlags},
fallback::fish_mkstemp_cloexec,
fds::{wopen_cloexec, AutoCloseFd},
ffi::wcstring_list_ffi_t,
flog::{FLOG, FLOGF},
global_safety::RelaxedAtomicBool,
history::file::{append_history_item_to_buffer, HistoryFileContents},
io::IoStreams,
operation_context::{OperationContext, EXPANSION_LIMIT_BACKGROUND},
parse_constants::ParseTreeFlags,
parse_constants::{ParseTreeFlags, StatementDecoration},
parse_util::{parse_util_detect_errors, parse_util_unescape_wildcards},
path::{
path_get_config, path_get_data, path_get_data_remoteness, path_is_valid, DirRemoteness,
},
signal::signal_check_cancel,
threads::{assert_is_background_thread, iothread_perform},
util::find_subslice,
wchar::prelude::*,
wchar_ext::WExt,
wchar_ffi::{WCharFromFFI, WCharToFFI},
wcstringutil::subsequence_in_string,
wildcard::{wildcard_match, ANY_STRING},
wutil::{
@@ -77,152 +73,42 @@
mod file;
#[cxx::bridge]
mod history_ffi {
extern "C++" {
include!("wutil.h");
include!("io.h");
include!("env.h");
include!("operation_context.h");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SearchType {
/// Search for commands exactly matching the given string.
Exact,
/// Search for commands containing the given string.
Contains,
/// Search for commands starting with the given string.
Prefix,
/// Search for commands containing the given glob pattern.
ContainsGlob,
/// Search for commands starting with the given glob pattern.
PrefixGlob,
/// Search for commands containing the given string as a subsequence
ContainsSubsequence,
/// Matches everything.
MatchEverything,
}
type IoStreams<'a> = crate::io::IoStreams<'a>;
#[cxx_name = "EnvDyn"]
type EnvDynFFI = crate::env::EnvDynFFI;
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI = crate::env::EnvStackRefFFI;
type OperationContext<'a> = crate::operation_context::OperationContext<'a>;
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
}
/// Ways that a history item may be written to disk (or omitted).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PersistenceMode {
/// The history item is written to disk normally
Disk,
/// The history item is stored in-memory only, not written to disk
Memory,
/// The history item is stored in-memory and deleted when a new item is added
Ephemeral,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SearchType {
/// Search for commands exactly matching the given string.
Exact,
/// Search for commands containing the given string.
Contains,
/// Search for commands starting with the given string.
Prefix,
/// Search for commands containing the given glob pattern.
ContainsGlob,
/// Search for commands starting with the given glob pattern.
PrefixGlob,
/// Search for commands containing the given string as a subsequence
ContainsSubsequence,
/// Matches everything.
MatchEverything,
}
/// Ways that a history item may be written to disk (or omitted).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PersistenceMode {
/// The history item is written to disk normally
Disk,
/// The history item is stored in-memory only, not written to disk
Memory,
/// The history item is stored in-memory and deleted when a new item is added
Ephemeral,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SearchDirection {
Forward,
Backward,
}
extern "Rust" {
#[cxx_name = "history_save_all"]
fn save_all();
#[cxx_name = "history_session_id"]
fn rust_session_id(vars: &EnvStackRefFFI) -> UniquePtr<CxxWString>;
fn rust_expand_and_detect_paths(
paths: &wcstring_list_ffi_t,
vars: &EnvStackRefFFI,
) -> UniquePtr<wcstring_list_ffi_t>;
fn rust_all_paths_are_valid(
paths: &wcstring_list_ffi_t,
ctx: &OperationContext<'_>,
) -> bool;
#[rust_name = "start_private_mode_ffi"]
fn start_private_mode(vars: &EnvStackRefFFI);
#[rust_name = "in_private_mode_ffi"]
fn in_private_mode(vars: &EnvStackRefFFI) -> bool;
#[cxx_name = "all_paths_are_valid"]
fn all_paths_are_valid_ffi(paths: &wcstring_list_ffi_t, ctx: &OperationContext<'_>)
-> bool;
}
extern "Rust" {
type ItemIndexes;
fn get(&self, index: usize) -> UniquePtr<CxxWString>;
}
extern "Rust" {
type HistoryItem;
fn rust_history_item_new(
s: &CxxWString,
when: i64,
ident: u64,
persist_mode: PersistenceMode,
) -> Box<HistoryItem>;
#[rust_name = "str_ffi"]
fn str(&self) -> UniquePtr<CxxWString>;
fn is_empty(&self) -> bool;
#[rust_name = "matches_search_ffi"]
fn matches_search(&self, term: &CxxWString, typ: SearchType, case_sensitive: bool) -> bool;
#[rust_name = "timestamp_ffi"]
fn timestamp(&self) -> i64;
fn should_write_to_disk(&self) -> bool;
#[rust_name = "get_required_paths_ffi"]
fn get_required_paths(&self) -> UniquePtr<wcstring_list_ffi_t>;
#[rust_name = "set_required_paths_ffi"]
fn set_required_paths(&mut self, paths: &wcstring_list_ffi_t);
}
extern "Rust" {
type HistorySharedPtr;
fn history_with_name(name: &CxxWString) -> Box<HistorySharedPtr>;
fn is_default(&self) -> bool;
fn is_empty(&self) -> bool;
fn remove(&self, s: &CxxWString);
fn remove_ephemeral_items(&self);
fn resolve_pending(&self);
fn save(&self);
fn clear(&self);
fn clear_session(&self);
fn populate_from_config_path(&self);
fn populate_from_bash(&self, filename: &CxxWString);
fn incorporate_external_changes(&self);
fn get_history(&self) -> UniquePtr<wcstring_list_ffi_t>;
fn items_at_indexes(&self, indexes: &[isize]) -> Box<ItemIndexes>;
fn item_at_index(&self, idx: usize) -> Box<HistoryItem>;
fn size(&self) -> usize;
fn clone(&self) -> Box<HistorySharedPtr>;
}
extern "Rust" {
type HistorySearch;
fn rust_history_search_new(
hist: &HistorySharedPtr,
s: &CxxWString,
search_type: SearchType,
flags: u32,
starting_index: usize,
) -> Box<HistorySearch>;
#[rust_name = "original_term_ffi"]
fn original_term(&self) -> UniquePtr<CxxWString>;
fn go_to_next_match(&mut self, direction: SearchDirection) -> bool;
fn current_item(&self) -> &HistoryItem;
#[rust_name = "current_string_ffi"]
fn current_string(&self) -> UniquePtr<CxxWString>;
fn current_index(&self) -> usize;
fn ignores_case(&self) -> bool;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SearchDirection {
Forward,
Backward,
}
use self::file::time_to_seconds;
pub use self::history_ffi::{PersistenceMode, SearchDirection, SearchType};
// Our history format is intended to be valid YAML. Here it is:
//
@@ -375,10 +261,6 @@ pub fn str(&self) -> &wstr {
&self.contents
}
fn str_ffi(&self) -> UniquePtr<CxxWString> {
self.str().to_ffi()
}
/// Returns whether the text is empty.
pub fn is_empty(&self) -> bool {
self.contents.is_empty()
@@ -419,32 +301,14 @@ pub fn matches_search(&self, term: &wstr, typ: SearchType, case_sensitive: bool)
}
SearchType::ContainsSubsequence => subsequence_in_string(term, &content_to_match),
SearchType::MatchEverything => true,
_ => unreachable!("invalid SearchType"),
}
}
fn matches_search_ffi(&self, term: &CxxWString, typ: SearchType, case_sensitive: bool) -> bool {
self.matches_search(&term.from_ffi(), typ, case_sensitive)
}
/// Returns the timestamp for creating this history item.
pub fn timestamp(&self) -> SystemTime {
self.creation_timestamp
}
fn timestamp_ffi(&self) -> i64 {
match self.timestamp().duration_since(UNIX_EPOCH) {
Ok(d) => {
// after epoch
i64::try_from(d.as_secs()).unwrap()
}
Err(e) => {
// before epoch
-i64::try_from(e.duration().as_secs()).unwrap()
}
}
}
/// Returns whether this item should be persisted (written to disk).
pub fn should_write_to_disk(&self) -> bool {
self.persist_mode == PersistenceMode::Disk
@@ -456,20 +320,12 @@ pub fn get_required_paths(&self) -> &[WString] {
&self.required_paths
}
fn get_required_paths_ffi(&self) -> UniquePtr<wcstring_list_ffi_t> {
self.get_required_paths().to_ffi()
}
/// Set the list of arguments which referred to files.
/// This is used for autosuggestion hinting.
pub fn set_required_paths(&mut self, paths: Vec<WString>) {
self.required_paths = paths;
}
fn set_required_paths_ffi(&mut self, paths: &wcstring_list_ffi_t) {
self.set_required_paths(paths.from_ffi())
}
/// We can merge two items if they are the same command. We use the more recent timestamp, more
/// recent identifier, and the longer list of required paths.
fn merge(&mut self, item: &HistoryItem) -> bool {
@@ -1986,16 +1842,11 @@ pub fn original_term(&self) -> &wstr {
&self.orig_term
}
fn original_term_ffi(&self) -> UniquePtr<CxxWString> {
self.original_term().to_ffi()
}
/// Finds the next search result. Returns `true` if one was found.
pub fn go_to_next_match(&mut self, direction: SearchDirection) -> bool {
let invalid_index = match direction {
SearchDirection::Backward => usize::MAX,
SearchDirection::Forward => 0,
_ => unreachable!(),
};
if self.current_index == invalid_index {
@@ -2008,7 +1859,6 @@ pub fn go_to_next_match(&mut self, direction: SearchDirection) -> bool {
match direction {
SearchDirection::Backward => index += 1,
SearchDirection::Forward => index -= 1,
_ => unreachable!(),
};
if self.current_index == invalid_index {
@@ -2055,10 +1905,6 @@ pub fn current_string(&self) -> &wstr {
self.current_item().str()
}
fn current_string_ffi(&self) -> UniquePtr<CxxWString> {
self.current_string().to_ffi()
}
/// Returns the index of the current history item.
pub fn current_index(&self) -> usize {
self.current_index
@@ -2172,10 +2018,6 @@ pub fn all_paths_are_valid<P: IntoIterator<Item = WString>>(
true
}
fn all_paths_are_valid_ffi(paths: &wcstring_list_ffi_t, ctx: &OperationContext<'_>) -> bool {
all_paths_are_valid(paths.from_ffi(), ctx)
}
/// Sets private mode on. Once in private mode, it cannot be turned off.
pub fn start_private_mode(vars: &EnvStack) {
vars.set_one(L!("fish_history"), EnvMode::GLOBAL, L!("").to_owned());
@@ -2193,185 +2035,3 @@ pub fn in_private_mode(vars: &dyn Environment) -> bool {
/// Whether we're in maximum chaos mode, useful for testing.
/// This causes things like locks to fail.
pub static CHAOS_MODE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
// ========
// FFI crud
// ========
struct ItemIndexes(HashMap<usize, WString>);
impl ItemIndexes {
fn get(&self, index: usize) -> UniquePtr<CxxWString> {
self.0
.get(&index)
.map(|s| s.to_ffi())
.unwrap_or_else(UniquePtr::null)
}
}
fn rust_history_item_new(
s: &CxxWString,
when: i64,
ident: u64,
persist_mode: PersistenceMode,
) -> Box<HistoryItem> {
let s = s.from_ffi();
let when = if when < 0 {
UNIX_EPOCH - Duration::from_secs(u64::try_from(-when).unwrap())
} else {
UNIX_EPOCH + Duration::from_secs(u64::try_from(when).unwrap())
};
Box::new(HistoryItem::new(s, when, ident, persist_mode))
}
pub struct HistorySharedPtr(pub Arc<History>);
impl HistorySharedPtr {
fn is_default(&self) -> bool {
self.0.is_default()
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn remove(&self, s: &CxxWString) {
self.0.remove(s.from_ffi())
}
fn remove_ephemeral_items(&self) {
self.0.remove_ephemeral_items()
}
fn resolve_pending(&self) {
self.0.resolve_pending()
}
fn save(&self) {
self.0.save()
}
#[allow(clippy::too_many_arguments)]
fn search(
&self,
search_type: SearchType,
search_args: &wcstring_list_ffi_t,
show_time_format: &UniquePtr<CxxWString>,
max_items: usize,
case_sensitive: bool,
null_terminate: bool,
reverse: bool,
cancel_on_signal: bool,
streams: &mut IoStreams,
) -> bool {
let show_time_format = if show_time_format.is_null() {
None
} else {
Some(show_time_format.from_ffi().to_string())
};
let search_args = search_args.from_ffi();
let search_args: Vec<&wstr> = search_args.iter().map(|s| s.as_ref()).collect();
let cancel_checker = move || {
if cancel_on_signal {
signal_check_cancel() != 0
} else {
false
}
};
Arc::clone(&self.0).search(
search_type,
&search_args,
show_time_format.as_deref(),
max_items,
case_sensitive,
null_terminate,
reverse,
&{ Box::new(cancel_checker) as _ },
streams,
)
}
fn clear(&self) {
self.0.clear()
}
fn clear_session(&self) {
self.0.clear_session()
}
fn populate_from_config_path(&self) {
self.0.populate_from_config_path()
}
fn populate_from_bash(&self, filename: &CxxWString) {
let file = AutoCloseFd::new(wopen_cloexec(&filename.from_ffi(), O_RDONLY, 0));
if !file.is_valid() {
return;
}
self.0.populate_from_bash(BufReader::new(file))
}
fn incorporate_external_changes(&self) {
self.0.incorporate_external_changes()
}
fn get_history(&self) -> UniquePtr<wcstring_list_ffi_t> {
self.0.get_history().to_ffi()
}
fn items_at_indexes(&self, indexes: &[isize]) -> Box<ItemIndexes> {
Box::new(ItemIndexes(self.0.items_at_indexes(
indexes.iter().filter_map(|&n| n.try_into().ok()),
)))
}
fn item_at_index(&self, idx: usize) -> Box<HistoryItem> {
Box::new(self.0.item_at_index(idx).unwrap_or_else(|| HistoryItem {
contents: WString::new(),
creation_timestamp: UNIX_EPOCH,
required_paths: vec![],
identifier: 0,
persist_mode: PersistenceMode::Disk,
}))
}
fn size(&self) -> usize {
self.0.size()
}
fn clone(&self) -> Box<Self> {
Box::new(Self(Arc::clone(&self.0)))
}
}
fn history_with_name(name: &CxxWString) -> Box<HistorySharedPtr> {
Box::new(HistorySharedPtr(History::with_name(&name.from_ffi())))
}
fn rust_history_search_new(
hist: &HistorySharedPtr,
s: &CxxWString,
search_type: SearchType,
flags: u32,
starting_index: usize,
) -> Box<HistorySearch> {
Box::new(HistorySearch::new_with(
Arc::clone(&hist.0),
s.from_ffi(),
search_type,
SearchFlags::from_bits(flags).unwrap(),
starting_index,
))
}
fn rust_session_id(vars: &EnvStackRefFFI) -> UniquePtr<CxxWString> {
history_session_id(&*vars.0).to_ffi()
}
fn rust_expand_and_detect_paths(
paths: &wcstring_list_ffi_t,
vars: &EnvStackRefFFI,
) -> UniquePtr<wcstring_list_ffi_t> {
expand_and_detect_paths(paths.from_ffi(), &*vars.0).to_ffi()
}
fn rust_all_paths_are_valid(paths: &wcstring_list_ffi_t, ctx: &OperationContext<'_>) -> bool {
all_paths_are_valid(paths.from_ffi(), ctx)
}
fn start_private_mode_ffi(vars: &EnvStackRefFFI) {
start_private_mode(&vars.0)
}
fn in_private_mode_ffi(vars: &EnvStackRefFFI) -> bool {
in_private_mode(&*vars.0)
}
unsafe impl cxx::ExternType for HistoryItem {
type Id = cxx::type_id!("HistoryItem");
type Kind = cxx::kind::Opaque;
}

View File

@@ -13,14 +13,13 @@
PROT_WRITE, SEEK_END, SEEK_SET,
};
use super::{HistoryItem, PersistenceMode};
use crate::{
common::{str2wcstring, subslice_position, wcs2string},
flog::FLOG,
path::{path_get_config_remoteness, DirRemoteness},
};
use super::{history_ffi::PersistenceMode, HistoryItem};
/// History file types.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HistoryFileType {

View File

@@ -14,10 +14,106 @@
use std::sync::atomic::{AtomicUsize, Ordering};
// The range of key codes for inputrc-style keyboard functions.
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump.repr as usize) + 1;
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1;
// TODO: move CharInputStyle and ReadlineCmd here once they no longer must be exposed to C++.
pub use crate::input_ffi::{CharInputStyle, ReadlineCmd};
/// Hackish: the input style, which describes how char events (only) are applied to the command
/// line. Note this is set only after applying bindings; it is not set from readb().
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CharInputStyle {
// Insert characters normally.
Normal,
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
NotFirst,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum ReadlineCmd {
BeginningOfLine,
EndOfLine,
ForwardChar,
BackwardChar,
ForwardSingleChar,
ForwardWord,
BackwardWord,
ForwardBigword,
BackwardBigword,
NextdOrForwardWord,
PrevdOrBackwardWord,
HistorySearchBackward,
HistorySearchForward,
HistoryPrefixSearchBackward,
HistoryPrefixSearchForward,
HistoryPager,
HistoryPagerDelete,
DeleteChar,
BackwardDeleteChar,
KillLine,
Yank,
YankPop,
Complete,
CompleteAndSearch,
PagerToggleSearch,
BeginningOfHistory,
EndOfHistory,
BackwardKillLine,
KillWholeLine,
KillInnerLine,
KillWord,
KillBigword,
BackwardKillWord,
BackwardKillPathComponent,
BackwardKillBigword,
HistoryTokenSearchBackward,
HistoryTokenSearchForward,
SelfInsert,
SelfInsertNotFirst,
TransposeChars,
TransposeWords,
UpcaseWord,
DowncaseWord,
CapitalizeWord,
TogglecaseChar,
TogglecaseSelection,
Execute,
BeginningOfBuffer,
EndOfBuffer,
RepaintMode,
Repaint,
ForceRepaint,
UpLine,
DownLine,
SuppressAutosuggestion,
AcceptAutosuggestion,
BeginSelection,
SwapSelectionStartStop,
EndSelection,
KillSelection,
InsertLineUnder,
InsertLineOver,
ForwardJump,
BackwardJump,
ForwardJumpTill,
BackwardJumpTill,
FuncAnd,
FuncOr,
ExpandAbbr,
DeleteOrExit,
Exit,
CancelCommandline,
Cancel,
Undo,
Redo,
BeginUndoGroup,
EndUndoGroup,
RepeatJump,
DisableMouseTracking,
// ncurses uses the obvious name
ClearScreenAndRepaint,
// NOTE: This one has to be last.
ReverseRepeatJump,
}
/// Represents an event on the character input stream.
#[derive(Debug, Copy, Clone)]

View File

@@ -1,267 +0,0 @@
use crate::ffi::wcstring_list_ffi_t;
use crate::input::*;
use crate::input_common::*;
use crate::parser::ParserRefFFI;
use crate::threads::CppCallback;
use crate::wchar::prelude::*;
use crate::wchar_ffi::AsWstr;
use crate::wchar_ffi::WCharToFFI;
use cxx::CxxWString;
pub use ffi::{CharInputStyle, ReadlineCmd};
use std::pin::Pin;
// Returns the code, or -1 on failure.
fn input_function_get_code_ffi(name: &CxxWString) -> i32 {
if let Some(code) = input_function_get_code(name.as_wstr()) {
code.repr as i32
} else {
-1
}
}
fn char_event_from_readline_ffi(cmd: ReadlineCmd) -> Box<CharEvent> {
Box::new(CharEvent::from_readline(cmd))
}
fn char_event_from_char_ffi(c: u8) -> Box<CharEvent> {
Box::new(CharEvent::from_char(c.into()))
}
fn make_inputter_ffi(parser: &ParserRefFFI, in_fd: i32) -> Box<Inputter> {
Box::new(Inputter::new(parser.0.clone(), in_fd))
}
fn make_input_event_queue_ffi(in_fd: i32) -> Box<InputEventQueue> {
Box::new(InputEventQueue::new(in_fd))
}
fn input_terminfo_get_name_ffi(seq: &CxxWString, out: Pin<&mut CxxWString>) -> bool {
let Some(name) = input_terminfo_get_name(seq.as_wstr()) else {
return false;
};
out.push_chars(name.as_char_slice());
true
}
impl Inputter {
#[allow(clippy::boxed_local)]
fn queue_char_ffi(&mut self, ch: Box<CharEvent>) {
self.queue_char(*ch);
}
fn read_char_ffi(&mut self, command_handler: &cxx::SharedPtr<CppCallback>) -> Box<CharEvent> {
let mut rust_handler = |cmds: &[WString]| {
let ffi_cmds = cmds.to_ffi();
command_handler.invoke_with_param(ffi_cmds.as_ref().unwrap() as *const _ as *const u8);
};
let mhandler = if !command_handler.is_null() {
Some(&mut rust_handler as &mut CommandHandler)
} else {
None
};
Box::new(self.read_char(mhandler))
}
fn function_pop_arg_ffi(&mut self) -> u32 {
self.function_pop_arg().into()
}
}
impl CharEvent {
fn get_char_ffi(&self) -> u32 {
self.get_char().into()
}
fn get_input_style_ffi(&self) -> CharInputStyle {
self.input_style
}
}
impl InputEventQueue {
// Returns Box<CharEvent>::into_raw(), or nullptr if None.
fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent {
match self.readch_timed_esc() {
Some(ch) => Box::into_raw(Box::new(ch)),
None => std::ptr::null_mut(),
}
}
}
#[cxx::bridge]
mod ffi {
/// Hackish: the input style, which describes how char events (only) are applied to the command
/// line. Note this is set only after applying bindings; it is not set from readb().
#[cxx_name = "char_input_style_t"]
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum CharInputStyle {
// Insert characters normally.
Normal,
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
NotFirst,
}
#[cxx_name = "readline_cmd_t"]
#[repr(u8)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ReadlineCmd {
BeginningOfLine,
EndOfLine,
ForwardChar,
BackwardChar,
ForwardSingleChar,
ForwardWord,
BackwardWord,
ForwardBigword,
BackwardBigword,
NextdOrForwardWord,
PrevdOrBackwardWord,
HistorySearchBackward,
HistorySearchForward,
HistoryPrefixSearchBackward,
HistoryPrefixSearchForward,
HistoryPager,
HistoryPagerDelete,
DeleteChar,
BackwardDeleteChar,
KillLine,
Yank,
YankPop,
Complete,
CompleteAndSearch,
PagerToggleSearch,
BeginningOfHistory,
EndOfHistory,
BackwardKillLine,
KillWholeLine,
KillInnerLine,
KillWord,
KillBigword,
BackwardKillWord,
BackwardKillPathComponent,
BackwardKillBigword,
HistoryTokenSearchBackward,
HistoryTokenSearchForward,
SelfInsert,
SelfInsertNotFirst,
TransposeChars,
TransposeWords,
UpcaseWord,
DowncaseWord,
CapitalizeWord,
TogglecaseChar,
TogglecaseSelection,
Execute,
BeginningOfBuffer,
EndOfBuffer,
RepaintMode,
Repaint,
ForceRepaint,
UpLine,
DownLine,
SuppressAutosuggestion,
AcceptAutosuggestion,
BeginSelection,
SwapSelectionStartStop,
EndSelection,
KillSelection,
InsertLineUnder,
InsertLineOver,
ForwardJump,
BackwardJump,
ForwardJumpTill,
BackwardJumpTill,
FuncAnd,
FuncOr,
ExpandAbbr,
DeleteOrExit,
Exit,
CancelCommandline,
Cancel,
Undo,
Redo,
BeginUndoGroup,
EndUndoGroup,
RepeatJump,
DisableMouseTracking,
// ncurses uses the obvious name
ClearScreenAndRepaint,
// NOTE: This one has to be last.
ReverseRepeatJump,
}
extern "C++" {
include!("parser.h");
include!("reader.h");
include!("callback.h");
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
type ParserRef = crate::parser::ParserRefFFI;
#[rust_name = "CppCallback"]
type callback_t = crate::threads::CppCallback;
}
extern "Rust" {
fn init_input();
#[cxx_name = "input_function_get_code"]
fn input_function_get_code_ffi(name: &CxxWString) -> i32;
#[cxx_name = "input_terminfo_get_name"]
fn input_terminfo_get_name_ffi(seq: &CxxWString, out: Pin<&mut CxxWString>) -> bool;
}
extern "Rust" {
type CharEvent;
#[cxx_name = "char_event_from_readline"]
fn char_event_from_readline_ffi(cmd: ReadlineCmd) -> Box<CharEvent>;
#[cxx_name = "char_event_from_char"]
fn char_event_from_char_ffi(c: u8) -> Box<CharEvent>;
fn is_char(&self) -> bool;
fn is_readline(&self) -> bool;
fn is_check_exit(&self) -> bool;
fn is_eof(&self) -> bool;
#[cxx_name = "get_char"]
fn get_char_ffi(&self) -> u32;
#[cxx_name = "get_input_style"]
fn get_input_style_ffi(&self) -> CharInputStyle;
fn get_readline(&self) -> ReadlineCmd;
}
extern "Rust" {
type Inputter;
#[cxx_name = "make_inputter"]
fn make_inputter_ffi(parser: &ParserRef, in_fd: i32) -> Box<Inputter>;
#[cxx_name = "queue_char"]
fn queue_char_ffi(&mut self, ch: Box<CharEvent>);
fn queue_readline(&mut self, cmd: ReadlineCmd);
#[cxx_name = "read_char"]
fn read_char_ffi(&mut self, command_handler: &SharedPtr<CppCallback>) -> Box<CharEvent>;
fn function_set_status(&mut self, status: bool);
#[cxx_name = "function_pop_arg"]
fn function_pop_arg_ffi(&mut self) -> u32;
}
extern "Rust" {
type InputEventQueue;
#[cxx_name = "make_input_event_queue"]
fn make_input_event_queue_ffi(in_fd: i32) -> Box<InputEventQueue>;
#[cxx_name = "readch_timed_esc"]
fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent;
}
}

View File

@@ -15,9 +15,7 @@
use crate::signal::SigChecker;
use crate::topic_monitor::topic_t;
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharFromFFI;
use crate::wutil::{perror, perror_io, wdirname, wstat, wwrite_to_fd};
use cxx::CxxWString;
use errno::Errno;
use libc::{EAGAIN, EEXIST, EINTR, ENOENT, ENOTDIR, EPIPE, EWOULDBLOCK, O_EXCL, STDOUT_FILENO};
use std::cell::{RefCell, UnsafeCell};
@@ -618,11 +616,6 @@ fn begin_filling(iobuffer: &Arc<IoBuffer>, fd: AutoCloseFd) {
#[derive(Clone, Default)]
pub struct IoChain(pub Vec<IoDataRef>);
unsafe impl cxx::ExternType for IoChain {
type Id = cxx::type_id!("IoChain");
type Kind = cxx::kind::Opaque;
}
impl IoChain {
pub fn new() -> Self {
Default::default()
@@ -981,11 +974,6 @@ pub struct IoStreams<'a> {
pub job_group: Option<JobGroupRef>,
}
unsafe impl cxx::ExternType for IoStreams<'_> {
type Id = cxx::type_id!("IoStreams");
type Kind = cxx::kind::Opaque;
}
impl<'a> IoStreams<'a> {
pub fn new(out: &'a mut OutputStream, err: &'a mut OutputStream) -> Self {
IoStreams {
@@ -1025,69 +1013,3 @@ fn fd_monitor() -> &'static mut FdMonitor {
let ptr: *mut FdMonitor = unsafe { (*FDM).get() };
unsafe { &mut *ptr }
}
#[cxx::bridge]
#[allow(clippy::needless_lifetimes)]
mod io_ffi {
extern "Rust" {
type IoChain;
type IoStreams<'a>;
type OutputStreamFfi<'a>;
fn new_io_chain() -> Box<IoChain>;
#[cxx_name = "out"]
unsafe fn out_ffi<'a>(self: &'a mut IoStreams<'a>) -> Box<OutputStreamFfi<'a>>;
#[cxx_name = "err"]
unsafe fn err_ffi<'a>(self: &'a mut IoStreams<'a>) -> Box<OutputStreamFfi<'a>>;
#[cxx_name = "out_is_redirected"]
unsafe fn out_is_redirected_ffi<'a>(self: &IoStreams<'a>) -> bool;
#[cxx_name = "stdin_is_directly_redirected"]
unsafe fn stdin_is_directly_redirected_ffi<'a>(self: &IoStreams<'a>) -> bool;
#[cxx_name = "stdin_fd"]
unsafe fn stdin_fd_ffi<'a>(self: &IoStreams<'a>) -> i32;
#[cxx_name = "append"]
unsafe fn append_ffi<'a>(self: &mut OutputStreamFfi<'a>, s: &CxxWString) -> bool;
#[cxx_name = "push"]
unsafe fn push_ffi<'a>(self: &mut OutputStreamFfi<'a>, s: u32) -> bool;
}
}
impl<'a> IoStreams<'a> {
fn out_ffi(&'a mut self) -> Box<OutputStreamFfi<'a>> {
Box::new(OutputStreamFfi(self.out))
}
fn err_ffi(&'a mut self) -> Box<OutputStreamFfi<'a>> {
Box::new(OutputStreamFfi(self.err))
}
unsafe fn out_is_redirected_ffi(&self) -> bool {
self.out_is_redirected
}
unsafe fn stdin_is_directly_redirected_ffi(&self) -> bool {
self.stdin_is_directly_redirected
}
unsafe fn stdin_fd_ffi(&self) -> i32 {
self.stdin_fd
}
}
pub struct OutputStreamFfi<'a>(pub &'a mut OutputStream);
unsafe impl cxx::ExternType for OutputStreamFfi<'_> {
type Id = cxx::type_id!("OutputStreamFfi");
type Kind = cxx::kind::Opaque;
}
impl<'a> OutputStreamFfi<'a> {
fn append_ffi(&mut self, s: &CxxWString) -> bool {
self.0.append(s.from_ffi())
}
fn push_ffi(&mut self, s: u32) -> bool {
self.0.append_char(char::from_u32(s).unwrap())
}
}
fn new_io_chain() -> Box<IoChain> {
Box::new(IoChain::new())
}

View File

@@ -1,51 +1,12 @@
use self::ffi::pgid_t;
use crate::global_safety::RelaxedAtomicBool;
use crate::proc::JobGroupRef;
use crate::signal::Signal;
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharToFFI;
use cxx::{CxxWString, UniquePtr};
use std::cell::RefCell;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
#[cxx::bridge]
mod ffi {
// Not only does cxx bridge not recognize libc::pid_t, it doesn't even recognize i32 as a POD
// type! :sadface:
struct pgid_t {
value: i32,
}
extern "Rust" {
#[cxx_name = "job_group_t"]
type JobGroup;
fn wants_job_control(&self) -> bool;
fn wants_terminal(&self) -> bool;
fn is_foreground(&self) -> bool;
fn set_is_foreground(&self, value: bool);
#[cxx_name = "get_command"]
fn get_command_ffi(&self) -> UniquePtr<CxxWString>;
#[cxx_name = "get_job_id"]
fn get_job_id_ffi(&self) -> i32;
#[cxx_name = "get_cancel_signal"]
fn get_cancel_signal_ffi(&self) -> i32;
#[cxx_name = "cancel_with_signal"]
fn cancel_with_signal_ffi(&self, signal: i32);
fn set_pgid(&mut self, pgid: i32);
#[cxx_name = "get_pgid"]
fn get_pgid_ffi(&self) -> UniquePtr<pgid_t>;
fn has_job_id(&self) -> bool;
// cxx bridge doesn't recognize `libc::*` as being POD types, so it won't let us use them in
// a SharedPtr/UniquePtr/Box and won't let us pass/return them by value/reference, either.
unsafe fn get_modes_ffi(&self, size: usize) -> *const u8; /* actually `* const libc::termios` */
unsafe fn set_modes_ffi(&mut self, modes: *const u8, size: usize); /* actually `* const libc::termios` */
}
}
/// A job id, corresponding to what is printed by `jobs`. 1 is the first valid job id.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
@@ -150,16 +111,6 @@ pub fn set_is_foreground(&self, in_foreground: bool) {
self.is_foreground.store(in_foreground);
}
/// Return the command which produced this job tree.
pub fn get_command_ffi(&self) -> UniquePtr<CxxWString> {
self.command.to_ffi()
}
/// Return the job id or -1 if none.
pub fn get_job_id_ffi(&self) -> i32 {
self.job_id.map(|j| u32::from(j.0) as i32).unwrap_or(-1)
}
/// Returns whether we have valid job id. "Simple block" groups like function calls do not.
pub fn has_job_id(&self) -> bool {
self.job_id.is_some()
@@ -173,12 +124,6 @@ pub fn get_cancel_signal(&self) -> Option<Signal> {
}
}
/// Gets the cancellation signal or `0` if none.
pub fn get_cancel_signal_ffi(&self) -> i32 {
// Legacy C++ code expects a zero in case of no signal.
self.get_cancel_signal().map(|s| s.code()).unwrap_or(0)
}
/// Mark that a process in this group got a signal and should cancel.
pub fn cancel_with_signal(&self, signal: Signal) {
// We only assign the signal if one hasn't yet been assigned. This means the first signal to
@@ -188,11 +133,6 @@ pub fn cancel_with_signal(&self, signal: Signal) {
.ok();
}
/// Mark that a process in this group got a signal and should cancel
pub fn cancel_with_signal_ffi(&self, signal: i32) {
self.cancel_with_signal(Signal::new(signal))
}
/// Set the pgid for this job group, latching it to this value. This should only be called if
/// job control is active for this group. The pgid should not already have been set, and should
/// be different from fish's pgid. Of course this does not keep the pgid alive by itself.
@@ -218,53 +158,6 @@ pub fn set_pgid(&self, pgid: libc::pid_t) {
pub fn get_pgid(&self) -> Option<libc::pid_t> {
*self.pgid.borrow()
}
/// Returns the value of [`JobGroup::pgid`] in a `UniquePtr<T>` to take the place of an
/// `Option<T>` for ffi purposes. A null `UniquePtr` is equivalent to `None`.
pub fn get_pgid_ffi(&self) -> cxx::UniquePtr<pgid_t> {
match *self.pgid.borrow() {
Some(value) => UniquePtr::new(pgid_t { value }),
None => UniquePtr::null(),
}
}
/// Returns the current terminal modes associated with the `JobGroup` for ffi purposes.
unsafe fn get_modes_ffi(&self, size: usize) -> *const u8 {
assert_eq!(
size,
core::mem::size_of::<libc::termios>(),
"Mismatch between expected and actual ffi size of struct termios!"
);
self.tmodes
.borrow()
.as_ref()
// Really cool that type inference works twice in a row here. The first `_` is deduced
// from the left and the second `_` is deduced from the right (the return type).
.map(|val| val as *const _ as *const _)
.unwrap_or(core::ptr::null())
}
/// Sets the current terminal modes associated with the `JobGroup`. Only use for ffi.
///
/// Unlike `set_pgid()`, this isn't documented in the C++ codebase as being only called at
/// initialization but as the underlying [`self.tmodes`] wasn't wrapped in any sort of
/// thread-safe marshalling struct, we'll assume it can only be called from one thread and use
/// `&mut self` for safety.
unsafe fn set_modes_ffi(&mut self, modes: *const u8, size: usize) {
assert_eq!(
size,
core::mem::size_of::<libc::termios>(),
"Mismatch between expected and actual ffi size of struct termios!"
);
let modes = modes as *const libc::termios;
if modes.is_null() {
self.tmodes.replace(None);
} else {
self.tmodes.replace(Some(*modes));
}
}
}
/// Basic thread-safe sorted vector of job ids currently in use.

View File

@@ -3,33 +3,11 @@
//! Works like the killring in emacs and readline. The killring is cut and paste with a memory of
//! previous cuts.
use cxx::{CxxWString, UniquePtr};
use once_cell::sync::Lazy;
use std::collections::VecDeque;
use std::sync::Mutex;
use crate::ffi::wcstring_list_ffi_t;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
#[cxx::bridge]
mod kill_ffi {
extern "C++" {
include!("wutil.h");
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
}
extern "Rust" {
#[cxx_name = "kill_add"]
fn kill_add_ffi(new_entry: &CxxWString);
#[cxx_name = "kill_replace"]
fn kill_replace_ffi(old_entry: &CxxWString, new_entry: &CxxWString);
#[cxx_name = "kill_yank_rotate"]
fn kill_yank_rotate_ffi() -> UniquePtr<CxxWString>;
#[cxx_name = "kill_yank"]
fn kill_yank_ffi() -> UniquePtr<CxxWString>;
}
}
struct KillRing(VecDeque<WString>);
@@ -104,22 +82,6 @@ pub fn kill_entries() -> Vec<WString> {
KILL_RING.lock().unwrap().entries()
}
fn kill_add_ffi(new_entry: &CxxWString) {
kill_add(new_entry.from_ffi());
}
fn kill_replace_ffi(old_entry: &CxxWString, new_entry: &CxxWString) {
kill_replace(old_entry.as_wstr(), new_entry.from_ffi())
}
fn kill_yank_ffi() -> UniquePtr<CxxWString> {
kill_yank().to_ffi()
}
fn kill_yank_rotate_ffi() -> UniquePtr<CxxWString> {
kill_yank_rotate().to_ffi()
}
#[cfg(test)]
fn test_killring() {
let mut kr = KillRing::new();

View File

@@ -50,12 +50,6 @@
mod fd_monitor;
mod fd_readable_set;
mod fds;
#[allow(rustdoc::broken_intra_doc_links)]
#[allow(clippy::module_inception)]
#[allow(clippy::new_ret_no_self)]
#[allow(clippy::wrong_self_convention)]
#[allow(clippy::needless_lifetimes)]
#[allow(unused_imports)]
mod ffi;
mod ffi_init;
mod fish;
@@ -71,7 +65,6 @@
mod history;
mod input;
mod input_common;
mod input_ffi;
mod io;
mod job_group;
mod kill;
@@ -109,7 +102,6 @@
mod wait_handle;
mod wchar;
mod wchar_ext;
mod wchar_ffi;
mod wcstringutil;
mod wgetopt;
mod widecharwidth;

View File

@@ -139,15 +139,6 @@ pub fn null_terminated_array_length<T>(mut arr: *const *const T) -> usize {
len
}
/// Convert a CxxString to a CString, truncating at the first NUL.
use cxx::CxxString;
fn cxxstring_to_cstring(s: &CxxString) -> CString {
let bytes: &[u8] = s.as_bytes();
let nul_pos = bytes.iter().position(|&b| b == 0);
let slice = &bytes[..nul_pos.unwrap_or(bytes.len())];
CString::new(slice).unwrap()
}
#[test]
fn test_null_terminated_array_length() {
let arr = [&1, &2, &3, std::ptr::null()];

View File

@@ -1,5 +1,5 @@
use crate::common::CancelChecker;
use crate::env::{EnvDyn, EnvDynFFI};
use crate::env::EnvDyn;
use crate::env::{EnvStack, EnvStackRef, Environment};
use crate::parser::{Parser, ParserRef};
use crate::proc::JobGroupRef;
@@ -156,52 +156,3 @@ pub fn get_bg_context(env: &EnvDyn, generation_count: u32) -> OperationContext {
EXPANSION_LIMIT_BACKGROUND,
)
}
#[cxx::bridge]
#[allow(clippy::needless_lifetimes)]
mod operation_context_ffi {
extern "C++" {
include!("env_fwd.h");
include!("parser.h");
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI = crate::env::EnvStackRefFFI;
#[cxx_name = "EnvDyn"]
type EnvDynFFI = crate::env::EnvDynFFI;
type Parser = crate::parser::Parser;
}
extern "Rust" {
type OperationContext<'a>;
fn empty_operation_context() -> Box<OperationContext<'static>>;
fn operation_context_globals() -> Box<OperationContext<'static>>;
fn check_cancel(&self) -> bool;
#[cxx_name = "get_bg_context"]
unsafe fn get_bg_context_ffi(
env: &EnvDynFFI,
generation_count: u32,
) -> Box<OperationContext<'_>>;
#[cxx_name = "parser_context"]
fn parser_context_ffi(parser: &Parser) -> Box<OperationContext<'static>>;
}
}
fn get_bg_context_ffi(env: &EnvDynFFI, generation_count: u32) -> Box<OperationContext<'_>> {
Box::new(get_bg_context(&env.0, generation_count))
}
fn parser_context_ffi(parser: &Parser) -> Box<OperationContext<'static>> {
Box::new(parser.context())
}
unsafe impl cxx::ExternType for OperationContext<'_> {
type Id = cxx::type_id!("OperationContext");
type Kind = cxx::kind::Opaque;
}
fn empty_operation_context() -> Box<OperationContext<'static>> {
Box::new(OperationContext::empty())
}
fn operation_context_globals() -> Box<OperationContext<'static>> {
Box::new(OperationContext::globals())
}

View File

@@ -23,17 +23,6 @@ pub struct ColorSupport: u8 {
/// Whether term256 and term24bit are supported.
static COLOR_SUPPORT: AtomicU8 = AtomicU8::new(0);
/// FFI bits.
#[no_mangle]
extern "C" fn output_get_color_support() -> u8 {
COLOR_SUPPORT.load(Ordering::Relaxed)
}
#[no_mangle]
extern "C" fn output_set_color_support(val: u8) {
COLOR_SUPPORT.store(val, Ordering::Relaxed);
}
/// Returns true if we think tparm can handle outputting a color index.
fn term_supports_color_natively(term: &Term, c: u8) -> bool {
#[allow(clippy::int_plus_one)]
@@ -592,91 +581,3 @@ pub fn parse_color(var: &EnvVar, is_background: bool) -> RgbColor {
result.set_reverse(is_reverse);
result
}
/// FFI junk.
fn stdoutput_ffi() -> &'static mut Outputter {
// TODO: this is bogus because it avoids RefCell's check, but is temporary for FFI purposes.
unsafe { &mut *Outputter::stdoutput().as_ptr() }
}
/// Make an outputter which outputs to its string.
fn make_buffering_outputter_ffi() -> Box<Outputter> {
Box::new(Outputter::new_buffering())
}
pub type RgbColorFFI = crate::ffi::rgb_color_t;
use crate::wchar_ffi::AsWstr;
impl Outputter {
fn set_color_ffi(&mut self, fg: &RgbColorFFI, bg: &RgbColorFFI) {
self.set_color(fg.from_ffi(), bg.from_ffi());
}
fn writech_ffi(&mut self, ch: crate::ffi::wchar_t) {
self.writech(char::from_u32(ch).expect("Invalid wchar"));
}
// Write a nul-terminated string.
// We accept CxxString because it prevents needing to do typecasts at the call site,
// as it's unclear what Cxx type corresponds to const char *.
// We are unconcerned with interior nul-bytes: none of the termcap sequences contain them
// for obvious reasons.
fn writembs_ffi(&mut self, mbs: &cxx::CxxString) {
let mbs = unsafe { CStr::from_ptr(mbs.as_ptr() as *const std::ffi::c_char) };
self.tputs(mbs);
}
fn writestr_ffi(&mut self, str: crate::ffi::wcharz_t) {
self.write_wstr(str.as_wstr());
}
fn write_color_ffi(&mut self, color: &RgbColorFFI, is_fg: bool) -> bool {
self.write_color(color.from_ffi(), is_fg)
}
}
#[cxx::bridge]
mod ffi {
extern "C++" {
include!("color.h");
include!("wutil.h");
#[cxx_name = "rgb_color_t"]
type RgbColorFFI = super::RgbColorFFI;
type wcharz_t = crate::ffi::wcharz_t;
}
extern "Rust" {
#[cxx_name = "outputter_t"]
type Outputter;
#[cxx_name = "make_buffering_outputter"]
fn make_buffering_outputter_ffi() -> Box<Outputter>;
#[cxx_name = "stdoutput"]
fn stdoutput_ffi() -> &'static mut Outputter;
#[cxx_name = "set_color"]
fn set_color_ffi(&mut self, fg: &RgbColorFFI, bg: &RgbColorFFI);
#[cxx_name = "writech"]
fn writech_ffi(&mut self, ch: wchar_t);
#[cxx_name = "writestr"]
fn writestr_ffi(&mut self, str: wcharz_t);
#[cxx_name = "writembs"]
fn writembs_ffi(&mut self, mbs: &CxxString);
#[cxx_name = "write_color"]
fn write_color_ffi(&mut self, color: &RgbColorFFI, is_fg: bool) -> bool;
// These do not need separate FFI variants.
fn contents(&self) -> &[u8];
fn begin_buffering(&mut self);
fn end_buffering(&mut self);
#[cxx_name = "push_back"]
fn push(&mut self, ch: u8);
}
}

View File

@@ -1,6 +1,5 @@
//! Pager support.
use cxx::{CxxWString, UniquePtr};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
@@ -8,7 +7,7 @@
escape_string, get_ellipsis_char, get_ellipsis_str, EscapeFlags, EscapeStringStyle,
};
use crate::compat::MB_CUR_MAX;
use crate::complete::{Completion, CompletionListFfi};
use crate::complete::Completion;
use crate::editable_line::EditableLine;
use crate::fallback::{fish_wcswidth, fish_wcwidth};
#[allow(unused_imports)]
@@ -18,7 +17,6 @@
use crate::screen::{Line, ScreenData};
use crate::termsize::Termsize;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use crate::wcstringutil::string_fuzzy_match_string;
/// Represents rendering from the pager.
@@ -485,17 +483,16 @@ fn completion_print_item(
assert!(comp_width <= width);
}
let modify_role = |mut role: HighlightRole| {
let mut base = role.repr;
let modify_role = |role: HighlightRole| {
let mut base = role as u8;
if selected {
base += HighlightRole::pager_selected_background.repr
- HighlightRole::pager_background.repr;
base += HighlightRole::pager_selected_background as u8
- HighlightRole::pager_background as u8;
} else if secondary {
base += HighlightRole::pager_secondary_background.repr
- HighlightRole::pager_background.repr;
base += HighlightRole::pager_secondary_background as u8
- HighlightRole::pager_background as u8;
}
role.repr = base;
role
unsafe { std::mem::transmute(base) }
};
let bg_role = modify_role(HighlightRole::pager_background);
@@ -1219,153 +1216,14 @@ fn process_completions_into_infos(lst: &[Completion]) -> Vec<PagerComp> {
result
}
#[cxx::bridge]
mod pager_ffi {
extern "C++" {
include!("complete.h");
include!("editable_line.h");
type CompletionListFfi = crate::complete::CompletionListFfi;
type Completion = crate::complete::Completion;
type EditableLine = crate::editable_line::EditableLine;
}
enum selection_motion_t {
north,
east,
south,
west,
page_north,
page_south,
next,
prev,
deselect,
}
extern "Rust" {
type PageRendering;
fn new_page_rendering() -> Box<PageRendering>;
fn remaining_to_disclose(&self) -> usize;
}
extern "Rust" {
type Pager;
fn new_pager() -> Box<Pager>;
fn clear(&mut self);
fn cursor_position(&self) -> usize;
fn empty(&self) -> bool;
fn extra_progress_text(&self) -> UniquePtr<CxxWString>;
fn set_extra_progress_text(&mut self, text: &CxxWString);
fn is_navigating_contents(&self) -> bool;
fn is_search_field_shown(&self) -> bool;
fn refilter_completions(&mut self);
fn rendering_needs_update(&self, rendering: &PageRendering) -> bool;
fn search_field_line(&mut self) -> *mut EditableLine;
#[cxx_name = "set_completions"]
fn set_completions_ffi(&mut self, completions: &CompletionListFfi, enable_refilter: bool);
fn set_fully_disclosed(&mut self);
#[cxx_name = "set_prefix"]
fn set_prefix_ffi(&mut self, prefix: &CxxWString, highlight: bool);
fn set_search_field_shown(&mut self, flag: bool);
#[cxx_name = "selected_completion"]
fn selected_completion_ffi(&self, rendering: &PageRendering) -> *const Completion;
#[cxx_name = "get_selected_row"]
fn get_selected_row_ffi(&self, rendering: &PageRendering) -> usize;
#[cxx_name = "get_selected_column"]
fn get_selected_column_ffi(&self, rendering: &PageRendering) -> usize;
#[cxx_name = "select_next_completion_in_direction"]
fn select_next_completion_in_direction_ffi(
&mut self,
direction: selection_motion_t,
rendering: &PageRendering,
) -> bool;
#[cxx_name = "selected_completion_index"]
fn selected_completion_index_ffi(&self) -> usize;
#[cxx_name = "set_selected_completion_index"]
fn set_selected_completion_index_ffi(&mut self, new_index: usize);
}
}
fn new_page_rendering() -> Box<PageRendering> {
Box::default()
}
impl PageRendering {
fn remaining_to_disclose(&self) -> usize {
self.remaining_to_disclose
}
}
fn new_pager() -> Box<Pager> {
Box::default()
}
impl Pager {
fn select_next_completion_in_direction_ffi(
&mut self,
direction: selection_motion_t,
rendering: &PageRendering,
) -> bool {
self.select_next_completion_in_direction(direction.from_ffi(), rendering)
}
fn selected_completion_ffi(&self, rendering: &PageRendering) -> *const Completion {
match self.selected_completion(rendering) {
Some(completion) => completion as *const Completion,
None => std::ptr::null(),
}
}
fn set_prefix_ffi(&mut self, prefix: &CxxWString, highlight: bool) {
self.set_prefix(prefix.as_wstr(), highlight);
}
fn search_field_line(&mut self) -> *mut EditableLine {
&mut self.search_field_line as *mut EditableLine
}
fn empty(&self) -> bool {
self.is_empty()
}
fn extra_progress_text(&self) -> UniquePtr<CxxWString> {
self.extra_progress_text.to_ffi()
}
fn set_extra_progress_text(&mut self, text: &CxxWString) {
self.extra_progress_text = text.from_ffi();
}
fn set_completions_ffi(&mut self, completions: &CompletionListFfi, enable_refilter: bool) {
self.set_completions(&completions.0, enable_refilter)
}
fn selected_completion_index_ffi(&self) -> usize {
self.selected_completion_idx.unwrap_or(PAGER_SELECTION_NONE)
}
fn set_selected_completion_index_ffi(&mut self, new_index: usize) {
self.set_selected_completion_index(if new_index == PAGER_SELECTION_NONE {
None
} else {
Some(new_index)
});
}
fn get_selected_row_ffi(&self, rendering: &PageRendering) -> usize {
self.get_selected_row(rendering)
.unwrap_or(PAGER_SELECTION_NONE)
}
fn get_selected_column_ffi(&self, rendering: &PageRendering) -> usize {
self.get_selected_column(rendering)
.unwrap_or(PAGER_SELECTION_NONE)
}
}
use pager_ffi::selection_motion_t;
impl selection_motion_t {
#[allow(clippy::wrong_self_convention)]
fn from_ffi(self) -> SelectionMotion {
match self {
selection_motion_t::north => SelectionMotion::North,
selection_motion_t::east => SelectionMotion::East,
selection_motion_t::south => SelectionMotion::South,
selection_motion_t::west => SelectionMotion::West,
selection_motion_t::page_north => SelectionMotion::PageNorth,
selection_motion_t::page_south => SelectionMotion::PageSouth,
selection_motion_t::next => SelectionMotion::Next,
selection_motion_t::prev => SelectionMotion::Prev,
selection_motion_t::deselect => SelectionMotion::Deselect,
_ => unreachable!(),
}
}
}
unsafe impl cxx::ExternType for Pager {
type Id = cxx::type_id!("Pager");
type Kind = cxx::kind::Opaque;
}
unsafe impl cxx::ExternType for PageRendering {
type Id = cxx::type_id!("PageRendering");
type Kind = cxx::kind::Opaque;
pub enum selection_motion_t {
north,
east,
south,
west,
page_north,
page_south,
next,
prev,
deselect,
}

View File

@@ -1,12 +1,8 @@
//! Constants used in the programmatic representation of fish code.
use crate::fallback::{fish_wcswidth, fish_wcwidth};
use crate::ffi::wcharz_t;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use bitflags::bitflags;
use cxx::{type_id, ExternType};
use cxx::{CxxWString, UniquePtr};
pub type SourceOffset = u32;
@@ -41,168 +37,100 @@ pub struct ParserTestErrorBits: u8 {
}
}
#[cxx::bridge]
mod parse_constants_ffi {
extern "C++" {
include!("wutil.h");
type wcharz_t = super::wcharz_t;
}
/// A range of source code.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct SourceRange {
start: u32,
length: u32,
}
extern "Rust" {
#[cxx_name = "end"]
fn end_ffi(self: &SourceRange) -> u32;
#[cxx_name = "contains_inclusive"]
fn contains_inclusive_ffi(self: &SourceRange, loc: u32) -> bool;
}
#[derive(Clone, Copy, Debug)]
pub enum ParseTokenType {
invalid = 1,
// Terminal types.
string,
pipe,
redirection,
background,
andand,
oror,
end,
// Special terminal type that means no more tokens forthcoming.
terminate,
// Very special terminal types that don't appear in the production list.
error,
tokenizer_error,
comment,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ParseKeyword {
// 'none' is not a keyword, it is a sentinel indicating nothing.
none,
kw_and,
kw_begin,
kw_builtin,
kw_case,
kw_command,
kw_else,
kw_end,
kw_exclam,
kw_exec,
kw_for,
kw_function,
kw_if,
kw_in,
kw_not,
kw_or,
kw_switch,
kw_time,
kw_while,
}
// Statement decorations like 'command' or 'exec'.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StatementDecoration {
none,
command,
builtin,
exec,
}
// Parse error code list.
#[derive(Debug)]
pub enum ParseErrorCode {
none,
// Matching values from enum parser_error.
syntax,
cmdsubst,
generic, // unclassified error types
// Tokenizer errors.
tokenizer_unterminated_quote,
tokenizer_unterminated_subshell,
tokenizer_unterminated_slice,
tokenizer_unterminated_escape,
tokenizer_other,
unbalancing_end, // end outside of block
unbalancing_else, // else outside of if
unbalancing_case, // case outside of switch
bare_variable_assignment, // a=b without command
andor_in_pipeline, // "and" or "or" after a pipe
}
struct parse_error_t {
text: UniquePtr<CxxWString>,
code: ParseErrorCode,
source_start: usize,
source_length: usize,
}
extern "Rust" {
type ParseError;
fn code(self: &ParseError) -> ParseErrorCode;
fn source_start(self: &ParseError) -> usize;
fn text(self: &ParseError) -> UniquePtr<CxxWString>;
#[cxx_name = "describe"]
fn describe_ffi(
self: &ParseError,
src: &CxxWString,
is_interactive: bool,
) -> UniquePtr<CxxWString>;
#[cxx_name = "describe_with_prefix"]
fn describe_with_prefix_ffi(
self: &ParseError,
src: &CxxWString,
prefix: &CxxWString,
is_interactive: bool,
skip_caret: bool,
) -> UniquePtr<CxxWString>;
fn describe_with_prefix(
self: &parse_error_t,
src: &CxxWString,
prefix: &CxxWString,
is_interactive: bool,
skip_caret: bool,
) -> UniquePtr<CxxWString>;
type ParseErrorListFfi;
fn new_parse_error_list() -> Box<ParseErrorListFfi>;
#[cxx_name = "offset_source_start"]
fn offset_source_start_ffi(self: &mut ParseErrorListFfi, amt: usize);
fn size(self: &ParseErrorListFfi) -> usize;
fn at(self: &ParseErrorListFfi, offset: usize) -> *const ParseError;
fn empty(self: &ParseErrorListFfi) -> bool;
fn push_back(self: &mut ParseErrorListFfi, error: &parse_error_t);
fn append(self: &mut ParseErrorListFfi, other: *mut ParseErrorListFfi);
fn erase(self: &mut ParseErrorListFfi, index: usize);
fn clear(self: &mut ParseErrorListFfi);
}
// The location of a pipeline.
pub enum PipelinePosition {
none, // not part of a pipeline
first, // first command in a pipeline
subsequent, // second or further command in a pipeline
}
/// A range of source code.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct SourceRange {
pub start: u32,
pub length: u32,
}
pub use parse_constants_ffi::{
parse_error_t, ParseErrorCode, ParseKeyword, ParseTokenType, PipelinePosition, SourceRange,
StatementDecoration,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ParseTokenType {
invalid = 1,
// Terminal types.
string,
pipe,
redirection,
background,
andand,
oror,
end,
// Special terminal type that means no more tokens forthcoming.
terminate,
// Very special terminal types that don't appear in the production list.
error,
tokenizer_error,
comment,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ParseKeyword {
// 'none' is not a keyword, it is a sentinel indicating nothing.
none,
kw_and,
kw_begin,
kw_builtin,
kw_case,
kw_command,
kw_else,
kw_end,
kw_exclam,
kw_exec,
kw_for,
kw_function,
kw_if,
kw_in,
kw_not,
kw_or,
kw_switch,
kw_time,
kw_while,
}
// Statement decorations like 'command' or 'exec'.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StatementDecoration {
none,
command,
builtin,
exec,
}
// Parse error code list.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ParseErrorCode {
none,
// Matching values from enum parser_error.
syntax,
cmdsubst,
generic, // unclassified error types
// Tokenizer errors.
tokenizer_unterminated_quote,
tokenizer_unterminated_subshell,
tokenizer_unterminated_slice,
tokenizer_unterminated_escape,
tokenizer_other,
unbalancing_end, // end outside of block
unbalancing_else, // else outside of if
unbalancing_case, // case outside of switch
bare_variable_assignment, // a=b without command
andor_in_pipeline, // "and" or "or" after a pipe
}
// The location of a pipeline.
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum PipelinePosition {
none, // not part of a pipeline
first, // first command in a pipeline
subsequent, // second or further command in a pipeline
}
impl SourceRange {
pub fn new(start: usize, length: usize) -> Self {
@@ -236,17 +164,10 @@ pub fn combine(&self, other: Self) -> Self {
}
}
fn end_ffi(&self) -> u32 {
self.start.checked_add(self.length).expect("Overflow")
}
// \return true if a location is in this range, including one-past-the-end.
pub fn contains_inclusive(&self, loc: usize) -> bool {
self.start() <= loc && loc - self.start() <= self.length()
}
fn contains_inclusive_ffi(&self, loc: u32) -> bool {
self.start <= loc && loc - self.start <= self.length
}
}
impl From<SourceRange> for std::ops::Range<usize> {
@@ -278,7 +199,6 @@ pub fn to_wstr(self) -> &'static wstr {
ParseTokenType::oror => "ParseTokenType::oror"L,
ParseTokenType::terminate => "ParseTokenType::terminate"L,
ParseTokenType::invalid => "ParseTokenType::invalid"L,
_ => "unknown token type"L,
}
}
}
@@ -482,29 +402,6 @@ pub fn describe_with_prefix(
}
}
impl From<&parse_error_t> for ParseError {
fn from(error: &parse_error_t) -> Self {
ParseError {
text: error.text.from_ffi(),
code: error.code,
source_start: error.source_start,
source_length: error.source_length,
}
}
}
impl parse_error_t {
fn describe_with_prefix(
self: &parse_error_t,
src: &CxxWString,
prefix: &CxxWString,
is_interactive: bool,
skip_caret: bool,
) -> UniquePtr<CxxWString> {
ParseError::from(self).describe_with_prefix_ffi(src, prefix, is_interactive, skip_caret)
}
}
impl ParseError {
fn code(&self) -> ParseErrorCode {
self.code
@@ -512,28 +409,6 @@ fn code(&self) -> ParseErrorCode {
fn source_start(&self) -> usize {
self.source_start
}
fn text(&self) -> UniquePtr<CxxWString> {
self.text.to_ffi()
}
fn describe_ffi(
self: &ParseError,
src: &CxxWString,
is_interactive: bool,
) -> UniquePtr<CxxWString> {
self.describe(src.as_wstr(), is_interactive).to_ffi()
}
fn describe_with_prefix_ffi(
self: &ParseError,
src: &CxxWString,
prefix: &CxxWString,
is_interactive: bool,
skip_caret: bool,
) -> UniquePtr<CxxWString> {
self.describe_with_prefix(src.as_wstr(), prefix.as_wstr(), is_interactive, skip_caret)
.to_ffi()
}
}
#[widestrs]
@@ -562,14 +437,6 @@ pub fn token_type_user_presentable_description(
pub type ParseErrorList = Vec<ParseError>;
#[derive(Clone)]
pub struct ParseErrorListFfi(pub ParseErrorList);
unsafe impl ExternType for ParseErrorListFfi {
type Id = type_id!("ParseErrorListFfi");
type Kind = cxx::kind::Opaque;
}
/// Helper function to offset error positions by the given amount. This is used when determining
/// errors in a substring of a larger source buffer.
pub fn parse_error_offset_source_start(errors: &mut ParseErrorList, amt: usize) {
@@ -583,44 +450,6 @@ pub fn parse_error_offset_source_start(errors: &mut ParseErrorList, amt: usize)
}
}
fn new_parse_error_list() -> Box<ParseErrorListFfi> {
Box::new(ParseErrorListFfi(Vec::new()))
}
impl ParseErrorListFfi {
fn offset_source_start_ffi(&mut self, amt: usize) {
parse_error_offset_source_start(&mut self.0, amt)
}
fn size(&self) -> usize {
self.0.len()
}
fn at(&self, offset: usize) -> *const ParseError {
&self.0[offset]
}
fn empty(&self) -> bool {
self.0.is_empty()
}
fn push_back(&mut self, error: &parse_error_t) {
self.0.push(error.into())
}
fn append(&mut self, other: *mut ParseErrorListFfi) {
self.0.append(&mut (unsafe { &*other }.0.clone()));
}
fn erase(&mut self, index: usize) {
self.0.remove(index);
}
fn clear(&mut self) {
self.0.clear()
}
}
/// Maximum number of function calls.
pub const FISH_MAX_STACK_DEPTH: usize = 128;

View File

@@ -615,7 +615,6 @@ fn process_type_for_command(
ProcessType::external
}
}
_ => unreachable!(),
}
}
@@ -658,7 +657,6 @@ fn apply_variable_assignments(
}
ExpandResultCode::wildcard_no_match // nullglob (equivalent to set)
| ExpandResultCode::ok => {}
_ => unreachable!(),
}
let vals: Vec<_> = expression_expanded
.into_iter()
@@ -1150,7 +1148,6 @@ fn run_switch_statement(
);
}
}
_ => unreachable!(),
}
// If we expanded to nothing, match the empty string.
@@ -1426,7 +1423,6 @@ fn expand_arguments_from_nodes(
}
}
ExpandResultCode::ok => {}
_ => unreachable!(),
}
// Now copy over any expanded arguments. Use std::move() to avoid extra allocations; this

View File

@@ -7,13 +7,11 @@
use crate::ast::{Ast, Node};
use crate::common::{assert_send, assert_sync};
use crate::parse_constants::{
token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseErrorListFfi,
ParseKeyword, ParseTokenType, ParseTreeFlags, SourceOffset, SourceRange, SOURCE_OFFSET_INVALID,
token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseKeyword,
ParseTokenType, ParseTreeFlags, SourceOffset, SourceRange, SOURCE_OFFSET_INVALID,
};
use crate::tokenizer::TokenizerError;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{WCharFromFFI, WCharToFFI};
use cxx::{CxxWString, UniquePtr};
/// A struct representing the token type that we use internally.
#[derive(Clone, Copy)]
@@ -102,7 +100,6 @@ fn from(err: TokenizerError) -> Self {
/// A type wrapping up a parse tree and the original source behind it.
pub struct ParsedSource {
pub src: WString,
src_ffi: UniquePtr<CxxWString>,
pub ast: Ast,
}
@@ -115,8 +112,7 @@ unsafe impl Sync for ParsedSource {}
impl ParsedSource {
pub fn new(src: WString, ast: Ast) -> Self {
let src_ffi = src.to_ffi();
ParsedSource { src, src_ffi, ast }
ParsedSource { src, ast }
}
}
@@ -194,79 +190,3 @@ pub fn parse_source(
Some(Arc::new(ParsedSource::new(src, ast)))
}
}
pub struct ParsedSourceRefFFI(pub Option<ParsedSourceRef>);
unsafe impl cxx::ExternType for ParsedSourceRefFFI {
type Id = cxx::type_id!("ParsedSourceRefFFI");
type Kind = cxx::kind::Opaque;
}
#[cxx::bridge]
mod parse_tree_ffi {
extern "C++" {
include!("ast.h");
pub type Ast = crate::ast::Ast;
pub type ParseErrorListFfi = crate::parse_constants::ParseErrorListFfi;
}
extern "Rust" {
type ParsedSourceRefFFI;
fn empty_parsed_source_ref() -> Box<ParsedSourceRefFFI>;
fn has_value(&self) -> bool;
fn new_parsed_source_ref(src: &CxxWString, ast: Pin<&mut Ast>) -> Box<ParsedSourceRefFFI>;
#[cxx_name = "parse_source"]
fn parse_source_ffi(
src: &CxxWString,
flags: u8,
errors: *mut ParseErrorListFfi,
) -> Box<ParsedSourceRefFFI>;
fn clone(self: &ParsedSourceRefFFI) -> Box<ParsedSourceRefFFI>;
fn src(self: &ParsedSourceRefFFI) -> &CxxWString;
fn ast(self: &ParsedSourceRefFFI) -> &Ast;
}
}
impl ParsedSourceRefFFI {
fn has_value(&self) -> bool {
self.0.is_some()
}
}
fn empty_parsed_source_ref() -> Box<ParsedSourceRefFFI> {
Box::new(ParsedSourceRefFFI(None))
}
fn new_parsed_source_ref(src: &CxxWString, ast: Pin<&mut Ast>) -> Box<ParsedSourceRefFFI> {
let mut stolen_ast = Ast::default();
std::mem::swap(&mut stolen_ast, ast.get_mut());
Box::new(ParsedSourceRefFFI(Some(Arc::new(ParsedSource::new(
src.from_ffi(),
stolen_ast,
)))))
}
fn parse_source_ffi(
src: &CxxWString,
flags: u8,
errors: *mut ParseErrorListFfi,
) -> Box<ParsedSourceRefFFI> {
Box::new(ParsedSourceRefFFI(parse_source(
src.from_ffi(),
ParseTreeFlags::from_bits(flags).unwrap(),
if errors.is_null() {
None
} else {
Some(unsafe { &mut (*errors).0 })
},
)))
}
impl ParsedSourceRefFFI {
fn clone(&self) -> Box<ParsedSourceRefFFI> {
Box::new(ParsedSourceRefFFI(self.0.clone()))
}
fn src(&self) -> &CxxWString {
&self.0.as_ref().unwrap().src_ffi
}
fn ast(&self) -> &Ast {
&self.0.as_ref().unwrap().ast
}
}

View File

@@ -12,12 +12,12 @@
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::operation_context::OperationContext;
use crate::parse_constants::{
parse_error_offset_source_start, ParseError, ParseErrorCode, ParseErrorList, ParseErrorListFfi,
ParseKeyword, ParseTokenType, ParseTreeFlags, ParserTestErrorBits, PipelinePosition,
StatementDecoration, ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE1,
ERROR_BRACKETED_VARIABLE_QUOTED1, ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR,
ERROR_NOT_PID, ERROR_NOT_STATUS, ERROR_NO_VAR_NAME, INVALID_BREAK_ERR_MSG,
INVALID_CONTINUE_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, UNKNOWN_BUILTIN_ERR_MSG,
parse_error_offset_source_start, ParseError, ParseErrorCode, ParseErrorList, ParseKeyword,
ParseTokenType, ParseTreeFlags, ParserTestErrorBits, PipelinePosition, StatementDecoration,
ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE1, ERROR_BRACKETED_VARIABLE_QUOTED1,
ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR, ERROR_NOT_PID, ERROR_NOT_STATUS,
ERROR_NO_VAR_NAME, INVALID_BREAK_ERR_MSG, INVALID_CONTINUE_ERR_MSG,
INVALID_PIPELINE_CMD_ERR_MSG, UNKNOWN_BUILTIN_ERR_MSG,
};
#[cfg(test)]
use crate::tests::prelude::*;
@@ -26,10 +26,8 @@
TOK_SHOW_COMMENTS,
};
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharFromFFI};
use crate::wcstringutil::truncate;
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
use cxx::CxxWString;
use std::ops;
/// Handles slices: the square brackets in an expression like $foo[5..4]
@@ -2036,58 +2034,3 @@ macro_rules! validate {
);
})();
}
#[cxx::bridge]
mod parse_util_ffi {
extern "C++" {
include!("parse_constants.h");
include!("parse_tree.h");
include!("ast.h");
type ParseErrorListFfi = crate::parse_constants::ParseErrorListFfi;
type DecoratedStatement = crate::ast::DecoratedStatement;
}
extern "Rust" {
fn parse_util_compute_indents_ffi(src: &CxxWString) -> Vec<i32>;
#[cxx_name = "detect_errors_in_decorated_statement"]
// Getting weird linker errors when using pointers.
fn detect_errors_in_decorated_statement_ffi(
buff_src: &CxxWString,
dst: usize,
out_errors: usize,
) -> bool;
}
}
fn detect_errors_in_decorated_statement_ffi(
buff_src: &CxxWString,
dst: usize,
out_errors: usize,
) -> bool {
let dst = unsafe { &*(dst as *const ast::DecoratedStatement) };
let out_errors = out_errors as *mut ParseErrorListFfi;
let mut out_errors = if out_errors.is_null() {
None
} else {
Some(unsafe { &mut (*out_errors).0 })
};
detect_errors_in_decorated_statement(buff_src.as_wstr(), dst, &mut out_errors)
}
fn parse_util_compute_indents_ffi(src: &CxxWString) -> Vec<i32> {
parse_util_compute_indents(&src.from_ffi())
}
fn parse_util_detect_errors_ffi(
buff_src: &CxxWString,
out_errors: *mut ParseErrorListFfi,
allow_incomplete: bool,
) -> u8 {
let out_errors = if out_errors.is_null() {
None
} else {
Some(unsafe { &mut (*out_errors).0 })
};
parse_util_detect_errors(buff_src.as_wstr(), out_errors, allow_incomplete)
.err()
.map_or(0, |error_bits| error_bits.bits())
}

View File

@@ -7,15 +7,12 @@
FilenameRef, ScopeGuarding, PROFILING_ACTIVE,
};
use crate::complete::CompletionList;
use crate::env::{
EnvMode, EnvStack, EnvStackRef, EnvStackRefFFI, EnvStackSetResult, Environment, Statuses,
};
use crate::env::{EnvMode, EnvStack, EnvStackRef, EnvStackSetResult, Environment, Statuses};
use crate::event::{self, Event};
use crate::expand::{
expand_string, replace_home_directory_with_tilde, ExpandFlags, ExpandResultCode,
};
use crate::fds::{open_cloexec, AutoCloseFd};
use crate::ffi::{self, wcstring_list_ffi_t};
use crate::flog::FLOGF;
use crate::function;
use crate::global_safety::{RelaxedAtomicBool, SharedFromThis, SharedFromThisBase};
@@ -27,20 +24,17 @@
SOURCE_LOCATION_UNKNOWN,
};
use crate::parse_execution::{EndExecutionReason, ParseExecutionContext};
use crate::parse_tree::{parse_source, ParsedSourceRef, ParsedSourceRefFFI};
use crate::parse_tree::{parse_source, ParsedSourceRef};
use crate::proc::{job_reap, JobGroupRef, JobList, JobRef, ProcStatus};
use crate::signal::{signal_check_cancel, signal_clear_cancel, Signal};
use crate::threads::assert_is_main_thread;
use crate::util::get_time;
use crate::wait_handle::WaitHandleStore;
use crate::wchar::{wstr, WString, L};
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use crate::wutil::{perror, wgettext, wgettext_fmt};
use cxx::{CxxWString, UniquePtr};
use libc::c_int;
use libc::O_RDONLY;
use once_cell::sync::Lazy;
pub use parser_ffi::{library_data_pod_t, BlockType, LoopStatus};
use printf_compat::sprintf;
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::{CStr, OsStr};
@@ -54,8 +48,6 @@
};
use widestring_suffix::widestrs;
use self::parser_ffi::ParseErrorListFfi;
/// block_t represents a block of commands.
#[derive(Default)]
pub struct Block {
@@ -121,7 +113,6 @@ pub fn description(&self) -> WString {
BlockType::event => "event"L,
BlockType::breakpoint => "breakpoint"L,
BlockType::variable_assignment => "variable_assignment"L,
_ => panic!(),
}
.to_owned();
@@ -327,7 +318,6 @@ pub struct Parser {
/// Set of variables for the parser.
pub variables: EnvStackRef,
variables_ffi: EnvStackRefFFI,
/// Miscellaneous library data.
library_data: RefCell<LibraryData>,
@@ -355,7 +345,6 @@ fn get_base(&self) -> &SharedFromThisBase<Parser> {
impl Parser {
/// Create a parser
pub fn new(variables: EnvStackRef, is_principal: bool) -> ParserRef {
let variables_ffi = EnvStackRefFFI(variables.clone());
let result = Rc::new(Self {
base: SharedFromThisBase::new(),
execution_context: RefCell::default(),
@@ -364,7 +353,6 @@ pub fn new(variables: EnvStackRef, is_principal: bool) -> ParserRef {
block_list: RefCell::default(),
eval_level: AtomicIsize::new(-1),
variables,
variables_ffi,
library_data: RefCell::new(LibraryData::new()),
syncs_uvars: RelaxedAtomicBool::new(false),
is_principal: RelaxedAtomicBool::new(is_principal),
@@ -1198,7 +1186,6 @@ fn append_block_description_to_stack_trace(parser: &Parser, b: &Block, trace: &m
| BlockType::if_block
| BlockType::breakpoint
| BlockType::variable_assignment => {}
_ => unreachable!(),
}
if print_call_site {
@@ -1215,347 +1202,108 @@ fn append_block_description_to_stack_trace(parser: &Parser, b: &Block, trace: &m
}
}
#[cxx::bridge]
mod parser_ffi {
/// Types of blocks.
#[derive(Debug)]
#[cxx_name = "block_type_t"]
pub enum BlockType {
/// While loop block
while_block,
/// For loop block
for_block,
/// If block
if_block,
/// Function invocation block
function_call,
/// Function invocation block with no variable shadowing
function_call_no_shadow,
/// Switch block
switch_block,
/// Command substitution scope
subst,
/// Outermost block
top,
/// Unconditional block
begin,
/// Block created by the . (source) builtin
source,
/// Block created on event notifier invocation
event,
/// Breakpoint block
breakpoint,
/// Variable assignment before a command
variable_assignment,
}
/// Possible states for a loop.
pub enum LoopStatus {
/// current loop block executed as normal
normals,
/// current loop block should be removed
breaks,
/// current loop block should be skipped
continues,
}
/// Plain-Old-Data components of `struct library_data_t` that can be shared over FFI
#[derive(Default)]
pub struct library_data_pod_t {
/// A counter incremented every time a command executes.
pub exec_count: u64,
/// A counter incremented every time a command produces a $status.
pub status_count: u64,
/// Last reader run count.
pub last_exec_run_counter: u64,
/// Number of recursive calls to the internal completion function.
pub complete_recursion_level: u32,
/// If set, we are currently within fish's initialization routines.
pub within_fish_init: bool,
/// If we're currently repainting the commandline.
/// Useful to stop infinite loops.
pub is_repaint: bool,
/// Whether we called builtin_complete -C without parameter.
pub builtin_complete_current_commandline: bool,
/// Whether we are currently cleaning processes.
pub is_cleaning_procs: bool,
/// The internal job id of the job being populated, or 0 if none.
/// This supports the '--on-job-exit caller' feature.
pub caller_id: u64, // TODO should be InternalJobId
/// Whether we are running a subshell command.
pub is_subshell: bool,
/// Whether we are running an event handler. This is not a bool because we keep count of the
/// event nesting level.
pub is_event: i32,
/// Whether we are currently interactive.
pub is_interactive: bool,
/// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key
/// bindings.
pub suppress_fish_trace: bool,
/// Whether we should break or continue the current loop.
/// This is set by the 'break' and 'continue' commands.
pub loop_status: LoopStatus,
/// Whether we should return from the current function.
/// This is set by the 'return' command.
pub returning: bool,
/// Whether we should stop executing.
/// This is set by the 'exit' command, and unset after 'reader_read'.
/// Note this only exits up to the "current script boundary." That is, a call to exit within a
/// 'source' or 'read' command will only exit up to that command.
pub exit_current_script: bool,
/// The read limit to apply to captured subshell output, or 0 for none.
pub read_limit: usize,
}
extern "C++" {
include!("operation_context.h");
include!("wutil.h");
include!("env.h");
include!("io.h");
include!("proc.h");
include!("parse_tree.h");
include!("parse_constants.h");
type IoChain = crate::io::IoChain;
type JobRefFfi = crate::proc::JobRefFfi;
type JobGroupRefFfi = crate::proc::JobGroupRefFfi;
type ParsedSourceRefFFI = crate::parse_tree::ParsedSourceRefFFI;
type ParseErrorListFfi = crate::parse_constants::ParseErrorListFfi;
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI = crate::env::EnvStackRefFFI;
#[cxx_name = "env_stack_set_result_t"]
type EnvStackSetResult = crate::env::EnvStackSetResult;
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
type OperationContext<'a> = crate::operation_context::OperationContext<'a>;
type Statuses = crate::env::Statuses;
}
extern "Rust" {
type Block;
}
extern "Rust" {
type LibraryData;
fn exec_count(&self) -> u64;
#[cxx_name = "is_repaint"]
fn is_repaint_ffi(&self) -> bool;
fn set_status_vars_command(&mut self, s: &CxxWString);
fn set_status_vars_commandline(&mut self, s: &CxxWString);
#[cxx_name = "transient_commandlines_empty"]
fn transient_commandlines_empty_ffi(&self) -> bool;
#[cxx_name = "transient_commandlines_back"]
fn transient_commandlines_back_ffi(&self) -> UniquePtr<CxxWString>;
#[cxx_name = "transient_commandlines_push"]
fn transient_commandlines_push_ffi(&mut self, s: &CxxWString);
#[cxx_name = "transient_commandlines_pop"]
fn transient_commandlines_pop_ffi(&mut self);
}
extern "Rust" {
type EvalRes;
fn no_status(&self) -> bool;
}
extern "Rust" {
type Parser;
fn get_last_status(&self) -> i32;
fn assert_can_execute(&self);
fn is_interactive(&self) -> bool;
#[cxx_name = "eval"]
fn ffi_eval(&self, cmd: &CxxWString, io: &IoChain) -> Box<EvalRes>;
#[cxx_name = "eval_with"]
#[cxx_name = "eval_parsed_source"]
fn ffi_eval_parsed_source(&self, ps: &ParsedSourceRefFFI, io: &IoChain);
#[cxx_name = "is_breakpoint"]
fn ffi_is_breakpoint(&self) -> bool;
#[cxx_name = "libdata"]
fn ffi_libdata(&self) -> &LibraryData;
#[cxx_name = "libdata_mut"]
#[allow(clippy::mut_from_ref)]
fn ffi_libdata_mut(&self) -> &mut LibraryData;
#[cxx_name = "libdata_pods"]
fn ffi_libdata_pods(&self) -> &library_data_pod_t;
#[cxx_name = "libdata_pods_mut"]
#[allow(clippy::mut_from_ref)]
fn ffi_libdata_pods_mut(&self) -> &mut library_data_pod_t;
#[cxx_name = "get_backtrace"]
fn ffi_get_backtrace(
&self,
src: &CxxWString,
errors: &ParseErrorListFfi,
) -> UniquePtr<CxxWString>;
#[cxx_name = "set_last_statuses"]
fn ffi_set_last_statuses(&self, s: &Statuses);
#[cxx_name = "vars"]
fn ffi_vars(&self) -> &EnvStackRefFFI;
#[cxx_name = "vars_boxed"]
fn ffi_vars_boxed(&self) -> *mut u8;
fn sync_uvars_and_fire(&self, always: bool);
#[cxx_name = "parser_principal_parser"]
fn ffi_parser_principal_parser() -> Box<ParserRefFFI>;
#[cxx_name = "shared"]
fn ffi_shared(&self) -> Box<ParserRefFFI>;
#[cxx_name = "set_var_and_fire"]
fn ffi_set_var_and_fire(
&self,
key: &CxxWString,
mode: u16,
vals: &wcstring_list_ffi_t,
) -> i32;
fn parser_expand_argument_list_ffi(
arg_list_src: &CxxWString,
flags: u16,
ctx: &OperationContext<'_>,
out: Pin<&mut wcstring_list_ffi_t>,
);
}
extern "Rust" {
#[cxx_name = "ParserRef"]
type ParserRefFFI;
fn vars(&self) -> &EnvStackRefFFI;
fn deref(&self) -> &Parser;
}
/// Types of blocks.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BlockType {
/// While loop block
while_block,
/// For loop block
for_block,
/// If block
if_block,
/// Function invocation block
function_call,
/// Function invocation block with no variable shadowing
function_call_no_shadow,
/// Switch block
switch_block,
/// Command substitution scope
subst,
/// Outermost block
top,
/// Unconditional block
begin,
/// Block created by the . (source) builtin
source,
/// Block created on event notifier invocation
event,
/// Breakpoint block
breakpoint,
/// Variable assignment before a command
variable_assignment,
}
impl LibraryData {
fn exec_count(&self) -> u64 {
self.pods.exec_count
}
fn is_repaint_ffi(&self) -> bool {
self.pods.is_repaint
}
fn set_status_vars_command(&mut self, s: &CxxWString) {
self.status_vars.command = s.from_ffi()
}
fn set_status_vars_commandline(&mut self, s: &CxxWString) {
self.status_vars.commandline = s.from_ffi()
}
fn transient_commandlines_empty_ffi(&self) -> bool {
self.transient_commandlines.is_empty()
}
fn transient_commandlines_back_ffi(&self) -> UniquePtr<CxxWString> {
self.transient_commandlines.last().unwrap().to_ffi()
}
fn transient_commandlines_push_ffi(&mut self, s: &CxxWString) {
self.transient_commandlines.push(s.from_ffi());
}
fn transient_commandlines_pop_ffi(&mut self) {
self.transient_commandlines.pop();
}
/// Possible states for a loop.
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum LoopStatus {
/// current loop block executed as normal
normals,
/// current loop block should be removed
breaks,
/// current loop block should be skipped
continues,
}
impl EvalRes {
fn no_status(&self) -> bool {
self.no_status
}
}
/// Plain-Old-Data components of `struct library_data_t` that can be shared over FFI
#[derive(Default)]
pub struct library_data_pod_t {
/// A counter incremented every time a command executes.
pub exec_count: u64,
unsafe impl cxx::ExternType for Parser {
type Id = cxx::type_id!("Parser");
type Kind = cxx::kind::Opaque;
}
/// A counter incremented every time a command produces a $status.
pub status_count: u64,
pub struct ParserRefFFI(pub ParserRef);
/// Last reader run count.
pub last_exec_run_counter: u64,
unsafe impl cxx::ExternType for ParserRefFFI {
type Id = cxx::type_id!("ParserRef"); // CXX name!
type Kind = cxx::kind::Opaque;
}
/// Number of recursive calls to the internal completion function.
pub complete_recursion_level: u32,
fn ffi_parser_principal_parser() -> Box<ParserRefFFI> {
Box::new(ParserRefFFI(Parser::principal_parser().shared()))
}
/// If set, we are currently within fish's initialization routines.
pub within_fish_init: bool,
impl Parser {
fn ffi_shared(&self) -> Box<ParserRefFFI> {
Box::new(ParserRefFFI(self.shared()))
}
fn ffi_set_var_and_fire(
&self,
key: &CxxWString,
mode: u16,
vals: &ffi::wcstring_list_ffi_t,
) -> i32 {
let val: u8 = unsafe {
std::mem::transmute(self.set_var_and_fire(
key.as_wstr(),
EnvMode::from_bits(mode).unwrap(),
vals.from_ffi(),
))
};
val as _
}
pub fn ffi_eval(&self, cmd: &CxxWString, io: &IoChain) -> Box<EvalRes> {
Box::new(self.eval(cmd.as_wstr(), io))
}
fn ffi_eval_parsed_source(&self, ps: &ParsedSourceRefFFI, io: &IoChain) {
self.eval_parsed_source(ps.0.as_ref().unwrap(), io, None, BlockType::top);
}
fn ffi_is_breakpoint(&self) -> bool {
self.is_breakpoint()
}
fn ffi_vars(&self) -> &EnvStackRefFFI {
&self.variables_ffi
}
fn ffi_vars_boxed(&self) -> *mut u8 {
Box::into_raw(Box::new(self.variables_ffi.clone())).cast()
}
fn ffi_libdata(&self) -> &LibraryData {
unsafe { self.library_data.try_borrow_unguarded() }.unwrap()
}
fn ffi_libdata_mut(&self) -> &mut LibraryData {
unsafe { &mut *self.library_data.as_ptr() }
}
fn ffi_libdata_pods(&self) -> &library_data_pod_t {
&unsafe { &*self.library_data.as_ptr() }.pods
}
fn ffi_libdata_pods_mut(&self) -> &mut library_data_pod_t {
&mut unsafe { &mut *self.library_data.as_ptr() }.pods
}
fn ffi_get_backtrace(
&self,
src: &CxxWString,
errors: &ParseErrorListFfi,
) -> UniquePtr<CxxWString> {
self.get_backtrace(&src.from_ffi(), &errors.0).to_ffi()
}
fn ffi_set_last_statuses(&self, s: &Statuses) {
self.set_last_statuses(s.clone())
}
}
/// If we're currently repainting the commandline.
/// Useful to stop infinite loops.
pub is_repaint: bool,
fn parser_expand_argument_list_ffi(
arg_list_src: &CxxWString,
flags: u16,
ctx: &OperationContext<'_>,
mut out: Pin<&mut wcstring_list_ffi_t>,
) {
let arg_list_src = arg_list_src.as_wstr();
let flags = ExpandFlags::from_bits(flags).unwrap();
for c in Parser::expand_argument_list(arg_list_src, flags, ctx) {
out.as_mut().push(c.completion);
}
}
/// Whether we called builtin_complete -C without parameter.
pub builtin_complete_current_commandline: bool,
impl ParserRefFFI {
fn deref(&self) -> &Parser {
let ptr = self.0.as_ref() as *const _;
unsafe { &*ptr }
}
fn vars(&self) -> &EnvStackRefFFI {
&self.0.variables_ffi
}
/// Whether we are currently cleaning processes.
pub is_cleaning_procs: bool,
/// The internal job id of the job being populated, or 0 if none.
/// This supports the '--on-job-exit caller' feature.
pub caller_id: u64, // TODO should be InternalJobId
/// Whether we are running a subshell command.
pub is_subshell: bool,
/// Whether we are running an event handler. This is not a bool because we keep count of the
/// event nesting level.
pub is_event: i32,
/// Whether we are currently interactive.
pub is_interactive: bool,
/// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key
/// bindings.
pub suppress_fish_trace: bool,
/// Whether we should break or continue the current loop.
/// This is set by the 'break' and 'continue' commands.
pub loop_status: LoopStatus,
/// Whether we should return from the current function.
/// This is set by the 'return' command.
pub returning: bool,
/// Whether we should stop executing.
/// This is set by the 'exit' command, and unset after 'reader_read'.
/// Note this only exits up to the "current script boundary." That is, a call to exit within a
/// 'source' or 'read' command will only exit up to that command.
pub exit_current_script: bool,
/// The read limit to apply to captured subshell output, or 0 for none.
pub read_limit: usize,
}

View File

@@ -1,19 +1,11 @@
//! Helper for executables (not builtins) to print a help message
//! Uses the fish in PATH, not necessarily the matching fish binary
use libc::c_char;
use std::ffi::{CStr, OsStr, OsString};
use std::ffi::{OsStr, OsString};
use std::process::Command;
const HELP_ERR: &str = "Could not show help message";
#[cxx::bridge]
mod ffi2 {
extern "Rust" {
unsafe fn unsafe_print_help(command: *const c_char);
}
}
pub fn print_help(command: &str) {
let mut cmdline = OsString::new();
cmdline.push("__fish_print_help ");
@@ -24,9 +16,3 @@ pub fn print_help(command: &str) {
.spawn()
.expect(HELP_ERR);
}
unsafe fn unsafe_print_help(command_buf: *const c_char) {
let command_cstr: &CStr = unsafe { CStr::from_ptr(command_buf) };
let command = command_cstr.to_str().unwrap();
print_help(command);
}

View File

@@ -1787,86 +1787,3 @@ pub fn have_proc_stat() -> bool {
/// The signals that signify crashes to us.
const CRASHSIGNALS: [libc::c_int; 6] = [SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS];
pub struct JobRefFfi(JobRef);
unsafe impl cxx::ExternType for JobRefFfi {
type Id = cxx::type_id!("JobRefFfi");
type Kind = cxx::kind::Opaque;
}
pub struct JobGroupRefFfi(JobGroupRef);
unsafe impl cxx::ExternType for JobGroupRefFfi {
type Id = cxx::type_id!("JobGroupRefFfi");
type Kind = cxx::kind::Opaque;
}
#[cxx::bridge]
mod proc_ffi {
extern "C++" {
include!("parser.h");
type Parser = crate::parser::Parser;
}
extern "Rust" {
fn set_interactive_session(flag: bool);
fn get_login() -> bool;
fn mark_login();
fn mark_no_exec();
fn proc_init();
type JobRefFfi;
type JobGroupRefFfi;
fn job_reap(parser: &Parser, allow_interactive: bool) -> bool;
fn is_interactive_session() -> bool;
fn have_proc_stat() -> bool;
fn proc_update_jiffies(parser: &Parser);
}
extern "Rust" {
type TtyTransfer;
fn new_tty_transfer() -> Box<TtyTransfer>;
#[cxx_name = "to_job_group"]
fn to_job_group_ffi(&mut self, jg: &JobGroupRefFfi);
fn save_tty_modes(&mut self);
fn reclaim(&mut self);
fn no_exec() -> bool;
}
extern "Rust" {
type JobListFFI;
#[cxx_name = "jobs_requiring_warning_on_exit"]
fn jobs_requiring_warning_on_exit_ffi(parser: &Parser) -> Box<JobListFFI>;
#[cxx_name = "print_exit_warning_for_jobs"]
fn print_exit_warning_for_jobs_ffi(jobs: &JobListFFI);
fn empty(&self) -> bool;
#[cxx_name = "hup_jobs"]
fn hup_jobs_ffi(parser: &Parser);
}
}
struct JobListFFI(JobList);
fn jobs_requiring_warning_on_exit_ffi(parser: &Parser) -> Box<JobListFFI> {
Box::new(JobListFFI(jobs_requiring_warning_on_exit(parser)))
}
fn print_exit_warning_for_jobs_ffi(jobs: &JobListFFI) {
print_exit_warning_for_jobs(&jobs.0)
}
fn hup_jobs_ffi(parser: &Parser) {
hup_jobs(&parser.jobs())
}
impl JobListFFI {
fn empty(&self) -> bool {
self.0.is_empty()
}
}
fn new_tty_transfer() -> Box<TtyTransfer> {
Box::new(TtyTransfer::new())
}
impl TtyTransfer {
#[allow(clippy::wrong_self_convention)]
fn to_job_group_ffi(&mut self, jg: &JobGroupRefFfi) {
self.to_job_group(&jg.0);
}
}

View File

@@ -2988,7 +2988,6 @@ fn handle_readline_command(&mut self, c: ReadlineCmd, rls: &mut ReadlineLoopStat
rl::SelfInsert | rl::SelfInsertNotFirst | rl::FuncAnd | rl::FuncOr => {
panic!("should have been handled by inputter_t::readch");
}
_ => panic!("unhandled readline command {c:?}"),
}
}
}
@@ -4188,7 +4187,6 @@ fn fill_history_pager(
index = match direction {
SearchDirection::Forward => self.history_pager_history_index_start,
SearchDirection::Backward => self.history_pager_history_index_end,
_ => unreachable!(),
}
}
HistoryPagerInvocation::Refresh => {
@@ -4227,7 +4225,6 @@ fn fill_history_pager(
zelf.history_pager_history_index_start = index;
zelf.history_pager_history_index_end = result.final_index;
}
_ => unreachable!(),
};
zelf.pager.extra_progress_text = if result.have_more_results {
wgettext!("Search again for more results")
@@ -5105,7 +5102,6 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd, rls: &mut ReadlineLo
);
return;
}
_ => unreachable!(),
}
// Construct a copy of the string from the beginning of the command substitution
@@ -5367,28 +5363,3 @@ fn event_is_normal_char(evt: &CharEvent) -> bool {
let c = evt.get_char();
!fish_reserved_codepoint(c) && c > char::from(31) && c != char::from(127)
}
#[cxx::bridge]
mod reader_ffi {
extern "Rust" {
#[cxx_name = "check_exit_loop_maybe_warning"]
fn check_exit_loop_maybe_warning_ffi() -> bool;
fn reader_test_and_clear_interrupted() -> i32;
fn reader_init();
fn restore_term_mode();
fn reader_schedule_prompt_repaint();
fn reader_reading_interrupted() -> i32;
fn reader_reset_interrupted();
#[cxx_name = "reader_current_data"]
fn reader_current_data_ffi() -> *mut u8;
}
}
fn check_exit_loop_maybe_warning_ffi() -> bool {
check_exit_loop_maybe_warning(None)
}
fn reader_current_data_ffi() -> *mut u8 {
let data = current_data().unwrap();
data as *mut ReaderData as *mut u8
}

View File

@@ -1,80 +1,35 @@
//! This file supports specifying and applying redirections.
use crate::ffi::wcharz_t;
use crate::io::IoChain;
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharToFFI;
use crate::wutil::fish_wcstoi;
use cxx::{CxxVector, CxxWString, SharedPtr, UniquePtr};
use libc::{c_int, O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, O_WRONLY};
use std::os::fd::RawFd;
#[cxx::bridge]
mod redirection_ffi {
extern "C++" {
include!("wutil.h");
type wcharz_t = super::wcharz_t;
}
#[derive(Debug)]
enum RedirectionMode {
overwrite, // normal redirection: > file.txt
append, // appending redirection: >> file.txt
input, // input redirection: < file.txt
fd, // fd redirection: 2>&1
noclob, // noclobber redirection: >? file.txt
}
extern "Rust" {
type RedirectionSpec;
fn is_close(self: &RedirectionSpec) -> bool;
#[cxx_name = "get_target_as_fd"]
fn get_target_as_fd_ffi(self: &RedirectionSpec) -> SharedPtr<i32>;
fn oflags(self: &RedirectionSpec) -> i32;
fn fd(self: &RedirectionSpec) -> i32;
fn mode(self: &RedirectionSpec) -> RedirectionMode;
fn target(self: &RedirectionSpec) -> UniquePtr<CxxWString>;
fn new_redirection_spec(
fd: i32,
mode: RedirectionMode,
target: wcharz_t,
) -> Box<RedirectionSpec>;
type RedirectionSpecListFfi;
fn new_redirection_spec_list() -> Box<RedirectionSpecListFfi>;
fn size(self: &RedirectionSpecListFfi) -> usize;
fn at(self: &RedirectionSpecListFfi, offset: usize) -> *const RedirectionSpec;
fn push_back(self: &mut RedirectionSpecListFfi, spec: Box<RedirectionSpec>);
fn clone(self: &RedirectionSpecListFfi) -> Box<RedirectionSpecListFfi>;
}
/// A type that represents the action dup2(src, target).
/// If target is negative, this represents close(src).
/// Note none of the fds here are considered 'owned'.
#[derive(Clone, Copy)]
struct Dup2Action {
src: i32,
target: i32,
}
/// A class representing a sequence of basic redirections.
#[derive(Default)]
struct Dup2List {
/// The list of actions.
pub actions: Vec<Dup2Action>,
}
extern "Rust" {
fn get_actions(self: &Dup2List) -> &[Dup2Action];
#[cxx_name = "dup2_list_resolve_chain"]
fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector<Dup2Action>) -> Dup2List;
fn fd_for_target_fd(self: &Dup2List, target: i32) -> i32;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RedirectionMode {
overwrite, // normal redirection: > file.txt
append, // appending redirection: >> file.txt
input, // input redirection: < file.txt
fd, // fd redirection: 2>&1
noclob, // noclobber redirection: >? file.txt
}
pub use redirection_ffi::{Dup2Action, Dup2List, RedirectionMode};
/// A type that represents the action dup2(src, target).
/// If target is negative, this represents close(src).
/// Note none of the fds here are considered 'owned'.
#[derive(Clone, Copy)]
pub struct Dup2Action {
pub src: i32,
pub target: i32,
}
/// A class representing a sequence of basic redirections.
#[derive(Default)]
pub struct Dup2List {
/// The list of actions.
pub actions: Vec<Dup2Action>,
}
impl RedirectionMode {
/// The open flags for this redirection mode.
@@ -126,12 +81,6 @@ pub fn is_close(&self) -> bool {
pub fn get_target_as_fd(&self) -> Option<RawFd> {
fish_wcstoi(&self.target).ok()
}
fn get_target_as_fd_ffi(&self) -> SharedPtr<i32> {
match self.get_target_as_fd() {
Some(fd) => SharedPtr::new(fd),
None => SharedPtr::null(),
}
}
/// \return the open flags for this redirection.
pub fn oflags(&self) -> c_int {
@@ -148,44 +97,10 @@ fn fd(&self) -> RawFd {
fn mode(&self) -> RedirectionMode {
self.mode
}
fn target(&self) -> UniquePtr<CxxWString> {
self.target.to_ffi()
}
}
fn new_redirection_spec(fd: i32, mode: RedirectionMode, target: wcharz_t) -> Box<RedirectionSpec> {
Box::new(RedirectionSpec {
fd,
mode,
target: target.into(),
})
}
pub type RedirectionSpecList = Vec<RedirectionSpec>;
struct RedirectionSpecListFfi(RedirectionSpecList);
fn new_redirection_spec_list() -> Box<RedirectionSpecListFfi> {
Box::new(RedirectionSpecListFfi(Vec::new()))
}
impl RedirectionSpecListFfi {
fn size(&self) -> usize {
self.0.len()
}
fn at(&self, offset: usize) -> *const RedirectionSpec {
&self.0[offset]
}
#[allow(clippy::boxed_local)]
fn push_back(&mut self, spec: Box<RedirectionSpec>) {
self.0.push(*spec)
}
fn clone(&self) -> Box<RedirectionSpecListFfi> {
Box::new(RedirectionSpecListFfi(self.0.clone()))
}
}
/// Produce a dup_fd_list_t from an io_chain. This may not be called before fork().
/// The result contains the list of fd actions (dup2 and close), as well as the list
/// of fds opened.
@@ -201,18 +116,6 @@ pub fn dup2_list_resolve_chain(io_chain: &IoChain) -> Dup2List {
result
}
fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector<Dup2Action>) -> Dup2List {
let mut result = Dup2List { actions: vec![] };
for io in io_chain {
if io.src < 0 {
result.add_close(io.target)
} else {
result.add_dup2(io.src, io.target)
}
}
result
}
impl Dup2List {
pub fn new() -> Self {
Default::default()

View File

@@ -11,11 +11,9 @@
use std::collections::LinkedList;
use std::ffi::{CStr, CString};
use std::io::Write;
use std::pin::Pin;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Mutex;
use cxx::{CxxVector, CxxWString, UniquePtr};
use libc::{ONLCR, STDERR_FILENO, STDOUT_FILENO};
use crate::common::{
@@ -24,17 +22,16 @@
ScopeGuarding,
};
use crate::curses::{term, tparm0, tparm1};
use crate::env::{EnvStackRef, Environment, TERM_HAS_XN};
use crate::env::{Environment, TERM_HAS_XN};
use crate::fallback::fish_wcwidth;
use crate::flog::FLOGF;
#[allow(unused_imports)]
use crate::future::IsSomeAnd;
use crate::global_safety::RelaxedAtomicBool;
use crate::highlight::{HighlightColorResolver, HighlightSpecListFFI};
use crate::highlight::HighlightColorResolver;
use crate::output::Outputter;
use crate::termsize::{termsize_last, Termsize};
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharFromFFI, WCharToFFI};
use crate::wcstringutil::string_prefixes_string;
use crate::{highlight::HighlightSpec, wcstringutil::fish_wcwidth_visible};
@@ -1838,146 +1835,3 @@ fn compute_layout(
result.autosuggestion = autosuggestion.to_owned();
result
}
#[allow(clippy::needless_lifetimes)] // add odds with cxx
#[cxx::bridge]
mod screen_ffi {
extern "C++" {
include!("screen.h");
include!("pager.h");
include!("highlight.h");
pub type HighlightSpec = crate::highlight::HighlightSpec;
pub type HighlightSpecListFFI = crate::highlight::HighlightSpecListFFI;
// pub type pager_t = crate::ffi::pager_t;
// pub type page_rendering_t = crate::ffi::page_rendering_t;
pub type Pager = crate::pager::Pager;
pub type PageRendering = crate::pager::PageRendering;
pub type highlight_spec_t = crate::ffi::highlight_spec_t;
}
extern "Rust" {
type Line;
fn new_line() -> Box<Line>;
#[cxx_name = "append"]
fn append_ffi(&mut self, character: u32, highlight: &HighlightSpec);
#[cxx_name = "append_str"]
fn append_str_ffi(&mut self, txt: &CxxWString, highlight: &HighlightSpec);
fn append_line(&mut self, line: &Line);
fn text_characters_ffi(&self) -> UniquePtr<CxxWString>;
}
extern "Rust" {
type ScreenData;
fn new_screen_data() -> Box<ScreenData>;
#[cxx_name = "create_line"]
fn create_line_ffi(&mut self, index: usize) -> *mut Line;
fn add_line(&mut self) -> &mut Line;
fn insert_line_at_index(&mut self, index: usize) -> &mut Line;
fn empty(&self) -> bool;
fn resize(&mut self, size: usize);
fn line_count(&self) -> usize;
fn line_ffi(&self, index: usize) -> *const Line;
}
extern "Rust" {
type Screen;
fn new_screen() -> Box<Screen>;
#[cxx_name = "write"]
fn write_ffi(
&mut self,
left_prompt: &CxxWString,
right_prompt: &CxxWString,
commandline: &CxxWString,
explicit_len: usize,
colors: &HighlightSpecListFFI,
indent: &CxxVector<i32>,
cursor_pos: usize,
vars: *mut u8,
pager: Pin<&mut Pager>,
page_rendering: Pin<&mut PageRendering>,
cursor_is_within_pager: bool,
);
fn reset_abandoning_line(&mut self, screen_width: usize);
fn cursor_is_wrapped_to_own_line(&self) -> bool;
fn save_status(&mut self);
fn reset_line(&mut self, repaint_prompt: bool);
fn autosuggestion_is_truncated(&self) -> bool;
}
extern "Rust" {
type PromptLayout;
}
extern "Rust" {
type LayoutCache;
}
extern "Rust" {
#[cxx_name = "screen_clear"]
fn screen_clear_ffi() -> UniquePtr<CxxWString>;
fn screen_force_clear_to_end();
}
}
fn new_line() -> Box<Line> {
Box::new(Line::new())
}
impl Line {
fn append_ffi(&mut self, character: u32, highlight: &HighlightSpec) {
self.append(char::try_from(character).unwrap(), *highlight)
}
fn append_str_ffi(&mut self, txt: &CxxWString, highlight: &HighlightSpec) {
self.append_str(&txt.from_ffi(), *highlight)
}
fn text_characters_ffi(&self) -> UniquePtr<CxxWString> {
WString::from_iter(self.text.iter().map(|hc| hc.character)).to_ffi()
}
}
fn new_screen_data() -> Box<ScreenData> {
Box::new(ScreenData::new())
}
impl ScreenData {
fn create_line_ffi(&mut self, index: usize) -> *mut Line {
self.create_line(index);
self.line_mut(index) as *mut Line
}
fn empty(&self) -> bool {
self.is_empty()
}
fn line_ffi(&self, index: usize) -> *const Line {
self.line(index) as *const Line
}
}
fn new_screen() -> Box<Screen> {
Box::new(Screen::new())
}
impl Screen {
fn write_ffi(
&mut self,
left_prompt: &CxxWString,
right_prompt: &CxxWString,
commandline: &CxxWString,
explicit_len: usize,
colors: &HighlightSpecListFFI,
indent: &CxxVector<i32>,
cursor_pos: usize,
vars: *mut u8,
pager: Pin<&mut Pager>,
page_rendering: Pin<&mut PageRendering>,
cursor_is_within_pager: bool,
) {
let vars = unsafe { Box::from_raw(vars as *mut EnvStackRef) };
self.write(
left_prompt.as_wstr(),
right_prompt.as_wstr(),
commandline.as_wstr(),
explicit_len,
&colors.0,
indent.as_slice(),
cursor_pos,
vars.as_ref().as_ref().get_ref(),
pager.get_mut(),
page_rendering.get_mut(),
cursor_is_within_pager,
);
}
fn autosuggestion_is_truncated(&self) -> bool {
self.autosuggestion_is_truncated
}
}
fn screen_clear_ffi() -> UniquePtr<CxxWString> {
screen_clear().to_ffi()
}

View File

@@ -7,68 +7,10 @@
use crate::termsize::TermsizeContainer;
use crate::topic_monitor::{generation_t, topic_monitor_principal, topic_t, GenerationsList};
use crate::wchar::prelude::*;
use crate::wchar_ffi::{AsWstr, WCharToFFI};
use crate::wutil::{fish_wcstoi, perror};
use cxx::{CxxWString, UniquePtr};
use errno::{errno, set_errno};
use std::sync::atomic::{AtomicI32, Ordering};
#[cxx::bridge]
mod signal_ffi {
extern "Rust" {
fn signal_set_handlers(interactive: bool);
fn signal_set_handlers_once(interactive: bool);
#[cxx_name = "signal_handle"]
fn signal_handle_ffi(sig: i32);
fn signal_unblock_all();
#[cxx_name = "sig2wcs"]
fn sig2wcs_ffi(sig: i32) -> UniquePtr<CxxWString>;
#[cxx_name = "wcs2sig"]
fn wcs2sig_ffi(sig: &CxxWString) -> i32;
#[cxx_name = "signal_get_desc"]
fn signal_get_desc_ffi(sig: i32) -> UniquePtr<CxxWString>;
fn signal_check_cancel() -> i32;
fn signal_clear_cancel();
fn signal_reset_handlers();
}
extern "Rust" {
type SigChecker;
fn new_sighupint_checker() -> Box<SigChecker>;
fn check(&mut self) -> bool;
}
}
fn sig2wcs_ffi(sig: i32) -> UniquePtr<CxxWString> {
Signal::new(sig).name().to_ffi()
}
fn wcs2sig_ffi(sig: &CxxWString) -> i32 {
if let Some(sig) = Signal::parse(sig.as_wstr()) {
sig.code()
} else {
-1
}
}
fn signal_get_desc_ffi(sig: i32) -> UniquePtr<CxxWString> {
Signal::new(sig).desc().to_ffi()
}
fn signal_handle_ffi(sig: i32) {
signal_handle(Signal::new(sig));
}
// This is extern "C" for FFI purposes, as this is used after fork().
#[no_mangle]
pub extern "C" fn get_signals_with_handlers_ffi(set: *mut libc::sigset_t) {
get_signals_with_handlers(unsafe { &mut *set });
}
/// Store the "main" pid. This allows us to reliably determine if we are in a forked child.
static MAIN_PID: AtomicI32 = AtomicI32::new(0);

View File

@@ -2,6 +2,7 @@
use crate::common::assert_sync;
use crate::env::{EnvMode, EnvVar, Environment};
use crate::flog::FLOG;
use crate::parser::Parser;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
@@ -9,36 +10,16 @@
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Mutex;
#[cxx::bridge]
mod termsize_ffi {
#[cxx_name = "termsize_t"]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Termsize {
/// Width of the terminal, in columns.
// TODO: Change to u32
pub width: isize,
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Termsize {
/// Width of the terminal, in columns.
// TODO: Change to u32
pub width: isize,
/// Height of the terminal, in rows.
// TODO: Change to u32
pub height: isize,
}
extern "C++" {
include!("env.h");
include!("parser.h");
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI = crate::env::EnvStackRefFFI;
type Parser = crate::parser::Parser;
}
extern "Rust" {
pub fn termsize_default() -> Termsize;
pub fn termsize_last() -> Termsize;
pub fn termsize_invalidate_tty();
pub fn termsize_update(parser: &Parser) -> Termsize;
}
/// Height of the terminal, in rows.
// TODO: Change to u32
pub height: isize,
}
pub use termsize_ffi::Termsize;
// A counter which is incremented every SIGWINCH, or when the tty is otherwise invalidated.
static TTY_TERMSIZE_GEN_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -290,7 +271,6 @@ pub fn termsize_invalidate_tty() {
TermsizeContainer::invalidate_tty();
}
use self::termsize_ffi::Parser;
#[test]
#[serial]
fn test_termsize() {

View File

@@ -85,7 +85,6 @@ fn callback(&self, fd: &mut AutoCloseFd, reason: ItemWakeReason) {
self.length_read.fetch_add(amt as usize, Ordering::Relaxed);
was_closed = amt == 0;
}
_ => unreachable!(),
}
self.total_calls.fetch_add(1, Ordering::Relaxed);

View File

@@ -45,72 +45,6 @@ impl FloggableDebug for ThreadId {}
static NOTIFY_SIGNALLER: once_cell::sync::Lazy<crate::fd_monitor::FdEventSignaller> =
once_cell::sync::Lazy::new(crate::fd_monitor::FdEventSignaller::new);
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("callback.h");
#[rust_name = "CppCallback"]
type callback_t;
fn invoke(&self) -> *const u8;
fn invoke_with_param(&self, param: *const u8) -> *const u8;
}
extern "Rust" {
#[cxx_name = "ASSERT_IS_MAIN_THREAD"]
fn assert_is_main_thread();
#[cxx_name = "ASSERT_IS_BACKGROUND_THREAD"]
fn assert_is_background_thread();
#[cxx_name = "ASSERT_IS_NOT_FORKED_CHILD"]
fn assert_is_not_forked_child();
fn configure_thread_assertions_for_testing();
fn is_main_thread() -> bool;
fn is_forked_child() -> bool;
}
extern "Rust" {
#[cxx_name = "make_detached_pthread"]
fn spawn_ffi(callback: &SharedPtr<CppCallback>) -> bool;
fn asan_before_exit();
fn asan_maybe_exit(code: i32);
}
extern "Rust" {
fn iothread_port() -> i32;
#[cxx_name = "iothread_service_main"]
fn iothread_service_main_ffi(ctx: *mut u8);
#[cxx_name = "iothread_perform"]
fn iothread_perform_ffi(callback: &SharedPtr<CppCallback>);
#[cxx_name = "iothread_perform_cantwait"]
fn iothread_perform_cant_wait_ffi(callback: &SharedPtr<CppCallback>);
}
}
fn iothread_service_main_ffi(ctx: *mut u8) {
let ctx = unsafe { &mut *(ctx as *mut ReaderData) };
iothread_service_main(ctx);
}
pub use ffi::CppCallback;
unsafe impl Send for ffi::CppCallback {}
unsafe impl Sync for ffi::CppCallback {}
fn iothread_perform_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
let callback = callback.clone();
iothread_perform(move || {
callback.invoke();
});
}
fn iothread_perform_cant_wait_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
let callback = callback.clone();
iothread_perform_cant_wait(move || {
callback.invoke();
});
}
/// A [`ThreadPool`] work request.
type WorkItem = Box<dyn FnOnce() + 'static + Send>;
@@ -193,10 +127,6 @@ fn not_background_thread() -> ! {
}
}
pub fn configure_thread_assertions_for_testing() {
THREAD_ASSERTS_CFG_FOR_TESTING.store(true, Ordering::Relaxed);
}
pub fn is_forked_child() -> bool {
IS_FORKED_PROC.load(Ordering::Relaxed)
}
@@ -278,13 +208,6 @@ pub fn spawn<F: FnOnce() + Send + 'static>(callback: F) -> bool {
result
}
fn spawn_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) -> bool {
let callback = callback.clone();
spawn(move || {
callback.invoke();
})
}
/// Exits calling onexit handlers if running under ASAN, otherwise does nothing.
///
/// This function is always defined but is a no-op if not running under ASAN. This is to make it

View File

@@ -17,15 +17,6 @@
use std::io::Write;
use std::time::{Duration, Instant};
#[cxx::bridge]
mod timer_ffi {
extern "Rust" {
type PrintElapsedOnDropFfi;
#[cxx_name = "push_timer"]
fn push_timer_ffi(enabled: bool) -> Box<PrintElapsedOnDropFfi>;
}
}
enum Unit {
Minutes,
Seconds,
@@ -52,22 +43,6 @@ pub fn push_timer(enabled: bool) -> Option<PrintElapsedOnDrop> {
})
}
/// cxx bridge does not support UniquePtr<NativeRustType> so we can't use a null UniquePtr to
/// represent a None, and cxx bridge does not support Box<Option<NativeRustType>> so we need to make
/// our own wrapper type that incorporates the Some/None states directly into it.
#[allow(clippy::large_enum_variant)]
enum PrintElapsedOnDropFfi {
Some(PrintElapsedOnDrop),
None,
}
fn push_timer_ffi(enabled: bool) -> Box<PrintElapsedOnDropFfi> {
Box::new(match push_timer(enabled) {
Some(t) => PrintElapsedOnDropFfi::Some(t),
None => PrintElapsedOnDropFfi::None,
})
}
/// An enumeration of supported libc rusage types used by [`getrusage()`].
/// NB: RUSAGE_THREAD is not supported on macOS.
enum RUsage {

View File

@@ -2,172 +2,110 @@
//! extended to support marks, tokenizing multiple strings and disposing of unused string segments.
use crate::common::valid_var_name_char;
use crate::ffi::wcharz_t;
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::parse_constants::SOURCE_OFFSET_INVALID;
use crate::redirection::RedirectionMode;
use crate::wchar::prelude::*;
use crate::wchar_ffi::{wchar_t, AsWstr, WCharToFFI};
use cxx::{CxxWString, SharedPtr, UniquePtr};
use libc::{c_int, STDIN_FILENO, STDOUT_FILENO};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
use std::os::fd::RawFd;
#[cxx::bridge]
mod tokenizer_ffi {
extern "C++" {
include!("wutil.h");
include!("redirection.h");
type wcharz_t = super::wcharz_t;
type RedirectionMode = super::RedirectionMode;
}
/// Token types. XXX Why this isn't ParseTokenType, I'm not really sure.
#[derive(Debug)]
enum TokenType {
/// Error reading token
error,
/// String token
string,
/// Pipe token
pipe,
/// && token
andand,
/// || token
oror,
/// End token (semicolon or newline, not literal end)
end,
/// redirection token
redirect,
/// send job to bg token
background,
/// comment token
comment,
}
#[derive(Debug, Eq, PartialEq)]
enum TokenizerError {
none,
unterminated_quote,
unterminated_subshell,
unterminated_slice,
unterminated_escape,
invalid_redirect,
invalid_pipe,
invalid_pipe_ampersand,
closing_unopened_subshell,
illegal_slice,
closing_unopened_brace,
unterminated_brace,
expected_pclose_found_bclose,
expected_bclose_found_pclose,
}
extern "Rust" {
fn tokenizer_get_error_message(err: TokenizerError) -> UniquePtr<CxxWString>;
}
struct Tok {
// Offset of the token.
offset: u32,
// Length of the token.
length: u32,
// If an error, this is the offset of the error within the token. A value of 0 means it occurred
// at 'offset'.
error_offset_within_token: u32,
error_length: u32,
// If an error, this is the error code.
error: TokenizerError,
// The type of the token.
type_: TokenType,
}
// TODO static_assert(sizeof(Tok) <= 32, "Tok expected to be 32 bytes or less");
extern "Rust" {
fn location_in_or_at_end_of_source_range(self: &Tok, loc: usize) -> bool;
#[cxx_name = "get_source"]
fn get_source_ffi(self: &Tok, str: &CxxWString) -> UniquePtr<CxxWString>;
}
extern "Rust" {
type Tokenizer;
fn new_tokenizer(start: wcharz_t, flags: u8) -> Box<Tokenizer>;
#[cxx_name = "next"]
fn next_ffi(self: &mut Tokenizer) -> UniquePtr<Tok>;
#[cxx_name = "text_of"]
fn text_of_ffi(self: &Tokenizer, tok: &Tok) -> UniquePtr<CxxWString>;
#[cxx_name = "is_token_delimiter"]
fn is_token_delimiter_ffi(c: wchar_t, next: SharedPtr<wchar_t>) -> bool;
}
extern "Rust" {
#[cxx_name = "tok_command"]
fn tok_command_ffi(str: &CxxWString) -> UniquePtr<CxxWString>;
}
/// Struct wrapping up a parsed pipe or redirection.
struct PipeOrRedir {
// The redirected fd, or -1 on overflow.
// In the common case of a pipe, this is 1 (STDOUT_FILENO).
// For example, in the case of "3>&1" this will be 3.
fd: i32,
// Whether we are a pipe (true) or redirection (false).
is_pipe: bool,
// The redirection mode if the type is redirect.
// Ignored for pipes.
mode: RedirectionMode,
// Whether, in addition to this redirection, stderr should also be dup'd to stdout
// For example &| or &>
stderr_merge: bool,
// Number of characters consumed when parsing the string.
consumed: usize,
}
extern "Rust" {
fn pipe_or_redir_from_string(buff: wcharz_t) -> UniquePtr<PipeOrRedir>;
fn is_valid(self: &PipeOrRedir) -> bool;
fn oflags(self: &PipeOrRedir) -> i32;
fn token_type(self: &PipeOrRedir) -> TokenType;
}
enum MoveWordStyle {
/// stop at punctuation
Punctuation,
/// stops at path components
PathComponents,
/// stops at whitespace
Whitespace,
}
/// Our state machine that implements "one word" movement or erasure.
struct MoveWordStateMachine {
state: u8,
style: MoveWordStyle,
}
extern "Rust" {
fn new_move_word_state_machine(syl: MoveWordStyle) -> Box<MoveWordStateMachine>;
#[cxx_name = "consume_char"]
fn consume_char_ffi(self: &mut MoveWordStateMachine, c: wchar_t) -> bool;
fn reset(self: &mut MoveWordStateMachine);
}
extern "Rust" {
#[cxx_name = "variable_assignment_equals_pos"]
fn variable_assignment_equals_pos_ffi(txt: &CxxWString) -> SharedPtr<usize>;
}
/// Token types. XXX Why this isn't ParseTokenType, I'm not really sure.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TokenType {
/// Error reading token
error,
/// String token
string,
/// Pipe token
pipe,
/// && token
andand,
/// || token
oror,
/// End token (semicolon or newline, not literal end)
end,
/// redirection token
redirect,
/// send job to bg token
background,
/// comment token
comment,
}
pub use tokenizer_ffi::{
MoveWordStateMachine, MoveWordStyle, PipeOrRedir, Tok, TokenType, TokenizerError,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TokenizerError {
none,
unterminated_quote,
unterminated_subshell,
unterminated_slice,
unterminated_escape,
invalid_redirect,
invalid_pipe,
invalid_pipe_ampersand,
closing_unopened_subshell,
illegal_slice,
closing_unopened_brace,
unterminated_brace,
expected_pclose_found_bclose,
expected_bclose_found_pclose,
}
pub struct Tok {
// Offset of the token.
pub offset: u32,
// Length of the token.
pub length: u32,
// If an error, this is the offset of the error within the token. A value of 0 means it occurred
// at 'offset'.
pub error_offset_within_token: u32,
pub error_length: u32,
// If an error, this is the error code.
pub error: TokenizerError,
// The type of the token.
pub type_: TokenType,
}
// TODO static_assert(sizeof(Tok) <= 32, "Tok expected to be 32 bytes or less");
/// Struct wrapping up a parsed pipe or redirection.
pub struct PipeOrRedir {
// The redirected fd, or -1 on overflow.
// In the common case of a pipe, this is 1 (STDOUT_FILENO).
// For example, in the case of "3>&1" this will be 3.
pub fd: i32,
// Whether we are a pipe (true) or redirection (false).
pub is_pipe: bool,
// The redirection mode if the type is redirect.
// Ignored for pipes.
pub mode: RedirectionMode,
// Whether, in addition to this redirection, stderr should also be dup'd to stdout
// For example &| or &>
pub stderr_merge: bool,
// Number of characters consumed when parsing the string.
pub consumed: usize,
}
pub enum MoveWordStyle {
/// stop at punctuation
Punctuation,
/// stops at path components
PathComponents,
/// stops at whitespace
Whitespace,
}
/// Our state machine that implements "one word" movement or erasure.
pub struct MoveWordStateMachine {
state: u8,
style: MoveWordStyle,
}
#[derive(Clone, Copy)]
pub struct TokFlags(pub u8);
@@ -204,12 +142,6 @@ fn bitor_assign(&mut self, rhs: Self) {
/// Make an effort to continue after an error.
pub const TOK_CONTINUE_AFTER_ERROR: TokFlags = TokFlags(8);
/// Get the error message for an error \p err.
pub fn tokenizer_get_error_message(err: TokenizerError) -> UniquePtr<CxxWString> {
let s: &'static wstr = err.into();
s.to_ffi()
}
impl From<TokenizerError> for &'static wstr {
#[widestrs]
fn from(err: TokenizerError) -> Self {
@@ -254,9 +186,6 @@ fn from(err: TokenizerError) -> Self {
TokenizerError::expected_bclose_found_pclose => {
wgettext!("Unexpected ')' found, expecting '}'")
}
_ => {
panic!("Unexpected tokenizer error");
}
}
}
}
@@ -285,9 +214,6 @@ pub fn location_in_or_at_end_of_source_range(self: &Tok, loc: usize) -> bool {
pub fn get_source<'a, 'b>(self: &'a Tok, str: &'b wstr) -> &'b wstr {
&str[self.offset as usize..(self.offset + self.length) as usize]
}
fn get_source_ffi(self: &Tok, str: &CxxWString) -> UniquePtr<CxxWString> {
self.get_source(str.as_wstr()).to_ffi()
}
pub fn set_offset(&mut self, value: usize) {
self.offset = value.try_into().unwrap();
}
@@ -359,10 +285,6 @@ pub fn new(start: &wstr, flags: TokFlags) -> Self {
}
}
fn new_tokenizer(start: wcharz_t, flags: u8) -> Box<Tokenizer> {
Box::new(Tokenizer::new(start.into(), TokFlags(flags)))
}
impl Iterator for Tokenizer {
type Item = Tok;
@@ -557,14 +479,6 @@ fn next(&mut self) -> Option<Self::Item> {
}
}
}
impl Tokenizer {
fn next_ffi(&mut self) -> UniquePtr<Tok> {
match self.next() {
Some(tok) => UniquePtr::new(tok),
None => UniquePtr::null(),
}
}
}
/// Test if a character is whitespace. Differs from iswspace in that it does not consider a
/// newline to be whitespace.
@@ -581,9 +495,6 @@ impl Tokenizer {
pub fn text_of(&self, tok: &Tok) -> &wstr {
tok.get_source(&self.start)
}
fn text_of_ffi(&self, tok: &Tok) -> UniquePtr<CxxWString> {
self.text_of(tok).to_ffi()
}
/// Return an error token and mark that we no longer have a next token.
fn call_error(
@@ -950,14 +861,7 @@ pub fn is_token_delimiter(c: char, next: Option<char>) -> bool {
c == '(' || !tok_is_string_character(c, next)
}
fn is_token_delimiter_ffi(c: wchar_t, next: SharedPtr<wchar_t>) -> bool {
is_token_delimiter(
c.try_into().unwrap(),
next.as_ref().map(|c| (*c).try_into().unwrap()),
)
}
/// \return the_ffi first token from the string, skipping variable assignments like A=B.
/// \return the first token from the string, skipping variable assignments like A=B.
pub fn tok_command(str: &wstr) -> WString {
let mut t = Tokenizer::new(str, TokFlags(0));
while let Some(token) = t.next() {
@@ -972,9 +876,6 @@ pub fn tok_command(str: &wstr) -> WString {
}
WString::new()
}
fn tok_command_ffi(str: &CxxWString) -> UniquePtr<CxxWString> {
tok_command(str.as_wstr()).to_ffi()
}
impl TryFrom<&wstr> for PipeOrRedir {
type Error = ();
@@ -1139,13 +1040,6 @@ fn try_from(buff: &wstr) -> Result<PipeOrRedir, ()> {
}
}
fn pipe_or_redir_from_string(buff: wcharz_t) -> UniquePtr<PipeOrRedir> {
match PipeOrRedir::try_from(Into::<&wstr>::into(buff)) {
Ok(p) => UniquePtr::new(p),
Err(()) => UniquePtr::null(),
}
}
impl PipeOrRedir {
/// \return the oflags (as in open(2)) for this redirection.
pub fn oflags(&self) -> c_int {
@@ -1197,12 +1091,8 @@ pub fn consume_char(&mut self, c: char) -> bool {
MoveWordStyle::Punctuation => self.consume_char_punctuation(c),
MoveWordStyle::PathComponents => self.consume_char_path_components(c),
MoveWordStyle::Whitespace => self.consume_char_whitespace(c),
_ => panic!(),
}
}
pub fn consume_char_ffi(&mut self, c: wchar_t) -> bool {
self.consume_char(c.try_into().unwrap())
}
pub fn reset(&mut self) {
self.state = 0;
@@ -1411,10 +1301,3 @@ pub fn variable_assignment_equals_pos(txt: &wstr) -> Option<usize> {
}
None
}
fn variable_assignment_equals_pos_ffi(txt: &CxxWString) -> SharedPtr<usize> {
match variable_assignment_equals_pos(txt.as_wstr()) {
Some(p) => SharedPtr::new(p),
None => SharedPtr::null(),
}
}

View File

@@ -362,11 +362,6 @@ unsafe impl Sync for topic_monitor_t {}
/// Do not attempt to move this into a lazy_static, it must be accessed from a signal handler.
static mut s_principal: *const topic_monitor_t = std::ptr::null();
/// Create a new topic monitor. Exposed for the FFI.
pub fn new_topic_monitor() -> Box<topic_monitor_t> {
Box::default()
}
impl topic_monitor_t {
/// Initialize the principal monitor, and return it.
/// This should be called only on the main thread.
@@ -374,7 +369,7 @@ pub fn initialize() -> &'static Self {
unsafe {
if s_principal.is_null() {
// We simply leak.
s_principal = Box::into_raw(new_topic_monitor());
s_principal = Box::into_raw(Box::default());
}
&*s_principal
}

View File

@@ -1,30 +1,6 @@
use crate::flog::log_extra_to_flog_file;
use crate::parser::Parser;
use crate::{
common::escape,
ffi::{wcharz_t, wcstring_list_ffi_t},
global_safety::RelaxedAtomicBool,
wchar::prelude::*,
wchar_ffi::WCharFromFFI,
};
#[cxx::bridge]
mod trace_ffi {
extern "C++" {
include!("wutil.h");
include!("parser.h");
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
type wcharz_t = super::wcharz_t;
type Parser = crate::parser::Parser;
}
extern "Rust" {
fn trace_set_enabled(do_enable: bool);
fn trace_enabled(parser: &Parser) -> bool;
#[cxx_name = "trace_argv"]
fn trace_argv_ffi(parser: &Parser, command: wcharz_t, args: &wcstring_list_ffi_t);
}
}
use crate::{common::escape, global_safety::RelaxedAtomicBool, wchar::prelude::*};
static DO_TRACE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
@@ -43,14 +19,6 @@ pub fn trace_enabled(parser: &Parser) -> bool {
/// Trace an "argv": a list of arguments where the first is the command.
// Allow the `&Vec` parameter as this function only exists temporarily for the FFI
#[allow(clippy::ptr_arg)]
fn trace_argv_ffi(parser: &Parser, command: wcharz_t, args: &wcstring_list_ffi_t) {
let command: WString = command.into();
let args: Vec<WString> = args.from_ffi();
let args_ref: Vec<&wstr> = args.iter().map(WString::as_utfstr).collect();
trace_argv(parser, command.as_utfstr(), &args_ref);
}
pub fn trace_argv<S: AsRef<wstr>>(parser: &Parser, command: &wstr, args: &[S]) {
// Format into a string to prevent interleaving with flog in other threads.
// Add the + prefix.
@@ -69,12 +37,6 @@ pub fn trace_argv<S: AsRef<wstr>>(parser: &Parser, command: &wstr, args: &[S]) {
log_extra_to_flog_file(&trace_text);
}
pub fn trace_if_enabled_ffi<S: AsRef<wstr>>(parser: &Parser, command: &wstr, args: &[S]) {
if trace_enabled(parser) {
trace_argv(parser, command, args);
}
}
/// Convenience helper to trace a single command if tracing is enabled.
pub fn trace_if_enabled(parser: &Parser, command: &wstr) {
if trace_enabled(parser) {

View File

@@ -71,39 +71,3 @@ pub fn create_notifier() -> Box<dyn UniversalNotifier> {
pub fn default_notifier() -> &'static dyn UniversalNotifier {
DEFAULT_NOTIFIER.get_or_init(create_notifier).as_ref()
}
struct UniversalNotifierFFI(&'static dyn UniversalNotifier);
fn default_notifier_ffi() -> Box<UniversalNotifierFFI> {
Box::new(UniversalNotifierFFI(default_notifier()))
}
impl UniversalNotifierFFI {
fn post_notification(&self) {
self.0.post_notification();
}
fn notification_fd_ffi(&self) -> i32 {
self.0.notification_fd().unwrap_or(-1)
}
fn notification_fd_became_readable_ffi(&self, fd: i32) -> bool {
self.0.notification_fd_became_readable(fd)
}
}
#[cxx::bridge]
mod ffi {
extern "Rust" {
type UniversalNotifierFFI;
#[cxx_name = "default_notifier"]
fn default_notifier_ffi() -> Box<UniversalNotifierFFI>;
fn post_notification(&self);
#[cxx_name = "notification_fd"]
fn notification_fd_ffi(&self) -> i32;
#[cxx_name = "notification_fd_became_readable"]
fn notification_fd_became_readable_ffi(&self, fd: i32) -> bool;
}
}

View File

@@ -1,26 +1,9 @@
//! Generic utilities library.
use crate::ffi::wcharz_t;
use crate::wchar::prelude::*;
use std::cmp::Ordering;
use std::time;
#[cxx::bridge]
mod ffi {
extern "C++" {
include!("wutil.h");
type wcharz_t = super::wcharz_t;
}
extern "Rust" {
#[cxx_name = "wcsfilecmp"]
fn wcsfilecmp_ffi(a: wcharz_t, b: wcharz_t) -> i32;
#[cxx_name = "wcsfilecmp_glob"]
fn wcsfilecmp_glob_ffi(a: wcharz_t, b: wcharz_t) -> i32;
fn get_time() -> i64;
}
}
fn ordering_to_int(ord: Ordering) -> i32 {
match ord {
Ordering::Less => -1,
@@ -29,14 +12,6 @@ fn ordering_to_int(ord: Ordering) -> i32 {
}
}
fn wcsfilecmp_glob_ffi(a: wcharz_t, b: wcharz_t) -> i32 {
ordering_to_int(wcsfilecmp_glob(a.into(), b.into()))
}
fn wcsfilecmp_ffi(a: wcharz_t, b: wcharz_t) -> i32 {
ordering_to_int(wcsfilecmp(a.into(), b.into()))
}
/// Compares two wide character strings with an (arguably) intuitive ordering. This function tries
/// to order strings in a way which is intuitive to humans with regards to sorting strings
/// containing numbers.

View File

@@ -1,257 +0,0 @@
//! Interfaces for various FFI string types.
//!
//! We have the following string types for FFI purposes:
//! - CxxWString: the Rust view of a C++ wstring.
//! - W0String: an owning string with a nul terminator.
//! - wcharz_t: a "newtyped" pointer to a nul-terminated string, implemented in C++.
//! This is useful for FFI boundaries, to work around autocxx limitations on pointers.
pub use crate::ffi::{wchar_t, wcharz_t, wcstring_list_ffi_t, ToCppWString};
use crate::wchar::{wstr, WString};
use autocxx::WithinUniquePtr;
use once_cell::sync::Lazy;
pub use widestring::u32cstr;
pub use widestring::U32CString as W0String;
/// \return the length of a nul-terminated raw string.
pub fn wcslen(str: *const wchar_t) -> usize {
assert!(!str.is_null(), "Null pointer");
let mut len = 0;
unsafe {
while *str.offset(len) != 0 {
len += 1;
}
}
len as usize
}
impl wcharz_t {
/// \return the chars of a wcharz_t.
pub fn chars(&self) -> &[char] {
assert!(!self.str_.is_null(), "Null wcharz");
let data = self.str_ as *const char;
let len = self.size();
unsafe { std::slice::from_raw_parts(data, len) }
}
}
/// W0String can be cheaply converted to a wcharz_t (but be mindful that W0String is kept alive).
impl From<&W0String> for wcharz_t {
fn from(w0: &W0String) -> Self {
wcharz_t {
str_: w0.as_ptr() as *const wchar_t,
}
}
}
/// Convert wcharz_t to an WString.
impl From<&wcharz_t> for WString {
fn from(wcharz: &wcharz_t) -> Self {
WString::from_chars(wcharz.chars())
}
}
/// Convert a wstr or WString to a W0String, which contains a nul-terminator.
/// This is useful for passing across FFI boundaries.
/// In general you don't need to use this directly - use the c_str macro below.
pub fn wstr_to_u32string<Str: AsRef<wstr>>(str: Str) -> W0String {
W0String::from_ustr(str.as_ref()).expect("String contained intermediate NUL character")
}
/// Convert a wstr to a nul-terminated pointer.
/// This needs to be a macro so we can create a temporary with the proper lifetime.
macro_rules! c_str {
($string:expr) => {
crate::wchar_ffi::wstr_to_u32string($string)
.as_ucstr()
.as_ptr()
.cast::<crate::ffi::wchar_t>()
};
}
/// Convert a wstr to a wcharz_t.
macro_rules! wcharz {
($string:expr) => {
crate::wchar_ffi::wcharz_t {
str_: crate::wchar_ffi::c_str!($string),
}
};
}
/// Convert a CxxVector of wcharz_t to a Vec<WString>.
pub fn wcharzs_to_vec(wcharz_vec: cxx::UniquePtr<cxx::CxxVector<wcharz_t>>) -> Vec<WString> {
wcharz_vec
.as_ref()
.expect("UniquePtr was null")
.iter()
.map(|s| s.into())
.collect()
}
pub(crate) use c_str;
pub(crate) use wcharz;
static EMPTY_WSTRING: Lazy<cxx::UniquePtr<cxx::CxxWString>> =
Lazy::new(|| cxx::CxxWString::create(&[]));
/// \return a reference to a shared empty wstring.
pub fn empty_wstring() -> &'static cxx::CxxWString {
&EMPTY_WSTRING
}
/// Implement Debug for wcharz_t.
impl std::fmt::Debug for wcharz_t {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.str_.is_null() {
write!(f, "((null))")
} else {
self.chars().fmt(f)
}
}
}
/// Convert self to a CxxWString, in preparation for using over FFI.
/// We can't use "From" as WString is implemented in an external crate.
pub trait WCharToFFI {
type Target;
fn to_ffi(&self) -> Self::Target;
}
impl ToCppWString for &wstr {
fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxWString> {
self.to_ffi()
}
}
impl ToCppWString for WString {
fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxWString> {
self.to_ffi()
}
}
impl ToCppWString for &WString {
fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxWString> {
self.to_ffi()
}
}
/// WString may be converted to CxxWString.
impl WCharToFFI for WString {
type Target = cxx::UniquePtr<cxx::CxxWString>;
fn to_ffi(&self) -> cxx::UniquePtr<cxx::CxxWString> {
cxx::CxxWString::create(self.as_char_slice())
}
}
/// wstr (wide string slices) may be converted to CxxWString.
impl WCharToFFI for wstr {
type Target = cxx::UniquePtr<cxx::CxxWString>;
fn to_ffi(&self) -> cxx::UniquePtr<cxx::CxxWString> {
cxx::CxxWString::create(self.as_char_slice())
}
}
/// wcharz_t (wide char) may be converted to CxxWString.
impl WCharToFFI for wcharz_t {
type Target = cxx::UniquePtr<cxx::CxxWString>;
fn to_ffi(&self) -> cxx::UniquePtr<cxx::CxxWString> {
cxx::CxxWString::create(self.chars())
}
}
/// Convert from a slice of something that can be referenced as a wstr,
/// to unique_ptr<wcstring_list_ffi_t>.
impl<T: AsRef<wstr>> WCharToFFI for [T] {
type Target = cxx::UniquePtr<wcstring_list_ffi_t>;
fn to_ffi(&self) -> cxx::UniquePtr<wcstring_list_ffi_t> {
let mut list_ptr = wcstring_list_ffi_t::create();
for s in self {
list_ptr.as_mut().unwrap().push(s.as_ref());
}
list_ptr
}
}
/// Convert from a CxxWString, in preparation for using over FFI.
pub trait WCharFromFFI<Target> {
/// Convert from a CxxWString for FFI purposes.
#[allow(clippy::wrong_self_convention)]
fn from_ffi(self) -> Target;
}
impl WCharFromFFI<WString> for &cxx::CxxWString {
fn from_ffi(self) -> WString {
WString::from_chars(self.as_chars())
}
}
impl WCharFromFFI<WString> for &cxx::UniquePtr<cxx::CxxWString> {
fn from_ffi(self) -> WString {
WString::from_chars(self.as_chars())
}
}
impl WCharFromFFI<WString> for &cxx::SharedPtr<cxx::CxxWString> {
fn from_ffi(self) -> WString {
WString::from_chars(self.as_chars())
}
}
impl WCharFromFFI<Vec<u8>> for &cxx::UniquePtr<cxx::CxxString> {
fn from_ffi(self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
impl WCharFromFFI<Vec<u8>> for &cxx::SharedPtr<cxx::CxxString> {
fn from_ffi(self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
impl WCharFromFFI<WString> for &wcharz_t {
fn from_ffi(self) -> WString {
self.into()
}
}
/// Convert wcstring_list_ffi_t to Vec<WString>.
impl WCharFromFFI<Vec<WString>> for &wcstring_list_ffi_t {
fn from_ffi(self) -> Vec<WString> {
let count: usize = self.size();
(0..count).map(|i| self.at(i).from_ffi()).collect()
}
}
/// Convert from the type we get back for C++ functions which return wcstring_list_ffi_t.
impl<T> WCharFromFFI<Vec<WString>> for T
where
T: autocxx::moveit::new::New<Output = wcstring_list_ffi_t>,
{
fn from_ffi(self) -> Vec<WString> {
self.within_unique_ptr().as_ref().unwrap().from_ffi()
}
}
/// Convert from FFI types to a reference to a wide string (i.e. a [`wstr`]) without allocating.
pub trait AsWstr<'a> {
fn as_wstr(&'a self) -> &'a wstr;
}
impl<'a> AsWstr<'a> for cxx::UniquePtr<cxx::CxxWString> {
fn as_wstr(&'a self) -> &'a wstr {
wstr::from_char_slice(self.as_chars())
}
}
impl<'a> AsWstr<'a> for cxx::CxxWString {
fn as_wstr(&'a self) -> &'a wstr {
wstr::from_char_slice(self.as_chars())
}
}
impl AsWstr<'_> for wcharz_t {
fn as_wstr(&self) -> &wstr {
wstr::from_char_slice(self.chars())
}
}

View File

@@ -1,6 +1,5 @@
// Enumeration of all wildcard types.
use cxx::CxxWString;
use libc::X_OK;
use std::cmp::Ordering;
use std::collections::HashSet;
@@ -16,7 +15,6 @@
use crate::future_feature_flags::feature_test;
use crate::future_feature_flags::FeatureFlag;
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharFromFFI;
use crate::wcstringutil::{
string_fuzzy_match_string, string_suffixes_string_case_insensitive, CaseFold,
};
@@ -1234,37 +1232,3 @@ fn test_wildcards() {
});
}
}
#[cxx::bridge]
mod ffi {
extern "C++" {
include!("wutil.h");
}
extern "Rust" {
#[cxx_name = "wildcard_match"]
fn wildcard_match_ffi(str: &CxxWString, wc: &CxxWString) -> bool;
#[cxx_name = "wildcard_has"]
fn wildcard_has_ffi(s: &CxxWString) -> bool;
#[cxx_name = "wildcard_has_internal"]
fn wildcard_has_internal_ffi(s: &CxxWString) -> bool;
}
}
fn wildcard_match_ffi(str: &CxxWString, wc: &CxxWString) -> bool {
wildcard_match(
str.from_ffi(),
wc.from_ffi(),
/*leading_dots_fail_to_match*/ false,
)
}
fn wildcard_has_ffi(s: &CxxWString) -> bool {
wildcard_has(s.from_ffi())
}
fn wildcard_has_internal_ffi(s: &CxxWString) -> bool {
wildcard_has_internal(s.from_ffi())
}

View File

@@ -6,11 +6,11 @@
use std::sync::Mutex;
use crate::common::{charptr2wcstring, wcs2zstring};
use crate::ffi::wchar_t;
use crate::fish::PACKAGE_NAME;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wchar_ffi::wchar_t;
use errno::{errno, set_errno};
use once_cell::sync::{Lazy, OnceCell};
use widestring::U32CString;

View File

@@ -4,6 +4,7 @@
use std::{ffi::CString, ptr};
use crate::fallback::fish_mkstemp_cloexec;
use crate::tests::prelude::*;
use super::*;
@@ -68,7 +69,7 @@ fn test_wwrite_to_fd() {
assert!(tmpfd.is_valid());
tmpfd.close();
}
let sizes = [0, 1, 2, 3, 5, 13, 23, 64, 128, 255, 4096, 4096 * 2];
let sizes = [1, 2, 3, 5, 13, 23, 64, 128, 255, 4096, 4096 * 2];
for &size in &sizes {
let fd = AutoCloseFd::new(unsafe {
libc::open(filename.as_ptr(), O_RDWR | O_TRUNC | O_CREAT, 0o666)

View File

@@ -1,26 +0,0 @@
// Support for abbreviations.
//
#ifndef FISH_ABBRS_H
#define FISH_ABBRS_H
#include <unordered_map>
#include <unordered_set>
#include "common.h"
#include "maybe.h"
#include "parse_constants.h"
#if INCLUDE_RUST_HEADERS
#include "abbrs.rs.h"
#else
// Hacks to allow us to compile without Rust headers.
struct abbrs_replacer_t;
struct abbrs_replacement_t;
struct abbreviation_t;
#endif
#endif

View File

@@ -1,25 +0,0 @@
#include "config.h" // IWYU pragma: keep
#include "ast.h"
#include <algorithm>
#include <array>
#include <cstdarg>
#include <cstdlib>
#include <string>
#include "common.h"
#include "flog.h"
#include "parse_constants.h"
#include "parse_tree.h"
#include "tokenizer.h"
#include "wutil.h" // IWYU pragma: keep
rust::Box<Ast> ast_parse(const wcstring &src, parse_tree_flags_t flags,
parse_error_list_t *out_errors) {
return ast_parse_ffi(src, flags, out_errors);
}
rust::Box<Ast> ast_parse_argument_list(const wcstring &src, parse_tree_flags_t flags,
parse_error_list_t *out_errors) {
return ast_parse_argument_list_ffi(src, flags, out_errors);
}

View File

@@ -1,98 +0,0 @@
// Programmatic representation of fish grammar.
#ifndef FISH_AST_H
#define FISH_AST_H
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "common.h"
#include "cxx.h"
#include "maybe.h"
#include "parse_constants.h"
#if INCLUDE_RUST_HEADERS
#include "ast.rs.h"
namespace ast {
using ast_t = Ast;
using category_t = Category;
using type_t = Type;
using andor_job_list_t = AndorJobList;
using andor_job_t = AndorJob;
using argument_list_t = ArgumentList;
using argument_or_redirection_list_t = ArgumentOrRedirectionList;
using argument_or_redirection_t = ArgumentOrRedirection;
using argument_t = Argument;
using begin_header_t = BeginHeader;
using block_statement_t = BlockStatement;
using case_item_t = CaseItem;
using decorated_statement_t = DecoratedStatement;
using elseif_clause_list_t = ElseifClauseList;
using for_header_t = ForHeader;
using freestanding_argument_list_t = FreestandingArgumentList;
using function_header_t = FunctionHeader;
using if_clause_t = IfClause;
using if_statement_t = IfStatement;
using job_conjunction_continuation_t = JobConjunctionContinuation;
using job_conjunction_t = JobConjunction;
using job_continuation_t = JobContinuation;
using job_list_t = JobList;
using job_pipeline_t = JobPipeline;
using maybe_newlines_t = MaybeNewlines;
using not_statement_t = NotStatement;
using redirection_t = Redirection;
using semi_nl_t = SemiNl;
using statement_t = Statement;
using string_t = String_;
using switch_statement_t = SwitchStatement;
using variable_assignment_list_t = VariableAssignmentList;
using variable_assignment_t = VariableAssignment;
using while_header_t = WhileHeader;
} // namespace ast
#else
struct Ast;
struct NodeFfi;
struct BlockStatement;
namespace ast {
using ast_t = Ast;
using block_statement_t = BlockStatement;
struct argument_t;
struct statement_t;
struct string_t;
struct maybe_newlines_t;
struct redirection_t;
struct variable_assignment_t;
struct semi_nl_t;
struct decorated_statement_t;
struct keyword_base_t;
} // namespace ast
#endif
using DecoratedStatement = ast::decorated_statement_t;
using BlockStatement = ast::block_statement_t;
namespace ast {
using node_t = ::NodeFfi;
}
rust::Box<Ast> ast_parse(const wcstring &src, parse_tree_flags_t flags = parse_flag_none,
parse_error_list_t *out_errors = nullptr);
rust::Box<Ast> ast_parse_argument_list(const wcstring &src,
parse_tree_flags_t flags = parse_flag_none,
parse_error_list_t *out_errors = nullptr);
#endif // FISH_AST_H

View File

@@ -1,31 +0,0 @@
#include "config.h" // IWYU pragma: keep
#include "builtin.h"
#include "wutil.h" // IWYU pragma: keep
/// Counts the number of arguments in the specified null-terminated array
int builtin_count_args(const wchar_t *const *argv) {
int argc;
for (argc = 1; argv[argc] != nullptr;) {
argc++;
}
assert(argv[argc] == nullptr);
return argc;
}
/// This function works like wperror, but it prints its result into the streams.err string instead
/// to stderr. Used by the builtin commands.
void builtin_wperror(const wchar_t *program_name, io_streams_t &streams) {
char *err = std::strerror(errno);
if (program_name != nullptr) {
streams.err()->append(program_name);
streams.err()->append(L": ");
}
if (err != nullptr) {
const wcstring werr = str2wcstring(err);
streams.err()->append(werr);
streams.err()->push(L'\n');
}
}

View File

@@ -1,91 +0,0 @@
// Prototypes for functions for executing builtin functions.
#ifndef FISH_BUILTIN_H
#define FISH_BUILTIN_H
#include <vector>
#include "common.h"
#include "complete.h"
#include "maybe.h"
#include "wutil.h"
struct Parser;
struct IoStreams;
using parser_t = Parser;
using io_streams_t = IoStreams;
class proc_status_t;
struct OutputStreamFfi;
using output_stream_t = OutputStreamFfi;
struct CompletionListFfi;
using completion_list_t = CompletionListFfi;
/// Data structure to describe a builtin.
struct builtin_data_t {
// Name of the builtin.
const wchar_t *name;
// Function pointer to the builtin implementation.
maybe_t<int> (*func)(const parser_t &parser, io_streams_t &streams, const wchar_t **argv);
// Description of what the builtin does.
const wchar_t *desc;
};
/// The default prompt for the read command.
#define DEFAULT_READ_PROMPT L"set_color green; echo -n read; set_color normal; echo -n \"> \""
/// Error message on missing argument.
#define BUILTIN_ERR_MISSING _(L"%ls: %ls: option requires an argument\n")
/// Error message on missing man page.
#define BUILTIN_ERR_MISSING_HELP \
_(L"fish: %ls: missing man page\nDocumentation may not be installed.\n`help %ls` will " \
L"show an online version\n")
/// Error message on invalid combination of options.
#define BUILTIN_ERR_COMBO _(L"%ls: invalid option combination\n")
/// Error message on invalid combination of options.
#define BUILTIN_ERR_COMBO2 _(L"%ls: invalid option combination, %ls\n")
#define BUILTIN_ERR_COMBO2_EXCLUSIVE _(L"%ls: %ls %ls: options cannot be used together\n")
/// Error message on multiple scope levels for variables.
#define BUILTIN_ERR_GLOCAL _(L"%ls: scope can be only one of: universal function global local\n")
/// Error message for specifying both export and unexport to set/read.
#define BUILTIN_ERR_EXPUNEXP _(L"%ls: cannot both export and unexport\n")
/// Error message for unknown switch.
#define BUILTIN_ERR_UNKNOWN _(L"%ls: %ls: unknown option\n")
/// Error message for unexpected args.
#define BUILTIN_ERR_ARG_COUNT0 _(L"%ls: missing argument\n")
#define BUILTIN_ERR_ARG_COUNT1 _(L"%ls: expected %d arguments; got %d\n")
#define BUILTIN_ERR_ARG_COUNT2 _(L"%ls: %ls: expected %d arguments; got %d\n")
#define BUILTIN_ERR_MIN_ARG_COUNT1 _(L"%ls: expected >= %d arguments; got %d\n")
#define BUILTIN_ERR_MAX_ARG_COUNT1 _(L"%ls: expected <= %d arguments; got %d\n")
/// Error message for invalid variable name.
#define BUILTIN_ERR_VARNAME _(L"%ls: %ls: invalid variable name. See `help identifiers`\n")
/// Error message for invalid bind mode name.
#define BUILTIN_ERR_BIND_MODE _(L"%ls: %ls: invalid mode name. See `help identifiers`\n")
/// Error message when too many arguments are supplied to a builtin.
#define BUILTIN_ERR_TOO_MANY_ARGUMENTS _(L"%ls: too many arguments\n")
/// Error message when integer expected
#define BUILTIN_ERR_NOT_NUMBER _(L"%ls: %ls: invalid integer\n")
/// Command that requires a subcommand was invoked without a recognized subcommand.
#define BUILTIN_ERR_MISSING_SUBCMD _(L"%ls: missing subcommand\n")
#define BUILTIN_ERR_INVALID_SUBCMD _(L"%ls: %ls: invalid subcommand\n")
/// The send stuff to foreground message.
#define FG_MSG _(L"Send job %d (%ls) to foreground\n")
int builtin_count_args(const wchar_t *const *argv);
void builtin_wperror(const wchar_t *program_name, io_streams_t &streams);
#endif

View File

@@ -1,49 +0,0 @@
#pragma once
#include <cstdlib>
#include <functional>
#include <memory>
#include <utility>
#include <vector>
/// A RAII callback container that can be used when the rust code needs to (or might need to) free
/// up the resources allocated for a callback (either the type-erased std::function wrapping the
/// lambda itself or the parameter to it.)
struct callback_t {
std::function<void *(const void *param)> callback;
std::vector<std::function<void()>> cleanups;
/// The default no-op constructor for the callback_t type.
callback_t() {
this->callback = [=](const void *) { return (void *)nullptr; };
}
/// Creates a new callback_t instance wrapping the specified type-erased std::function with an
/// optional parameter (defaulting to nullptr).
callback_t(std::function<void *(const void *param)> &&callback) {
this->callback = std::move(callback);
}
/// Executes the wrapped callback with the parameter stored at the time of creation and returns
/// the type-erased (void *) result, but cast to a `const uint8_t *` to please cxx::bridge.
const uint8_t *invoke() const {
const void *result = callback(nullptr);
return (const uint8_t *)result;
}
/// Executes the wrapped callback with the provided parameter and returns the type-erased
/// (void *) result, but cast to a `const uint8_t *` to please cxx::bridge.
const uint8_t *invoke_with_param(const uint8_t *param) const {
const void *result = callback((const void *)param);
return (const uint8_t *)result;
}
~callback_t() {
if (cleanups.size() > 0) {
for (const std::function<void()> &dtor : cleanups) {
(dtor)();
}
cleanups.clear();
}
}
};

View File

@@ -1,280 +0,0 @@
// Color class implementation.
#include "config.h" // IWYU pragma: keep
#include "color.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <cwchar> // IWYU pragma: keep
#include <iterator>
#include "common.h"
#include "fallback.h" // IWYU pragma: keep
/// Compare wide strings with simple ASCII canonicalization.
/// \return -1, 0, or 1 if s1 is less than, equal to, or greater than s2, respectively.
static int simple_icase_compare(const wchar_t *s1, const wchar_t *s2) {
for (size_t idx = 0; s1[idx] || s2[idx]; idx++) {
wchar_t c1 = s1[idx];
wchar_t c2 = s2[idx];
// "Canonicalize" to lower case.
if (L'A' <= c1 && c1 <= L'Z') c1 = L'a' + (c1 - L'A');
if (L'A' <= c2 && c2 <= L'Z') c2 = L'a' + (c2 - L'A');
if (c1 != c2) {
return c1 < c2 ? -1 : 1;
}
}
// We must have equal lengths and equal values.
return 0;
}
bool rgb_color_t::try_parse_special(const wcstring &special) {
std::memset(&data, 0, sizeof data);
const wchar_t *name = special.c_str();
// wcscasecmp is so slow that using it directly causes `try_parse_special` to consume up to
// 3% of all of fish's cpu time due to extremely inefficient invariant case lookups for wide
// characters (tested: Fedora Server 32 w/ glibc 2.31 with -O2). (This function is also called
// virtually non-stop while emitting output to determine colorization.)
// Take advantage of the fact that std::string length is O(1) to speed things up, and perform
// what amounts to a simple memcmp before needing to access the invariant case lookup tables.
this->type = type_none;
if (special.size() == const_strlen(L"normal")) {
if (!simple_icase_compare(name, L"normal")) {
this->type = type_normal;
}
} else if (special.size() == const_strlen(L"reset")) {
if (!simple_icase_compare(name, L"reset")) {
this->type = type_reset;
}
}
return this->type != type_none;
}
static unsigned long squared_difference(long p1, long p2) {
auto diff = static_cast<unsigned long>(labs(p1 - p2));
return diff * diff;
}
static uint8_t convert_color(const uint8_t rgb[3], const uint32_t *colors, size_t color_count) {
long r = rgb[0], g = rgb[1], b = rgb[2];
auto best_distance = static_cast<unsigned long>(-1);
auto best_index = static_cast<uint8_t>(-1);
for (size_t idx = 0; idx < color_count; idx++) {
uint32_t color = colors[idx];
long test_r = (color >> 16) & 0xFF, test_g = (color >> 8) & 0xFF,
test_b = (color >> 0) & 0xFF;
unsigned long distance = squared_difference(r, test_r) + squared_difference(g, test_g) +
squared_difference(b, test_b);
if (distance <= best_distance) {
best_index = idx;
best_distance = distance;
}
}
return best_index;
}
bool rgb_color_t::try_parse_rgb(const wcstring &name) {
std::memset(&data, 0, sizeof data);
// We support the following style of rgb formats (case insensitive):
// #FA3
// #F3A035
// FA3
// F3A035
size_t digit_idx = 0, len = name.size();
// Skip any leading #.
if (len > 0 && name.at(0) == L'#') digit_idx++;
bool success = false;
size_t i;
if (len - digit_idx == 3) {
// Format: FA3
for (i = 0; i < 3; i++) {
int val = convert_digit(name.at(digit_idx++), 16);
if (val < 0) break;
data.color.rgb[i] = val * 16 + val;
}
success = (i == 3);
} else if (len - digit_idx == 6) {
// Format: F3A035
for (i = 0; i < 3; i++) {
int hi = convert_digit(name.at(digit_idx++), 16);
int lo = convert_digit(name.at(digit_idx++), 16);
if (lo < 0 || hi < 0) break;
data.color.rgb[i] = hi * 16 + lo;
}
success = (i == 3);
}
if (success) {
this->type = type_rgb;
}
return success;
}
struct named_color_t {
const wchar_t *name;
uint8_t idx;
uint8_t rgb[3];
bool hidden;
};
// Keep this sorted alphabetically
static constexpr named_color_t named_colors[] = {
{L"black", 0, {0x00, 0x00, 0x00}, false}, {L"blue", 4, {0x00, 0x00, 0x80}, false},
{L"brblack", 8, {0x80, 0x80, 0x80}, false}, {L"brblue", 12, {0x00, 0x00, 0xFF}, false},
{L"brbrown", 11, {0xFF, 0xFF, 0x00}, true}, {L"brcyan", 14, {0x00, 0xFF, 0xFF}, false},
{L"brgreen", 10, {0x00, 0xFF, 0x00}, false}, {L"brgrey", 8, {0x55, 0x55, 0x55}, true},
{L"brmagenta", 13, {0xFF, 0x00, 0xFF}, false}, {L"brown", 3, {0x72, 0x50, 0x00}, true},
{L"brpurple", 13, {0xFF, 0x00, 0xFF}, true}, {L"brred", 9, {0xFF, 0x00, 0x00}, false},
{L"brwhite", 15, {0xFF, 0xFF, 0xFF}, false}, {L"bryellow", 11, {0xFF, 0xFF, 0x00}, false},
{L"cyan", 6, {0x00, 0x80, 0x80}, false}, {L"green", 2, {0x00, 0x80, 0x00}, false},
{L"grey", 7, {0xE5, 0xE5, 0xE5}, true}, {L"magenta", 5, {0x80, 0x00, 0x80}, false},
{L"purple", 5, {0x80, 0x00, 0x80}, true}, {L"red", 1, {0x80, 0x00, 0x00}, false},
{L"white", 7, {0xC0, 0xC0, 0xC0}, false}, {L"yellow", 3, {0x80, 0x80, 0x00}, false},
};
ASSERT_SORTED_BY_NAME(named_colors);
std::vector<wcstring> rgb_color_t::named_color_names() {
std::vector<wcstring> result;
constexpr size_t colors_count = sizeof(named_colors) / sizeof(named_colors[0]);
result.reserve(1 + colors_count);
for (const auto &named_color : named_colors) {
if (!named_color.hidden) {
result.push_back(named_color.name);
}
}
// "normal" isn't really a color and does not have a color palette index or
// RGB value. Therefore, it does not appear in the named_colors table.
// However, it is a legitimate color name for the "set_color" command so
// include it in the publicly known list of colors. This is primarily so it
// appears in the output of "set_color --print-colors".
result.push_back(L"normal");
return result;
}
bool rgb_color_t::try_parse_named(const wcstring &str) {
std::memset(&data, 0, sizeof data);
if (str.empty()) {
return false;
}
// Binary search with simple case-insensitive compares.
auto is_less = [](const named_color_t &s1, const wchar_t *s2) -> bool {
return simple_icase_compare(s1.name, s2) < 0;
};
auto start = std::begin(named_colors);
auto end = std::end(named_colors);
auto where = std::lower_bound(start, end, str.c_str(), is_less);
if (where != end && simple_icase_compare(where->name, str.c_str()) == 0) {
data.name_idx = where->idx;
this->type = type_named;
return true;
}
return false;
}
rgb_color_t::rgb_color_t(uint8_t t, uint8_t i) : type(t), flags(), data() { data.name_idx = i; }
rgb_color_t rgb_color_t::normal() { return rgb_color_t(type_normal); }
rgb_color_t rgb_color_t::reset() { return rgb_color_t(type_reset); }
rgb_color_t rgb_color_t::none() { return rgb_color_t(type_none); }
rgb_color_t rgb_color_t::white() { return rgb_color_t(type_named, 7); }
rgb_color_t rgb_color_t::black() { return rgb_color_t(type_named, 0); }
static uint8_t term16_color_for_rgb(const uint8_t rgb[3]) {
const uint32_t kColors[] = {
0x000000, // Black
0x800000, // Red
0x008000, // Green
0x808000, // Yellow
0x000080, // Blue
0x800080, // Magenta
0x008080, // Cyan
0xc0c0c0, // White
0x808080, // Bright Black
0xFF0000, // Bright Red
0x00FF00, // Bright Green
0xFFFF00, // Bright Yellow
0x0000FF, // Bright Blue
0xFF00FF, // Bright Magenta
0x00FFFF, // Bright Cyan
0xFFFFFF // Bright White
};
return convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
}
static uint8_t term256_color_for_rgb(const uint8_t rgb[3]) {
const uint32_t kColors[240] = {
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87,
0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff,
0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787,
0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff,
0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87,
0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff,
0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787,
0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87,
0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff,
0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787,
0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff,
0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87,
0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff,
0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787,
0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87,
0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff,
0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787,
0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff,
0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87,
0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff,
0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787,
0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858,
0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2,
0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee};
return 16 + convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
}
uint8_t rgb_color_t::to_term256_index() const {
assert(type == type_rgb);
return term256_color_for_rgb(data.color.rgb);
}
color24_t rgb_color_t::to_color24() const {
assert(type == type_rgb);
return data.color;
}
uint8_t rgb_color_t::to_name_index() const {
// TODO: This should look for the nearest color.
assert(type == type_named || type == type_rgb);
if (type == type_named) return data.name_idx;
if (type == type_rgb) return term16_color_for_rgb(data.color.rgb);
return static_cast<uint8_t>(-1); // this is an error
}
void rgb_color_t::parse(const wcstring &str) {
bool success = false;
if (!success) success = try_parse_special(str);
if (!success) success = try_parse_named(str);
if (!success) success = try_parse_rgb(str);
if (!success) {
std::memset(&this->data, 0, sizeof this->data);
this->type = type_none;
}
}
rgb_color_t::rgb_color_t(const wcstring &str) : type(), flags() { this->parse(str); }
rgb_color_t::rgb_color_t(const std::string &str) : type(), flags() {
this->parse(str2wcstring(str));
}

View File

@@ -1,183 +0,0 @@
// Color class.
#ifndef FISH_COLOR_H
#define FISH_COLOR_H
#include <cstdint>
#include <cstring>
#include <string>
#include "common.h"
// 24-bit color.
struct color24_t {
uint8_t rgb[3];
};
/// A type that represents a color. We work hard to keep it at a size of 4 bytes and verify with
/// static_assert
class rgb_color_t {
// Types
enum { type_none, type_named, type_rgb, type_normal, type_reset };
uint8_t type : 3;
// Flags
enum {
flag_bold = 1 << 0,
flag_underline = 1 << 1,
flag_italics = 1 << 2,
flag_dim = 1 << 3,
flag_reverse = 1 << 4
};
uint8_t flags : 5;
union {
uint8_t name_idx; // 0-10
color24_t color;
} data;
/// Try parsing a special color name like "normal".
bool try_parse_special(const wcstring &special);
/// Try parsing an rgb color like "#F0A030".
bool try_parse_rgb(const wcstring &name);
/// Try parsing an explicit color name like "magenta".
bool try_parse_named(const wcstring &str);
/// Parsing entry point.
void parse(const wcstring &str);
/// Private constructor.
explicit rgb_color_t(uint8_t t, uint8_t i = 0);
public:
/// Default constructor of type none.
explicit rgb_color_t() : type(type_none), flags(), data() {}
/// Parse a color from a string.
explicit rgb_color_t(const wcstring &str);
explicit rgb_color_t(const std::string &str);
/// Returns white.
static rgb_color_t white();
/// Returns black.
static rgb_color_t black();
/// Returns the reset special color.
static rgb_color_t reset();
/// Returns the normal special color.
static rgb_color_t normal();
/// Returns the none special color.
static rgb_color_t none();
/// Returns whether the color is the normal special color.
bool is_normal(void) const { return type == type_normal; }
void set_is_named() { type = type_named; }
void set_is_rgb() { type = type_rgb; }
void set_is_normal() { type = type_normal; }
void set_is_reset() { type = type_reset; }
void set_name_idx(uint8_t idx) { data.name_idx = idx; }
void set_color(uint8_t r, uint8_t g, uint8_t b) {
data.color.rgb[0] = r;
data.color.rgb[1] = g;
data.color.rgb[2] = b;
}
/// Returns whether the color is the reset special color.
bool is_reset(void) const { return type == type_reset; }
/// Returns whether the color is the none special color.
bool is_none(void) const { return type == type_none; }
/// Returns whether the color is a named color (like "magenta").
bool is_named(void) const { return type == type_named; }
/// Returns whether the color is specified via RGB components.
bool is_rgb(void) const { return type == type_rgb; }
/// Returns whether the color is special, that is, not rgb or named.
bool is_special(void) const { return type != type_named && type != type_rgb; }
/// Returns the name index for the given color. Requires that the color be named or RGB.
uint8_t to_name_index() const;
/// Returns the term256 index for the given color. Requires that the color be RGB.
uint8_t to_term256_index() const;
/// Returns the 24 bit color for the given color. Requires that the color be RGB.
color24_t to_color24() const;
/// Returns whether the color is bold.
bool is_bold() const { return static_cast<bool>(flags & flag_bold); }
/// Set whether the color is bold.
void set_bold(bool x) {
if (x)
flags |= flag_bold;
else
flags &= ~flag_bold;
}
/// Returns whether the color is underlined.
bool is_underline() const { return static_cast<bool>(flags & flag_underline); }
/// Set whether the color is underlined.
void set_underline(bool x) {
if (x)
flags |= flag_underline;
else
flags &= ~flag_underline;
}
/// Returns whether the color is italics.
bool is_italics() const { return static_cast<bool>(flags & flag_italics); }
/// Set whether the color is italics.
void set_italics(bool x) {
if (x)
flags |= flag_italics;
else
flags &= ~flag_italics;
}
/// Returns whether the color is dim.
bool is_dim() const { return static_cast<bool>(flags & flag_dim); }
/// Set whether the color is dim.
void set_dim(bool x) {
if (x)
flags |= flag_dim;
else
flags &= ~flag_dim;
}
/// Returns whether the color is reverse.
bool is_reverse() const { return static_cast<bool>(flags & flag_reverse); }
/// Set whether the color is reverse.
void set_reverse(bool x) {
if (x)
flags |= flag_reverse;
else
flags &= ~flag_reverse;
}
/// Compare two colors for equality.
bool operator==(const rgb_color_t &other) const {
return type == other.type && !std::memcmp(&data, &other.data, sizeof data);
}
/// Compare two colors for inequality.
bool operator!=(const rgb_color_t &other) const { return !(*this == other); }
/// Returns the names of all named colors.
static std::vector<wcstring> named_color_names(void);
};
static_assert(sizeof(rgb_color_t) <= 4, "rgb_color_t is too big");
#endif

View File

@@ -1,867 +0,0 @@
// Various functions, mostly string utilities, that are used by most parts of fish.
#include "config.h"
#ifdef HAVE_BACKTRACE_SYMBOLS
#include <cxxabi.h>
#endif
#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdarg.h>
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif
#ifdef __linux__
// Includes for WSL detection
#include <sys/utsname.h>
#endif
#include <algorithm>
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <memory>
#include "common.h"
#if INCLUDE_RUST_HEADERS
#include "common.rs.h"
#endif
#include "expand.h"
#include "fallback.h" // IWYU pragma: keep
#include "flog.h"
#include "future_feature_flags.h"
#include "global_safety.h"
#include "iothread.h"
#include "signals.h"
#include "termsize.h"
#include "wcstringutil.h"
#include "wildcard.h"
#include "wutil.h" // IWYU pragma: keep
// Keep after "common.h"
#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h> // IWYU pragma: keep
#endif
#if defined(__APPLE__)
#include <mach-o/dyld.h> // IWYU pragma: keep
#endif
struct termios shell_modes;
const wcstring g_empty_string{};
const std::vector<wcstring> g_empty_string_list{};
/// This allows us to notice when we've forked.
static relaxed_atomic_bool_t is_forked_proc{false};
/// This allows us to bypass the main thread checks
static relaxed_atomic_bool_t thread_asserts_cfg_for_testing{false};
static relaxed_atomic_t<wchar_t> ellipsis_char;
wchar_t get_ellipsis_char() { return ellipsis_char; }
static relaxed_atomic_t<const wchar_t *> ellipsis_str;
const wchar_t *get_ellipsis_str() { return ellipsis_str; }
static relaxed_atomic_t<const wchar_t *> omitted_newline_str;
const wchar_t *get_omitted_newline_str() { return omitted_newline_str; }
static relaxed_atomic_t<int> omitted_newline_width;
int get_omitted_newline_width() { return omitted_newline_width; }
static relaxed_atomic_t<wchar_t> obfuscation_read_char;
wchar_t get_obfuscation_read_char() { return obfuscation_read_char; }
bool g_profiling_active = false;
void set_profiling_active(bool val) { g_profiling_active = val; }
const wchar_t *program_name;
/// Be able to restore the term's foreground process group.
/// This is set during startup and not modified after.
static relaxed_atomic_t<pid_t> initial_fg_process_group{-1};
#if defined(OS_IS_CYGWIN) || defined(WSL)
// MS Windows tty devices do not currently have either a read or write timestamp. Those
// respective fields of `struct stat` are always the current time. Which means we can't
// use them. So we assume no external program has written to the terminal behind our
// back. This makes multiline promptusable. See issue #2859 and
// https://github.com/Microsoft/BashOnWindows/issues/545
const bool has_working_tty_timestamps = false;
#else
const bool has_working_tty_timestamps = true;
#endif
/// Convert a character to its integer equivalent if it is a valid character for the requested base.
/// Return the integer value if it is valid else -1.
long convert_digit(wchar_t d, int base) {
long res = -1;
if ((d <= L'9') && (d >= L'0')) {
res = d - L'0';
} else if ((d <= L'z') && (d >= L'a')) {
res = d + 10 - L'a';
} else if ((d <= L'Z') && (d >= L'A')) {
res = d + 10 - L'A';
}
if (res >= base) {
res = -1;
}
return res;
}
bool is_windows_subsystem_for_linux() {
#if defined(WSL)
return true;
#elif not defined(__linux__)
return false;
#else
// We are purposely not using std::call_once as it may invoke locking, which is an unnecessary
// overhead since there's no actual race condition here - even if multiple threads call this
// routine simultaneously the first time around, we just end up needlessly querying uname(2) one
// more time.
static bool wsl_state = [] {
utsname info;
uname(&info);
// Sample utsname.release under WSL, testing for something like `4.4.0-17763-Microsoft`
if (std::strstr(info.release, "Microsoft") != nullptr) {
const char *dash = std::strchr(info.release, '-');
if (dash == nullptr || strtod(dash + 1, nullptr) < 17763) {
// #5298, #5661: There are acknowledged, published, and (later) fixed issues with
// job control under early WSL releases that prevent fish from running correctly,
// with unexpected failures when piping. Fish 3.0 nightly builds worked around this
// issue with some needlessly complicated code that was later stripped from the
// fish 3.0 release, so we just bail. Note that fish 2.0 was also broken, but we
// just didn't warn about it.
// #6038 & 5101bde: It's been requested that there be some sort of way to disable
// this check: if the environment variable FISH_NO_WSL_CHECK is present, this test
// is bypassed. We intentionally do not include this in the error message because
// it'll only allow fish to run but not to actually work. Here be dragons!
if (getenv("FISH_NO_WSL_CHECK") == nullptr) {
FLOGF(error,
"This version of WSL has known bugs that prevent fish from working."
"Please upgrade to Windows 10 1809 (17763) or higher to use fish!");
}
}
return true;
} else {
return false;
}
}();
// Subsequent calls to this function may take place after fork() and before exec() in
// postfork.cpp. Make sure we never dynamically allocate any memory in the fast path!
return wsl_state;
#endif
}
#ifdef HAVE_BACKTRACE_SYMBOLS
// This function produces a stack backtrace with demangled function & method names. It is based on
// https://gist.github.com/fmela/591333 but adapted to the style of the fish project.
[[gnu::noinline]] static std::vector<wcstring> demangled_backtrace(int max_frames,
int skip_levels) {
void *callstack[128];
const int n_max_frames = sizeof(callstack) / sizeof(callstack[0]);
int n_frames = backtrace(callstack, n_max_frames);
char **symbols = backtrace_symbols(callstack, n_frames);
wchar_t text[1024];
std::vector<wcstring> backtrace_text;
if (skip_levels + max_frames < n_frames) n_frames = skip_levels + max_frames;
for (int i = skip_levels; i < n_frames; i++) {
Dl_info info;
if (dladdr(callstack[i], &info) && info.dli_sname) {
char *demangled = nullptr;
int status = -1;
if (info.dli_sname[0] == '_')
demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
swprintf(text, sizeof(text) / sizeof(wchar_t), L"%-3d %s + %td", i - skip_levels,
status == 0 ? demangled
: info.dli_sname == nullptr ? symbols[i]
: info.dli_sname,
static_cast<char *>(callstack[i]) - static_cast<const char *>(info.dli_saddr));
free(demangled);
} else {
swprintf(text, sizeof(text) / sizeof(wchar_t), L"%-3d %s", i - skip_levels, symbols[i]);
}
backtrace_text.push_back(text);
}
free(symbols);
return backtrace_text;
}
[[gnu::noinline]] void show_stackframe(int frame_count, int skip_levels) {
if (frame_count < 1) return;
std::vector<wcstring> bt = demangled_backtrace(frame_count, skip_levels + 2);
FLOG(error, L"Backtrace:\n" + join_strings(bt, L'\n') + L'\n');
}
#else // HAVE_BACKTRACE_SYMBOLS
[[gnu::noinline]] void show_stackframe(int, int) {
FLOGF(error, L"Sorry, but your system does not support backtraces");
}
#endif // HAVE_BACKTRACE_SYMBOLS
/// \return the smallest pointer in the range [start, start + len] which is aligned to Align.
/// If there is no such pointer, return \p start + len.
/// alignment must be a power of 2 and in range [1, 64].
/// This is intended to return the end point of the "unaligned prefix" of a vectorized loop.
template <size_t Align>
static inline const char *align_start(const char *start, size_t len) {
static_assert(Align >= 1 && Align <= 64, "Alignment must be in range [1, 64]");
static_assert((Align & (Align - 1)) == 0, "Alignment must be power of 2");
uintptr_t startu = reinterpret_cast<uintptr_t>(start);
// How much do we have to add to start to make it 0 mod Align?
// To compute 17 up-aligned by 8, compute its skew 17 % 8, yielding 1,
// and then we will add 8 - 1. Of course if we align 16 with the same idea, we will
// add 8 instead of 0, so then mod the sum by Align again.
// Note all of these mods are optimized to masks.
uintptr_t add_which_aligns = Align - (startu % Align);
add_which_aligns %= Align;
// Add that much but not more than len. If we add 'add_which_aligns' we may overflow the
// pointer.
return start + std::min(static_cast<size_t>(add_which_aligns), len);
}
/// \return the largest pointer in the range [start, start + len] which is aligned to Align.
/// If there is no such pointer, return \p start.
/// This is intended to be the start point of the "unaligned suffix" of a vectorized loop.
template <size_t Align>
static inline const char *align_end(const char *start, size_t len) {
static_assert(Align >= 1 && Align <= 64, "Alignment must be in range [1, 64]");
static_assert((Align & (Align - 1)) == 0, "Alignment must be power of 2");
// How much do we have to subtract to align it? Its value, mod Align.
uintptr_t endu = reinterpret_cast<uintptr_t>(start + len);
uintptr_t sub_which_aligns = endu % Align;
return start + len - std::min(static_cast<size_t>(sub_which_aligns), len);
}
/// \return the count of initial characters in \p in which are ASCII.
static size_t count_ascii_prefix(const char *in, size_t in_len) {
// We'll use aligned reads of this type.
using WordType = uint32_t;
const char *aligned_start = align_start<alignof(WordType)>(in, in_len);
const char *aligned_end = align_end<alignof(WordType)>(in, in_len);
// Consume the unaligned prefix.
for (const char *cursor = in; cursor < aligned_start; cursor++) {
if (cursor[0] & 0x80) return &cursor[0] - in;
}
// Consume the aligned middle.
for (const char *cursor = aligned_start; cursor < aligned_end; cursor += sizeof(WordType)) {
if (*reinterpret_cast<const WordType *>(cursor) & 0x80808080) {
if (cursor[0] & 0x80) return &cursor[0] - in;
if (cursor[1] & 0x80) return &cursor[1] - in;
if (cursor[2] & 0x80) return &cursor[2] - in;
return &cursor[3] - in;
}
}
// Consume the unaligned suffix.
for (const char *cursor = aligned_end; cursor < in + in_len; cursor++) {
if (cursor[0] & 0x80) return &cursor[0] - in;
}
return in_len;
}
/// Converts the narrow character string \c in into its wide equivalent, and return it.
///
/// The string may contain embedded nulls.
///
/// This function encodes illegal character sequences in a reversible way using the private use
/// area.
static wcstring str2wcs_internal(const char *in, const size_t in_len) {
if (in_len == 0) return wcstring();
assert(in != nullptr);
wcstring result;
result.reserve(in_len);
size_t in_pos = 0;
mbstate_t state = {};
while (in_pos < in_len) {
// Append any initial sequence of ascii characters.
// Note we do not support character sets which are not supersets of ASCII.
size_t ascii_prefix_length = count_ascii_prefix(&in[in_pos], in_len - in_pos);
result.insert(result.end(), &in[in_pos], &in[in_pos + ascii_prefix_length]);
in_pos += ascii_prefix_length;
assert(in_pos <= in_len && "Position overflowed length");
if (in_pos == in_len) break;
// We have found a non-ASCII character.
bool use_encode_direct = false;
size_t ret = 0;
wchar_t wc = 0;
if (false) {
#if defined(HAVE_BROKEN_MBRTOWC_UTF8)
} else if ((in[in_pos] & 0xF8) == 0xF8) {
// Protect against broken std::mbrtowc() implementations which attempt to encode UTF-8
// sequences longer than four bytes (e.g., OS X Snow Leopard).
use_encode_direct = true;
#endif
} else if (sizeof(wchar_t) == 2 && //!OCLINT(constant if expression)
(in[in_pos] & 0xF8) == 0xF0) {
// Assume we are in a UTF-16 environment (e.g., Cygwin) using a UTF-8 encoding.
// The bits set check will be true for a four byte UTF-8 sequence that requires
// two UTF-16 chars. Something that doesn't work with our simple use of std::mbrtowc().
use_encode_direct = true;
} else {
ret = std::mbrtowc(&wc, &in[in_pos], in_len - in_pos, &state);
// Determine whether to encode this character with our crazy scheme.
if (fish_reserved_codepoint(wc)) {
use_encode_direct = true;
} else if ((wc >= 0xD800 && wc <= 0xDFFF) || static_cast<uint32_t>(wc) >= 0x110000) {
use_encode_direct = true;
} else if (ret == static_cast<size_t>(-2)) {
// Incomplete sequence.
use_encode_direct = true;
} else if (ret == static_cast<size_t>(-1)) {
// Invalid data.
use_encode_direct = true;
} else if (ret > in_len - in_pos) {
// Other error codes? Terrifying, should never happen.
use_encode_direct = true;
} else if (sizeof(wchar_t) == 2 && wc >= 0xD800 && //!OCLINT(constant if expression)
wc <= 0xDFFF) {
// If we get a surrogate pair char on a UTF-16 system (e.g., Cygwin) then
// it's guaranteed the UTF-8 decoding is wrong so use direct encoding.
use_encode_direct = true;
}
}
if (use_encode_direct) {
wc = ENCODE_DIRECT_BASE + static_cast<unsigned char>(in[in_pos]);
result.push_back(wc);
in_pos++;
std::memset(&state, 0, sizeof state);
} else if (ret == 0) { // embedded null byte!
result.push_back(L'\0');
in_pos++;
std::memset(&state, 0, sizeof state);
} else { // normal case
result.push_back(wc);
in_pos += ret;
}
}
return result;
}
wcstring str2wcstring(const char *in, size_t len) { return str2wcs_internal(in, len); }
wcstring str2wcstring(const char *in) { return str2wcs_internal(in, std::strlen(in)); }
wcstring str2wcstring(const std::string &in) {
// Handles embedded nulls!
return str2wcs_internal(in.data(), in.size());
}
wcstring str2wcstring(const std::string &in, size_t len) {
// Handles embedded nulls!
return str2wcs_internal(in.data(), len);
}
std::string wcs2string(const wcstring &input) { return wcs2string(input.data(), input.size()); }
std::string wcs2string(const wchar_t *in, size_t len) {
if (len == 0) return std::string{};
std::string result;
wcs2string_appending(in, len, &result);
return result;
}
std::string wcs2zstring(const wcstring &input) { return wcs2zstring(input.data(), input.size()); }
std::string wcs2zstring(const wchar_t *in, size_t len) {
if (len == 0) return std::string{};
std::string result;
wcs2string_appending(in, len, &result);
return result;
}
void wcs2string_appending(const wchar_t *in, size_t len, std::string *receiver) {
assert(receiver && "Null receiver");
receiver->reserve(receiver->size() + len);
wcs2string_callback(in, len, [&](const char *buff, size_t bufflen) {
receiver->append(buff, bufflen);
return true;
});
}
/// Test if the character can be encoded using the current locale.
static bool can_be_encoded(wchar_t wc) {
char converted[MB_LEN_MAX];
mbstate_t state = {};
return std::wcrtomb(converted, wc, &state) != static_cast<size_t>(-1);
}
wcstring format_string(const wchar_t *format, ...) {
va_list va;
va_start(va, format);
wcstring result = vformat_string(format, va);
va_end(va);
return result;
}
void append_formatv(wcstring &target, const wchar_t *format, va_list va_orig) {
const int saved_err = errno;
// As far as I know, there is no way to check if a vswprintf-call failed because of a badly
// formated string option or because the supplied destination string was to small. In GLIBC,
// errno seems to be set to EINVAL either way.
//
// Because of this, on failure we try to increase the buffer size until the free space is
// larger than max_size, at which point it will conclude that the error was probably due to a
// badly formated string option, and return an error. Make sure to null terminate string before
// that, though.
const size_t max_size = (128 * 1024 * 1024);
wchar_t static_buff[256];
size_t size = 0;
wchar_t *buff = nullptr;
int status = -1;
while (status < 0) {
// Reallocate if necessary.
if (size == 0) {
buff = static_buff;
size = sizeof static_buff;
} else {
size *= 2;
if (size >= max_size) {
buff[0] = '\0';
break;
}
buff = static_cast<wchar_t *>(realloc((buff == static_buff ? nullptr : buff), size));
assert(buff != nullptr);
}
// Try printing.
va_list va;
va_copy(va, va_orig);
status = std::vswprintf(buff, size / sizeof(wchar_t), format, va);
va_end(va);
}
target.append(buff);
if (buff != static_buff) {
free(buff);
}
errno = saved_err;
}
wcstring vformat_string(const wchar_t *format, va_list va_orig) {
wcstring result;
append_formatv(result, format, va_orig);
return result;
}
void append_format(wcstring &str, const wchar_t *format, ...) {
va_list va;
va_start(va, format);
append_formatv(str, format, va);
va_end(va);
}
const wchar_t *quote_end(const wchar_t *pos, wchar_t quote) {
while (true) {
pos++;
if (!*pos) return nullptr;
if (*pos == L'\\') {
pos++;
if (!*pos) return nullptr;
} else {
if (*pos == quote ||
// Command substitutions also end a double quoted string. This is how we
// support command substitutions inside double quotes.
(quote == L'"' && *pos == L'$' && *(pos + 1) == L'(')) {
return pos;
}
}
}
return nullptr;
}
const wchar_t *comment_end(const wchar_t *pos) {
do {
pos++;
} while (*pos && *pos != L'\n');
return pos;
}
void fish_setlocale() {
// Use various Unicode symbols if they can be encoded using the current locale, else a simple
// ASCII char alternative. All of the can_be_encoded() invocations should return the same
// true/false value since the code points are in the BMP but we're going to be paranoid. This
// is also technically wrong if we're not in a Unicode locale but we expect (or hope)
// can_be_encoded() will return false in that case.
if (can_be_encoded(L'\u2026')) {
ellipsis_char = L'\u2026';
ellipsis_str = L"\u2026";
} else {
ellipsis_char = L'$'; // "horizontal ellipsis"
ellipsis_str = L"...";
}
if (is_windows_subsystem_for_linux()) {
// neither of \u23CE and \u25CF can be displayed in the default fonts on Windows, though
// they can be *encoded* just fine. Use alternative glyphs.
omitted_newline_str = L"\u00b6"; // "pilcrow"
omitted_newline_width = 1;
obfuscation_read_char = L'\u2022'; // "bullet"
} else if (is_console_session()) {
omitted_newline_str = L"^J";
omitted_newline_width = 2;
obfuscation_read_char = L'*';
} else {
if (can_be_encoded(L'\u23CE')) {
omitted_newline_str = L"\u23CE"; // "return symbol" (⏎)
omitted_newline_width = 1;
} else {
omitted_newline_str = L"^J";
omitted_newline_width = 2;
}
obfuscation_read_char = can_be_encoded(L'\u25CF') ? L'\u25CF' : L'#'; // "black circle"
}
}
long read_blocked(int fd, void *buf, size_t count) {
ssize_t res;
do {
res = read(fd, buf, count);
} while (res < 0 && errno == EINTR);
return res;
}
/// Loop a write request while failure is non-critical. Return -1 and set errno in case of critical
/// error.
ssize_t write_loop(int fd, const char *buff, size_t count) {
size_t out_cum = 0;
while (out_cum < count) {
ssize_t out = write(fd, &buff[out_cum], count - out_cum);
if (out < 0) {
if (errno != EAGAIN && errno != EINTR) {
return -1;
}
} else {
out_cum += static_cast<size_t>(out);
}
}
return static_cast<ssize_t>(out_cum);
}
/// Hack to not print error messages in the tests. Do not call this from functions in this module
/// like `debug()`. It is only intended to suppress diagnostic noise from testing things like the
/// fish parser where we expect a lot of diagnostic messages due to testing error conditions.
bool should_suppress_stderr_for_tests() {
return program_name && !std::wcscmp(program_name, TESTS_PROGRAM_NAME);
}
// Careful to not negate LLONG_MIN.
static unsigned long long absolute_value(long long x) {
if (x >= 0) return static_cast<unsigned long long>(x);
x = -(x + 1);
return static_cast<unsigned long long>(x) + 1;
}
template <typename CharT>
static void format_safe_impl(CharT *buff, size_t size, unsigned long long val) {
size_t idx = 0;
if (val == 0) {
buff[idx++] = '0';
} else {
// Generate the string backwards, then reverse it.
while (val != 0) {
buff[idx++] = (val % 10) + '0';
val /= 10;
}
std::reverse(buff, buff + idx);
}
buff[idx++] = '\0';
assert(idx <= size && "Buffer overflowed");
}
void format_long_safe(char buff[64], long val) {
unsigned long long uval = absolute_value(val);
if (val >= 0) {
format_safe_impl(buff, 64, uval);
} else {
buff[0] = '-';
format_safe_impl(buff + 1, 63, uval);
}
}
void format_long_safe(wchar_t buff[64], long val) {
unsigned long long uval = absolute_value(val);
if (val >= 0) {
format_safe_impl(buff, 64, uval);
} else {
buff[0] = '-';
format_safe_impl(buff + 1, 63, uval);
}
}
void format_llong_safe(wchar_t buff[64], long long val) {
unsigned long long uval = absolute_value(val);
if (val >= 0) {
format_safe_impl(buff, 64, uval);
} else {
buff[0] = '-';
format_safe_impl(buff + 1, 63, uval);
}
}
void format_ullong_safe(wchar_t buff[64], unsigned long long val) {
return format_safe_impl(buff, 64, val);
}
/// Escape a string in a fashion suitable for using as a URL. Store the result in out_str.
static void escape_string_url(const wcstring &in, wcstring &out) {
auto result = escape_string_url(in.c_str(), in.size());
if (result) {
out = *result;
}
}
/// Escape a string in a fashion suitable for using as a fish var name. Store the result in out_str.
static void escape_string_var(const wcstring &in, wcstring &out) {
auto result = escape_string_var(in.c_str(), in.size());
if (result) {
out = *result;
}
}
/// Escape a string in a fashion suitable for using in fish script. Store the result in out_str.
static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring &out,
escape_flags_t flags) {
auto result = escape_string_script(orig_in, in_len, flags);
if (result) {
out = *result;
}
}
/// Escapes a string for use in a regex string. Not safe for use with `eval` as only
/// characters reserved by PCRE2 are escaped.
/// \param in is the raw string to be searched for literally when substituted in a PCRE2 expression.
static wcstring escape_string_pcre2(const wcstring &in) {
wcstring out;
out.reserve(in.size() * 1.3); // a wild guess
for (auto c : in) {
switch (c) {
case L'.':
case L'^':
case L'$':
case L'*':
case L'+':
case L'(':
case L')':
case L'?':
case L'[':
case L'{':
case L'}':
case L'\\':
case L'|':
// these two only *need* to be escaped within a character class, and technically it
// makes no sense to ever use process substitution output to compose a character class,
// but...
case L'-':
case L']':
out.push_back('\\');
__fallthrough__ default : out.push_back(c);
}
}
return out;
}
wcstring escape_string(const wchar_t *in, escape_flags_t flags, escape_string_style_t style) {
wcstring result;
switch (style) {
case STRING_STYLE_SCRIPT: {
escape_string_script(in, std::wcslen(in), result, flags);
break;
}
case STRING_STYLE_URL: {
escape_string_url(in, result);
break;
}
case STRING_STYLE_VAR: {
escape_string_var(in, result);
break;
}
case STRING_STYLE_REGEX: {
result = escape_string_pcre2(in);
break;
}
}
return result;
}
wcstring escape_string(const wcstring &in, escape_flags_t flags, escape_string_style_t style) {
wcstring result;
switch (style) {
case STRING_STYLE_SCRIPT: {
escape_string_script(in.c_str(), in.size(), result, flags);
break;
}
case STRING_STYLE_URL: {
escape_string_url(in, result);
break;
}
case STRING_STYLE_VAR: {
escape_string_var(in, result);
break;
}
case STRING_STYLE_REGEX: {
result = escape_string_pcre2(in);
break;
}
}
return result;
}
double timef() {
struct timeval tv;
assert_with_errno(gettimeofday(&tv, nullptr) != -1);
return static_cast<timepoint_t>(tv.tv_sec) + 1e-6 * tv.tv_usec;
}
void exit_without_destructors(int code) { _exit(code); }
extern "C" void debug_thread_error();
/// Test if the specified character is in a range that fish uses internally to store special tokens.
///
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
/// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted
/// string. We don't want external input to be able to feed reserved characters into our
/// lexer/parser or code evaluator.
//
// TODO: Actually implement the replacement as documented above.
bool fish_reserved_codepoint(wchar_t c) {
return (c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END) ||
(c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END);
}
/// Reopen stdin, stdout and/or stderr on /dev/null. This is invoked when we find that our tty has
/// become invalid.
void redirect_tty_output() {
struct termios t;
int fd = open("/dev/null", O_WRONLY);
if (fd == -1) {
__fish_assert("Could not open /dev/null!", __FILE__, __LINE__, errno);
}
if (tcgetattr(STDIN_FILENO, &t) == -1 && errno == EIO) dup2(fd, STDIN_FILENO);
if (tcgetattr(STDOUT_FILENO, &t) == -1 && errno == EIO) dup2(fd, STDOUT_FILENO);
if (tcgetattr(STDERR_FILENO, &t) == -1 && errno == EIO) dup2(fd, STDERR_FILENO);
close(fd);
}
/// Display a failed assertion message, dump a stack trace if possible, then die.
[[noreturn]] void __fish_assert(const char *msg, const char *file, size_t line, int error) {
if (unlikely(error)) {
FLOGF(error, L"%s:%zu: failed assertion: %s: errno %d (%s)", file, line, msg, error,
std::strerror(error));
} else {
FLOGF(error, L"%s:%zu: failed assertion: %s", file, line, msg);
}
show_stackframe(99, 1);
abort();
}
/// Test if the given char is valid in a variable name.
bool valid_var_name_char(wchar_t chr) { return fish_iswalnum(chr) || chr == L'_'; }
/// Test if the given string is a valid variable name.
bool valid_var_name(const wcstring &str) {
// Note do not use c_str(), we want to fail on embedded nul bytes.
return !str.empty() && std::all_of(str.begin(), str.end(), valid_var_name_char);
}
bool valid_var_name(const wchar_t *str) {
if (str[0] == L'\0') return false;
for (size_t i = 0; str[i] != L'\0'; i++) {
if (!valid_var_name_char(str[i])) return false;
}
return true;
}
/// Return a path to a directory where we can store temporary files.
std::string get_path_to_tmp_dir() {
char *env_tmpdir = getenv("TMPDIR");
if (env_tmpdir) {
return env_tmpdir;
}
#if defined(_CS_DARWIN_USER_TEMP_DIR)
char osx_tmpdir[PATH_MAX];
size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, osx_tmpdir, PATH_MAX);
if (0 < n && n <= PATH_MAX) {
return osx_tmpdir;
} else {
return "/tmp";
}
#elif defined(P_tmpdir)
return P_tmpdir;
#elif defined(_PATH_TMP)
return _PATH_TMP;
#else
return "/tmp";
#endif
}
// This function attempts to distinguish between a console session (at the actual login vty) and a
// session within a terminal emulator inside a desktop environment or over SSH. Unfortunately
// there are few values of $TERM that we can interpret as being exclusively console sessions, and
// most common operating systems do not use them. The value is cached for the duration of the fish
// session. We err on the side of assuming it's not a console session. This approach isn't
// bullet-proof and that's OK.
bool is_console_session() {
static const bool console_session = [] {
char tty_name[PATH_MAX];
if (ttyname_r(STDIN_FILENO, tty_name, sizeof tty_name) != 0) {
return false;
}
constexpr auto len = const_strlen("/dev/tty");
const char *TERM = getenv("TERM");
return
// Test that the tty matches /dev/(console|dcons|tty[uv\d])
((strncmp(tty_name, "/dev/tty", len) == 0 &&
(tty_name[len] == 'u' || tty_name[len] == 'v' || isdigit(tty_name[len]))) ||
strcmp(tty_name, "/dev/dcons") == 0 || strcmp(tty_name, "/dev/console") == 0)
// and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something`
&& (!TERM || !strchr(TERM, '-') || !strcmp(TERM, "sun-color"));
}();
return console_session;
}
/// Expose the C++ version of fish_setlocale as fish_setlocale_ffi so the variables we initialize
/// can be init even if the rust version of the function is called instead. This is easier than
/// declaring all those variables as extern, which I'll do in a separate PR.
extern "C" {
void fish_setlocale_ffi() { fish_setlocale(); }
}

View File

@@ -25,413 +25,6 @@
#include <utility>
#include <vector>
#include "fallback.h" // IWYU pragma: keep
#include "maybe.h"
// Create a generic define for all BSD platforms
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#define __BSD__
#endif
// PATH_MAX may not exist.
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
// Define a symbol we can use elsewhere in our code to determine if we're being built on MS Windows
// under Cygwin.
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32) || defined(__CYGWIN__) || \
defined(__WIN32__)
#define OS_IS_CYGWIN
#endif
// Check if Thread Sanitizer is enabled.
#if defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define FISH_TSAN_WORKAROUNDS 1
#endif
#endif
#ifdef __SANITIZE_THREAD__
#define FISH_TSAN_WORKAROUNDS 1
#endif
// Common string type.
typedef std::wstring wcstring;
struct termsize_t;
// Highest legal ASCII value.
#define ASCII_MAX 127u
// Highest legal 16-bit Unicode value.
#define UCS2_MAX 0xFFFFu
// Highest legal byte value.
#define BYTE_MAX 0xFFu
// Unicode BOM value.
#define UTF8_BOM_WCHAR 0xFEFFu
// Use Unicode "non-characters" for internal characters as much as we can. This
// gives us 32 "characters" for internal use that we can guarantee should not
// appear in our input stream. See http://www.unicode.org/faq/private_use.html.
#define RESERVED_CHAR_BASE static_cast<wchar_t>(0xFDD0)
#define RESERVED_CHAR_END static_cast<wchar_t>(0xFDF0)
// Split the available non-character values into two ranges to ensure there are
// no conflicts among the places we use these special characters.
#define EXPAND_RESERVED_BASE RESERVED_CHAR_BASE
#define EXPAND_RESERVED_END (EXPAND_RESERVED_BASE + 16)
#define WILDCARD_RESERVED_BASE EXPAND_RESERVED_END
#define WILDCARD_RESERVED_END (WILDCARD_RESERVED_BASE + 16)
// Make sure the ranges defined above don't exceed the range for non-characters.
// This is to make sure we didn't do something stupid in subdividing the
// Unicode range for our needs.
// #if WILDCARD_RESERVED_END > RESERVED_CHAR_END
// #error
// #endif
// These are in the Unicode private-use range. We really shouldn't use this
// range but have little choice in the matter given how our lexer/parser works.
// We can't use non-characters for these two ranges because there are only 66 of
// them and we need at least 256 + 64.
//
// If sizeof(wchar_t))==4 we could avoid using private-use chars; however, that
// would result in fish having different behavior on machines with 16 versus 32
// bit wchar_t. It's better that fish behave the same on both types of systems.
//
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
#define ENCODE_DIRECT_BASE static_cast<wchar_t>(0xF600)
#define ENCODE_DIRECT_END (ENCODE_DIRECT_BASE + 256)
// NAME_MAX is not defined on Solaris
#if !defined(NAME_MAX)
#include <sys/param.h>
#if defined(MAXNAMELEN)
// MAXNAMELEN is defined on Linux, BSD, and Solaris among others
#define NAME_MAX MAXNAMELEN
#else
static_assert(false, "Neither NAME_MAX nor MAXNAMELEN is defined!");
#endif
#endif
// PATH_MAX may not exist.
#ifndef PATH_MAX
#ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN
#else
/// Fallback length of MAXPATHLEN. Hopefully a sane value.
#define PATH_MAX 4096
#endif
#endif
enum escape_string_style_t {
STRING_STYLE_SCRIPT,
STRING_STYLE_URL,
STRING_STYLE_VAR,
STRING_STYLE_REGEX,
};
// Flags for unescape_string functions.
enum {
UNESCAPE_DEFAULT = 0, // default behavior
UNESCAPE_SPECIAL = 1 << 0, // escape special fish syntax characters like the semicolon
UNESCAPE_INCOMPLETE = 1 << 1, // allow incomplete escape sequences
UNESCAPE_NO_BACKSLASHES = 1 << 2, // don't handle backslash escapes
};
typedef unsigned int unescape_flags_t;
// Flags for the escape_string() function. These are only applicable when the escape style is
// "script" (i.e., STRING_STYLE_SCRIPT).
enum {
/// Do not escape special fish syntax characters like the semicolon. Only escape non-printable
/// characters and backslashes.
ESCAPE_NO_PRINTABLES = 1 << 0,
/// Do not try to use 'simplified' quoted escapes, and do not use empty quotes as the empty
/// string.
ESCAPE_NO_QUOTED = 1 << 1,
/// Do not escape tildes.
ESCAPE_NO_TILDE = 1 << 2,
/// Replace non-printable control characters with Unicode symbols.
ESCAPE_SYMBOLIC = 1 << 3
};
typedef unsigned int escape_flags_t;
/// A user-visible job ID.
using job_id_t = int;
/// The non user-visible, never-recycled job ID.
/// Every job has a unique positive value for this.
using internal_job_id_t = uint64_t;
/// Exits without invoking destructors (via _exit), useful for code after fork.
[[noreturn]] void exit_without_destructors(int code);
/// Save the shell mode on startup so we can restore them on exit.
extern struct termios shell_modes;
/// The character to use where the text has been truncated. Is an ellipsis on unicode system and a $
/// on other systems.
wchar_t get_ellipsis_char();
/// The character or string to use where text has been truncated (ellipsis if possible, otherwise
/// ...)
const wchar_t *get_ellipsis_str();
/// Character representing an omitted newline at the end of text.
const wchar_t *get_omitted_newline_str();
int get_omitted_newline_width();
/// Character used for the silent mode of the read command
wchar_t get_obfuscation_read_char();
/// Profiling flag. True if commands should be profiled.
extern bool g_profiling_active;
void set_profiling_active(bool val);
/// Name of the current program. Should be set at startup. Used by the debug function.
extern const wchar_t *program_name;
/// Set to false if it's been determined we can't trust the last modified timestamp on the tty.
extern const bool has_working_tty_timestamps;
/// A global, empty string. This is useful for functions which wish to return a reference to an
/// empty string.
extern const wcstring g_empty_string;
/// A global, empty std::vector<wcstring>. This is useful for functions which wish to return a
/// reference to an empty string.
extern const std::vector<wcstring> g_empty_string_list;
// Pause for input, then exit the program. If supported, print a backtrace first.
#define FATAL_EXIT() \
do { \
char exit_read_buff; \
show_stackframe(); \
ignore_result(read(0, &exit_read_buff, 1)); \
exit_without_destructors(1); \
} while (0)
/// Exit the program at once after emitting an error message and stack trace if possible.
/// We use our own private implementation of `assert()` for two reasons. First, some implementations
/// are subtly broken. For example, using `printf()` which can cause problems when mixed with wide
/// stdio functions and should be writing the message to stderr rather than stdout. Second, if
/// possible it is useful to provide additional context such as a stack backtrace.
#undef assert
#define assert(e) likely(e) ? ((void)0) : __fish_assert(#e, __FILE__, __LINE__, 0)
#define assert_with_errno(e) likely(e) ? ((void)0) : __fish_assert(#e, __FILE__, __LINE__, errno)
#define DIE(msg) __fish_assert(msg, __FILE__, __LINE__, 0)
#define DIE_WITH_ERRNO(msg) __fish_assert(msg, __FILE__, __LINE__, errno)
/// This macro is meant to be used with functions that return zero on success otherwise return an
/// errno value. Most notably the pthread family of functions which we never expect to fail.
#define DIE_ON_FAILURE(e) \
do { \
int status = e; \
if (unlikely(status != 0)) { \
__fish_assert(#e, __FILE__, __LINE__, status); \
} \
} while (0)
[[noreturn]] void __fish_assert(const char *msg, const char *file, size_t line, int error);
/// Shorthand for wgettext call in situations where a C-style string is needed (e.g.,
/// std::fwprintf()).
#define _(wstr) wgettext(wstr).c_str()
/// Noop, used to tell xgettext that a string should be translated. Use this when a string cannot be
/// passed through wgettext() at the point where it is used. For example, when initializing a
/// static array or structure. You must pass the string through wgettext() when it is used.
/// See https://developer.gnome.org/glib/stable/glib-I18N.html#N-:CAPS
#define N_(wstr) wstr
/// An empty struct which may be embedded (or inherited from) to prevent copying.
struct [[gnu::unused]] noncopyable_t {
noncopyable_t() = default;
noncopyable_t(noncopyable_t &&) = default;
noncopyable_t &operator=(noncopyable_t &&) = default;
noncopyable_t(const noncopyable_t &) = delete;
noncopyable_t &operator=(const noncopyable_t &) = delete;
};
struct [[gnu::unused]] nonmovable_t {
nonmovable_t() = default;
nonmovable_t(nonmovable_t &&) = delete;
nonmovable_t &operator=(nonmovable_t &&) = delete;
};
/// Test if a collection contains a value.
template <typename Col, typename T2>
bool contains(const Col &col, const T2 &val) {
return std::find(std::begin(col), std::end(col), val) != std::end(col);
}
template <typename T1, typename T2>
bool contains(std::initializer_list<T1> col, const T2 &val) {
return std::find(std::begin(col), std::end(col), val) != std::end(col);
}
/// Append a vector \p donator to the vector \p receiver.
template <typename T>
void vec_append(std::vector<T> &receiver, std::vector<T> &&donator) {
if (receiver.empty()) {
receiver = std::move(donator);
} else {
receiver.insert(receiver.end(), std::make_move_iterator(donator.begin()),
std::make_move_iterator(donator.end()));
}
}
/// A function type to check for cancellation.
/// \return true if execution should cancel.
using cancel_checker_t = std::function<bool()>;
/// Print a stack trace to stderr.
void show_stackframe(int frame_count = 100, int skip_levels = 0);
/// Returns a wide character string equivalent of the specified multibyte character string.
///
/// This function encodes illegal character sequences in a reversible way using the private use
/// area.
wcstring str2wcstring(const std::string &in);
wcstring str2wcstring(const std::string &in, size_t len);
wcstring str2wcstring(const char *in);
wcstring str2wcstring(const char *in, size_t len);
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
/// string.
///
/// This function decodes illegal character sequences in a reversible way using the private use
/// area.
std::string wcs2string(const wcstring &input);
std::string wcs2string(const wchar_t *in, size_t len);
/// Same as wcs2string. Meant to be used when we need a zero-terminated string to feed legacy APIs.
std::string wcs2zstring(const wcstring &input);
std::string wcs2zstring(const wchar_t *in, size_t len);
/// Like wcs2string, but appends to \p receiver instead of returning a new string.
void wcs2string_appending(const wchar_t *in, size_t len, std::string *receiver);
// Check if we are running in the test mode, where we should suppress error output
#define TESTS_PROGRAM_NAME L"(ignore)"
bool should_suppress_stderr_for_tests();
/// Branch prediction hints. Idea borrowed from Linux kernel. Just used for asserts.
#define likely(x) __builtin_expect(bool(x), 1)
#define unlikely(x) __builtin_expect(bool(x), 0)
/// Writes out a long safely.
void format_long_safe(char buff[64], long val);
void format_long_safe(wchar_t buff[64], long val);
void format_llong_safe(wchar_t buff[64], long long val);
void format_ullong_safe(wchar_t buff[64], unsigned long long val);
/// Stored in blocks to reference the file which created the block.
using filename_ref_t = std::shared_ptr<wcstring>;
using scoped_lock = std::lock_guard<std::mutex>;
// An object wrapping a scoped lock and a value
// This is returned from owning_lock.acquire()
// Sample usage:
// owning_lock<string> locked_name;
// acquired_lock<string> name = name.acquire();
// name.value = "derp"
//
// Or for simple cases:
// name.acquire().value = "derp"
//
template <typename Data>
class acquired_lock : noncopyable_t {
template <typename T>
friend class owning_lock;
template <typename T>
friend class acquired_lock;
acquired_lock(std::mutex &lk, Data *v) : lock(lk), value(v) {}
acquired_lock(std::unique_lock<std::mutex> &&lk, Data *v) : lock(std::move(lk)), value(v) {}
std::unique_lock<std::mutex> lock;
Data *value;
public:
Data *operator->() { return value; }
const Data *operator->() const { return value; }
Data &operator*() { return *value; }
const Data &operator*() const { return *value; }
/// Implicit conversion to const version.
operator acquired_lock<const Data>() {
// We're about to give up our lock, don't hold onto the data.
const Data *cvalue = value;
value = nullptr;
return acquired_lock<const Data>(std::move(lock), cvalue);
}
/// Create from a global lock.
/// This is used in weird cases where a global lock protects more than one piece of data.
static acquired_lock from_global(std::mutex &lk, Data *v) { return acquired_lock{lk, v}; }
/// \return a reference to the lock, for use with a condition variable.
std::unique_lock<std::mutex> &get_lock() { return lock; }
};
// A lock that owns a piece of data
// Access to the data is only provided by taking the lock
template <typename Data>
class owning_lock {
// No copying
owning_lock &operator=(const scoped_lock &) = delete;
owning_lock(const scoped_lock &) = delete;
owning_lock(owning_lock &&) = default;
owning_lock &operator=(owning_lock &&) = default;
std::mutex lock;
Data data;
public:
owning_lock(Data &&d) : data(std::move(d)) {}
owning_lock(const Data &d) : data(d) {}
owning_lock() : data() {}
acquired_lock<Data> acquire() { return {lock, &data}; }
};
/// A scoped manager to save the current value of some variable, and optionally set it to a new
/// value. On destruction it restores the variable to its old value.
///
/// This can be handy when there are multiple code paths to exit a block.
template <typename T>
class scoped_push {
T *const ref;
T saved_value;
bool restored;
public:
explicit scoped_push(T *r) : ref(r), saved_value(*r), restored(false) {}
scoped_push(T *r, T new_value) : ref(r), restored(false) {
saved_value = std::move(*ref);
*ref = std::move(new_value);
}
~scoped_push() { restore(); }
void restore() {
if (!restored) {
*ref = std::move(saved_value);
restored = true;
}
}
};
wcstring format_string(const wchar_t *format, ...);
wcstring vformat_string(const wchar_t *format, va_list va_orig);
void append_format(wcstring &str, const wchar_t *format, ...);
void append_formatv(wcstring &target, const wchar_t *format, va_list va_orig);
#ifndef HAVE_STD__MAKE_UNIQUE
/// make_unique implementation
namespace std {
@@ -443,213 +36,4 @@ std::unique_ptr<T> make_unique(Args &&...args) {
#endif
using std::make_unique;
/// This functions returns the end of the quoted substring beginning at \c pos. Returns 0 on error.
///
/// \param pos the position of the opening quote.
/// \param quote the quote to use, usually pointed to by \c pos.
const wchar_t *quote_end(const wchar_t *pos, wchar_t quote);
/// This functions returns the end of the comment substring beginning at \c pos.
///
/// \param pos the position where the comment starts, including the '#' symbol.
const wchar_t *comment_end(const wchar_t *pos);
/// This function should be called after calling `setlocale()` to perform fish specific locale
/// initialization.
void fish_setlocale();
/// Call read, blocking and repeating on EINTR. Exits on EAGAIN.
/// \return the number of bytes read, or 0 on EOF. On EAGAIN, returns -1 if nothing was read.
long read_blocked(int fd, void *buf, size_t count);
/// Loop a write request while failure is non-critical. Return -1 and set errno in case of critical
/// error.
ssize_t write_loop(int fd, const char *buff, size_t count);
/// Replace special characters with backslash escape sequences. Newline is replaced with \n, etc.
///
/// \param in The string to be escaped
/// \param flags Flags to control the escaping
/// \return The escaped string
wcstring escape_string(const wchar_t *in, escape_flags_t flags = 0,
escape_string_style_t style = STRING_STYLE_SCRIPT);
wcstring escape_string(const wcstring &in, escape_flags_t flags = 0,
escape_string_style_t style = STRING_STYLE_SCRIPT);
/// Expand backslashed escapes and substitute them with their unescaped counterparts. Also
/// optionally change the wildcards, the tilde character and a few more into constants which are
/// defined in a private use area of Unicode. This assumes wchar_t is a unicode character set.
/// Return the number of seconds from the UNIX epoch, with subsecond precision. This function uses
/// the gettimeofday function and will have the same precision as that function.
using timepoint_t = double;
timepoint_t timef();
/// Determines if we are running under Microsoft's Windows Subsystem for Linux to work around
/// some known limitations and/or bugs.
/// See https://github.com/Microsoft/WSL/issues/423 and Microsoft/WSL#2997
bool is_windows_subsystem_for_linux();
/// Detect if we are running under Cygwin or Cygwin64
constexpr bool is_cygwin() {
#ifdef __CYGWIN__
return true;
#else
return false;
#endif
}
extern "C" {
[[gnu::noinline]] void debug_thread_error(void);
}
/// Converts from wide char to digit in the specified base. If d is not a valid digit in the
/// specified base, return -1.
long convert_digit(wchar_t d, int base);
/// This is a macro that can be used to silence "unused parameter" warnings from the compiler for
/// functions which need to accept parameters they do not use because they need to be compatible
/// with an interface. It's similar to the Python idiom of doing `_ = expr` at the top of a
/// function in the same situation.
#define UNUSED(expr) \
do { \
(void)(expr); \
} while (0)
// Return true if the character is in a range reserved for fish's private use.
bool fish_reserved_codepoint(wchar_t c);
void redirect_tty_output();
std::string get_path_to_tmp_dir();
bool valid_var_name_char(wchar_t chr);
bool valid_var_name(const wcstring &str);
bool valid_var_name(const wchar_t *str);
// Return values (`$status` values for fish scripts) for various situations.
enum {
/// The status code used for normal exit in a command.
STATUS_CMD_OK = 0,
/// The status code used for failure exit in a command (but not if the args were invalid).
STATUS_CMD_ERROR = 1,
/// The status code used for invalid arguments given to a command. This is distinct from valid
/// arguments that might result in a command failure. An invalid args condition is something
/// like an unrecognized flag, missing or too many arguments, an invalid integer, etc. But
STATUS_INVALID_ARGS = 2,
/// The status code used when a command was not found.
STATUS_CMD_UNKNOWN = 127,
/// The status code used when an external command can not be run.
STATUS_NOT_EXECUTABLE = 126,
/// The status code used when a wildcard had no matches.
STATUS_UNMATCHED_WILDCARD = 124,
/// The status code used when illegal command name is encountered.
STATUS_ILLEGAL_CMD = 123,
/// The status code used when `read` is asked to consume too much data.
STATUS_READ_TOO_MUCH = 122,
/// The status code when an expansion fails, for example, "$foo["
STATUS_EXPAND_ERROR = 121,
};
/* Normally casting an expression to void discards its value, but GCC
versions 3.4 and newer have __attribute__ ((__warn_unused_result__))
which may cause unwanted diagnostics in that case. Use __typeof__
and __extension__ to work around the problem, if the workaround is
known to be needed. */
#if 3 < __GNUC__ + (4 <= __GNUC_MINOR__)
#define ignore_result(x) \
(__extension__({ \
__typeof__(x) __x = (x); \
(void)__x; \
}))
#else
#define ignore_result(x) ((void)(x))
#endif
// Custom hash function used by unordered_map/unordered_set when key is const
#ifndef CONST_WCSTRING_HASH
#define CONST_WCSTRING_HASH 1
namespace std {
template <>
struct hash<const wcstring> {
std::size_t operator()(const wcstring &w) const {
std::hash<wcstring> hasher;
return hasher(w);
}
};
} // namespace std
#endif
/// A RAII wrapper for resources that don't recur, so we don't have to create a separate RAII
/// wrapper for each function. Avoids needing to call "return cleanup()" or similar / everywhere.
struct cleanup_t {
private:
const std::function<void()> cleanup;
public:
cleanup_t(std::function<void()> exit_actions) : cleanup{std::move(exit_actions)} {}
~cleanup_t() { cleanup(); }
};
bool is_console_session();
/// Compile-time agnostic-size strcmp/wcscmp implementation. Unicode-unaware.
template <typename T>
constexpr ssize_t const_strcmp(const T *lhs, const T *rhs) {
return (*lhs == *rhs) ? (*lhs == 0 ? 0 : const_strcmp(lhs + 1, rhs + 1))
: (*lhs > *rhs ? 1 : -1);
}
/// Compile-time agnostic-size strlen/wcslen implementation. Unicode-unaware.
template <typename T, size_t N>
constexpr size_t const_strlen(const T (&val)[N], size_t last_checked_idx = N,
size_t first_nul_idx = N) {
// Assume there's a nul char at the end (index N) but there may be one before that that.
return last_checked_idx == 0
? first_nul_idx
: const_strlen(val, last_checked_idx - 1,
val[last_checked_idx - 1] ? first_nul_idx : last_checked_idx - 1);
}
/// \return true if the array \p vals is sorted by its name property.
template <typename T, size_t N>
constexpr bool is_sorted_by_name(const T (&vals)[N], size_t idx = 1) {
return idx >= N ? true
: (const_strcmp(vals[idx - 1].name, vals[idx].name) <= 0 &&
is_sorted_by_name(vals, idx + 1));
}
#define ASSERT_SORTED_BY_NAME(x) static_assert(is_sorted_by_name(x), #x " not sorted by name")
/// \return a pointer to the first entry with the given name, assuming the entries are sorted by
/// name. \return nullptr if not found.
template <typename T, size_t N>
const T *get_by_sorted_name(const wchar_t *name, const T (&vals)[N]) {
assert(name && "Null name");
auto is_less = [](const T &v, const wchar_t *n) -> bool { return std::wcscmp(v.name, n) < 0; };
auto where = std::lower_bound(std::begin(vals), std::end(vals), name, is_less);
if (where != std::end(vals) && std::wcscmp(where->name, name) == 0) {
return &*where;
}
return nullptr;
}
template <typename T, size_t N>
const T *get_by_sorted_name(const wcstring &name, const T (&vals)[N]) {
return get_by_sorted_name(name.c_str(), vals);
}
/// As established in 1ab81ab90d1a408702e11f081fdaaafa30636c31, iswdigit() is very slow under glibc,
/// and does nothing more than establish whether or not the single specified character is in the
/// range ('0','9').
__attribute__((always_inline)) bool inline iswdigit(const wchar_t c) {
return c >= L'0' && c <= L'9';
}
#if INCLUDE_RUST_HEADERS
#include "common.rs.h"
#endif
#endif // FISH_COMMON_H

View File

@@ -1,67 +0,0 @@
/// Prototypes for functions related to tab-completion.
///
/// These functions are used for storing and retrieving tab-completion data, as well as for
/// performing tab-completion.
#ifndef FISH_COMPLETE_H
#define FISH_COMPLETE_H
#include "config.h" // IWYU pragma: keep
#include <cstddef>
#include <cstdint>
#include <functional>
#include <utility>
#include <vector>
#include "common.h"
#include "expand.h"
#include "parser.h"
#include "wcstringutil.h"
struct completion_mode_t {
/// If set, skip file completions.
bool no_files{false};
bool force_files{false};
/// If set, require a parameter after completion.
bool requires_param{false};
};
/// Character that separates the completion and description on programmable completions.
#define PROG_COMPLETE_SEP L'\t'
enum {
/// Do not insert space afterwards if this is the only completion. (The default is to try insert
/// a space).
COMPLETE_NO_SPACE = 1 << 0,
/// This is not the suffix of a token, but replaces it entirely.
COMPLETE_REPLACES_TOKEN = 1 << 1,
/// This completion may or may not want a space at the end - guess by checking the last
/// character of the completion.
COMPLETE_AUTO_SPACE = 1 << 2,
/// This completion should be inserted as-is, without escaping.
COMPLETE_DONT_ESCAPE = 1 << 3,
/// If you do escape, don't escape tildes.
COMPLETE_DONT_ESCAPE_TILDES = 1 << 4,
/// Do not sort supplied completions
COMPLETE_DONT_SORT = 1 << 5,
/// This completion looks to have the same string as an existing argument.
COMPLETE_DUPLICATES_ARGUMENT = 1 << 6,
/// This completes not just a token but replaces the entire commandline.
COMPLETE_REPLACES_COMMANDLINE = 1 << 7,
};
using complete_flags_t = uint8_t;
#if INCLUDE_RUST_HEADERS
#include "complete.rs.h"
#else
struct CompletionListFfi;
struct Completion;
struct CompletionRequestOptions;
#endif
using completion_t = Completion;
using completion_request_options_t = CompletionRequestOptions;
using completion_list_t = CompletionListFfi;
#endif

View File

@@ -1,18 +0,0 @@
#ifndef FISH_EDITABLE_LINE_H
#define FISH_EDITABLE_LINE_H
struct HighlightSpecListFFI;
#if INCLUDE_RUST_HEADERS
#include "editable_line.rs.h"
#else
struct Edit;
struct UndoHistory;
struct EditableLine;
#endif
using edit_t = Edit;
using undo_history_t = UndoHistory;
using editable_line_t = EditableLine;
#endif

1
src/empty.cpp Normal file
View File

@@ -0,0 +1 @@

View File

@@ -1,100 +0,0 @@
#ifndef FISH_ENUM_SET_H
#define FISH_ENUM_SET_H
#include <array>
#include <bitset>
#include <iterator>
/// A type (to specialize) that provides a count for an enum.
/// Example:
/// template<> struct enum_info_t<MyEnum>
/// { static constexpr auto count = MyEnum::COUNT; };
template <typename T>
struct enum_info_t {};
/// \return the count of an enum.
template <typename T>
constexpr size_t enum_count() {
return static_cast<size_t>(enum_info_t<T>::count);
}
/// A bit set indexed by an enum type.
template <typename T>
class enum_set_t : private std::bitset<enum_count<T>()> {
private:
using super = std::bitset<enum_count<T>()>;
static size_t index_of(T t) { return static_cast<size_t>(t); }
explicit enum_set_t(unsigned long raw) : super(raw) {}
explicit enum_set_t(super sup) : super(std::move(sup)) {}
public:
enum_set_t() = default;
/*implicit*/ enum_set_t(T v) { set(v); }
/*implicit*/ enum_set_t(std::initializer_list<T> vs) {
for (T v : vs) set(v);
}
static enum_set_t from_raw(unsigned long v) { return enum_set_t{v}; }
unsigned long to_raw() const { return super::to_ulong(); }
bool get(T t) const { return super::test(index_of(t)); }
void set(T t, bool v = true) { super::set(index_of(t), v); }
void clear(T t) { super::reset(index_of(t)); }
bool none() const { return super::none(); }
bool any() const { return super::any(); }
bool operator==(const enum_set_t &rhs) const { return super::operator==(rhs); }
bool operator!=(const enum_set_t &rhs) const { return super::operator!=(rhs); }
/// OR in a single flag, returning a new set.
enum_set_t operator|(T rhs) const {
enum_set_t result = *this;
result.set(rhs);
return result;
}
/// Compute the union of two sets.
enum_set_t operator|(enum_set_t rhs) const { return from_raw(to_raw() | rhs.to_raw()); }
/// OR in a single flag, modifying the set in place.
enum_set_t operator|=(T rhs) {
*this = *this | rhs;
return *this;
}
/// Set this to the union of two sets.
enum_set_t operator|=(enum_set_t rhs) {
*this = *this | rhs;
return *this;
}
/// Test a value of a single flag. Note this does not return an enum_set_t; there is no such
/// boolean conversion. This simply makes flags work more naturally as bit masks.
bool operator&(T rhs) const { return get(rhs); }
};
/// An array of Elem indexed by an enum class.
template <typename Elem, typename T>
class enum_array_t : public std::array<Elem, enum_count<T>()> {
using super = std::array<Elem, enum_count<T>()>;
using base_type_t = typename std::underlying_type<T>::type;
static int index_of(T t) { return static_cast<base_type_t>(t); }
public:
Elem &at(T t) { return super::at(index_of(t)); }
const Elem &at(T t) const { return super::at(index_of(t)); }
Elem &operator[](T t) { return super::operator[](index_of(t)); }
const Elem &operator[](T t) const { return super::operator[](index_of(t)); }
};
#endif

View File

@@ -1,225 +0,0 @@
// Functions for setting and getting environment variables.
#include "config.h" // IWYU pragma: keep
#include "env.h"
#include <pwd.h>
#include <unistd.h>
#include "history.h"
#include "path.h"
#include "reader.h"
/// At init, we read all the environment variables from this array.
extern char **environ;
// static
env_var_t env_var_t::new_ffi(EnvVar *ptr) {
assert(ptr != nullptr && "env_var_t::new_ffi called with null pointer");
return env_var_t(rust::Box<EnvVar>::from_raw(ptr));
}
wchar_t env_var_t::get_delimiter() const { return impl_->get_delimiter(); }
bool env_var_t::empty() const { return impl_->is_empty(); }
bool env_var_t::exports() const { return impl_->exports(); }
bool env_var_t::is_read_only() const { return impl_->is_read_only(); }
bool env_var_t::is_pathvar() const { return impl_->is_pathvar(); }
env_var_t::env_var_flags_t env_var_t::get_flags() const { return impl_->get_flags(); }
wcstring env_var_t::as_string() const {
wcstring res = std::move(*impl_->as_string());
return res;
}
void env_var_t::to_list(std::vector<wcstring> &out) const {
wcstring_list_ffi_t list{};
impl_->to_list(list);
out = std::move(list.vals);
}
std::vector<wcstring> env_var_t::as_list() const {
std::vector<wcstring> res = std::move(impl_->as_list()->vals);
return res;
}
env_var_t &env_var_t::operator=(const env_var_t &rhs) {
this->impl_ = rhs.impl_->clone_box();
return *this;
}
env_var_t::env_var_t(const wcstring_list_ffi_t &vals, uint8_t flags)
: impl_(env_var_create(vals, flags)) {}
env_var_t::env_var_t(const env_var_t &rhs) : impl_(rhs.impl_->clone_box()) {}
bool env_var_t::operator==(const env_var_t &rhs) const { return impl_->equals(*rhs.impl_); }
environment_t::~environment_t() = default;
env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { return env_flags_for(name); }
wcstring environment_t::get_pwd_slash() const {
// Return "/" if PWD is missing.
// See https://github.com/fish-shell/fish-shell/issues/5080
auto pwd_var = get_unless_empty(L"PWD");
wcstring pwd;
if (pwd_var) {
pwd = pwd_var->as_string();
}
if (!string_suffixes_string(L"/", pwd)) {
pwd.push_back(L'/');
}
return pwd;
}
maybe_t<env_var_t> environment_t::get_unless_empty(const wcstring &key,
env_mode_flags_t mode) const {
if (auto variable = this->get(key, mode)) {
if (!variable->empty()) {
return variable;
}
}
return none();
}
std::unique_ptr<env_var_t> environment_t::get_or_null(wcstring const &key,
env_mode_flags_t mode) const {
auto variable = this->get(key, mode);
if (!variable.has_value()) {
return nullptr;
}
return make_unique<env_var_t>(variable.acquire());
}
null_environment_t::null_environment_t() : impl_(env_null_create()) {}
null_environment_t::~null_environment_t() = default;
maybe_t<env_var_t> null_environment_t::get(const wcstring &key, env_mode_flags_t mode) const {
if (auto *ptr = impl_->getf(key, mode)) {
return env_var_t::new_ffi(ptr);
}
return none();
}
std::vector<wcstring> null_environment_t::get_names(env_mode_flags_t flags) const {
wcstring_list_ffi_t names;
impl_->get_names(flags, names);
return std::move(names.vals);
}
bool env_stack_t::is_principal() const { return impl_->is_principal(); }
static std::map<wcstring, wcstring> inheriteds;
void set_inheriteds_ffi() {
wcstring key, val;
const char *const *envp = environ;
int i = 0;
while (envp && envp[i]) i++;
while (i--) {
const wcstring key_and_val = str2wcstring(envp[i]);
size_t eql = key_and_val.find(L'=');
if (eql == wcstring::npos) {
continue;
}
key.assign(key_and_val, 0, eql);
val.assign(key_and_val, eql + 1, wcstring::npos);
inheriteds[key] = val;
}
}
/// Update the PWD variable directory from the result of getcwd().
void env_stack_t::set_pwd_from_getcwd() { impl_->set_pwd_from_getcwd(); }
maybe_t<env_var_t> env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const {
if (auto *ptr = impl_->getf(key, mode)) {
return env_var_t::new_ffi(ptr);
}
return none();
}
std::vector<wcstring> env_stack_t::get_names(env_mode_flags_t flags) const {
wcstring_list_ffi_t names;
impl_->get_names(flags, names);
return std::move(names.vals);
}
int env_stack_t::set(const wcstring &key, env_mode_flags_t mode, std::vector<wcstring> vals) {
return static_cast<int>(impl_->set(key, mode, std::move(vals)));
}
int env_stack_t::set_ffi(const wcstring &key, env_mode_flags_t mode, const void *vals,
size_t count) {
const wchar_t *const *ptr = static_cast<const wchar_t *const *>(vals);
return this->set(key, mode, std::vector<wcstring>(ptr, ptr + count));
}
int env_stack_t::set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) {
std::vector<wcstring> vals;
vals.push_back(std::move(val));
return set(key, mode, std::move(vals));
}
int env_stack_t::remove(const wcstring &key, int mode) {
return static_cast<int>(impl_->remove(key, mode));
}
maybe_t<env_var_t> env_dyn_t::get(const wcstring &key, env_mode_flags_t mode) const {
if (auto *ptr = impl_->getf(key, mode)) {
return env_var_t::new_ffi(ptr);
}
return none();
}
std::vector<wcstring> env_dyn_t::get_names(env_mode_flags_t flags) const {
wcstring_list_ffi_t names;
impl_->get_names(flags, names);
return std::move(names.vals);
}
std::shared_ptr<environment_t> env_stack_t::snapshot() const {
auto res = std::make_shared<env_dyn_t>(impl_->snapshot());
return std::static_pointer_cast<environment_t>(res);
}
wcstring env_stack_t::get_pwd_slash() const {
std::unique_ptr<wcstring> res = impl_->get_pwd_slash();
return std::move(*res);
}
void env_stack_t::push(bool new_scope) { impl_->push(new_scope); }
void env_stack_t::pop() { impl_->pop(); }
env_stack_t &env_stack_t::globals() {
static env_stack_t s_globals(env_get_globals_ffi());
return s_globals;
}
const std::shared_ptr<env_stack_t> &env_stack_t::principal_ref() {
static const std::shared_ptr<env_stack_t> s_principal{new env_stack_t(env_get_principal_ffi())};
return s_principal;
}
env_stack_t::~env_stack_t() = default;
env_stack_t::env_stack_t(env_stack_t &&) = default;
env_stack_t::env_stack_t(rust::Box<EnvStackRef> imp) : impl_(std::move(imp)) {}
env_stack_t::env_stack_t(uint8_t *imp)
: impl_(rust::Box<EnvStackRef>::from_raw(reinterpret_cast<EnvStackRef *>(imp))) {}
static std::mutex s_setenv_lock{};
extern "C" {
void setenv_lock(const char *name, const char *value, int overwrite) {
scoped_lock locker(s_setenv_lock);
setenv(name, value, overwrite);
}
void unsetenv_lock(const char *name) {
scoped_lock locker(s_setenv_lock);
unsetenv(name);
}
}
const EnvStackRef &env_stack_t::get_impl_ffi() const { return *impl_; }

326
src/env.h
View File

@@ -1,326 +0,0 @@
// Prototypes for functions for manipulating fish script variables.
#ifndef FISH_ENV_H
#define FISH_ENV_H
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "common.h"
#include "cxx.h"
#include "maybe.h"
#include "wutil.h"
#if INCLUDE_RUST_HEADERS
#include "env/env_ffi.rs.h"
#else
struct EnvVar;
struct EnvNull;
struct EnvStack;
struct EnvStackRef;
struct EnvDyn;
enum class env_stack_set_result_t : uint8_t;
struct Statuses;
#endif
struct event_list_ffi_t;
struct function_properties_t;
using statuses_t = Statuses;
/// 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;
#if INCLUDE_RUST_HEADERS
#include "env/env_ffi.rs.h"
#else
struct EnvVar;
struct EnvNull;
struct EnvStackRef;
#endif
struct owning_null_terminated_array_t;
extern "C" {
extern bool CURSES_INITIALIZED;
/// Does the terminal have the "eat_newline_glitch".
extern bool TERM_HAS_XN;
extern size_t READ_BYTE_LIMIT;
}
struct Event;
// Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get().
enum : uint16_t {
/// Default mode. Used with `env_stack_t::get()` to indicate the caller doesn't care what scope
/// the var is in or whether it is exported or unexported.
ENV_DEFAULT = 0,
/// Flag for local (to the current block) variable.
ENV_LOCAL = 1 << 0,
ENV_FUNCTION = 1 << 1,
/// Flag for global variable.
ENV_GLOBAL = 1 << 2,
/// Flag for universal variable.
ENV_UNIVERSAL = 1 << 3,
/// Flag for exported (to commands) variable.
ENV_EXPORT = 1 << 4,
/// Flag for unexported variable.
ENV_UNEXPORT = 1 << 5,
/// Flag to mark a variable as a path variable.
ENV_PATHVAR = 1 << 6,
/// Flag to unmark a variable as a path variable.
ENV_UNPATHVAR = 1 << 7,
/// Flag for variable update request from the user. All variable changes that are made directly
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
/// serves one purpose: to indicate that an error should be returned if the user is attempting
/// to modify a var that should not be modified by direct user action; e.g., a read-only var.
ENV_USER = 1 << 8,
};
using env_mode_flags_t = uint16_t;
/// Return values for `env_stack_t::set()`.
enum { ENV_OK, ENV_PERM, ENV_SCOPE, ENV_INVALID, ENV_NOT_FOUND };
/// env_var_t is an immutable value-type data structure representing the value of an environment
/// variable. This wraps the EnvVar type from Rust.
class env_var_t {
public:
using env_var_flags_t = uint8_t;
enum {
flag_export = 1 << 0, // whether the variable is exported
flag_read_only = 1 << 1, // whether the variable is read only
flag_pathvar = 1 << 2, // whether the variable is a path variable
};
env_var_t() : env_var_t(wcstring_list_ffi_t{}, 0) {}
env_var_t(const wcstring_list_ffi_t &vals, uint8_t flags);
env_var_t(const env_var_t &);
env_var_t(env_var_t &&) = default;
env_var_t(wcstring val, env_var_flags_t flags)
: env_var_t{std::vector<wcstring>{std::move(val)}, flags} {}
// Construct from FFI. This transfers ownership of the EnvVar, which should originate
// in Box::into_raw().
static env_var_t new_ffi(EnvVar *ptr);
// Get the underlying EnvVar pointer.
// Note you may need to mem::transmute this, since autocxx gets confused when going from Rust ->
// C++ -> Rust.
const EnvVar *ffi_ptr() const { return &*this->impl_; }
bool empty() const;
bool exports() const;
bool is_read_only() const;
bool is_pathvar() const;
env_var_flags_t get_flags() const;
wcstring as_string() const;
void to_list(std::vector<wcstring> &out) const;
std::vector<wcstring> as_list() const;
wcstring_list_ffi_t as_list_ffi() const { return as_list(); }
/// \return the character used when delimiting quoted expansion.
wchar_t get_delimiter() const;
static env_var_flags_t flags_for(const wchar_t *name);
env_var_t &operator=(const env_var_t &);
env_var_t &operator=(env_var_t &&) = default;
bool operator==(const env_var_t &rhs) const;
bool operator!=(const env_var_t &rhs) const { return !(*this == rhs); }
private:
env_var_t(rust::Box<EnvVar> &&impl) : impl_(std::move(impl)) {}
rust::Box<EnvVar> impl_;
};
/// An environment is read-only access to variable values.
class environment_t {
protected:
environment_t() = default;
public:
virtual maybe_t<env_var_t> get(const wcstring &key,
env_mode_flags_t mode = ENV_DEFAULT) const = 0;
virtual std::vector<wcstring> get_names(env_mode_flags_t flags) const = 0;
virtual ~environment_t();
maybe_t<env_var_t> get_unless_empty(const wcstring &key,
env_mode_flags_t mode = ENV_DEFAULT) const;
/// \return a environment variable as a unique pointer, or nullptr if none.
std::unique_ptr<env_var_t> get_or_null(const wcstring &key,
env_mode_flags_t mode = ENV_DEFAULT) const;
/// Returns the PWD with a terminating slash.
virtual wcstring get_pwd_slash() const;
};
/// The null environment contains nothing.
class null_environment_t : public environment_t {
public:
null_environment_t();
~null_environment_t();
maybe_t<env_var_t> get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override;
std::vector<wcstring> get_names(env_mode_flags_t flags) const override;
private:
rust::Box<EnvNull> impl_;
};
/// A mutable environment which allows scopes to be pushed and popped.
class env_stack_t final : public environment_t {
friend struct Parser;
/// \return whether we are the principal stack.
bool is_principal() const;
public:
~env_stack_t() override;
env_stack_t(env_stack_t &&);
/* implicit */ env_stack_t(rust::Box<EnvStackRef> imp);
/* implicit */ env_stack_t(uint8_t *imp);
/// Implementation of environment_t.
maybe_t<env_var_t> get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override;
/// Implementation of environment_t.
std::vector<wcstring> get_names(env_mode_flags_t flags) const override;
/// Sets the variable with the specified name to the given values.
int set(const wcstring &key, env_mode_flags_t mode, std::vector<wcstring> vals);
/// Sets the variable with the specified name to the given values.
/// The values should have type const wchar_t *const * (but autocxx doesn't support that).
int set_ffi(const wcstring &key, env_mode_flags_t mode, const void *vals, size_t count);
/// Sets the variable with the specified name to a single value.
int set_one(const wcstring &key, env_mode_flags_t mode, wcstring val);
/// Sets the variable with the specified name to no values.
/// Update the PWD variable based on the result of getcwd.
void set_pwd_from_getcwd();
/// 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 zero if the variable existed, and non-zero if the variable did not exist
int remove(const wcstring &key, int mode);
/// Push the variable stack. Used for implementing local variables for functions and for-loops.
void push(bool new_scope);
/// Pop the variable stack. Used for implementing local variables for functions and for-loops.
void pop();
/// 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). This
/// returns a shared_ptr for convenience, since the most common reason to snapshot is because
/// you want to read from another thread.
std::shared_ptr<environment_t> snapshot() const;
/// Slightly optimized implementation.
wcstring get_pwd_slash() const override;
/// "Override" of get_or_null, as autocxx doesn't understand inheritance.
std::unique_ptr<env_var_t> get_or_null(const wcstring &key,
env_mode_flags_t mode = ENV_DEFAULT) const {
return environment_t::get_or_null(key, mode);
}
// Compatibility hack; access the "environment stack" from back when there was just one.
static const std::shared_ptr<env_stack_t> &principal_ref();
static env_stack_t &principal() { return *principal_ref(); }
// Access a variable stack that only represents globals.
// Do not push or pop from this.
static env_stack_t &globals();
/// Access the underlying Rust implementation.
/// This returns a const rust::Box<EnvStackRef> *, or in Rust terms, a *const Box<EnvStackRef>.
const EnvStackRef &get_impl_ffi() const;
private:
/// The implementation. Do not access this directly.
rust::Box<EnvStackRef> impl_;
};
#if INCLUDE_RUST_HEADERS
struct EnvDyn;
/// Wrapper around rust's `&dyn Environment` deriving from `environment_t`.
class env_dyn_t final : public environment_t {
public:
env_dyn_t(rust::Box<EnvDyn> impl) : impl_(std::move(impl)) {}
maybe_t<env_var_t> get(const wcstring &key, env_mode_flags_t mode) const;
std::vector<wcstring> get_names(env_mode_flags_t flags) const;
private:
rust::Box<EnvDyn> impl_;
};
#endif
/// A struct of configuration directories, determined in main() that fish will optionally pass to
/// env_init.
struct config_paths_t {
wcstring data; // e.g., /usr/local/share
wcstring sysconf; // e.g., /usr/local/etc
wcstring doc; // e.g., /usr/local/share/doc/fish
wcstring bin; // e.g., /usr/local/bin
};
/// Initialize environment variable data.
void env_init(const struct config_paths_t *paths = nullptr, bool do_uvars = true,
bool default_paths = false);
using env_var_flags_t = uint8_t;
enum {
env_var_flag_export = 1 << 0, // whether the variable is exported
env_var_flag_read_only = 1 << 1, // whether the variable is read only
env_var_flag_pathvar = 1 << 2, // whether the variable is a path variable
};
/// A mutable environment which allows scopes to be pushed and popped.
#if INCLUDE_RUST_HEADERS
struct EnvDyn;
#endif
/// A wrapper around setenv() and unsetenv() which use a lock.
/// In general setenv() and getenv() are highly incompatible with threads. This makes it only
/// slightly safer.
extern "C" {
void setenv_lock(const char *name, const char *value, int overwrite);
void unsetenv_lock(const char *name);
}
void set_inheriteds_ffi();
#endif

View File

@@ -1,16 +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"
#include "env.h"
/// 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);
#endif

View File

@@ -1,2 +0,0 @@
struct EnvStackRef;
struct EnvDyn;

View File

@@ -1,22 +0,0 @@
#ifndef FISH_ENV_UNIVERSAL_COMMON_H
#define FISH_ENV_UNIVERSAL_COMMON_H
#include "config.h" // IWYU pragma: keep
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "common.h"
#include "env.h"
#include "fds.h"
#include "maybe.h"
#include "wutil.h"
#if INCLUDE_RUST_HEADERS
#include "universal_notifier/mod.rs.h"
#endif
#endif

View File

@@ -1,36 +0,0 @@
// event.h and event.cpp only contain cpp-side ffi compat code to make event.rs.h a drop-in
// replacement. There is no logic still in here that needs to be ported to rust.
#include "config.h" // IWYU pragma: keep
#include "event.h"
#include <signal.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <bitset>
#include <memory>
#include <string>
#include <utility>
#include "common.h"
#include "fallback.h" // IWYU pragma: keep
#include "flog.h"
#include "io.h"
#include "maybe.h"
#include "parser.h"
#include "proc.h"
#include "signals.h"
#include "termsize.h"
#include "wcstringutil.h"
#include "wutil.h" // IWYU pragma: keep
void event_fire_generic(const parser_t &parser, const wcstring &name,
const std::vector<wcstring> &args) {
wcstring_list_ffi_t ffi_args;
for (const auto &arg : args) ffi_args.push(arg);
event_fire_generic_ffi(parser, name, ffi_args);
}

View File

@@ -1,38 +0,0 @@
// event.h and event.cpp only contain cpp-side ffi compat code to make event.rs.h a drop-in
// replacement. There is no logic still in here that needs to be ported to rust.
#ifndef FISH_EVENT_H
#define FISH_EVENT_H
#include <unistd.h>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "common.h"
#include "cxx.h"
#include "global_safety.h"
#include "parser.h"
#include "wutil.h"
#if INCLUDE_RUST_HEADERS
#include "event.rs.h"
#else
struct Event;
#endif
/// The process id that is used to match any process id.
// TODO: Remove after porting functions.cpp
#define EVENT_ANY_PID 0
/// Null-terminated list of valid event filter names.
/// These are what are valid to pass to 'functions --handlers-type'
// TODO: Remove after porting functions.cpp
extern const wchar_t *const event_filter_names[];
void event_fire_generic(const parser_t &parser, const wcstring &name,
const std::vector<wcstring> &args = g_empty_string_list);
#endif

View File

@@ -1,3 +0,0 @@
#if INCLUDE_RUST_HEADERS
#include "exec.rs.h"
#endif

View File

@@ -1,11 +0,0 @@
#include "expand.h"
/// \param input the string to tilde expand
void expand_tilde(wcstring &input, const env_stack_t &vars) {
// Avoid needless COW behavior by ensuring we use const at.
const wcstring &tmp = input;
if (!tmp.empty() && tmp.at(0) == L'~') {
input.at(0) = HOME_DIRECTORY;
input = *expand_home_directory(input, vars.get_impl_ffi());
}
}

View File

@@ -1,102 +0,0 @@
// Prototypes for string expansion functions. These functions perform several kinds of parameter
// expansion. There are a lot of issues with regards to memory allocation. Overall, these functions
// would benefit from using a more clever memory allocation scheme, perhaps an evil combination of
// talloc, string buffers and reference counting.
#ifndef FISH_EXPAND_H
#define FISH_EXPAND_H
#include "config.h"
#include <initializer_list>
#include <map>
#include <string>
#include <vector>
#include "common.h"
#include "enum_set.h"
#include "env.h"
#include "maybe.h"
#include "operation_context.h"
#include "parse_constants.h"
/// Set of flags controlling expansions.
enum expand_flag {
/// Skip command substitutions.
skip_cmdsubst = 1 << 0,
/// Skip variable expansion.
skip_variables = 1 << 1,
/// Skip wildcard expansion.
skip_wildcards = 1 << 2,
/// The expansion is being done for tab or auto completions. Returned completions may have the
/// wildcard as a prefix instead of a match.
for_completions = 1 << 3,
/// Only match files that are executable by the current user.
executables_only = 1 << 4,
/// Only match directories.
directories_only = 1 << 5,
/// Generate descriptions, stored in the description field of completions.
gen_descriptions = 1 << 6,
/// Un-expand home directories to tildes after.
preserve_home_tildes = 1 << 7,
/// Allow fuzzy matching.
fuzzy_match = 1 << 8,
/// Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if
/// fuzzy_match is set.
no_fuzzy_directories = 1 << 9,
/// Allows matching a leading dot even if the wildcard does not contain one.
/// By default, wildcards only match a leading dot literally; this is why e.g. '*' does not
/// match hidden files.
allow_nonliteral_leading_dot = 1 << 10,
/// Do expansions specifically to support cd. This means using CDPATH as a list of potential
/// working directories, and to use logical instead of physical paths.
special_for_cd = 1 << 11,
/// Do expansions specifically for cd autosuggestion. This is to differentiate between cd
/// completions and cd autosuggestions.
special_for_cd_autosuggestion = 1 << 12,
/// Do expansions specifically to support external command completions. This means using PATH as
/// a list of potential working directories.
special_for_command = 1 << 13,
};
using expand_flags_t = uint64_t;
enum : wchar_t {
/// Character representing a home directory.
HOME_DIRECTORY = EXPAND_RESERVED_BASE,
/// Character representing process expansion for %self.
PROCESS_EXPAND_SELF,
/// Character representing variable expansion.
VARIABLE_EXPAND,
/// Character representing variable expansion into a single element.
VARIABLE_EXPAND_SINGLE,
/// Character representing the start of a bracket expansion.
BRACE_BEGIN,
/// Character representing the end of a bracket expansion.
BRACE_END,
/// Character representing separation between two bracket elements.
BRACE_SEP,
/// Character that takes the place of any whitespace within non-quoted text in braces
BRACE_SPACE,
/// Separate subtokens in a token with this character.
INTERNAL_SEPARATOR,
/// Character representing an empty variable expansion. Only used transitively while expanding
/// variables.
VARIABLE_EXPAND_EMPTY,
/// This is a special pseudo-char that is not used other than to mark the end of the the special
/// characters so we can sanity check the enum range.
EXPAND_SENTINEL
};
/// The string represented by PROCESS_EXPAND_SELF
#define PROCESS_EXPAND_SELF_STR L"%self"
#define PROCESS_EXPAND_SELF_STR_LEN 5
#if INCLUDE_RUST_HEADERS
#include "expand.rs.h"
using expand_result_t = ExpandResult;
#endif
/// \param input the string to tilde expand
void expand_tilde(wcstring &input, const env_stack_t &vars);
#endif

View File

@@ -1,181 +0,0 @@
// This file only contains fallback implementations of functions which have been found to be missing
// or broken by the configuration scripts.
//
// Many of these functions are more or less broken and incomplete.
#include "config.h"
// IWYU likes to recommend adding term.h when we want ncurses.h.
// IWYU pragma: no_include "term.h"
#include <errno.h> // IWYU pragma: keep
#include <fcntl.h> // IWYU pragma: keep
#include <limits.h> // IWYU pragma: keep
#include <unistd.h> // IWYU pragma: keep
#include <wctype.h>
#include <cstdlib>
#include <cwchar>
#if HAVE_GETTEXT
#include <libintl.h>
#endif
#if defined(TPARM_SOLARIS_KLUDGE)
#if HAVE_CURSES_H
#include <curses.h> // IWYU pragma: keep
#elif HAVE_NCURSES_H
#include <ncurses.h> // IWYU pragma: keep
#elif HAVE_NCURSES_CURSES_H
#include <ncurses/curses.h> // IWYU pragma: keep
#endif
#if HAVE_TERM_H
#include <term.h> // IWYU pragma: keep
#elif HAVE_NCURSES_TERM_H
#include <ncurses/term.h>
#endif
#endif
#include <signal.h> // IWYU pragma: keep
#include "common.h" // IWYU pragma: keep
#include "fallback.h" // IWYU pragma: keep
#if defined(TPARM_SOLARIS_KLUDGE)
char *tparm_solaris_kludge(char *str, long p1, long p2, long p3, long p4, long p5, long p6, long p7,
long p8, long p9) {
return tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
#endif
/// Fallback implementations of wcsncasecmp and wcscasecmp. On systems where these are not needed
/// (e.g. building on Linux) these should end up just being stripped, as they are static functions
/// that are not referenced in this file.
// cppcheck-suppress unusedFunction
[[gnu::unused]] static int wcscasecmp_fallback(const wchar_t *a, const wchar_t *b) {
if (*a == 0) {
return *b == 0 ? 0 : -1;
} else if (*b == 0) {
return 1;
}
int diff = towlower(*a) - towlower(*b);
if (diff != 0) {
return diff;
}
return wcscasecmp_fallback(a + 1, b + 1);
}
[[gnu::unused]] static int wcsncasecmp_fallback(const wchar_t *a, const wchar_t *b, size_t count) {
if (count == 0) return 0;
if (*a == 0) {
return *b == 0 ? 0 : -1;
} else if (*b == 0) {
return 1;
}
int diff = towlower(*a) - towlower(*b);
if (diff != 0) return diff;
return wcsncasecmp_fallback(a + 1, b + 1, count - 1);
}
#ifndef HAVE_WCSCASECMP
#ifndef HAVE_STD__WCSCASECMP
int wcscasecmp(const wchar_t *a, const wchar_t *b) { return wcscasecmp_fallback(a, b); }
#endif
#endif
#ifndef HAVE_WCSNCASECMP
#ifndef HAVE_STD__WCSNCASECMP
int wcsncasecmp(const wchar_t *a, const wchar_t *b, size_t n) {
return wcsncasecmp_fallback(a, b, n);
}
#endif
#endif
#if HAVE_GETTEXT
char *fish_gettext(const char *msgid) { return gettext(msgid); }
char *fish_bindtextdomain(const char *domainname, const char *dirname) {
return bindtextdomain(domainname, dirname);
}
char *fish_textdomain(const char *domainname) { return textdomain(domainname); }
#else
char *fish_gettext(const char *msgid) { return (char *)msgid; }
char *fish_bindtextdomain(const char *domainname, const char *dirname) {
UNUSED(domainname);
UNUSED(dirname);
return nullptr;
}
char *fish_textdomain(const char *domainname) {
UNUSED(domainname);
return nullptr;
}
#endif
#ifndef HAVE_KILLPG
int killpg(int pgr, int sig) {
assert(pgr > 1);
return kill(-pgr, sig);
}
#endif
static int fish_get_emoji_width(wchar_t c) {
(void)c;
return FISH_EMOJI_WIDTH;
}
// Big hack to use our versions of wcswidth where we know them to be broken, which is
// EVERYWHERE (https://github.com/fish-shell/fish-shell/issues/2199)
#include "widecharwidth/widechar_width.h"
int fish_wcwidth(wchar_t wc) {
// The system version of wcwidth should accurately reflect the ability to represent characters
// in the console session, but knows nothing about the capabilities of other terminal emulators
// or ttys. Use it from the start only if we are logged in to the physical console.
if (is_console_session()) {
return wcwidth(wc);
}
// Check for VS16 which selects emoji presentation. This "promotes" a character like U+2764
// (width 1) to an emoji (probably width 2). So treat it as width 1 so the sums work. See #2652.
// VS15 selects text presentation.
const wchar_t variation_selector_16 = L'\uFE0F', variation_selector_15 = L'\uFE0E';
if (wc == variation_selector_16)
return 1;
else if (wc == variation_selector_15)
return 0;
// Check for Emoji_Modifier property. Only the Fitzpatrick modifiers have this, in range
// 1F3FB..1F3FF. This is a hack because such an emoji appearing on its own would be drawn as
// width 2, but that's unlikely to be useful. See #8275.
if (wc >= 0x1F3FB && wc <= 0x1F3FF) return 0;
int width = widechar_wcwidth(wc);
switch (width) {
case widechar_non_character:
case widechar_nonprint:
case widechar_combining:
case widechar_unassigned:
// Fall back to system wcwidth in this case.
return wcwidth(wc);
case widechar_ambiguous:
case widechar_private_use:
// TR11: "All private-use characters are by default classified as Ambiguous".
return FISH_AMBIGUOUS_WIDTH;
case widechar_widened_in_9:
return fish_get_emoji_width(wc);
default:
assert(width > 0 && "Unexpectedly nonpositive width");
return width;
}
}
int fish_wcswidth(const wchar_t *str, size_t n) {
int result = 0;
for (size_t i = 0; i < n && str[i] != L'\0'; i++) {
int w = fish_wcwidth(str[i]);
if (w < 0) {
result = -1;
break;
}
result += w;
}
return result;
}

View File

@@ -1,113 +0,0 @@
#ifndef FISH_FALLBACK_H
#define FISH_FALLBACK_H
#include <stdint.h>
#include "config.h"
// The following include must be kept despite what IWYU says. That's because of the interaction
// between the weak linking of `wcscasecmp` via `#define`s below and the declarations
// in <wchar.h>. At least on OS X if we don't do this we get compilation errors do to the macro
// substitution if wchar.h is included after this header.
#include <cwchar> // IWYU pragma: keep
//
// Width of ambiguous characters. 1 is typical default.
extern int32_t FISH_AMBIGUOUS_WIDTH;
// Width of emoji characters.
// 1 is the typical emoji width in Unicode 8.
extern int32_t FISH_EMOJI_WIDTH;
/// fish's internal versions of wcwidth and wcswidth, which can use an internal implementation if
/// the system one is busted.
int fish_wcwidth(wchar_t wc);
int fish_wcswidth(const wchar_t *str, size_t n);
/// thread_local support.
#if HAVE_CX11_THREAD_LOCAL
#define FISH_THREAD_LOCAL thread_local
#elif defined(__GNUC__)
#define FISH_THREAD_LOCAL __thread
#elif defined(_MSC_VER)
#define FISH_THREAD_LOCAL __declspec(thread)
#else // !C++11 && !__GNUC__ && !_MSC_VER
#error "No known thread local storage qualifier for this platform"
#endif
#ifndef WCHAR_MAX
/// This _should_ be defined by wchar.h, but e.g. OpenBSD doesn't.
#define WCHAR_MAX INT_MAX
#endif
/// Both ncurses and NetBSD curses expect an int (*func)(int) as the last parameter for tputs.
/// Apparently OpenIndiana's curses still uses int (*func)(char) here.
#if TPUTS_USES_INT_ARG
using tputs_arg_t = int;
#else
using tputs_arg_t = char;
#endif
#ifndef HAVE_WINSIZE
/// Structure used to get the size of a terminal window.
struct winsize {
/// Number of rows.
unsigned short ws_row;
/// Number of columns.
unsigned short ws_col;
};
#endif
#if defined(TPARM_SOLARIS_KLUDGE)
/// Solaris tparm has a set fixed of parameters in its curses implementation, work around this here.
#define fish_tparm tparm_solaris_kludge
char *tparm_solaris_kludge(char *str, long p1 = 0, long p2 = 0, long p3 = 0, long p4 = 0,
long p5 = 0, long p6 = 0, long p7 = 0, long p8 = 0, long p9 = 0);
#else
#define fish_tparm tparm
#endif
/// These functions are missing from Solaris 10, and only accessible from
/// Solaris 11 in the std:: namespace.
#ifndef HAVE_WCSCASECMP
#ifdef HAVE_STD__WCSCASECMP
using std::wcscasecmp;
#else
int wcscasecmp(const wchar_t *a, const wchar_t *b);
#endif // HAVE_STD__WCSCASECMP
#endif // HAVE_WCSCASECMP
#ifndef HAVE_WCSNCASECMP
#ifdef HAVE_STD__WCSNCASECMP
using std::wcsncasecmp;
#else
int wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n);
#endif // HAVE_STD__WCSNCASECMP
#endif // HAVE_WCSNCASECMP
#ifndef HAVE_DIRFD
#ifndef __XOPEN_OR_POSIX
#define dirfd(d) (d->dd_fd)
#else
#define dirfd(d) (d->d_fd)
#endif
#endif
// autoconf may fail to detect gettext (645), so don't define a function call gettext or we'll get
// build errors.
/// Cover for gettext().
char *fish_gettext(const char *msgid);
/// Cover for bindtextdomain().
char *fish_bindtextdomain(const char *domainname, const char *dirname);
/// Cover for textdomain().
char *fish_textdomain(const char *domainname);
#ifndef HAVE_KILLPG
/// Send specified signal to specified process group.
int killpg(int pgr, int sig);
#endif
#endif // FISH_FALLBACK_H

View File

@@ -1,30 +0,0 @@
/** Facilities for working with file descriptors. */
#ifndef FISH_FDS_H
#define FISH_FDS_H
#include "config.h" // IWYU pragma: keep
#include <poll.h> // IWYU pragma: keep
#include <sys/select.h> // IWYU pragma: keep
#include <sys/types.h>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include "common.h"
#include "maybe.h"
#if INCLUDE_RUST_HEADERS
#include "fds.rs.h"
#endif
/// A sentinel value indicating no timeout.
#define kNoTimeout (std::numeric_limits<uint64_t>::max())
/// Mark an fd as blocking; returns errno or 0 on success.
int make_fd_blocking(int fd);
#endif

View File

@@ -1,23 +0,0 @@
#include <algorithm>
#include <memory>
#include "cxx.h"
#include "trace.rs.h"
#if INCLUDE_RUST_HEADERS
// For some unknown reason, the definition of rust::Box is in this particular header:
#include "parse_constants.rs.h"
#endif
template <typename T>
inline std::shared_ptr<T> box_to_shared_ptr(rust::Box<T> &&value) {
T *ptr = value.into_raw();
std::shared_ptr<T> shared(ptr, [](T *ptr) { rust::Box<T>::from_raw(ptr); });
return shared;
}
inline static void trace_if_enabled(const parser_t &parser, wcharz_t command,
const std::vector<wcstring> &args = {}) {
if (trace_enabled(parser)) {
trace_argv(parser, command, args);
}
}

View File

@@ -1,25 +0,0 @@
#include "builtin.h"
#include "event.h"
#include "fds.h"
#include "highlight.h"
#include "parse_util.h"
#include "reader.h"
#include "screen.h"
// Symbols that get autocxx bindings but are not used in a given binary, will cause "undefined
// reference" when trying to link that binary. Work around this by marking them as used in
// all binaries.
void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
wcstring s;
event_fire_generic(parser, {});
event_fire_generic(parser, {}, {});
expand_tilde(s, env_stack);
highlight_spec_t{};
rgb_color_t{};
setenv_lock({}, {}, {});
set_inheriteds_ffi();
unsetenv_lock({});
rgb_color_t::white();
rgb_color_t{};
}

View File

@@ -16,11 +16,6 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "common.h"
#include "ffi_baggage.h"
#include "fish.rs.h"
int main() {
program_name = L"fish";
return rust_main();
}
int main() { return rust_main(); }

View File

@@ -15,44 +15,6 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "config.h" // IWYU pragma: keep
#include <errno.h>
#include <getopt.h>
#include <locale.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <cwchar>
#include <cwctype>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "ast.h"
#include "env.h"
#include "env/env_ffi.rs.h"
#include "fds.h"
#include "ffi_baggage.h"
#include "ffi_init.rs.h"
#include "fish_indent.rs.h"
#include "fish_version.h"
#include "flog.h"
#include "future_feature_flags.h"
#include "highlight.h"
#include "operation_context.h"
#include "print_help.rs.h"
#include "tokenizer.h"
#include "wcstringutil.h"
#include "wutil.h" // IWYU pragma: keep
int main() {
program_name = L"fish_indent";
return fish_indent_main();
}
int main() { return fish_indent_main(); }

View File

@@ -20,7 +20,6 @@
#include <vector>
#include "common.h"
#include "ffi_baggage.h"
#include "fish_key_reader.rs.h"
int main() { return fish_key_reader_main(); }

View File

@@ -1,17 +0,0 @@
// Fish version receiver.
//
// This file has a specific purpose of shortening compilation times when
// the only change is different `git describe` version.
#include "fish_version.h"
#ifndef FISH_BUILD_VERSION
// The contents of FISH-BUILD-VERSION-FILE looks like:
// FISH_BUILD_VERSION="2.7.1-62-gc0480092-dirty"
// Arrange for it to become a variable.
static const char *const
#include "FISH-BUILD-VERSION-FILE"
;
#endif
/// Return fish shell version.
const char *get_fish_version() { return FISH_BUILD_VERSION; }

View File

@@ -1,2 +0,0 @@
// Prototype for version receiver.
const char *get_fish_version();

View File

@@ -1,206 +0,0 @@
/// fish logging
#include "config.h" // IWYU pragma: keep
#include "flog.h"
#include <stdarg.h>
#include <unistd.h>
#include <algorithm>
#include <cstring>
#include <cwchar>
#include <memory>
#include <vector>
#include "common.h"
#include "global_safety.h"
#include "parse_util.h"
#include "wcstringutil.h"
#include "wildcard.h"
namespace flog_details {
// Note we are relying on the order of global initialization within this file.
// It is important that 'all' be initialized before 'g_categories', because g_categories wants to
// append to all in the ctor.
/// This is not modified after initialization.
static std::vector<category_t *> s_all_categories;
/// The fd underlying the flog output file.
/// This is separated out for flogf_async_safe.
static int s_flog_file_fd{STDERR_FILENO};
/// When a category is instantiated it adds itself to the 'all' list.
category_t::category_t(const wchar_t *name, const wchar_t *desc, bool enabled)
: name(name), description(desc), enabled(enabled) {
s_all_categories.push_back(this);
}
/// Instantiate all categories.
/// This is deliberately leaked to avoid pointless destructor registration.
category_list_t *const category_list_t::g_instance = new category_list_t();
logger_t::logger_t() : file_(stderr) {}
owning_lock<logger_t> g_logger;
void logger_t::log1(const wchar_t *s) { std::fputws(s, file_); }
void logger_t::log1(const char *s) {
// Note glibc prohibits mixing narrow and wide I/O, so always use wide-printing functions.
// See #5900.
std::fwprintf(file_, L"%s", s);
}
void logger_t::log1(wchar_t c) { std::fputwc(c, file_); }
void logger_t::log1(char c) { std::fwprintf(file_, L"%c", c); }
void logger_t::log1(int64_t v) { std::fwprintf(file_, L"%lld", v); }
void logger_t::log1(uint64_t v) { std::fwprintf(file_, L"%llu", v); }
void logger_t::log_fmt(const category_t &cat, const wchar_t *fmt, ...) {
va_list va;
va_start(va, fmt);
log1(cat.name);
log1(L": ");
std::vfwprintf(file_, fmt, va);
log1(L'\n');
va_end(va);
}
void logger_t::log_fmt(const category_t &cat, const char *fmt, ...) {
// glibc dislikes mixing wide and narrow output functions.
// So construct a narrow string in-place and output that via wide functions.
va_list va;
va_start(va, fmt);
int ret = vsnprintf(nullptr, 0, fmt, va);
va_end(va);
if (ret < 0) {
perror("vsnprintf");
return;
}
size_t len = static_cast<size_t>(ret) + 1;
std::unique_ptr<char[]> buff(new char[len]);
va_start(va, fmt);
ret = vsnprintf(buff.get(), len, fmt, va);
va_end(va);
if (ret < 0) {
perror("vsnprintf");
return;
}
log_fmt(cat, L"%s", buff.get());
}
inline void async_safe_write_str(const char *s, size_t len = std::string::npos) {
if (s_flog_file_fd < 0) return;
if (len == std::string::npos) len = std::strlen(s);
ignore_result(write(s_flog_file_fd, s, len));
}
// static
void logger_t::flogf_async_safe(const char *category, const char *fmt, const char *param1,
const char *param2, const char *param3, const char *param4,
const char *param5, const char *param6, const char *param7,
const char *param8, const char *param9, const char *param10,
const char *param11, const char *param12) {
const char *const params[] = {param1, param2, param3, param4, param5, param6,
param7, param8, param9, param10, param11, param12};
const size_t max_params = sizeof params / sizeof *params;
// Can't call fwprintf, that may allocate memory.
// Just call write() over and over.
async_safe_write_str(category);
async_safe_write_str(": ");
size_t param_idx = 0;
const char *cursor = fmt;
while (*cursor != '\0') {
const char *end = std::strchr(cursor, '%');
if (end == nullptr) end = cursor + std::strlen(cursor);
if (end > cursor) async_safe_write_str(cursor, end - cursor);
if (end[0] == '%' && end[1] == 's') {
// Handle a format string.
const char *param = (param_idx < max_params ? params[param_idx++] : nullptr);
if (!param) param = "(null)";
async_safe_write_str(param);
cursor = end + 2;
} else if (end[0] == '\0') {
// Must be at the end of the string.
cursor = end;
} else {
// Some other format specifier, just skip it.
cursor = end + 1;
}
}
// We always append a newline.
async_safe_write_str("\n");
}
} // namespace flog_details
using namespace flog_details;
/// For each category, if its name matches the wildcard, set its enabled to the given sense.
static void apply_one_wildcard(const wcstring &wc_esc, bool sense) {
wcstring wc = parse_util_unescape_wildcards(wc_esc);
bool match_found = false;
for (category_t *cat : s_all_categories) {
if (wildcard_match(cat->name, wc)) {
cat->enabled = sense;
match_found = true;
}
}
if (!match_found) {
fprintf(stderr, "Failed to match debug category: %ls\n", wc_esc.c_str());
}
}
void activate_flog_categories_by_pattern(wcstring wc) {
// Normalize underscores to dashes, allowing the user to be sloppy.
std::replace(wc.begin(), wc.end(), L'_', L'-');
for (const wcstring &s : split_string(wc, L',')) {
if (string_prefixes_string(L"-", s)) {
apply_one_wildcard(s.substr(1), false);
} else {
apply_one_wildcard(s, true);
}
}
}
// autocxx fails with FILE*
void set_flog_output_file_ffi(void* fp) {
auto f = static_cast<FILE *>(fp);
set_flog_output_file(f);
}
// Rust libc does not contain setlinebuf
void flog_setlinebuf_ffi(void *f) {
auto fp = static_cast<FILE *>(f);
if (fp == nullptr) {
return;
}
setlinebuf(fp);
}
void set_flog_output_file(FILE *f) {
assert(f && "Null file");
g_logger.acquire()->set_file(f);
s_flog_file_fd = fileno(f);
}
void log_extra_to_flog_file(const wcstring &s) { g_logger.acquire()->log_extra(s.c_str()); }
int get_flog_file_fd() { return s_flog_file_fd; }
std::vector<const category_t *> get_flog_categories() {
std::vector<const category_t *> result(s_all_categories.begin(), s_all_categories.end());
std::sort(result.begin(), result.end(), [](const category_t *a, const category_t *b) {
return wcscmp(a->name, b->name) < 0;
});
return result;
}

Some files were not shown because too many files have changed in this diff Show More