Skip non-regular files when detecting pasted image paths (#57774)

## Why

Probing a pasted serial device or FIFO path for image dimensions can block the TUI input loop.

## What changed

Only probe regular files for image dimensions, leaving pasted device and FIFO paths as literal text.

## Testing

Add a Unix regression test that pastes a FIFO path and verifies it remains editable with Backspace.

GitOrigin-RevId: c9ae3479f8b14920232e75fe6d2cee86c3300bda
This commit is contained in:
Eric Traut
2026-10-28 16:15:21 +00:00
committed by copyberry
parent 5c0e42161e
commit 1cc3cf8060
2 changed files with 28 additions and 3 deletions
@@ -283,6 +283,7 @@
//! [`ChatComposer::handle_paste`], which first commits buffered typing before classifying the
//! pasted text. This preserves the order when a paste arrives before the next burst-flush tick;
//! buffered keys have already been classified and go directly through `apply_paste`.
//! Image detection probes only regular files. Pasted device and FIFO paths stay literal.
//! - Startup recovery previews held keys with the live Vim Replace policy without consuming them.
//! Buffered pastes remain literal in recovery so large-paste and image classification stays with
//! the live paste path.
@@ -1267,8 +1268,11 @@ impl ChatComposer {
return false;
};
// normalize_pasted_path already handles Windows → WSL path conversion,
// so we can directly try to read the image dimensions.
// Opening a serial device or FIFO can block the input loop.
if !path_buf.is_file() {
return false;
}
match image::image_dimensions(&path_buf) {
Ok((width, height)) => {
tracing::info!("OK: {pasted}");
@@ -1,4 +1,4 @@
//! Raw paste tabs remain draft text until the user explicitly submits or queues it.
//! Composer paste handling for literal paths and raw tabs.
use super::tests::new_test_composer;
use super::*;
@@ -6,6 +6,27 @@ use pretty_assertions::assert_eq;
use ratatui::Terminal;
use ratatui::backend::TestBackend;
#[cfg(unix)]
#[test]
fn pasting_fifo_path_keeps_it_editable() {
let dir = tempfile::tempdir().unwrap();
let name = "tty.usbserial-110";
let status = std::process::Command::new("mkfifo")
.arg(dir.path().join(name))
.status()
.unwrap();
assert!(status.success());
let (mut composer, _rx) = new_test_composer();
let prefix = shlex::try_quote(dir.path().to_str().unwrap()).unwrap();
composer.handle_paste(format!("{prefix}/{name}"));
composer.handle_key_event(KeyEvent::from(KeyCode::Backspace));
insta::assert_snapshot!(
composer.current_text().strip_prefix(prefix.as_ref()).unwrap(),
@"/tty.usbserial-11"
);
}
#[test]
fn paste_burst_tabs_preserve_multiline_draft() {
for running in [false, true] {