Files
codex/codex-rs/tui2/src/bottom_pane/queued_user_messages.rs
T
Josh McKinneyandGitHub 90f262e9a4 feat(tui2): copy tui crate and normalize snapshots (#7833)
Introduce a full codex-tui source snapshot under the new codex-tui2
crate so viewport work can be replayed in isolation.

This change copies the entire codex-rs/tui/src tree into
codex-rs/tui2/src in one atomic step, rather than piecemeal, to keep
future diffs vs the original viewport bookmark easy to reason about.

The goal is for codex-tui2 to render identically to the existing TUI
behind the `features.tui2` flag while we gradually port the
viewport/history commits from the joshka/viewport bookmark onto this
forked tree.

While on this baseline change, we also ran the codex-tui2 snapshot test
suite and accepted all insta snapshots for the new crate, so the
snapshot files now use the codex-tui2 naming scheme and encode the
unmodified legacy TUI behavior. This keeps later viewport commits
focused on intentional behavior changes (and their snapshots) rather
than on mechanical snapshot renames.
2025-12-10 22:53:46 +00:00

158 lines
5.0 KiB
Rust

use crossterm::event::KeyCode;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use crate::key_hint;
use crate::render::renderable::Renderable;
use crate::wrapping::RtOptions;
use crate::wrapping::word_wrap_lines;
/// Widget that displays a list of user messages queued while a turn is in progress.
pub(crate) struct QueuedUserMessages {
pub messages: Vec<String>,
}
impl QueuedUserMessages {
pub(crate) fn new() -> Self {
Self {
messages: Vec::new(),
}
}
fn as_renderable(&self, width: u16) -> Box<dyn Renderable> {
if self.messages.is_empty() || width < 4 {
return Box::new(());
}
let mut lines = vec![];
for message in &self.messages {
let wrapped = word_wrap_lines(
message.lines().map(|line| line.dim().italic()),
RtOptions::new(width as usize)
.initial_indent(Line::from(" ↳ ".dim()))
.subsequent_indent(Line::from(" ")),
);
let len = wrapped.len();
for line in wrapped.into_iter().take(3) {
lines.push(line);
}
if len > 3 {
lines.push(Line::from(" …".dim().italic()));
}
}
lines.push(
Line::from(vec![
" ".into(),
key_hint::alt(KeyCode::Up).into(),
" edit".into(),
])
.dim(),
);
Paragraph::new(lines).into()
}
}
impl Renderable for QueuedUserMessages {
fn render(&self, area: Rect, buf: &mut Buffer) {
if area.is_empty() {
return;
}
self.as_renderable(area.width).render(area, buf);
}
fn desired_height(&self, width: u16) -> u16 {
self.as_renderable(width).desired_height(width)
}
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
#[test]
fn desired_height_empty() {
let queue = QueuedUserMessages::new();
assert_eq!(queue.desired_height(40), 0);
}
#[test]
fn desired_height_one_message() {
let mut queue = QueuedUserMessages::new();
queue.messages.push("Hello, world!".to_string());
assert_eq!(queue.desired_height(40), 2);
}
#[test]
fn render_one_message() {
let mut queue = QueuedUserMessages::new();
queue.messages.push("Hello, world!".to_string());
let width = 40;
let height = queue.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
queue.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_one_message", format!("{buf:?}"));
}
#[test]
fn render_two_messages() {
let mut queue = QueuedUserMessages::new();
queue.messages.push("Hello, world!".to_string());
queue.messages.push("This is another message".to_string());
let width = 40;
let height = queue.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
queue.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_two_messages", format!("{buf:?}"));
}
#[test]
fn render_more_than_three_messages() {
let mut queue = QueuedUserMessages::new();
queue.messages.push("Hello, world!".to_string());
queue.messages.push("This is another message".to_string());
queue.messages.push("This is a third message".to_string());
queue.messages.push("This is a fourth message".to_string());
let width = 40;
let height = queue.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
queue.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_more_than_three_messages", format!("{buf:?}"));
}
#[test]
fn render_wrapped_message() {
let mut queue = QueuedUserMessages::new();
queue
.messages
.push("This is a longer message that should be wrapped".to_string());
queue.messages.push("This is another message".to_string());
let width = 40;
let height = queue.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
queue.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_wrapped_message", format!("{buf:?}"));
}
#[test]
fn render_many_line_message() {
let mut queue = QueuedUserMessages::new();
queue
.messages
.push("This is\na message\nwith many\nlines".to_string());
let width = 40;
let height = queue.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
queue.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_many_line_message", format!("{buf:?}"));
}
}