Search code examples
rubywinapiconsoleterminalwindows-console

How to get/set the console cursor position in Ruby (Windows)


I'm trying to write a shell in Ruby, and to implement tab completion I am using the WinAPI function getch to read in a character at a time from the user, checking for tabs.

The problem with this is the backspace key:

  1. It moves the cursor further back than the prompt (eg, with prompt hello>, the user can backspace the cursor on to the h. I would like it to stop at the final space.
  2. When the user's text overflows on to the next row of the console, backspace will not move back up to the previous line.

(I know both of these behaviours are by design.)

My imagined solution to these problems involve controlling the cursor movement; I need to know where the cursor is, and be able to move it.

On Linux, I would use ANSI escape sequences, but these aren't supported by the Windows console.

I have looked into the WinAPI and tried to find functions that would let me do this, but all I could find was GetConsoleCursorInfo function, which only returns the size and visibility of the cursor.

Examples would be appreciated, as I am hopeless at using the Win32API class for anything other than primitive functions.

Thanks.


Solution

  • You're probably better off using readline. It is included in the Ruby One-Click installer. A basic setup is:

    require 'readline'
    
    while line = Readline.readline('hello> ', true)
      #do something with line
      break if line == 'quit'
    end
    

    Already you'll have standard readline capabilities such as backspacing, Alt+backspace to delete a word, history, and tab completion. There's good documentation on how to customize it for your needs here.


    Edit:

    If you don't have readline installed, you can get it and other external libraries here. You'll want the readline-4.3-2-mswin32 package. Copy the readline.dll file (located in the bin directory) to your ruby\bin directory. That should do it.

    Although it isn't documented on the Ruby homepage, it looks as if you can also use readline 5, available here. Specifically, you need the binaries distribution. Copy readline5.dll (in the bin directory) to your ruby\bin directory, and rename it to readline.dll.

    Also, as a side note, don't be alarmed if require 'readline' returns false when using irb, since it appears to pre-load it.