Experiment and Routine

Musings of a part-time hacker

Command-T Optimized

Command-T is a vim plugin for rapid file navigation using fuzzy path matching. It is one of the few plugins I find truly essential. Although profoundly useful, I find that there are a few customizations that make it unbeatable.

In order to make Command-T a little more friendly, I do the following:

  • Make all Command-T actions relative the current git repo root
  • Use the .gitignore to filter the file list
  • Setup a collection of maps for starting Command-T in subfolders

Command-T Relative the Git Root

The first thing I do in order to make working with Command-T more friendly is to make it work from the root of the current git repo. Reaching for Commend-T implies that I am in a project of some non trivial size, ie anything with one or more subfolders, then I can pretty safely assume I will have it in a git repo.

The following functions allow me to move the current working directory to the root of the git repo before starting up Command-T.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function! Git_Repo_Cdup() " Get the relative path to repo root
    "Ask git for the root of the git repo (as a relative '../../' path)
    let git_top = system('git rev-parse --show-cdup')
    let git_fail = 'fatal: Not a git repository'
    if strpart(git_top, 0, strlen(git_fail)) == git_fail
        " Above line says we are not in git repo. Ugly. Better version?
        return ''
    else
        " Return the cdup path to the root. If already in root,
        " path will be empty, so add './'
        return './' . git_top
    endif
endfunction

function! CD_Git_Root()
    execute 'cd '.Git_Repo_Cdup()
    let curdir = getcwd()
    echo 'CWD now set to: '.curdir
endfunction
nnoremap <LEADER>gr :call CD_Git_Root()<cr>

Using the .gitignore

By default, Command-T will use the vim wildignore setting to filter files from the Command-T list. This means that there will only be one global filter used in every vim session to filter things out. This can be an issue when working with things like an Octopress blog (like this one), a Rails 3.1 app, or anything else that has build output or other numerous files that will be in the gitignore.

I was able to track down a vim plugin create by Adam Bellaire that can parse a gitignore and use it to define the vim wildignore setting. His original script can be found here, but since it only included a single function I just included that directly in my vimrc. What follows is my version of Adam’s original script, wrapped up as a vim function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
" Define the wildignore from gitignore. Primarily for CommandT
function! WildignoreFromGitignore()
    silent call CD_Git_Root()
    let gitignore = '.gitignore'
    if filereadable(gitignore)
        let igstring = ''
        for oline in readfile(gitignore)
            let line = substitute(oline, '\s|\n|\r', '', "g")
            if line =~ '^#' | con | endif
            if line == '' | con  | endif
            if line =~ '^!' | con  | endif
            if line =~ '/$' | let igstring .= "," . line . "*" | con | endif
            let igstring .= "," . line
        endfor
        let execstring = "set wildignore=".substitute(igstring,'^,','',"g")
        execute execstring
        echo 'Wildignore defined from gitignore in: '.getcwd()
    else
        echo 'Unable to find gitignore'
    endif
endfunction
nnoremap <LEADER>cti :call WildignoreFromGitignore()<cr>
nnoremap <LEADER>cwi :set wildignore=''<cr>:echo 'Wildignore cleared'<cr>

Subfolder Focused Command-T Mappings

This last piece was inspired by a Destroy All Software screencast by Gary Bernhardt. I highly recommend anything by Gary Bernhardt.

This configuration involves setting up a series of normal mode mappings in my vimrc to start Command-T in the context of a subfolder. Most of the work I have been doing lately has been on Rails apps with heavy javascript. As a result of the consistent directory structure of Rails, this works great, but YMMV if you tend to work on different types of projects.

1
2
3
4
5
6
7
8
nmap <leader>gv :call CD_Git_Root()<cr>\|:CommandT app/views<cr>
nmap <leader>gc :call CD_Git_Root()<cr>\|:CommandT app/controllers<cr>
nmap <leader>gm :call CD_Git_Root()<cr>\|:CommandT app/models<cr>
nmap <leader>gj :call CD_Git_Root()<cr>\|:CommandT app/assets/javascripts<cr>
nmap <leader>gy :call CD_Git_Root()<cr>\|:CommandT app/assets/stylesheets<cr>
nmap <leader>gs :call CD_Git_Root()<cr>\|:CommandT spec<cr>
nmap <leader>gt :call CD_Git_Root()<cr>\|:CommandT app/assets/templates<cr>
nmap <leader>gl :call CD_Git_Root()<cr>\|:CommandT app/lib<cr>

All in all I am very happy with where I am at with file navigation. I barely have to think at this point to get to a file, and that’s the way I like it. You can checkout the full details of this and my other vim configs in my vimrc.

Room for Improvement

The one major issue I have with my current Command-t usage outlined above is the need to explicitly fire the Command-t from gitignore function. I would like to set this to fire based on an autocommand or by hooking into vims cd and trigger it automatically. I would imagine it would be necessary to cache the current project in order to avoid doing this all the time. As of now I haven’t felt a strong enough pull to figure this out, but I expect I will revisit it someday.

Comments