mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[1 of 2] Optimize TUI startup terminal probes (#23175)
## Why Codex TUI startup still feels slower than 0.117.0 after the app-server move in 0.118.0. A visible chunk of launch-to-input latency comes from serial terminal startup probes: cursor position, keyboard enhancement support, and default foreground/background color queries can each wait on terminal responses before the first usable frame. Refs #16335. ## What This PR batches the terminal startup probes into one bounded probe. It also reuses the probed cursor position and default colors during TUI setup, fast-paths the primary-device-attributes fallback as keyboard enhancement unsupported, and keeps lightweight startup timing logs for future tuning. The startup telemetry is intentionally left in production: it records phase timings for terminal probes and initial-frame scheduling so future startup regressions can be diagnosed from normal logs rather than re-adding one-off debug instrumentation. ## Benchmark In the local pty startup benchmark, the pre-optimization `main` baseline was about 250.5ms median from launch to accepted chat input. This probe-only branch measured about 152ms median, for an approximate savings of 95-100ms. ## Stack 1. [#23175: [1 of 2] Optimize TUI startup terminal probes](https://github.com/openai/codex/pull/23175) — this PR 2. [#23176: [2 of 2] Start fresh TUI thread in background](https://github.com/openai/codex/pull/23176) — layered on this PR ## Verification - `cargo test -p codex-tui`
This commit is contained in:
committed by
GitHub
Unverified
parent
e734cb5713
commit
adca1b643f
@@ -660,6 +660,7 @@ impl App {
|
||||
startup_hooks_browser: Option<HooksListEntry>,
|
||||
) -> Result<AppExitInfo> {
|
||||
use tokio_stream::StreamExt;
|
||||
let startup_started_at = Instant::now();
|
||||
let (app_event_tx, mut app_event_rx) = unbounded_channel();
|
||||
let app_event_tx = AppEventSender::new(app_event_tx);
|
||||
emit_project_config_warnings(&app_event_tx, &config);
|
||||
@@ -703,7 +704,9 @@ impl App {
|
||||
});
|
||||
}
|
||||
};
|
||||
let bootstrap_started_at = Instant::now();
|
||||
let bootstrap = app_server.bootstrap(&config).await?;
|
||||
let bootstrap_ms = bootstrap_started_at.elapsed().as_millis();
|
||||
let mut model = bootstrap.default_model;
|
||||
let available_models = bootstrap.available_models;
|
||||
let exit_info = handle_model_migration_prompt_if_needed(
|
||||
@@ -760,8 +763,10 @@ impl App {
|
||||
let workspace_command_runner: WorkspaceCommandRunner = Arc::new(
|
||||
AppServerWorkspaceCommandRunner::new(app_server.request_handle()),
|
||||
);
|
||||
let runtime_model_provider_started_at = Instant::now();
|
||||
let runtime_model_provider_base_url =
|
||||
resolve_runtime_model_provider_base_url(&config.model_provider).await;
|
||||
let runtime_model_provider_ms = runtime_model_provider_started_at.elapsed().as_millis();
|
||||
|
||||
let enhanced_keys_supported = tui.enhanced_keys_supported();
|
||||
let wait_for_initial_session_configured =
|
||||
@@ -772,6 +777,7 @@ impl App {
|
||||
&initial_prompt,
|
||||
&initial_images,
|
||||
);
|
||||
let thread_and_widget_started_at = Instant::now();
|
||||
let (mut chat_widget, initial_started_thread) = match session_selection {
|
||||
SessionSelection::StartFresh | SessionSelection::Exit => {
|
||||
let started = app_server.start_thread(&config).await?;
|
||||
@@ -888,6 +894,7 @@ impl App {
|
||||
(ChatWidget::new_with_app_event(init), Some(forked))
|
||||
}
|
||||
};
|
||||
let thread_and_widget_ms = thread_and_widget_started_at.elapsed().as_millis();
|
||||
if let Some(message) = external_agent_config_migration_message {
|
||||
chat_widget.add_info_message(message, /*hint*/ None);
|
||||
}
|
||||
@@ -958,6 +965,7 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
if let Some(entry) = startup_hooks_browser {
|
||||
app.chat_widget.open_hooks_browser(entry);
|
||||
}
|
||||
let initial_session_started_at = Instant::now();
|
||||
if let Some(started) = initial_started_thread {
|
||||
let thread_id = started.session.thread_id;
|
||||
app.enqueue_primary_thread_session(started.session, started.turns)
|
||||
@@ -967,6 +975,7 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
.await;
|
||||
}
|
||||
}
|
||||
let initial_session_ms = initial_session_started_at.elapsed().as_millis();
|
||||
|
||||
// On startup, if a managed filesystem sandbox is active, warn about
|
||||
// world-writable dirs on Windows.
|
||||
@@ -996,10 +1005,20 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
}
|
||||
}
|
||||
|
||||
let event_stream_started_at = Instant::now();
|
||||
let tui_events = tui.event_stream();
|
||||
tokio::pin!(tui_events);
|
||||
|
||||
tui.frame_requester().schedule_frame();
|
||||
tracing::info!(
|
||||
duration_ms = %startup_started_at.elapsed().as_millis(),
|
||||
bootstrap_ms = %bootstrap_ms,
|
||||
runtime_model_provider_ms = %runtime_model_provider_ms,
|
||||
thread_and_widget_ms = %thread_and_widget_ms,
|
||||
initial_session_ms = %initial_session_ms,
|
||||
event_stream_ms = %event_stream_started_at.elapsed().as_millis(),
|
||||
"tui startup initial frame scheduled"
|
||||
);
|
||||
app.refresh_startup_skills(&app_server);
|
||||
// Kick off a non-blocking rate-limit prefetch so the first `/status`
|
||||
// already has data, without delaying the initial frame render.
|
||||
|
||||
@@ -1219,10 +1219,13 @@ async fn run_ratatui_app(
|
||||
tracing::error!("panic: {info}");
|
||||
prev_hook(info);
|
||||
}));
|
||||
let mut terminal = tui::init()?;
|
||||
terminal.clear()?;
|
||||
let mut initialized_terminal = tui::init()?;
|
||||
initialized_terminal.terminal.clear()?;
|
||||
|
||||
let mut tui = Tui::new(terminal);
|
||||
let mut tui = Tui::new(
|
||||
initialized_terminal.terminal,
|
||||
initialized_terminal.enhanced_keys_supported,
|
||||
);
|
||||
let mut terminal_restore_guard = TerminalRestoreGuard::new();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
|
||||
@@ -68,6 +68,13 @@ pub fn default_bg() -> Option<(u8, u8, u8)> {
|
||||
default_colors().map(|c| c.bg)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn set_default_colors_from_startup_probe(
|
||||
colors: Option<crate::terminal_probe::DefaultColors>,
|
||||
) {
|
||||
imp::set_default_colors_from_startup_probe(colors);
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(test)))]
|
||||
mod imp {
|
||||
use super::DefaultColors;
|
||||
@@ -112,6 +119,18 @@ mod imp {
|
||||
cache.get_or_init_with(query_default_colors)
|
||||
}
|
||||
|
||||
pub(super) fn set_default_colors_from_startup_probe(
|
||||
colors: Option<crate::terminal_probe::DefaultColors>,
|
||||
) {
|
||||
if let Ok(mut cache) = default_colors_cache().lock() {
|
||||
cache.value = colors.map(|colors| DefaultColors {
|
||||
fg: colors.fg,
|
||||
bg: colors.bg,
|
||||
});
|
||||
cache.attempted = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn requery_default_colors() {
|
||||
if let Ok(mut cache) = default_colors_cache().lock() {
|
||||
// Don't try to refresh if the cache is already attempted and failed.
|
||||
@@ -166,6 +185,12 @@ mod imp {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(super) fn set_default_colors_from_startup_probe(
|
||||
_colors: Option<crate::terminal_probe::DefaultColors>,
|
||||
) {
|
||||
}
|
||||
|
||||
pub(super) fn requery_default_colors() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,21 @@ mod imp {
|
||||
pub(crate) bg: (u8, u8, u8),
|
||||
}
|
||||
|
||||
/// Results from the TUI's one-shot startup terminal probe.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) struct StartupProbe {
|
||||
pub(crate) cursor_position: Option<Position>,
|
||||
pub(crate) default_colors: Option<DefaultColors>,
|
||||
pub(crate) keyboard_enhancement_supported: Option<bool>,
|
||||
}
|
||||
|
||||
/// Whether the startup probe should query keyboard enhancement support.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) enum StartupKeyboardEnhancementProbe {
|
||||
Query,
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Temporary terminal handle used while a startup probe owns terminal input.
|
||||
///
|
||||
/// The preferred path is duplicated stdin/stdout, because terminal replies are delivered to the
|
||||
@@ -198,19 +213,6 @@ mod imp {
|
||||
Ok(unsafe { File::from_raw_fd(duplicated) })
|
||||
}
|
||||
|
||||
/// Queries the current cursor position and returns a zero-based Ratatui position.
|
||||
///
|
||||
/// A timeout or a non-CPR response is not fatal. Callers should treat `Ok(None)` as "terminal
|
||||
/// did not answer this optional query" and choose a conservative fallback.
|
||||
pub(crate) fn cursor_position(timeout: Duration) -> io::Result<Option<Position>> {
|
||||
let mut tty = Tty::open()?;
|
||||
tty.write_all(b"\x1B[6n")?;
|
||||
let Some(response) = read_until(&mut tty, timeout, parse_cursor_position)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(response))
|
||||
}
|
||||
|
||||
/// Queries OSC 10 and OSC 11 default colors under one shared deadline.
|
||||
///
|
||||
/// Foreground and background are only useful as a pair for palette calculations, so a missing
|
||||
@@ -226,16 +228,24 @@ mod imp {
|
||||
Ok(Some(colors))
|
||||
}
|
||||
|
||||
/// Checks whether the terminal reports support for keyboard enhancement flags.
|
||||
/// Runs the optional terminal queries needed during TUI startup under one shared deadline.
|
||||
///
|
||||
/// The probe sends the kitty keyboard-status query followed by primary-device-attributes as a
|
||||
/// fallback. A PDA response proves that the terminal answered but does not prove that keyboard
|
||||
/// enhancement is unsupported until the bounded wait has expired; flags that arrive later in
|
||||
/// the same deadline must still win.
|
||||
pub(crate) fn keyboard_enhancement_supported(timeout: Duration) -> io::Result<Option<bool>> {
|
||||
/// Keeping these queries batched avoids paying one timeout per unsupported capability before
|
||||
/// the first frame can render.
|
||||
pub(crate) fn startup(
|
||||
timeout: Duration,
|
||||
keyboard_probe: StartupKeyboardEnhancementProbe,
|
||||
) -> io::Result<StartupProbe> {
|
||||
let mut tty = Tty::open()?;
|
||||
tty.write_all(b"\x1B[?u\x1B[c")?;
|
||||
read_keyboard_enhancement_supported(&mut tty, timeout)
|
||||
match keyboard_probe {
|
||||
StartupKeyboardEnhancementProbe::Query => {
|
||||
tty.write_all(b"\x1B[6n\x1B]10;?\x1B\\\x1B]11;?\x1B\\\x1B[?u\x1B[c")?;
|
||||
}
|
||||
StartupKeyboardEnhancementProbe::Skip => {
|
||||
tty.write_all(b"\x1B[6n\x1B]10;?\x1B\\\x1B]11;?\x1B\\")?;
|
||||
}
|
||||
}
|
||||
read_startup_probe(&mut tty, timeout, keyboard_probe)
|
||||
}
|
||||
|
||||
/// Reads available terminal bytes until `parse` recognizes a probe response or time expires.
|
||||
@@ -265,42 +275,95 @@ mod imp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads keyboard-enhancement responses while giving flags the full bounded window to arrive.
|
||||
fn read_keyboard_enhancement_supported(
|
||||
fn read_startup_probe(
|
||||
tty: &mut Tty,
|
||||
timeout: Duration,
|
||||
) -> io::Result<Option<bool>> {
|
||||
keyboard_probe: StartupKeyboardEnhancementProbe,
|
||||
) -> io::Result<StartupProbe> {
|
||||
let deadline = Instant::now() + timeout;
|
||||
let mut buffer = Vec::new();
|
||||
let mut saw_supported = false;
|
||||
let mut saw_unsupported_fallback = false;
|
||||
let mut probe = StartupProbe {
|
||||
cursor_position: None,
|
||||
default_colors: None,
|
||||
keyboard_enhancement_supported: None,
|
||||
};
|
||||
let mut saw_supported_keyboard = false;
|
||||
loop {
|
||||
tty.read_available(&mut buffer)?;
|
||||
match parse_keyboard_enhancement_support(&buffer) {
|
||||
KeyboardProbeState::SupportedAndFallback => return Ok(Some(true)),
|
||||
KeyboardProbeState::Supported => saw_supported = true,
|
||||
KeyboardProbeState::UnsupportedFallback => saw_unsupported_fallback = true,
|
||||
KeyboardProbeState::Pending => {}
|
||||
}
|
||||
if saw_supported && saw_unsupported_fallback {
|
||||
return Ok(Some(true));
|
||||
update_startup_probe(
|
||||
&mut probe,
|
||||
&mut saw_supported_keyboard,
|
||||
&buffer,
|
||||
keyboard_probe,
|
||||
);
|
||||
if startup_probe_complete(&probe, keyboard_probe) {
|
||||
return Ok(probe);
|
||||
}
|
||||
let now = Instant::now();
|
||||
if now >= deadline {
|
||||
if saw_supported {
|
||||
return Ok(Some(true));
|
||||
}
|
||||
return Ok(saw_unsupported_fallback.then_some(false));
|
||||
finish_startup_probe(&mut probe, keyboard_probe, saw_supported_keyboard);
|
||||
return Ok(probe);
|
||||
}
|
||||
if !tty.poll_readable(deadline.saturating_duration_since(now))? {
|
||||
if saw_supported {
|
||||
return Ok(Some(true));
|
||||
}
|
||||
return Ok(saw_unsupported_fallback.then_some(false));
|
||||
finish_startup_probe(&mut probe, keyboard_probe, saw_supported_keyboard);
|
||||
return Ok(probe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_startup_probe(
|
||||
probe: &mut StartupProbe,
|
||||
saw_supported_keyboard: &mut bool,
|
||||
buffer: &[u8],
|
||||
keyboard_probe: StartupKeyboardEnhancementProbe,
|
||||
) {
|
||||
if probe.cursor_position.is_none() {
|
||||
probe.cursor_position = parse_cursor_position(buffer);
|
||||
}
|
||||
if probe.default_colors.is_none() {
|
||||
probe.default_colors = parse_default_colors(buffer);
|
||||
}
|
||||
if keyboard_probe == StartupKeyboardEnhancementProbe::Skip
|
||||
|| probe.keyboard_enhancement_supported.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
match parse_keyboard_enhancement_support(buffer) {
|
||||
KeyboardProbeState::SupportedAndFallback => {
|
||||
probe.keyboard_enhancement_supported = Some(true);
|
||||
}
|
||||
KeyboardProbeState::Supported => {
|
||||
*saw_supported_keyboard = true;
|
||||
}
|
||||
KeyboardProbeState::UnsupportedFallback => {
|
||||
probe.keyboard_enhancement_supported = Some(false);
|
||||
}
|
||||
KeyboardProbeState::Pending => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn startup_probe_complete(
|
||||
probe: &StartupProbe,
|
||||
keyboard_probe: StartupKeyboardEnhancementProbe,
|
||||
) -> bool {
|
||||
probe.cursor_position.is_some()
|
||||
&& probe.default_colors.is_some()
|
||||
&& (keyboard_probe == StartupKeyboardEnhancementProbe::Skip
|
||||
|| probe.keyboard_enhancement_supported.is_some())
|
||||
}
|
||||
|
||||
fn finish_startup_probe(
|
||||
probe: &mut StartupProbe,
|
||||
keyboard_probe: StartupKeyboardEnhancementProbe,
|
||||
saw_supported_keyboard: bool,
|
||||
) {
|
||||
if keyboard_probe == StartupKeyboardEnhancementProbe::Query
|
||||
&& probe.keyboard_enhancement_supported.is_none()
|
||||
{
|
||||
probe.keyboard_enhancement_supported = saw_supported_keyboard.then_some(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cursor_position(buffer: &[u8]) -> Option<Position> {
|
||||
for start in find_all_subslices(buffer, b"\x1B[") {
|
||||
let rest = &buffer[start + 2..];
|
||||
@@ -382,11 +445,12 @@ mod imp {
|
||||
|
||||
/// Parser state for the keyboard enhancement probe.
|
||||
///
|
||||
/// `UnsupportedFallback` records that a primary-device-attributes response arrived, but the
|
||||
/// caller should keep waiting until the deadline because a later keyboard-flags response is
|
||||
/// more specific. `Supported` records that keyboard flags arrived, but the caller should still
|
||||
/// drain the PDA fallback response if it arrives before the deadline so those bytes do not leak
|
||||
/// into the normal event stream.
|
||||
/// `UnsupportedFallback` records that a primary-device-attributes response arrived without
|
||||
/// keyboard flags. Startup treats that as unsupported immediately, matching crossterm's
|
||||
/// previous behavior and avoiding a fixed delay in terminals without the keyboard protocol.
|
||||
/// `Supported` records that keyboard flags arrived, but the caller should still drain the PDA
|
||||
/// fallback response if it arrives before the deadline so those bytes do not leak into the
|
||||
/// normal event stream.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
enum KeyboardProbeState {
|
||||
Pending,
|
||||
@@ -556,6 +620,38 @@ mod imp {
|
||||
KeyboardProbeState::Pending
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_probe_parses_batched_terminal_responses() {
|
||||
let mut probe = StartupProbe {
|
||||
cursor_position: None,
|
||||
default_colors: None,
|
||||
keyboard_enhancement_supported: None,
|
||||
};
|
||||
let mut saw_supported_keyboard = false;
|
||||
update_startup_probe(
|
||||
&mut probe,
|
||||
&mut saw_supported_keyboard,
|
||||
b"\x1B[20;10R\x1B]11;rgb:1111/1111/1111\x07\x1B[?64;1;2c\x1B]10;rgb:eeee/eeee/eeee\x1B\\\x1B[?7u",
|
||||
StartupKeyboardEnhancementProbe::Query,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
probe,
|
||||
StartupProbe {
|
||||
cursor_position: Some(Position { x: 9, y: 19 }),
|
||||
default_colors: Some(DefaultColors {
|
||||
fg: (238, 238, 238),
|
||||
bg: (17, 17, 17),
|
||||
}),
|
||||
keyboard_enhancement_supported: Some(true),
|
||||
}
|
||||
);
|
||||
assert!(startup_probe_complete(
|
||||
&probe,
|
||||
StartupKeyboardEnhancementProbe::Query
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+61
-23
@@ -63,6 +63,11 @@ pub(crate) const TARGET_FRAME_INTERVAL: Duration = frame_rate_limiter::MIN_FRAME
|
||||
/// A type alias for the terminal type used in this application
|
||||
pub type Terminal = CustomTerminal<CrosstermBackend<Stdout>>;
|
||||
|
||||
pub(crate) struct InitializedTerminal {
|
||||
pub(crate) terminal: Terminal,
|
||||
pub(crate) enhanced_keys_supported: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn running_in_vscode_terminal() -> bool {
|
||||
keyboard_modes::running_in_vscode_terminal()
|
||||
}
|
||||
@@ -334,7 +339,7 @@ fn flush_terminal_input_buffer() {
|
||||
pub(crate) fn flush_terminal_input_buffer() {}
|
||||
|
||||
/// Initialize the terminal (inline viewport; history stays in normal scrollback)
|
||||
pub fn init() -> Result<Terminal> {
|
||||
pub(crate) fn init() -> Result<InitializedTerminal> {
|
||||
if !stdin().is_terminal() {
|
||||
return Err(std::io::Error::other("stdin is not a terminal"));
|
||||
}
|
||||
@@ -351,20 +356,57 @@ pub fn init() -> Result<Terminal> {
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
|
||||
#[cfg(unix)]
|
||||
let cursor_pos =
|
||||
match crate::terminal_probe::cursor_position(crate::terminal_probe::DEFAULT_TIMEOUT) {
|
||||
Ok(Some(pos)) => pos,
|
||||
Ok(None) => {
|
||||
tracing::warn!("initial cursor position probe timed out; defaulting to origin");
|
||||
Position { x: 0, y: 0 }
|
||||
let startup_probe = {
|
||||
use crate::terminal_probe::StartupKeyboardEnhancementProbe;
|
||||
|
||||
let started_at = std::time::Instant::now();
|
||||
let keyboard_probe = if keyboard_modes::keyboard_enhancement_disabled() {
|
||||
StartupKeyboardEnhancementProbe::Skip
|
||||
} else {
|
||||
StartupKeyboardEnhancementProbe::Query
|
||||
};
|
||||
match crate::terminal_probe::startup(crate::terminal_probe::DEFAULT_TIMEOUT, keyboard_probe)
|
||||
{
|
||||
Ok(probe) => {
|
||||
tracing::info!(
|
||||
duration_ms = %started_at.elapsed().as_millis(),
|
||||
cursor_position = probe.cursor_position.is_some(),
|
||||
default_colors = probe.default_colors.is_some(),
|
||||
keyboard_enhancement_supported = ?probe.keyboard_enhancement_supported,
|
||||
"terminal startup probes completed"
|
||||
);
|
||||
probe
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"failed to read initial cursor position; defaulting to origin: {err}"
|
||||
duration_ms = %started_at.elapsed().as_millis(),
|
||||
"terminal startup probes failed: {err}"
|
||||
);
|
||||
Position { x: 0, y: 0 }
|
||||
crate::terminal_probe::StartupProbe {
|
||||
cursor_position: None,
|
||||
default_colors: None,
|
||||
keyboard_enhancement_supported: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
crate::terminal_palette::set_default_colors_from_startup_probe(startup_probe.default_colors);
|
||||
|
||||
#[cfg(unix)]
|
||||
let cursor_pos = match startup_probe.cursor_position {
|
||||
Some(pos) => pos,
|
||||
None => {
|
||||
tracing::warn!("initial cursor position probe timed out; defaulting to origin");
|
||||
Position { x: 0, y: 0 }
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let enhanced_keys_supported = startup_probe
|
||||
.keyboard_enhancement_supported
|
||||
.unwrap_or(/*default*/ false);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let mut backend = CrosstermBackend::new(stdout());
|
||||
@@ -372,8 +414,15 @@ pub fn init() -> Result<Terminal> {
|
||||
#[cfg(not(unix))]
|
||||
let cursor_pos = cursor_position_with_crossterm(&mut backend);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let enhanced_keys_supported =
|
||||
!keyboard_modes::keyboard_enhancement_disabled() && detect_keyboard_enhancement_supported();
|
||||
|
||||
let tui = CustomTerminal::with_options_and_cursor_position(backend, cursor_pos)?;
|
||||
Ok(tui)
|
||||
Ok(InitializedTerminal {
|
||||
terminal: tui,
|
||||
enhanced_keys_supported,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
@@ -384,13 +433,6 @@ fn cursor_position_with_crossterm(backend: &mut CrosstermBackend<Stdout>) -> Pos
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn detect_keyboard_enhancement_supported() -> bool {
|
||||
crate::terminal_probe::keyboard_enhancement_supported(crate::terminal_probe::DEFAULT_TIMEOUT)
|
||||
.unwrap_or(/*default*/ None)
|
||||
.unwrap_or(/*default*/ false)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn detect_keyboard_enhancement_supported() -> bool {
|
||||
// Non-Unix startup keeps the existing crossterm path because the bounded probe implementation
|
||||
@@ -461,14 +503,10 @@ where
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
pub fn new(terminal: Terminal) -> Self {
|
||||
pub fn new(terminal: Terminal, enhanced_keys_supported: bool) -> Self {
|
||||
let (draw_tx, _) = broadcast::channel(1);
|
||||
let frame_requester = FrameRequester::new(draw_tx.clone());
|
||||
|
||||
// Detect keyboard enhancement support before any EventStream is created so the
|
||||
// crossterm poller can acquire its lock without contention.
|
||||
let enhanced_keys_supported = !keyboard_modes::keyboard_enhancement_disabled()
|
||||
&& detect_keyboard_enhancement_supported();
|
||||
// Cache this to avoid contention with the event reader.
|
||||
supports_color::on_cached(supports_color::Stream::Stdout);
|
||||
let _ = crate::terminal_palette::default_colors();
|
||||
|
||||
Reference in New Issue
Block a user