mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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.
158 lines
5.0 KiB
Rust
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:?}"));
|
|
}
|
|
}
|