I have an abbrevation for running git blame on the current file, but it fails in the presence of symlinks.

Here's the abbreviation

cnoreabbrev gbl ! ( cd %:h ; exec git blame %:t )

My .vimrc is a symlink to ~/config/vim/vimrc.

$ ls -l .vimrc | cut -d' ' -f8-
16 Jan 29 20:54 .vimrc -> config/vim/vimrc

When I open my .vimrc from the home directory, it doesn't show me the git blame because I'm not inside the project root.

Is there something kind of like realpath(3) that I can use from the vim side to get a canonical path to the current file? I'd like to replace %:h with a canonical path to the directory containing the current file and %:t the corresponding file name.


1 Answer 1


You could try this abbreviation:

cnoreabbrev <expr> gbl getcmdtype() == ':' && getcmdpos() == 4 ? '! ( cd '.fnamemodify(resolve(expand('%')), ':h').' ; git blame '.fnamemodify(resolve(expand('%')), ':t').' )' : 'gbl'

It uses the resolve() function to resolve the symbolic links, and fnamemodify() to extract the head of the path as well as the filename component:

fnamemodify(resolve(expand('%')), ':h')    → head of the path
fnamemodify(resolve(expand('%')), ':t')    → tail of the path

Also, it makes sure that your abbreviation is only expanded on a regular Ex command line (getcmdtype() == ':'), and only if you're at the very beginning of the line (getcmdpos() == 4).

If you wanted the abbreviation to be expanded anywhere on the command line, you could remove the 2nd condition:

cnoreabbrev <expr> gbl getcmdtype() == ':' ? '! ( cd '.fnamemodify(resolve(expand('%')), ':h').' ; git blame '.fnamemodify(resolve(expand('%')), ':t').' )' : 'gbl'
  • Why is the 4 in getcmdpos(4) ... uh ... 4? Is it 1 for the : and 3 for the gbl? Commented Feb 3, 2017 at 5:20
  • @GregoryNisbet 4 stands for the position of the cursor on the command line. It's expressed as a byte count. When you type gbl at the beginning of the line, the next byte after the cursor is the 4th, because the weight of gbl in bytes is 3. If you don't like this condition, you could replace it with getcmdline() ==# 'gbl'. Commented Feb 3, 2017 at 5:28
  • 1
    I don't have a problem with the 4, I was just trying to figure out whether it was a magic constant that meant "the first command" or not. lt's neat and only somewhat terrifying that you can define a command/alias/abbrevation that can inspect its calling context like that. Commented Feb 3, 2017 at 5:41
  • Yes, as soon as you pass the argument <expr> to :cnoreabbrev, you can use the functions getcmdline(), getcmdtype(), getcmdpos() to check the contents of the command line, its type, and the position of the cursor. Then, depending on the result, you can expand your abbreviation differently. Commented Feb 3, 2017 at 5:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.