mirror of
https://github.com/openai/codex.git
synced 2026-04-28 18:32:04 +03:00
220 lines
8.4 KiB
Markdown
220 lines
8.4 KiB
Markdown
# PR #1766: Supporting both shift+enter and ctrl+j and adding kpp check + unit test
|
|
|
|
- URL: https://github.com/openai/codex/pull/1766
|
|
- Author: pap-openai
|
|
- Created: 2025-07-31 21:05:22 UTC
|
|
- Updated: 2025-08-01 00:32:14 UTC
|
|
- Changes: +98/-1, Files changed: 5, Commits: 8
|
|
|
|
## Description
|
|
|
|
Apple terminal shows ctrl+j
|
|
|
|
<img width="682" height="483" alt="image" src="https://github.com/user-attachments/assets/8572223d-4a13-46fd-9a16-2281b796faa0" />
|
|
|
|
iTerm that supports KPP shows shift+enter
|
|
|
|
<img width="682" height="570" alt="image" src="https://github.com/user-attachments/assets/e8f7dd4c-ec63-4f1c-8f1e-e35235554783" />
|
|
|
|
## Full Diff
|
|
|
|
```diff
|
|
diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock
|
|
index 87d59b21be..e6b5c11bd9 100644
|
|
--- a/codex-rs/Cargo.lock
|
|
+++ b/codex-rs/Cargo.lock
|
|
@@ -854,6 +854,7 @@ dependencies = [
|
|
"image",
|
|
"insta",
|
|
"lazy_static",
|
|
+ "libc",
|
|
"mcp-types",
|
|
"path-clean",
|
|
"pretty_assertions",
|
|
diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml
|
|
index 63d287ca11..e5739ec505 100644
|
|
--- a/codex-rs/tui/Cargo.toml
|
|
+++ b/codex-rs/tui/Cargo.toml
|
|
@@ -32,6 +32,7 @@ color-eyre = "0.6.3"
|
|
crossterm = { version = "0.28.1", features = ["bracketed-paste"] }
|
|
image = { version = "^0.25.6", default-features = false, features = ["jpeg"] }
|
|
lazy_static = "1"
|
|
+libc = "0.2"
|
|
mcp-types = { path = "../mcp-types" }
|
|
path-clean = "1.0.1"
|
|
ratatui = { version = "0.29.0", features = [
|
|
diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs
|
|
index 3bc573a003..6b7017f6f0 100644
|
|
--- a/codex-rs/tui/src/bottom_pane/chat_composer.rs
|
|
+++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs
|
|
@@ -712,11 +712,16 @@ impl WidgetRef for &ChatComposer<'_> {
|
|
Span::from(" to quit"),
|
|
]
|
|
} else {
|
|
+ let newline_hint = if crate::tui::is_kkp_enabled() {
|
|
+ "Shift+⏎"
|
|
+ } else {
|
|
+ "Ctrl+J"
|
|
+ };
|
|
vec![
|
|
Span::from(" "),
|
|
"⏎".set_style(key_hint_style),
|
|
Span::from(" send "),
|
|
- "Shift+⏎".set_style(key_hint_style),
|
|
+ newline_hint.set_style(key_hint_style),
|
|
Span::from(" newline "),
|
|
"Ctrl+C".set_style(key_hint_style),
|
|
Span::from(" quit"),
|
|
@@ -961,6 +966,9 @@ mod tests {
|
|
use ratatui::Terminal;
|
|
use ratatui::backend::TestBackend;
|
|
|
|
+ // First, run snapshots with KKP enabled so hints show Shift+⏎.
|
|
+ crate::tui::set_kkp_for_tests(true);
|
|
+
|
|
let (tx, _rx) = std::sync::mpsc::channel();
|
|
let sender = AppEventSender::new(tx);
|
|
let mut terminal = match Terminal::new(TestBackend::new(100, 10)) {
|
|
@@ -1005,6 +1013,18 @@ mod tests {
|
|
|
|
assert_snapshot!(name, terminal.backend());
|
|
}
|
|
+
|
|
+ // Also add one snapshot with KKP disabled so we still see Ctrl+J.
|
|
+ crate::tui::set_kkp_for_tests(false);
|
|
+ let mut terminal_ctrlj = match Terminal::new(TestBackend::new(100, 10)) {
|
|
+ Ok(t) => t,
|
|
+ Err(e) => panic!("Failed to create terminal: {e}"),
|
|
+ };
|
|
+ let composer = ChatComposer::new(true, sender.clone());
|
|
+ terminal_ctrlj
|
|
+ .draw(|f| f.render_widget_ref(&composer, f.area()))
|
|
+ .unwrap_or_else(|e| panic!("Failed to draw empty_ctrlj composer: {e}"));
|
|
+ assert_snapshot!("empty_ctrlj", terminal_ctrlj.backend());
|
|
}
|
|
|
|
#[test]
|
|
diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty_ctrlj.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty_ctrlj.snap
|
|
new file mode 100644
|
|
index 0000000000..f798f1af16
|
|
--- /dev/null
|
|
+++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__empty_ctrlj.snap
|
|
@@ -0,0 +1,14 @@
|
|
+---
|
|
+source: tui/src/bottom_pane/chat_composer.rs
|
|
+expression: terminal_ctrlj.backend()
|
|
+---
|
|
+"▌ ... "
|
|
+"▌ "
|
|
+"▌ "
|
|
+"▌ "
|
|
+"▌ "
|
|
+"▌ "
|
|
+"▌ "
|
|
+"▌ "
|
|
+"▌ "
|
|
+" ⏎ send Ctrl+J newline Ctrl+C quit "
|
|
diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs
|
|
index 268483cbcf..81ec9aebdc 100644
|
|
--- a/codex-rs/tui/src/tui.rs
|
|
+++ b/codex-rs/tui/src/tui.rs
|
|
@@ -1,6 +1,8 @@
|
|
use std::io::Result;
|
|
use std::io::Stdout;
|
|
use std::io::stdout;
|
|
+use std::sync::atomic::AtomicBool;
|
|
+use std::sync::atomic::Ordering;
|
|
|
|
use codex_core::config::Config;
|
|
use crossterm::event::DisableBracketedPaste;
|
|
@@ -18,6 +20,60 @@ use crate::custom_terminal::Terminal;
|
|
/// A type alias for the terminal type used in this application
|
|
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
|
|
|
+// Global flag indicating whether Kitty Keyboard Protocol (KKP) appears enabled.
|
|
+static KKP_ENABLED: AtomicBool = AtomicBool::new(false);
|
|
+
|
|
+/// Return whether KKP (alternate key reporting) appears enabled.
|
|
+pub(crate) fn is_kkp_enabled() -> bool {
|
|
+ KKP_ENABLED.load(Ordering::Relaxed)
|
|
+}
|
|
+
|
|
+#[cfg(test)]
|
|
+pub(crate) fn set_kkp_for_tests(value: bool) {
|
|
+ KKP_ENABLED.store(value, Ordering::Relaxed);
|
|
+}
|
|
+
|
|
+/// Try to detect Kitty Keyboard Protocol support by issuing a progressive
|
|
+/// enhancement query and waiting briefly for a response.
|
|
+#[cfg(unix)]
|
|
+fn detect_kitty_protocol() -> std::io::Result<bool> {
|
|
+ use std::io::Read;
|
|
+ use std::io::Write;
|
|
+ use std::io::{self};
|
|
+ use std::os::unix::io::AsRawFd;
|
|
+
|
|
+ let mut stdout = io::stdout();
|
|
+ let mut stdin = io::stdin();
|
|
+
|
|
+ // Send query for progressive enhancement + DA1
|
|
+ write!(stdout, "\x1b[?u\x1b[c")?;
|
|
+ stdout.flush()?;
|
|
+
|
|
+ // Wait up to ~200ms for a response
|
|
+ let fd = stdin.as_raw_fd();
|
|
+ let mut pfd = libc::pollfd {
|
|
+ fd,
|
|
+ events: libc::POLLIN,
|
|
+ revents: 0,
|
|
+ };
|
|
+ let rc = unsafe { libc::poll(&mut pfd as *mut libc::pollfd, 1, 200) };
|
|
+ if rc > 0 && (pfd.revents & libc::POLLIN) != 0 {
|
|
+ let mut buf = [0u8; 256];
|
|
+ if let Ok(n) = stdin.read(&mut buf) {
|
|
+ let response = String::from_utf8_lossy(&buf[..n]);
|
|
+ if response.contains("[?") && response.contains('u') {
|
|
+ return Ok(true);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ Ok(false)
|
|
+}
|
|
+
|
|
+#[cfg(not(unix))]
|
|
+fn detect_kitty_protocol() -> std::io::Result<bool> {
|
|
+ Ok(false)
|
|
+}
|
|
+
|
|
/// Initialize the terminal (inline viewport; history stays in normal scrollback)
|
|
pub fn init(_config: &Config) -> Result<Tui> {
|
|
execute!(stdout(), EnableBracketedPaste)?;
|
|
@@ -34,6 +90,11 @@ pub fn init(_config: &Config) -> Result<Tui> {
|
|
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
|
|
)
|
|
)?;
|
|
+
|
|
+ // Detect KKP availability; used to adjust UI hints in the composer.
|
|
+ let kkp = detect_kitty_protocol().unwrap_or(false);
|
|
+ KKP_ENABLED.store(kkp, Ordering::Relaxed);
|
|
+
|
|
set_panic_hook();
|
|
|
|
let backend = CrosstermBackend::new(stdout());
|
|
```
|
|
|
|
## Review Comments
|
|
|
|
### codex-rs/tui/src/tui.rs
|
|
|
|
- Created: 2025-08-01 00:17:33 UTC | Link: https://github.com/openai/codex/pull/1766#discussion_r2246611560
|
|
|
|
```diff
|
|
@@ -18,6 +20,60 @@ use crate::custom_terminal::Terminal;
|
|
/// A type alias for the terminal type used in this application
|
|
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
|
|
|
+// Global flag indicating whether Kitty Keyboard Protocol (KKP) appears enabled.
|
|
+static KKP_ENABLED: AtomicBool = AtomicBool::new(false);
|
|
```
|
|
|
|
> This feels like we are going to end up with flaky tests due to race conditions? |