remove conversation history widget (#1727)
this widget is no longer used.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
use crate::cell_widget::CellWidget;
|
||||
use crate::exec_command::escape_command;
|
||||
use crate::markdown::append_markdown;
|
||||
use crate::text_block::TextBlock;
|
||||
@@ -11,11 +10,10 @@ use codex_core::WireApi;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::model_supports_reasoning_summaries;
|
||||
use codex_core::protocol::FileChange;
|
||||
use codex_core::protocol::McpInvocation;
|
||||
use codex_core::protocol::SessionConfiguredEvent;
|
||||
use image::DynamicImage;
|
||||
use image::GenericImageView;
|
||||
use image::ImageReader;
|
||||
use lazy_static::lazy_static;
|
||||
use mcp_types::EmbeddedResourceResource;
|
||||
use mcp_types::ResourceLink;
|
||||
use ratatui::prelude::*;
|
||||
@@ -24,14 +22,10 @@ use ratatui::style::Modifier;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::Line as RtLine;
|
||||
use ratatui::text::Span as RtSpan;
|
||||
use ratatui_image::Image as TuiImage;
|
||||
use ratatui_image::Resize as ImgResize;
|
||||
use ratatui_image::picker::ProtocolType;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) struct CommandOutput {
|
||||
@@ -46,6 +40,21 @@ pub(crate) enum PatchEventType {
|
||||
ApplyBegin { auto_approved: bool },
|
||||
}
|
||||
|
||||
fn span_to_static(span: &Span) -> Span<'static> {
|
||||
Span {
|
||||
style: span.style,
|
||||
content: std::borrow::Cow::Owned(span.content.clone().into_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn line_to_static(line: &Line) -> Line<'static> {
|
||||
Line {
|
||||
style: line.style,
|
||||
alignment: line.alignment,
|
||||
spans: line.spans.iter().map(span_to_static).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an event to display in the conversation history. Returns its
|
||||
/// `Vec<Line<'static>>` representation to make it easier to display in a
|
||||
/// scrollable list.
|
||||
@@ -63,25 +72,13 @@ pub(crate) enum HistoryCell {
|
||||
AgentReasoning { view: TextBlock },
|
||||
|
||||
/// An exec tool call that has not finished yet.
|
||||
ActiveExecCommand {
|
||||
call_id: String,
|
||||
/// The shell command, escaped and formatted.
|
||||
command: String,
|
||||
start: Instant,
|
||||
view: TextBlock,
|
||||
},
|
||||
ActiveExecCommand { view: TextBlock },
|
||||
|
||||
/// Completed exec tool call.
|
||||
CompletedExecCommand { view: TextBlock },
|
||||
|
||||
/// An MCP tool call that has not finished yet.
|
||||
ActiveMcpToolCall {
|
||||
call_id: String,
|
||||
/// Formatted line that shows the command name and arguments
|
||||
invocation: Line<'static>,
|
||||
start: Instant,
|
||||
view: TextBlock,
|
||||
},
|
||||
ActiveMcpToolCall { view: TextBlock },
|
||||
|
||||
/// Completed MCP tool call where we show the result serialized as JSON.
|
||||
CompletedMcpToolCall { view: TextBlock },
|
||||
@@ -94,13 +91,7 @@ pub(crate) enum HistoryCell {
|
||||
// resized version avoids doing the potentially expensive rescale twice
|
||||
// because the scroll-view first calls `height()` for layouting and then
|
||||
// `render_window()` for painting.
|
||||
CompletedMcpToolCallWithImageOutput {
|
||||
image: DynamicImage,
|
||||
/// Cached data derived from the current terminal width. The cache is
|
||||
/// invalidated whenever the width changes (e.g. when the user
|
||||
/// resizes the window).
|
||||
render_cache: std::cell::RefCell<Option<ImageRenderCache>>,
|
||||
},
|
||||
CompletedMcpToolCallWithImageOutput { _image: DynamicImage },
|
||||
|
||||
/// Background event.
|
||||
BackgroundEvent { view: TextBlock },
|
||||
@@ -140,7 +131,9 @@ impl HistoryCell {
|
||||
| HistoryCell::CompletedMcpToolCall { view }
|
||||
| HistoryCell::PendingPatch { view }
|
||||
| HistoryCell::ActiveExecCommand { view, .. }
|
||||
| HistoryCell::ActiveMcpToolCall { view, .. } => view.lines.clone(),
|
||||
| HistoryCell::ActiveMcpToolCall { view, .. } => {
|
||||
view.lines.iter().map(line_to_static).collect()
|
||||
}
|
||||
HistoryCell::CompletedMcpToolCallWithImageOutput { .. } => vec![
|
||||
Line::from("tool result (image output omitted)"),
|
||||
Line::from(""),
|
||||
@@ -252,9 +245,8 @@ impl HistoryCell {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_active_exec_command(call_id: String, command: Vec<String>) -> Self {
|
||||
pub(crate) fn new_active_exec_command(command: Vec<String>) -> Self {
|
||||
let command_escaped = escape_command(&command);
|
||||
let start = Instant::now();
|
||||
|
||||
let lines: Vec<Line<'static>> = vec![
|
||||
Line::from(vec!["command".magenta(), " running...".dim()]),
|
||||
@@ -263,9 +255,6 @@ impl HistoryCell {
|
||||
];
|
||||
|
||||
HistoryCell::ActiveExecCommand {
|
||||
call_id,
|
||||
command: command_escaped,
|
||||
start,
|
||||
view: TextBlock::new(lines),
|
||||
}
|
||||
}
|
||||
@@ -310,41 +299,15 @@ impl HistoryCell {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_active_mcp_tool_call(
|
||||
call_id: String,
|
||||
server: String,
|
||||
tool: String,
|
||||
arguments: Option<serde_json::Value>,
|
||||
) -> Self {
|
||||
// Format the arguments as compact JSON so they roughly fit on one
|
||||
// line. If there are no arguments we keep it empty so the invocation
|
||||
// mirrors a function-style call.
|
||||
let args_str = arguments
|
||||
.as_ref()
|
||||
.map(|v| {
|
||||
// Use compact form to keep things short but readable.
|
||||
serde_json::to_string(v).unwrap_or_else(|_| v.to_string())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let invocation_spans = vec![
|
||||
Span::styled(server, Style::default().fg(Color::Blue)),
|
||||
Span::raw("."),
|
||||
Span::styled(tool, Style::default().fg(Color::Blue)),
|
||||
Span::raw("("),
|
||||
Span::styled(args_str, Style::default().fg(Color::Gray)),
|
||||
Span::raw(")"),
|
||||
];
|
||||
let invocation = Line::from(invocation_spans);
|
||||
|
||||
let start = Instant::now();
|
||||
pub(crate) fn new_active_mcp_tool_call(invocation: McpInvocation) -> Self {
|
||||
let title_line = Line::from(vec!["tool".magenta(), " running...".dim()]);
|
||||
let lines: Vec<Line<'static>> = vec![title_line, invocation.clone(), Line::from("")];
|
||||
let lines: Vec<Line> = vec![
|
||||
title_line,
|
||||
format_mcp_invocation(invocation.clone()),
|
||||
Line::from(""),
|
||||
];
|
||||
|
||||
HistoryCell::ActiveMcpToolCall {
|
||||
call_id,
|
||||
invocation,
|
||||
start,
|
||||
view: TextBlock::new(lines),
|
||||
}
|
||||
}
|
||||
@@ -382,10 +345,7 @@ impl HistoryCell {
|
||||
}
|
||||
};
|
||||
|
||||
Some(HistoryCell::CompletedMcpToolCallWithImageOutput {
|
||||
image,
|
||||
render_cache: std::cell::RefCell::new(None),
|
||||
})
|
||||
Some(HistoryCell::CompletedMcpToolCallWithImageOutput { _image: image })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -396,8 +356,8 @@ impl HistoryCell {
|
||||
|
||||
pub(crate) fn new_completed_mcp_tool_call(
|
||||
num_cols: u16,
|
||||
invocation: Line<'static>,
|
||||
start: Instant,
|
||||
invocation: McpInvocation,
|
||||
duration: Duration,
|
||||
success: bool,
|
||||
result: Result<mcp_types::CallToolResult, String>,
|
||||
) -> Self {
|
||||
@@ -405,7 +365,7 @@ impl HistoryCell {
|
||||
return cell;
|
||||
}
|
||||
|
||||
let duration = format_duration(start.elapsed());
|
||||
let duration = format_duration(duration);
|
||||
let status_str = if success { "success" } else { "failed" };
|
||||
let title_line = Line::from(vec![
|
||||
"tool".magenta(),
|
||||
@@ -420,7 +380,7 @@ impl HistoryCell {
|
||||
|
||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||
lines.push(title_line);
|
||||
lines.push(invocation);
|
||||
lines.push(format_mcp_invocation(invocation));
|
||||
|
||||
match result {
|
||||
Ok(mcp_types::CallToolResult { content, .. }) => {
|
||||
@@ -581,85 +541,6 @@ impl HistoryCell {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// `CellWidget` implementation – most variants delegate to their internal
|
||||
// `TextBlock`. Variants that need custom painting can add their own logic in
|
||||
// the match arms.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
impl CellWidget for HistoryCell {
|
||||
fn height(&self, width: u16) -> usize {
|
||||
match self {
|
||||
HistoryCell::WelcomeMessage { view }
|
||||
| HistoryCell::UserPrompt { view }
|
||||
| HistoryCell::AgentMessage { view }
|
||||
| HistoryCell::AgentReasoning { view }
|
||||
| HistoryCell::BackgroundEvent { view }
|
||||
| HistoryCell::GitDiffOutput { view }
|
||||
| HistoryCell::ErrorEvent { view }
|
||||
| HistoryCell::SessionInfo { view }
|
||||
| HistoryCell::CompletedExecCommand { view }
|
||||
| HistoryCell::CompletedMcpToolCall { view }
|
||||
| HistoryCell::PendingPatch { view }
|
||||
| HistoryCell::ActiveExecCommand { view, .. }
|
||||
| HistoryCell::ActiveMcpToolCall { view, .. } => view.height(width),
|
||||
HistoryCell::CompletedMcpToolCallWithImageOutput {
|
||||
image,
|
||||
render_cache,
|
||||
} => ensure_image_cache(image, width, render_cache),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_window(&self, first_visible_line: usize, area: Rect, buf: &mut Buffer) {
|
||||
match self {
|
||||
HistoryCell::WelcomeMessage { view }
|
||||
| HistoryCell::UserPrompt { view }
|
||||
| HistoryCell::AgentMessage { view }
|
||||
| HistoryCell::AgentReasoning { view }
|
||||
| HistoryCell::BackgroundEvent { view }
|
||||
| HistoryCell::GitDiffOutput { view }
|
||||
| HistoryCell::ErrorEvent { view }
|
||||
| HistoryCell::SessionInfo { view }
|
||||
| HistoryCell::CompletedExecCommand { view }
|
||||
| HistoryCell::CompletedMcpToolCall { view }
|
||||
| HistoryCell::PendingPatch { view }
|
||||
| HistoryCell::ActiveExecCommand { view, .. }
|
||||
| HistoryCell::ActiveMcpToolCall { view, .. } => {
|
||||
view.render_window(first_visible_line, area, buf)
|
||||
}
|
||||
HistoryCell::CompletedMcpToolCallWithImageOutput {
|
||||
image,
|
||||
render_cache,
|
||||
} => {
|
||||
// Ensure we have a cached, resized copy that matches the current width.
|
||||
// `height()` should have prepared the cache, but if something invalidated it
|
||||
// (e.g. the first `render_window()` call happens *before* `height()` after a
|
||||
// resize) we rebuild it here.
|
||||
|
||||
let width_cells = area.width;
|
||||
|
||||
// Ensure the cache is up-to-date and extract the scaled image.
|
||||
let _ = ensure_image_cache(image, width_cells, render_cache);
|
||||
|
||||
let Some(resized) = render_cache
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|c| c.scaled_image.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let picker = &*TERMINAL_PICKER;
|
||||
|
||||
if let Ok(protocol) = picker.new_protocol(resized, area, ImgResize::Fit(None)) {
|
||||
let img_widget = TuiImage::new(&protocol);
|
||||
img_widget.render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_diff_summary(changes: HashMap<PathBuf, FileChange>) -> Vec<String> {
|
||||
// Build a concise, human‑readable summary list similar to the
|
||||
// `git status` short format so the user can reason about the
|
||||
@@ -692,119 +573,23 @@ fn create_diff_summary(changes: HashMap<PathBuf, FileChange>) -> Vec<String> {
|
||||
summaries
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// Helper types for image rendering
|
||||
// -------------------------------------
|
||||
fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> {
|
||||
let args_str = invocation
|
||||
.arguments
|
||||
.as_ref()
|
||||
.map(|v| {
|
||||
// Use compact form to keep things short but readable.
|
||||
serde_json::to_string(v).unwrap_or_else(|_| v.to_string())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
/// Cached information for rendering an image inside a conversation cell.
|
||||
///
|
||||
/// The cache ties the resized image to a *specific* content width (in
|
||||
/// terminal cells). Whenever the terminal is resized and the width changes
|
||||
/// we need to re-compute the scaled variant so that it still fits the
|
||||
/// available space. Keeping the resized copy around saves a costly rescale
|
||||
/// between the back-to-back `height()` and `render_window()` calls that the
|
||||
/// scroll-view performs while laying out the UI.
|
||||
pub(crate) struct ImageRenderCache {
|
||||
/// Width in *terminal cells* the cached image was generated for.
|
||||
width_cells: u16,
|
||||
/// Height in *terminal rows* that the conversation cell must occupy so
|
||||
/// the whole image becomes visible.
|
||||
height_rows: usize,
|
||||
/// The resized image that fits the given width / height constraints.
|
||||
scaled_image: DynamicImage,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TERMINAL_PICKER: ratatui_image::picker::Picker = {
|
||||
use ratatui_image::picker::Picker;
|
||||
use ratatui_image::picker::cap_parser::QueryStdioOptions;
|
||||
|
||||
// Ask the terminal for capabilities and explicit font size. Request the
|
||||
// Kitty *text-sizing protocol* as a fallback mechanism for terminals
|
||||
// (like iTerm2) that do not reply to the standard CSI 16/18 queries.
|
||||
match Picker::from_query_stdio_with_options(QueryStdioOptions {
|
||||
text_sizing_protocol: true,
|
||||
}) {
|
||||
Ok(picker) => picker,
|
||||
Err(err) => {
|
||||
// Fall back to the conservative default that assumes ~8×16 px cells.
|
||||
// Still better than breaking the build in a headless test run.
|
||||
tracing::warn!("terminal capability query failed: {err:?}; using default font size");
|
||||
Picker::from_fontsize((8, 16))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Resize `image` to fit into `width_cells`×10-rows keeping the original aspect
|
||||
/// ratio. The function updates `render_cache` and returns the number of rows
|
||||
/// (<= 10) the picture will occupy.
|
||||
fn ensure_image_cache(
|
||||
image: &DynamicImage,
|
||||
width_cells: u16,
|
||||
render_cache: &std::cell::RefCell<Option<ImageRenderCache>>,
|
||||
) -> usize {
|
||||
if let Some(cache) = render_cache.borrow().as_ref() {
|
||||
if cache.width_cells == width_cells {
|
||||
return cache.height_rows;
|
||||
}
|
||||
}
|
||||
|
||||
let picker = &*TERMINAL_PICKER;
|
||||
let (char_w_px, char_h_px) = picker.font_size();
|
||||
|
||||
// Heuristic to compensate for Hi-DPI terminals (iTerm2 on Retina Mac) that
|
||||
// report logical pixels (≈ 8×16) while the iTerm2 graphics protocol
|
||||
// expects *device* pixels. Empirically the device-pixel-ratio is almost
|
||||
// always 2 on macOS Retina panels.
|
||||
let hidpi_scale = if picker.protocol_type() == ProtocolType::Iterm2 {
|
||||
2.0f64
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
// The fallback Halfblocks protocol encodes two pixel rows per cell, so each
|
||||
// terminal *row* represents only half the (possibly scaled) font height.
|
||||
let effective_char_h_px: f64 = if picker.protocol_type() == ProtocolType::Halfblocks {
|
||||
(char_h_px as f64) * hidpi_scale / 2.0
|
||||
} else {
|
||||
(char_h_px as f64) * hidpi_scale
|
||||
};
|
||||
|
||||
let char_w_px_f64 = (char_w_px as f64) * hidpi_scale;
|
||||
|
||||
const MAX_ROWS: f64 = 10.0;
|
||||
let max_height_px: f64 = effective_char_h_px * MAX_ROWS;
|
||||
|
||||
let (orig_w_px, orig_h_px) = {
|
||||
let (w, h) = image.dimensions();
|
||||
(w as f64, h as f64)
|
||||
};
|
||||
|
||||
if orig_w_px == 0.0 || orig_h_px == 0.0 || width_cells == 0 {
|
||||
*render_cache.borrow_mut() = None;
|
||||
return 0;
|
||||
}
|
||||
|
||||
let max_w_px = char_w_px_f64 * width_cells as f64;
|
||||
let scale_w = max_w_px / orig_w_px;
|
||||
let scale_h = max_height_px / orig_h_px;
|
||||
let scale = scale_w.min(scale_h).min(1.0);
|
||||
|
||||
use image::imageops::FilterType;
|
||||
let scaled_w_px = (orig_w_px * scale).round().max(1.0) as u32;
|
||||
let scaled_h_px = (orig_h_px * scale).round().max(1.0) as u32;
|
||||
|
||||
let scaled_image = image.resize(scaled_w_px, scaled_h_px, FilterType::Lanczos3);
|
||||
|
||||
let height_rows = ((scaled_h_px as f64 / effective_char_h_px).ceil()) as usize;
|
||||
|
||||
let new_cache = ImageRenderCache {
|
||||
width_cells,
|
||||
height_rows,
|
||||
scaled_image,
|
||||
};
|
||||
*render_cache.borrow_mut() = Some(new_cache);
|
||||
|
||||
height_rows
|
||||
let invocation_spans = vec![
|
||||
Span::styled(invocation.server.clone(), Style::default().fg(Color::Blue)),
|
||||
Span::raw("."),
|
||||
Span::styled(invocation.tool.clone(), Style::default().fg(Color::Blue)),
|
||||
Span::raw("("),
|
||||
Span::styled(args_str, Style::default().fg(Color::Gray)),
|
||||
Span::raw(")"),
|
||||
];
|
||||
Line::from(invocation_spans)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user