mirror of
https://github.com/openai/codex.git
synced 2026-09-15 12:08:01 +00: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.
148 lines
3.9 KiB
Rust
148 lines
3.9 KiB
Rust
use ratatui::prelude::*;
|
|
use ratatui::style::Stylize;
|
|
use std::collections::BTreeSet;
|
|
use unicode_width::UnicodeWidthChar;
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct FieldFormatter {
|
|
indent: &'static str,
|
|
label_width: usize,
|
|
value_offset: usize,
|
|
value_indent: String,
|
|
}
|
|
|
|
impl FieldFormatter {
|
|
pub(crate) const INDENT: &'static str = " ";
|
|
|
|
pub(crate) fn from_labels<S>(labels: impl IntoIterator<Item = S>) -> Self
|
|
where
|
|
S: AsRef<str>,
|
|
{
|
|
let label_width = labels
|
|
.into_iter()
|
|
.map(|label| UnicodeWidthStr::width(label.as_ref()))
|
|
.max()
|
|
.unwrap_or(0);
|
|
let indent_width = UnicodeWidthStr::width(Self::INDENT);
|
|
let value_offset = indent_width + label_width + 1 + 3;
|
|
|
|
Self {
|
|
indent: Self::INDENT,
|
|
label_width,
|
|
value_offset,
|
|
value_indent: " ".repeat(value_offset),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn line(
|
|
&self,
|
|
label: &'static str,
|
|
value_spans: Vec<Span<'static>>,
|
|
) -> Line<'static> {
|
|
Line::from(self.full_spans(label, value_spans))
|
|
}
|
|
|
|
pub(crate) fn continuation(&self, mut spans: Vec<Span<'static>>) -> Line<'static> {
|
|
let mut all_spans = Vec::with_capacity(spans.len() + 1);
|
|
all_spans.push(Span::from(self.value_indent.clone()).dim());
|
|
all_spans.append(&mut spans);
|
|
Line::from(all_spans)
|
|
}
|
|
|
|
pub(crate) fn value_width(&self, available_inner_width: usize) -> usize {
|
|
available_inner_width.saturating_sub(self.value_offset)
|
|
}
|
|
|
|
pub(crate) fn full_spans(
|
|
&self,
|
|
label: &str,
|
|
mut value_spans: Vec<Span<'static>>,
|
|
) -> Vec<Span<'static>> {
|
|
let mut spans = Vec::with_capacity(value_spans.len() + 1);
|
|
spans.push(self.label_span(label));
|
|
spans.append(&mut value_spans);
|
|
spans
|
|
}
|
|
|
|
fn label_span(&self, label: &str) -> Span<'static> {
|
|
let mut buf = String::with_capacity(self.value_offset);
|
|
buf.push_str(self.indent);
|
|
|
|
buf.push_str(label);
|
|
buf.push(':');
|
|
|
|
let label_width = UnicodeWidthStr::width(label);
|
|
let padding = 3 + self.label_width.saturating_sub(label_width);
|
|
for _ in 0..padding {
|
|
buf.push(' ');
|
|
}
|
|
|
|
Span::from(buf).dim()
|
|
}
|
|
}
|
|
|
|
pub(crate) fn push_label(labels: &mut Vec<String>, seen: &mut BTreeSet<String>, label: &str) {
|
|
if seen.contains(label) {
|
|
return;
|
|
}
|
|
|
|
let owned = label.to_string();
|
|
seen.insert(owned.clone());
|
|
labels.push(owned);
|
|
}
|
|
|
|
pub(crate) fn line_display_width(line: &Line<'static>) -> usize {
|
|
line.iter()
|
|
.map(|span| UnicodeWidthStr::width(span.content.as_ref()))
|
|
.sum()
|
|
}
|
|
|
|
pub(crate) fn truncate_line_to_width(line: Line<'static>, max_width: usize) -> Line<'static> {
|
|
if max_width == 0 {
|
|
return Line::from(Vec::<Span<'static>>::new());
|
|
}
|
|
|
|
let mut used = 0usize;
|
|
let mut spans_out: Vec<Span<'static>> = Vec::new();
|
|
|
|
for span in line.spans {
|
|
let text = span.content.into_owned();
|
|
let style = span.style;
|
|
let span_width = UnicodeWidthStr::width(text.as_str());
|
|
|
|
if span_width == 0 {
|
|
spans_out.push(Span::styled(text, style));
|
|
continue;
|
|
}
|
|
|
|
if used >= max_width {
|
|
break;
|
|
}
|
|
|
|
if used + span_width <= max_width {
|
|
used += span_width;
|
|
spans_out.push(Span::styled(text, style));
|
|
continue;
|
|
}
|
|
|
|
let mut truncated = String::new();
|
|
for ch in text.chars() {
|
|
let ch_width = UnicodeWidthChar::width(ch).unwrap_or(0);
|
|
if used + ch_width > max_width {
|
|
break;
|
|
}
|
|
truncated.push(ch);
|
|
used += ch_width;
|
|
}
|
|
|
|
if !truncated.is_empty() {
|
|
spans_out.push(Span::styled(truncated, style));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
Line::from(spans_out)
|
|
}
|