mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
tui: gate slash help by runtime availability
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
co-authored by
Codex
parent
1a54e52fd9
commit
a6ffd1c887
@@ -14,12 +14,13 @@ use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::Widget;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::bottom_pane::BuiltinCommandFlags;
|
||||
use crate::bottom_pane::CancellationEvent;
|
||||
use crate::bottom_pane::bottom_pane_view::BottomPaneView;
|
||||
use crate::bottom_pane::popup_consts::MAX_POPUP_ROWS;
|
||||
use crate::bottom_pane::selection_popup_common::render_menu_surface;
|
||||
use crate::bottom_pane::visible_builtins_for_input;
|
||||
use crate::key_hint;
|
||||
use crate::slash_command::visible_built_in_slash_commands;
|
||||
use crate::wrapping::RtOptions;
|
||||
use crate::wrapping::word_wrap_lines;
|
||||
|
||||
@@ -49,15 +50,17 @@ struct HelpSearch {
|
||||
|
||||
pub(crate) struct SlashHelpView {
|
||||
complete: bool,
|
||||
rows: Vec<HelpRow>,
|
||||
scroll_top: Cell<usize>,
|
||||
follow_selected_match: Cell<bool>,
|
||||
search: HelpSearch,
|
||||
}
|
||||
|
||||
impl SlashHelpView {
|
||||
pub(crate) fn new() -> Self {
|
||||
pub(crate) fn new(flags: BuiltinCommandFlags) -> Self {
|
||||
Self {
|
||||
complete: false,
|
||||
rows: Self::build_document(flags),
|
||||
scroll_top: Cell::new(0),
|
||||
follow_selected_match: Cell::new(false),
|
||||
search: HelpSearch::default(),
|
||||
@@ -71,7 +74,7 @@ impl SlashHelpView {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn build_document() -> Vec<HelpRow> {
|
||||
fn build_document(flags: BuiltinCommandFlags) -> Vec<HelpRow> {
|
||||
let mut rows = vec![
|
||||
HelpRow {
|
||||
plain_text: "Slash Commands".to_string(),
|
||||
@@ -103,7 +106,7 @@ impl SlashHelpView {
|
||||
},
|
||||
];
|
||||
|
||||
for cmd in visible_built_in_slash_commands() {
|
||||
for cmd in visible_builtins_for_input(flags) {
|
||||
rows.push(HelpRow {
|
||||
plain_text: format!("/{}", cmd.command()),
|
||||
line: Line::from(format!("/{}", cmd.command()).cyan().bold()),
|
||||
@@ -273,8 +276,7 @@ impl SlashHelpView {
|
||||
return;
|
||||
}
|
||||
|
||||
let matches =
|
||||
Self::matching_logical_rows(&Self::build_document(), &self.search.active_query);
|
||||
let matches = Self::matching_logical_rows(&self.rows, &self.search.active_query);
|
||||
if matches.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -425,8 +427,7 @@ impl crate::render::renderable::Renderable for SlashHelpView {
|
||||
let [_footer_gap_area, footer_line_area] =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(footer_area);
|
||||
|
||||
let rows = Self::build_document();
|
||||
let (lines, row_starts, row_ends) = Self::wrap_rows(&rows, body_area.width);
|
||||
let (lines, row_starts, row_ends) = Self::wrap_rows(&self.rows, body_area.width);
|
||||
let header_lines = lines.iter().take(2).cloned().collect::<Vec<_>>();
|
||||
let mut body_lines = lines.iter().skip(2).cloned().collect::<Vec<_>>();
|
||||
let visible_rows = Self::visible_body_rows(body_area.height);
|
||||
@@ -436,7 +437,7 @@ impl crate::render::renderable::Renderable for SlashHelpView {
|
||||
if self.search.input.is_none()
|
||||
&& !self.search.active_query.is_empty()
|
||||
&& let Some(selected_row_idx) =
|
||||
Self::matching_logical_rows(&rows, &self.search.active_query)
|
||||
Self::matching_logical_rows(&self.rows, &self.search.active_query)
|
||||
.get(self.search.selected_match)
|
||||
.copied()
|
||||
{
|
||||
@@ -464,7 +465,8 @@ impl crate::render::renderable::Renderable for SlashHelpView {
|
||||
|
||||
let footer_line = self.footer_line();
|
||||
Paragraph::new(footer_line.clone()).render(footer_line_area, buf);
|
||||
let indicator = self.search_indicator(&rows, body_lines.len(), visible_rows, scroll_top);
|
||||
let indicator =
|
||||
self.search_indicator(&self.rows, body_lines.len(), visible_rows, scroll_top);
|
||||
let indicator_width = UnicodeWidthStr::width(indicator.as_str()) as u16;
|
||||
let footer_width = footer_line.width() as u16;
|
||||
if footer_width + indicator_width + 2 <= footer_line_area.width {
|
||||
@@ -481,8 +483,7 @@ impl crate::render::renderable::Renderable for SlashHelpView {
|
||||
}
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
let rows = Self::build_document();
|
||||
let (wrapped_rows, _, _) = Self::wrap_rows(&rows, width.saturating_sub(4));
|
||||
let (wrapped_rows, _, _) = Self::wrap_rows(&self.rows, width.saturating_sub(4));
|
||||
let content_rows = wrapped_rows.len() as u16;
|
||||
content_rows.max(HELP_VIEW_MIN_BODY_ROWS + 4)
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ pub(crate) use list_selection_view::SelectionItem;
|
||||
pub(crate) use prompt_args::parse_slash_name;
|
||||
pub(crate) use slash_commands::BuiltinCommandFlags;
|
||||
pub(crate) use slash_commands::find_builtin_command;
|
||||
pub(crate) use slash_commands::visible_builtins_for_input;
|
||||
|
||||
/// Pane displayed in the lower half of the chat UI.
|
||||
///
|
||||
|
||||
@@ -4358,7 +4358,9 @@ impl ChatWidget {
|
||||
match cmd {
|
||||
SlashCommand::Help => {
|
||||
self.bottom_pane
|
||||
.show_view(Box::new(crate::bottom_pane::SlashHelpView::new()));
|
||||
.show_view(Box::new(crate::bottom_pane::SlashHelpView::new(
|
||||
self.builtin_command_flags(),
|
||||
)));
|
||||
QueueReplayControl::Continue
|
||||
}
|
||||
SlashCommand::Feedback => {
|
||||
@@ -4793,7 +4795,9 @@ impl ChatWidget {
|
||||
match cmd {
|
||||
SlashCommand::Help => {
|
||||
self.bottom_pane
|
||||
.show_view(Box::new(crate::bottom_pane::SlashHelpView::new()));
|
||||
.show_view(Box::new(crate::bottom_pane::SlashHelpView::new(
|
||||
self.builtin_command_flags(),
|
||||
)));
|
||||
QueueReplayControl::Continue
|
||||
}
|
||||
SlashCommand::Approvals | SlashCommand::Permissions => {
|
||||
@@ -5829,34 +5833,35 @@ impl ChatWidget {
|
||||
text: &'a str,
|
||||
) -> Option<(SlashCommand, &'a str, usize)> {
|
||||
let (name, rest, rest_offset) = parse_slash_name(text)?;
|
||||
let cmd = find_builtin_command(
|
||||
name,
|
||||
BuiltinCommandFlags {
|
||||
collaboration_modes_enabled: self.collaboration_modes_enabled(),
|
||||
connectors_enabled: self.connectors_enabled(),
|
||||
fast_command_enabled: self.fast_mode_enabled(),
|
||||
personality_command_enabled: self.config.features.enabled(Feature::Personality),
|
||||
realtime_conversation_enabled: self.realtime_conversation_enabled(),
|
||||
audio_device_selection_enabled: self.realtime_audio_device_selection_enabled(),
|
||||
allow_elevate_sandbox: {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
&& matches!(
|
||||
WindowsSandboxLevel::from_config(&self.config),
|
||||
WindowsSandboxLevel::RestrictedToken
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
},
|
||||
},
|
||||
)?;
|
||||
let cmd = find_builtin_command(name, self.builtin_command_flags())?;
|
||||
Some((cmd, rest, rest_offset))
|
||||
}
|
||||
|
||||
fn builtin_command_flags(&self) -> BuiltinCommandFlags {
|
||||
BuiltinCommandFlags {
|
||||
collaboration_modes_enabled: self.collaboration_modes_enabled(),
|
||||
connectors_enabled: self.connectors_enabled(),
|
||||
fast_command_enabled: self.fast_mode_enabled(),
|
||||
personality_command_enabled: self.config.features.enabled(Feature::Personality),
|
||||
realtime_conversation_enabled: self.realtime_conversation_enabled(),
|
||||
audio_device_selection_enabled: self.realtime_audio_device_selection_enabled(),
|
||||
allow_elevate_sandbox: {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
&& matches!(
|
||||
WindowsSandboxLevel::from_config(&self.config),
|
||||
WindowsSandboxLevel::RestrictedToken
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_known_slash_draft(&self, draft: &UserMessage) -> bool {
|
||||
let Some((name, _, _)) = parse_slash_name(&draft.text) else {
|
||||
return false;
|
||||
|
||||
+1
-22
@@ -41,11 +41,6 @@ expression: popup
|
||||
[--confirm-world-writable] [--remember-world-writable] [--enable-windows-
|
||||
sandbox=elevated|legacy]
|
||||
|
||||
/setup-default-sandbox
|
||||
set up elevated agent sandbox
|
||||
Usage:
|
||||
/setup-default-sandbox
|
||||
|
||||
/experimental
|
||||
toggle experimental features
|
||||
Usage:
|
||||
@@ -161,11 +156,6 @@ expression: popup
|
||||
Usage:
|
||||
/mcp
|
||||
|
||||
/apps
|
||||
manage apps
|
||||
Usage:
|
||||
/apps
|
||||
|
||||
/logout
|
||||
log out of Codex
|
||||
Usage:
|
||||
@@ -213,17 +203,6 @@ expression: popup
|
||||
/personality
|
||||
/personality <none|friendly|pragmatic>
|
||||
|
||||
/realtime
|
||||
toggle realtime voice mode (experimental)
|
||||
Usage:
|
||||
/realtime
|
||||
|
||||
/settings
|
||||
configure realtime microphone/speaker
|
||||
Usage:
|
||||
/settings
|
||||
/settings <microphone|speaker> [default|<device-name>]
|
||||
|
||||
/test-approval
|
||||
test approval request
|
||||
Usage:
|
||||
@@ -241,4 +220,4 @@ expression: popup
|
||||
/debug-m-drop
|
||||
|
||||
|
||||
↑/↓ scroll | [ctrl + p / ctrl + n] page | / search | esc close 1-233/240
|
||||
↑/↓ scroll | [ctrl + p / ctrl + n] page | / search | esc close 1-212/219
|
||||
|
||||
+1
-22
@@ -41,11 +41,6 @@ expression: searching
|
||||
[--confirm-world-writable] [--remember-world-writable] [--enable-windows-
|
||||
sandbox=elevated|legacy]
|
||||
|
||||
/setup-default-sandbox
|
||||
set up elevated agent sandbox
|
||||
Usage:
|
||||
/setup-default-sandbox
|
||||
|
||||
/experimental
|
||||
toggle experimental features
|
||||
Usage:
|
||||
@@ -161,11 +156,6 @@ expression: searching
|
||||
Usage:
|
||||
/mcp
|
||||
|
||||
/apps
|
||||
manage apps
|
||||
Usage:
|
||||
/apps
|
||||
|
||||
/logout
|
||||
log out of Codex
|
||||
Usage:
|
||||
@@ -213,17 +203,6 @@ expression: searching
|
||||
/personality
|
||||
/personality <none|friendly|pragmatic>
|
||||
|
||||
/realtime
|
||||
toggle realtime voice mode (experimental)
|
||||
Usage:
|
||||
/realtime
|
||||
|
||||
/settings
|
||||
configure realtime microphone/speaker
|
||||
Usage:
|
||||
/settings
|
||||
/settings <microphone|speaker> [default|<device-name>]
|
||||
|
||||
/test-approval
|
||||
test approval request
|
||||
Usage:
|
||||
@@ -241,4 +220,4 @@ expression: searching
|
||||
/debug-m-drop
|
||||
|
||||
|
||||
Search: /maintainers | enter apply | esc cancel 1 match | 1-233/240
|
||||
Search: /maintainers | enter apply | esc cancel 1 match | 1-212/219
|
||||
|
||||
@@ -6132,30 +6132,30 @@ async fn slash_help_search_navigates_matches_with_n_and_p() {
|
||||
let _ = render_bottom_popup(&chat, 100);
|
||||
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Char('/')));
|
||||
for ch in "toggle".chars() {
|
||||
for ch in "show".chars() {
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Char(ch)));
|
||||
}
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let first = render_bottom_popup(&chat, 100);
|
||||
assert!(first.contains("n/p match"));
|
||||
assert!(first.contains("/fast"));
|
||||
assert!(first.contains("/help"));
|
||||
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Char('n')));
|
||||
let second = render_bottom_popup(&chat, 100);
|
||||
assert!(second.contains("/experimental"));
|
||||
assert!(second.contains("/diff"));
|
||||
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Char('n')));
|
||||
let third = render_bottom_popup(&chat, 100);
|
||||
assert!(third.contains("/realtime"));
|
||||
assert!(third.contains("/status"));
|
||||
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Char('p')));
|
||||
let previous = render_bottom_popup(&chat, 100);
|
||||
assert!(previous.contains("/experimental"));
|
||||
assert!(previous.contains("/diff"));
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Char('N'), KeyModifiers::SHIFT));
|
||||
let shifted_previous = render_bottom_popup(&chat, 100);
|
||||
assert!(shifted_previous.contains("/fast"));
|
||||
assert!(shifted_previous.contains("/help"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user