mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-04-19 06:31:13 -03:00
termsize: better types
The u16 is implied by libc::winsize.
This commit is contained in:
@@ -1339,7 +1339,7 @@ macro_rules! write_to_output {
|
||||
pub fn reformat_for_screen(msg: &wstr, termsize: &Termsize) -> WString {
|
||||
let mut buff = WString::new();
|
||||
|
||||
let screen_width = termsize.width;
|
||||
let screen_width = isize::try_from(termsize.width()).unwrap();
|
||||
if screen_width != 0 {
|
||||
let mut start = 0;
|
||||
let mut pos = start;
|
||||
|
||||
8
src/env/environment.rs
vendored
8
src/env/environment.rs
vendored
@@ -747,10 +747,14 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
|
||||
// Initialize termsize variables.
|
||||
let termsize = termsize::SHARED_CONTAINER.initialize(vars as &dyn Environment);
|
||||
if vars.get_unless_empty(L!("COLUMNS")).is_none() {
|
||||
vars.set_one(L!("COLUMNS"), EnvMode::GLOBAL, termsize.width.to_wstring());
|
||||
vars.set_one(
|
||||
L!("COLUMNS"),
|
||||
EnvMode::GLOBAL,
|
||||
termsize.width().to_wstring(),
|
||||
);
|
||||
}
|
||||
if vars.get_unless_empty(L!("LINES")).is_none() {
|
||||
vars.set_one(L!("LINES"), EnvMode::GLOBAL, termsize.height.to_wstring());
|
||||
vars.set_one(L!("LINES"), EnvMode::GLOBAL, termsize.height().to_wstring());
|
||||
}
|
||||
|
||||
// Set fish_bind_mode to "default".
|
||||
|
||||
12
src/pager.rs
12
src/pager.rs
@@ -650,8 +650,8 @@ pub fn set_prefix(&mut self, pref: &wstr, highlight: bool /* = true */) {
|
||||
|
||||
// Sets the terminal size.
|
||||
pub fn set_term_size(&mut self, ts: &Termsize) {
|
||||
self.available_term_width = usize::try_from(ts.width).unwrap_or_default();
|
||||
self.available_term_height = usize::try_from(ts.height).unwrap_or_default();
|
||||
self.available_term_width = ts.width();
|
||||
self.available_term_height = ts.height();
|
||||
}
|
||||
|
||||
// Changes the selected completion in the given direction according to the layout of the given
|
||||
@@ -1270,6 +1270,7 @@ mod tests {
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wcstringutil::StringFuzzyMatch;
|
||||
use std::num::NonZeroU16;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
@@ -1368,8 +1369,11 @@ fn test_pager_layout() {
|
||||
// These tests are woefully incomplete
|
||||
// They only test the truncation logic for a single completion
|
||||
|
||||
let rendered_line = |pager: &mut Pager, width: isize| {
|
||||
pager.set_term_size(&Termsize::new(width, 24));
|
||||
let rendered_line = |pager: &mut Pager, width: u16| {
|
||||
pager.set_term_size(&Termsize::new(
|
||||
NonZeroU16::new(width).unwrap(),
|
||||
Termsize::DEFAULT_HEIGHT,
|
||||
));
|
||||
let rendering = pager.render();
|
||||
let sd = &rendering.screen_data;
|
||||
assert_eq!(sd.line_count(), 1);
|
||||
|
||||
@@ -391,7 +391,7 @@ pub fn reader_pop() {
|
||||
if let Some(new_reader) = current_data() {
|
||||
new_reader
|
||||
.screen
|
||||
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
|
||||
.reset_abandoning_line(termsize_last().width());
|
||||
} else {
|
||||
Outputter::stdoutput().borrow_mut().reset_text_face();
|
||||
*commandline_state_snapshot() = CommandlineState::new();
|
||||
@@ -2320,8 +2320,7 @@ fn readline(
|
||||
//
|
||||
// I can't see a good way around this.
|
||||
if !self.first_prompt {
|
||||
self.screen
|
||||
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
|
||||
self.screen.reset_abandoning_line(termsize_last().width());
|
||||
}
|
||||
self.first_prompt = false;
|
||||
|
||||
@@ -2810,8 +2809,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
Edit::new(0..self.command_line_len(), L!("").to_owned()),
|
||||
);
|
||||
if c == rl::CancelCommandline {
|
||||
self.screen
|
||||
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
|
||||
self.screen.reset_abandoning_line(termsize_last().width());
|
||||
}
|
||||
|
||||
// Post fish_cancel.
|
||||
@@ -3092,8 +3090,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
L!("fish_posterror").to_owned(),
|
||||
vec![self.command_line.text().to_owned()],
|
||||
);
|
||||
self.screen
|
||||
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
|
||||
self.screen.reset_abandoning_line(termsize_last().width());
|
||||
}
|
||||
}
|
||||
rl::HistoryPrefixSearchBackward
|
||||
@@ -5358,7 +5355,7 @@ fn history_pager_search(
|
||||
// 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)
|
||||
let page_size = usize::try_from(cmp::max(termsize_last().height / 2 - 2, 12)).unwrap();
|
||||
let page_size = cmp::max(termsize_last().height() / 2 - 2, 12);
|
||||
let mut completions = Vec::with_capacity(page_size);
|
||||
let mut search = HistorySearch::new_with(
|
||||
history.clone(),
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::LinkedList;
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroU16;
|
||||
use std::ops::Range;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
@@ -294,8 +295,8 @@ pub fn write(
|
||||
is_final_rendering: bool,
|
||||
) {
|
||||
let curr_termsize = termsize_last();
|
||||
let screen_width = curr_termsize.width;
|
||||
let screen_height = curr_termsize.height;
|
||||
let screen_width = curr_termsize.width();
|
||||
let screen_height = curr_termsize.height();
|
||||
static REPAINTS: AtomicU32 = AtomicU32::new(0);
|
||||
FLOGF!(
|
||||
screen,
|
||||
@@ -333,8 +334,6 @@ struct ScrolledCursor {
|
||||
if screen_width < 4 || screen_height == 0 {
|
||||
return;
|
||||
}
|
||||
let screen_width = usize::try_from(screen_width).unwrap();
|
||||
let screen_height = usize::try_from(screen_height).unwrap();
|
||||
|
||||
// Compute a layout.
|
||||
let layout = compute_layout(
|
||||
@@ -474,18 +473,20 @@ struct ScrolledCursor {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let pager_available_height = std::cmp::max(
|
||||
1,
|
||||
curr_termsize
|
||||
.height
|
||||
.saturating_sub_unsigned(full_line_count),
|
||||
);
|
||||
fn saturating_sub(m: NonZeroU16, s: usize) -> NonZeroU16 {
|
||||
NonZeroU16::new(std::cmp::max(
|
||||
1,
|
||||
m.get().saturating_sub(s.try_into().unwrap_or(u16::MAX)),
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
let pager_available_height = saturating_sub(curr_termsize.height_u16(), full_line_count);
|
||||
|
||||
// Now that we've output everything, set the cursor to the position that we saved in the loop
|
||||
// above.
|
||||
self.desired.cursor = match pager_search_field_position {
|
||||
Some(pager_cursor_pos)
|
||||
if pager_available_height >= isize::try_from(PAGER_MIN_HEIGHT).unwrap() =>
|
||||
if usize::from(pager_available_height.get()) >= PAGER_MIN_HEIGHT =>
|
||||
{
|
||||
Cursor {
|
||||
x: pager_cursor_pos,
|
||||
@@ -508,7 +509,7 @@ struct ScrolledCursor {
|
||||
// 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::new(
|
||||
std::cmp::max(1, curr_termsize.width),
|
||||
curr_termsize.width_u16(),
|
||||
pager_available_height,
|
||||
));
|
||||
|
||||
|
||||
144
src/termsize.rs
144
src/termsize.rs
@@ -6,77 +6,81 @@
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroU16;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Termsize {
|
||||
/// Width of the terminal, in columns.
|
||||
// TODO: Change to u32
|
||||
pub width: isize,
|
||||
width: NonZeroU16,
|
||||
|
||||
/// Height of the terminal, in rows.
|
||||
// TODO: Change to u32
|
||||
pub height: isize,
|
||||
height: NonZeroU16,
|
||||
}
|
||||
|
||||
// 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.
|
||||
/// Convert an environment variable to an int.
|
||||
/// The int must be >0 and <USHRT_MAX (from struct winsize).
|
||||
fn var_to_int_or(var: Option<EnvVar>, default: isize) -> isize {
|
||||
let val: WString = var.map(|v| v.as_string()).unwrap_or_default();
|
||||
if !val.is_empty() {
|
||||
if let Ok(proposed) = fish_wcstoi(&val) {
|
||||
if proposed > 0 && proposed <= u16::MAX as i32 {
|
||||
return proposed as isize;
|
||||
}
|
||||
}
|
||||
}
|
||||
default
|
||||
fn var_to_int(var: Option<EnvVar>) -> Option<NonZeroU16> {
|
||||
var.and_then(|v| fish_wcstoi(&v.as_string()).ok())
|
||||
.and_then(|i| u16::try_from(i).ok())
|
||||
.and_then(NonZeroU16::new)
|
||||
}
|
||||
|
||||
/// 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 = MaybeUninit::<libc::winsize>::uninit();
|
||||
if unsafe { libc::ioctl(0, libc::TIOCGWINSZ, winsize.as_mut_ptr()) } >= 0 {
|
||||
let mut winsize = unsafe { winsize.assume_init() };
|
||||
// 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;
|
||||
let winsize = {
|
||||
let mut winsize = MaybeUninit::<libc::winsize>::uninit();
|
||||
if unsafe { libc::ioctl(0, libc::TIOCGWINSZ, winsize.as_mut_ptr()) } < 0 {
|
||||
return None;
|
||||
}
|
||||
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
|
||||
unsafe { winsize.assume_init() }
|
||||
};
|
||||
let width = NonZeroU16::new(winsize.ws_col).unwrap_or_else(|| {
|
||||
FLOG!(
|
||||
term_support,
|
||||
L!("Terminal has 0 columns, falling back to default width")
|
||||
);
|
||||
Termsize::DEFAULT_WIDTH
|
||||
});
|
||||
let height = NonZeroU16::new(winsize.ws_row).unwrap_or_else(|| {
|
||||
FLOG!(
|
||||
term_support,
|
||||
L!("Terminal has 0 rows, falling back to default height")
|
||||
);
|
||||
Termsize::DEFAULT_HEIGHT
|
||||
});
|
||||
Some(Termsize::new(width, height))
|
||||
}
|
||||
|
||||
impl Termsize {
|
||||
/// Default width and height.
|
||||
pub const DEFAULT_WIDTH: isize = 80;
|
||||
pub const DEFAULT_HEIGHT: isize = 24;
|
||||
pub const DEFAULT_WIDTH: NonZeroU16 = NonZeroU16::new(80).unwrap();
|
||||
pub const DEFAULT_HEIGHT: NonZeroU16 = NonZeroU16::new(24).unwrap();
|
||||
|
||||
/// Construct from width and height.
|
||||
pub fn new(width: isize, height: isize) -> Self {
|
||||
pub fn new(width: NonZeroU16, height: NonZeroU16) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
pub fn width_u16(&self) -> NonZeroU16 {
|
||||
self.width
|
||||
}
|
||||
pub fn height_u16(&self) -> NonZeroU16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
usize::from(self.width.get())
|
||||
}
|
||||
pub fn height(&self) -> usize {
|
||||
usize::from(self.height.get())
|
||||
}
|
||||
|
||||
/// Return a default-sized termsize.
|
||||
pub fn defaults() -> Self {
|
||||
Self::new(Self::DEFAULT_WIDTH, Self::DEFAULT_HEIGHT)
|
||||
@@ -147,14 +151,11 @@ pub fn last(&self) -> Termsize {
|
||||
/// 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(&self, vars: &dyn Environment) -> Termsize {
|
||||
let new_termsize = Termsize {
|
||||
width: var_to_int_or(vars.getf(L!("COLUMNS"), EnvMode::GLOBAL), -1),
|
||||
height: var_to_int_or(vars.getf(L!("LINES"), EnvMode::GLOBAL), -1),
|
||||
};
|
||||
|
||||
let width = var_to_int(vars.getf(L!("COLUMNS"), EnvMode::GLOBAL));
|
||||
let height = var_to_int(vars.getf(L!("LINES"), EnvMode::GLOBAL));
|
||||
let mut data = self.data.lock().unwrap();
|
||||
if new_termsize.width > 0 && new_termsize.height > 0 {
|
||||
data.mark_override_from_env(new_termsize);
|
||||
if let (Some(width), Some(height)) = (width, height) {
|
||||
data.mark_override_from_env(Termsize { width, height });
|
||||
} else {
|
||||
data.last_tty_gen_count = TTY_TERMSIZE_GEN_COUNT.load(Ordering::Relaxed);
|
||||
data.last_from_tty = (self.tty_size_reader)();
|
||||
@@ -197,8 +198,16 @@ fn updating(&self, parser: &Parser) -> Termsize {
|
||||
|
||||
fn set_columns_lines_vars(&self, val: Termsize, parser: &Parser) {
|
||||
let saved = self.setting_env_vars.swap(true, Ordering::Relaxed);
|
||||
parser.set_var_and_fire(L!("COLUMNS"), EnvMode::GLOBAL, vec![val.width.to_wstring()]);
|
||||
parser.set_var_and_fire(L!("LINES"), EnvMode::GLOBAL, vec![val.height.to_wstring()]);
|
||||
parser.set_var_and_fire(
|
||||
L!("COLUMNS"),
|
||||
EnvMode::GLOBAL,
|
||||
vec![val.width().to_wstring()],
|
||||
);
|
||||
parser.set_var_and_fire(
|
||||
L!("LINES"),
|
||||
EnvMode::GLOBAL,
|
||||
vec![val.height().to_wstring()],
|
||||
);
|
||||
self.setting_env_vars.store(saved, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -210,15 +219,9 @@ pub(crate) fn handle_columns_lines_var_change(&self, vars: &dyn Environment) {
|
||||
}
|
||||
// Construct a new termsize from COLUMNS and LINES, then set it in our data.
|
||||
let new_termsize = Termsize {
|
||||
width: vars
|
||||
.getf(L!("COLUMNS"), EnvMode::GLOBAL)
|
||||
.map(|v| v.as_string())
|
||||
.and_then(|v| fish_wcstoi(&v).ok().map(|h| h as isize))
|
||||
width: var_to_int(vars.getf(L!("COLUMNS"), EnvMode::GLOBAL))
|
||||
.unwrap_or(Termsize::DEFAULT_WIDTH),
|
||||
height: vars
|
||||
.getf(L!("LINES"), EnvMode::GLOBAL)
|
||||
.map(|v| v.as_string())
|
||||
.and_then(|v| fish_wcstoi(&v).ok().map(|h| h as isize))
|
||||
height: var_to_int(vars.getf(L!("LINES"), EnvMode::GLOBAL))
|
||||
.unwrap_or(Termsize::DEFAULT_HEIGHT),
|
||||
};
|
||||
|
||||
@@ -289,8 +292,8 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
|
||||
// Haha we change the value, it doesn't even know.
|
||||
*STUBBY_TERMSIZE.lock().unwrap() = Some(Termsize {
|
||||
width: 42,
|
||||
height: 84,
|
||||
width: NonZeroU16::new(42).unwrap(),
|
||||
height: NonZeroU16::new(84).unwrap(),
|
||||
});
|
||||
assert_eq!(ts.last(), Termsize::defaults());
|
||||
|
||||
@@ -299,9 +302,16 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
handle_winch();
|
||||
assert_eq!(ts.last(), Termsize::defaults());
|
||||
|
||||
let new_test_termsize = |width, height| {
|
||||
Termsize::new(
|
||||
NonZeroU16::new(width).unwrap(),
|
||||
NonZeroU16::new(height).unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
// Ok now we tell it to update.
|
||||
ts.updating(&parser);
|
||||
assert_eq!(ts.last(), Termsize::new(42, 84));
|
||||
assert_eq!(ts.last(), new_test_termsize(42, 84));
|
||||
assert_eq!(vars.get(L!("COLUMNS")).unwrap().as_string(), "42");
|
||||
assert_eq!(vars.get(L!("LINES")).unwrap().as_string(), "84");
|
||||
|
||||
@@ -310,17 +320,17 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
vars.set_one(L!("COLUMNS"), env_global, L!("75").to_owned());
|
||||
vars.set_one(L!("LINES"), env_global, L!("150").to_owned());
|
||||
ts.handle_columns_lines_var_change(parser.vars());
|
||||
assert_eq!(ts.last(), Termsize::new(75, 150));
|
||||
assert_eq!(ts.last(), new_test_termsize(75, 150));
|
||||
assert_eq!(vars.get(L!("COLUMNS")).unwrap().as_string(), "75");
|
||||
assert_eq!(vars.get(L!("LINES")).unwrap().as_string(), "150");
|
||||
|
||||
vars.set_one(L!("COLUMNS"), env_global, L!("33").to_owned());
|
||||
ts.handle_columns_lines_var_change(parser.vars());
|
||||
assert_eq!(ts.last(), Termsize::new(33, 150));
|
||||
assert_eq!(ts.last(), new_test_termsize(33, 150));
|
||||
|
||||
// Oh it got SIGWINCH, now the tty matters again.
|
||||
handle_winch();
|
||||
assert_eq!(ts.last(), Termsize::new(33, 150));
|
||||
assert_eq!(ts.last(), new_test_termsize(33, 150));
|
||||
assert_eq!(ts.updating(&parser), stubby_termsize().unwrap());
|
||||
assert_eq!(vars.get(L!("COLUMNS")).unwrap().as_string(), "42");
|
||||
assert_eq!(vars.get(L!("LINES")).unwrap().as_string(), "84");
|
||||
@@ -329,7 +339,7 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
vars.set_one(L!("COLUMNS"), env_global, L!("83").to_owned());
|
||||
vars.set_one(L!("LINES"), env_global, L!("38").to_owned());
|
||||
ts.initialize(vars);
|
||||
assert_eq!(ts.last(), Termsize::new(83, 38));
|
||||
assert_eq!(ts.last(), new_test_termsize(83, 38));
|
||||
|
||||
// initialize() even beats the tty reader until a sigwinch.
|
||||
let ts2 = TermsizeContainer {
|
||||
@@ -339,7 +349,7 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
};
|
||||
ts.initialize(parser.vars());
|
||||
ts2.updating(&parser);
|
||||
assert_eq!(ts.last(), Termsize::new(83, 38));
|
||||
assert_eq!(ts.last(), new_test_termsize(83, 38));
|
||||
handle_winch();
|
||||
assert_eq!(ts2.updating(&parser), stubby_termsize().unwrap());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user