I work a lot in the twig template language, and make use of path variables. For example, an include might look like

{% include "@my_path_var/my_folder/my-template.twig" %}

The @my_path_var is shorthand for a full filesystem path, e.g. /Users/me/Sites/project/twig/patterns/.


I would like to be able to follow these paths using gf.


Is there a way to enable this behavior, using Vim's native path variables, or some other Vim configuration? Or would this need to be a plugin?


1 Answer 1


gf and CTRL-W_F are able to search for path under directories listed in &path variable. Alas, I suspect that @my_path_var is some kind of variable related to your current twig (?) project.

Vim can't understand it natively. It has no idea @my_path_var means something, nor what it means (for what we know, it could have been %var%, {var}, @@var@@...)

So, you'll have to implement your own version of gf.

  1. parse the line under the cursor, either with expand('<cfile>)', or analyse the result of expand('<cWORD>') if the first doesn't return something that can be exploited because of the @

  2. split the path obtained to extract the @varname part, resolve it to build the finalpathname

  3. and eventually open if with exe 'e '.finalpathname or anything more advanced that'll reuse windows already opened (I have such a function in lh-vim-lib)


As @Peter suggested (I did completely forget about that), we don't need to define a new mapping. We just have to set the options 'includeexpr' -- and 'include' which is not mandatory to answer the need you've expressed

As I'm the lazy kind of guy regarding option evaluation, I'd use a function from my toolbox library:

" To be defined in a ~/.vim/ftplugin/twig.vim file

" First we specify how include lines are recognized
setlocal inc={\%\ include

" then, we specify how variables are evaluated
setlocal includeexpr=substitute(v:fname,'\\v\\@(\\k+)','\\=lh\#option\#get(submatch(1))','g')

Note: this version, doesn't properly notifies when a pathname isn't found: the call to lh#option#get() should be encapsulated to throw something if lh#option#is_unset(lh#option#get(......)) evaluates to true. A simplified version could be defined with something like:

" to be defined in ~/.vim/autoload/my/twig.vim
function my#twig#get_var(name) abort " untested!
    let scopes = filter([b:, g:, t:], 'has_key(v:val, a:name)')
    if empty(scopes)
        throw "No variable named '".a:name. "' known in b:, g:, nor t:"
    return scopes[0][a:name]

For visual modes mappings, I can't find anything with :h v_CTRL-W_f, yet it seems to work correctly. In case this is an artefact of my configuration, visual mode mappings could be defined with:

" edit the buffer in the current window, 
" or jump to the first window where the buffer is already displayed
xnoremap <silent> gf <c-\><c-n>:call lh#buffer#jump(
           \ substitute(lh#visual#selection(), '\v\@(\k+)', '\=lh#option#get(submatch(1))', 'g'), 
           \ 'e')<cr>

xnoremap <silent> <c-w>f <c-\><c-n>:call lh#buffer#jump(
           \ substitute(lh#visual#selection(), '\v\@(\k+)', '\=lh#option#get(submatch(1))', 'g'), 
           \ 'sp')<cr>

xnoremap <silent> <c-w>v <c-\><c-n>:call lh#buffer#jump(
           \ substitute(lh#visual#selection(), '\v\@(\k+)', '\=lh#option#get(submatch(1))', 'g'), 
           \ 'vert sp')<cr>

Last thing, you'll then need a way to define your variables in the scope of your current project.

  • 1
    May also want to mention 'incudeexpr'. See :h 'incudeexpr'. Commented May 21, 2018 at 18:10
  • @PeterRincker, you're right, I did completely forget about that. Actually, thanks to 'includeexpr', fixing the 3 (/6?) mapping is done with a single instruction. It's much simpler. Thanks! Commented May 22, 2018 at 1:45

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.