mirror of
https://github.com/openai/codex.git
synced 2026-04-28 02:11:08 +03:00
11 KiB
11 KiB
PR #1710: fix: long lines incorrectly wrapped
- URL: https://github.com/openai/codex/pull/1710
- Author: nornagon-openai
- Created: 2025-07-28 17:36:34 UTC
- Updated: 2025-07-28 19:19:13 UTC
- Changes: +113/-30, Files changed: 1, Commits: 8
Description
fix to #1685.
Full Diff
diff --git a/codex-rs/tui/src/insert_history.rs b/codex-rs/tui/src/insert_history.rs
index 7948436cd8..1e8b1f5392 100644
--- a/codex-rs/tui/src/insert_history.rs
+++ b/codex-rs/tui/src/insert_history.rs
@@ -1,7 +1,9 @@
+use std::fmt;
use std::io;
use std::io::Write;
use crate::tui;
+use crossterm::Command;
use crossterm::queue;
use crossterm::style::Color as CColor;
use crossterm::style::Colors;
@@ -11,46 +13,127 @@ use crossterm::style::SetBackgroundColor;
use crossterm::style::SetColors;
use crossterm::style::SetForegroundColor;
use ratatui::layout::Position;
+use ratatui::layout::Size;
use ratatui::prelude::Backend;
use ratatui::style::Color;
use ratatui::style::Modifier;
use ratatui::text::Line;
use ratatui::text::Span;
+/// Insert `lines` above the viewport.
pub(crate) fn insert_history_lines(terminal: &mut tui::Tui, lines: Vec<Line<'static>>) {
- let screen_height = terminal
- .backend()
- .size()
- .map(|s| s.height)
- .unwrap_or(0xffffu16);
+ let screen_size = terminal.backend().size().unwrap_or(Size::new(0, 0));
+
let mut area = terminal.get_frame().area();
- // We scroll up one line at a time because we can't position the cursor
- // above the top of the screen. i.e. if
- // lines.len() > screen_height - area.top()
- // we would need to print the first line above the top of the screen, which
- // can't be done.
- for line in lines.into_iter() {
- // 1. Scroll everything above the viewport up by one line
- if area.bottom() >= screen_height {
- let top = area.top();
- terminal.backend_mut().scroll_region_up(0..top, 1).ok();
- // 2. Move the cursor to the blank line
- terminal.set_cursor_position(Position::new(0, top - 1)).ok();
- } else {
- // If the viewport isn't at the bottom of the screen, scroll down instead
- terminal
- .backend_mut()
- .scroll_region_down(area.top()..area.bottom() + 1, 1)
- .ok();
- terminal
- .set_cursor_position(Position::new(0, area.top()))
- .ok();
- area.y += 1;
- }
- // 3. Write the line
+
+ let wrapped_lines = wrapped_line_count(&lines, area.width);
+ let cursor_top = if area.bottom() < screen_size.height {
+ // If the viewport is not at the bottom of the screen, scroll it down to make room.
+ // Don't scroll it past the bottom of the screen.
+ let scroll_amount = wrapped_lines.min(screen_size.height - area.bottom());
+ terminal
+ .backend_mut()
+ .scroll_region_down(area.top()..screen_size.height, scroll_amount)
+ .ok();
+ let cursor_top = area.top() - 1;
+ area.y += scroll_amount;
+ terminal.set_viewport_area(area);
+ cursor_top
+ } else {
+ area.top() - 1
+ };
+
+ // Limit the scroll region to the lines from the top of the screen to the
+ // top of the viewport. With this in place, when we add lines inside this
+ // area, only the lines in this area will be scrolled. We place the cursor
+ // at the end of the scroll region, and add lines starting there.
+ //
+ // ┌─Screen───────────────────────┐
+ // │┌╌Scroll region╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐│
+ // │┆ ┆│
+ // │┆ ┆│
+ // │┆ ┆│
+ // │█╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘│
+ // │╭─Viewport───────────────────╮│
+ // ││ ││
+ // │╰────────────────────────────╯│
+ // └──────────────────────────────┘
+ queue!(std::io::stdout(), SetScrollRegion(1..area.top())).ok();
+
+ terminal
+ .set_cursor_position(Position::new(0, cursor_top))
+ .ok();
+
+ for line in lines {
+ queue!(std::io::stdout(), Print("\r\n")).ok();
write_spans(&mut std::io::stdout(), line.iter()).ok();
}
- terminal.set_viewport_area(area);
+
+ queue!(std::io::stdout(), ResetScrollRegion).ok();
+}
+
+fn wrapped_line_count(lines: &[Line], width: u16) -> u16 {
+ let mut count = 0;
+ for line in lines {
+ count += line_height(line, width);
+ }
+ count
+}
+
+fn line_height(line: &Line, width: u16) -> u16 {
+ use unicode_width::UnicodeWidthStr;
+ // get the total display width of the line, accounting for double-width chars
+ let total_width = line
+ .spans
+ .iter()
+ .map(|span| span.content.width())
+ .sum::<usize>();
+ // divide by width to get the number of lines, rounding up
+ if width == 0 {
+ 1
+ } else {
+ (total_width as u16).div_ceil(width).max(1)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SetScrollRegion(pub std::ops::Range<u16>);
+
+impl Command for SetScrollRegion {
+ fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
+ write!(f, "\x1b[{};{}r", self.0.start, self.0.end)
+ }
+
+ #[cfg(windows)]
+ fn execute_winapi(&self) -> std::io::Result<()> {
+ panic!("tried to execute SetScrollRegion command using WinAPI, use ANSI instead");
+ }
+
+ #[cfg(windows)]
+ fn is_ansi_code_supported(&self) -> bool {
+ // TODO(nornagon): is this supported on Windows?
+ true
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ResetScrollRegion;
+
+impl Command for ResetScrollRegion {
+ fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
+ write!(f, "\x1b[r")
+ }
+
+ #[cfg(windows)]
+ fn execute_winapi(&self) -> std::io::Result<()> {
+ panic!("tried to execute ResetScrollRegion command using WinAPI, use ANSI instead");
+ }
+
+ #[cfg(windows)]
+ fn is_ansi_code_supported(&self) -> bool {
+ // TODO(nornagon): is this supported on Windows?
+ true
+ }
}
struct ModifierDiff {
Review Comments
codex-rs/tui/src/insert_history.rs
- Created: 2025-07-28 18:12:00 UTC | Link: https://github.com/openai/codex/pull/1710#discussion_r2237491320
@@ -11,46 +11,26 @@ use crossterm::style::SetBackgroundColor;
use crossterm::style::SetColors;
use crossterm::style::SetForegroundColor;
use ratatui::layout::Position;
-use ratatui::prelude::Backend;
use ratatui::style::Color;
use ratatui::style::Modifier;
use ratatui::text::Line;
use ratatui::text::Span;
pub(crate) fn insert_history_lines(terminal: &mut tui::Tui, lines: Vec<Line<'static>>) {
- let screen_height = terminal
- .backend()
- .size()
- .map(|s| s.height)
- .unwrap_or(0xffffu16);
- let mut area = terminal.get_frame().area();
- // We scroll up one line at a time because we can't position the cursor
- // above the top of the screen. i.e. if
- // lines.len() > screen_height - area.top()
- // we would need to print the first line above the top of the screen, which
- // can't be done.
- for line in lines.into_iter() {
- // 1. Scroll everything above the viewport up by one line
- if area.bottom() >= screen_height {
- let top = area.top();
- terminal.backend_mut().scroll_region_up(0..top, 1).ok();
- // 2. Move the cursor to the blank line
- terminal.set_cursor_position(Position::new(0, top - 1)).ok();
- } else {
- // If the viewport isn't at the bottom of the screen, scroll down instead
- terminal
- .backend_mut()
- .scroll_region_down(area.top()..area.bottom() + 1, 1)
- .ok();
- terminal
- .set_cursor_position(Position::new(0, area.top()))
- .ok();
- area.y += 1;
- }
- // 3. Write the line
+ let area = terminal.get_frame().area();
+
+ queue!(std::io::stdout(), Print(format!("\x1b[1;{}r", area.top()))).ok();
Can you add a comment because I don't know ANSI codes offhand?
- Created: 2025-07-28 18:14:44 UTC | Link: https://github.com/openai/codex/pull/1710#discussion_r2237497109
@@ -11,46 +11,26 @@ use crossterm::style::SetBackgroundColor;
use crossterm::style::SetColors;
use crossterm::style::SetForegroundColor;
use ratatui::layout::Position;
-use ratatui::prelude::Backend;
use ratatui::style::Color;
use ratatui::style::Modifier;
use ratatui::text::Line;
use ratatui::text::Span;
pub(crate) fn insert_history_lines(terminal: &mut tui::Tui, lines: Vec<Line<'static>>) {
- let screen_height = terminal
- .backend()
- .size()
- .map(|s| s.height)
- .unwrap_or(0xffffu16);
- let mut area = terminal.get_frame().area();
- // We scroll up one line at a time because we can't position the cursor
- // above the top of the screen. i.e. if
- // lines.len() > screen_height - area.top()
- // we would need to print the first line above the top of the screen, which
- // can't be done.
- for line in lines.into_iter() {
- // 1. Scroll everything above the viewport up by one line
- if area.bottom() >= screen_height {
- let top = area.top();
- terminal.backend_mut().scroll_region_up(0..top, 1).ok();
- // 2. Move the cursor to the blank line
- terminal.set_cursor_position(Position::new(0, top - 1)).ok();
- } else {
- // If the viewport isn't at the bottom of the screen, scroll down instead
- terminal
- .backend_mut()
- .scroll_region_down(area.top()..area.bottom() + 1, 1)
- .ok();
- terminal
- .set_cursor_position(Position::new(0, area.top()))
- .ok();
- area.y += 1;
- }
- // 3. Write the line
+ let area = terminal.get_frame().area();
+
+ queue!(std::io::stdout(), Print(format!("\x1b[1;{}r", area.top()))).ok();
+
+ terminal
+ .set_cursor_position(Position::new(0, area.top() - 1))
+ .ok();
+
+ for line in lines {
+ queue!(std::io::stdout(), Print("\r\n")).ok();
write_spans(&mut std::io::stdout(), line.iter()).ok();
}
- terminal.set_viewport_area(area);
+
+ queue!(std::io::stdout(), Print("\x1b[r")).ok();
Same here?