diff --git a/Cargo.toml b/Cargo.toml index c7a91ef..f61bd1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,24 @@ debug = 0 strip = "debuginfo" incremental = false +# Debug 壳仍要承担真实终端负载:GPUI 布局/字体塑形和 VT 解析若以 +# opt-level 0 运行,窗口拖拽与持续输入都会出现发布版没有的延迟。 +# 只优化热依赖,主 crate 继续使用增量 debug,避免把日常构建变成 release。 +[profile.dev] +codegen-units = 16 +debug = "limited" +split-debuginfo = "unpacked" + +[profile.dev.package] +resvg = { opt-level = 3 } +rustybuzz = { opt-level = 3 } +taffy = { opt-level = 3 } +ttf-parser = { opt-level = 3 } +smol = { opt-level = 3 } +gpui = { opt-level = 3 } +gpui_macros = { opt-level = 3 } +nebula_terminal = { opt-level = 3 } + [workspace.dependencies] mlua = { version = "0.11.4", features = ["lua54", "vendored", "serialize", "send"] } serde_path_to_error = "0.1.20" diff --git a/nebula_terminal/src/event_loop.rs b/nebula_terminal/src/event_loop.rs index 9c66bc6..c9e1683 100644 --- a/nebula_terminal/src/event_loop.rs +++ b/nebula_terminal/src/event_loop.rs @@ -181,8 +181,9 @@ where /// Drain the channel. /// - /// Returns `false` when a shutdown message was received. - fn drain_recv_channel(&mut self, state: &mut State) -> bool { + /// Returns `Err(())` when a shutdown message was received; otherwise the + /// last resize in this channel batch is returned for an ordered commit. + fn drain_recv_channel(&mut self, state: &mut State) -> Result, ()> { // Resize storms (live window drags) queue faster than // ResizePseudoConsole drains them — the console host performs a full // viewport reflow per call. Within one drain only the newest size @@ -195,16 +196,11 @@ where match msg { Msg::Input(input) => state.write_list.push_back(input), Msg::Resize(window_size) => resize = Some(window_size), - Msg::Shutdown => return false, + Msg::Shutdown => return Err(()), } } - if let Some(window_size) = resize { - state.stream.resize(window_size); - self.pty.on_resize(window_size); - } - - true + Ok(resize) } #[inline] @@ -213,7 +209,7 @@ where state: &mut State, buf: &mut [u8], mut writer: Option<&mut X>, - ) -> io::Result<()> + ) -> io::Result where X: Write, { @@ -285,7 +281,7 @@ where self.event_proxy.send_event(Event::Wakeup); } - Ok(()) + Ok(processed) } #[inline] @@ -374,8 +370,37 @@ where } // Handle channel events, if there are any. - if !self.drain_recv_channel(&mut state) { - break; + let pending_resize = match self.drain_recv_channel(&mut state) { + Ok(resize) => resize, + Err(()) => break, + }; + + if let Some(window_size) = pending_resize { + // A resize is a stream boundary, not a UI-side property. + // Drain everything already readable while the grid still + // has the old geometry; otherwise old-width absolute CUP + // output can be parsed into a new-width grid. This is the + // local equivalent of tty7's ordered Size echo. + loop { + match self.pty_read(&mut state, &mut buf, pipe.as_mut()) { + Ok(processed) if processed >= MAX_LOCKED_READ => continue, + Ok(_) => break, + Err(err) => { + error!("PTY read before resize failed: {err}"); + failure = Some(format!("PTY read before resize failed: {err}")); + break 'event_loop; + }, + } + } + + // Match Windows Terminal's order: reflow the client model + // first, then ask ConPTY to resize. ResizePseudoConsole is + // synchronous; repaint bytes it produces are consumed by + // the normal readable-event path below against this grid. + self.terminal.lock().resize(window_size); + self.pty.on_resize(window_size); + state.stream.resize(window_size); + self.event_proxy.send_event(Event::Wakeup); } for event in events.iter() { diff --git a/nebula_terminal/src/grid/resize.rs b/nebula_terminal/src/grid/resize.rs index 63813d6..2c13338 100644 --- a/nebula_terminal/src/grid/resize.rs +++ b/nebula_terminal/src/grid/resize.rs @@ -1,6 +1,6 @@ //! Grid resize and reflow. -use std::cmp::{max, min, Ordering}; +use std::cmp::{Ordering, max, min}; use std::mem; use crate::index::{Boundary, Column, Line}; @@ -99,8 +99,9 @@ impl Grid { /// is scrollback available. Once scrollback is exhausted, new lines are /// simply added to the bottom of the screen. /// - /// ConPTY does not restore scrollback when growing. It leaves visible rows - /// and the cursor pinned, opening blank rows below them instead. + /// ConPTY 增高时不会把先前推出视口的行重新拉回:现有内容和光标保持 + /// 原位,只在底部补空行。这里必须复刻 host 的行坐标,否则下一次 + /// PSReadLine 绝对定位重绘会落到旧输出中间。 fn grow_lines(&mut self, conpty: bool, target: usize) where T: ResetDiscriminant, @@ -192,10 +193,7 @@ impl Grid { /// Bottommost viewport row containing a non-empty cell. #[inline] fn bottommost_occupied_line(&self) -> usize { - (0..self.lines) - .rev() - .find(|&line| !self.raw[Line(line as i32)].is_clear()) - .unwrap_or(0) + (0..self.lines).rev().find(|&line| !self.raw[Line(line as i32)].is_clear()).unwrap_or(0) } /// Grow number of columns in each row, reflowing if necessary. @@ -402,7 +400,8 @@ impl Grid { let mut wrapped = match row.shrink_with_minimum(columns, minimum) { Some(wrapped) if reflow => wrapped, _ => { - if reflow && i == cursor_buffer_line && self.cursor.point.column >= columns { + if reflow && i == cursor_buffer_line && self.cursor.point.column >= columns + { // If there are empty cells before the cursor, we assume it is explicit // whitespace and need to wrap it like normal content. Vec::new() diff --git a/nebula_terminal/src/grid/tests.rs b/nebula_terminal/src/grid/tests.rs index 99fdd61..da9b58b 100644 --- a/nebula_terminal/src/grid/tests.rs +++ b/nebula_terminal/src/grid/tests.rs @@ -402,10 +402,8 @@ fn display_iterator_can_crop_to_the_bottom_of_the_live_viewport() { } } - let points: Vec<_> = grid - .display_iter_from(Line(2), 2, 2) - .map(|cell| (cell.point, *cell.cell)) - .collect(); + let points: Vec<_> = + grid.display_iter_from(Line(2), 2, 2).map(|cell| (cell.point, *cell.cell)).collect(); assert_eq!( points, diff --git a/nebula_terminal/src/term/mod.rs b/nebula_terminal/src/term/mod.rs index 33d157b..a96539c 100644 --- a/nebula_terminal/src/term/mod.rs +++ b/nebula_terminal/src/term/mod.rs @@ -8,8 +8,8 @@ use std::{cmp, mem, ptr, str}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use base64::engine::general_purpose::STANDARD as Base64; use base64::Engine; +use base64::engine::general_purpose::STANDARD as Base64; use bitflags::bitflags; use log::{debug, trace}; use unicode_width::UnicodeWidthChar; @@ -2429,11 +2429,7 @@ enum ModeState { impl From for ModeState { fn from(value: bool) -> Self { - if value { - Self::Set - } else { - Self::Reset - } + if value { Self::Set } else { Self::Reset } } } diff --git a/nebula_terminal/src/tty/windows/conpty.rs b/nebula_terminal/src/tty/windows/conpty.rs index 2f33e37..0223913 100644 --- a/nebula_terminal/src/tty/windows/conpty.rs +++ b/nebula_terminal/src/tty/windows/conpty.rs @@ -393,6 +393,8 @@ impl OnResize for Conpty { // not take the whole process down — log and let the exit path handle it. let result = unsafe { (self.api.resize)(self.handle, window_size.into()) }; if result != S_OK { + // stderr 兜底:GPUI 主窗形态没有装 logger,失败不能无声。 + eprintln!("[nebula:conpty] ResizePseudoConsole failed: HRESULT {result:#x}"); warn!("ResizePseudoConsole failed: HRESULT {result:#x}"); } }