parent
648afa1fd5
commit
670656b26f
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Fixed
|
||||
- Resizing restored panes no longer aborts the server when libghostty-vt reflows a terminal whose pre-resize cursor row is past the new height. (#465)
|
||||
- Full-screen TUIs such as Neovim now receive resize-generated terminal responses after Herdr internal pane resizes, so grown panes redraw without waiting for extra input. (#471)
|
||||
|
||||
## [0.6.8] - 2026-06-04
|
||||
|
||||
|
|
|
|||
28
src/pane.rs
28
src/pane.rs
|
|
@ -722,11 +722,24 @@ impl PaneRuntimeIo {
|
|||
}
|
||||
}
|
||||
|
||||
fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) {
|
||||
fn resize(
|
||||
&self,
|
||||
rows: u16,
|
||||
cols: u16,
|
||||
cell_width_px: u32,
|
||||
cell_height_px: u32,
|
||||
terminal_responses: Vec<Bytes>,
|
||||
) {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
PaneRuntimeIo::Actor(actor) => {
|
||||
actor.resize(rows, cols, cell_width_px, cell_height_px);
|
||||
actor.resize(
|
||||
rows,
|
||||
cols,
|
||||
cell_width_px,
|
||||
cell_height_px,
|
||||
terminal_responses,
|
||||
);
|
||||
}
|
||||
#[cfg(test)]
|
||||
PaneRuntimeIo::TestChannel { resize_tx, .. } => {
|
||||
|
|
@ -1814,9 +1827,16 @@ impl PaneRuntime {
|
|||
return;
|
||||
}
|
||||
self.current_size.set(size);
|
||||
self.terminal
|
||||
let terminal_responses = self
|
||||
.terminal
|
||||
.resize(rows, cols, cell_width_px, cell_height_px);
|
||||
self.io.resize(rows, cols, cell_width_px, cell_height_px);
|
||||
self.io.resize(
|
||||
rows,
|
||||
cols,
|
||||
cell_width_px,
|
||||
cell_height_px,
|
||||
terminal_responses,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn nudge_child_redraw_after_handoff(&self) {
|
||||
|
|
|
|||
|
|
@ -146,9 +146,15 @@ impl PaneTerminal {
|
|||
.process_pty_bytes(pane_id, shell_pid, bytes, response_writer)
|
||||
}
|
||||
|
||||
pub fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) {
|
||||
pub fn resize(
|
||||
&self,
|
||||
rows: u16,
|
||||
cols: u16,
|
||||
cell_width_px: u32,
|
||||
cell_height_px: u32,
|
||||
) -> Vec<Bytes> {
|
||||
self.ghostty
|
||||
.resize(rows, cols, cell_width_px, cell_height_px);
|
||||
.resize(rows, cols, cell_width_px, cell_height_px)
|
||||
}
|
||||
|
||||
pub fn scroll_up(&self, lines: usize) {
|
||||
|
|
@ -662,7 +668,13 @@ impl GhosttyPaneTerminal {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) {
|
||||
pub fn resize(
|
||||
&self,
|
||||
rows: u16,
|
||||
cols: u16,
|
||||
cell_width_px: u32,
|
||||
cell_height_px: u32,
|
||||
) -> Vec<Bytes> {
|
||||
if let Ok(mut core) = self.core.lock() {
|
||||
let offset_from_bottom = core
|
||||
.terminal
|
||||
|
|
@ -694,6 +706,7 @@ impl GhosttyPaneTerminal {
|
|||
let _ = core
|
||||
.terminal
|
||||
.resize(cols, rows, cell_width_px, cell_height_px);
|
||||
let terminal_responses = self.drain_pending_pty_responses();
|
||||
|
||||
let bottom_is_blank = ghostty_detection_text(&core)
|
||||
.map(|text| text.trim().is_empty())
|
||||
|
|
@ -716,6 +729,9 @@ impl GhosttyPaneTerminal {
|
|||
remaining -= 1;
|
||||
}
|
||||
}
|
||||
terminal_responses
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2621,6 +2637,21 @@ mod tests {
|
|||
assert!(pane.recent_text(3).trim().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_returns_in_band_size_report_response() {
|
||||
let (tx, _rx) = mpsc::channel(4);
|
||||
let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap();
|
||||
terminal.mode_set(2048, true).unwrap();
|
||||
let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap();
|
||||
|
||||
let responses = pane.resize(40, 100, 9, 18);
|
||||
|
||||
assert_eq!(
|
||||
responses,
|
||||
vec![Bytes::from_static(b"\x1B[48;40;100;720;900t")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synchronized_output_suppresses_intermediate_render_requests_until_batch_ends() {
|
||||
let (tx, _rx) = mpsc::channel(4);
|
||||
|
|
|
|||
|
|
@ -50,9 +50,15 @@ struct PtyResize {
|
|||
cell_height_px: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct PtyResizeRequest {
|
||||
resize: PtyResize,
|
||||
terminal_responses: Vec<Bytes>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SharedPtyControls {
|
||||
resize: Option<PtyResize>,
|
||||
resize: Option<PtyResizeRequest>,
|
||||
nudge: Option<PtyResize>,
|
||||
}
|
||||
|
||||
|
|
@ -151,17 +157,27 @@ impl PtyIoActorHandle {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) {
|
||||
pub(crate) fn resize(
|
||||
&self,
|
||||
rows: u16,
|
||||
cols: u16,
|
||||
cell_width_px: u32,
|
||||
cell_height_px: u32,
|
||||
terminal_responses: Vec<Bytes>,
|
||||
) {
|
||||
{
|
||||
let mut controls = self
|
||||
.controls
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
controls.resize = Some(PtyResize {
|
||||
rows,
|
||||
cols,
|
||||
cell_width_px,
|
||||
cell_height_px,
|
||||
controls.resize = Some(PtyResizeRequest {
|
||||
resize: PtyResize {
|
||||
rows,
|
||||
cols,
|
||||
cell_width_px,
|
||||
cell_height_px,
|
||||
},
|
||||
terminal_responses,
|
||||
});
|
||||
}
|
||||
self.wake_actor();
|
||||
|
|
@ -592,8 +608,9 @@ impl PtyIoActorRunner {
|
|||
if self.state == ActorState::Released {
|
||||
return;
|
||||
}
|
||||
if let Some(resize) = resize {
|
||||
self.resize(resize);
|
||||
if let Some(request) = resize {
|
||||
self.resize(request.resize);
|
||||
self.enqueue_terminal_responses(request.terminal_responses);
|
||||
}
|
||||
if let Some(nudge) = nudge {
|
||||
self.nudge(nudge);
|
||||
|
|
@ -612,16 +629,19 @@ impl PtyIoActorRunner {
|
|||
}
|
||||
Ok(n) => {
|
||||
let result = (self.on_read)(&buf[..n]);
|
||||
for response in result.terminal_responses {
|
||||
if self.state != ActorState::Released {
|
||||
self.pending_writes.push_back(response);
|
||||
}
|
||||
}
|
||||
self.enqueue_terminal_responses(result.terminal_responses);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enqueue_terminal_responses(&mut self, terminal_responses: Vec<Bytes>) {
|
||||
if self.state == ActorState::Released {
|
||||
return;
|
||||
}
|
||||
self.pending_writes.extend(terminal_responses);
|
||||
}
|
||||
|
||||
fn flush_pending_writes_once(&mut self) {
|
||||
while let Some(bytes) = self.pending_writes.front() {
|
||||
let chunk = &bytes[self.current_write_offset..];
|
||||
|
|
@ -922,18 +942,21 @@ mod tests {
|
|||
controls: Arc::clone(&controls),
|
||||
};
|
||||
|
||||
handle.resize(20, 80, 8, 16);
|
||||
handle.resize(40, 120, 9, 18);
|
||||
handle.resize(20, 80, 8, 16, vec![Bytes::from_static(b"old")]);
|
||||
handle.resize(40, 120, 9, 18, vec![Bytes::from_static(b"new")]);
|
||||
handle.nudge_child_redraw_after_handoff(41, 121, 10, 20);
|
||||
|
||||
let controls = controls.lock().expect("controls lock");
|
||||
assert_eq!(
|
||||
controls.resize,
|
||||
Some(PtyResize {
|
||||
rows: 40,
|
||||
cols: 120,
|
||||
cell_width_px: 9,
|
||||
cell_height_px: 18,
|
||||
Some(PtyResizeRequest {
|
||||
resize: PtyResize {
|
||||
rows: 40,
|
||||
cols: 120,
|
||||
cell_width_px: 9,
|
||||
cell_height_px: 18,
|
||||
},
|
||||
terminal_responses: vec![Bytes::from_static(b"new")],
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -947,6 +970,20 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_writes_terminal_responses_after_applying_resize() {
|
||||
let (handle, mut peer, _read_rx) = actor_with_socket_pair(false);
|
||||
let response = Bytes::from_static(b"\x1B[48;40;100;720;900t");
|
||||
|
||||
handle.resize(40, 100, 9, 18, vec![response.clone()]);
|
||||
|
||||
let mut buf = vec![0; response.len()];
|
||||
peer.read_exact(&mut buf)
|
||||
.expect("peer receives resize response");
|
||||
assert_eq!(Bytes::from(buf), response);
|
||||
handle.shutdown();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn async_user_input_waits_for_queue_capacity() {
|
||||
let (data_tx, mut data_rx) = mpsc::channel(1);
|
||||
|
|
|
|||
Loading…
Reference in New Issue