tui: bring the transcript closer to display mode (#4848)
before <img width="1161" height="836" alt="Screenshot 2025-10-06 at 3 06 52 PM" src="https://github.com/user-attachments/assets/7622fd6b-9d37-402f-8651-61c2c55dcbc6" /> after <img width="1161" height="858" alt="Screenshot 2025-10-06 at 3 07 02 PM" src="https://github.com/user-attachments/assets/1498f327-1d1a-4630-951f-7ca371ab0139" />
This commit is contained in:
@@ -134,8 +134,9 @@ impl App {
|
|||||||
/// Useful when switching sessions to ensure prior history remains visible.
|
/// Useful when switching sessions to ensure prior history remains visible.
|
||||||
pub(crate) fn render_transcript_once(&mut self, tui: &mut tui::Tui) {
|
pub(crate) fn render_transcript_once(&mut self, tui: &mut tui::Tui) {
|
||||||
if !self.transcript_cells.is_empty() {
|
if !self.transcript_cells.is_empty() {
|
||||||
|
let width = tui.terminal.last_known_screen_size.width;
|
||||||
for cell in &self.transcript_cells {
|
for cell in &self.transcript_cells {
|
||||||
tui.insert_history_lines(cell.transcript_lines());
|
tui.insert_history_lines(cell.display_lines(width));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ use crate::bottom_pane::prompt_args::prompt_has_numeric_placeholders;
|
|||||||
use crate::slash_command::SlashCommand;
|
use crate::slash_command::SlashCommand;
|
||||||
use crate::slash_command::built_in_slash_commands;
|
use crate::slash_command::built_in_slash_commands;
|
||||||
use crate::style::user_message_style;
|
use crate::style::user_message_style;
|
||||||
use crate::terminal_palette;
|
|
||||||
use codex_protocol::custom_prompts::CustomPrompt;
|
use codex_protocol::custom_prompts::CustomPrompt;
|
||||||
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
|
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
|
||||||
|
|
||||||
@@ -1533,7 +1532,7 @@ impl WidgetRef for ChatComposer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let style = user_message_style(terminal_palette::default_bg());
|
let style = user_message_style();
|
||||||
let mut block_rect = composer_rect;
|
let mut block_rect = composer_rect;
|
||||||
block_rect.y = composer_rect.y.saturating_sub(1);
|
block_rect.y = composer_rect.y.saturating_sub(1);
|
||||||
block_rect.height = composer_rect.height.saturating_add(1);
|
block_rect.height = composer_rect.height.saturating_add(1);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use crate::render::RectExt as _;
|
|||||||
use crate::render::renderable::ColumnRenderable;
|
use crate::render::renderable::ColumnRenderable;
|
||||||
use crate::render::renderable::Renderable;
|
use crate::render::renderable::Renderable;
|
||||||
use crate::style::user_message_style;
|
use crate::style::user_message_style;
|
||||||
use crate::terminal_palette;
|
|
||||||
|
|
||||||
use super::CancellationEvent;
|
use super::CancellationEvent;
|
||||||
use super::bottom_pane_view::BottomPaneView;
|
use super::bottom_pane_view::BottomPaneView;
|
||||||
@@ -350,7 +349,7 @@ impl Renderable for ListSelectionView {
|
|||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
Block::default()
|
Block::default()
|
||||||
.style(user_message_style(terminal_palette::default_bg()))
|
.style(user_message_style())
|
||||||
.render(content_area, buf);
|
.render(content_area, buf);
|
||||||
|
|
||||||
let header_height = self
|
let header_height = self
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ where
|
|||||||
/// Last known position of the cursor. Used to find the new area when the viewport is inlined
|
/// Last known position of the cursor. Used to find the new area when the viewport is inlined
|
||||||
/// and the terminal resized.
|
/// and the terminal resized.
|
||||||
pub last_known_cursor_pos: Position,
|
pub last_known_cursor_pos: Position,
|
||||||
|
|
||||||
|
use_custom_flush: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> Drop for Terminal<B>
|
impl<B> Drop for Terminal<B>
|
||||||
@@ -158,6 +160,7 @@ where
|
|||||||
viewport_area: Rect::new(0, cursor_pos.y, 0, 0),
|
viewport_area: Rect::new(0, cursor_pos.y, 0, 0),
|
||||||
last_known_screen_size: screen_size,
|
last_known_screen_size: screen_size,
|
||||||
last_known_cursor_pos: cursor_pos,
|
last_known_cursor_pos: cursor_pos,
|
||||||
|
use_custom_flush: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,15 +193,24 @@ where
|
|||||||
pub fn flush(&mut self) -> io::Result<()> {
|
pub fn flush(&mut self) -> io::Result<()> {
|
||||||
let previous_buffer = &self.buffers[1 - self.current];
|
let previous_buffer = &self.buffers[1 - self.current];
|
||||||
let current_buffer = &self.buffers[self.current];
|
let current_buffer = &self.buffers[self.current];
|
||||||
let updates = diff_buffers(previous_buffer, current_buffer);
|
|
||||||
if let Some(DrawCommand::Put { x, y, .. }) = updates
|
if self.use_custom_flush {
|
||||||
.iter()
|
let updates = diff_buffers(previous_buffer, current_buffer);
|
||||||
.rev()
|
if let Some(DrawCommand::Put { x, y, .. }) = updates
|
||||||
.find(|cmd| matches!(cmd, DrawCommand::Put { .. }))
|
.iter()
|
||||||
{
|
.rev()
|
||||||
self.last_known_cursor_pos = Position { x: *x, y: *y };
|
.find(|cmd| matches!(cmd, DrawCommand::Put { .. }))
|
||||||
|
{
|
||||||
|
self.last_known_cursor_pos = Position { x: *x, y: *y };
|
||||||
|
}
|
||||||
|
draw(&mut self.backend, updates.into_iter())
|
||||||
|
} else {
|
||||||
|
let updates = previous_buffer.diff(current_buffer);
|
||||||
|
if let Some((x, y, _)) = updates.last() {
|
||||||
|
self.last_known_cursor_pos = Position { x: *x, y: *y };
|
||||||
|
}
|
||||||
|
self.backend.draw(updates.into_iter())
|
||||||
}
|
}
|
||||||
draw(&mut self.backend, updates.into_iter())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the Terminal so that internal buffers match the requested area.
|
/// Updates the Terminal so that internal buffers match the requested area.
|
||||||
@@ -408,11 +420,13 @@ fn diff_buffers<'a>(a: &'a Buffer, b: &'a Buffer) -> Vec<DrawCommand<'a>> {
|
|||||||
|
|
||||||
let x = row
|
let x = row
|
||||||
.iter()
|
.iter()
|
||||||
.rposition(|cell| cell.symbol() != " " || cell.bg != bg)
|
.rposition(|cell| {
|
||||||
|
cell.symbol() != " " || cell.bg != bg || cell.modifier != Modifier::empty()
|
||||||
|
})
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
last_nonblank_column[y as usize] = x as u16;
|
last_nonblank_column[y as usize] = x as u16;
|
||||||
let (x_abs, y_abs) = a.pos_of(row_start + x + 1);
|
|
||||||
if x < (a.area.width as usize).saturating_sub(1) {
|
if x < (a.area.width as usize).saturating_sub(1) {
|
||||||
|
let (x_abs, y_abs) = a.pos_of(row_start + x + 1);
|
||||||
updates.push(DrawCommand::ClearToEnd {
|
updates.push(DrawCommand::ClearToEnd {
|
||||||
x: x_abs,
|
x: x_abs,
|
||||||
y: y_abs,
|
y: y_abs,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl From<DiffSummary> for Box<dyn Renderable> {
|
|||||||
rows.push(Box::new(path));
|
rows.push(Box::new(path));
|
||||||
rows.push(Box::new(RtLine::from("")));
|
rows.push(Box::new(RtLine::from("")));
|
||||||
rows.push(Box::new(InsetRenderable::new(
|
rows.push(Box::new(InsetRenderable::new(
|
||||||
Box::new(row.change),
|
row.change,
|
||||||
Insets::tlbr(0, 2, 0, 0),
|
Insets::tlbr(0, 2, 0, 0),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::render::line_utils::push_owned_lines;
|
|||||||
use crate::shimmer::shimmer_spans;
|
use crate::shimmer::shimmer_spans;
|
||||||
use crate::wrapping::RtOptions;
|
use crate::wrapping::RtOptions;
|
||||||
use crate::wrapping::word_wrap_line;
|
use crate::wrapping::word_wrap_line;
|
||||||
|
use crate::wrapping::word_wrap_lines;
|
||||||
use codex_ansi_escape::ansi_escape_line;
|
use codex_ansi_escape::ansi_escape_line;
|
||||||
use codex_common::elapsed::format_duration;
|
use codex_common::elapsed::format_duration;
|
||||||
use codex_protocol::parse_command::ParsedCommand;
|
use codex_protocol::parse_command::ParsedCommand;
|
||||||
@@ -138,17 +139,25 @@ impl HistoryCell for ExecCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
fn desired_transcript_height(&self, width: u16) -> u16 {
|
||||||
|
self.transcript_lines(width).len() as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transcript_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||||
let mut lines: Vec<Line<'static>> = vec![];
|
let mut lines: Vec<Line<'static>> = vec![];
|
||||||
for call in self.iter_calls() {
|
for (i, call) in self.iter_calls().enumerate() {
|
||||||
let cmd_display = strip_bash_lc_and_escape(&call.command);
|
if i > 0 {
|
||||||
for (i, part) in cmd_display.lines().enumerate() {
|
lines.push("".into());
|
||||||
if i == 0 {
|
|
||||||
lines.push(vec!["$ ".magenta(), part.to_string().into()].into());
|
|
||||||
} else {
|
|
||||||
lines.push(vec![" ".into(), part.to_string().into()].into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let script = strip_bash_lc_and_escape(&call.command);
|
||||||
|
let highlighted_script = highlight_bash_to_lines(&script);
|
||||||
|
let cmd_display = word_wrap_lines(
|
||||||
|
&highlighted_script,
|
||||||
|
RtOptions::new(width as usize)
|
||||||
|
.initial_indent("$ ".magenta().into())
|
||||||
|
.subsequent_indent(" ".into()),
|
||||||
|
);
|
||||||
|
lines.extend(cmd_display);
|
||||||
|
|
||||||
if let Some(output) = call.output.as_ref() {
|
if let Some(output) = call.output.as_ref() {
|
||||||
lines.extend(output.formatted_output.lines().map(ansi_escape_line));
|
lines.extend(output.formatted_output.lines().map(ansi_escape_line));
|
||||||
@@ -167,7 +176,6 @@ impl HistoryCell for ExecCell {
|
|||||||
result.push_span(format!(" • {duration}").dim());
|
result.push_span(format!(" • {duration}").dim());
|
||||||
lines.push(result);
|
lines.push(result);
|
||||||
}
|
}
|
||||||
lines.push("".into());
|
|
||||||
}
|
}
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use crate::markdown::append_markdown;
|
|||||||
use crate::render::line_utils::line_to_static;
|
use crate::render::line_utils::line_to_static;
|
||||||
use crate::render::line_utils::prefix_lines;
|
use crate::render::line_utils::prefix_lines;
|
||||||
use crate::style::user_message_style;
|
use crate::style::user_message_style;
|
||||||
use crate::terminal_palette::default_bg;
|
|
||||||
use crate::text_formatting::format_and_truncate_tool_result;
|
use crate::text_formatting::format_and_truncate_tool_result;
|
||||||
use crate::ui_consts::LIVE_PREFIX_COLS;
|
use crate::ui_consts::LIVE_PREFIX_COLS;
|
||||||
use crate::wrapping::RtOptions;
|
use crate::wrapping::RtOptions;
|
||||||
@@ -56,10 +55,6 @@ use unicode_width::UnicodeWidthStr;
|
|||||||
pub(crate) trait HistoryCell: std::fmt::Debug + Send + Sync + Any {
|
pub(crate) trait HistoryCell: std::fmt::Debug + Send + Sync + Any {
|
||||||
fn display_lines(&self, width: u16) -> Vec<Line<'static>>;
|
fn display_lines(&self, width: u16) -> Vec<Line<'static>>;
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
|
||||||
self.display_lines(u16::MAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&self, width: u16) -> u16 {
|
fn desired_height(&self, width: u16) -> u16 {
|
||||||
Paragraph::new(Text::from(self.display_lines(width)))
|
Paragraph::new(Text::from(self.display_lines(width)))
|
||||||
.wrap(Wrap { trim: false })
|
.wrap(Wrap { trim: false })
|
||||||
@@ -68,6 +63,29 @@ pub(crate) trait HistoryCell: std::fmt::Debug + Send + Sync + Any {
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transcript_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||||
|
self.display_lines(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_transcript_height(&self, width: u16) -> u16 {
|
||||||
|
let lines = self.transcript_lines(width);
|
||||||
|
// Workaround for ratatui bug: if there's only one line and it's whitespace-only, ratatui gives 2 lines.
|
||||||
|
if let [line] = &lines[..]
|
||||||
|
&& line
|
||||||
|
.spans
|
||||||
|
.iter()
|
||||||
|
.all(|s| s.content.chars().all(char::is_whitespace))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Paragraph::new(Text::from(lines))
|
||||||
|
.wrap(Wrap { trim: false })
|
||||||
|
.line_count(width)
|
||||||
|
.try_into()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_stream_continuation(&self) -> bool {
|
fn is_stream_continuation(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -92,12 +110,10 @@ impl HistoryCell for UserHistoryCell {
|
|||||||
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||||
|
|
||||||
// Use ratatui-aware word wrapping and prefixing to avoid lifetime issues.
|
let wrap_width = width.saturating_sub(LIVE_PREFIX_COLS);
|
||||||
let wrap_width = width.saturating_sub(LIVE_PREFIX_COLS); // account for the ▌ prefix and trailing space
|
|
||||||
|
|
||||||
let style = user_message_style(default_bg());
|
let style = user_message_style();
|
||||||
|
|
||||||
// Use our ratatui wrapping helpers for correct styling and lifetimes.
|
|
||||||
let wrapped = word_wrap_lines(
|
let wrapped = word_wrap_lines(
|
||||||
&self
|
&self
|
||||||
.message
|
.message
|
||||||
@@ -113,13 +129,6 @@ impl HistoryCell for UserHistoryCell {
|
|||||||
lines.push(Line::from("").style(style));
|
lines.push(Line::from("").style(style));
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
|
||||||
lines.push("user".cyan().bold().into());
|
|
||||||
lines.extend(self.message.lines().map(|l| l.to_string().into()));
|
|
||||||
lines
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -127,6 +136,7 @@ pub(crate) struct ReasoningSummaryCell {
|
|||||||
_header: String,
|
_header: String,
|
||||||
content: String,
|
content: String,
|
||||||
citation_context: MarkdownCitationContext,
|
citation_context: MarkdownCitationContext,
|
||||||
|
transcript_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReasoningSummaryCell {
|
impl ReasoningSummaryCell {
|
||||||
@@ -134,17 +144,17 @@ impl ReasoningSummaryCell {
|
|||||||
header: String,
|
header: String,
|
||||||
content: String,
|
content: String,
|
||||||
citation_context: MarkdownCitationContext,
|
citation_context: MarkdownCitationContext,
|
||||||
|
transcript_only: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
_header: header,
|
_header: header,
|
||||||
content,
|
content,
|
||||||
citation_context,
|
citation_context,
|
||||||
|
transcript_only,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl HistoryCell for ReasoningSummaryCell {
|
fn lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||||
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||||
append_markdown(
|
append_markdown(
|
||||||
&self.content,
|
&self.content,
|
||||||
@@ -152,7 +162,7 @@ impl HistoryCell for ReasoningSummaryCell {
|
|||||||
&mut lines,
|
&mut lines,
|
||||||
self.citation_context.clone(),
|
self.citation_context.clone(),
|
||||||
);
|
);
|
||||||
let summary_style = Style::default().add_modifier(Modifier::DIM | Modifier::ITALIC);
|
let summary_style = Style::default().dim().italic();
|
||||||
let summary_lines = lines
|
let summary_lines = lines
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut line| {
|
.map(|mut line| {
|
||||||
@@ -172,19 +182,31 @@ impl HistoryCell for ReasoningSummaryCell {
|
|||||||
.subsequent_indent(" ".into()),
|
.subsequent_indent(" ".into()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
impl HistoryCell for ReasoningSummaryCell {
|
||||||
let mut out: Vec<Line<'static>> = Vec::new();
|
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||||
out.push("thinking".magenta().bold().into());
|
if self.transcript_only {
|
||||||
let mut lines = Vec::new();
|
Vec::new()
|
||||||
append_markdown(
|
} else {
|
||||||
&self.content,
|
self.lines(width)
|
||||||
None,
|
}
|
||||||
&mut lines,
|
}
|
||||||
self.citation_context.clone(),
|
|
||||||
);
|
fn desired_height(&self, width: u16) -> u16 {
|
||||||
out.extend(lines);
|
if self.transcript_only {
|
||||||
out
|
0
|
||||||
|
} else {
|
||||||
|
self.lines(width).len() as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transcript_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||||
|
self.lines(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_transcript_height(&self, width: u16) -> u16 {
|
||||||
|
self.lines(width).len() as u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,15 +239,6 @@ impl HistoryCell for AgentMessageCell {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
|
||||||
let mut out: Vec<Line<'static>> = Vec::new();
|
|
||||||
if self.is_first_line {
|
|
||||||
out.push("codex".magenta().bold().into());
|
|
||||||
}
|
|
||||||
out.extend(self.lines.clone());
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_stream_continuation(&self) -> bool {
|
fn is_stream_continuation(&self) -> bool {
|
||||||
!self.is_first_line
|
!self.is_first_line
|
||||||
}
|
}
|
||||||
@@ -248,21 +261,6 @@ impl HistoryCell for PlainHistoryCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct TranscriptOnlyHistoryCell {
|
|
||||||
lines: Vec<Line<'static>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HistoryCell for TranscriptOnlyHistoryCell {
|
|
||||||
fn display_lines(&self, _width: u16) -> Vec<Line<'static>> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
|
||||||
self.lines.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cyan history cell line showing the current review status.
|
/// Cyan history cell line showing the current review status.
|
||||||
pub(crate) fn new_review_status_line(message: String) -> PlainHistoryCell {
|
pub(crate) fn new_review_status_line(message: String) -> PlainHistoryCell {
|
||||||
PlainHistoryCell {
|
PlainHistoryCell {
|
||||||
@@ -1050,16 +1048,6 @@ pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistor
|
|||||||
PlainHistoryCell { lines }
|
PlainHistoryCell { lines }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_reasoning_block(
|
|
||||||
full_reasoning_buffer: String,
|
|
||||||
config: &Config,
|
|
||||||
) -> TranscriptOnlyHistoryCell {
|
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
|
||||||
lines.push(Line::from("thinking".magenta().italic()));
|
|
||||||
append_markdown(&full_reasoning_buffer, None, &mut lines, config);
|
|
||||||
TranscriptOnlyHistoryCell { lines }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new_reasoning_summary_block(
|
pub(crate) fn new_reasoning_summary_block(
|
||||||
full_reasoning_buffer: String,
|
full_reasoning_buffer: String,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
@@ -1085,12 +1073,18 @@ pub(crate) fn new_reasoning_summary_block(
|
|||||||
header_buffer,
|
header_buffer,
|
||||||
summary_buffer,
|
summary_buffer,
|
||||||
config.into(),
|
config.into(),
|
||||||
|
false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box::new(new_reasoning_block(full_reasoning_buffer, config))
|
Box::new(ReasoningSummaryCell::new(
|
||||||
|
"".to_string(),
|
||||||
|
full_reasoning_buffer,
|
||||||
|
config.into(),
|
||||||
|
true,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1121,10 +1115,6 @@ impl HistoryCell for FinalMessageSeparator {
|
|||||||
vec![Line::from_iter(["─".repeat(width as usize).dim()])]
|
vec![Line::from_iter(["─".repeat(width as usize).dim()])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> {
|
fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> {
|
||||||
@@ -1188,7 +1178,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_transcript(cell: &dyn HistoryCell) -> Vec<String> {
|
fn render_transcript(cell: &dyn HistoryCell) -> Vec<String> {
|
||||||
render_lines(&cell.transcript_lines())
|
render_lines(&cell.transcript_lines(u16::MAX))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_agent_message_cell_transcript() {
|
||||||
|
let cell = AgentMessageCell::new(vec![Line::default()], false);
|
||||||
|
assert_eq!(cell.transcript_lines(80), vec![Line::from(" ")]);
|
||||||
|
assert_eq!(cell.desired_transcript_height(80), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1883,10 +1880,7 @@ mod tests {
|
|||||||
assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);
|
assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);
|
||||||
|
|
||||||
let rendered_transcript = render_transcript(cell.as_ref());
|
let rendered_transcript = render_transcript(cell.as_ref());
|
||||||
assert_eq!(
|
assert_eq!(rendered_transcript, vec!["• Detailed reasoning goes here."]);
|
||||||
rendered_transcript,
|
|
||||||
vec!["thinking", "Detailed reasoning goes here."]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1898,7 +1892,7 @@ mod tests {
|
|||||||
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &config);
|
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &config);
|
||||||
|
|
||||||
let rendered = render_transcript(cell.as_ref());
|
let rendered = render_transcript(cell.as_ref());
|
||||||
assert_eq!(rendered, vec!["thinking", "Detailed reasoning goes here."]);
|
assert_eq!(rendered, vec!["• Detailed reasoning goes here."]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1912,10 +1906,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rendered = render_transcript(cell.as_ref());
|
let rendered = render_transcript(cell.as_ref());
|
||||||
assert_eq!(
|
assert_eq!(rendered, vec!["• **High level reasoning without closing"]);
|
||||||
rendered,
|
|
||||||
vec!["thinking", "**High level reasoning without closing"]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1929,10 +1920,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rendered = render_transcript(cell.as_ref());
|
let rendered = render_transcript(cell.as_ref());
|
||||||
assert_eq!(
|
assert_eq!(rendered, vec!["• High level reasoning without closing"]);
|
||||||
rendered,
|
|
||||||
vec!["thinking", "High level reasoning without closing"]
|
|
||||||
);
|
|
||||||
|
|
||||||
let cell = new_reasoning_summary_block(
|
let cell = new_reasoning_summary_block(
|
||||||
"**High level reasoning without closing**\n\n ".to_string(),
|
"**High level reasoning without closing**\n\n ".to_string(),
|
||||||
@@ -1940,10 +1928,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rendered = render_transcript(cell.as_ref());
|
let rendered = render_transcript(cell.as_ref());
|
||||||
assert_eq!(
|
assert_eq!(rendered, vec!["• High level reasoning without closing"]);
|
||||||
rendered,
|
|
||||||
vec!["thinking", "High level reasoning without closing"]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1960,9 +1945,6 @@ mod tests {
|
|||||||
assert_eq!(rendered_display, vec!["• We should fix the bug next."]);
|
assert_eq!(rendered_display, vec!["• We should fix the bug next."]);
|
||||||
|
|
||||||
let rendered_transcript = render_transcript(cell.as_ref());
|
let rendered_transcript = render_transcript(cell.as_ref());
|
||||||
assert_eq!(
|
assert_eq!(rendered_transcript, vec!["• We should fix the bug next."]);
|
||||||
rendered_transcript,
|
|
||||||
vec!["thinking", "We should fix the bug next."]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::history_cell::HistoryCell;
|
use crate::history_cell::HistoryCell;
|
||||||
|
use crate::history_cell::UserHistoryCell;
|
||||||
use crate::key_hint;
|
use crate::key_hint;
|
||||||
use crate::key_hint::KeyBinding;
|
use crate::key_hint::KeyBinding;
|
||||||
|
use crate::render::Insets;
|
||||||
|
use crate::render::renderable::InsetRenderable;
|
||||||
use crate::render::renderable::Renderable;
|
use crate::render::renderable::Renderable;
|
||||||
|
use crate::style::user_message_style;
|
||||||
use crate::tui;
|
use crate::tui;
|
||||||
use crate::tui::TuiEvent;
|
use crate::tui::TuiEvent;
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
@@ -13,6 +17,7 @@ use crossterm::event::KeyEvent;
|
|||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::buffer::Cell;
|
use ratatui::buffer::Cell;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
use ratatui::style::Style;
|
||||||
use ratatui::style::Stylize;
|
use ratatui::style::Stylize;
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
use ratatui::text::Span;
|
use ratatui::text::Span;
|
||||||
@@ -21,7 +26,6 @@ use ratatui::widgets::Clear;
|
|||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use ratatui::widgets::Widget;
|
use ratatui::widgets::Widget;
|
||||||
use ratatui::widgets::WidgetRef;
|
use ratatui::widgets::WidgetRef;
|
||||||
use ratatui::widgets::Wrap;
|
|
||||||
|
|
||||||
pub(crate) enum Overlay {
|
pub(crate) enum Overlay {
|
||||||
Transcript(TranscriptOverlay),
|
Transcript(TranscriptOverlay),
|
||||||
@@ -317,29 +321,30 @@ impl PagerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CachedParagraph {
|
/// A renderable that caches its desired height.
|
||||||
paragraph: Paragraph<'static>,
|
struct CachedRenderable {
|
||||||
|
renderable: Box<dyn Renderable>,
|
||||||
height: std::cell::Cell<Option<u16>>,
|
height: std::cell::Cell<Option<u16>>,
|
||||||
last_width: std::cell::Cell<Option<u16>>,
|
last_width: std::cell::Cell<Option<u16>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CachedParagraph {
|
impl CachedRenderable {
|
||||||
fn new(paragraph: Paragraph<'static>) -> Self {
|
fn new(renderable: Box<dyn Renderable>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
paragraph,
|
renderable,
|
||||||
height: std::cell::Cell::new(None),
|
height: std::cell::Cell::new(None),
|
||||||
last_width: std::cell::Cell::new(None),
|
last_width: std::cell::Cell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderable for CachedParagraph {
|
impl Renderable for CachedRenderable {
|
||||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||||
self.paragraph.render_ref(area, buf);
|
self.renderable.render(area, buf);
|
||||||
}
|
}
|
||||||
fn desired_height(&self, width: u16) -> u16 {
|
fn desired_height(&self, width: u16) -> u16 {
|
||||||
if self.last_width.get() != Some(width) {
|
if self.last_width.get() != Some(width) {
|
||||||
let height = self.paragraph.line_count(width) as u16;
|
let height = self.renderable.desired_height(width);
|
||||||
self.height.set(Some(height));
|
self.height.set(Some(height));
|
||||||
self.last_width.set(Some(width));
|
self.last_width.set(Some(width));
|
||||||
}
|
}
|
||||||
@@ -347,6 +352,23 @@ impl Renderable for CachedParagraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CellRenderable {
|
||||||
|
cell: Arc<dyn HistoryCell>,
|
||||||
|
style: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable for CellRenderable {
|
||||||
|
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let p =
|
||||||
|
Paragraph::new(Text::from(self.cell.transcript_lines(area.width))).style(self.style);
|
||||||
|
p.render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_height(&self, width: u16) -> u16 {
|
||||||
|
self.cell.desired_transcript_height(width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct TranscriptOverlay {
|
pub(crate) struct TranscriptOverlay {
|
||||||
view: PagerView,
|
view: PagerView,
|
||||||
cells: Vec<Arc<dyn HistoryCell>>,
|
cells: Vec<Arc<dyn HistoryCell>>,
|
||||||
@@ -358,7 +380,7 @@ impl TranscriptOverlay {
|
|||||||
pub(crate) fn new(transcript_cells: Vec<Arc<dyn HistoryCell>>) -> Self {
|
pub(crate) fn new(transcript_cells: Vec<Arc<dyn HistoryCell>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view: PagerView::new(
|
view: PagerView::new(
|
||||||
Self::render_cells_to_texts(&transcript_cells, None),
|
Self::render_cells(&transcript_cells, None),
|
||||||
"T R A N S C R I P T".to_string(),
|
"T R A N S C R I P T".to_string(),
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
),
|
),
|
||||||
@@ -368,46 +390,46 @@ impl TranscriptOverlay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_cells_to_texts(
|
fn render_cells(
|
||||||
cells: &[Arc<dyn HistoryCell>],
|
cells: &[Arc<dyn HistoryCell>],
|
||||||
highlight_cell: Option<usize>,
|
highlight_cell: Option<usize>,
|
||||||
) -> Vec<Box<dyn Renderable>> {
|
) -> Vec<Box<dyn Renderable>> {
|
||||||
let mut texts: Vec<Box<dyn Renderable>> = Vec::new();
|
cells
|
||||||
let mut first = true;
|
.iter()
|
||||||
for (idx, cell) in cells.iter().enumerate() {
|
.enumerate()
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
.flat_map(|(i, c)| {
|
||||||
if !cell.is_stream_continuation() && !first {
|
let mut v: Vec<Box<dyn Renderable>> = Vec::new();
|
||||||
lines.push(Line::from(""));
|
let mut cell_renderable = if c.as_any().is::<UserHistoryCell>() {
|
||||||
}
|
Box::new(CachedRenderable::new(Box::new(CellRenderable {
|
||||||
let cell_lines = if Some(idx) == highlight_cell {
|
cell: c.clone(),
|
||||||
cell.transcript_lines()
|
style: if highlight_cell == Some(i) {
|
||||||
.into_iter()
|
user_message_style().reversed()
|
||||||
.map(Stylize::reversed)
|
} else {
|
||||||
.collect()
|
user_message_style()
|
||||||
} else {
|
},
|
||||||
cell.transcript_lines()
|
}))) as Box<dyn Renderable>
|
||||||
};
|
} else {
|
||||||
lines.extend(cell_lines);
|
Box::new(CachedRenderable::new(Box::new(CellRenderable {
|
||||||
texts.push(Box::new(CachedParagraph::new(
|
cell: c.clone(),
|
||||||
Paragraph::new(Text::from(lines)).wrap(Wrap { trim: false }),
|
style: Style::default(),
|
||||||
)));
|
}))) as Box<dyn Renderable>
|
||||||
first = false;
|
};
|
||||||
}
|
if !c.is_stream_continuation() && i > 0 {
|
||||||
texts
|
cell_renderable = Box::new(InsetRenderable::new(
|
||||||
|
cell_renderable,
|
||||||
|
Insets::tlbr(1, 0, 0, 0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
v.push(cell_renderable);
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_cell(&mut self, cell: Arc<dyn HistoryCell>) {
|
pub(crate) fn insert_cell(&mut self, cell: Arc<dyn HistoryCell>) {
|
||||||
let follow_bottom = self.view.is_scrolled_to_bottom();
|
let follow_bottom = self.view.is_scrolled_to_bottom();
|
||||||
// Append as a new Text chunk (with a separating blank if needed)
|
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
|
||||||
if !cell.is_stream_continuation() && !self.cells.is_empty() {
|
|
||||||
lines.push(Line::from(""));
|
|
||||||
}
|
|
||||||
lines.extend(cell.transcript_lines());
|
|
||||||
self.view.renderables.push(Box::new(CachedParagraph::new(
|
|
||||||
Paragraph::new(Text::from(lines)).wrap(Wrap { trim: false }),
|
|
||||||
)));
|
|
||||||
self.cells.push(cell);
|
self.cells.push(cell);
|
||||||
|
self.view.renderables = Self::render_cells(&self.cells, self.highlight_cell);
|
||||||
if follow_bottom {
|
if follow_bottom {
|
||||||
self.view.scroll_offset = usize::MAX;
|
self.view.scroll_offset = usize::MAX;
|
||||||
}
|
}
|
||||||
@@ -415,7 +437,7 @@ impl TranscriptOverlay {
|
|||||||
|
|
||||||
pub(crate) fn set_highlight_cell(&mut self, cell: Option<usize>) {
|
pub(crate) fn set_highlight_cell(&mut self, cell: Option<usize>) {
|
||||||
self.highlight_cell = cell;
|
self.highlight_cell = cell;
|
||||||
self.view.renderables = Self::render_cells_to_texts(&self.cells, self.highlight_cell);
|
self.view.renderables = Self::render_cells(&self.cells, self.highlight_cell);
|
||||||
if let Some(idx) = self.highlight_cell {
|
if let Some(idx) = self.highlight_cell {
|
||||||
self.view.scroll_chunk_into_view(idx);
|
self.view.scroll_chunk_into_view(idx);
|
||||||
}
|
}
|
||||||
@@ -475,8 +497,8 @@ pub(crate) struct StaticOverlay {
|
|||||||
impl StaticOverlay {
|
impl StaticOverlay {
|
||||||
pub(crate) fn with_title(lines: Vec<Line<'static>>, title: String) -> Self {
|
pub(crate) fn with_title(lines: Vec<Line<'static>>, title: String) -> Self {
|
||||||
Self::with_renderables(
|
Self::with_renderables(
|
||||||
vec![Box::new(CachedParagraph::new(Paragraph::new(Text::from(
|
vec![Box::new(CachedRenderable::new(Box::new(Paragraph::new(
|
||||||
lines,
|
Text::from(lines),
|
||||||
))))],
|
))))],
|
||||||
title,
|
title,
|
||||||
)
|
)
|
||||||
@@ -585,7 +607,7 @@ mod tests {
|
|||||||
self.lines.clone()
|
self.lines.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript_lines(&self) -> Vec<Line<'static>> {
|
fn transcript_lines(&self, _width: u16) -> Vec<Line<'static>> {
|
||||||
self.lines.clone()
|
self.lines.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,11 +38,13 @@ pub trait RectExt {
|
|||||||
|
|
||||||
impl RectExt for Rect {
|
impl RectExt for Rect {
|
||||||
fn inset(&self, insets: Insets) -> Rect {
|
fn inset(&self, insets: Insets) -> Rect {
|
||||||
|
let horizontal = insets.left.saturating_add(insets.right);
|
||||||
|
let vertical = insets.top.saturating_add(insets.bottom);
|
||||||
Rect {
|
Rect {
|
||||||
x: self.x + insets.left,
|
x: self.x.saturating_add(insets.left),
|
||||||
y: self.y + insets.top,
|
y: self.y.saturating_add(insets.top),
|
||||||
width: self.width - insets.left - insets.right,
|
width: self.width.saturating_sub(horizontal),
|
||||||
height: self.height - insets.top - insets.bottom,
|
height: self.height.saturating_sub(vertical),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
@@ -12,6 +14,12 @@ pub trait Renderable {
|
|||||||
fn desired_height(&self, width: u16) -> u16;
|
fn desired_height(&self, width: u16) -> u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Renderable + 'static> From<R> for Box<dyn Renderable> {
|
||||||
|
fn from(value: R) -> Self {
|
||||||
|
Box::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Renderable for () {
|
impl Renderable for () {
|
||||||
fn render(&self, _area: Rect, _buf: &mut Buffer) {}
|
fn render(&self, _area: Rect, _buf: &mut Buffer) {}
|
||||||
fn desired_height(&self, _width: u16) -> u16 {
|
fn desired_height(&self, _width: u16) -> u16 {
|
||||||
@@ -71,6 +79,15 @@ impl<R: Renderable> Renderable for Option<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Renderable> Renderable for Arc<R> {
|
||||||
|
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
self.as_ref().render(area, buf);
|
||||||
|
}
|
||||||
|
fn desired_height(&self, width: u16) -> u16 {
|
||||||
|
self.as_ref().desired_height(width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ColumnRenderable {
|
pub struct ColumnRenderable {
|
||||||
children: Vec<Box<dyn Renderable>>,
|
children: Vec<Box<dyn Renderable>>,
|
||||||
}
|
}
|
||||||
@@ -122,7 +139,10 @@ impl Renderable for InsetRenderable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InsetRenderable {
|
impl InsetRenderable {
|
||||||
pub fn new(child: Box<dyn Renderable>, insets: Insets) -> Self {
|
pub fn new(child: impl Into<Box<dyn Renderable>>, insets: Insets) -> Self {
|
||||||
Self { child, insets }
|
Self {
|
||||||
|
child: child.into(),
|
||||||
|
insets,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ pub(crate) fn log_inbound_app_event(event: &AppEvent) {
|
|||||||
"ts": now_ts(),
|
"ts": now_ts(),
|
||||||
"dir": "to_tui",
|
"dir": "to_tui",
|
||||||
"kind": "insert_history_cell",
|
"kind": "insert_history_cell",
|
||||||
"lines": cell.transcript_lines().len(),
|
"lines": cell.transcript_lines(u16::MAX).len(),
|
||||||
});
|
});
|
||||||
LOGGER.write_json_line(value);
|
LOGGER.write_json_line(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::ConfigOverrides;
|
use codex_core::config::ConfigOverrides;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
async fn test_config() -> Config {
|
async fn test_config() -> Config {
|
||||||
let overrides = ConfigOverrides {
|
let overrides = ConfigOverrides {
|
||||||
@@ -195,7 +196,7 @@ mod tests {
|
|||||||
for d in deltas.iter() {
|
for d in deltas.iter() {
|
||||||
ctrl.push(d);
|
ctrl.push(d);
|
||||||
while let (Some(cell), idle) = ctrl.on_commit_tick() {
|
while let (Some(cell), idle) = ctrl.on_commit_tick() {
|
||||||
lines.extend(cell.transcript_lines());
|
lines.extend(cell.transcript_lines(u16::MAX));
|
||||||
if idle {
|
if idle {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -203,21 +204,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
// Finalize and flush remaining lines now.
|
// Finalize and flush remaining lines now.
|
||||||
if let Some(cell) = ctrl.finalize() {
|
if let Some(cell) = ctrl.finalize() {
|
||||||
lines.extend(cell.transcript_lines());
|
lines.extend(cell.transcript_lines(u16::MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut flat = lines;
|
let streamed: Vec<_> = lines_to_plain_strings(&lines)
|
||||||
// Drop leading blank and header line if present.
|
.into_iter()
|
||||||
if !flat.is_empty() && lines_to_plain_strings(&[flat[0].clone()])[0].is_empty() {
|
// skip • and 2-space indentation
|
||||||
flat.remove(0);
|
.map(|s| s.chars().skip(2).collect::<String>())
|
||||||
}
|
.collect();
|
||||||
if !flat.is_empty() {
|
|
||||||
let s0 = lines_to_plain_strings(&[flat[0].clone()])[0].clone();
|
|
||||||
if s0 == "codex" {
|
|
||||||
flat.remove(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let streamed = lines_to_plain_strings(&flat);
|
|
||||||
|
|
||||||
// Full render of the same source
|
// Full render of the same source
|
||||||
let source: String = deltas.iter().copied().collect();
|
let source: String = deltas.iter().copied().collect();
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
use crate::color::blend;
|
use crate::color::blend;
|
||||||
use crate::color::is_light;
|
use crate::color::is_light;
|
||||||
use crate::color::perceptual_distance;
|
use crate::color::perceptual_distance;
|
||||||
|
use crate::terminal_palette::default_bg;
|
||||||
use crate::terminal_palette::terminal_palette;
|
use crate::terminal_palette::terminal_palette;
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
use ratatui::style::Style;
|
use ratatui::style::Style;
|
||||||
|
|
||||||
|
pub fn user_message_style() -> Style {
|
||||||
|
user_message_style_for(default_bg())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the style for a user-authored message using the provided terminal background.
|
/// Returns the style for a user-authored message using the provided terminal background.
|
||||||
pub fn user_message_style(terminal_bg: Option<(u8, u8, u8)>) -> Style {
|
pub fn user_message_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style {
|
||||||
match terminal_bg {
|
match terminal_bg {
|
||||||
Some(bg) => Style::default().bg(user_message_bg(bg)),
|
Some(bg) => Style::default().bg(user_message_bg(bg)),
|
||||||
None => Style::default(),
|
None => Style::default(),
|
||||||
|
|||||||
Reference in New Issue
Block a user