tabtab

DESCRIPTION:

Create and install double-tab (‘tab tab’) auto-completions for any command-line application on any shell (bash, fish, ksh, etc).

When you use the command-line, you can double-tab to auto-complete the name of a command-line application or a target file or folder. Its possible to provide your own completions for applications: git comes with bash shell completions, and the fish shell includes a library of completions for many applications.

Quick Start/Trial Me:

The tabtab gem comes with some pre-defined completions for some popular applications that benefit from completions: rails, newgem, cucumber, github (and its alias gh). It takes 2 minutes to trial this project and see if you like it:

bash>
sudo gem install tabtab
install_tabtab
source ~/.tabtab.bash

rails -d TABTAB

sudo gem install defunkt-github -s http://gems.github.com
cd project/hosted/on/github/with/contributors/like/rails/or/rspec

github TABTAB
gh netTAB feTAB
gh netTAB web TABTAB

It just works. Flags. Commands. Intelligent values. Aliases. Ooh yeah.

Now, add ‘source ~/.tabtab.bash’ to your .bash_profile so you have this awesomeness in all your terminal shells.

Read on to learn how to write your own auto-completions with only a few lines of Ruby…

FEATURES/PROBLEMS:

  • Completion defintions/recipes are shell agnostic (bash, fish, ksh, etc)

  • Definitions are written in Ruby

  • Can be bundled within RubyGems, explicitly referenced in local files, or automatically generated from -h help output from existing applications.

  • Very easy to use: ‘install_tabtab’ to find available completions, then ‘source ~/.tabtab.bash’

SAMPLE DEFINITION:

TabTab definitions for auto-completions are very easy to write. Initially you can store them in a normal Ruby file (say ~/.tabtab/myapp.rb) and later, if the application is a Ruby application and distributed as a RubyGem you can bundle it with the distribution.

Flags

A sample TabTab definition for the script/server command that is found within all Ruby on Rails applications:

