feat: fall back to CSI 16 t when PowerShell OpenSSH returns a fake terminal size (#2636)

This commit is contained in:
三咲雅 · Misaki Masa 2025-04-18 03:00:53 +08:00 committed by GitHub
parent 510cb14ab6
commit c38be35250
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 78 additions and 73 deletions

View file

@ -4,36 +4,61 @@ use crossterm::terminal::WindowSize;
use crate::EMULATOR;
pub struct Dimension;
#[derive(Clone, Copy, Debug)]
pub struct Dimension {
pub rows: u16,
pub columns: u16,
pub width: u16,
pub height: u16,
}
impl From<WindowSize> for Dimension {
fn from(s: WindowSize) -> Self {
Self { rows: s.rows, columns: s.columns, width: s.width, height: s.height }
}
}
impl From<Dimension> for WindowSize {
fn from(d: Dimension) -> Self {
Self { rows: d.rows, columns: d.columns, width: d.width, height: d.height }
}
}
impl Dimension {
pub fn available() -> WindowSize {
pub fn available() -> Self {
let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 };
if let Ok(s) = crossterm::terminal::window_size() {
_ = mem::replace(&mut size, s);
}
if size.rows == 0 || size.columns == 0 {
if size.columns == 0 || size.rows == 0 {
if let Ok((cols, rows)) = crossterm::terminal::size() {
size.columns = cols;
size.rows = rows;
}
}
// TODO: Use `CSI 14 t` to get the actual size of the terminal
// if size.width == 0 || size.height == 0 {}
size
size.into()
}
#[inline]
pub fn ratio() -> Option<(f64, f64)> {
let s = Self::available();
Some(if s.width == 0 || s.height == 0 {
let s = EMULATOR.get().cell_size?;
(s.0 as f64, s.1 as f64)
pub fn ratio(self) -> Option<(f64, f64)> {
if self.rows == 0 || self.columns == 0 || self.width == 0 || self.height == 0 {
None
} else {
(f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows))
Some((self.width as f64 / self.columns as f64, self.height as f64 / self.rows as f64))
}
}
pub fn cell_size() -> Option<(f64, f64)> {
let emu = EMULATOR.get();
Some(if emu.force_16t {
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
} else if let Some(r) = Self::available().ratio() {
r
} else if emu.csi_16t.0 != 0 && emu.csi_16t.1 != 0 {
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
} else {
None?
})
}
}

View file

@ -53,12 +53,12 @@ impl Ueberzug {
})
.await??;
let area = Dimension::ratio()
.map(|(r1, r2)| Rect {
let area = Dimension::cell_size()
.map(|(cw, ch)| Rect {
x: max.x,
y: max.y,
width: max.width.min((w.min(YAZI.preview.max_width as _) as f64 / r1).ceil() as _),
height: max.height.min((h.min(YAZI.preview.max_height as _) as f64 / r2).ceil() as _),
width: max.width.min((w.min(YAZI.preview.max_width as _) as f64 / cw).ceil() as _),
height: max.height.min((h.min(YAZI.preview.max_height as _) as f64 / ch).ceil() as _),
})
.unwrap_or(max);

View file

@ -7,13 +7,14 @@ use tokio::time::sleep;
use tracing::{debug, error, warn};
use yazi_shared::{Either, tty::{Handle, TTY}};
use crate::{Adapter, Brand, Mux, TMUX, Unknown};
use crate::{Adapter, Brand, Dimension, Mux, TMUX, Unknown};
#[derive(Clone, Copy, Debug)]
pub struct Emulator {
pub kind: Either<Brand, Unknown>,
pub light: bool,
pub cell_size: Option<(u16, u16)>,
pub csi_16t: (u16, u16),
pub force_16t: bool,
}
impl Default for Emulator {
@ -55,15 +56,22 @@ impl Emulator {
})
};
let csi_16t = Self::csi_16t(&resp).unwrap_or_default();
Ok(Self {
kind,
light: Self::light_bg(&resp).unwrap_or_default(),
cell_size: Self::cell_size(&resp),
csi_16t,
force_16t: Self::force_16t(csi_16t),
})
}
pub const fn unknown() -> Self {
Self { kind: Either::Right(Unknown::default()), light: false, cell_size: None }
Self {
kind: Either::Right(Unknown::default()),
light: false,
csi_16t: (0, 0),
force_16t: false,
}
}
pub fn adapters(self) -> &'static [Adapter] {
@ -160,7 +168,7 @@ impl Emulator {
);
}
fn cell_size(resp: &str) -> Option<(u16, u16)> {
fn csi_16t(resp: &str) -> Option<(u16, u16)> {
let b = resp.split_once("\x1b[6;")?.1.as_bytes();
let h: Vec<_> = b.iter().copied().take_while(|&c| c.is_ascii_digit()).collect();
@ -189,4 +197,14 @@ impl Emulator {
}
}
}
fn force_16t((w, h): (u16, u16)) -> bool {
if w == 0 || h == 0 {
return false;
}
Dimension::available()
.ratio()
.is_none_or(|(rw, rh)| rw.floor() as u16 != w || rh.floor() as u16 != h)
}
}

View file

@ -65,21 +65,21 @@ impl Image {
}
pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
Dimension::ratio()
.map(|(r1, r2)| {
let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32);
Dimension::cell_size()
.map(|(cw, ch)| {
let (w, h) = ((rect.width as f64 * cw) as u32, (rect.height as f64 * ch) as u32);
(w.min(YAZI.preview.max_width), h.min(YAZI.preview.max_height))
})
.unwrap_or((YAZI.preview.max_width, YAZI.preview.max_height))
}
pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {
Dimension::ratio()
.map(|(r1, r2)| Rect {
Dimension::cell_size()
.map(|(cw, ch)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
width: (size.0 as f64 / cw).ceil() as u16,
height: (size.1 as f64 / ch).ceil() as u16,
})
.unwrap_or(rect)
}

View file

@ -30,7 +30,7 @@ impl Mgr {
if pos.origin == Origin::Hovered {
self.active().hovered_rect_based(pos)
} else {
pos.rect(Dimension::available())
pos.rect(Dimension::available().into())
}
}

View file

@ -77,7 +77,7 @@ impl Tab {
}
pub fn hovered_rect_based(&self, pos: Position) -> Rect {
let ws = Dimension::available();
let ws = Dimension::available().into();
if let Some(r) = self.hovered_rect() {
Position::sticky(ws, r, pos.offset)
} else {

View file

@ -1,4 +1,3 @@
use crossterm::terminal::WindowSize;
use ratatui::layout::Rect;
use yazi_adapter::Dimension;
use yazi_shared::event::CmdCow;
@ -7,7 +6,7 @@ use crate::{app::App, notify};
impl App {
pub(crate) fn update_notify(&mut self, cmd: CmdCow) {
let WindowSize { rows, columns, .. } = Dimension::available();
let Dimension { rows, columns, .. } = Dimension::available();
let area =
notify::Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });

View file

@ -38,7 +38,7 @@ impl Widget for Cmp<'_> {
.collect();
let input_area = self.cx.mgr.area(self.cx.input.position);
let mut area = Position::sticky(Dimension::available(), input_area, Offset {
let mut area = Position::sticky(Dimension::available().into(), input_area, Offset {
x: 1,
y: 0,
width: input_area.width.saturating_sub(2),

View file

@ -1,3 +1,3 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(cha chan icon image input layer mouse permit range window);
yazi_macro::mod_flat!(cha chan icon image input layer mouse permit range);

View file

@ -1,37 +0,0 @@
use mlua::{FromLua, UserData};
use yazi_adapter::Dimension;
#[derive(Debug, Clone, Copy, FromLua)]
pub struct Window {
pub rows: u16,
pub cols: u16,
pub width: u16,
pub height: u16,
}
impl Window {
pub fn get() -> Self {
let ws = Dimension::available();
Self { rows: ws.rows, cols: ws.columns, width: ws.width, height: ws.height }
}
}
impl From<Window> for crossterm::terminal::WindowSize {
fn from(value: Window) -> Self {
crossterm::terminal::WindowSize {
rows: value.rows,
columns: value.cols,
width: value.width,
height: value.height,
}
}
}
impl UserData for Window {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("rows", |_, me| Ok(me.rows));
fields.add_field_method_get("cols", |_, me| Ok(me.cols));
fields.add_field_method_get("width", |_, me| Ok(me.width));
fields.add_field_method_get("height", |_, me| Ok(me.height));
}
}