0

Is there a simple way to setup a binding that will :vimgrep up until a tags file is found?

I'm curious because I can do set tags=tags;/ to do a similar search to define tags, but am not quite sure how to transfer this to a different use case.

0

I believe you're looking for a function that will return the directory where the tags file is located, by performing the same kind of lookup up the tree.

You can use findfile() or finddir() for this purpose, both will use a lookup method similar to the one used in the 'tags' option. See :help file-searching for more details on file searching syntax and a more complete list of functions and options that support that syntax.

For example, you can find the tags file up the tree using:

:echo findfile('tags', '.;')

You can get the path to the directory where the tags file using fnamemodify():

:echo fnamemodify(findfile('tags', '.;'), ':p:h')

And you can use that expression to execute a :vimgrep starting on that directory. You'll need :execute to build the command from a string:

:execute 'vimgrep /something/ '
    \ . fnamemodify(findfile('tags', '.;'), ':p:h')
    \ . '/**'

You can easily adapt that to write a function or user-defined command starting the search at the expected directory.

UPDATE: In order to bind a key combination to searching from the directory where your tags file is, you can either use :execute or create an <expr> mapping.

For example:

nnoremap <Leader>s
    \ :execute "vimgrep /"
    \ . escape(input('/'), '/') . "/ "
    \ . fnamemodify(findfile('tags', '.;'), ':p:h') 
    \ . "/**"<CR>

The final <CR> executes the Ex command right away. Input is being escaped so that / characters are allowed.

An alternative, using <expr>, would be:

nnoremap <expr> <Leader>s
    \ ":vimgrep /" . escape(input('/'), '/') . "/ "
    \ . fnamemodify(findfile('tags', '.;'), ':p:h') 
    \ . "/**\<CR>"

This time we're using a string with : so it enters Ex mode first. We're also including the final <CR> inside the string with a "\<CR>" (using "\r" would also be possible, it's equivalent.)

Either approach would do what you want.

UPDATE 2: If you're interested in searching relative to where you found the tags file in specific, then consider using the tagfiles() function to get the path of the one you're using.

Something like this would work:

nnoremap <Leader>s
    \ :execute "vimgrep /"
    \ . escape(input('/'), '/') . "/ "
    \ . fnamemodify(tagfiles()[0], ':p:h') 
    \ . "/**"<CR>
  • Awesome, this is exactly the direction that I was looking for. When using <expr> to be able to map this to a key combination and input "something" in the middle, would I also have to use execute? – ThoseKind Oct 30 '19 at 13:48
  • @ThoseKind It depends... Your question is a bit vague regarding what you actually want to have in the end. If you're able to explain that in more details (example: a mapping that will prompt for a search term/look up the word under the cursor using vimgrep on all the *.c or *.h files starting up the tree where the tags file is found) then you might get a more concrete answer... – filbranden Oct 30 '19 at 14:13
  • CtrlP allows you to define "root" files from which something like a tags search can start from. Using this idea, I want to do something along these lines without plugins, as a simple mapping to "grep from where the nearest tags file is found searching upwards", but allow input along the way. Something like: nnoremap <expr> <Leader>s <esc>:exec ":vimgrep /" . input("") . "/ " . fnamemodify(findfile('tags', ';'), ':p:h') . "/**" This works if I just put the :exec ... part as a command, but I can't seem to get it to work for mapping to a key combination. – ThoseKind Oct 30 '19 at 14:18
  • I have a habit of being vague apparently, but my question is: :exec ":vimgrep /" . input("") . "/ " . fnamemodify(findfile('tags', ';'), ':p:h') . "/**" works exactly as I want when input as a command. But I am having trouble mapping this to a key binding. – ThoseKind Oct 30 '19 at 14:27
  • 1
    Ahh, turns out I don't need the :execute part if I am mapping with <expr>. So the resulting mapping is: noremap <expr> <Leader>s "<ESC>:vimgrep /" . input("") . "/ " . fnamemodify(findfile('tags', ';'), ':p:h') . "/**<ESC>". Thanks for your help! – ThoseKind Oct 30 '19 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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