Sync tui2 with tui and keep dual-run glue (#7965)

- Copy latest tui sources into tui2
- Restore notifications, tests, and styles
- Keep codex-tui interop conversions and snapshots

The expected changes that are necessary to make this work are still in
place:

diff -ru codex-rs/tui codex-rs/tui2 --exclude='*.snap'
--exclude='*.snap.new'

```diff
diff -ru --ex codex-rs/tui/Cargo.toml codex-rs/tui2/Cargo.toml
--- codex-rs/tui/Cargo.toml	2025-12-12 16:39:12
+++ codex-rs/tui2/Cargo.toml	2025-12-12 17:31:01
@@ -1,15 +1,15 @@
 [package]
-name = "codex-tui"
+name = "codex-tui2"
 version.workspace = true
 edition.workspace = true
 license.workspace = true
 
 [[bin]]
-name = "codex-tui"
+name = "codex-tui2"
 path = "src/main.rs"
 
 [lib]
-name = "codex_tui"
+name = "codex_tui2"
 path = "src/lib.rs"
 
 [features]
@@ -42,6 +42,7 @@
 codex-login = { workspace = true }
 codex-protocol = { workspace = true }
 codex-utils-absolute-path = { workspace = true }
+codex-tui = { workspace = true }
 color-eyre = { workspace = true }
 crossterm = { workspace = true, features = ["bracketed-paste", "event-stream"] }
 derive_more = { workspace = true, features = ["is_variant"] }
diff -ru --ex codex-rs/tui/src/app.rs codex-rs/tui2/src/app.rs
--- codex-rs/tui/src/app.rs	2025-12-12 16:39:05
+++ codex-rs/tui2/src/app.rs	2025-12-12 17:30:36
@@ -69,6 +69,16 @@
     pub update_action: Option<UpdateAction>,
 }
 
+impl From<AppExitInfo> for codex_tui::AppExitInfo {
+    fn from(info: AppExitInfo) -> Self {
+        codex_tui::AppExitInfo {
+            token_usage: info.token_usage,
+            conversation_id: info.conversation_id,
+            update_action: info.update_action.map(Into::into),
+        }
+    }
+}
+
 fn session_summary(
     token_usage: TokenUsage,
     conversation_id: Option<ConversationId>,
Only in codex-rs/tui/src/bin: md-events.rs
Only in codex-rs/tui2/src/bin: md-events2.rs
diff -ru --ex codex-rs/tui/src/cli.rs codex-rs/tui2/src/cli.rs
--- codex-rs/tui/src/cli.rs	2025-11-19 13:40:42
+++ codex-rs/tui2/src/cli.rs	2025-12-12 17:30:43
@@ -88,3 +88,28 @@
     #[clap(skip)]
     pub config_overrides: CliConfigOverrides,
 }
+
+impl From<codex_tui::Cli> for Cli {
+    fn from(cli: codex_tui::Cli) -> Self {
+        Self {
+            prompt: cli.prompt,
+            images: cli.images,
+            resume_picker: cli.resume_picker,
+            resume_last: cli.resume_last,
+            resume_session_id: cli.resume_session_id,
+            resume_show_all: cli.resume_show_all,
+            model: cli.model,
+            oss: cli.oss,
+            oss_provider: cli.oss_provider,
+            config_profile: cli.config_profile,
+            sandbox_mode: cli.sandbox_mode,
+            approval_policy: cli.approval_policy,
+            full_auto: cli.full_auto,
+            dangerously_bypass_approvals_and_sandbox: cli.dangerously_bypass_approvals_and_sandbox,
+            cwd: cli.cwd,
+            web_search: cli.web_search,
+            add_dir: cli.add_dir,
+            config_overrides: cli.config_overrides,
+        }
+    }
+}
diff -ru --ex codex-rs/tui/src/main.rs codex-rs/tui2/src/main.rs
--- codex-rs/tui/src/main.rs	2025-12-12 16:39:05
+++ codex-rs/tui2/src/main.rs	2025-12-12 16:39:06
@@ -1,8 +1,8 @@
 use clap::Parser;
 use codex_arg0::arg0_dispatch_or_else;
 use codex_common::CliConfigOverrides;
-use codex_tui::Cli;
-use codex_tui::run_main;
+use codex_tui2::Cli;
+use codex_tui2::run_main;
 
 #[derive(Parser, Debug)]
 struct TopCli {
diff -ru --ex codex-rs/tui/src/update_action.rs codex-rs/tui2/src/update_action.rs
--- codex-rs/tui/src/update_action.rs	2025-11-19 11:11:47
+++ codex-rs/tui2/src/update_action.rs	2025-12-12 17:30:48
@@ -9,6 +9,20 @@
     BrewUpgrade,
 }
 
+impl From<UpdateAction> for codex_tui::update_action::UpdateAction {
+    fn from(action: UpdateAction) -> Self {
+        match action {
+            UpdateAction::NpmGlobalLatest => {
+                codex_tui::update_action::UpdateAction::NpmGlobalLatest
+            }
+            UpdateAction::BunGlobalLatest => {
+                codex_tui::update_action::UpdateAction::BunGlobalLatest
+            }
+            UpdateAction::BrewUpgrade => codex_tui::update_action::UpdateAction::BrewUpgrade,
+        }
+    }
+}
+
 impl UpdateAction {
     /// Returns the list of command-line arguments for invoking the update.
     pub fn command_args(self) -> (&'static str, &'static [&'static str]) {
```
This commit is contained in:
Josh McKinney authored and GitHub committed 2025-12-12 20:46:18 -08:00
1 parent ad7b9d63c3
commit 6ec2831b91
31 files changed
+9206 -415

No files matched your search

+16 -4
View File
@@ -43,6 +43,7 @@ use crate::render::renderable::Renderable;
use crate::slash_command::SlashCommand;
use crate::slash_command::built_in_slash_commands;
use crate::style::user_message_style;
use codex_common::fuzzy_match::fuzzy_match;
use codex_protocol::custom_prompts::CustomPrompt;
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
@@ -801,6 +802,10 @@ impl ChatComposer {
self.skills.as_ref().is_some_and(|s| !s.is_empty())
}
pub fn skills(&self) -> Option<&Vec<SkillMetadata>> {
self.skills.as_ref()
}
/// Extract a token prefixed with `prefix` under the cursor, if any.
///
/// The returned string **does not** include the prefix.
@@ -1617,7 +1622,7 @@ impl ChatComposer {
let builtin_match = built_in_slash_commands()
.into_iter()
.any(|(cmd_name, _)| cmd_name.starts_with(name));
.any(|(cmd_name, _)| fuzzy_match(cmd_name, name).is_some());
if builtin_match {
return true;
@@ -1626,7 +1631,7 @@ impl ChatComposer {
let prompt_prefix = format!("{PROMPTS_CMD_PREFIX}:");
self.custom_prompts
.iter()
.any(|p| format!("{prompt_prefix}{}", p.name).starts_with(name))
.any(|p| fuzzy_match(&format!("{prompt_prefix}{}", p.name), name).is_some())
}
/// Synchronize `self.command_popup` with the current text in the
@@ -3978,8 +3983,15 @@ mod tests {
"'/re' should activate slash popup via prefix match"
);
// Case 3: invalid prefix "/zzz" – still allowed to open popup if it
// matches no built-in command, our current logic will *not* open popup.
// Case 3: fuzzy match "/ac" (subsequence of /compact and /feedback)
composer.set_text_content("/ac".to_string());
assert!(
matches!(composer.active_popup, ActivePopup::Command(_)),
"'/ac' should activate slash popup via fuzzy match"
);
// Case 4: invalid prefix "/zzz" – still allowed to open popup if it
// matches no built-in command; our current logic will not open popup.
// Verify that explicitly.
composer.set_text_content("/zzz".to_string());
assert!(
@@ -373,4 +373,23 @@ mod tests {
let description = rows.first().and_then(|row| row.description.as_deref());
assert_eq!(description, Some("send saved prompt"));
}
#[test]
fn fuzzy_filter_matches_subsequence_for_ac() {
let mut popup = CommandPopup::new(Vec::new(), false);
popup.on_composer_text_change("/ac".to_string());
let cmds: Vec<&str> = popup
.filtered_items()
.into_iter()
.filter_map(|item| match item {
CommandItem::Builtin(cmd) => Some(cmd.command()),
CommandItem::UserPrompt(_) => None,
})
.collect();
assert!(
cmds.contains(&"compact") && cmds.contains(&"feedback"),
"expected fuzzy search for '/ac' to include compact and feedback, got {cmds:?}"
);
}
}
@@ -40,6 +40,7 @@ pub(crate) struct SelectionItem {
pub description: Option<String>,
pub selected_description: Option<String>,
pub is_current: bool,
pub is_default: bool,
pub actions: Vec<SelectionAction>,
pub dismiss_on_select: bool,
pub search_value: Option<String>,
@@ -187,11 +188,14 @@ impl ListSelectionView {
let is_selected = self.state.selected_idx == Some(visible_idx);
let prefix = if is_selected { '›' } else { ' ' };
let name = item.name.as_str();
let name_with_marker = if item.is_current {
format!("{name} (current)")
let marker = if item.is_current {
" (current)"
} else if item.is_default {
" (default)"
} else {
item.name.clone()
""
};
let name_with_marker = format!("{name}{marker}");
let n = visible_idx + 1;
let wrap_prefix = if self.is_searchable {
// The number keys don't work when search is enabled (since we let the
+9
View File
@@ -131,10 +131,19 @@ impl BottomPane {
}
}
pub fn set_skills(&mut self, skills: Option<Vec<SkillMetadata>>) {
self.composer.set_skill_mentions(skills);
self.request_redraw();
}
pub fn status_widget(&self) -> Option<&StatusIndicatorWidget> {
self.status.as_ref()
}
pub fn skills(&self) -> Option<&Vec<SkillMetadata>> {
self.composer.skills()
}
#[cfg(test)]
pub(crate) fn context_window_percent(&self) -> Option<i64> {
self.context_window_percent