From 449be3afce03c32ca6d2b633629e2d4ef6472527 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 22 Apr 2026 12:42:18 +0200 Subject: [PATCH] Fix reading redirected input from stdin --- crates/edit/src/bin/edit/main.rs | 31 ++++++++++------- crates/edit/src/sys/unix.rs | 22 +++++------- crates/edit/src/sys/windows.rs | 60 +++++++++++++++++--------------- 3 files changed, 60 insertions(+), 53 deletions(-) diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs index 30ad149b20b..18f70eeacbc 100644 --- a/crates/edit/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -75,12 +75,13 @@ fn run() -> apperr::Result<()> { return Ok(()); } + handle_stdin(&mut state)?; + if let Err(err) = Settings::reload() { state.add_error(err); } - // This will reopen stdin if it's redirected (which may fail) and switch - // the terminal to raw mode which prevents the user from pressing Ctrl+C. + // Switch the terminal to raw mode which prevents the user from pressing Ctrl+C. // `handle_args` may want to print a help message (must not fail), // and reads files (may hang; should be cancelable with Ctrl+C). // As such, we call this after `handle_args`. @@ -276,16 +277,6 @@ fn handle_args(state: &mut State) -> apperr::Result { state.documents.add_file_path(p)?; } - if let Some(mut file) = sys::open_stdin_if_redirected() { - let doc = state.documents.add_untitled()?; - let mut tb = doc.buffer.borrow_mut(); - tb.read_file(&mut file, None)?; - tb.mark_as_dirty(); - } else if paths.is_empty() { - // No files were passed, and stdin is not redirected. - state.documents.add_untitled()?; - } - if dir.is_none() && let Some(parent) = paths.last().and_then(|p| p.parent()) { @@ -296,6 +287,22 @@ fn handle_args(state: &mut State) -> apperr::Result { Ok(false) } +// Read any redirected (piped) stdin into a new document. +// This doubles as a stdin handle validation. We do this after `handle_args` +// (may exit early) and before `switch_modes` (needs a console stdin). +fn handle_stdin(state: &mut State) -> apperr::Result<()> { + if let Some(mut file) = sys::reopen_stdin_if_redirected()? { + let doc = state.documents.add_untitled()?; + let mut tb = doc.buffer.borrow_mut(); + tb.read_file(&mut file, None)?; + tb.mark_as_dirty(); + } else if state.documents.len() == 0 { + // No files were passed, and stdin is not redirected. + state.documents.add_untitled()?; + } + Ok(()) +} + fn print_help() { sys::write_stdout(concat!( "Usage: edit [OPTIONS] [FILE[:LINE[:COLUMN]]]\n", diff --git a/crates/edit/src/sys/unix.rs b/crates/edit/src/sys/unix.rs index be4cc1cbdad..34683ac3fc9 100644 --- a/crates/edit/src/sys/unix.rs +++ b/crates/edit/src/sys/unix.rs @@ -51,13 +51,20 @@ pub fn init() -> Deinit { Deinit } -pub fn switch_modes() -> io::Result<()> { +/// Reopen stdin if it's redirected (= piped input). +pub fn reopen_stdin_if_redirected() -> io::Result> { unsafe { - // Reopen stdin if it's redirected (= piped input). if libc::isatty(STATE.stdin) == 0 { STATE.stdin = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_RDONLY))?; + Ok(Some(File::from_raw_fd(libc::STDIN_FILENO))) + } else { + Ok(None) } + } +} +pub fn switch_modes() -> io::Result<()> { + unsafe { // Store the stdin flags so we can more easily toggle `O_NONBLOCK` later on. STATE.stdin_flags = check_int_return(libc::fcntl(STATE.stdin, libc::F_GETFL))?; @@ -333,17 +340,6 @@ fn set_tty_nonblocking(nonblock: bool) { } } -pub fn open_stdin_if_redirected() -> Option { - unsafe { - // Did we reopen stdin during `init()`? - if STATE.stdin != libc::STDIN_FILENO { - Some(File::from_raw_fd(libc::STDIN_FILENO)) - } else { - None - } - } -} - #[derive(Clone, PartialEq, Eq)] pub struct FileId { st_dev: libc::dev_t, diff --git a/crates/edit/src/sys/windows.rs b/crates/edit/src/sys/windows.rs index f2536496063..f4a36e82eb7 100644 --- a/crates/edit/src/sys/windows.rs +++ b/crates/edit/src/sys/windows.rs @@ -115,6 +115,38 @@ pub fn init() -> Deinit { } } +/// Reopen stdin if it's redirected (= piped input). +pub fn reopen_stdin_if_redirected() -> io::Result> { + unsafe { + let stdin = STATE.stdin; + + if stdin != Foundation::INVALID_HANDLE_VALUE + && FileSystem::GetFileType(stdin) == FileSystem::FILE_TYPE_CHAR + { + return Ok(None); // stdin refers to a TTY + } + + STATE.stdin = FileSystem::CreateFileW( + w!("CONIN$"), + Foundation::GENERIC_READ | Foundation::GENERIC_WRITE, + FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE, + null_mut(), + FileSystem::OPEN_EXISTING, + 0, + null_mut(), + ); + if STATE.stdin == Foundation::INVALID_HANDLE_VALUE { + return Err(last_os_error()); + } + + if stdin != Foundation::INVALID_HANDLE_VALUE { + Ok(Some(File::from_raw_handle(stdin))) + } else { + Ok(None) + } + } +} + /// Switches the terminal into raw mode, etc. pub fn switch_modes() -> io::Result<()> { unsafe { @@ -138,20 +170,6 @@ pub fn switch_modes() -> io::Result<()> { }, }; - // Reopen stdin if it's redirected (= piped input). - if ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE) - || !matches!(FileSystem::GetFileType(STATE.stdin), FileSystem::FILE_TYPE_CHAR) - { - STATE.stdin = FileSystem::CreateFileW( - w!("CONIN$"), - Foundation::GENERIC_READ | Foundation::GENERIC_WRITE, - FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE, - null_mut(), - FileSystem::OPEN_EXISTING, - 0, - null_mut(), - ); - } if ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE) || ptr::eq(STATE.stdout, Foundation::INVALID_HANDLE_VALUE) { @@ -414,20 +432,6 @@ pub fn write_stdout(text: &str) { } } -/// Check if the stdin handle is redirected to a file, etc. -/// -/// # Returns -/// -/// * `Some(file)` if stdin is redirected. -/// * Otherwise, `None`. -pub fn open_stdin_if_redirected() -> Option { - unsafe { - let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE); - // Did we reopen stdin during `init()`? - if !std::ptr::eq(STATE.stdin, handle) { Some(File::from_raw_handle(handle)) } else { None } - } -} - pub fn drives() -> impl Iterator { unsafe { let mut mask = FileSystem::GetLogicalDrives();