mirror of
https://github.com/openai/codex.git
synced 2026-10-25 18:28:38 +00:00
Fix output width issue
This commit is contained in:
+15
-5
@@ -3985,10 +3985,7 @@ impl App {
|
||||
event: TuiEvent,
|
||||
) -> Result<AppRunControl> {
|
||||
if matches!(event, TuiEvent::Draw) {
|
||||
let size = tui.terminal.size()?;
|
||||
if size != tui.terminal.last_known_screen_size {
|
||||
self.refresh_status_line();
|
||||
}
|
||||
self.sync_terminal_size_dependent_state(tui)?;
|
||||
}
|
||||
|
||||
if self.overlay.is_some() {
|
||||
@@ -4040,6 +4037,15 @@ impl App {
|
||||
Ok(AppRunControl::Continue)
|
||||
}
|
||||
|
||||
fn sync_terminal_size_dependent_state(&mut self, tui: &mut tui::Tui) -> Result<()> {
|
||||
let size = tui.terminal.size()?;
|
||||
if size != tui.terminal.last_known_screen_size {
|
||||
self.chat_widget.update_stream_render_width(size.width);
|
||||
self.refresh_status_line();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_event(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
@@ -4256,7 +4262,8 @@ impl App {
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
self.transcript_cells.push(cell.clone());
|
||||
let mut display = cell.display_lines(tui.terminal.last_known_screen_size.width);
|
||||
let display_width = tui.terminal.size()?.width;
|
||||
let mut display = cell.display_lines(display_width);
|
||||
if !display.is_empty() {
|
||||
// Only insert a separating blank line for new cells that are not
|
||||
// part of an ongoing stream. Streaming continuations should not
|
||||
@@ -4300,6 +4307,7 @@ impl App {
|
||||
self.commit_anim_running.store(false, Ordering::Release);
|
||||
}
|
||||
AppEvent::CommitTick => {
|
||||
self.sync_terminal_size_dependent_state(tui)?;
|
||||
self.chat_widget.on_commit_tick();
|
||||
}
|
||||
AppEvent::Exit(mode) => {
|
||||
@@ -5709,6 +5717,8 @@ impl App {
|
||||
app_server: &mut AppServerSession,
|
||||
event: ThreadBufferedEvent,
|
||||
) -> Result<()> {
|
||||
self.sync_terminal_size_dependent_state(tui)?;
|
||||
|
||||
// Capture this before any potential thread switch: we only want to clear
|
||||
// the exit marker when the currently active thread acknowledges shutdown.
|
||||
let pending_shutdown_exit_completed = matches!(
|
||||
|
||||
@@ -4026,6 +4026,16 @@ impl ChatWidget {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_stream_render_width(&mut self, terminal_width: u16) {
|
||||
let terminal_width = usize::from(terminal_width);
|
||||
if let Some(controller) = self.stream_controller.as_mut() {
|
||||
controller.set_width(Some(terminal_width.saturating_sub(2)));
|
||||
}
|
||||
if let Some(controller) = self.plan_stream_controller.as_mut() {
|
||||
controller.set_width(Some(terminal_width.saturating_sub(4)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle completion of an `AgentMessage` turn item.
|
||||
///
|
||||
/// Commentary completion sets a deferred restore flag so the status row
|
||||
|
||||
@@ -3,12 +3,14 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::markdown;
|
||||
use crate::render::line_utils::is_blank_line_spaces_only;
|
||||
|
||||
/// Newline-gated accumulator that renders markdown and commits only fully
|
||||
/// completed logical lines.
|
||||
pub(crate) struct MarkdownStreamCollector {
|
||||
buffer: String,
|
||||
committed_line_count: usize,
|
||||
committed_source_len: usize,
|
||||
width: Option<usize>,
|
||||
cwd: PathBuf,
|
||||
}
|
||||
@@ -24,6 +26,7 @@ impl MarkdownStreamCollector {
|
||||
Self {
|
||||
buffer: String::new(),
|
||||
committed_line_count: 0,
|
||||
committed_source_len: 0,
|
||||
width,
|
||||
cwd: cwd.to_path_buf(),
|
||||
}
|
||||
@@ -32,6 +35,18 @@ impl MarkdownStreamCollector {
|
||||
pub fn clear(&mut self) {
|
||||
self.buffer.clear();
|
||||
self.committed_line_count = 0;
|
||||
self.committed_source_len = 0;
|
||||
}
|
||||
|
||||
pub fn set_width(&mut self, width: Option<usize>) {
|
||||
if self.width == width {
|
||||
return;
|
||||
}
|
||||
|
||||
self.width = width;
|
||||
// The rendered line index is width-dependent; preserve the source commit boundary and
|
||||
// recompute how many rendered lines it occupies at the new width.
|
||||
self.committed_line_count = self.rendered_committed_line_count();
|
||||
}
|
||||
|
||||
pub fn push_delta(&mut self, delta: &str) {
|
||||
@@ -43,35 +58,37 @@ impl MarkdownStreamCollector {
|
||||
/// since the last commit. When the buffer does not end with a newline, the
|
||||
/// final rendered line is considered incomplete and is not emitted.
|
||||
pub fn commit_complete_lines(&mut self) -> Vec<Line<'static>> {
|
||||
let source = self.buffer.clone();
|
||||
let last_newline_idx = source.rfind('\n');
|
||||
let source = if let Some(last_newline_idx) = last_newline_idx {
|
||||
source[..=last_newline_idx].to_string()
|
||||
} else {
|
||||
let Some(last_newline_idx) = self.buffer.rfind('\n') else {
|
||||
return Vec::new();
|
||||
};
|
||||
let commit_source_len = last_newline_idx + 1;
|
||||
let source = &self.buffer[..commit_source_len];
|
||||
let mut rendered: Vec<Line<'static>> = Vec::new();
|
||||
markdown::append_markdown(&source, self.width, Some(self.cwd.as_path()), &mut rendered);
|
||||
let mut complete_line_count = rendered.len();
|
||||
if complete_line_count > 0
|
||||
&& crate::render::line_utils::is_blank_line_spaces_only(
|
||||
&rendered[complete_line_count - 1],
|
||||
)
|
||||
{
|
||||
complete_line_count -= 1;
|
||||
}
|
||||
markdown::append_markdown(source, self.width, Some(self.cwd.as_path()), &mut rendered);
|
||||
let complete_line_count = complete_line_count(&rendered);
|
||||
|
||||
if self.committed_line_count >= complete_line_count {
|
||||
return Vec::new();
|
||||
}
|
||||
let out = if self.committed_line_count >= complete_line_count {
|
||||
Vec::new()
|
||||
} else {
|
||||
rendered[self.committed_line_count..complete_line_count].to_vec()
|
||||
};
|
||||
|
||||
let out_slice = &rendered[self.committed_line_count..complete_line_count];
|
||||
|
||||
let out = out_slice.to_vec();
|
||||
self.committed_line_count = complete_line_count;
|
||||
self.committed_line_count = complete_line_count.max(self.committed_line_count);
|
||||
self.committed_source_len = commit_source_len;
|
||||
out
|
||||
}
|
||||
|
||||
fn rendered_committed_line_count(&self) -> usize {
|
||||
if self.committed_source_len == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let source = &self.buffer[..self.committed_source_len];
|
||||
let mut rendered: Vec<Line<'static>> = Vec::new();
|
||||
markdown::append_markdown(source, self.width, Some(self.cwd.as_path()), &mut rendered);
|
||||
complete_line_count(&rendered)
|
||||
}
|
||||
|
||||
/// Finalize the stream: emit all remaining lines beyond the last commit.
|
||||
/// If the buffer does not end with a newline, a temporary one is appended
|
||||
/// for rendering. Optionally unwraps ```markdown language fences in
|
||||
@@ -106,6 +123,14 @@ impl MarkdownStreamCollector {
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_line_count(rendered: &[Line<'static>]) -> usize {
|
||||
let mut complete_line_count = rendered.len();
|
||||
if complete_line_count > 0 && is_blank_line_spaces_only(&rendered[complete_line_count - 1]) {
|
||||
complete_line_count -= 1;
|
||||
}
|
||||
complete_line_count
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_cwd() -> PathBuf {
|
||||
// These tests only need a stable absolute cwd; using temp_dir() avoids baking Unix- or
|
||||
@@ -156,6 +181,24 @@ mod tests {
|
||||
assert_eq!(out.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn changing_width_preserves_source_commit_position() {
|
||||
let mut c = super::MarkdownStreamCollector::new(Some(16), &super::test_cwd());
|
||||
c.push_delta("- first second third fourth\n");
|
||||
|
||||
let first = c.commit_complete_lines();
|
||||
assert_eq!(
|
||||
lines_to_plain_strings(&first),
|
||||
vec!["- first second", " third fourth"]
|
||||
);
|
||||
|
||||
c.set_width(Some(40));
|
||||
c.push_delta("- fifth sixth\n");
|
||||
|
||||
let next = c.commit_complete_lines();
|
||||
assert_eq!(lines_to_plain_strings(&next), vec!["- fifth sixth"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn e2e_stream_blockquote_simple_is_green() {
|
||||
let out = super::simulate_stream_markdown_for_tests(&["> Hello\n"], /*finalize*/ true);
|
||||
|
||||
@@ -100,6 +100,11 @@ impl StreamController {
|
||||
self.state.oldest_queued_age(now)
|
||||
}
|
||||
|
||||
/// Updates the markdown render width used for future stream commits.
|
||||
pub(crate) fn set_width(&mut self, width: Option<usize>) {
|
||||
self.state.set_width(width);
|
||||
}
|
||||
|
||||
fn emit(&mut self, lines: Vec<Line<'static>>) -> Option<Box<dyn HistoryCell>> {
|
||||
if lines.is_empty() {
|
||||
return None;
|
||||
@@ -204,6 +209,11 @@ impl PlanStreamController {
|
||||
self.state.oldest_queued_age(now)
|
||||
}
|
||||
|
||||
/// Updates the markdown render width used for future stream commits.
|
||||
pub(crate) fn set_width(&mut self, width: Option<usize>) {
|
||||
self.state.set_width(width);
|
||||
}
|
||||
|
||||
fn emit(
|
||||
&mut self,
|
||||
lines: Vec<Line<'static>>,
|
||||
|
||||
@@ -51,6 +51,10 @@ impl StreamState {
|
||||
self.queued_lines.clear();
|
||||
self.has_seen_delta = false;
|
||||
}
|
||||
/// Updates the markdown render width used for future stream commits.
|
||||
pub(crate) fn set_width(&mut self, width: Option<usize>) {
|
||||
self.collector.set_width(width);
|
||||
}
|
||||
/// Drains one queued line from the front of the queue.
|
||||
pub(crate) fn step(&mut self) -> Vec<Line<'static>> {
|
||||
self.queued_lines
|
||||
|
||||
Reference in New Issue
Block a user