Add a WindowSize struct for the terminal dimensions

See <d14e5c51c6 (r156782395)>.
The tuple can be confusing, especially since it's reversed compared to
crossterm.

Also <https://github.com/helix-editor/termina/issues/5#issuecomment-2869972748>

Co-authored-by: museun <museun@users.noreply.github.com>
This commit is contained in:
Michael Davis
2025-05-11 12:38:06 -04:00
parent d14e5c51c6
commit 07cc87ef7a
8 changed files with 49 additions and 33 deletions

View File

@@ -9,7 +9,7 @@ use std::{
use termina::{
escape::csi::{self, KittyKeyboardFlags},
event::{KeyCode, KeyEvent},
Event, OneBased, PlatformTerminal, Terminal,
Event, PlatformTerminal, Terminal, WindowSize,
};
const HELP: &str = r#"Blocking read()
@@ -99,8 +99,8 @@ fn main() -> io::Result<()> {
eprintln!("Failed to read the cursor position within 50msec\r");
}
}
Event::WindowResized { rows, cols } => {
let new_size = flush_resize_events(&terminal, (rows, cols));
Event::WindowResized(dimensions) => {
let new_size = flush_resize_events(&terminal, dimensions);
println!("Resize from {size:?} to {new_size:?}\r");
size = new_size;
}
@@ -124,15 +124,12 @@ fn main() -> io::Result<()> {
Ok(())
}
fn flush_resize_events(
terminal: &PlatformTerminal,
original_size: (OneBased, OneBased),
) -> (OneBased, OneBased) {
fn flush_resize_events(terminal: &PlatformTerminal, original_size: WindowSize) -> WindowSize {
let mut size = original_size;
let filter = |event: &Event| matches!(event, Event::WindowResized { .. });
while let Ok(true) = terminal.poll(filter, Some(Duration::from_millis(50))) {
if let Ok(Event::WindowResized { rows, cols }) = terminal.read(filter) {
size = (rows, cols)
if let Ok(Event::WindowResized(dimensions)) = terminal.read(filter) {
size = dimensions;
}
}
size

View File

@@ -4,7 +4,7 @@
use crate::{
escape::{csi::Csi, dcs::Dcs},
OneBased,
WindowSize,
};
pub(crate) mod reader;
@@ -17,10 +17,7 @@ pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
/// The window was resized to the given dimensions.
WindowResized {
rows: OneBased,
cols: OneBased,
},
WindowResized(WindowSize),
FocusIn,
FocusOut,
/// A "bracketed" paste.

View File

@@ -16,7 +16,7 @@ use std::{
use parking_lot::Mutex;
use rustix::termios;
use crate::{parse::Parser, terminal::FileDescriptor, Event, OneBased};
use crate::{parse::Parser, terminal::FileDescriptor, Event};
use super::{EventSource, PollTimeout};
@@ -122,10 +122,7 @@ impl EventSource for UnixEventSource {
while read_complete(&self.sigwinch_pipe, &mut [0; 1024])? != 0 {}
let winsize = termios::tcgetwinsize(&self.write)?;
// winsize is already one-based.
let rows = OneBased::new(winsize.ws_row).unwrap();
let cols = OneBased::new(winsize.ws_col).unwrap();
let event = Event::WindowResized { rows, cols };
let event = Event::WindowResized(winsize.into());
return Ok(Some(event));
}

View File

@@ -59,3 +59,16 @@ impl From<NonZeroU16> for OneBased {
Self(n)
}
}
/// The dimensions of a terminal screen.
///
/// For both Unix and Windows, Termina returns the width and height
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WindowSize {
/// The width - the number of columns.
#[doc(alias = "width")]
pub cols: u16,
/// The height - the number of rows.
#[doc(alias = "height")]
pub rows: u16,
}

View File

@@ -91,7 +91,7 @@ impl Parser {
mod windows {
use windows_sys::Win32::System::Console;
use crate::OneBased;
use crate::{OneBased, WindowSize};
use super::*;
@@ -125,7 +125,10 @@ mod windows {
let Some(cols) = OneBased::new(record.dwSize.X as u16) else {
continue;
};
self.events.push_back(Event::WindowResized { rows, cols });
self.events.push_back(Event::WindowResized(WindowSize {
rows: rows.get(),
cols: cols.get(),
}));
}
_ => (),
}

View File

@@ -12,7 +12,7 @@ pub use unix::*;
#[cfg(windows)]
pub use windows::*;
use crate::{Event, EventReader, OneBased};
use crate::{Event, EventReader, WindowSize};
/// An alias to the terminal available for the current platform.
///
@@ -48,7 +48,7 @@ pub trait Terminal: io::Write {
/// While in "cooked" mode a terminal will interpret the incoming data in ways that are useful
/// such as waiting for an Enter key press to pass input to the application.
fn enter_cooked_mode(&mut self) -> io::Result<()>;
fn get_dimensions(&self) -> io::Result<(OneBased, OneBased)>;
fn get_dimensions(&self) -> io::Result<WindowSize>;
fn event_reader(&self) -> EventReader;
/// Checks if there is an `Event` available.
///

View File

@@ -5,7 +5,7 @@ use std::{
os::unix::prelude::*,
};
use crate::{event::source::UnixEventSource, Event, EventReader, OneBased};
use crate::{event::source::UnixEventSource, Event, EventReader, WindowSize};
use super::Terminal;
@@ -81,6 +81,15 @@ fn open_pty() -> io::Result<(FileDescriptor, FileDescriptor)> {
Ok((read, write))
}
impl From<termios::Winsize> for WindowSize {
fn from(size: termios::Winsize) -> Self {
Self {
cols: size.ws_col,
rows: size.ws_row,
}
}
}
// CREDIT: <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/terminal/unix.rs>
// Some discussion, though: Termwiz's terminals combine the terminal interaction (reading
// dimensions, reading events, writing bytes, etc.) all in one type. Crossterm splits these
@@ -139,12 +148,9 @@ impl Terminal for UnixTerminal {
Ok(())
}
fn get_dimensions(&self) -> io::Result<(OneBased, OneBased)> {
fn get_dimensions(&self) -> io::Result<WindowSize> {
let winsize = termios::tcgetwinsize(self.write.get_ref())?;
// winsize is already one-based.
let rows = OneBased::new(winsize.ws_row).unwrap();
let cols = OneBased::new(winsize.ws_col).unwrap();
Ok((rows, cols))
Ok(winsize.into())
}
fn event_reader(&self) -> EventReader {

View File

@@ -15,7 +15,7 @@ use windows_sys::Win32::{
},
};
use crate::{event::source::WindowsEventSource, Event, EventReader, OneBased};
use crate::{event::source::WindowsEventSource, Event, EventReader, OneBased, WindowSize};
use super::Terminal;
@@ -243,7 +243,7 @@ impl OutputHandle {
Ok(())
}
fn get_dimensions(&self) -> io::Result<(OneBased, OneBased)> {
fn get_dimensions(&self) -> io::Result<WindowSize> {
let mut info: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
if unsafe { GetConsoleScreenBufferInfo(self.as_raw_handle(), &mut info) } == 0 {
bail!(
@@ -253,7 +253,10 @@ impl OutputHandle {
}
let rows = OneBased::from_zero_based((info.srWindow.Bottom - info.srWindow.Top) as u16);
let cols = OneBased::from_zero_based((info.srWindow.Right - info.srWindow.Left) as u16);
Ok((rows, cols))
Ok(WindowSize {
rows: rows.get(),
cols: cols.get(),
})
}
}
@@ -399,7 +402,7 @@ impl Terminal for WindowsTerminal {
Ok(())
}
fn get_dimensions(&self) -> io::Result<(OneBased, OneBased)> {
fn get_dimensions(&self) -> io::Result<WindowSize> {
// NOTE: setting dimensions should be done by VT instead of `SetConsoleScreenBufferInfo`.
// <https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#window-width>
self.output.get_ref().get_dimensions()