Godot scripts sometimes contain lines like:

extends "res://cars/driveable.gd"

If my project is in c:/project, then that line refers to the path c:/project/cars/car_base.gd.

How can I make gf work with this root substitution scheme?

I would expect this to work, but it does not:

let &includeexpr = "substitute(v:fname, 'res:/', substitute(fnamemodify(findfile('project.godot', '.;'), ':h'), '\\', '/', 'g'), '')"

As a test, I can't get res:// paths to be affected by 'includeexpr' at all:

let &includeexpr = "substitute(v:fname, '.*', '~/.vimrc', '')"

Doing gf on my res:// line edits "res://cars/driveable.gd" (a nonexistent file, but no E447). If I change res: to res_, then that 'includeexpr' works. So it seems like vim thinks res:// is a real filepath and doesn't care that it doesn't exist.

I saw the question open salt://whatever as somedir/whatever and it seems close, but findfile("project.godot", ".;") returns res://cars/project.godot inside my BufReadCmd instead of the actual path. The returned path should be c:/code/project/project.godot and definitely shouldn't have the "cars" directory in it. (Neither resolve() nor expand() are helpful.)

I have netrw disabled, but I get similar results with gvim --clean.


1 Answer 1


For some reason (possibly Netrw integration), Vim seems to treat all paths that start with a scheme-like foo:// as legitimate paths so includeexpr, which is invoked when the path can't be found, is never invoked. If these tests are not enough, one can test this empirically with this file:

" filename.ext

" no netrw, no vimrc, no defaults.vim
$ vim -Nu NONE filename.ext
:1normal gf
<opens buffer for patapouf://sdsgifd/sdjfghsd/sdgfisd>
:2normal gf
E447: Can't find file "sdsgifd/sdjfghsd/sdgfisd" in 'path

This is not a satisfying situation, IMO, which creates similar problems in other filetypes (./foo/bar should resolve to ./foo/bar.ext and ./foo/bar/index.ext in JavaScript but only the former is possible because Vim thinks ./foo/bar/ is a legitimate path).

The workaround I've used is to map gf and friends to a custom function:

nnoremap <silent> <buffer> gf      :<C-u>call GF('n', 0, 0, v:count1)<CR>
nnoremap <silent> <buffer> <C-w>f  :<C-u>call GF('n', 1, 0, v:count1)<CR>
nnoremap <silent> <buffer> <C-w>gf :<C-u>call GF('n', 1, 1, v:count1)<CR>
xnoremap <silent> <buffer> gf      <C-c>:call GF('v', 0, 0, v:count1)<CR>
xnoremap <silent> <buffer> <C-w>f  <C-c>:call GF('v', 1, 0, v:count1)<CR>
xnoremap <silent> <buffer> <C-w>gf <C-c>:call GF('v', 1, 1, v:count1)<CR>

function! GF(mode, split, tab, count) abort
    if a:mode == "n"
        let fname = expand('<cfile>')
        let old_reg = getreg("v")
        normal! gv"vy
        let fname = getreg("v")
        call setreg("v", old_reg)

    let transformed_name = SomeFunction(fname)    " your implementation here
    let found_file = findfile(transformed_name, &path, a:count)

    if empty(found_file)
        echohl Error
        echo "E447: Can't find file \"" . fname . "\" in path"
        echohl None
        return 0

    let cmds = {
                \ "00": "silent edit ",
                \ "10": "silent split ",
                \ "11": "silent tab split ",
                \ }

    execute cmds[a:split . a:tab] . found_file

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.