mirror of
https://github.com/openai/codex.git
synced 2026-05-05 05:42:33 +03:00
This PR makes the `/statusline` and `/title` setup UIs share one preview-value source instead of each surface using its own examples. Both pickers now render consistent live values when available, and stable placeholders when they are not. It also resolves live preview values at the shared preview-item layer, so `/title` preview can use real runtime values for title-specific cases like status text, task progress, and project-name fallback behavior. - Adds a shared preview data model for status surfaces - Maps status-line items and terminal-title items onto that shared preview list - Feeds both setup views from the same chatwidget-derived preview data, with terminal-title-specific formatting applied before `/title` preview renders - Keeps project-root preview aligned with status-line behavior while project in /title keeps its title fallback/truncation behavior - Adds snapshot coverage for live-only, hardcoded-only, and mixed cases Test Steps - Open Codex TUI and launch `/statusline`. - Toggle and reorder items, then verify the preview uses current session values when possible, and placeholder values for missing values (ex: no thread ID). - Open `/title` and verify it shows the same normalized values, including live status/task-progress values when available.
174 lines
5.0 KiB
Rust
174 lines
5.0 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use ratatui::text::Line;
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub(crate) enum StatusSurfacePreviewItem {
|
|
AppName,
|
|
ProjectName,
|
|
ProjectRoot,
|
|
CurrentDir,
|
|
Status,
|
|
ThreadTitle,
|
|
GitBranch,
|
|
ContextRemaining,
|
|
ContextUsed,
|
|
FiveHourLimit,
|
|
WeeklyLimit,
|
|
CodexVersion,
|
|
ContextWindowSize,
|
|
UsedTokens,
|
|
TotalInputTokens,
|
|
TotalOutputTokens,
|
|
SessionId,
|
|
FastMode,
|
|
Model,
|
|
ModelWithReasoning,
|
|
TaskProgress,
|
|
}
|
|
|
|
impl StatusSurfacePreviewItem {
|
|
fn placeholder(self) -> &'static str {
|
|
match self {
|
|
StatusSurfacePreviewItem::AppName => "codex",
|
|
StatusSurfacePreviewItem::ProjectName => "my-project",
|
|
StatusSurfacePreviewItem::ProjectRoot => "my-project",
|
|
StatusSurfacePreviewItem::CurrentDir => "~/my-project/subdir",
|
|
StatusSurfacePreviewItem::Status => "Working",
|
|
StatusSurfacePreviewItem::ThreadTitle => "thread title",
|
|
StatusSurfacePreviewItem::GitBranch => "feat/awesome-feature",
|
|
StatusSurfacePreviewItem::ContextRemaining => "Context 0% left",
|
|
StatusSurfacePreviewItem::ContextUsed => "Context 0% used",
|
|
StatusSurfacePreviewItem::FiveHourLimit => "5h 0%",
|
|
StatusSurfacePreviewItem::WeeklyLimit => "weekly 0%",
|
|
StatusSurfacePreviewItem::CodexVersion => "0.0.0",
|
|
StatusSurfacePreviewItem::ContextWindowSize => "0 window",
|
|
StatusSurfacePreviewItem::UsedTokens => "0 used",
|
|
StatusSurfacePreviewItem::TotalInputTokens => "0 in",
|
|
StatusSurfacePreviewItem::TotalOutputTokens => "0 out",
|
|
StatusSurfacePreviewItem::SessionId => "550e8400-e29b-41d4",
|
|
StatusSurfacePreviewItem::FastMode => "Fast on",
|
|
StatusSurfacePreviewItem::Model => "gpt-5.2-codex",
|
|
StatusSurfacePreviewItem::ModelWithReasoning => "gpt-5.2-codex medium",
|
|
StatusSurfacePreviewItem::TaskProgress => "Tasks 0/0",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn iter() -> impl Iterator<Item = Self> {
|
|
[
|
|
Self::AppName,
|
|
Self::ProjectName,
|
|
Self::ProjectRoot,
|
|
Self::CurrentDir,
|
|
Self::Status,
|
|
Self::ThreadTitle,
|
|
Self::GitBranch,
|
|
Self::ContextRemaining,
|
|
Self::ContextUsed,
|
|
Self::FiveHourLimit,
|
|
Self::WeeklyLimit,
|
|
Self::CodexVersion,
|
|
Self::ContextWindowSize,
|
|
Self::UsedTokens,
|
|
Self::TotalInputTokens,
|
|
Self::TotalOutputTokens,
|
|
Self::SessionId,
|
|
Self::FastMode,
|
|
Self::Model,
|
|
Self::ModelWithReasoning,
|
|
Self::TaskProgress,
|
|
]
|
|
.into_iter()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
struct PreviewValue {
|
|
text: String,
|
|
is_placeholder: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub(crate) struct StatusSurfacePreviewData {
|
|
values: BTreeMap<StatusSurfacePreviewItem, PreviewValue>,
|
|
}
|
|
|
|
impl Default for StatusSurfacePreviewData {
|
|
fn default() -> Self {
|
|
let mut data = Self {
|
|
values: BTreeMap::new(),
|
|
};
|
|
for item in StatusSurfacePreviewItem::iter() {
|
|
data.set_placeholder(item, item.placeholder());
|
|
}
|
|
data
|
|
}
|
|
}
|
|
|
|
impl StatusSurfacePreviewData {
|
|
pub(crate) fn from_iter<I, V>(values: I) -> Self
|
|
where
|
|
I: IntoIterator<Item = (StatusSurfacePreviewItem, V)>,
|
|
V: Into<String>,
|
|
{
|
|
let mut data = Self::default();
|
|
for (item, value) in values {
|
|
data.set_live(item, value);
|
|
}
|
|
data
|
|
}
|
|
|
|
pub(crate) fn set_live<V>(&mut self, item: StatusSurfacePreviewItem, value: V)
|
|
where
|
|
V: Into<String>,
|
|
{
|
|
self.values.insert(
|
|
item,
|
|
PreviewValue {
|
|
text: value.into(),
|
|
is_placeholder: false,
|
|
},
|
|
);
|
|
}
|
|
|
|
pub(crate) fn set_placeholder<V>(&mut self, item: StatusSurfacePreviewItem, value: V)
|
|
where
|
|
V: Into<String>,
|
|
{
|
|
if self
|
|
.values
|
|
.get(&item)
|
|
.is_some_and(|value| !value.is_placeholder)
|
|
{
|
|
return;
|
|
}
|
|
self.values.insert(
|
|
item,
|
|
PreviewValue {
|
|
text: value.into(),
|
|
is_placeholder: true,
|
|
},
|
|
);
|
|
}
|
|
|
|
pub(crate) fn value_for(&self, item: StatusSurfacePreviewItem) -> Option<&str> {
|
|
self.values.get(&item).map(|value| value.text.as_str())
|
|
}
|
|
|
|
pub(crate) fn line_for_items<I>(&self, items: I) -> Option<Line<'static>>
|
|
where
|
|
I: IntoIterator<Item = StatusSurfacePreviewItem>,
|
|
{
|
|
let preview = items
|
|
.into_iter()
|
|
.filter_map(|item| self.value_for(item))
|
|
.collect::<Vec<_>>()
|
|
.join(" · ");
|
|
if preview.is_empty() {
|
|
None
|
|
} else {
|
|
Some(Line::from(preview))
|
|
}
|
|
}
|
|
}
|