Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
66 changes: 43 additions & 23 deletions codex-rs/tui/assets/tooltips.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
Use /compact when the conversation gets long to summarize history and free up context.
Start a fresh idea with /new; the previous session stays in history.
Use /feedback to send logs to the maintainers when something looks off.
Switch models or reasoning effort quickly with /model.
Use /permissions to control when Codex asks for confirmation.
Run /review to get a code review of your current changes.
Use /skills to list available skills or ask Codex to use one.
Use /status to see the current model, approvals, and token usage.
Use /statusline to configure which items appear in the status line.
Use /fork to branch the current chat into a new thread.
Use /side to start a side conversation in a temporary fork without polluting the main thread.
Use /init to create an AGENTS.md with project-specific guidance.
Use /mcp to list configured MCP tools.
Use /rename to rename your threads for easier thread resuming.
# Tips support Markdown: **emphasis**, `code`, and [label](https://example.com) links.
# Tips may use {key:context.action} to insert the configured primary key or chord.
# A tip is skipped if any referenced action is unbound or unavailable.
Start a fresh idea with **/new**; the previous session stays in history.
Use **/feedback** to send logs to the maintainers when something looks off.
Switch models or reasoning effort quickly with **/model**.
Use **/permissions** to control when Codex asks for confirmation.
Run **/review** to get a code review of your current changes.
Use **/skills** to list available skills or ask Codex to use one.
Use **/status** to see the current model, approvals, and token usage.
Use **/statusline** to configure which items appear in the status line.
Use **/fork** to branch the current chat into a new thread.
Use **/side** to start a side conversation in a temporary fork without polluting the main thread.
Use **/init** to create an `AGENTS.md` with project-specific guidance.
Use **/mcp** to list configured MCP tools.
Use **/rename** to rename your threads for easier thread resuming.
Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`.
Join the OpenAI community Discord: http://discord.gg/openai
Visit the Codex community forum: https://community.openai.com/c/codex/37
Visit the [Codex community forum](https://community.openai.com/c/codex/37).
You can run any shell command from Codex using `!` (e.g. `!ls`)
Type / to open the command popup; Tab autocompletes slash commands.
When the composer is empty, press Esc to step back and edit your last message; Enter confirms.
Press Tab to queue a message when a task is running; otherwise it sends immediately (except `!`).
[tui.keymap] in ~/.codex/config.toml lets you rebind supported shortcuts.
See the Codex keymap documentation for supported actions and examples.
Paste an image with Ctrl+V to attach it to your next message.
Type `/` to open the command popup; `Tab` autocompletes slash commands.
When the composer is empty, press `Esc` to step back and edit your last message; `Enter` confirms.
Press {key:composer.queue} to queue a message when a task is running; otherwise it sends immediately (except `!`).
Use **/keymap** to configure keyboard shortcuts.
Paste an image with `Ctrl+V` to attach it to your next message.
You can resume a previous conversation by running `codex resume`
Use /copy or press Ctrl+O to copy the latest agent response as Markdown.
Use **/copy** or press {key:global.copy} to copy the latest agent response as Markdown.
Use **/vim** to toggle Vim editing in the composer.
Moving from Claude Code? Use **/import** to bring over your setup, project, and recent chats.
Use **/hooks** to view and manage lifecycle hooks.
Use **/archive** to archive the current session.
When worktree support is available, use **/worktree** to start or continue a conversation in a separate Git worktree.
Coming back to a conversation? Use **/recap** to summarize it.
When voice is available, use **/voice** to start or stop a voice conversation, or **/voice settings** to choose a voice.
When goals are enabled, use **/goal** to set or inspect the objective for a long-running task.
Use **/agents** to open the agent command center.
Use **/subagents** to switch between this session’s subagents.
Use **/export** to save your conversation as Markdown.
When signed in with ChatGPT, use **/usage** to check your account usage and access available usage limit resets.
Use **/title** to choose what appears in your terminal’s title.
Use **/theme** to choose a syntax highlighting theme.
When plugins are enabled, use **/plugins** to browse plugins.
Press {key:global.open_transcript} to open the full transcript.
Press {key:global.find_transcript} to search this conversation.
Press {key:global.open_external_editor} to edit your current draft in an external editor.
Press {key:composer.history_search_previous} to search previously entered prompts.
For models with adjustable reasoning, press {key:chat.increase_reasoning_effort} to increase reasoning effort or {key:chat.decrease_reasoning_effort} to decrease it.
1 change: 1 addition & 0 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ pub(crate) struct App {
pub(crate) file_search: FileSearchManager,

pub(crate) transcript_cells: Vec<Arc<dyn HistoryCell>>,
composer_tips: composer_hints::ComposerTips,
native_history: native_history::NativeHistory,
pub(crate) transcript_view: crate::transcript_view::TranscriptView,
last_rendered_history_tail: Option<history_ui::RenderedHistoryTail>,
Expand Down
120 changes: 82 additions & 38 deletions codex-rs/tui/src/app/composer_hints.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,75 @@
//! Usage warnings and contextual discovery share the existing composer-gap row.
//! Interaction controls take priority; only discovery tips require idle input and show_tooltips.
//! Usage warnings take priority over startup announcements and stable catalog tips in fullscreen.
//! Only complete tips that fit one row are shown; tips require idle input and show_tooltips.

use super::*;
use crate::history_cell::SessionInfoCell;
use crate::history_cell::UserHistoryCell;
use crate::keymap::KeymapContext;
use crate::style::secondary_text_style;
use ratatui::style::Styled;
use ratatui::text::Line;
use crate::terminal_hyperlinks::HyperlinkLine;
use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;

/// Keep announcement eligibility and random tip selection stable across redraws.
pub(super) struct ComposerTips {
seed: u64,
boot_turn_count: Option<usize>,
announcement_dismissed: bool,
}

impl Default for ComposerTips {
fn default() -> Self {
Self::new(rand::random())
}
}

impl ComposerTips {
pub(super) fn new(seed: u64) -> Self {
Self {
seed,
boot_turn_count: None,
announcement_dismissed: false,
}
}

fn select(
&mut self,
turn_count: usize,
announcement: impl FnOnce() -> Option<String>,
keymap: &RuntimeKeymap,
width: usize,
cwd: &Path,
) -> Option<HyperlinkLine> {
// Remember leaving startup even if /clear or backtracking later removes user cells.
self.announcement_dismissed |=
*self.boot_turn_count.get_or_insert(turn_count) != turn_count;
let render = |tip: String| {
let lines = crate::tooltips::render_tooltip_lines(&tip, width, cwd);
match lines.as_slice() {
[line] if line.width() <= width => Some(line.clone().style(secondary_text_style())),
_ => None,
}
};
if !self.announcement_dismissed
&& let Some(line) = announcement().and_then(&render)
{
return Some(line);
}

let mut rng = StdRng::seed_from_u64(self.seed.wrapping_add(turn_count as u64));
// Randomize before fitting so short tips are not favored by catalog order.
let mut tips = crate::tooltips::resolved_tooltips(Some(keymap)).collect::<Vec<_>>();
tips.shuffle(&mut rng);
tips.into_iter().find_map(render)
}
}

impl App {
pub(super) fn composer_hint(&self, width: u16) -> Option<Line<'static>> {
pub(super) fn composer_hint(&mut self, width: u16) -> Option<HyperlinkLine> {
let running = self.chat_widget.is_user_turn_pending_or_running();
if running {
self.composer_tips.announcement_dismissed = true;
}
if !self.chat_widget.no_modal_or_popup_active()
|| !self.transcript_view.is_following()
|| self.transcript_view.has_active_interaction()
Expand All @@ -19,12 +78,16 @@ impl App {
{
return None;
}
let width = width.checked_sub(/*rhs*/ 2)?;
if width == 0 {
return None;
}
if let Some(notice) = self.chat_widget.usage_notice(width) {
return Some(notice);
return Some(HyperlinkLine::new(notice));
}
if !self.local_settings.tui.show_tooltips
|| !self.chat_widget.composer_is_empty()
|| self.chat_widget.is_user_turn_pending_or_running()
|| running
{
return None;
}
Expand All @@ -36,36 +99,17 @@ impl App {
.take_while(|cell| !cell.as_any().is::<SessionInfoCell>())
.filter(|cell| cell.as_any().is::<UserHistoryCell>())
.count();
let (prefix, keys, label) = if count == 0 {
(
"Tip: ",
crate::key_hint::key_label_spans("@"),
" mentions files, skills, and plugins",
)
} else {
match count % 3 {
0 => (
"Tip: drag to select text · ",
crate::key_hint::key_label_spans("ctrl+c"),
" copies",
),
1 => {
let key = self
.keymap
.primary_hint(KeymapContext::Global, "find_transcript")?;
("Tip: ", key.spans(), " searches this conversation")
}
_ => {
return Some(Line::from(
"Tip: /copy copies the last response".set_style(secondary_text_style()),
));
}
}
};
let mut tip = Line::from(prefix.set_style(secondary_text_style()));
tip.spans.extend(keys);
tip.spans.push(label.set_style(secondary_text_style()));
Some(tip)
self.composer_tips.select(
count,
|| {
crate::tooltips::announcement::fetch_announcement_tip(
self.chat_widget.current_plan_type(),
)
},
&self.keymap,
usize::from(width),
self.config.cwd.as_path(),
)
}
}

Expand Down
Loading
Loading