views:

618

answers:

3

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.

+1  A: 

Hmm, it's certainly possible to back up, and with reasonably portable code, as bash(1) can back up to the previous line even in a dos box. I imagine it is using termcap or ncurses, and it has in the termcap database a set of control codes that work for the dos box.

In Ruby, I don't believe there are any termcap bindings, so you use ncurses, rather than hardwiring into your program a set of device-dependent control codes. (You would want ncurses over termcap anyway.)

Once you switch to ncurses I believe you will find API elements to do everything you need, including backing up lines and not overwriting the prompt. (And certainly you should not back up over anything that you didn't output to start with, no matter what library is in use.)

Actually, I kind of like Pesto's answer. Use ncurses if readline doesn't work out or if you need cursor addressing for some other reason.

DigitalRoss
+2  A: 

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.

Pesto
OK, I'll try that; I had heard of weirdness on Windows, so I'll keep you posted.
Lucas Jones
Is there any way I can get readline without reinstalling Ruby? I don't seem to have it installed, and (going for the low-hanging fruit) `gem install readline` didn't work.
Lucas Jones
I revised the answer to address this.
Pesto
Thanks. (15 chars)
Lucas Jones
+1  A: 

For a windows-friendly readline implementation try this ruby-based readline