1

Problem

I am trying to create a more dynamic mapping for vimgrep. Many project directories I work in don't have consistent names for directories in which source/test code lives (ie. test vs. tst). I would like to run a command when I first begin a vim session at the root of the project to change the mapping for vimgrep to limit my searching to only user-provided directories.

The problem is that I am having difficulty creating a mapped expression to provide this functionality.

Desired workflow:

$ (.../proj_root) vim .

[in vim session]
:<user-command> src test

:<user-command> src test would execute the following and create the mapping:

:nn <c-\> :vimgrep /<c-r><c-w>/j src/** test/** <bar>:cw<cr>

Attempted solution:

{.vimrc}

command! -nargs=* ExeVpaths call ExeVpaths(<f-args>)
function! ExeVpaths(...)
    if len(a:000) == 0
        nn <expr> <c-\> printf(":vimgrep /<c-r><c-w>/j %s <bar>:cw<cr>", "*/**")
    else
        let l:vpaths = join(a:000, "/** ") . "/** "
        nn <expr> <c-\> printf(":vimgrep /<c-r><c-w>/j %s <bar>:cw<cr>", l:vpaths)
    endif
endfunction

Solution error

The above function executes with no errors for the following two examples:

(1) :ExeVpaths

(2) :ExeVpaths src test

However, using <c-\> after having run (2) fails with the two error messages:

E121: Undefined variable: l:vpaths
E116: Invalid arguments for function printf(":vimgrep /^R^W/j %s |:cw^M", l:vpaths)

This seems to be a scoping issue, seeing l:vpaths appears to be undefined. But that does not make sense to me because the variable is defined right above it.

Comments and critiques are appreciated.

3
  • Tip 1: Clearly l:vpaths is being evaluated when the mapping is executed and that won't work. You only have access to l:vpaths and its contained value when the function is running. Include the value itself in the mapping...not the string 'l:vpaths'.
    – B Layer
    Commented May 31, 2021 at 19:25
  • (Alternatively, if it is something you need to be evaluated when the mapping is keyed than you'd need to use a global variable...or get it from a function call.)
    – B Layer
    Commented May 31, 2021 at 19:35
  • Welcome to Vi and Vim! You might be interested in How do I use a variable or return value in option, command, or mapping?.
    – D. Ben Knoble
    Commented Jun 1, 2021 at 22:11

1 Answer 1

1

The problem you're having is that l:vpaths is no longer in context when the mapping is invoked, and the printf() will only be evaluated at invocation time.

Using a global variable (of buffer variable) would work to fix the issue here, since it would be available after the function has returned.

But I'm guessing what you want to do here is incorporate l:vpaths into the mapping that you define in the function.

You can easily do so by having your printf() produce the whole nn command and then use execute on it.

function! ExeVpaths(...)
    if len(a:000) == 0
        let l:vpaths = "*/**"
    else
        let l:vpaths = join(a:000, "/** ") . "/** "
    endif
    execute printf('nn <c-\> :vimgrep /<c-r><c-w>/j %s <bar>:cw<cr>', l:vpaths)
endfunction

Note that you don't need the <expr> anymore, since you're evaluating the printf() expression at the time when you're creating the mapping.

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.