Fix fork popup mouse support

This commit is contained in:
Rakan El Khalil
2026-04-02 13:17:08 -07:00
parent f5c15fa5ce
commit 5938a9e5b5
13 changed files with 150 additions and 101 deletions
+10 -6
View File
@@ -153,19 +153,18 @@ use uuid::Uuid;
mod agent_navigation;
mod app_server_adapter;
mod app_server_requests;
mod loaded_threads;
mod fork_session_overlay;
mod fork_session_overlay_mouse;
mod fork_session_overlay_stack;
mod fork_session_terminal;
mod loaded_threads;
mod pending_interactive_replay;
use self::agent_navigation::AgentNavigationDirection;
use self::agent_navigation::AgentNavigationState;
use self::app_server_requests::PendingAppServerRequests;
use self::loaded_threads::find_loaded_subagent_threads_for_primary;
use self::fork_session_overlay::ForkSessionOverlayState;
use self::fork_session_overlay_stack::ForkSessionOverlayStack;
use self::loaded_threads::find_loaded_subagent_threads_for_primary;
use self::pending_interactive_replay::PendingInteractiveReplayState;
const EXTERNAL_EDITOR_HINT: &str = "Save and close external editor to continue.";
@@ -3905,7 +3904,7 @@ impl App {
if self.overlay.is_some() {
let _ = self.handle_backtrack_overlay_event(tui, event).await?;
} else if self.fork_session_overlay.is_some() {
self.handle_fork_session_overlay_tui_event(tui, event)
self.handle_fork_session_overlay_tui_event(tui, app_server, event)
.await?;
} else {
match event {
@@ -3974,7 +3973,7 @@ impl App {
.await;
}
AppEvent::ClearUi => {
self.clear_terminal_ui(tui, false)?;
self.clear_terminal_ui(tui, /*redraw_header*/ false)?;
self.reset_app_ui_state_after_clear();
self.start_fresh_session_with_summary_hint(tui, app_server)
@@ -4125,7 +4124,12 @@ impl App {
// Fresh threads expose a precomputed path, but the file is
// materialized lazily on first user message.
if path.exists() {
match crate::resolve_session_thread_id(path.as_path(), None).await {
match crate::resolve_session_thread_id(
path.as_path(),
/*id_str_if_uuid*/ None,
)
.await
{
Some(thread_id) => {
if let Err(err) =
self.open_fork_session_overlay(tui, thread_id).await
+88 -49
View File
@@ -18,6 +18,7 @@ use ratatui::widgets::Clear;
use ratatui::widgets::Widget;
use crate::app::App;
use crate::app_server_session::AppServerSession;
use crate::chatwidget::ExternalEditorState;
use crate::custom_terminal::Frame;
use crate::insert_history::insert_history_lines;
@@ -281,7 +282,7 @@ fn popup_block(
fn popup_terminal_size(popup: Rect) -> codex_utils_pty::TerminalSize {
let inner = popup_block(
None,
/*exit_code*/ None,
OverlayCommandState::PassThrough,
OverlayFocusedPane::Popup,
)
@@ -382,7 +383,7 @@ impl App {
stack.push_popup(popup_state);
} else {
self.fork_session_overlay = Some(ForkSessionOverlayStack::new(popup_state));
tui.set_mouse_capture_enabled(true)?;
tui.set_mouse_capture_enabled(/*enabled*/ true)?;
}
tui.frame_requester().schedule_frame();
Ok(())
@@ -393,7 +394,7 @@ impl App {
let _ = stack.close_active_popup();
if stack.is_empty() {
self.fork_session_overlay = None;
tui.set_mouse_capture_enabled(false)?;
tui.set_mouse_capture_enabled(/*enabled*/ false)?;
self.restore_inline_view_after_fork_overlay_close(tui)?;
}
}
@@ -404,6 +405,7 @@ impl App {
pub(crate) async fn handle_fork_session_overlay_tui_event(
&mut self,
tui: &mut tui::Tui,
app_server: &mut AppServerSession,
event: TuiEvent,
) -> Result<()> {
match event {
@@ -438,7 +440,7 @@ impl App {
} else {
match stack.focused_pane() {
OverlayFocusedPane::Background => {
self.handle_key_event(tui, key_event).await;
self.handle_key_event(tui, app_server, key_event).await;
}
OverlayFocusedPane::Popup => {
forward_key = Some(key_event);
@@ -637,61 +639,92 @@ impl App {
}
KeyCode::Char('h') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_left_edge(area, popup.popup, -1);
popup.popup = resize_left_edge(
area,
popup.popup,
/*delta*/ -1,
);
}
}
KeyCode::Char('H') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_left_edge(area, popup.popup, 1);
popup.popup = resize_left_edge(
area,
popup.popup,
/*delta*/ 1,
);
}
}
KeyCode::Char('j') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_bottom_edge(area, popup.popup, 1);
popup.popup = resize_bottom_edge(
area,
popup.popup,
/*delta*/ 1,
);
}
}
KeyCode::Char('J') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_bottom_edge(area, popup.popup, -1);
popup.popup = resize_bottom_edge(
area,
popup.popup,
/*delta*/ -1,
);
}
}
KeyCode::Char('k') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_top_edge(area, popup.popup, -1);
popup.popup = resize_top_edge(
area,
popup.popup,
/*delta*/ -1,
);
}
}
KeyCode::Char('K') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup = resize_top_edge(area, popup.popup, 1);
popup.popup = resize_top_edge(
area,
popup.popup,
/*delta*/ 1,
);
}
}
KeyCode::Char('l') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_right_edge(area, popup.popup, 1);
popup.popup = resize_right_edge(
area,
popup.popup,
/*delta*/ 1,
);
}
}
KeyCode::Char('L') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_right_edge(area, popup.popup, -1);
popup.popup = resize_right_edge(
area,
popup.popup,
/*delta*/ -1,
);
}
}
KeyCode::Char('=') | KeyCode::Char('+') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_all_edges(area, popup.popup, 1);
popup.popup = resize_all_edges(
area,
popup.popup,
/*delta*/ 1,
);
}
}
KeyCode::Char('-') => {
if let Some(popup) = stack.active_popup_mut() {
popup.popup =
resize_all_edges(area, popup.popup, -1);
popup.popup = resize_all_edges(
area,
popup.popup,
/*delta*/ -1,
);
}
}
KeyCode::Esc | KeyCode::Enter => {
@@ -778,7 +811,7 @@ impl App {
};
if close_all_overlays {
self.fork_session_overlay = None;
tui.set_mouse_capture_enabled(false)?;
tui.set_mouse_capture_enabled(/*enabled*/ false)?;
self.restore_inline_view_after_fork_overlay_close(tui)?;
return Ok(());
}
@@ -987,8 +1020,9 @@ mod tests {
use crate::app_backtrack::BacktrackState;
use crate::chatwidget::tests::make_chatwidget_manual_with_sender;
use crate::file_search::FileSearchManager;
use codex_core::CodexAuth;
use codex_core::config::ConfigOverrides;
use codex_login::CodexAuth;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SessionSource;
@@ -1022,42 +1056,38 @@ mod tests {
async fn make_test_app() -> App {
let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await;
let config = chat_widget.config_ref().clone();
let server = Arc::new(
let _server = Arc::new(
codex_core::test_support::thread_manager_with_models_provider(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
),
);
let auth_manager = codex_core::test_support::auth_manager_from_auth(
let _auth_manager = codex_core::test_support::auth_manager_from_auth(
CodexAuth::from_api_key("Test API Key"),
);
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
let model = codex_core::test_support::get_model_offline(config.model.as_deref());
let session_telemetry = SessionTelemetry::new(
ThreadId::new(),
model.as_str(),
model.as_str(),
None,
None,
None,
/*account_id*/ None,
/*account_email*/ None,
/*auth_mode*/ None,
"test_originator".to_string(),
false,
/*log_user_prompts*/ false,
"test".to_string(),
SessionSource::Cli,
);
App {
server,
model_catalog: chat_widget.model_catalog(),
session_telemetry,
app_event_tx,
chat_widget,
auth_manager,
config,
active_profile: None,
cli_kv_overrides: Vec::new(),
arg0_paths: codex_arg0::Arg0DispatchPaths::default(),
loader_overrides: codex_core::config_loader::LoaderOverrides::default(),
cloud_requirements: codex_core::config_loader::CloudRequirementsLoader::default(),
harness_overrides: ConfigOverrides::default(),
runtime_approval_policy_override: None,
runtime_sandbox_policy_override: None,
@@ -1075,8 +1105,9 @@ mod tests {
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
remote_app_server_url: None,
remote_app_server_auth_token: None,
pending_update_action: None,
suppress_shutdown_complete: false,
pending_shutdown_exit_thread_id: None,
windows_sandbox: WindowsSandboxState::default(),
thread_event_channels: HashMap::new(),
@@ -1085,8 +1116,10 @@ mod tests {
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: Default::default(),
}
}
@@ -1095,7 +1128,10 @@ mod tests {
let temp_dir = tempdir().expect("tempdir");
let mut app = make_test_app().await;
app.active_profile = Some("dev".to_string());
app.config.cwd = temp_dir.path().join("project");
app.config.cwd = codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(
temp_dir.path().join("project"),
)
.unwrap();
app.chat_widget.set_model("gpt-5.4");
app.on_update_reasoning_effort(Some(codex_protocol::openai_models::ReasoningEffort::High));
app.runtime_approval_policy_override =
@@ -1157,7 +1193,7 @@ mod tests {
let area = Rect::new(0, 0, 100, 28);
let popup = default_popup_rect(area);
let moved = move_popup_rect(area, popup, -100, 100);
let moved = move_popup_rect(area, popup, /*dx*/ -100, /*dy*/ 100);
assert_eq!(moved, Rect::new(0, 13, 64, 15));
}
@@ -1207,7 +1243,7 @@ mod tests {
];
assert_eq!(
app.transcript_history_lines(80),
app.transcript_history_lines(/*width*/ 80),
vec![Line::from("first"), Line::from(""), Line::from("second")]
);
}
@@ -1217,8 +1253,8 @@ mod tests {
let area = Rect::new(0, 0, 100, 28);
let popup = default_popup_rect(area);
let shrunk = resize_all_edges(area, popup, -100);
let grown = resize_all_edges(area, popup, 100);
let shrunk = resize_all_edges(area, popup, /*delta*/ -100);
let grown = resize_all_edges(area, popup, /*delta*/ 100);
assert_eq!(shrunk, Rect::new(38, 11, 44, 10));
assert_eq!(grown, Rect::new(0, 0, 96, 26));
@@ -1229,7 +1265,7 @@ mod tests {
let area = Rect::new(0, 0, 140, 40);
let popup = default_popup_rect(area);
let grown = resize_all_edges(area, popup, 100);
let grown = resize_all_edges(area, popup, /*delta*/ 100);
assert_eq!(grown, Rect::new(0, 0, 136, 38));
}
@@ -1260,7 +1296,7 @@ mod tests {
ready for a fresh turn\r\n",
);
app.fork_session_overlay = Some(ForkSessionOverlayStack::new(ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(parser, None),
terminal: ForkSessionTerminal::for_test(parser, /*exit_code*/ None),
popup: default_popup_rect(Rect::new(0, 0, 100, 28)),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
@@ -1305,13 +1341,13 @@ ready for a fresh turn\r\n",
);
let mut stack = ForkSessionOverlayStack::new(ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(first_popup, None),
terminal: ForkSessionTerminal::for_test(first_popup, /*exit_code*/ None),
popup: Rect::new(14, 7, 52, 12),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
});
stack.push_popup(ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(second_popup, None),
terminal: ForkSessionTerminal::for_test(second_popup, /*exit_code*/ None),
popup: Rect::new(28, 11, 52, 12),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
@@ -1354,7 +1390,7 @@ ready for a fresh turn\r\n",
ready for a fresh turn\r\n",
);
let mut stack = ForkSessionOverlayStack::new(ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(parser, None),
terminal: ForkSessionTerminal::for_test(parser, /*exit_code*/ None),
popup: default_popup_rect(Rect::new(0, 0, 100, 28)),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
@@ -1399,7 +1435,10 @@ ready for a fresh turn\r\n",
Vec::new(),
);
let mut stack = ForkSessionOverlayStack::new(ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(vt100::Parser::new(1, 1, 0), None),
terminal: ForkSessionTerminal::for_test(
vt100::Parser::new(1, 1, 0),
/*exit_code*/ None,
),
popup: default_popup_rect(Rect::new(0, 0, 80, 18)),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
@@ -104,7 +104,7 @@ mod tests {
};
assert_eq!(
overlay_mouse_action(area, &popups, None, mouse_event),
overlay_mouse_action(area, &popups, /*drag_state*/ None, mouse_event),
OverlayMouseAction::FocusBackground
);
}
@@ -121,7 +121,7 @@ mod tests {
};
assert_eq!(
overlay_mouse_action(area, &popups, None, mouse_event),
overlay_mouse_action(area, &popups, /*drag_state*/ None, mouse_event),
OverlayMouseAction::FocusPopup {
popup_index: 0,
drag_state: PopupDragState {
@@ -165,7 +165,7 @@ mod tests {
};
assert_eq!(
overlay_mouse_action(area, &popups, None, mouse_event),
overlay_mouse_action(area, &popups, /*drag_state*/ None, mouse_event),
OverlayMouseAction::EndDrag
);
}
@@ -182,7 +182,7 @@ mod tests {
};
assert_eq!(
overlay_mouse_action(area, &popups, None, mouse_event),
overlay_mouse_action(area, &popups, /*drag_state*/ None, mouse_event),
OverlayMouseAction::FocusPopup {
popup_index: 1,
drag_state: PopupDragState {
@@ -128,8 +128,11 @@ mod tests {
fn popup(x: u16, y: u16) -> ForkSessionOverlayState {
ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(vt100::Parser::new(1, 1, 0), None),
popup: Rect::new(x, y, 40, 16),
terminal: ForkSessionTerminal::for_test(
vt100::Parser::new(1, 1, 0),
/*exit_code*/ None,
),
popup: Rect::new(/*x*/ x, /*y*/ y, 40, 16),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
}
@@ -142,7 +145,7 @@ mod tests {
stack.push_popup(popup(30, 30));
let active = stack
.bring_popup_to_front(0)
.bring_popup_to_front(/*index*/ 0)
.expect("bring first popup to front");
assert_eq!(active.popup, Rect::new(10, 10, 40, 16));
@@ -10,8 +10,9 @@ use crate::file_search::FileSearchManager;
use crate::history_cell;
use crate::history_cell::PlainHistoryCell;
use crate::test_backend::VT100Backend;
use codex_core::CodexAuth;
use codex_config::types::ApprovalsReviewer;
use codex_core::config::ConfigOverrides;
use codex_login::CodexAuth;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
@@ -35,41 +36,37 @@ use tempfile::NamedTempFile;
async fn make_test_app() -> App {
let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await;
let config = chat_widget.config_ref().clone();
let server = Arc::new(
let _server = Arc::new(
codex_core::test_support::thread_manager_with_models_provider(
CodexAuth::from_api_key("Test API Key"),
config.model_provider.clone(),
),
);
let auth_manager =
let _auth_manager =
codex_core::test_support::auth_manager_from_auth(CodexAuth::from_api_key("Test API Key"));
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
let model = codex_core::test_support::get_model_offline(config.model.as_deref());
let session_telemetry = SessionTelemetry::new(
ThreadId::new(),
model.as_str(),
model.as_str(),
None,
None,
None,
/*account_id*/ None,
/*account_email*/ None,
/*auth_mode*/ None,
"test_originator".to_string(),
false,
/*log_user_prompts*/ false,
"test".to_string(),
SessionSource::Cli,
);
App {
server,
model_catalog: chat_widget.model_catalog(),
session_telemetry,
app_event_tx,
chat_widget,
auth_manager,
config,
active_profile: None,
cli_kv_overrides: Vec::new(),
arg0_paths: codex_arg0::Arg0DispatchPaths::default(),
loader_overrides: codex_core::config_loader::LoaderOverrides::default(),
cloud_requirements: codex_core::config_loader::CloudRequirementsLoader::default(),
harness_overrides: ConfigOverrides::default(),
runtime_approval_policy_override: None,
runtime_sandbox_policy_override: None,
@@ -87,8 +84,9 @@ async fn make_test_app() -> App {
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
remote_app_server_url: None,
remote_app_server_auth_token: None,
pending_update_action: None,
suppress_shutdown_complete: false,
pending_shutdown_exit_thread_id: None,
windows_sandbox: WindowsSandboxState::default(),
thread_event_channels: HashMap::new(),
@@ -97,14 +95,16 @@ async fn make_test_app() -> App {
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: Default::default(),
}
}
fn configure_chat_widget(app: &mut App) {
let rollout_file = NamedTempFile::new().expect("rollout file");
app.handle_codex_event_now(Event {
app.chat_widget.handle_codex_event(Event {
id: "configured".to_string(),
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
session_id: ThreadId::new(),
@@ -114,7 +114,7 @@ fn configure_chat_widget(app: &mut App) {
model_provider_id: "test-provider".to_string(),
service_tier: None,
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_core::config::types::ApprovalsReviewer::User,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
cwd: PathBuf::from("/tmp/worktree"),
reasoning_effort: Some(ReasoningEffortConfig::XHigh),
@@ -216,7 +216,7 @@ async fn fork_session_overlay_open_from_inline_viewport_snapshot() {
terminal.set_viewport_area(Rect::new(0, height - 7, width, 7));
app.fork_session_overlay = Some(ForkSessionOverlayStack::new(ForkSessionOverlayState {
terminal: ForkSessionTerminal::for_test(child, None),
terminal: ForkSessionTerminal::for_test(child, /*exit_code*/ None),
popup: default_popup_rect(Rect::new(0, 0, width, height)),
command_state: OverlayCommandState::PassThrough,
drag_state: None,
@@ -423,7 +423,7 @@ mod tests {
assert_eq!(
encode_key_event(
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL),
false
/*application_cursor*/ false
),
Some(vec![0x03])
);
@@ -432,7 +432,10 @@ mod tests {
#[test]
fn alt_left_uses_word_motion_fallback() {
assert_eq!(
encode_key_event(KeyEvent::new(KeyCode::Left, KeyModifiers::ALT), false),
encode_key_event(
KeyEvent::new(KeyCode::Left, KeyModifiers::ALT),
/*application_cursor*/ false,
),
Some(b"\x1bb".to_vec())
);
}
@@ -440,7 +443,7 @@ mod tests {
#[test]
fn bracketed_paste_wraps_contents() {
assert_eq!(
encode_paste(true, "hello"),
encode_paste(/*bracketed_paste*/ true, "hello"),
b"\x1b[200~hello\x1b[201~".to_vec()
);
}
@@ -5,7 +5,7 @@ expression: snapshot_buffer(&buf)
│ >_ OpenAI Codex (v0.0.0) │
│ │
│ model: gpt-5.3-codex /model to change │
│ directory: ~/code/codex/codex-rs/tui │
│ directory: /tmp/project │
╰─────────────────────────────────────────────╯
@@ -15,4 +15,4 @@ expression: snapshot_buffer(&buf)
› Ask Codex to do anything
? for shortcuts 100% context left
gpt-5.3-codex default · 100% left · /tmp/project
@@ -6,10 +6,10 @@ expression: snapshot_buffer(&buf)
│ >_ OpenAI Codex (v0.0.0) │
│ │
│ model: gpt-5.3-codex /model to change │
│ directory: ~/code/codex/codex-rs/tui │
│ directory: /tmp/project │
╰─────────────────────────────────────────────╯
› Ask Codex to do anything
? for shortcuts 100% context left
gpt-5.3-codex default · 100% left · /tmp/project
@@ -6,7 +6,7 @@ expression: snapshot_buffer(&buf)
│ >_ OpenAI Codex (v0.0.0) │
│ │
│ model: gpt-5.3-codex /model to change │
│ directory: ~/code/codex/codex-rs/tui │
│ directory: /tmp/project │
╰─────────────────────────────────────────────╯
╭ fork session running ctrl+] prefix───────────────╮
@@ -16,7 +16,7 @@ expression: snapshot_buffer(&buf)
│ ╭ fork session running ctrl+] prefix───────────────╮
› Ask Codex to│ │Second fork session │
│ │ │
? for shortc│ │> /tmp/second │ 100% context left
gpt-5.3-code│ │> /tmp/second │
│ │ │
│ │ │
│ │ │
@@ -6,7 +6,7 @@ expression: snapshot_buffer(&buf)
│ >_ OpenAI Codex (v0.0.0) │
│ │
│ model: gpt-5.3-codex /model to change │
│ directory: ~/code/codex/codex-rs/tui │
│ directory: /tmp/project │
╰─────────────────────────────────────────────╯
╭ fork session running ctrl+] prefix───────────────────────────╮
│Independent Codex session │
@@ -16,7 +16,7 @@ expression: snapshot_buffer(&buf)
│ready for a fresh turn │
› Ask Codex to do │ │
│ │
? for shortcuts │ │00% context left
gpt-5.3-codex de│ │
│ │
│ │
│ │
@@ -6,7 +6,7 @@ expression: snapshot_buffer(&buf)
│ >_ OpenAI Codex (v0.0.0) │
│ │
│ model: gpt-5.3-codex /model to change │
│ directory: ~/code/codex/codex-rs/tui │
│ directory: /tmp/project │
╰─────────────────────────────────────────────╯
╭ fork session running ctrl+] prefix───────────────────────────╮
│Independent Codex session │
@@ -16,7 +16,7 @@ expression: snapshot_buffer(&buf)
│ready for a fresh turn │
› Ask Codex to do │ │
│ │
? for shortcuts │ │00% context left
gpt-5.3-codex de│ │
│ │
│ │
│ │
+2 -2
View File
@@ -155,10 +155,10 @@ mod updates;
mod version;
#[cfg(all(not(target_os = "linux"), feature = "voice-input"))]
mod voice;
mod vt100_backend;
mod vt100_render;
#[cfg(all(not(target_os = "linux"), not(feature = "voice-input")))]
mod voice;
mod vt100_backend;
mod vt100_render;
#[cfg(target_os = "linux")]
#[allow(dead_code)]
mod voice {
+7 -7
View File
@@ -497,18 +497,18 @@ pub const XTERM_COLORS: [(u8, u8, u8); 256] = [
#[cfg(test)]
mod tests {
use super::parse_bg_rgb_env;
use super::parse_rgb_env;
use pretty_assertions::assert_eq;
#[test]
fn parse_bg_rgb_env_accepts_rgb_triplet() {
assert_eq!(parse_bg_rgb_env("12,34,56"), Some((12, 34, 56)));
fn parse_rgb_env_accepts_rgb_triplet() {
assert_eq!(parse_rgb_env("12,34,56"), Some((12, 34, 56)));
}
#[test]
fn parse_bg_rgb_env_rejects_invalid_values() {
assert_eq!(parse_bg_rgb_env("12,34"), None);
assert_eq!(parse_bg_rgb_env("12,34,56,78"), None);
assert_eq!(parse_bg_rgb_env("12,nope,56"), None);
fn parse_rgb_env_rejects_invalid_values() {
assert_eq!(parse_rgb_env("12,34"), None);
assert_eq!(parse_rgb_env("12,34,56,78"), None);
assert_eq!(parse_rgb_env("12,nope,56"), None);
}
}