mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
## What changed - Allow dragging the pane divider and save the preferred width in `tui.sidebar_width_percent`, defaulting to one third of the terminal. Defer reflow until release when retained images are present. - Support scrolling and loading older history in the unfocused pane, opening links, copying selections, and clicking its composer to switch focus while preserving the clicked text position. - Keep assistant replies, running command completion, and turn status current in the unfocused pane without duplicating answers or completion metadata when focus returns. ## Testing Add regression coverage for divider previews and saved widths, image-aware resizing, composer clicks, unfocused history pagination and copying, and streaming replies and command completion across focus changes. GitOrigin-RevId: a26accc0f8df3c17f53bc915db29a11c8de3c480
192 lines
8.2 KiB
Rust
192 lines
8.2 KiB
Rust
//! Shared App fixtures for app submodule unit tests.
|
|
//!
|
|
//! This module keeps heavyweight `App` construction and config-inspection helpers available to
|
|
//! focused sibling test modules without making `app/tests.rs` the only practical place to test
|
|
//! app-owned behavior.
|
|
//! Return the large fixture on the heap so async scenarios do not embed it in their stack frames.
|
|
|
|
use super::owned_transcript::tests::buffer_text;
|
|
use super::*;
|
|
use crate::chatwidget::tests::make_chatwidget_manual_with_sender;
|
|
use crate::test_support::boxed_future;
|
|
use codex_models_manager::test_support::construct_model_info_offline_for_tests;
|
|
use codex_models_manager::test_support::get_model_offline_for_tests;
|
|
|
|
pub(super) fn enable_side_retention(app: &mut App) -> Result<()> {
|
|
app.config.features.enable(Feature::TuiWorkspace)?;
|
|
app.local_settings.transcript_mode = crate::transcript_mode::TranscriptMode::Owned;
|
|
app.keymap = crate::keymap::RuntimeKeymap::for_session(
|
|
&app.local_settings.tui.keymap,
|
|
app.local_settings.workspace_enabled(&app.config),
|
|
)
|
|
.map_err(color_eyre::eyre::Report::msg)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn side_frame(app: &mut App, tui: &mut tui::Tui, size: Size) -> Result<String> {
|
|
let size = tui.screen_size_for_event(&TuiEvent::Resize(size))?;
|
|
app.handle_draw_pre_render(tui, size)?;
|
|
app.render_chat_widget_frame(tui, size)?;
|
|
Ok(buffer_text(tui.terminal.previous_buffer()))
|
|
}
|
|
|
|
pub(crate) async fn make_test_app() -> Box<App> {
|
|
let (mut chat_widget, app_event_tx, _rx, _op_rx) =
|
|
boxed_future!(make_chatwidget_manual_with_sender()).await;
|
|
let test_codex_home = chat_widget.test_codex_home.take();
|
|
let config = chat_widget.config_ref().clone();
|
|
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
|
let model = get_model_offline_for_tests(config.model.as_deref());
|
|
let session_telemetry = test_session_telemetry(&config, model.as_str());
|
|
|
|
Box::new(App {
|
|
feature_write_lock: Arc::default(),
|
|
config_notification_test_generation: 0,
|
|
config_notification_test_pending: None,
|
|
model_catalog: chat_widget.model_catalog(),
|
|
session_telemetry,
|
|
terminal_size_tags: Vec::new(),
|
|
app_event_tx,
|
|
chat_widget,
|
|
workspace_command_runner: None,
|
|
backend_preference_context: backend_selection::BackendPreferenceContext::new(
|
|
&config,
|
|
Some(config.cwd.to_path_buf()),
|
|
),
|
|
launch_cwd: config.cwd.to_path_buf(),
|
|
runtime_working_directory_override: None,
|
|
local_settings: crate::local_settings::LocalSettings::from(&config),
|
|
config,
|
|
state_db: None,
|
|
cli_kv_overrides: Vec::new(),
|
|
harness_overrides: ConfigOverrides::default(),
|
|
loader_overrides: LoaderOverrides::without_managed_config_for_tests(),
|
|
cloud_config_bundle: CloudConfigBundleLoader::default(),
|
|
runtime_approval_policy_override: None,
|
|
runtime_approvals_reviewer_override: None,
|
|
runtime_permission_profile_override: None,
|
|
pending_server_profiles: HashMap::new(),
|
|
file_search,
|
|
transcript_cells: Vec::new(),
|
|
native_history: Default::default(),
|
|
turn_tips: Default::default(),
|
|
transcript_view: Default::default(),
|
|
conversation_frame: Default::default(),
|
|
last_rendered_history_tail: None,
|
|
last_thread_usage_status_cell: None,
|
|
pending_thread_usage_history_refresh: false,
|
|
overlay: None,
|
|
retained_analytics: None,
|
|
deferred_history_lines: Vec::new(),
|
|
has_emitted_history_lines: false,
|
|
transcript_reflow: TranscriptReflowState::default(),
|
|
initial_history_replay_buffer: None,
|
|
pending_thread_switch_resets: 0,
|
|
scrollback_has_older_history: false,
|
|
enhanced_keys_supported: false,
|
|
keymap: crate::keymap::RuntimeKeymap::defaults(),
|
|
key_chord_matcher: crate::keymap::KeyChordMatcher::default(),
|
|
routed_key_activation: None,
|
|
commit_animation: None,
|
|
status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),
|
|
terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),
|
|
skill_load_warnings: SkillLoadWarningState::default(),
|
|
backtrack: BacktrackState::default(),
|
|
backtrack_render_pending: false,
|
|
feedback: codex_feedback::CodexFeedback::new(),
|
|
feedback_audience: FeedbackAudience::External,
|
|
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
|
app_server_target: crate::AppServerTarget::Embedded,
|
|
pending_right_click_paste: None,
|
|
right_click_paste_environment: super::right_click_paste::PasteEnvironment {
|
|
primary: false,
|
|
platform_default: true,
|
|
ssh: false,
|
|
wsl: false,
|
|
vscode: crate::tui::VscodeDetection::Other,
|
|
},
|
|
reconnect: Default::default(),
|
|
daemon_cli_executable: None,
|
|
pending_update_action: None,
|
|
daemon_restart: None,
|
|
pending_shutdown_exit_thread_id: None,
|
|
windows_sandbox: WindowsSandboxState::default(),
|
|
thread_event_channels: HashMap::new(),
|
|
pending_realtime_speech_replay: HashMap::new(),
|
|
pending_realtime_transcript_replay: HashMap::new(),
|
|
realtime_replay_order: VecDeque::new(),
|
|
background_voice: None,
|
|
background_voice_error: None,
|
|
temporary_structured_requests: HashMap::new(),
|
|
pending_thread_titles: HashMap::new(),
|
|
thread_event_listener_tasks: HashMap::new(),
|
|
agent_navigation: AgentNavigationState::default(),
|
|
agents_overview: Default::default(),
|
|
side_threads: HashMap::new(),
|
|
side_companions: HashMap::new(),
|
|
divider_drag: None,
|
|
passive_link_drag: None,
|
|
abandoned_side_threads: HashSet::new(),
|
|
active_thread_id: None,
|
|
active_thread_rx: None,
|
|
primary_thread_id: None,
|
|
last_subagent_backfill_attempt: None,
|
|
primary_session_configured: None,
|
|
pending_primary_events: VecDeque::new(),
|
|
pending_app_server_requests: PendingAppServerRequests::default(),
|
|
dynamic_tool_status_updates: tokio::sync::broadcast::channel(/*capacity*/ 64).0,
|
|
dynamic_tool_tasks: HashMap::new(),
|
|
pending_startup_thread_start: false,
|
|
pending_server_version_notice: None,
|
|
pending_open_resume_picker: false,
|
|
pending_working_directory_change: None,
|
|
pending_start_managed_worktree: None,
|
|
pending_managed_worktree_creation: false,
|
|
pending_managed_worktree_created: None,
|
|
pending_managed_worktree_transition: None,
|
|
pending_managed_worktree_attach: None,
|
|
startup_protected_input_boundary: false,
|
|
startup_pending_protected_request: false,
|
|
account_email_request_id: None,
|
|
rate_limit_hard_stop_generation: 0,
|
|
rate_limit_refresh_state: Default::default(),
|
|
pending_mcp_login_start: None,
|
|
active_mcp_login_ids: HashMap::new(),
|
|
pending_plugin_enabled_writes: HashMap::new(),
|
|
pending_hook_enabled_writes: HashMap::new(),
|
|
recap: recap::RecapState::default(),
|
|
_test_codex_home: test_codex_home,
|
|
})
|
|
}
|
|
|
|
fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry {
|
|
let model_info =
|
|
construct_model_info_offline_for_tests(model, &config.to_models_manager_config());
|
|
SessionTelemetry::new(
|
|
ThreadId::new(),
|
|
model,
|
|
model_info.slug.as_str(),
|
|
/*account_id*/ None,
|
|
/*account_email*/ None,
|
|
/*auth_mode*/ None,
|
|
"test_originator".to_string(),
|
|
/*log_user_prompts*/ false,
|
|
"test".to_string(),
|
|
serde_json::from_value(serde_json::json!("cli"))
|
|
.expect("cli session source should deserialize"),
|
|
)
|
|
}
|
|
|
|
pub(super) fn app_enabled_in_effective_config(config: &Config, app_id: &str) -> Option<bool> {
|
|
config
|
|
.config_layer_stack
|
|
.effective_config()
|
|
.as_table()
|
|
.and_then(|table| table.get("apps"))
|
|
.and_then(TomlValue::as_table)
|
|
.and_then(|apps| apps.get(app_id))
|
|
.and_then(TomlValue::as_table)
|
|
.and_then(|app| app.get("enabled"))
|
|
.and_then(TomlValue::as_bool)
|
|
}
|