Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ pub struct Config {
/// Enable ASCII animations and shimmer effects in the TUI.
pub animations: bool,

/// Show startup tooltips in the TUI welcome screen.
pub show_tooltips: bool,

/// The directory that should be treated as the current working directory
/// for the session. All relative paths inside the business-logic layer are
/// resolved against this path.
Expand Down Expand Up @@ -1258,6 +1261,8 @@ impl Config {
.map(|t| t.notifications.clone())
.unwrap_or_default(),
animations: cfg.tui.as_ref().map(|t| t.animations).unwrap_or(true),
// TODO(jif) toggle default once design is done.
show_tooltips: cfg.tui.as_ref().map(|t| t.show_tooltips).unwrap_or(false),
otel: {
let t: OtelConfigToml = cfg.otel.unwrap_or_default();
let log_user_prompt = t.log_user_prompt.unwrap_or(false);
Expand Down Expand Up @@ -1436,6 +1441,7 @@ persistence = "none"
let tui = parsed.tui.expect("config should include tui section");

assert_eq!(tui.notifications, Notifications::Enabled(true));
assert!(!tui.show_tooltips);
}

#[test]
Expand Down Expand Up @@ -3009,6 +3015,7 @@ model_verbosity = "high"
disable_paste_burst: false,
tui_notifications: Default::default(),
animations: true,
show_tooltips: false,
otel: OtelConfig::default(),
},
o3_profile_config
Expand Down Expand Up @@ -3082,6 +3089,7 @@ model_verbosity = "high"
disable_paste_burst: false,
tui_notifications: Default::default(),
animations: true,
show_tooltips: false,
otel: OtelConfig::default(),
};

Expand Down Expand Up @@ -3170,6 +3178,7 @@ model_verbosity = "high"
disable_paste_burst: false,
tui_notifications: Default::default(),
animations: true,
show_tooltips: false,
otel: OtelConfig::default(),
};

Expand Down Expand Up @@ -3244,6 +3253,7 @@ model_verbosity = "high"
disable_paste_burst: false,
tui_notifications: Default::default(),
animations: true,
show_tooltips: false,
otel: OtelConfig::default(),
};

Expand Down
6 changes: 6 additions & 0 deletions codex-rs/core/src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ pub struct Tui {
/// Defaults to `true`.
#[serde(default = "default_true")]
pub animations: bool,

// TODO(jif) toggle default once design is done.
/// Show startup tooltips in the TUI welcome screen.
/// Defaults to `false`.
#[serde(default)]
pub show_tooltips: bool,
}

const fn default_true() -> bool {
Expand Down
58 changes: 53 additions & 5 deletions codex-rs/tui/src/history_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::render::renderable::Renderable;
use crate::style::user_message_style;
use crate::text_formatting::format_and_truncate_tool_result;
use crate::text_formatting::truncate_text;
use crate::tooltips;
use crate::ui_consts::LIVE_PREFIX_COLS;
use crate::update_action::UpdateAction;
use crate::version::CODEX_CLI_VERSION;
Expand Down Expand Up @@ -559,6 +560,44 @@ pub(crate) fn padded_emoji(emoji: &str) -> String {
format!("{emoji}\u{200A}")
}

#[derive(Debug)]
struct TooltipHistoryCell {
tip: &'static str,
}

impl TooltipHistoryCell {
fn new(tip: &'static str) -> Self {
Self { tip }
}
}

impl HistoryCell for TooltipHistoryCell {
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
let indent: Line<'static> = " ".into();
let mut lines = Vec::new();
lines.push(Line::from(""));
let tooltip_line: Line<'static> = vec!["Tip: ".cyan(), self.tip.into()].into();
let wrap_opts = RtOptions::new(usize::from(width.max(1)))
.initial_indent(indent.clone())
.subsequent_indent(indent.clone());
lines.extend(
word_wrap_line(&tooltip_line, wrap_opts.clone())
.into_iter()
.map(|line| line_to_static(&line)),
);
let disable_line: Line<'static> =
"Disable tips via tui.show_tooltips = false in config.toml"
.dim()
.into();
lines.extend(
word_wrap_line(&disable_line, wrap_opts)
.into_iter()
.map(|line| line_to_static(&line)),
);
lines
}
}

#[derive(Debug)]
pub struct SessionInfoCell(CompositeHistoryCell);

Expand Down Expand Up @@ -628,12 +667,21 @@ pub(crate) fn new_session_info(
]),
];

CompositeHistoryCell {
parts: vec![
Box::new(header),
Box::new(PlainHistoryCell { lines: help_lines }),
],
let tooltip = if config.show_tooltips {
tooltips::random_tooltip().map(TooltipHistoryCell::new)
} else {
None
};

let mut parts: Vec<Box<dyn HistoryCell>> = vec![
Box::new(header),
Box::new(PlainHistoryCell { lines: help_lines }),
];
if let Some(cell) = tooltip {
parts.push(Box::new(cell));
}

CompositeHistoryCell { parts }
} else if config.model == model {
CompositeHistoryCell { parts: vec![] }
} else {
Expand Down
1 change: 1 addition & 0 deletions codex-rs/tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ mod streaming;
mod style;
mod terminal_palette;
mod text_formatting;
mod tooltips;
mod tui;
mod ui_consts;
pub mod update_action;
Expand Down
49 changes: 49 additions & 0 deletions codex-rs/tui/src/tooltips.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use lazy_static::lazy_static;
use rand::Rng;

const RAW_TOOLTIPS: &str = include_str!("../tooltips.txt");

lazy_static! {
static ref TOOLTIPS: Vec<&'static str> = RAW_TOOLTIPS
.lines()
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.collect();
}

pub(crate) fn random_tooltip() -> Option<&'static str> {
let mut rng = rand::rng();
pick_tooltip(&mut rng)
}

fn pick_tooltip<R: Rng + ?Sized>(rng: &mut R) -> Option<&'static str> {
if TOOLTIPS.is_empty() {
None
} else {
TOOLTIPS.get(rng.random_range(0..TOOLTIPS.len())).copied()
}
}

#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand::rngs::StdRng;

#[test]
fn random_tooltip_returns_some_tip_when_available() {
let mut rng = StdRng::seed_from_u64(42);
assert!(pick_tooltip(&mut rng).is_some());
}

#[test]
fn random_tooltip_is_reproducible_with_seed() {
let expected = {
let mut rng = StdRng::seed_from_u64(7);
pick_tooltip(&mut rng)
};

let mut rng = StdRng::seed_from_u64(7);
assert_eq!(expected, pick_tooltip(&mut rng));
}
}
7 changes: 7 additions & 0 deletions codex-rs/tui/tooltips.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Use /compact when the conversation gets long to summarize history and free up context.
Use /mention path/to/file to pull a specific file into the conversation.
Start a fresh idea with /new; the previous session stays available in history.
If a turn went sideways, /undo asks Codex to revert the last changes.
Use /feedback to send logs to the maintainers when something looks off.
Switch models or reasoning effort quickly with /model.
You can run any shell commands from codex using `!` (e.g. `!ls`)
Loading