Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save edhowland/43ec23b84d257e3ca8e3b42131b6ba34 to your computer and use it in GitHub Desktop.
Save edhowland/43ec23b84d257e3ca8e3b42131b6ba34 to your computer and use it in GitHub Desktop.
Ruby Reline module leaves Terminal in bad state after the last thing was a print without a newline affecting MacOS VoiceOver screenreader users

If using a screenreader like VoiceOver for the Mac and you use Reline.readmultiline , for example in a loop , say in a REPL, and you output a string without terminating the line with a newline, funny things happen to your screenreader. Like, it no longer outputs the characters you type.

This can be easily demonstrated using Docker and 2 images: ruby:2.6 and ruby:3.2 In ruby:2.6 and before, the same program 'irb', Ruby's own REPL does not experience this problem. But in Ruby 2.7 and above, irb was changed to use the new Reline module instead of the venerable Readline, a wrapper around the venerable GNU readline library.

This probably does not seem noticable to a sighted user, but for VoiceOver users, it does something nasty to the Terminal (or to VoiceOver itself).

The following screen captures might look exactly the same to you, but trust me they are radically different. in audio output of the VoiceOver screen reader.

docker run --rm -it ruby:2.6 bash
root@0ae8700a82b6:/# irb
irb(main):001:0> print('foo')
foo=> nil
irb(main):002:0> 

root@0ae8700a82b6:/# 
exit
$# fff
$ # The above "# fff" echos each character normally  
$  docker run --rm -it ruby:3.2 bash
root@c6c4f0bf7564:/# irb
irb(main):001:0> print('foo')
foo=> nil
irb(main):002:0> 
root@c6c4f0bf7564:/#                             
exit                                             
$  # fff                          

Note: that I typed 'fff' after pressing Control+d to exit the Docker running ruby:3.2 container. All I heard was: space space space

One way to fix this is by typing 'reset' in the terminal. This seems to clear this up.

My next step in the investigation is too run 'strace' and dump the calls to write on the FD #1 file descriptor. Then, diff the output from both Docker sessions. I want to have someone else attempt this and see if the same or similar results occur to them.

Because 'reset' clears up the problem, I can try and D/L another Terminal emulator and run the test again.

You should be on a Mac running MacOS Catalina or later. Press/toggle VoiceOver by holding down the command key and pressing F5. You should have Docker installed, or ssh into a machine that does. (That is how I tested it)

For completeness, I ran the test on a Debian version Bullseye (11, IIRC)

The Docker version is: Docker version 23.0.0, build e92dd87

As far as VoiceOver goes: Make sure to have all characters output one at a time and all punctuation symbols output. When finished, re-press Command plus F5 again to turn off VoiceOver

@edhowland
Copy link
Author

Update: Ran strace on both Ruby versions: 2.6.2 and 3.1.2

#!/bin/bash
sudo strace -xx -e trace=write   -p1 -P "$(tty)" irb 2>s.out

Then mv'd s.out to f2.out and f3.out

Then extracted only the hex strings:

``bash
egrep -n -o '"[^"]"' f2.out > f2.str


Also, did for f2.out to f3.str

Then ran a diff on both of these outputs

```bash
diff f2.str f3.str > f2.f3.diff

Then captured the final difference block, which starts around line 695

That is captured here:

695,698c695,699
< 696:"\x1b\x5b\x31\x42"
< 697:"\x1b\x5b\x31\x47"
< 698:"\x1b\x5b\x4b"
< 699:"\x1b\x5b\x31\x47"

696:"\x1b\x5b\x31\x47"
697:"\x1b\x5b\x31\x42"
698:"\x1b\x5b\x31\x47"
699:"\x1b\x5b\x4b"
700:"\x1b\x5b\x31\x47"

Summary:

There is a single ANSI escape sequence that is extra being sent to the terminal
by Ruby's Reline module that is not being sent by the old readline based on GNU readline.

Not sure what this has to do with the messing of the MacOS VoiceOver screenreader and the Terminal.app.
But it does, and the only to resolve is to type 'reset' (which you only
hear "space space space space space space" and have to trust you are typing it correctly.

@edhowland
Copy link
Author

Another update: It does not matter what you type in Irb, just exiting the irb session in 3.1.2 will cause the problem, but it works correctly if in Ruby 2.6.2
It helps to have a Ruby version manager like rbenv (what I use) or RVM to easily switch back and forth between versions.
I am trying to construct a table that matches tput capability commands against their ANSI equivalents.
The man 5 terminfo seems to be a somewhat incomplete list.
I found a reference somewhere that mentions that some terminals have extended capabilities and these have canonical names as well.
Haven't found a list of them yet.

One such capability does a sort of LineFeed+CarriageReturn combo. It moves the cursor down one line, then moves the cursor to the start of the line. I have the ANSI escape sequence for this, but not the matching tput string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment