Ruby: "Parasite" characters with gets after pressing arrow key on getch - ruby

I've been looking into a way to implement a "Press any key to continue" feature into my program, and figured I'd use $stdin.getch.
Seems to work in most cases, but I noticed that if I pressed an arrow key when prompted to press any key, the next gets command would have "parasites" characters at the beginning of the string it returned, presumably because arrow keys give several characters as input and only the first one is read by getch, with the rest being saved for the next gets. More precisely, when I press the Right Arrow key, getch returns "\e" and the next gets return has a "[C" prefixed to it.
I thought maybe I had to flush/empty the stdin buffer, but flush, iflush and ioflush don't seem to do anything. Trying to write '' into stdin before the gets doesn't work either. I've also tried sync and fsync on $stdin, but it doesn't work either. Any idea what the problem could be?

getch puts the IO in so-called raw mode and then reads a single character.
However, there are keys that generate more than one character when being pressed. The right arrow key → for example is usually configured to generate the 3-byte sequence \e[C which is the ANSI escape sequence for "cursor forward".
Now, if you attempt to read that sequence via getch, you'll only get \e, i.e. the very first character. All subsequent characters (here: [ and C) will be returned by the next read operation.
Instead of reading a single character via getch, you could put stdin into raw mode manually and read all available characters (up to a reasonable limit) via readpartial, e.g.:
$stdin.raw { |io| io.readpartial(100) }
The above returns the whole "\e[C" sequence when pressing → and behaves like getch for single-character keys.
Note that terminal emulators and operating systems behave differently. The above works just fine for me, but you might want to test the solution on various systems yourself.

Related

What does writing "\r\027[1A\027[K" to stdout do?

I came across some code for chat application in the terminal (in OCaml) and swa this string (in ASCII?) "\r\027[1A\027[K" being printed into the terminal before a new user message is printed to the terminal.
I have tried googling literals one by one, so I know that "\r" stands for cartridge return and \027 for ESC in ASCII, but what does "[1A" and "[K" do? What character encoding is this?
And finally, what is the aggregate effect of this command?
[ introduces a control sequence. A is the control sequence for "cursor up", and [1A moves the cursor up 1 line. K erases a line. So \x1b[1A\x1b[K moves up one line and deletes it (replaces it with spaces).
Of course, that is only valid if the terminal that receives that string recognizes the control sequences. Not all do.
See https://en.wikipedia.org/wiki/ANSI_escape_code
I'm not sure what 027 is trying to do. It seems like an error and should have been 033.

How to continue with <space>

I need a method with which I can proof if the last entered letter is a space while I'm typing something so the program jumps to the next line code.
Normally it works with Enter but I want to continue it with Space.
I tried to use gets.chomp[-1] but you always have to press Enter.
You might have to use getch. See "Get single char from console immediately" and compose your own line input. gets doesn't act on pressing a space but groups the whole line input until Enter is pressed.
The gets documentation says it:
returns (and assigns to $_) the next line ... from standard input.
so if you press the space bar, and you could press backspace and do other things before you press Enter, and Ruby won't act on the space being pressed.
When you call gets, Ruby delegates the line editing to your terminal. With default settings, your terminal buffers the input and provides some basic line-editing: it prints the entered character to the screen and allows you to delete the last caracter via del or an entire word via ^W (depending on your terminal). Once you press enter, the terminal sends the finished buffer back to Ruby.
Ruby isn't aware of any of this. It doesn't see your editing, nor does it see a del control character in the resulting string. The only thing it gets is the composed line as a whole. Likewise, Ruby isn't aware of the separate key presses and can't intercept space on its own.
But fortunately, terminals are very flexible. Almost any setting can be configured, including the end-of-line character. So in order to make space work like enter, we have to adjust the terminal settings!
This can be done using the stty command line tool. In addition, you have to change Ruby's input record separator $/ (which defaults to newline) to space, so gets and chomp behave accordingly.
Here's a quick and dirty example:
begin
settings = `stty -g` # get current terminal settings
system('stty', 'eol', ' ') # make terminal recognize space as end-of-line
$/ = ' ' # set Ruby's input record separator to space
puts 'hit ^D to exit'
while input = gets(chomp: true)
p input: input
end
ensure
system('stty', settings) # revert terminal settings
end
Running the above code in a terminal and entering foo bar baz (with trailing space) gives:
hit ^D to exit
foo {:input=>"foo"}
bar {:input=>"bar"}
baz {:input=>"baz"}
Another option is to put your terminal into raw mode, either by calling stty or via require 'io/console' (see the docs). This disables all line editing features (including printing the entered characters) and passes the input directly to Ruby. It gives you even more control over the input but obviously needs much more work to get the line editing features we're accustomed to.
The documentation for termios provides more information.

Are CTRL-based key-bindings like `C-X` or `C-n` always case-insensitive? [duplicate]

I am working with Cygwin/Mintty/Vim.
With <C-v> I see that <C-S-c> is encoded <83>. This mean vim can read it and I can map it using the map command.
Unfortunately if I try:
:inoremap <C-S-c> foobar
it doesn't work...
How can I make it work and why vim refuses to map Unicode keystokes?
Same question for <C-S-F1>. If I execute this command:
:inoremap <C-S-F1> foobar
I will get something like this:
[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~[20;5~
Where F1 is [1;5P and F9 is [20;5~
Vim apparently does not support exotic key combinations involving Control (<C-…>). That includes F1 – though your other attempted mapping ShiftC is supported (and is equivalent to Control with just C). From a post by Tony Mechelynck on a forum thread “Mapping ctrl-; (ctrl semicolon)”:
The only printable keys which can reliably be used with Ctrl, with
predictable results, in cooked mode on any OS, are those defined by
ASCII, and that means the following AND NO OTHERS:
ASCII characters 0x40 to 0x5F (i.e. uppercase A to Z, plus the six
nonalpha characters at-sign, left-bracket, slash, right-bracket,
[caret] and underscore), where Ctrl subtracts 0x40, thus mapping them
to 0x00 to 0x1F respectively. This explains why Ctrl-[ means Esc, Ctrl-I
means Tab, Ctrl-M means Enter, etc.
Lowercase letters, whose Ctrl counterpart is the same as for the
corresponding uppercase (thus Ctrl+letter and Ctrl+Shift+letter are
always the same for a given letter)
In addition, Ctrl-? is mapped to 0x7F (DEL).
Vim may or may not see other Ctrl-combinations, but that depends on the
terminal, and in most cases it won't see them, or confuse them with
something else such as the same key without Ctrl.
As for your <C-S-c> mapping, I’m not totally sure why you can see <C-S-c> with <C-v> but you can’t map it. When I test in MacVim (a GUI), I get the character ^C when I try <C-v><C-S-c>, and :inoremap <C-S-c> foobar works. When I test in OS X’s Terminal, the Terminal swallows the keystroke and beeps, with both <C-v> and :inoremap. In both cases, <C-v>’s behavior is consistent with :inoremap’s, so I don’t know why you are seeing the discrepancy.
Due to the way that the keyboard input is handled internally, this unfortunately isn't generally possible today, even in GVIM. Some key combinations, like Ctrl + non-alphabetic cannot be mapped, and Ctrl + letter vs. Ctrl + Shift + letter cannot be distinguished. (Unless your terminal sends a distinct termcap code for it, which most don't.) In insert or command-line mode, try typing the key combination. If nothing happens / is inserted, you cannot use that key combination. This also applies to <Tab> / <C-I>, <CR> / <C-M> / <Esc> / <C-[> etc. (Only exception is <BS> / <C-H>.) This is a known pain point, and the subject of various discussions on vim_dev and the #vim IRC channel.
Some people (foremost Paul LeoNerd Evans) want to fix that (even for console Vim in terminals that support this), and have floated various proposals, cp. http://groups.google.com/group/vim_dev/browse_thread/thread/626e83fa4588b32a/bfbcb22f37a8a1f8
But as of today, no patches or volunteers have yet come forward, though many have expressed a desire to have this in a future Vim 8 major release.

Where can I find a list of terminal ANSI codes sent by Ctrl-key sequences?

I am writing some behavioural tests for code that interacts with a terminal and I need to assert behaviour on the sequence C-p C-q (ctrl-p ctrl-q). In order to do this, I need to write the raw characters to the PTY. I have a small mapping at the moment for things like C-d => 0x04, C-h => 0x08.
Is there somewhere I can get a basic mapping of human readable control sequences, mapped to raw byte sequences for xterm?
Take the ASCII value of the character (e.g., for ^H, take 72), and subtract 64. Thus, ^H is 8.
This works for any control character. Using it, you can discover that, for example, ^# is the NUL character and ^[ is ESC.

Forward delete character?

To dynamically delete a character from a string, you can use the /b character.
puts "Hello\b World!" #=> Hell World!
\b basically does the same thing as a backspace. Is there a character that emulates a forward delete?
In the execution of:
puts "Hello\b World!"
The \b doesn't delete the prior character. This is a common misconception since a backspace used on a keyboard will delete the previously typed character prior to the cursor on screen and from the keyboard input buffer. That behavior occurs because of how the keyboard input software of the operating system is designed.
In the case of the above puts, the o still exists in the string. What happens is that, when displayed, the backspace causes the o to be overwritten by the following space. This occurs because the o is display first, followed by the backspace (output cursor is backed up one character position), followed by the space, in sequence.
If you could have such a case where:
puts "Hello<del> World!"
would display HelloWorld!, then that would mean the output of the value of <del> would somehow cause the following output of space () to not occur. In other words, the <del> would have the function of, "whatever the next charter is that comes for the output, skip it". I don't believe such a control character exists in Windows or Linux output, although I suppose it would be possible to write an output driver that would have that behavior for some defined control character.
You might even be able to do something like this:
"Hello W<left-arrow><left-arrow><del><right-arrow>orld!"
Which would display HelloWorld if your terminal is set up to accept control characters that move the cursor left or right. But it still obviously isn't the same functionality as the "delete in the future" case.

Resources