Skip to content

Read line-by-line without the line editor when stdin or stdout is not a tty - #924

Open
tompng wants to merge 1 commit into
ruby:masterfrom
tompng:non_tty_gets_transcript
Open

Read line-by-line without the line editor when stdin or stdout is not a tty#924
tompng wants to merge 1 commit into
ruby:masterfrom
tompng:non_tty_gets_transcript

Conversation

@tompng

@tompng tompng commented Sep 2, 2026

Copy link
Copy Markdown
Member

Fixes #886

When stdin or stdout is not a tty, read line-by-line with gets instead of running the key-by-key line editor, and echo the prompt and input back as a plain, escape-sequence-free transcript (like GNU Readline).

Line editing is a feedback loop of input and display; with either side redirected the loop is broken: escape sequences leak into the output, invisible history recall or completion can submit unseen content, and raw mode disables even the terminal driver's own echo.

$ echo input | ruby -rreline -e 'p Reline.readline(%{> })'
> input
"input"
  • The line editor runs only when both input and output are ttys (same as libedit). To edit interactively with stdout redirected, set Reline.output = $stderr, as bash does with rl_outstream (ANSI gate only).
  • Editing keys in piped input are not interpreted (libedit-style raw reads; GNU and libedit already disagree here). Control characters are echoed in caret notation (^A) so they cannot corrupt the output.
  • The prompt and input are echoed in the same situations GNU Readline echoes, so redirected output stays a self-contained transcript. The prompt line is closed with a newline even on EOF.
  • Multiline reads honor confirm_multiline_termination and prompt_proc, so irb transcripts keep their per-line dynamic prompts.
  • Reline::Dumb (TERM=dumb) is unchanged and keeps the line editor path.

… a tty

Key-by-key line editing is a closed feedback loop of input and display;
when either side is not a terminal the loop is broken, and an editor the
user cannot see is worse than useless (invisible history recall or
completion submits unseen content, and raw mode disables even the
terminal driver's own echo). Instead, read with gets and echo the prompt
and the input back to the output as a plain, escape-sequence-free
transcript. This makes piped usage behave like GNU Readline for
printable input: `echo input | ruby -rreadline -e '...'` now produces
"> input" with no escape sequences.

Behavior details:

- The line editor is used only when both input and output are ttys
  (libedit does the same). For interactive editing with stdout
  redirected, point the render target at a tty with
  `Reline.output = $stderr`, as bash does with rl_outstream (ANSI only;
  the Windows gate is bound to the console's stdout).
- Editing keys in piped input are no longer interpreted (libedit-style
  raw reads). GNU Readline interprets them, but GNU and libedit already
  disagree here, so exact readline-ext compatibility is not a
  well-defined target; control characters in input are the uncommon
  case. They are echoed in caret notation (^A) so they cannot corrupt
  the output.
- The prompt and the input are echoed even though frontends with local
  echo will then show the input twice; GNU Readline echoes in exactly
  the same situations, so this introduces no new doubling, and it keeps
  redirected output a self-contained transcript. The prompt line is
  closed with a newline even on EOF.
- Multiline reads honor confirm_multiline_termination and prompt_proc,
  so irb transcripts keep their per-line prompts and indentation.
- Reline::Dumb is unchanged: TERM=dumb and test-mode usage keep the
  line editor path (IO#both_tty? defaults to true).

Fixes ruby#886

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reline emits extra escape sequences when stdin/stdout/stderr are not TTY

1 participant