gf doesn't work for absolute links used in Azure DevOps yaml files, p.e.

  - template: /devops/template/my-template.yml

(It only works without the leading slash.)

This is why I have created my custom function that tries first a gf and then tries guessing the file that is meant:

function! GoToFile()
        normal gf
        let path = expand("<cfile>")
        if path[0] == '/'

I have mapped it to gF:

nnoremap gF :call GoToFile()<CR>

This is just a workaround. Actually, I would like to map it directly to gf:

nnoremap gf :call GoToFile()<CR>

But then strange things happen (after using gf):

  • No syntax highlighting for the opened file.
  • Other open buffers disappear from my "buffer tabs" (no real tabs, I use ap/vim-buftabline)
  • The file name in the remaining "buffer tab" looks different now (d/t/template.yml instead of just template.yml)

Is something wrong with this approach? Do I end up in a loop? Shouldn't nnoremap prevent this? Is there a way to call the "original" gf (like \ls in bash to ignore aliases)?

  • The topic of calling the original version of a mapped command (like command in bash) came up a couple of days ago. Unfortunately, I can't find the question. The answer was that vim has no such feature. Hopefully, someone can post the link.
    – Friedrich
    Mar 7, 2023 at 20:56

2 Answers 2


Try to use normal! gf with exclamation mark in your function.

With normal gf (without exclamation mark) you most probably are calling the same function over and over again until vim breaks it.

See :h :normal

            If the [!] is given, mappings will not be used.
            Without it, when this command is called from a
            non-remappable mapping (|:noremap|), the argument can
            be mapped anyway.

Is something wrong with this approach?


When you do gf and Vim doesn't find the target file, it tries to use :help 'includeexpr', if it is set, as a last resort. It is expected to be an expression that transforms the given "path-like" string into an actual usable path.

In this case, it would look something like this:

" in after/ftplugin/yaml.vim
setlocal includeexpr=substitute(v:fname, '^\/', '', '')

which would let you use the native gf and gF and make this problem moot.

Now, includeexpr is supposed to return a valid path but it doesn't prescribe a way to do so. There are, actually, many situations where a substantial amount of work is required in order to obtain a valid path and that work can all be encapsulated in a function:

" in after/ftplugin/yaml.vim
function HandleAzurePaths(fname)
    " your code here
setlocal includeexpr=HandleAzurePaths(v:fname)

If your logic in lines 677-694 works, you should be able to fit it there and make it return x… without having to deal with "real" gf and "fake" gf.

FWIW, I use such a function for JavaScript, where I reimplemented Node's module resolution algorithm in about 100 lines so don't feel constrained, here.

  • Thanks for your explanation! While Maxim provided the quick and easy solution to what I wanted to do in the first place, this one seems to be "vimic" way of doing it. Probably I'll change my config to this one day.
    – MaxGyver
    Mar 8, 2023 at 7:34
  • Well, that's not really how I would describe the difference between the two answers but oh well
    – romainl
    Mar 8, 2023 at 8:56

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.