mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-10 07:04:29 +00:00
Adopt the new termsize
This eliminates the C++ version.
This commit is contained in:
parent
6ec35ce182
commit
732f7284d4
13 changed files with 94 additions and 368 deletions
|
@ -123,7 +123,7 @@ set(FISH_SRCS
|
|||
src/pager.cpp src/parse_execution.cpp src/parse_tree.cpp src/parse_util.cpp
|
||||
src/parser.cpp src/parser_keywords.cpp src/path.cpp src/postfork.cpp
|
||||
src/proc.cpp src/re.cpp src/reader.cpp src/screen.cpp
|
||||
src/signals.cpp src/termsize.cpp src/tinyexpr.cpp
|
||||
src/signals.cpp src/tinyexpr.cpp
|
||||
src/trace.cpp src/utf8.cpp
|
||||
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp
|
||||
src/wutil.cpp src/fds.cpp src/rustffi.cpp
|
||||
|
|
|
@ -35,6 +35,7 @@ fn main() -> miette::Result<()> {
|
|||
"src/parse_constants.rs",
|
||||
"src/redirection.rs",
|
||||
"src/smoke.rs",
|
||||
"src/termsize.rs",
|
||||
"src/timer.rs",
|
||||
"src/tokenizer.rs",
|
||||
"src/topic_monitor.rs",
|
||||
|
|
|
@ -14,11 +14,10 @@ use widestring_suffix::widestrs;
|
|||
|
||||
use crate::builtins::shared::io_streams_t;
|
||||
use crate::common::{escape_string, replace_with, EscapeFlags, EscapeStringStyle, ScopeGuard};
|
||||
use crate::ffi::{
|
||||
self, block_t, parser_t, signal_check_cancel, signal_handle, termsize_container_t, Repin,
|
||||
};
|
||||
use crate::ffi::{self, block_t, parser_t, signal_check_cancel, signal_handle, Repin};
|
||||
use crate::flog::FLOG;
|
||||
use crate::signal::{sig2wcs, signal_get_desc};
|
||||
use crate::termsize;
|
||||
use crate::wchar::{wstr, WString, L};
|
||||
use crate::wchar_ffi::{wcharz_t, AsWstr, WCharFromFFI, WCharToFFI};
|
||||
use crate::wutil::sprintf;
|
||||
|
@ -783,7 +782,7 @@ pub fn fire_delayed(parser: &mut parser_t) {
|
|||
// HACK: The only variables we change in response to a *signal* are $COLUMNS and $LINES.
|
||||
// Do that now.
|
||||
if sig == libc::SIGWINCH {
|
||||
termsize_container_t::ffi_updating(parser.pin()).within_unique_ptr();
|
||||
termsize::SHARED_CONTAINER.updating(parser);
|
||||
}
|
||||
let event = Event {
|
||||
desc: EventDescription {
|
||||
|
|
|
@ -36,7 +36,6 @@ include_cpp! {
|
|||
#include "tokenizer.h"
|
||||
#include "wildcard.h"
|
||||
#include "wutil.h"
|
||||
#include "termsize.h"
|
||||
|
||||
// We need to block these types so when exposing C++ to Rust.
|
||||
block!("WaitHandleStoreFFI")
|
||||
|
@ -111,7 +110,6 @@ include_cpp! {
|
|||
generate!("statuses_t")
|
||||
generate!("io_chain_t")
|
||||
|
||||
generate!("termsize_container_t")
|
||||
generate!("env_var_t")
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,30 @@ use crate::wutil::fish_wcstoi;
|
|||
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.
|
||||
pub width: isize,
|
||||
|
||||
/// Height of the terminal, in rows.
|
||||
pub height: isize,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
pub fn termsize_default() -> Termsize;
|
||||
pub fn termsize_last() -> Termsize;
|
||||
pub fn termsize_initialize_ffi(vars: *const u8) -> Termsize;
|
||||
pub fn termsize_invalidate_tty();
|
||||
pub fn handle_columns_lines_var_change_ffi(vars: *const u8);
|
||||
pub fn termsize_update_ffi(parser: *mut u8) -> Termsize;
|
||||
pub fn termsize_handle_winch();
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -58,15 +82,6 @@ fn read_termsize_from_tty() -> Option<Termsize> {
|
|||
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;
|
||||
|
@ -150,7 +165,7 @@ impl TermsizeContainer {
|
|||
/// 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 {
|
||||
pub fn initialize(&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),
|
||||
|
@ -166,10 +181,11 @@ impl TermsizeContainer {
|
|||
data.current()
|
||||
}
|
||||
|
||||
/// If our termsize is stale, update it, using \p parser firing any events that may be
|
||||
/// If our termsize is stale, update it, using \p parser to fire any events that may be
|
||||
/// registered for COLUMNS and LINES.
|
||||
/// This requires a shared reference so it can work from a static.
|
||||
/// \return the updated termsize.
|
||||
pub fn updating(&mut self, parser: &mut parser_t) -> Termsize {
|
||||
pub fn updating(&self, parser: &mut parser_t) -> Termsize {
|
||||
let new_size;
|
||||
let prev_size;
|
||||
|
||||
|
@ -198,7 +214,7 @@ impl TermsizeContainer {
|
|||
new_size
|
||||
}
|
||||
|
||||
fn set_columns_lines_vars(&mut self, val: Termsize, parser: &mut parser_t) {
|
||||
fn set_columns_lines_vars(&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(),
|
||||
|
@ -249,7 +265,7 @@ impl TermsizeContainer {
|
|||
}
|
||||
}
|
||||
|
||||
static SHARED_CONTAINER: TermsizeContainer = TermsizeContainer {
|
||||
pub static SHARED_CONTAINER: TermsizeContainer = TermsizeContainer {
|
||||
data: Mutex::new(TermsizeData::defaults()),
|
||||
setting_env_vars: AtomicBool::new(false),
|
||||
tty_size_reader: read_termsize_from_tty,
|
||||
|
@ -257,11 +273,48 @@ static SHARED_CONTAINER: TermsizeContainer = TermsizeContainer {
|
|||
|
||||
const _: () = assert_sync::<TermsizeContainer>();
|
||||
|
||||
/// Helper to return the default termsize.
|
||||
pub fn termsize_default() -> Termsize {
|
||||
Termsize::defaults()
|
||||
}
|
||||
|
||||
/// Convenience helper to return the last known termsize.
|
||||
pub fn termsize_last() -> Termsize {
|
||||
return SHARED_CONTAINER.last();
|
||||
}
|
||||
|
||||
/// Called when the COLUMNS or LINES variables are changed.
|
||||
/// The pointer is to an environment_t, but has the wrong type to satisfy cxx.
|
||||
pub fn handle_columns_lines_var_change_ffi(vars_ptr: *const u8) {
|
||||
assert!(!vars_ptr.is_null());
|
||||
let vars: &environment_t = unsafe { &*(vars_ptr as *const environment_t) };
|
||||
SHARED_CONTAINER.handle_columns_lines_var_change(vars);
|
||||
}
|
||||
|
||||
/// Called to initialize the termsize.
|
||||
/// The pointer is to an environment_t, but has the wrong type to satisfy cxx.
|
||||
pub fn termsize_initialize_ffi(vars_ptr: *const u8) -> Termsize {
|
||||
assert!(!vars_ptr.is_null());
|
||||
let vars: &environment_t = unsafe { &*(vars_ptr as *const environment_t) };
|
||||
SHARED_CONTAINER.initialize(vars)
|
||||
}
|
||||
|
||||
/// Called to update termsize.
|
||||
pub fn termsize_update_ffi(parser_ptr: *mut u8) -> Termsize {
|
||||
assert!(!parser_ptr.is_null());
|
||||
let parser: &mut parser_t = unsafe { &mut *(parser_ptr as *mut parser_t) };
|
||||
SHARED_CONTAINER.updating(parser)
|
||||
}
|
||||
|
||||
/// FFI bridge for WINCH.
|
||||
pub fn termsize_handle_winch() {
|
||||
TermsizeContainer::handle_winch();
|
||||
}
|
||||
|
||||
pub fn termsize_invalidate_tty() {
|
||||
TermsizeContainer::invalidate_tty();
|
||||
}
|
||||
|
||||
use crate::ffi_tests::add_test;
|
||||
add_test!("test_termsize", || {
|
||||
let env_global = EnvMode::GLOBAL;
|
||||
|
@ -272,7 +325,7 @@ add_test!("test_termsize", || {
|
|||
fn stubby_termsize() -> Option<Termsize> {
|
||||
*STUBBY_TERMSIZE.lock().unwrap()
|
||||
}
|
||||
let mut ts = TermsizeContainer {
|
||||
let ts = TermsizeContainer {
|
||||
data: Mutex::new(TermsizeData::defaults()),
|
||||
setting_env_vars: AtomicBool::new(false),
|
||||
tty_size_reader: stubby_termsize,
|
||||
|
@ -325,7 +378,7 @@ add_test!("test_termsize", || {
|
|||
assert_eq!(ts.last(), Termsize::new(83, 38));
|
||||
|
||||
// initialize() even beats the tty reader until a sigwinch.
|
||||
let mut ts2 = TermsizeContainer {
|
||||
let ts2 = TermsizeContainer {
|
||||
data: Mutex::new(TermsizeData::defaults()),
|
||||
setting_env_vars: AtomicBool::new(false),
|
||||
tty_size_reader: stubby_termsize,
|
||||
|
|
|
@ -417,7 +417,8 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa
|
|||
}
|
||||
|
||||
// Initialize termsize variables.
|
||||
auto termsize = termsize_container_t::shared().initialize(vars);
|
||||
environment_t &env_vars = vars;
|
||||
auto termsize = termsize_initialize_ffi(reinterpret_cast<const unsigned char *>(&env_vars));
|
||||
if (vars.get(L"COLUMNS").missing_or_empty())
|
||||
vars.set_one(L"COLUMNS", ENV_GLOBAL, to_string(termsize.width));
|
||||
if (vars.get(L"LINES").missing_or_empty())
|
||||
|
|
|
@ -207,7 +207,9 @@ static void handle_change_ambiguous_width(const env_stack_t &vars) {
|
|||
}
|
||||
|
||||
static void handle_term_size_change(const env_stack_t &vars) {
|
||||
termsize_container_t::shared().handle_columns_lines_var_change(vars);
|
||||
// Need to use a pointer to send this through cxx ffi.
|
||||
const environment_t &env_vars = vars;
|
||||
handle_columns_lines_var_change_ffi(reinterpret_cast<const unsigned char *>(&env_vars));
|
||||
}
|
||||
|
||||
static void handle_fish_history_change(const env_stack_t &vars) {
|
||||
|
|
|
@ -2402,7 +2402,7 @@ static void test_pager_navigation() {
|
|||
|
||||
pager_t pager;
|
||||
pager.set_completions(completions);
|
||||
pager.set_term_size(termsize_t::defaults());
|
||||
pager.set_term_size(termsize_default());
|
||||
page_rendering_t render = pager.render();
|
||||
|
||||
if (render.term_width != 80) err(L"Wrong term width");
|
||||
|
@ -6715,72 +6715,6 @@ static void test_re_substitute() {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
struct termsize_tester_t {
|
||||
static void test();
|
||||
};
|
||||
|
||||
void termsize_tester_t::test() {
|
||||
say(L"Testing termsize");
|
||||
|
||||
parser_t &parser = parser_t::principal_parser();
|
||||
env_stack_t &vars = parser.vars();
|
||||
|
||||
// Use a static variable so we can pretend we're the kernel exposing a terminal size.
|
||||
static maybe_t<termsize_t> stubby_termsize{};
|
||||
termsize_container_t ts([] { return stubby_termsize; });
|
||||
|
||||
// Initially default value.
|
||||
do_test(ts.last() == termsize_t::defaults());
|
||||
|
||||
// Haha we change the value, it doesn't even know.
|
||||
stubby_termsize = termsize_t{42, 84};
|
||||
do_test(ts.last() == termsize_t::defaults());
|
||||
|
||||
// Ok let's tell it. But it still doesn't update right away.
|
||||
ts.handle_winch();
|
||||
do_test(ts.last() == termsize_t::defaults());
|
||||
|
||||
// Ok now we tell it to update.
|
||||
ts.updating(parser);
|
||||
do_test(ts.last() == *stubby_termsize);
|
||||
do_test(vars.get(L"COLUMNS")->as_string() == L"42");
|
||||
do_test(vars.get(L"LINES")->as_string() == L"84");
|
||||
|
||||
// Wow someone set COLUMNS and LINES to a weird value.
|
||||
// Now the tty's termsize doesn't matter.
|
||||
vars.set(L"COLUMNS", ENV_GLOBAL, {L"75"});
|
||||
vars.set(L"LINES", ENV_GLOBAL, {L"150"});
|
||||
ts.handle_columns_lines_var_change(vars);
|
||||
do_test(ts.last() == termsize_t(75, 150));
|
||||
do_test(vars.get(L"COLUMNS")->as_string() == L"75");
|
||||
do_test(vars.get(L"LINES")->as_string() == L"150");
|
||||
|
||||
vars.set(L"COLUMNS", ENV_GLOBAL, {L"33"});
|
||||
ts.handle_columns_lines_var_change(vars);
|
||||
do_test(ts.last() == termsize_t(33, 150));
|
||||
|
||||
// Oh it got SIGWINCH, now the tty matters again.
|
||||
ts.handle_winch();
|
||||
do_test(ts.last() == termsize_t(33, 150));
|
||||
do_test(ts.updating(parser) == *stubby_termsize);
|
||||
do_test(vars.get(L"COLUMNS")->as_string() == L"42");
|
||||
do_test(vars.get(L"LINES")->as_string() == L"84");
|
||||
|
||||
// Test initialize().
|
||||
vars.set(L"COLUMNS", ENV_GLOBAL, {L"83"});
|
||||
vars.set(L"LINES", ENV_GLOBAL, {L"38"});
|
||||
ts.initialize(vars);
|
||||
do_test(ts.last() == termsize_t(83, 38));
|
||||
|
||||
// initialize() even beats the tty reader until a sigwinch.
|
||||
termsize_container_t ts2([] { return stubby_termsize; });
|
||||
ts.initialize(vars);
|
||||
ts2.updating(parser);
|
||||
do_test(ts.last() == termsize_t(83, 38));
|
||||
ts2.handle_winch();
|
||||
do_test(ts2.updating(parser) == *stubby_termsize);
|
||||
}
|
||||
|
||||
void test_wgetopt() {
|
||||
// Regression test for a crash.
|
||||
const wchar_t *const short_options = L"-a";
|
||||
|
@ -6938,7 +6872,6 @@ static const test_t s_tests[]{
|
|||
{TEST_GROUP("topics"), test_topic_monitor_torture},
|
||||
{TEST_GROUP("pipes"), test_pipes},
|
||||
{TEST_GROUP("fd_event"), test_fd_event_signaller},
|
||||
{TEST_GROUP("termsize"), termsize_tester_t::test},
|
||||
{TEST_GROUP("killring"), test_killring},
|
||||
{TEST_GROUP("re"), test_re_errs},
|
||||
{TEST_GROUP("re"), test_re_basic},
|
||||
|
|
|
@ -931,7 +931,7 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
|
|||
void delete_char(bool backward = true);
|
||||
|
||||
/// Called to update the termsize, including $COLUMNS and $LINES, as necessary.
|
||||
void update_termsize() { (void)termsize_container_t::shared().updating(parser()); }
|
||||
void update_termsize() { termsize_update_ffi(reinterpret_cast<unsigned char *>(&parser())); }
|
||||
|
||||
// Import history from older location (config path) if our current history is empty.
|
||||
void import_history_if_necessary();
|
||||
|
@ -1064,7 +1064,7 @@ static void term_steal() {
|
|||
}
|
||||
}
|
||||
|
||||
termsize_container_t::shared().invalidate_tty();
|
||||
termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
bool fish_is_unwinding_for_exit() {
|
||||
|
@ -1292,7 +1292,7 @@ static history_pager_result_t history_pager_search(const std::shared_ptr<history
|
|||
// We can still push fish further upward in case the first entry is multiline,
|
||||
// but that can't really be helped.
|
||||
// (subtract 2 for the search line and the prompt)
|
||||
size_t page_size = std::max(termsize_last().height / 2 - 2, 12);
|
||||
size_t page_size = std::max(termsize_last().height / 2 - 2, (rust::isize)12);
|
||||
|
||||
completion_list_t completions;
|
||||
history_search_t search{history, search_string, history_search_type_t::contains,
|
||||
|
@ -2587,7 +2587,7 @@ static void reader_interactive_init(parser_t &parser) {
|
|||
}
|
||||
}
|
||||
|
||||
termsize_container_t::shared().invalidate_tty();
|
||||
termsize_invalidate_tty();
|
||||
|
||||
// Provide value for `status current-command`
|
||||
parser.libdata().status_vars.command = L"fish";
|
||||
|
@ -3353,7 +3353,7 @@ void reader_data_t::run_input_command_scripts(const wcstring_list_t &cmds) {
|
|||
if (res < 0) {
|
||||
wperror(L"tcsetattr");
|
||||
}
|
||||
termsize_container_t::shared().invalidate_tty();
|
||||
termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
/// Read normal characters, inserting them into the command line.
|
||||
|
|
|
@ -1209,8 +1209,9 @@ void screen_t::write(const wcstring &left_prompt, const wcstring &right_prompt,
|
|||
|
||||
// Re-render our completions page if necessary. Limit the term size of the pager to the true
|
||||
// term size, minus the number of lines consumed by our string.
|
||||
pager.set_term_size(termsize_t{std::max(1, curr_termsize.width),
|
||||
std::max(1, curr_termsize.height - full_line_count)});
|
||||
pager.set_term_size(
|
||||
termsize_t{std::max((rust::isize)1, curr_termsize.width),
|
||||
std::max((rust::isize)1, curr_termsize.height - full_line_count)});
|
||||
pager.update_rendering(&page_rendering);
|
||||
// Append pager_data (none if empty).
|
||||
this->desired.append_lines(page_rendering.screen_data);
|
||||
|
|
|
@ -234,7 +234,7 @@ static void fish_signal_handler(int sig, siginfo_t *info, void *context) {
|
|||
#ifdef SIGWINCH
|
||||
case SIGWINCH:
|
||||
// Respond to a winch signal by telling the termsize container.
|
||||
termsize_container_t::handle_winch();
|
||||
termsize_handle_winch();
|
||||
break;
|
||||
#endif
|
||||
|
||||
|
|
155
src/termsize.cpp
155
src/termsize.cpp
|
@ -1,155 +0,0 @@
|
|||
// Support for exposing the terminal size.
|
||||
|
||||
#include "termsize.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
|
||||
#include "env.h"
|
||||
#include "flog.h"
|
||||
#include "maybe.h"
|
||||
#include "parser.h"
|
||||
#include "wcstringutil.h"
|
||||
#include "wutil.h"
|
||||
|
||||
#ifdef HAVE_WINSIZE
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#endif
|
||||
|
||||
// A counter which is incremented every SIGWINCH, or when the tty is otherwise invalidated.
|
||||
static volatile uint32_t s_tty_termsize_gen_count{0};
|
||||
|
||||
/// \return a termsize from ioctl, or none on error or if not supported.
|
||||
static maybe_t<termsize_t> read_termsize_from_tty() {
|
||||
maybe_t<termsize_t> result{};
|
||||
#ifdef HAVE_WINSIZE
|
||||
struct winsize winsize = {0, 0, 0, 0};
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) >= 0) {
|
||||
// 0 values are unusable, fall back to the default instead.
|
||||
if (winsize.ws_col == 0) {
|
||||
FLOGF(term_support, L"Terminal has 0 columns, falling back to default width");
|
||||
winsize.ws_col = termsize_t::DEFAULT_WIDTH;
|
||||
}
|
||||
if (winsize.ws_row == 0) {
|
||||
FLOGF(term_support, L"Terminal has 0 rows, falling back to default height");
|
||||
winsize.ws_row = termsize_t::DEFAULT_HEIGHT;
|
||||
}
|
||||
result = termsize_t(winsize.ws_col, winsize.ws_row);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
termsize_container_t &termsize_container_t::shared() {
|
||||
// Heap-allocated to avoid runtime dtor registration.
|
||||
static auto *res = new termsize_container_t(read_termsize_from_tty);
|
||||
return *res;
|
||||
}
|
||||
|
||||
termsize_t termsize_container_t::ffi_updating(parser_t &parser) {
|
||||
return shared().updating(parser);
|
||||
}
|
||||
|
||||
termsize_t termsize_container_t::data_t::current() const {
|
||||
// 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 (this->last_from_tty) return *this->last_from_tty;
|
||||
if (this->last_from_env) return *this->last_from_env;
|
||||
return termsize_t::defaults();
|
||||
}
|
||||
|
||||
void termsize_container_t::data_t::mark_override_from_env(termsize_t ts) {
|
||||
// Here we pretend to have an up-to-date tty value so that we will prefer the environment value.
|
||||
this->last_from_env = ts;
|
||||
this->last_from_tty.reset();
|
||||
this->last_tty_gen_count = s_tty_termsize_gen_count;
|
||||
}
|
||||
|
||||
termsize_t termsize_container_t::last() const { return this->data_.acquire()->current(); }
|
||||
|
||||
termsize_t termsize_container_t::updating(parser_t &parser) {
|
||||
termsize_t new_size = termsize_t::defaults();
|
||||
termsize_t prev_size = termsize_t::defaults();
|
||||
|
||||
// Take the lock in a local region.
|
||||
// Capture the size before and the new size.
|
||||
{
|
||||
auto data = data_.acquire();
|
||||
prev_size = data->current();
|
||||
|
||||
// Critical read of signal-owned variable.
|
||||
// This must happen before the TIOCGWINSZ ioctl.
|
||||
const uint32_t tty_gen_count = s_tty_termsize_gen_count;
|
||||
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 = this->tty_size_reader_();
|
||||
}
|
||||
new_size = data->current();
|
||||
}
|
||||
|
||||
// Announce any updates.
|
||||
if (new_size != prev_size) set_columns_lines_vars(new_size, parser);
|
||||
return new_size;
|
||||
}
|
||||
|
||||
void termsize_container_t::set_columns_lines_vars(termsize_t val, parser_t &parser) {
|
||||
const bool saved = setting_env_vars_;
|
||||
setting_env_vars_ = true;
|
||||
parser.set_var_and_fire(L"COLUMNS", ENV_GLOBAL, to_string(val.width));
|
||||
parser.set_var_and_fire(L"LINES", ENV_GLOBAL, to_string(val.height));
|
||||
setting_env_vars_ = saved;
|
||||
}
|
||||
|
||||
/// Convert an environment variable to an int, or return a default value.
|
||||
/// The int must be >0 and <USHRT_MAX (from struct winsize).
|
||||
static int var_to_int_or(const maybe_t<env_var_t> &var, int def) {
|
||||
if (var.has_value() && !var->empty()) {
|
||||
errno = 0;
|
||||
int proposed = fish_wcstoi(var->as_string().c_str());
|
||||
if (errno == 0 && proposed > 0 && proposed <= USHRT_MAX) {
|
||||
return proposed;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
termsize_t termsize_container_t::initialize(const environment_t &vars) {
|
||||
termsize_t new_termsize{
|
||||
var_to_int_or(vars.get(L"COLUMNS", ENV_GLOBAL), -1),
|
||||
var_to_int_or(vars.get(L"LINES", ENV_GLOBAL), -1),
|
||||
};
|
||||
auto data = data_.acquire();
|
||||
if (new_termsize.width > 0 && new_termsize.height > 0) {
|
||||
data->mark_override_from_env(new_termsize);
|
||||
} else {
|
||||
data->last_tty_gen_count = s_tty_termsize_gen_count;
|
||||
data->last_from_tty = this->tty_size_reader_();
|
||||
}
|
||||
return data->current();
|
||||
}
|
||||
|
||||
void termsize_container_t::handle_columns_lines_var_change(const environment_t &vars) {
|
||||
// Do nothing if we are the ones setting it.
|
||||
if (setting_env_vars_) return;
|
||||
|
||||
// Construct a new termsize from COLUMNS and LINES, then set it in our data.
|
||||
termsize_t new_termsize{
|
||||
var_to_int_or(vars.get(L"COLUMNS", ENV_GLOBAL), termsize_t::DEFAULT_WIDTH),
|
||||
var_to_int_or(vars.get(L"LINES", ENV_GLOBAL), termsize_t::DEFAULT_HEIGHT),
|
||||
};
|
||||
|
||||
// Store our termsize as an environment override.
|
||||
data_.acquire()->mark_override_from_env(new_termsize);
|
||||
}
|
||||
|
||||
// static
|
||||
void termsize_container_t::handle_winch() { s_tty_termsize_gen_count += 1; }
|
||||
|
||||
// static
|
||||
void termsize_container_t::invalidate_tty() { s_tty_termsize_gen_count += 1; }
|
117
src/termsize.h
117
src/termsize.h
|
@ -4,117 +4,10 @@
|
|||
#ifndef FISH_TERMSIZE_H
|
||||
#define FISH_TERMSIZE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "global_safety.h"
|
||||
#include "maybe.h"
|
||||
|
||||
class environment_t;
|
||||
class parser_t;
|
||||
struct termsize_tester_t;
|
||||
|
||||
/// A simple value type wrapping up a terminal size.
|
||||
struct termsize_t {
|
||||
/// Default width and height.
|
||||
static constexpr int DEFAULT_WIDTH = 80;
|
||||
static constexpr int DEFAULT_HEIGHT = 24;
|
||||
|
||||
/// width of the terminal, in columns.
|
||||
int width{DEFAULT_WIDTH};
|
||||
|
||||
/// height of the terminal, in rows.
|
||||
int height{DEFAULT_HEIGHT};
|
||||
|
||||
/// Construct from width and height.
|
||||
termsize_t(int w, int h) : width(w), height(h) {}
|
||||
|
||||
/// Return a default-sized termsize.
|
||||
static termsize_t defaults() { return termsize_t{DEFAULT_WIDTH, DEFAULT_HEIGHT}; }
|
||||
|
||||
bool operator==(termsize_t rhs) const {
|
||||
return this->width == rhs.width && this->height == rhs.height;
|
||||
}
|
||||
|
||||
bool operator!=(termsize_t rhs) const { return !(*this == rhs); }
|
||||
};
|
||||
|
||||
/// 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.
|
||||
struct termsize_container_t {
|
||||
/// \return the termsize without applying any updates.
|
||||
/// Return the default termsize if none.
|
||||
termsize_t last() const;
|
||||
|
||||
/// 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.
|
||||
termsize_t updating(parser_t &parser);
|
||||
|
||||
/// 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.
|
||||
termsize_t initialize(const environment_t &vars);
|
||||
|
||||
/// Note that a WINCH signal is received.
|
||||
/// Naturally this may be called from within a signal handler.
|
||||
static void handle_winch();
|
||||
|
||||
/// Invalidate the tty in the sense that we need to re-fetch its termsize.
|
||||
static void invalidate_tty();
|
||||
|
||||
/// Note that COLUMNS and/or LINES global variables changed.
|
||||
void handle_columns_lines_var_change(const environment_t &vars);
|
||||
|
||||
/// \return the singleton shared container.
|
||||
static termsize_container_t &shared();
|
||||
|
||||
/// autocxx junk.
|
||||
static termsize_t ffi_updating(parser_t &parser);
|
||||
|
||||
private:
|
||||
/// A function used for accessing the termsize from the tty. This is only exposed for testing.
|
||||
using tty_size_reader_func_t = maybe_t<termsize_t> (*)();
|
||||
|
||||
struct data_t {
|
||||
// The last termsize returned by TIOCGWINSZ, or none if none.
|
||||
maybe_t<termsize_t> last_from_tty{};
|
||||
|
||||
// The last termsize seen from the environment (COLUMNS/LINES), or none if none.
|
||||
maybe_t<termsize_t> last_from_env{};
|
||||
|
||||
// The last-seen tty-invalidation generation count.
|
||||
// Set to a huge value so it's initially stale.
|
||||
uint32_t last_tty_gen_count{UINT32_MAX};
|
||||
|
||||
/// \return the current termsize from this data.
|
||||
termsize_t current() const;
|
||||
|
||||
/// Mark that our termsize is (for the time being) from the environment, not the tty.
|
||||
void mark_override_from_env(termsize_t ts);
|
||||
};
|
||||
|
||||
// Construct from a reader function.
|
||||
explicit termsize_container_t(tty_size_reader_func_t func) : tty_size_reader_(func) {}
|
||||
|
||||
// Update COLUMNS and LINES in the parser's stack.
|
||||
void set_columns_lines_vars(termsize_t val, parser_t &parser);
|
||||
|
||||
// Our lock-protected data.
|
||||
mutable owning_lock<data_t> data_{};
|
||||
|
||||
// An indication that we are currently in the process of setting COLUMNS and LINES, and so do
|
||||
// not react to any changes.
|
||||
relaxed_atomic_bool_t setting_env_vars_{false};
|
||||
const tty_size_reader_func_t tty_size_reader_;
|
||||
|
||||
friend termsize_tester_t;
|
||||
};
|
||||
|
||||
/// Convenience helper to return the last known termsize.
|
||||
inline termsize_t termsize_last() { return termsize_container_t::shared().last(); }
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
#include "termsize.rs.h"
|
||||
#else
|
||||
struct termsize_t;
|
||||
#endif
|
||||
|
||||
#endif // FISH_TERMSIZE_H
|
||||
|
|
Loading…
Reference in a new issue