TabTab::Definition.register('script/server') do |c|
  c.flag :debugger, :d

  c.flag :environment, :e do
    Dir['config/environments/*.rb'].map { |env| env.gsub(/^.*environments\//,'').gsub(/.rb$/,'') }
  end

  # script/server -p TABTAB -> generated first available port 3000, 3001, 3002
  c.flag :port, :p do
    port = 3000
    until `netstat -an | grep "^tcp" | grep #{port}`.strip.empty? || port > 3010
      port += 1
    end
    port > 3010 ? [] : [port.to_s]
  end
end

This definition defines 3 flags (each with short and long names): –debugger, –environment, and –port. The –debugger flag is a simple autocompletion. At the command-line, if you typed “script/server –d” and pressed double-tab it would instantly complete to “script/server –debugger ”.

The other two flags can take values (e.g. “–environment development” or “–port 3000”) and their definitions are more powerful. If you double-tab after “script/server –environment ” you will be presented with options [development, test, production] for completion. If you type the first letter, it will complete to the full value.

Similarly for “–port”. The algorithm above will find the first available port number from 3000+. As it only returns a single value in its result array, this value is automatically displayed on the command line. Very tricky indeed.

The #flag method (and its alias #flags) takes 1+ symbols describing the flag names. Its last argument can be a string as a description. This is not used for bash shell users, but is available to ksh/fish users who’s autocompletion systems are capable of displaying them inline with the completion options.

The #flag method can also take a block. The result of the block must be an array of strings. These blocks (also available to #command and #default methods below) are called ‘value blocks’. They return the complete, or a useful subset, of available values for the flag or command.

Commands

Many command-line apps take a command as their first argument. For example, the github CLI has commands such as: info, pull, and network. The latter even has sub-commands. Subsequently, you might run the following at the command line:

github info
github pull drnic
github network commits
github network web drnic

The following sample of the tabtab definition for the github command shows how tabtab can provide autocompletions for every example above, including the user values for the ‘github pull’ and ‘github network web’ commands.

TabTab::Definition.register('github') do |c|
  def users
    `github info | grep "^ -" | sed -e "s/ - //" | sed -e "s/ .*$//"`.split("\n")
  end

  c.command :info, "Show info"

  c.command :pull, "Pull from a remote." do |pull|
    pull.default { users }
    pull.flag :merge
  end

  c.command :network, "Project network tools" do |network|
    network.command(:web) { users }
    network.command :commits
  end
end

The #command method requires a symbol for the name, and can take a string for the command description (see Flags section above).

The #command method can also take a value block, like #flag above. It must return an array of strings.

The above example shows the behaviour of the ‘github network web’ command being abstracted into a separate method. Similarly, this method could be defined in an external Module, and included as necessary.

Note that ‘pull.flag :merge’ defines that ‘github pull’ can complete to ‘github pull –merge’ as well as the list of values returned from the #default value block.

Default value blocks

In the sample github definition above, the ‘c.command :pull’ value block is not the immediate block passed to the #command method. Instead the value block is defined via ‘pull.default { }’ This is an alternate syntax to the earlier value block, and is used where your command can autocomplete to various flags, sub-commands or other from within a generated list of values (via the #default value block).

Value block syntax

Value blocks are normal Ruby blocks that return an array of Strings.

The following syntax options for the ‘run’ command are functionally equivalent:

TabTab::Definition.register('myapp') do |c|
  c.command :run do
    %w[things to run]
  end

  c.command :run, "A description" do
    %w[things to run]
  end

  c.command :run do |run|
    run.default do
      %w[things to run]
    end
  end

  c.command(:run, "A description") { %w[things to run] }

  def things_to_run
    $[things to run]
  end
  c.command(:run) { things_to_run }
end

Application name aliases

I don’t ever type ‘script/generate’, rather I have an alias ‘gen’ for it.

TabTab supports user-defined aliases (you might have an alias ‘g’ for ‘script/generate’) via its ~/.tabtab.yml configuration file. See Aliases section below.

INSTALL:

  • sudo gem install tabtab

SETUP:

Run ‘install_tabtab` to install the completions that come built-in with the tabtab RubyGem, and within any other RubyGem, at the time of execution.

In your .bash_profile add:

source ~/.tabtab.bash

Future shells:

In your .fish_profile add:

source ~/.tabtab.fish

In your .ksh_profile add:

source ~/.tabtab.ksh

Re-running install_tabtab

You never need to run this command again after new RubyGem updates, but you do need to re-run this command each time you:

  • install a RubyGem that contains a tabtab_definitions.rb file for the first time (once install_tabtab has discovered the gem it will automatically pick up changes to the definitions in future gem versions)

  • want to add completions defined in a local file, rather than bundled in a gem (see Local Files below)

  • want to add completions for ‘external’ apps (see External below)

Each time you run install_tabtab the above ~/.tabtab.sh file(s) will be updated. You then need to restart your shell(s) to load the new completions, or run the above command explicitly.

RubyGems: Completion Definitions in RubyGems

By default, install_tabtab automatically finds any completion definitions bundled in RubyGems. The tabtab gem itself includes lots of definitions for applications such as rails, script/server, rake, gem, cucumber, github, and others.

In time, the development and deployment of each script may be adopted by the relevant project owners and bundled in their RubyGems rather than the tabtab gem itself.

You can bundle tabtab defintions for 1 or more applications in your own RubyGems. Typically, you will include tabtab defintions for the applications that you bundle with your gem. For example, the global ‘rails’ app and the per-rails project ‘script/server’ applications are bundled in the ‘railties’ gem. Rails would ideally include its tabtab definitions in this railties gem and the core Rails team would maintain/update the tabtab definition whenever the actual applications are updated. This way the application and the tabtab defintions will be consistent.

To include tabtab definitions in a RubyGem, you need to add a ‘tabtab_definitions.rb’ file somewhere in your gem. The suggested location is ‘lib/tabtab_definitions.rb’.

Local Files: Completion Definitions in Local Files

You can create and develop your own tabtab definitions for your apps or other people’s apps. Its really simple. Really easy.

  1. Create a file

  2. Include the definition code:

    TabTab::Definition.register('TARGET_APP_NAME') do |c| ... end
    
  3. Create a ~/.tabtab.yml file

  4. Add file configuration:

    files:
      "/Users/drnic/.tabtab_definitions/TARGET_APP_NAME.rb": TARGET_APP_NAME
    

You can include multiple definitions in a single file (using multiple ‘register(app_name)’ calls) and including a list of file names:

files:
  "/Users/drnic/.tabtab_definitions/my_definitions.rb": curl, mongrel_rails

You can include multiple files for multiple applications:

files:
    "/Users/drnic/.tabtab_definitions/curl.rb": curl
    "/Users/drnic/.tabtab_definitions/mongrel.rb": mongrel_rails

External: Completions for Applications without Definitions

As a bonus feature, tabtab supports autocompletions for existing applications without requiring a definition file. Neat! For example, the ‘curl’ command has lots of flag options. For shits-and-giggles, run ‘curl -h’. There are currently 56 different flag options. Fifty six!

It would be so sweet to instantly get an autocompletion for these flags without any coding effort; no definition file.

It is as simple as adding an “external” key to your ~/.tabtab.yml config file:

external:
  - curl
  - cap

Hide small flags

Many applications have command-line flag arguments, such as –port (long) or -p (short). The short form is useful if you are typing them yourself - they are two characters long. If you are using auto-completions, it may not be meaningful nor useful to see the short form flags.

You can disable short-form versions of flags via the ~/.tabtab.yml config file. Add the following line:

shortflags: disable

Aliases: Reusing Completions against local Aliases

TabTab supports user-defined aliases (you might have an alias ‘g’ for ‘script/generate’) via its ~/.tabtab.yml configuration file.

Use the “aliases” key to your ~/.tabtab.yml config file:

aliases:
  gen: script/generate
  console: script/console
  ss: script/server
  server: script/server

SHELL SUPPORT:

TabTab is shell agnostic. If your shell supports auto-completions, then it should be possible to hook in the TabTab definitions.

Yet it initially only supports Bash. Why? I know how to hook bash’s completion mechanism into an external app.

For bash’s complete command, the -C option allows me to specify an external command that will handle the request for completion options. Below, ‘tabtab’ is the external completion command and it will be used for the github command.

complete -o default -C tabtab github

For bash, when tabtab is executed, its last 3 arguments are: command name, current token, previous token. It also passes the entire current command line via $COMP_LINE.

I know that fish and ksh and other shells have sexy completion support. But I cannot figure out how to configure them to delegate to tabtab.

Fish’s complete only seems to allow calls to fish functions, and not external apps. But perhaps the fish function can then delegate to tabtab? Perhaps I just need a _tabtab fish function, which proxies the request through to the tabtab command?

If you know these things, or are proficient enough in your preferred shell to poke around and help out please let me know. I have some cucumber scenarios ready and waiting for your help.

REQUIREMENTS:

Currently, tabtab works with the bash shell, though it is designed to be shell agnostic.

KNOWN ISSUES/LIMITATIONS & ARCHITECTURE OVERVIEW:

Pride and ego mean this section goes at the bottom of the readme… this section includes the known issues and limitations of TabTab due to current architectural decisions, the aim of being shell agnostic whilst initially only implementing TabTab for bash, and DSL challenges in attempting to support all possible command-line app APIs with a sexy, small DSL for describing auto-completions.

Cannot support rake/sake/cap colon-separated completions

This will be fixed soon. I only just discovered this deficiency.

Auto-completion for rake/sake/cap - when I get it working - will allow you to tab through the namespacing across the colons. E.g. ‘rake db:test:’ and tabtab will show all the tasks within this namespace. That’s what it should do. That’s what all the current implementations of auto-completion do before tabtab. But with tabtab (currently) it can’t do it. I suck. But here’s the reason and the fix.

To fix it requires rewriting the guts of the tabtab application to use $COMP_LINE environment variable instead of the current, previous token.

Currently tabtab works by passing the current and previous token in the current command line string around. But bash isn’t coping with colon’s correctly. If you tabtab after a colon it just ignores the characters before it.

So, I’ll abandon the whole mechanism of (current, previous) tokens and use the $COMP_LINE variable and manually parse it into tokens via the Shellwords.shellwords function.

Nested intelligent lists

Some applications may want to have multiple auto-completed values in a row, with the 2nd value’s list being dependent upon the value of the first. For example, the ‘rubyforge add_release’ command takes four more arguments: group_id, package_id, release_name, userfile. The first two - group_id and package_id - are from known lists of values. The values available for package_id are dependent upon the group_id value. This is a nested list, and currently TabTab probably can’t do them.

Currently intelligent, dynamic lists of completable values are defined via ‘default’ blocks - either blocks passed to #flag or #command calls, or via #default calls. I guess to support nested lists you’d need nested default blocks. Not sure.

Or perhaps we need specific syntax to support commands with 2+ arguments.

TabTab::Definition.register('rubyforge') do |c|
  c.command(:add_release, 1) { list_of_rubyforge_group_names }
  c.command(:add_release, 2) { |group_id| list_of_rubyforge_package_names_in(group_id) }
end

In this pseudo code we redefine the :add_release command for each nested argument. The 2nd+ argument is passed the completed values from the previous arguments of the command so they can filter their lists as appropriate. Might work.

Of course, the second argument (1, 2, …) would be optional and only required if you were trying to describe a nested list of interdependent values.

SOURCE:

The source for this project is at github.com/drnic/tabtab

Using git, you can clone the project, run its tests and install it:

git clone git://github.com/drnic/tabtab.git
cd tabtab
rake
rake install_gem

ACKNOWLEDGEMENT and SPONSOR

I wrote most of this at Railscamp #4 in Adelaide, between 13th and 17th of November 2008. Railscamps are so awesome. They are a conference without the conference part. Coding, lightning talks, alcohol, and guitar hero. Thanks to all the people I demo’d tabtab during its development, for their thoughts on the final DSL, and finally for helping give TabTab a name.

After Railscamp, I kept coding tabtab instead of doing proper work. Therefore this project has a sponsor:

  • Mocra - the premier iPhone/Rails consultancy

  • mocra.com

Like TabTab? Don’t donate money, just hire us for your next Rails or iPhone party.

LICENSE:

(The MIT License)

Copyright © 2008 Dr Nic Williams (drnicwilliams.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.