mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-06 08:51:14 -03:00
Reimplement termsize in Rust
This is not yet adopted by fish.
This commit is contained in:
@@ -6,10 +6,12 @@
|
|||||||
use ::std::pin::Pin;
|
use ::std::pin::Pin;
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
use ::std::slice;
|
use ::std::slice;
|
||||||
|
use crate::env::flags::EnvMode;
|
||||||
pub use crate::wait_handle::{
|
pub use crate::wait_handle::{
|
||||||
WaitHandleRef, WaitHandleRefFFI, WaitHandleStore, WaitHandleStoreFFI,
|
WaitHandleRef, WaitHandleRefFFI, WaitHandleStore, WaitHandleStoreFFI,
|
||||||
};
|
};
|
||||||
use crate::wchar::wstr;
|
use crate::wchar::{wstr, WString};
|
||||||
|
use crate::wchar_ffi::WCharFromFFI;
|
||||||
use autocxx::prelude::*;
|
use autocxx::prelude::*;
|
||||||
use cxx::SharedPtr;
|
use cxx::SharedPtr;
|
||||||
use libc::pid_t;
|
use libc::pid_t;
|
||||||
@@ -47,7 +49,9 @@
|
|||||||
generate!("wperror")
|
generate!("wperror")
|
||||||
|
|
||||||
generate_pod!("pipes_ffi_t")
|
generate_pod!("pipes_ffi_t")
|
||||||
|
generate!("environment_t")
|
||||||
generate!("env_stack_t")
|
generate!("env_stack_t")
|
||||||
|
generate!("env_var_t")
|
||||||
generate!("make_pipes_ffi")
|
generate!("make_pipes_ffi")
|
||||||
|
|
||||||
generate!("valid_var_name_char")
|
generate!("valid_var_name_char")
|
||||||
@@ -148,6 +152,66 @@ pub fn job_get_from_pid(&self, pid: pid_t) -> Option<&job_t> {
|
|||||||
let job = self.ffi_job_get_from_pid(pid.into());
|
let job = self.ffi_job_get_from_pid(pid.into());
|
||||||
unsafe { job.as_ref() }
|
unsafe { job.as_ref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to get a variable as a string, using the default flags.
|
||||||
|
pub fn var_as_string(&mut self, name: &wstr) -> Option<WString> {
|
||||||
|
self.pin().vars().unpin().get_as_string(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_var_stack(&mut self) -> &mut env_stack_t {
|
||||||
|
self.pin().vars().unpin()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_var_stack_env(&mut self) -> &environment_t {
|
||||||
|
self.vars_env_ffi()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_var(&mut self, name: &wstr, value: &[&wstr], flags: EnvMode) -> libc::c_int {
|
||||||
|
self.get_var_stack().set_var(name, value, flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl environment_t {
|
||||||
|
/// Helper to get a variable as a string, using the default flags.
|
||||||
|
pub fn get_as_string(&self, name: &wstr) -> Option<WString> {
|
||||||
|
self.get_as_string_flags(name, EnvMode::DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to get a variable as a string, using the given flags.
|
||||||
|
pub fn get_as_string_flags(&self, name: &wstr, flags: EnvMode) -> Option<WString> {
|
||||||
|
self.get_or_null(&name.to_ffi(), flags.bits())
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_string().from_ffi())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl env_stack_t {
|
||||||
|
/// Helper to get a variable as a string, using the default flags.
|
||||||
|
pub fn get_as_string(&self, name: &wstr) -> Option<WString> {
|
||||||
|
self.get_as_string_flags(name, EnvMode::DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to get a variable as a string, using the given flags.
|
||||||
|
pub fn get_as_string_flags(&self, name: &wstr, flags: EnvMode) -> Option<WString> {
|
||||||
|
self.get_or_null(&name.to_ffi(), flags.bits())
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_string().from_ffi())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to set a value.
|
||||||
|
pub fn set_var(&mut self, name: &wstr, value: &[&wstr], flags: EnvMode) -> libc::c_int {
|
||||||
|
use crate::wchar_ffi::{wstr_to_u32string, W0String};
|
||||||
|
let strings: Vec<W0String> = value.iter().map(wstr_to_u32string).collect();
|
||||||
|
let ptrs: Vec<*const u32> = strings.iter().map(|s| s.as_ptr()).collect();
|
||||||
|
self.pin()
|
||||||
|
.set_ffi(
|
||||||
|
&name.to_ffi(),
|
||||||
|
flags.bits(),
|
||||||
|
ptrs.as_ptr() as *const c_void,
|
||||||
|
ptrs.len(),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_compile(anchored: &wstr, flags: &re::flags_t) -> Pin<Box<re::regex_result_ffi>> {
|
pub fn try_compile(anchored: &wstr, flags: &re::flags_t) -> Pin<Box<re::regex_result_ffi>> {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
mod redirection;
|
mod redirection;
|
||||||
mod signal;
|
mod signal;
|
||||||
mod smoke;
|
mod smoke;
|
||||||
|
mod termsize;
|
||||||
mod threads;
|
mod threads;
|
||||||
mod timer;
|
mod timer;
|
||||||
mod tokenizer;
|
mod tokenizer;
|
||||||
|
|||||||
338
fish-rust/src/termsize.rs
Normal file
338
fish-rust/src/termsize.rs
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
// Support for exposing the terminal size.
|
||||||
|
use crate::common::assert_sync;
|
||||||
|
use crate::env::flags::EnvMode;
|
||||||
|
use crate::ffi::{environment_t, parser_t, Repin};
|
||||||
|
use crate::flog::FLOG;
|
||||||
|
use crate::wchar::{WString, L};
|
||||||
|
use crate::wchar_ext::ToWString;
|
||||||
|
use crate::wchar_ffi::WCharToFFI;
|
||||||
|
use crate::wutil::fish_wcstoi;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
// A counter which is incremented every SIGWINCH, or when the tty is otherwise invalidated.
|
||||||
|
static TTY_TERMSIZE_GEN_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
/// Convert an environment variable to an int, or return a default value.
|
||||||
|
/// The int must be >0 and <USHRT_MAX (from struct winsize).
|
||||||
|
fn var_to_int_or(var: Option<WString>, default: isize) -> isize {
|
||||||
|
match var {
|
||||||
|
Some(s) => {
|
||||||
|
let proposed = fish_wcstoi(s.chars());
|
||||||
|
if let Ok(proposed) = proposed {
|
||||||
|
proposed
|
||||||
|
} else {
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \return a termsize from ioctl, or None on error or if not supported.
|
||||||
|
fn read_termsize_from_tty() -> Option<Termsize> {
|
||||||
|
let mut ret: Option<Termsize> = None;
|
||||||
|
// Note: historically we've supported libc::winsize not existing.
|
||||||
|
let mut winsize: libc::winsize = unsafe { std::mem::zeroed() };
|
||||||
|
if unsafe { libc::ioctl(0, libc::TIOCGWINSZ, &mut winsize as *mut libc::winsize) } >= 0 {
|
||||||
|
// 0 values are unusable, fall back to the default instead.
|
||||||
|
if winsize.ws_col == 0 {
|
||||||
|
FLOG!(
|
||||||
|
term_support,
|
||||||
|
L!("Terminal has 0 columns, falling back to default width")
|
||||||
|
);
|
||||||
|
winsize.ws_col = Termsize::DEFAULT_WIDTH as u16;
|
||||||
|
}
|
||||||
|
if winsize.ws_row == 0 {
|
||||||
|
FLOG!(
|
||||||
|
term_support,
|
||||||
|
L!("Terminal has 0 rows, falling back to default height")
|
||||||
|
);
|
||||||
|
winsize.ws_row = Termsize::DEFAULT_HEIGHT as u16;
|
||||||
|
}
|
||||||
|
ret = Some(Termsize::new(
|
||||||
|
winsize.ws_col as isize,
|
||||||
|
winsize.ws_row as isize,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Termsize {
|
||||||
|
/// Width of the terminal, in columns.
|
||||||
|
pub width: isize,
|
||||||
|
|
||||||
|
/// Height of the terminal, in rows.
|
||||||
|
pub height: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Termsize {
|
||||||
|
/// Default width and height.
|
||||||
|
pub const DEFAULT_WIDTH: isize = 80;
|
||||||
|
pub const DEFAULT_HEIGHT: isize = 24;
|
||||||
|
|
||||||
|
/// Construct from width and height.
|
||||||
|
pub fn new(width: isize, height: isize) -> Self {
|
||||||
|
Self { width, height }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a default-sized termsize.
|
||||||
|
pub fn defaults() -> Self {
|
||||||
|
Self::new(Self::DEFAULT_WIDTH, Self::DEFAULT_HEIGHT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TermsizeData {
|
||||||
|
// The last termsize returned by TIOCGWINSZ, or none if none.
|
||||||
|
last_from_tty: Option<Termsize>,
|
||||||
|
// The last termsize seen from the environment (COLUMNS/LINES), or none if none.
|
||||||
|
last_from_env: Option<Termsize>,
|
||||||
|
// The last-seen tty-invalidation generation count.
|
||||||
|
// Set to a huge value so it's initially stale.
|
||||||
|
last_tty_gen_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermsizeData {
|
||||||
|
const fn defaults() -> Self {
|
||||||
|
Self {
|
||||||
|
last_from_tty: None,
|
||||||
|
last_from_env: None,
|
||||||
|
last_tty_gen_count: u32::max_value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \return the current termsize from this data.
|
||||||
|
fn current(&self) -> Termsize {
|
||||||
|
// This encapsulates our ordering logic. If we have a termsize from a tty, use it; otherwise use
|
||||||
|
// what we have seen from the environment.
|
||||||
|
if let Some(ts) = self.last_from_tty {
|
||||||
|
ts
|
||||||
|
} else if let Some(ts) = self.last_from_env {
|
||||||
|
ts
|
||||||
|
} else {
|
||||||
|
Termsize::defaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark that our termsize is (for the time being) from the environment, not the tty.
|
||||||
|
fn mark_override_from_env(&mut self, ts: Termsize) {
|
||||||
|
self.last_from_env = Some(ts);
|
||||||
|
self.last_from_tty = None;
|
||||||
|
self.last_tty_gen_count = TTY_TERMSIZE_GEN_COUNT.load(Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Termsize monitoring is more complicated than one may think.
|
||||||
|
/// The main source of complexity is the interaction between the environment variables COLUMNS/ROWS,
|
||||||
|
/// the WINCH signal, and the TIOCGWINSZ ioctl.
|
||||||
|
/// Our policy is "last seen wins": if COLUMNS or LINES is modified, we respect that until we get a
|
||||||
|
/// SIGWINCH.
|
||||||
|
pub struct TermsizeContainer {
|
||||||
|
// Our lock-protected data.
|
||||||
|
data: Mutex<TermsizeData>,
|
||||||
|
|
||||||
|
// An indication that we are currently in the process of setting COLUMNS and LINES, and so do
|
||||||
|
// not react to any changes.
|
||||||
|
setting_env_vars: AtomicBool,
|
||||||
|
|
||||||
|
/// A function used for accessing the termsize from the tty. This is only exposed for testing.
|
||||||
|
tty_size_reader: fn() -> Option<Termsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermsizeContainer {
|
||||||
|
/// \return the termsize without applying any updates.
|
||||||
|
/// Return the default termsize if none.
|
||||||
|
pub fn last(&self) -> Termsize {
|
||||||
|
self.data.lock().unwrap().current()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize our termsize, using the given environment stack.
|
||||||
|
/// This will prefer to use COLUMNS and LINES, but will fall back to the tty size reader.
|
||||||
|
/// This does not change any variables in the environment.
|
||||||
|
pub fn initialize(&mut self, vars: &environment_t) -> Termsize {
|
||||||
|
let new_termsize = Termsize {
|
||||||
|
width: var_to_int_or(vars.get_as_string_flags(L!("COLUMNS"), EnvMode::GLOBAL), -1),
|
||||||
|
height: var_to_int_or(vars.get_as_string_flags(L!("LINES"), EnvMode::GLOBAL), -1),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut data = self.data.lock().unwrap();
|
||||||
|
if new_termsize.width > 0 && new_termsize.height > 0 {
|
||||||
|
data.mark_override_from_env(new_termsize);
|
||||||
|
} else {
|
||||||
|
data.last_tty_gen_count = TTY_TERMSIZE_GEN_COUNT.load(Ordering::Relaxed);
|
||||||
|
data.last_from_tty = (self.tty_size_reader)();
|
||||||
|
}
|
||||||
|
data.current()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If our termsize is stale, update it, using \p parser firing any events that may be
|
||||||
|
/// registered for COLUMNS and LINES.
|
||||||
|
/// \return the updated termsize.
|
||||||
|
pub fn updating(&mut self, parser: &mut parser_t) -> Termsize {
|
||||||
|
let new_size;
|
||||||
|
let prev_size;
|
||||||
|
|
||||||
|
// Take the lock in a local region.
|
||||||
|
// Capture the size before and the new size.
|
||||||
|
{
|
||||||
|
let mut data = self.data.lock().unwrap();
|
||||||
|
prev_size = data.current();
|
||||||
|
|
||||||
|
// Critical read of signal-owned variable.
|
||||||
|
// This must happen before the TIOCGWINSZ ioctl.
|
||||||
|
let tty_gen_count: u32 = TTY_TERMSIZE_GEN_COUNT.load(Ordering::Relaxed);
|
||||||
|
if data.last_tty_gen_count != tty_gen_count {
|
||||||
|
// Our idea of the size of the terminal may be stale.
|
||||||
|
// Apply any updates.
|
||||||
|
data.last_tty_gen_count = tty_gen_count;
|
||||||
|
data.last_from_tty = (self.tty_size_reader)();
|
||||||
|
}
|
||||||
|
new_size = data.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Announce any updates.
|
||||||
|
if new_size != prev_size {
|
||||||
|
self.set_columns_lines_vars(new_size, parser);
|
||||||
|
}
|
||||||
|
new_size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_columns_lines_vars(&mut self, val: Termsize, parser: &mut parser_t) {
|
||||||
|
let saved = self.setting_env_vars.swap(true, Ordering::Relaxed);
|
||||||
|
parser.pin().set_var_and_fire(
|
||||||
|
&L!("COLUMNS").to_ffi(),
|
||||||
|
EnvMode::GLOBAL.bits(),
|
||||||
|
val.width.to_wstring().to_ffi(),
|
||||||
|
);
|
||||||
|
parser.pin().set_var_and_fire(
|
||||||
|
&L!("LINES").to_ffi(),
|
||||||
|
EnvMode::GLOBAL.bits(),
|
||||||
|
val.height.to_wstring().to_ffi(),
|
||||||
|
);
|
||||||
|
self.setting_env_vars.store(saved, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note that COLUMNS and/or LINES global variables changed.
|
||||||
|
fn handle_columns_lines_var_change(&self, vars: &environment_t) {
|
||||||
|
// Do nothing if we are the ones setting it.
|
||||||
|
if self.setting_env_vars.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Construct a new termsize from COLUMNS and LINES, then set it in our data.
|
||||||
|
let new_termsize = Termsize {
|
||||||
|
width: var_to_int_or(
|
||||||
|
vars.get_as_string_flags(L!("COLUMNS"), EnvMode::GLOBAL),
|
||||||
|
Termsize::DEFAULT_WIDTH,
|
||||||
|
),
|
||||||
|
height: var_to_int_or(
|
||||||
|
vars.get_as_string_flags(L!("LINES"), EnvMode::GLOBAL),
|
||||||
|
Termsize::DEFAULT_HEIGHT,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store our termsize as an environment override.
|
||||||
|
self.data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.mark_override_from_env(new_termsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note that a WINCH signal is received.
|
||||||
|
/// Naturally this may be called from within a signal handler.
|
||||||
|
pub fn handle_winch() {
|
||||||
|
TTY_TERMSIZE_GEN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invalidate_tty() {
|
||||||
|
TTY_TERMSIZE_GEN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SHARED_CONTAINER: TermsizeContainer = TermsizeContainer {
|
||||||
|
data: Mutex::new(TermsizeData::defaults()),
|
||||||
|
setting_env_vars: AtomicBool::new(false),
|
||||||
|
tty_size_reader: read_termsize_from_tty,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _: () = assert_sync::<TermsizeContainer>();
|
||||||
|
|
||||||
|
/// Convenience helper to return the last known termsize.
|
||||||
|
pub fn termsize_last() -> Termsize {
|
||||||
|
return SHARED_CONTAINER.last();
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::ffi_tests::add_test;
|
||||||
|
add_test!("test_termsize", || {
|
||||||
|
let env_global = EnvMode::GLOBAL;
|
||||||
|
let parser: &mut parser_t = unsafe { &mut *parser_t::principal_parser_ffi() };
|
||||||
|
|
||||||
|
// Use a static variable so we can pretend we're the kernel exposing a terminal size.
|
||||||
|
static STUBBY_TERMSIZE: Mutex<Option<Termsize>> = Mutex::new(None);
|
||||||
|
fn stubby_termsize() -> Option<Termsize> {
|
||||||
|
*STUBBY_TERMSIZE.lock().unwrap()
|
||||||
|
}
|
||||||
|
let mut ts = TermsizeContainer {
|
||||||
|
data: Mutex::new(TermsizeData::defaults()),
|
||||||
|
setting_env_vars: AtomicBool::new(false),
|
||||||
|
tty_size_reader: stubby_termsize,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initially default value.
|
||||||
|
assert_eq!(ts.last(), Termsize::defaults());
|
||||||
|
|
||||||
|
// Haha we change the value, it doesn't even know.
|
||||||
|
*STUBBY_TERMSIZE.lock().unwrap() = Some(Termsize {
|
||||||
|
width: 42,
|
||||||
|
height: 84,
|
||||||
|
});
|
||||||
|
assert_eq!(ts.last(), Termsize::defaults());
|
||||||
|
|
||||||
|
// Ok let's tell it. But it still doesn't update right away.
|
||||||
|
TermsizeContainer::handle_winch();
|
||||||
|
assert_eq!(ts.last(), Termsize::defaults());
|
||||||
|
|
||||||
|
// Ok now we tell it to update.
|
||||||
|
ts.updating(parser);
|
||||||
|
assert_eq!(ts.last(), Termsize::new(42, 84));
|
||||||
|
assert_eq!(parser.var_as_string(L!("COLUMNS")).unwrap(), "42");
|
||||||
|
assert_eq!(parser.var_as_string(L!("LINES")).unwrap(), "84");
|
||||||
|
|
||||||
|
// Wow someone set COLUMNS and LINES to a weird value.
|
||||||
|
// Now the tty's termsize doesn't matter.
|
||||||
|
parser.set_var(L!("COLUMNS"), &[L!("75")], env_global);
|
||||||
|
parser.set_var(L!("LINES"), &[L!("150")], env_global);
|
||||||
|
ts.handle_columns_lines_var_change(parser.get_var_stack_env());
|
||||||
|
assert_eq!(ts.last(), Termsize::new(75, 150));
|
||||||
|
assert_eq!(parser.var_as_string(L!("COLUMNS")).unwrap(), "75");
|
||||||
|
assert_eq!(parser.var_as_string(L!("LINES")).unwrap(), "150");
|
||||||
|
|
||||||
|
parser.set_var(L!("COLUMNS"), &[L!("33")], env_global);
|
||||||
|
ts.handle_columns_lines_var_change(parser.get_var_stack_env());
|
||||||
|
assert_eq!(ts.last(), Termsize::new(33, 150));
|
||||||
|
|
||||||
|
// Oh it got SIGWINCH, now the tty matters again.
|
||||||
|
TermsizeContainer::handle_winch();
|
||||||
|
assert_eq!(ts.last(), Termsize::new(33, 150));
|
||||||
|
assert_eq!(ts.updating(parser), stubby_termsize().unwrap());
|
||||||
|
assert_eq!(parser.var_as_string(L!("COLUMNS")).unwrap(), "42");
|
||||||
|
assert_eq!(parser.var_as_string(L!("LINES")).unwrap(), "84");
|
||||||
|
|
||||||
|
// Test initialize().
|
||||||
|
parser.set_var(L!("COLUMNS"), &[L!("83")], env_global);
|
||||||
|
parser.set_var(L!("LINES"), &[L!("38")], env_global);
|
||||||
|
ts.initialize(parser.get_var_stack_env());
|
||||||
|
assert_eq!(ts.last(), Termsize::new(83, 38));
|
||||||
|
|
||||||
|
// initialize() even beats the tty reader until a sigwinch.
|
||||||
|
let mut ts2 = TermsizeContainer {
|
||||||
|
data: Mutex::new(TermsizeData::defaults()),
|
||||||
|
setting_env_vars: AtomicBool::new(false),
|
||||||
|
tty_size_reader: stubby_termsize,
|
||||||
|
};
|
||||||
|
ts.initialize(parser.get_var_stack_env());
|
||||||
|
ts2.updating(parser);
|
||||||
|
assert_eq!(ts.last(), Termsize::new(83, 38));
|
||||||
|
TermsizeContainer::handle_winch();
|
||||||
|
assert_eq!(ts2.updating(parser), stubby_termsize().unwrap());
|
||||||
|
});
|
||||||
@@ -1,6 +1,89 @@
|
|||||||
use crate::wchar::{wstr, WString};
|
use crate::wchar::{wstr, WString};
|
||||||
use widestring::utfstr::CharsUtf32;
|
use widestring::utfstr::CharsUtf32;
|
||||||
|
|
||||||
|
/// Helpers to convert things to widestring.
|
||||||
|
/// This is like std::string::ToString.
|
||||||
|
pub trait ToWString {
|
||||||
|
fn to_wstring(&self) -> WString;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_wstring_impl(mut val: u64, neg: bool) -> WString {
|
||||||
|
// 20 digits max in u64: 18446744073709551616.
|
||||||
|
let mut digits = [0; 24];
|
||||||
|
let mut ndigits = 0;
|
||||||
|
while val > 0 {
|
||||||
|
digits[ndigits] = (val % 10) as u8;
|
||||||
|
val /= 10;
|
||||||
|
ndigits += 1;
|
||||||
|
}
|
||||||
|
if ndigits == 0 {
|
||||||
|
digits[0] = 0;
|
||||||
|
ndigits = 1;
|
||||||
|
}
|
||||||
|
let mut result = WString::with_capacity(ndigits + neg as usize);
|
||||||
|
if neg {
|
||||||
|
result.push('-');
|
||||||
|
}
|
||||||
|
for i in (0..ndigits).rev() {
|
||||||
|
result.push((digits[i] + b'0') as char);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement to_wstring() for signed types.
|
||||||
|
macro_rules! impl_to_wstring_signed {
|
||||||
|
($t:ty) => {
|
||||||
|
impl ToWString for $t {
|
||||||
|
fn to_wstring(&self) -> WString {
|
||||||
|
let val = *self as i64;
|
||||||
|
to_wstring_impl(val.unsigned_abs(), val < 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_to_wstring_signed!(i8);
|
||||||
|
impl_to_wstring_signed!(i16);
|
||||||
|
impl_to_wstring_signed!(i32);
|
||||||
|
impl_to_wstring_signed!(i64);
|
||||||
|
impl_to_wstring_signed!(isize);
|
||||||
|
|
||||||
|
/// Implement to_wstring() for unsigned types.
|
||||||
|
macro_rules! impl_to_wstring_unsigned {
|
||||||
|
($t:ty) => {
|
||||||
|
impl ToWString for $t {
|
||||||
|
fn to_wstring(&self) -> WString {
|
||||||
|
to_wstring_impl(*self as u64, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_to_wstring_unsigned!(u8);
|
||||||
|
impl_to_wstring_unsigned!(u16);
|
||||||
|
impl_to_wstring_unsigned!(u32);
|
||||||
|
impl_to_wstring_unsigned!(u64);
|
||||||
|
impl_to_wstring_unsigned!(usize);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_wstring() {
|
||||||
|
assert_eq!(0_u64.to_wstring(), "0");
|
||||||
|
assert_eq!(1_u64.to_wstring(), "1");
|
||||||
|
assert_eq!(0_i64.to_wstring(), "0");
|
||||||
|
assert_eq!(1_i64.to_wstring(), "1");
|
||||||
|
assert_eq!((-1_i64).to_wstring(), "-1");
|
||||||
|
assert_eq!((-5_i64).to_wstring(), "-5");
|
||||||
|
let mut val: i64 = 1;
|
||||||
|
loop {
|
||||||
|
assert_eq!(val.to_wstring(), val.to_string());
|
||||||
|
let Some(next) = val.checked_mul(-3) else { break; };
|
||||||
|
val = next;
|
||||||
|
}
|
||||||
|
assert_eq!(u64::MAX.to_wstring(), "18446744073709551615");
|
||||||
|
assert_eq!(i64::MIN.to_wstring(), "-9223372036854775808");
|
||||||
|
assert_eq!(i64::MAX.to_wstring(), "9223372036854775807");
|
||||||
|
}
|
||||||
|
|
||||||
/// A thing that a wide string can start with or end with.
|
/// A thing that a wide string can start with or end with.
|
||||||
/// It must have a chars() method which returns a double-ended char iterator.
|
/// It must have a chars() method which returns a double-ended char iterator.
|
||||||
pub trait CharPrefixSuffix {
|
pub trait CharPrefixSuffix {
|
||||||
|
|||||||
@@ -1414,6 +1414,12 @@ int env_stack_t::set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t
|
|||||||
return ret.status;
|
return ret.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, wcstring_list_t(ptr, ptr + count));
|
||||||
|
}
|
||||||
|
|
||||||
int env_stack_t::set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) {
|
int env_stack_t::set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) {
|
||||||
wcstring_list_t vals;
|
wcstring_list_t vals;
|
||||||
vals.push_back(std::move(val));
|
vals.push_back(std::move(val));
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ class env_stack_t final : public environment_t {
|
|||||||
/// Sets the variable with the specified name to the given values.
|
/// Sets the variable with the specified name to the given values.
|
||||||
int set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals);
|
int set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t 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.
|
/// Sets the variable with the specified name to a single value.
|
||||||
int set_one(const wcstring &key, env_mode_flags_t mode, wcstring val);
|
int set_one(const wcstring &key, env_mode_flags_t mode, wcstring val);
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ parser_t &parser_t::principal_parser() {
|
|||||||
return *principal;
|
return *principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser_t *parser_t::principal_parser_ffi() { return &principal_parser(); }
|
||||||
|
|
||||||
void parser_t::assert_can_execute() const { ASSERT_IS_MAIN_THREAD(); }
|
void parser_t::assert_can_execute() const { ASSERT_IS_MAIN_THREAD(); }
|
||||||
|
|
||||||
rust::Box<WaitHandleStoreFFI> &parser_t::get_wait_handles_ffi() { return wait_handles; }
|
rust::Box<WaitHandleStoreFFI> &parser_t::get_wait_handles_ffi() { return wait_handles; }
|
||||||
|
|||||||
@@ -315,6 +315,9 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
|
|||||||
/// Get the "principal" parser, whatever that is.
|
/// Get the "principal" parser, whatever that is.
|
||||||
static parser_t &principal_parser();
|
static parser_t &principal_parser();
|
||||||
|
|
||||||
|
/// ffi helper. Obviously this is totally bogus.
|
||||||
|
static parser_t *principal_parser_ffi();
|
||||||
|
|
||||||
/// Assert that this parser is allowed to execute on the current thread.
|
/// Assert that this parser is allowed to execute on the current thread.
|
||||||
void assert_can_execute() const;
|
void assert_can_execute() const;
|
||||||
|
|
||||||
@@ -388,6 +391,9 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
|
|||||||
env_stack_t &vars() { return *variables; }
|
env_stack_t &vars() { return *variables; }
|
||||||
const env_stack_t &vars() const { return *variables; }
|
const env_stack_t &vars() const { return *variables; }
|
||||||
|
|
||||||
|
/// Rust helper - variables as an environment_t.
|
||||||
|
const environment_t &vars_env_ffi() const { return *variables; }
|
||||||
|
|
||||||
int remove_var_ffi(const wcstring &key, int mode) { return vars().remove(key, mode); }
|
int remove_var_ffi(const wcstring &key, int mode) { return vars().remove(key, mode); }
|
||||||
|
|
||||||
/// Get the library data.
|
/// Get the library data.
|
||||||
|
|||||||
Reference in New Issue
Block a user