On 11 Aug 2005, at 15:44, Bill Atkins wrote:

> How can I use the builtin readline library to complete arguments to
> commands?  I have basic completion working now, so that
>
> %wmi > st [TAB]
>
> will show valid commands starting with "st" (in this case "start" and
> "stop"), but how do I complete arguments specific to that command?
>
> For instance, if the user has already typed "start Te" and then hits
> TAB, it should show completions specific to the start command (in this
> case "TestDevice" and "TerminalServer"), but I'm not clear on how to
> do this.
>
> --  
> Bill Atkins
>
>

Unfortunately the ruby readline bindings are missing some key  
functions to make this easy.  Even raw access to the line buffer  
would make this trivial, but this is not available.

I used the  following code to augment the ruby library 'Cmd' (http:// 
code.vernix.org/cmd) to implement what you are asking for (the  
missing functions in the code below will be found in this library,  
but they just do what their name implies).  The main problem to solve  
is how to gain access to the whole command line and not just the last  
token being completed (this is all readline give you with the Ruby  
API).  The Ruby API doesn't provide this directly but you can change  
the word split character to be an unused character (I use bell  
character).  This gets you the complete line to make your completion  
decision on, but you need to also include the earlier characters on  
the line in the completion list you pass back to readline.  This  
unfortunately causes <tab><tab> to prepend  the whole line on every  
possible  completion in the presented completion list.

You also have to worry about completing on an empty set inserting  
spaces into the line buffer (more cosmetic than anything else if I  
recall).  The space is fixed by turning off the auto space on  
complete in readline and adding this yourself.  I also output the  
bell when necessary.  Still not perfect behaviour as <tab> <tab> on  
an empty set should just beep but not display anything while in this  
case it will display the line so far.

       # Configure readline
       set_completion_proc(:readline_completion_handler)
       Readline.completer_word_break_characters = 7.chr        # bell  
character
       Readline.completion_case_fold = true
       # Our empty completion sets return the original line so  
readline thinks we have sucessfully completed
       # and will append the following character.  We need to stop it  
from doing this.
       Readline.completion_append_character = ''


     def readline_completion_handler(line)
         tokens = line.split
         tokens << "" if line =~ /^\w.* $/ # starting new completion set
         if tokens.length == 0
             command_list
         else
             # Get completion set for token.last.  See if we have a  
valid single command with a completion proc defined.
             completion_proc = nil
             commands = completion_grep(command_list, tokens.first)
             if commands.size == 1
                 cmd = commands.first
                 completion_proc = complete_method(cmd) if  
collect_complete.include?(cmd)
             end
             if tokens.length == 1
                 matches = completion_grep(command_list, tokens.first)
                 matches = matches.map {|m| m + ' '} if  
matches.length == 1
                 matches
             else
                 if completion_proc
                     tokens.shift    # don't need to send the command
                     matches = self.send(completion_proc, tokens)
                     if matches.empty?
                         print "\a"    # beep
                         [line]        # empty completion set so pass  
back original line
                     else
                         # Prepend the command line up to the last  
token we have been completing on to all the match values.
                         line.sub!(Regexp.new("#{tokens.last}$"), "")
                         matches = matches.map {|m| line + m}

                         # If match is complete add space to move  
onto next token
                         matches = matches.map {|m| m + ' '} if  
matches.length == 1
                         matches
                     end
                 else
                     print "\a"    # beep
                     [line]        # empty completion set so pass  
back original line
                 end
             end
         end
     end


Hope this helps,

Dave.